Commit | Line | Data |
---|---|---|
6e512b93 ASL |
1 | /******************************************************************************* |
2 | * Copyright (c) 2009 Ericsson | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
dfaf8391 FC |
10 | * Yann N. Dauphin (dhaemon@gmail.com) - Implementation |
11 | * Francois Chouinard (fchouinard@gmail.com) - Initial API | |
6e512b93 ASL |
12 | *******************************************************************************/ |
13 | ||
14 | package org.eclipse.linuxtools.lttng.ui.views.statistics; | |
15 | ||
dfaf8391 FC |
16 | import java.text.DecimalFormat; |
17 | import java.util.Arrays; | |
18 | import java.util.HashSet; | |
19 | import java.util.Set; | |
20 | ||
21 | import org.eclipse.jface.viewers.ColumnLabelProvider; | |
22 | import org.eclipse.jface.viewers.ITreeContentProvider; | |
23 | import org.eclipse.jface.viewers.TreeViewer; | |
24 | import org.eclipse.jface.viewers.TreeViewerColumn; | |
25 | import org.eclipse.jface.viewers.Viewer; | |
26 | import org.eclipse.jface.viewers.ViewerComparator; | |
27 | import org.eclipse.linuxtools.lttng.state.IStateDataRequestListener; | |
28 | import org.eclipse.linuxtools.lttng.state.RequestCompletedSignal; | |
29 | import org.eclipse.linuxtools.lttng.state.RequestStartedSignal; | |
30 | import org.eclipse.linuxtools.lttng.state.evProcessor.EventProcessorProxy; | |
31 | import org.eclipse.linuxtools.lttng.state.experiment.StateExperimentManager; | |
32 | import org.eclipse.linuxtools.lttng.state.experiment.StateManagerFactory; | |
33 | import org.eclipse.linuxtools.lttng.ui.views.statistics.evProcessor.StatsEventCountHandlerFactory; | |
34 | import org.eclipse.linuxtools.lttng.ui.views.statistics.evProcessor.StatsTimeCountHandlerFactory; | |
35 | import org.eclipse.linuxtools.lttng.ui.views.statistics.model.StatisticsTreeFactory; | |
36 | import org.eclipse.linuxtools.lttng.ui.views.statistics.model.StatisticsTreeNode; | |
37 | import org.eclipse.linuxtools.tmf.signal.TmfSignalHandler; | |
38 | import org.eclipse.linuxtools.tmf.ui.views.TmfView; | |
39 | import org.eclipse.swt.SWT; | |
40 | import org.eclipse.swt.events.SelectionAdapter; | |
41 | import org.eclipse.swt.events.SelectionEvent; | |
42 | import org.eclipse.swt.graphics.Color; | |
43 | import org.eclipse.swt.graphics.Image; | |
44 | import org.eclipse.swt.layout.FillLayout; | |
6e512b93 | 45 | import org.eclipse.swt.widgets.Composite; |
dfaf8391 FC |
46 | import org.eclipse.swt.widgets.Event; |
47 | import org.eclipse.swt.widgets.Listener; | |
48 | import org.eclipse.ui.ISharedImages; | |
49 | import org.eclipse.ui.PlatformUI; | |
6e512b93 ASL |
50 | |
51 | /** | |
52 | * <b><u>StatisticsView</u></b> | |
53 | * <p> | |
dfaf8391 FC |
54 | * The Statistics View displays statistics for traces. |
55 | * | |
56 | * It is implemented according to the MVC pattern. - The model is a | |
57 | * StatisticsTreeNode built by the State Manager. - The view is built with a | |
58 | * TreeViewer. - The controller that keeps model and view synchronised is an | |
59 | * observer of the model. | |
6e512b93 | 60 | */ |
dfaf8391 FC |
61 | public class StatisticsView extends TmfView implements |
62 | IStateDataRequestListener { | |
63 | ||
64 | public static final String ID = "org.eclipse.linuxtools.lttng.ui.views.statistics"; | |
65 | ||
66 | private TreeViewer treeViewer; | |
67 | ||
68 | // Table column names | |
69 | private final String LEVEL_COLUMN = "Level"; | |
70 | private final String EVENTS_COUNT_COLUMN = "Number of Events"; | |
71 | private final String CPU_TIME_COLUMN = "CPU Time"; | |
72 | private final String CUMULATIVE_CPU_TIME_COLUMN = "Cumulative CPU Time"; | |
73 | private final String ELAPSED_TIME_COLUMN = "Elapsed Time"; | |
74 | ||
75 | // Table column tooltips | |
76 | private final String LEVEL_COLUMN_TIP = "Level at which statistics apply."; | |
77 | private final String EVENTS_COUNT_COLUMN_TIP = "Total amount of events that are tied to given resource."; | |
78 | private final String CPU_TIME_COLUMN_TIP = "Total amount of time the CPU was used excluding wait times(I/O, etc.) at that level."; | |
79 | private final String CUMULATIVE_CPU_TIME_COLUMN_TIP = "Total amount of time between the first and last event excluding wait times in a level."; | |
80 | private final String ELAPSED_TIME_COLUMN_TIP = "Total amount of time the CPU was used including wait times(I/O, etc.) at that level."; | |
6e512b93 | 81 | |
dfaf8391 FC |
82 | // Level for which statistics should not be displayed. |
83 | private Set<String> folderLevels = new HashSet<String>(Arrays | |
84 | .asList(new String[] { "Event Types", "Modes", "Submodes", "CPUs", | |
85 | "Processes", "Functions" })); | |
6e512b93 | 86 | |
dfaf8391 FC |
87 | // Levels for which sub-levels should not contain time-related statistics. |
88 | private Set<String> levelsWithEmptyTime = new HashSet<String>(Arrays | |
89 | .asList(new String[] { "Event Types" })); | |
90 | ||
91 | private DecimalFormat decimalFormat = new DecimalFormat("0.#########"); | |
92 | ||
93 | // Used to draw bar charts in columns. | |
94 | private interface ColumnPercentageProvider { | |
95 | public double getPercentage(StatisticsTreeNode node); | |
96 | } | |
97 | ||
98 | /** | |
99 | * Contains all the information necessary to build a column of the table. | |
100 | */ | |
101 | private class ColumnData { | |
102 | // Name of the column. | |
103 | public final String header; | |
104 | // Width of the column. | |
105 | public final int width; | |
106 | // Alignment of the column. | |
107 | public final int alignment; | |
108 | // Tooltip of the column. | |
109 | public final String tooltip; | |
110 | // Adapts a StatisticsTreeNode into the content of it's corresponding | |
111 | // cell for that column. | |
112 | public final ColumnLabelProvider labelProvider; | |
113 | // Used to sort elements of this column. Can be null. | |
114 | public final ViewerComparator comparator; | |
115 | // Used to draw bar charts in this column. Can be null. | |
116 | public final ColumnPercentageProvider percentageProvider; | |
117 | ||
118 | public ColumnData(String h, int w, int a, String t, | |
119 | ColumnLabelProvider l, ViewerComparator c, | |
120 | ColumnPercentageProvider p) { | |
121 | header = h; | |
122 | width = w; | |
123 | alignment = a; | |
124 | tooltip = t; | |
125 | labelProvider = l; | |
126 | comparator = c; | |
127 | percentageProvider = p; | |
128 | } | |
129 | }; | |
130 | ||
131 | // List that will be used to create the table. | |
132 | private ColumnData[] columnDataList = new ColumnData[] { | |
133 | new ColumnData(LEVEL_COLUMN, 200, SWT.LEFT, LEVEL_COLUMN_TIP, | |
134 | new ColumnLabelProvider() { | |
135 | @Override | |
136 | public String getText(Object element) { | |
137 | return ((StatisticsTreeNode) element).getKey(); | |
138 | } | |
139 | ||
140 | @Override | |
141 | public Image getImage(Object element) { | |
142 | StatisticsTreeNode node = (StatisticsTreeNode) element; | |
143 | if (folderLevels.contains(node.getKey())) { | |
144 | return PlatformUI.getWorkbench() | |
145 | .getSharedImages().getImage( | |
146 | ISharedImages.IMG_OBJ_FOLDER); | |
147 | } else { | |
148 | return PlatformUI.getWorkbench() | |
149 | .getSharedImages().getImage( | |
150 | ISharedImages.IMG_OBJ_ELEMENT); | |
151 | } | |
152 | } | |
153 | }, new ViewerComparator() { | |
154 | @Override | |
155 | public int compare(Viewer viewer, Object e1, Object e2) { | |
156 | StatisticsTreeNode n1 = (StatisticsTreeNode) e1; | |
157 | StatisticsTreeNode n2 = (StatisticsTreeNode) e2; | |
158 | ||
159 | return n1.getKey().compareTo(n2.getKey()); | |
160 | } | |
161 | }, null), | |
162 | new ColumnData(EVENTS_COUNT_COLUMN, 125, SWT.LEFT, | |
163 | EVENTS_COUNT_COLUMN_TIP, new ColumnLabelProvider() { | |
164 | @Override | |
165 | public String getText(Object element) { | |
166 | StatisticsTreeNode node = (StatisticsTreeNode) element; | |
167 | if (!folderLevels.contains(node.getKey())) { | |
168 | return Long.toString(node.getValue().nbEvents); | |
169 | } else { | |
170 | return ""; | |
171 | } | |
172 | } | |
173 | }, new ViewerComparator() { | |
174 | @Override | |
175 | public int compare(Viewer viewer, Object e1, Object e2) { | |
176 | StatisticsTreeNode n1 = (StatisticsTreeNode) e1; | |
177 | StatisticsTreeNode n2 = (StatisticsTreeNode) e2; | |
178 | ||
179 | return (int) (n1.getValue().nbEvents - n2 | |
180 | .getValue().nbEvents); | |
181 | } | |
182 | }, new ColumnPercentageProvider() { | |
183 | public double getPercentage(StatisticsTreeNode node) { | |
184 | StatisticsTreeNode parent = node; | |
185 | do { | |
186 | parent = parent.getParent(); | |
187 | } while (parent != null | |
188 | && parent.getValue().nbEvents == 0); | |
189 | ||
190 | if (parent == null) { | |
191 | return 0; | |
192 | } else { | |
193 | return (double) node.getValue().nbEvents | |
194 | / parent.getValue().nbEvents; | |
195 | } | |
196 | } | |
197 | }), | |
198 | new ColumnData(CPU_TIME_COLUMN, 125, SWT.LEFT, CPU_TIME_COLUMN_TIP, | |
199 | new ColumnLabelProvider() { | |
200 | @Override | |
201 | public String getText(Object element) { | |
202 | StatisticsTreeNode node = (StatisticsTreeNode) element; | |
203 | ||
204 | if (folderLevels.contains(node.getKey())) { | |
205 | return ""; | |
206 | } else if (node.getParent() != null | |
207 | && levelsWithEmptyTime.contains(node | |
208 | .getParent().getKey())) { | |
209 | return ""; | |
210 | } else { | |
211 | return decimalFormat | |
212 | .format(node.getValue().cpuTime | |
213 | / Math.pow(10, 9)); | |
214 | } | |
215 | } | |
216 | }, null, null), | |
217 | new ColumnData(CUMULATIVE_CPU_TIME_COLUMN, 155, SWT.LEFT, | |
218 | CUMULATIVE_CPU_TIME_COLUMN_TIP, new ColumnLabelProvider() { | |
219 | @Override | |
220 | public String getText(Object element) { | |
221 | StatisticsTreeNode node = (StatisticsTreeNode) element; | |
222 | if (folderLevels.contains(node.getKey())) { | |
223 | return ""; | |
224 | } else if (node.getParent() != null | |
225 | && levelsWithEmptyTime.contains(node | |
226 | .getParent().getKey())) { | |
227 | return ""; | |
228 | } else { | |
229 | return decimalFormat | |
230 | .format(node.getValue().cumulativeCpuTime | |
231 | / Math.pow(10, 9)); | |
232 | } | |
233 | } | |
234 | }, null, null), | |
235 | new ColumnData(ELAPSED_TIME_COLUMN, 100, SWT.LEFT, | |
236 | ELAPSED_TIME_COLUMN_TIP, new ColumnLabelProvider() { | |
237 | @Override | |
238 | public String getText(Object element) { | |
239 | StatisticsTreeNode node = (StatisticsTreeNode) element; | |
240 | if (folderLevels.contains(node.getKey())) { | |
241 | return ""; | |
242 | } else if (node.getParent() != null | |
243 | && levelsWithEmptyTime.contains(node | |
244 | .getParent().getKey())) { | |
245 | return ""; | |
246 | } else { | |
247 | return decimalFormat | |
248 | .format(node.getValue().elapsedTime | |
249 | / Math.pow(10, 9)); | |
250 | } | |
251 | } | |
252 | }, null, null) }; | |
253 | ||
254 | /** | |
255 | * Adapter TreeViewers can use to interact with StatisticsTreeNode objects. | |
6e512b93 | 256 | * |
dfaf8391 | 257 | * @see org.eclipse.jface.viewers.ITreeContentProvider |
6e512b93 | 258 | */ |
dfaf8391 FC |
259 | class TreeContentProvider implements ITreeContentProvider { |
260 | /* | |
261 | * (non-Javadoc) | |
262 | * | |
263 | * @see | |
264 | * org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang | |
265 | * .Object) | |
266 | */ | |
267 | public Object[] getChildren(Object parentElement) { | |
268 | return ((StatisticsTreeNode) parentElement).getChildren().toArray(); | |
269 | } | |
270 | ||
271 | /* | |
272 | * (non-Javadoc) | |
273 | * | |
274 | * @see | |
275 | * org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang | |
276 | * .Object) | |
277 | */ | |
278 | public Object getParent(Object element) { | |
279 | return ((StatisticsTreeNode) element).getParent(); | |
280 | } | |
281 | ||
282 | /* | |
283 | * (non-Javadoc) | |
284 | * | |
285 | * @see | |
286 | * org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang | |
287 | * .Object) | |
288 | */ | |
289 | public boolean hasChildren(Object element) { | |
290 | return ((StatisticsTreeNode) element).hasChildren(); | |
291 | } | |
292 | ||
293 | /* | |
294 | * (non-Javadoc) | |
295 | * | |
296 | * @see | |
297 | * org.eclipse.jface.viewers.IStructuredContentProvider#getElements( | |
298 | * java.lang.Object) | |
299 | */ | |
300 | public Object[] getElements(Object inputElement) { | |
301 | return getChildren(inputElement); | |
302 | } | |
303 | ||
304 | /* | |
305 | * (non-Javadoc) | |
306 | * | |
307 | * @see org.eclipse.jface.viewers.IContentProvider#dispose() | |
308 | */ | |
309 | public void dispose() { | |
310 | } | |
311 | ||
312 | /* | |
313 | * (non-Javadoc) | |
314 | * | |
315 | * @see | |
316 | * org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse | |
317 | * .jface.viewers.Viewer, java.lang.Object, java.lang.Object) | |
318 | */ | |
319 | // @Override | |
320 | public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { | |
321 | } | |
6e512b93 ASL |
322 | } |
323 | ||
dfaf8391 FC |
324 | /* |
325 | * (non-Javadoc) | |
326 | * | |
327 | * @see | |
328 | * org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets | |
329 | * .Composite) | |
6e512b93 ASL |
330 | */ |
331 | @Override | |
332 | public void createPartControl(Composite parent) { | |
dfaf8391 FC |
333 | EventProcessorProxy.getInstance().addEventProcessorFactory( |
334 | StatsTimeCountHandlerFactory.getInstance()); | |
335 | EventProcessorProxy.getInstance().addEventProcessorFactory( | |
336 | StatsEventCountHandlerFactory.getInstance()); | |
337 | ||
338 | parent.setLayout(new FillLayout()); | |
339 | ||
340 | treeViewer = new TreeViewer(parent, SWT.BORDER | SWT.H_SCROLL | |
341 | | SWT.V_SCROLL); | |
342 | treeViewer.setContentProvider(new TreeContentProvider()); | |
343 | treeViewer.getTree().setHeaderVisible(true); | |
344 | treeViewer.setUseHashlookup(true); | |
345 | ||
346 | for (final ColumnData columnData : columnDataList) { | |
347 | final TreeViewerColumn treeColumn = new TreeViewerColumn( | |
348 | treeViewer, columnData.alignment); | |
349 | treeColumn.getColumn().setText(columnData.header); | |
350 | treeColumn.getColumn().setWidth(columnData.width); | |
351 | treeColumn.getColumn().setToolTipText(columnData.tooltip); | |
352 | if (columnData.comparator != null) { | |
353 | treeColumn.getColumn().addSelectionListener( | |
354 | new SelectionAdapter() { | |
355 | @Override | |
356 | public void widgetSelected(SelectionEvent e) { | |
357 | if (treeViewer.getTree().getSortDirection() == SWT.UP | |
358 | || treeViewer.getTree().getSortColumn() != treeColumn | |
359 | .getColumn()) { | |
360 | treeViewer | |
361 | .setComparator(columnData.comparator); | |
362 | treeViewer.getTree().setSortDirection( | |
363 | SWT.DOWN); | |
364 | } else { | |
365 | treeViewer | |
366 | .setComparator(new ViewerComparator() { | |
367 | @Override | |
368 | public int compare( | |
369 | Viewer viewer, | |
370 | Object e1, Object e2) { | |
371 | return -1 | |
372 | * columnData.comparator | |
373 | .compare( | |
374 | viewer, | |
375 | e1, | |
376 | e2); | |
377 | } | |
378 | }); | |
379 | treeViewer.getTree().setSortDirection( | |
380 | SWT.UP); | |
381 | } | |
382 | treeViewer.getTree().setSortColumn( | |
383 | treeColumn.getColumn()); | |
384 | } | |
385 | }); | |
386 | } | |
387 | treeColumn.setLabelProvider(columnData.labelProvider); | |
388 | } | |
389 | ||
390 | // Handler that will draw the bar charts. | |
391 | treeViewer.getTree().addListener(SWT.EraseItem, new Listener() { | |
392 | // @Override | |
393 | public void handleEvent(Event event) { | |
394 | if (columnDataList[event.index].percentageProvider != null) { | |
395 | StatisticsTreeNode node = (StatisticsTreeNode) event.item | |
396 | .getData(); | |
397 | ||
398 | double percentage = columnDataList[event.index].percentageProvider | |
399 | .getPercentage(node); | |
400 | if (percentage == 0) { | |
401 | return; | |
402 | } | |
403 | ||
404 | if ((event.detail & SWT.SELECTED) > 0) { | |
405 | Color oldForeground = event.gc.getForeground(); | |
406 | event.gc.setForeground(event.item.getDisplay() | |
407 | .getSystemColor(SWT.COLOR_LIST_SELECTION)); | |
408 | event.gc.fillRectangle(event.x, event.y, event.width, | |
409 | event.height); | |
410 | event.gc.setForeground(oldForeground); | |
411 | event.detail &= ~SWT.SELECTED; | |
412 | } | |
413 | ||
414 | int barWidth = (int) ((treeViewer.getTree().getColumn(1) | |
415 | .getWidth() - 8) * percentage); | |
416 | int oldAlpha = event.gc.getAlpha(); | |
417 | Color oldForeground = event.gc.getForeground(); | |
418 | Color oldBackground = event.gc.getBackground(); | |
419 | event.gc.setAlpha(64); | |
420 | event.gc.setForeground(event.item.getDisplay() | |
421 | .getSystemColor(SWT.COLOR_BLUE)); | |
422 | event.gc.setBackground(event.item.getDisplay() | |
423 | .getSystemColor(SWT.COLOR_LIST_BACKGROUND)); | |
424 | event.gc.fillGradientRectangle(event.x, event.y, barWidth, | |
425 | event.height, true); | |
426 | event.gc.drawRectangle(event.x, event.y, barWidth, | |
427 | event.height); | |
428 | event.gc.setForeground(oldForeground); | |
429 | event.gc.setBackground(oldBackground); | |
430 | event.gc.setAlpha(oldAlpha); | |
431 | event.detail &= ~SWT.BACKGROUND; | |
432 | } | |
433 | } | |
434 | }); | |
435 | ||
436 | treeViewer.setComparator(columnDataList[0].comparator); | |
437 | treeViewer.getTree().setSortColumn(treeViewer.getTree().getColumn(0)); | |
438 | treeViewer.getTree().setSortDirection(SWT.DOWN); | |
439 | ||
440 | treeViewer.setInput(StatisticsTreeFactory | |
441 | .getStatisticsTree("Experiment")); | |
442 | ||
443 | // Read current data if any available | |
444 | StateExperimentManager experimentManger = StateManagerFactory | |
445 | .getExperimentManager(); | |
446 | experimentManger.readExperiment("statisticsView", this); | |
447 | ||
448 | } | |
449 | @Override | |
450 | public void dispose() { | |
451 | super.dispose(); | |
452 | EventProcessorProxy.getInstance().removeEventProcessorFactory( | |
453 | StatsTimeCountHandlerFactory.getInstance()); | |
454 | EventProcessorProxy.getInstance().removeEventProcessorFactory( | |
455 | StatsEventCountHandlerFactory.getInstance()); | |
6e512b93 | 456 | |
6e512b93 ASL |
457 | } |
458 | ||
dfaf8391 FC |
459 | /* |
460 | * (non-Javadoc) | |
461 | * | |
6e512b93 ASL |
462 | * @see org.eclipse.ui.part.WorkbenchPart#setFocus() |
463 | */ | |
464 | @Override | |
465 | public void setFocus() { | |
dfaf8391 FC |
466 | treeViewer.getTree().setFocus(); |
467 | } | |
468 | ||
469 | @TmfSignalHandler | |
470 | public void processingStarted(RequestStartedSignal request) { | |
471 | // Nothing to do for the time being | |
6e512b93 | 472 | |
6e512b93 ASL |
473 | } |
474 | ||
dfaf8391 FC |
475 | @TmfSignalHandler |
476 | public void processingCompleted(RequestCompletedSignal signal) { | |
477 | treeViewer.getTree().getDisplay().asyncExec(new Runnable() { | |
478 | // @Override | |
479 | public void run() { | |
480 | treeViewer.refresh(); | |
481 | } | |
482 | }); | |
483 | } | |
6e512b93 | 484 | } |