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