/******************************************************************************* * Copyright (c) 2002, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM - Initial API and implementation *******************************************************************************/ package org.eclipse.core.tools.resources; import java.util.*; import org.eclipse.core.internal.dtree.AbstractDataTreeNode; import org.eclipse.core.internal.dtree.DataTreeNode; import org.eclipse.core.internal.resources.*; import org.eclipse.core.internal.watson.*; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.tools.*; import org.eclipse.jface.action.*; import org.eclipse.jface.text.*; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.IActionBars; /** * A spy view that shows detailed information about the workspace's element tree, * including space usage for the tree, and all resource metadata including markers, * sync info, and session properties. */ public class ElementTreeView extends SpyView implements IResourceChangeListener { class UpdateAction extends Action { class Counter implements Comparable<Counter> { int count = 1; String name; Counter(String name) { this.name = name; } void add() { count++; } @Override public int compareTo(Counter o) { return o.getCount() - count; } int getCount() { return count; } String getName() { return name; } } int layerCount; int markerCount; int markerMemory; int nodeCount; int nonIdenticalStrings; int phantomCount; int hiddenCount; int resourceCount; DeepSize sessionPropertyMemory; List<Counter> sortedList; int stringMemory; final Map<String, Counter> strings = new HashMap<String, Counter>(); int syncInfoCount; int syncInfoMemory; int teamPrivateCount; //tree memory includes memory for strings and child array int treeNodeMemory; Workspace workspace = (Workspace) ResourcesPlugin.getWorkspace(); UpdateAction() { super("Update view"); this.setToolTipText("Update"); this.setImageDescriptor(CoreToolsPlugin.createImageDescriptor("refresh.gif")); } void addToStringCount(String name) { if (name == null) return; //want to track the number of non-identical strings if (!DeepSize.ignore(name)) { nonIdenticalStrings++; //can't call sizeof because it will call isUnique again and weed out duplicates stringMemory += basicSizeof(name); //now want to count the number of duplicate equal but non-identical strings Counter counter = strings.get(name); if (counter == null) strings.put(name, new Counter(name)); else counter.add(); } } void analyzeStrings() { sortedList = new ArrayList<Counter>(strings.values()); Collections.sort(sortedList); } void analyzeTrees() { // count the number of layers and the number of nodes // at each layer ElementTree tree = SpySupport.getOldestTree(); for (this.layerCount = 0; tree != null; tree = tree.getParent()) { layerCount++; visit(tree); } } int basicSizeof(Map<?, ?> map) { if (map == null) return 0; //formula taken from BundleStats int count = (int) Math.round(44 + (16 + (map.size() * 1.25 * 4)) + (24 * map.size())); for (Map.Entry<?, ?> entry : map.entrySet()) { count += sizeof(entry.getKey()); count += sizeof(entry.getValue()); } return count; } int basicSizeof(MarkerAttributeMap<?> markerMap) { int count = DeepSize.OBJECT_HEADER_SIZE + 8;//object header plus two slots Object[] elements = SpySupport.getElements(markerMap); if (elements != null) { count += DeepSize.ARRAY_HEADER_SIZE + 4 * elements.length; for (int i = 0; i < elements.length; i++) count += sizeof(elements[i]); } return count; } int basicSizeof(MarkerInfo info) { int count = DeepSize.OBJECT_HEADER_SIZE + 24;//object plus slots count += sizeof(info.getType()); count += sizeof(info.getAttributes(false)); return count; } int basicSizeof(MarkerSet markerSet) { if (markerSet == null) return 0; int count = DeepSize.OBJECT_HEADER_SIZE + 8;//object size plus two slots IMarkerSetElement[] elements = SpySupport.getElements(markerSet); if (elements != null) { count += DeepSize.ARRAY_HEADER_SIZE + 4 * elements.length;//size of elements array object for (int i = 0; i < elements.length; i++) if (elements[i] != null) count += sizeof(elements[i]); } return count; } int basicSizeof(String str) { //String object has four slots, plus char[] object, plus two bytes per character return 16 + DeepSize.OBJECT_HEADER_SIZE + DeepSize.ARRAY_HEADER_SIZE + 2 * str.length(); } void countResources() { // count the number of resources resourceCount = 0; markerCount = 0; teamPrivateCount = 0; phantomCount = 0; hiddenCount = 0; syncInfoCount = 0; IElementContentVisitor visitor = new IElementContentVisitor() { @Override public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { ResourceInfo info = (ResourceInfo) elementContents; if (info == null) return true; resourceCount++; if (info.isSet(ICoreConstants.M_TEAM_PRIVATE_MEMBER)) teamPrivateCount++; if (info.isSet(ICoreConstants.M_PHANTOM)) phantomCount++; if (info.isSet(ICoreConstants.M_HIDDEN)) hiddenCount++; MarkerSet markers = info.getMarkers(); if (markers != null) markerCount += markers.size(); Map<QualifiedName, Object> syncInfo = SpySupport.getSyncInfo(info); if (syncInfo != null) syncInfoCount += syncInfo.size(); return true; } }; new ElementTreeIterator(workspace.getElementTree(), Path.ROOT).iterate(visitor); } void reset() { resourceCount = 0; teamPrivateCount = 0; phantomCount = 0; hiddenCount = 0; layerCount = 0; nodeCount = 0; treeNodeMemory = 0; stringMemory = 0; markerMemory = 0; syncInfoMemory = 0; sessionPropertyMemory = new DeepSize(); strings.clear(); DeepSize.reset(); sortedList = null; nonIdenticalStrings = 0; } @Override public void run() { super.run(); reset(); countResources(); analyzeTrees(); analyzeStrings(); updateTextView(); reset(); } int sizeof(AbstractDataTreeNode node) { int count = DeepSize.OBJECT_HEADER_SIZE;//empty object if (node instanceof DataTreeNode) { //count memory for data count += 4;//reference to data Object data = ((DataTreeNode) node).getData(); if (data instanceof ResourceInfo) { count += sizeof((ResourceInfo) data); } } //name count += 4;//reference to name //NOTE: space for name string is counted separately (see addToStringCount) //children count += 4;//reference to child array AbstractDataTreeNode[] children = node.getChildren(); if (children != null && !DeepSize.ignore(children)) { count += DeepSize.ARRAY_HEADER_SIZE + (4 * children.length);//object header plus slots } return count; } /** * All sizeof tests should go through this central method to weed out * duplicates. */ int sizeof(Object object) { if (object == null || DeepSize.ignore(object)) return 0; if (object instanceof String) return basicSizeof((String) object); if (object instanceof byte[]) return DeepSize.ARRAY_HEADER_SIZE + ((byte[]) object).length; if (object instanceof MarkerAttributeMap) return basicSizeof((MarkerAttributeMap<?>) object); if (object instanceof MarkerInfo) return basicSizeof((MarkerInfo) object); if (object instanceof MarkerSet) return basicSizeof((MarkerSet) object); if (object instanceof Integer) return DeepSize.OBJECT_HEADER_SIZE + 4; if (object instanceof Map) return basicSizeof((Map<?, ?>) object); if (object instanceof QualifiedName) { QualifiedName name = (QualifiedName) object; return 20 + sizeof(name.getQualifier()) + sizeof(name.getLocalName()); } // unknown -- use deep size return 0; } int sizeof(ResourceInfo resourceInfo) { //object header plus all slots int count = DeepSize.OBJECT_HEADER_SIZE + (11 * 4); //markers markerMemory += sizeof(resourceInfo.getMarkers()); //sync info syncInfoMemory += sizeof(SpySupport.getSyncInfo(resourceInfo)); //session properties sessionPropertyMemory.deepSize(SpySupport.getSessionProperties(resourceInfo)); if (resourceInfo.getClass() == RootInfo.class) { count += 4;//ref to property store } if (resourceInfo.getClass() == ProjectInfo.class) { count += 4 * 4;//four more slots } return count; } /** * Sorts a set of entries whose keys are strings and values are Integer * objects, in decreasing order by the integer value. */ private List<Map.Entry<Object, Integer>> sortEntrySet(Set<Map.Entry<Object, Integer>> set) { List<Map.Entry<Object, Integer>> result = new ArrayList<Map.Entry<Object, Integer>>(); result.addAll(set); Collections.sort(result, new Comparator<Map.Entry<Object, Integer>>() { @Override public int compare(Map.Entry<Object, Integer> arg0, Map.Entry<Object, Integer> arg1) { return arg1.getValue().intValue() - arg0.getValue().intValue(); } }); return result; } void updateTextView() { final StringBuffer buffer = new StringBuffer(); buffer.append("Total resource count: " + prettyPrint(resourceCount) + "\n"); buffer.append("\tTeam private: " + prettyPrint(teamPrivateCount) + "\n"); buffer.append("\tPhantom: " + prettyPrint(phantomCount) + "\n"); buffer.append("\tHidden: " + prettyPrint(hiddenCount) + "\n"); buffer.append("\tMarkers: " + prettyPrint(markerCount) + "\n"); buffer.append("\tSyncInfo: " + prettyPrint(syncInfoCount) + "\n"); buffer.append("Number of layers: " + layerCount + "\n"); buffer.append("Number of nodes: " + prettyPrint(nodeCount) + "\n"); buffer.append("Number of non-identical strings: " + prettyPrint(nonIdenticalStrings) + "\n"); int sessionSize = sessionPropertyMemory.getSize(); int totalMemory = treeNodeMemory + stringMemory + markerMemory + syncInfoMemory + sessionSize; buffer.append("Total memory used by nodes: " + prettyPrint(totalMemory) + "\n"); buffer.append("\tNodes and ResourceInfo: " + prettyPrint(treeNodeMemory) + "\n"); buffer.append("\tStrings: " + prettyPrint(stringMemory) + "\n"); buffer.append("\tMarkers: " + prettyPrint(markerMemory) + "\n"); buffer.append("\tSync info: " + prettyPrint(syncInfoMemory) + "\n"); buffer.append("\tSession properties: " + prettyPrint(sessionSize) + "\n"); //breakdown of session property size by class @SuppressWarnings("unchecked") List<Map.Entry<Object, Integer>> sortedEntries = sortEntrySet(sessionPropertyMemory.getSizes().entrySet()); for (Map.Entry<Object, Integer> entry : sortedEntries) { buffer.append("\t\t" + entry.getKey() + ": " + prettyPrint(entry.getValue().intValue()) + "\n"); } int max = 20; int savings = 0; buffer.append("The top " + max + " equal but non-identical strings are:\n"); for (int i = 0; i < sortedList.size() && sortedList.get(i).getCount() > 1; i++) { Counter c = sortedList.get(i); if (i < max) buffer.append("\t" + c.getName() + "->" + prettyPrint(c.getCount()) + "\n"); savings += ((c.getCount() - 1) * basicSizeof(c.getName())); } buffer.append("Potential savings of using unique strings: " + prettyPrint(savings) + "\n"); //post changes to UI thread viewer.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!viewer.getControl().isDisposed()) { IDocument doc = viewer.getDocument(); doc.set(buffer.toString()); viewer.setDocument(doc); } } }); } void visit(AbstractDataTreeNode node) { // if ("CVS".equals(node.getName())) { // System.out.println("here"); // } nodeCount++; addToStringCount(node.getName()); treeNodeMemory += sizeof(node); AbstractDataTreeNode[] children = node.getChildren(); for (int i = 0; i < children.length; i++) visit(children[i]); } void visit(ElementTree tree) { AbstractDataTreeNode node = org.eclipse.core.internal.dtree.SpySupport.getRootNode(tree.getDataTree()); visit(node); } } private IAction updateAction; // The JFace widget used for showing the Element Tree info. protected TextViewer viewer; /** * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) */ @Override public void createPartControl(Composite parent) { viewer = new TextViewer(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.WRAP | SWT.READ_ONLY); viewer.setDocument(new Document()); IActionBars bars = getViewSite().getActionBars(); final GlobalAction clearOutputAction = new ClearTextAction(viewer.getDocument()); clearOutputAction.registerAsGlobalAction(bars); final GlobalAction selectAllAction = new SelectAllAction(viewer); selectAllAction.registerAsGlobalAction(bars); IMenuManager barMenuManager = getViewSite().getActionBars().getMenuManager(); updateAction = new UpdateAction(); barMenuManager.add(updateAction); // Delete action shortcuts are not captured by the workbench // so we need our key binding service to handle Delete keystrokes for us this.viewer.getControl().addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.character == SWT.DEL) clearOutputAction.run(); } }); GlobalAction copyAction = new CopyTextSelectionAction(viewer); copyAction.registerAsGlobalAction(bars); bars.getToolBarManager().add(updateAction); bars.getToolBarManager().add(clearOutputAction); bars.updateActionBars(); // creates a context menu with actions and adds it to the viewer control MenuManager menuMgr = new MenuManager(); menuMgr.add(copyAction); menuMgr.add(clearOutputAction); Menu menu = menuMgr.createContextMenu(viewer.getControl()); viewer.getControl().setMenu(menu); // add the resource change listener ResourcesPlugin.getWorkspace().addResourceChangeListener(this); // populate the view with the initial data if (updateAction != null) updateAction.run(); } /** * @see org.eclipse.ui.IWorkbenchPart#dispose() */ @Override public void dispose() { super.dispose(); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); updateAction = null; } String prettyPrint(int i) { StringBuffer buf = new StringBuffer(); for (;;) { if (i < 1000) { String val = Integer.toString(i); //pad with zeros if necessary if (buf.length() > 0) { if (val.length() < 2) buf.append('0'); if (val.length() < 3) buf.append('0'); } buf.append(val); return buf.toString(); } if (i < 1000000) { String val = Integer.toString(i / 1000); //pad with zeros if necessary if (buf.length() > 0) { if (val.length() < 2) buf.append('0'); if (val.length() < 3) buf.append('0'); } buf.append(val); buf.append(','); i = i % 1000; continue; } buf.append(Integer.toString(i / 1000000)); buf.append(','); i = i % 1000000; } } /** * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent) */ @Override public void resourceChanged(IResourceChangeEvent event) { if (updateAction != null) updateAction.run(); } }