blob: 06df05a0bca5c81d40a66e9d91b1346a7e8eff13 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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.resource.ImageDescriptor;
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<>();
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(ImageDescriptor.createFromURLSupplier(true, () -> ElementTreeView.class.getResource("/icons/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<>(strings.values());
sortedList.sort(null);
}
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 = (tree, requestor, 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<>();
result.addAll(set);
result.sort((arg0, arg1) -> arg1.getValue().intValue() - arg0.getValue().intValue());
return result;
}
void updateTextView() {
final StringBuilder buffer = new StringBuilder();
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
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(() -> {
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) {
StringBuilder buf = new StringBuilder();
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();
}
}