1 /******************************************************************************* 2 * Copyright (c) 2002, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM - Initial API and implementation 13 *******************************************************************************/ 14 15 package org.eclipse.core.tools.resources; 16 17 import java.util.*; 18 import org.eclipse.core.internal.dtree.AbstractDataTreeNode; 19 import org.eclipse.core.internal.dtree.DataTreeNode; 20 import org.eclipse.core.internal.resources.*; 21 import org.eclipse.core.internal.watson.*; 22 import org.eclipse.core.resources.*; 23 import org.eclipse.core.runtime.Path; 24 import org.eclipse.core.runtime.QualifiedName; 25 import org.eclipse.core.tools.*; 26 import org.eclipse.jface.action.*; 27 import org.eclipse.jface.text.*; 28 import org.eclipse.swt.SWT; 29 import org.eclipse.swt.events.KeyAdapter; 30 import org.eclipse.swt.events.KeyEvent; 31 import org.eclipse.swt.widgets.Composite; 32 import org.eclipse.swt.widgets.Menu; 33 import org.eclipse.ui.IActionBars; 34 35 /** 36 * A spy view that shows detailed information about the workspace's element tree, 37 * including space usage for the tree, and all resource metadata including markers, 38 * sync info, and session properties. 39 */ 40 public class ElementTreeView extends SpyView implements IResourceChangeListener { 41 42 class UpdateAction extends Action { 43 44 class Counter implements Comparable<Counter> { 45 int count = 1; 46 String name; 47 Counter(String name)48 Counter(String name) { 49 this.name = name; 50 } 51 add()52 void add() { 53 count++; 54 } 55 56 @Override compareTo(Counter o)57 public int compareTo(Counter o) { 58 return o.getCount() - count; 59 } 60 getCount()61 int getCount() { 62 return count; 63 } 64 getName()65 String getName() { 66 return name; 67 } 68 69 } 70 71 int layerCount; 72 int markerCount; 73 int markerMemory; 74 int nodeCount; 75 int nonIdenticalStrings; 76 int phantomCount; 77 int hiddenCount; 78 79 int resourceCount; 80 DeepSize sessionPropertyMemory; 81 List<Counter> sortedList; 82 int stringMemory; 83 84 final Map<String, Counter> strings = new HashMap<>(); 85 int syncInfoCount; 86 int syncInfoMemory; 87 int teamPrivateCount; 88 89 //tree memory includes memory for strings and child array 90 int treeNodeMemory; 91 Workspace workspace = (Workspace) ResourcesPlugin.getWorkspace(); 92 UpdateAction()93 UpdateAction() { 94 super("Update view"); 95 this.setToolTipText("Update"); 96 this.setImageDescriptor(CoreToolsPlugin.createImageDescriptor("refresh.gif")); 97 } 98 addToStringCount(String name)99 void addToStringCount(String name) { 100 if (name == null) 101 return; 102 //want to track the number of non-identical strings 103 if (!DeepSize.ignore(name)) { 104 nonIdenticalStrings++; 105 //can't call sizeof because it will call isUnique again and weed out duplicates 106 stringMemory += basicSizeof(name); 107 108 //now want to count the number of duplicate equal but non-identical strings 109 Counter counter = strings.get(name); 110 if (counter == null) 111 strings.put(name, new Counter(name)); 112 else 113 counter.add(); 114 } 115 } 116 analyzeStrings()117 void analyzeStrings() { 118 sortedList = new ArrayList<>(strings.values()); 119 Collections.sort(sortedList); 120 } 121 analyzeTrees()122 void analyzeTrees() { 123 // count the number of layers and the number of nodes 124 // at each layer 125 ElementTree tree = SpySupport.getOldestTree(); 126 for (this.layerCount = 0; tree != null; tree = tree.getParent()) { 127 layerCount++; 128 visit(tree); 129 } 130 } 131 basicSizeof(Map<?, ?> map)132 int basicSizeof(Map<?, ?> map) { 133 if (map == null) 134 return 0; 135 136 //formula taken from BundleStats 137 int count = (int) Math.round(44 + (16 + (map.size() * 1.25 * 4)) + (24 * map.size())); 138 139 for (Map.Entry<?, ?> entry : map.entrySet()) { 140 count += sizeof(entry.getKey()); 141 count += sizeof(entry.getValue()); 142 } 143 return count; 144 } 145 basicSizeof(MarkerAttributeMap<?> markerMap)146 int basicSizeof(MarkerAttributeMap<?> markerMap) { 147 int count = DeepSize.OBJECT_HEADER_SIZE + 8;//object header plus two slots 148 Object[] elements = SpySupport.getElements(markerMap); 149 if (elements != null) { 150 count += DeepSize.ARRAY_HEADER_SIZE + 4 * elements.length; 151 for (int i = 0; i < elements.length; i++) 152 count += sizeof(elements[i]); 153 } 154 return count; 155 } 156 basicSizeof(MarkerInfo info)157 int basicSizeof(MarkerInfo info) { 158 int count = DeepSize.OBJECT_HEADER_SIZE + 24;//object plus slots 159 count += sizeof(info.getType()); 160 count += sizeof(info.getAttributes(false)); 161 return count; 162 } 163 basicSizeof(MarkerSet markerSet)164 int basicSizeof(MarkerSet markerSet) { 165 if (markerSet == null) 166 return 0; 167 int count = DeepSize.OBJECT_HEADER_SIZE + 8;//object size plus two slots 168 IMarkerSetElement[] elements = SpySupport.getElements(markerSet); 169 if (elements != null) { 170 count += DeepSize.ARRAY_HEADER_SIZE + 4 * elements.length;//size of elements array object 171 for (int i = 0; i < elements.length; i++) 172 if (elements[i] != null) 173 count += sizeof(elements[i]); 174 } 175 return count; 176 } 177 basicSizeof(String str)178 int basicSizeof(String str) { 179 //String object has four slots, plus char[] object, plus two bytes per character 180 return 16 + DeepSize.OBJECT_HEADER_SIZE + DeepSize.ARRAY_HEADER_SIZE + 2 * str.length(); 181 } 182 countResources()183 void countResources() { 184 // count the number of resources 185 resourceCount = 0; 186 markerCount = 0; 187 teamPrivateCount = 0; 188 phantomCount = 0; 189 hiddenCount = 0; 190 syncInfoCount = 0; 191 IElementContentVisitor visitor = (tree, requestor, elementContents) -> { 192 ResourceInfo info = (ResourceInfo) elementContents; 193 if (info == null) 194 return true; 195 resourceCount++; 196 if (info.isSet(ICoreConstants.M_TEAM_PRIVATE_MEMBER)) 197 teamPrivateCount++; 198 if (info.isSet(ICoreConstants.M_PHANTOM)) 199 phantomCount++; 200 if (info.isSet(ICoreConstants.M_HIDDEN)) 201 hiddenCount++; 202 MarkerSet markers = info.getMarkers(); 203 if (markers != null) 204 markerCount += markers.size(); 205 Map<QualifiedName, Object> syncInfo = SpySupport.getSyncInfo(info); 206 if (syncInfo != null) 207 syncInfoCount += syncInfo.size(); 208 return true; 209 }; 210 new ElementTreeIterator(workspace.getElementTree(), Path.ROOT).iterate(visitor); 211 } 212 reset()213 void reset() { 214 resourceCount = 0; 215 teamPrivateCount = 0; 216 phantomCount = 0; 217 hiddenCount = 0; 218 layerCount = 0; 219 nodeCount = 0; 220 221 treeNodeMemory = 0; 222 stringMemory = 0; 223 markerMemory = 0; 224 syncInfoMemory = 0; 225 sessionPropertyMemory = new DeepSize(); 226 227 strings.clear(); 228 DeepSize.reset(); 229 sortedList = null; 230 nonIdenticalStrings = 0; 231 } 232 233 @Override run()234 public void run() { 235 super.run(); 236 reset(); 237 countResources(); 238 analyzeTrees(); 239 analyzeStrings(); 240 updateTextView(); 241 reset(); 242 } 243 sizeof(AbstractDataTreeNode node)244 int sizeof(AbstractDataTreeNode node) { 245 int count = DeepSize.OBJECT_HEADER_SIZE;//empty object 246 if (node instanceof DataTreeNode) { 247 //count memory for data 248 count += 4;//reference to data 249 Object data = ((DataTreeNode) node).getData(); 250 if (data instanceof ResourceInfo) { 251 count += sizeof((ResourceInfo) data); 252 } 253 } 254 //name 255 count += 4;//reference to name 256 //NOTE: space for name string is counted separately (see addToStringCount) 257 258 //children 259 count += 4;//reference to child array 260 AbstractDataTreeNode[] children = node.getChildren(); 261 if (children != null && !DeepSize.ignore(children)) { 262 count += DeepSize.ARRAY_HEADER_SIZE + (4 * children.length);//object header plus slots 263 } 264 return count; 265 } 266 267 /** 268 * All sizeof tests should go through this central method to weed out 269 * duplicates. 270 */ sizeof(Object object)271 int sizeof(Object object) { 272 if (object == null || DeepSize.ignore(object)) 273 return 0; 274 if (object instanceof String) 275 return basicSizeof((String) object); 276 if (object instanceof byte[]) 277 return DeepSize.ARRAY_HEADER_SIZE + ((byte[]) object).length; 278 if (object instanceof MarkerAttributeMap) 279 return basicSizeof((MarkerAttributeMap<?>) object); 280 if (object instanceof MarkerInfo) 281 return basicSizeof((MarkerInfo) object); 282 if (object instanceof MarkerSet) 283 return basicSizeof((MarkerSet) object); 284 if (object instanceof Integer) 285 return DeepSize.OBJECT_HEADER_SIZE + 4; 286 if (object instanceof Map) 287 return basicSizeof((Map<?, ?>) object); 288 if (object instanceof QualifiedName) { 289 QualifiedName name = (QualifiedName) object; 290 return 20 + sizeof(name.getQualifier()) + sizeof(name.getLocalName()); 291 } 292 // unknown -- use deep size 293 return 0; 294 } 295 sizeof(ResourceInfo resourceInfo)296 int sizeof(ResourceInfo resourceInfo) { 297 //object header plus all slots 298 int count = DeepSize.OBJECT_HEADER_SIZE + (11 * 4); 299 300 //markers 301 markerMemory += sizeof(resourceInfo.getMarkers()); 302 303 //sync info 304 syncInfoMemory += sizeof(SpySupport.getSyncInfo(resourceInfo)); 305 306 //session properties 307 sessionPropertyMemory.deepSize(SpySupport.getSessionProperties(resourceInfo)); 308 309 if (resourceInfo.getClass() == RootInfo.class) { 310 count += 4;//ref to property store 311 } 312 if (resourceInfo.getClass() == ProjectInfo.class) { 313 count += 4 * 4;//four more slots 314 } 315 return count; 316 } 317 318 /** 319 * Sorts a set of entries whose keys are strings and values are Integer 320 * objects, in decreasing order by the integer value. 321 */ sortEntrySet(Set<Map.Entry<Object, Integer>> set)322 private List<Map.Entry<Object, Integer>> sortEntrySet(Set<Map.Entry<Object, Integer>> set) { 323 List<Map.Entry<Object, Integer>> result = new ArrayList<>(); 324 result.addAll(set); 325 Collections.sort(result, (arg0, arg1) -> arg1.getValue().intValue() - arg0.getValue().intValue()); 326 return result; 327 } 328 updateTextView()329 void updateTextView() { 330 final StringBuilder buffer = new StringBuilder(); 331 buffer.append("Total resource count: " + prettyPrint(resourceCount) + "\n"); 332 buffer.append("\tTeam private: " + prettyPrint(teamPrivateCount) + "\n"); 333 buffer.append("\tPhantom: " + prettyPrint(phantomCount) + "\n"); 334 buffer.append("\tHidden: " + prettyPrint(hiddenCount) + "\n"); 335 buffer.append("\tMarkers: " + prettyPrint(markerCount) + "\n"); 336 buffer.append("\tSyncInfo: " + prettyPrint(syncInfoCount) + "\n"); 337 buffer.append("Number of layers: " + layerCount + "\n"); 338 buffer.append("Number of nodes: " + prettyPrint(nodeCount) + "\n"); 339 buffer.append("Number of non-identical strings: " + prettyPrint(nonIdenticalStrings) + "\n"); 340 341 int sessionSize = sessionPropertyMemory.getSize(); 342 int totalMemory = treeNodeMemory + stringMemory + markerMemory + syncInfoMemory + sessionSize; 343 buffer.append("Total memory used by nodes: " + prettyPrint(totalMemory) + "\n"); 344 buffer.append("\tNodes and ResourceInfo: " + prettyPrint(treeNodeMemory) + "\n"); 345 buffer.append("\tStrings: " + prettyPrint(stringMemory) + "\n"); 346 buffer.append("\tMarkers: " + prettyPrint(markerMemory) + "\n"); 347 buffer.append("\tSync info: " + prettyPrint(syncInfoMemory) + "\n"); 348 buffer.append("\tSession properties: " + prettyPrint(sessionSize) + "\n"); 349 //breakdown of session property size by class 350 List<Map.Entry<Object, Integer>> sortedEntries = sortEntrySet(sessionPropertyMemory.getSizes().entrySet()); 351 for (Map.Entry<Object, Integer> entry : sortedEntries) { 352 buffer.append("\t\t" + entry.getKey() + ": " + prettyPrint(entry.getValue().intValue()) + "\n"); 353 } 354 355 int max = 20; 356 int savings = 0; 357 buffer.append("The top " + max + " equal but non-identical strings are:\n"); 358 for (int i = 0; i < sortedList.size() && sortedList.get(i).getCount() > 1; i++) { 359 Counter c = sortedList.get(i); 360 if (i < max) 361 buffer.append("\t" + c.getName() + "->" + prettyPrint(c.getCount()) + "\n"); 362 savings += ((c.getCount() - 1) * basicSizeof(c.getName())); 363 } 364 buffer.append("Potential savings of using unique strings: " + prettyPrint(savings) + "\n"); 365 366 //post changes to UI thread 367 viewer.getControl().getDisplay().asyncExec(() -> { 368 if (!viewer.getControl().isDisposed()) { 369 IDocument doc = viewer.getDocument(); 370 doc.set(buffer.toString()); 371 viewer.setDocument(doc); 372 } 373 }); 374 } 375 visit(AbstractDataTreeNode node)376 void visit(AbstractDataTreeNode node) { 377 // if ("CVS".equals(node.getName())) { 378 // System.out.println("here"); 379 // } 380 nodeCount++; 381 addToStringCount(node.getName()); 382 treeNodeMemory += sizeof(node); 383 AbstractDataTreeNode[] children = node.getChildren(); 384 for (int i = 0; i < children.length; i++) 385 visit(children[i]); 386 } 387 visit(ElementTree tree)388 void visit(ElementTree tree) { 389 AbstractDataTreeNode node = org.eclipse.core.internal.dtree.SpySupport.getRootNode(tree.getDataTree()); 390 visit(node); 391 } 392 } 393 394 private IAction updateAction; 395 396 // The JFace widget used for showing the Element Tree info. 397 protected TextViewer viewer; 398 399 /** 400 * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) 401 */ 402 @Override createPartControl(Composite parent)403 public void createPartControl(Composite parent) { 404 405 viewer = new TextViewer(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.WRAP | SWT.READ_ONLY); 406 viewer.setDocument(new Document()); 407 408 IActionBars bars = getViewSite().getActionBars(); 409 410 final GlobalAction clearOutputAction = new ClearTextAction(viewer.getDocument()); 411 clearOutputAction.registerAsGlobalAction(bars); 412 413 final GlobalAction selectAllAction = new SelectAllAction(viewer); 414 selectAllAction.registerAsGlobalAction(bars); 415 416 IMenuManager barMenuManager = getViewSite().getActionBars().getMenuManager(); 417 updateAction = new UpdateAction(); 418 barMenuManager.add(updateAction); 419 420 // Delete action shortcuts are not captured by the workbench 421 // so we need our key binding service to handle Delete keystrokes for us 422 423 this.viewer.getControl().addKeyListener(new KeyAdapter() { 424 @Override 425 public void keyPressed(KeyEvent e) { 426 if (e.character == SWT.DEL) 427 clearOutputAction.run(); 428 } 429 }); 430 431 GlobalAction copyAction = new CopyTextSelectionAction(viewer); 432 copyAction.registerAsGlobalAction(bars); 433 434 bars.getToolBarManager().add(updateAction); 435 bars.getToolBarManager().add(clearOutputAction); 436 bars.updateActionBars(); 437 438 // creates a context menu with actions and adds it to the viewer control 439 MenuManager menuMgr = new MenuManager(); 440 menuMgr.add(copyAction); 441 menuMgr.add(clearOutputAction); 442 Menu menu = menuMgr.createContextMenu(viewer.getControl()); 443 viewer.getControl().setMenu(menu); 444 445 // add the resource change listener 446 ResourcesPlugin.getWorkspace().addResourceChangeListener(this); 447 448 // populate the view with the initial data 449 if (updateAction != null) 450 updateAction.run(); 451 } 452 453 /** 454 * @see org.eclipse.ui.IWorkbenchPart#dispose() 455 */ 456 @Override dispose()457 public void dispose() { 458 super.dispose(); 459 ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); 460 updateAction = null; 461 } 462 prettyPrint(int i)463 String prettyPrint(int i) { 464 StringBuilder buf = new StringBuilder(); 465 for (;;) { 466 if (i < 1000) { 467 String val = Integer.toString(i); 468 //pad with zeros if necessary 469 if (buf.length() > 0) { 470 if (val.length() < 2) 471 buf.append('0'); 472 if (val.length() < 3) 473 buf.append('0'); 474 } 475 buf.append(val); 476 return buf.toString(); 477 } 478 if (i < 1000000) { 479 String val = Integer.toString(i / 1000); 480 //pad with zeros if necessary 481 if (buf.length() > 0) { 482 if (val.length() < 2) 483 buf.append('0'); 484 if (val.length() < 3) 485 buf.append('0'); 486 } 487 buf.append(val); 488 buf.append(','); 489 i = i % 1000; 490 continue; 491 } 492 buf.append(Integer.toString(i / 1000000)); 493 buf.append(','); 494 i = i % 1000000; 495 } 496 } 497 498 /** 499 * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent) 500 */ 501 @Override resourceChanged(IResourceChangeEvent event)502 public void resourceChanged(IResourceChangeEvent event) { 503 if (updateAction != null) 504 updateAction.run(); 505 } 506 } 507