Added TMF statistics feature (Bug 360572)
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / views / statistics / TmfStatisticsView.java
1 /*******************************************************************************
2 * Copyright (c) 2011 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:
10 * Mathieu Denis (mathieu.denis@polymtl.ca) - Generalized version based on LTTng
11 * Bernd Hufmann - Updated to use trace reference in TmfEvent and streaming
12 *******************************************************************************/
13
14 package org.eclipse.linuxtools.tmf.ui.views.statistics;
15
16 import java.util.Vector;
17
18 import org.eclipse.jface.viewers.TreeViewer;
19 import org.eclipse.jface.viewers.TreeViewerColumn;
20 import org.eclipse.jface.viewers.Viewer;
21 import org.eclipse.jface.viewers.ViewerComparator;
22 import org.eclipse.linuxtools.tmf.event.TmfEvent;
23 import org.eclipse.linuxtools.tmf.event.TmfTimeRange;
24 import org.eclipse.linuxtools.tmf.experiment.TmfExperiment;
25 import org.eclipse.linuxtools.tmf.request.ITmfDataRequest;
26 import org.eclipse.linuxtools.tmf.request.ITmfDataRequest.ExecutionType;
27 import org.eclipse.linuxtools.tmf.request.ITmfEventRequest;
28 import org.eclipse.linuxtools.tmf.request.TmfDataRequest;
29 import org.eclipse.linuxtools.tmf.request.TmfEventRequest;
30 import org.eclipse.linuxtools.tmf.signal.TmfExperimentDisposedSignal;
31 import org.eclipse.linuxtools.tmf.signal.TmfExperimentRangeUpdatedSignal;
32 import org.eclipse.linuxtools.tmf.signal.TmfExperimentSelectedSignal;
33 import org.eclipse.linuxtools.tmf.signal.TmfSignalHandler;
34 import org.eclipse.linuxtools.tmf.trace.ITmfTrace;
35 import org.eclipse.linuxtools.tmf.ui.views.TmfView;
36 import org.eclipse.linuxtools.tmf.ui.views.statistics.model.AbsTmfStatisticsTree;
37 import org.eclipse.linuxtools.tmf.ui.views.statistics.model.ITmfColumnDataProvider;
38 import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfBaseColumnData;
39 import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfBaseColumnDataProvider;
40 import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfBaseStatisticsTree;
41 import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfStatisticsTreeNode;
42 import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfStatisticsTreeRootFactory;
43 import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfTreeContentProvider;
44 import org.eclipse.swt.SWT;
45 import org.eclipse.swt.events.SelectionAdapter;
46 import org.eclipse.swt.events.SelectionEvent;
47 import org.eclipse.swt.graphics.Color;
48 import org.eclipse.swt.graphics.Cursor;
49 import org.eclipse.swt.layout.FillLayout;
50 import org.eclipse.swt.widgets.Composite;
51 import org.eclipse.swt.widgets.Display;
52 import org.eclipse.swt.widgets.Event;
53 import org.eclipse.swt.widgets.Listener;
54
55 /**
56 * <b><u>TmfStatisticsView</u></b>
57 * <p>
58 * The generic Statistics View displays statistics for any kind of traces.
59 *
60 * It is implemented according to the MVC pattern. - The model is a TmfStatisticsTreeNode built by the State Manager. - The view is built with a
61 * TreeViewer. - The controller that keeps model and view synchronized is an observer of the model.
62 * </p>
63 */
64 public class TmfStatisticsView extends TmfView {
65 /**
66 * The ID correspond to the package in which this class is embedded
67 */
68 public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.statistics"; //$NON-NLS-1$
69
70 // view name
71 private static final String TMF_STATISTICS_VIEW = "StatisticsView"; //$NON-NLS-1$
72
73 // Refresh frequency
74 protected static final Long STATS_INPUT_CHANGED_REFRESH = 5000L;
75
76 // Default PAGE_SIZE
77 protected static final int PAGE_SIZE = 50000; // For background request
78
79 // The actual tree to display
80 protected TreeViewer fTreeViewer;
81 // Stores the request to the experiment
82 protected ITmfEventRequest<TmfEvent> fRequest = null;
83
84 // Update synchronization parameters (used for streaming)
85 protected boolean fStatisticsUpdateBusy = false;
86 protected boolean fStatisticsUpdatePending = false;
87 protected TmfTimeRange fStatisticsUpdateRange = null;
88 protected final Object fStatisticsUpdateSyncObj = new Object();
89
90 // Object to store the cursor while waiting for the experiment to load
91 private Cursor fWaitCursor = null;
92
93 // Stores the number of instance
94 private static int fCountInstance = 0;
95
96 // Number of this instance. Used as an instance ID
97 private int fInstanceNb;
98
99 /**
100 * Constructor of a statistics view.
101 *
102 * @param viewName
103 * The name to give to the view.
104 */
105 public TmfStatisticsView(String viewName) {
106 super(viewName);
107 fCountInstance++;
108 fInstanceNb = fCountInstance;
109 }
110
111 /**
112 * Default constructor.
113 */
114 public TmfStatisticsView() {
115 this(TMF_STATISTICS_VIEW);
116 }
117
118 /*
119 * (non-Javadoc)
120 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
121 */
122 @Override
123 public void createPartControl(Composite parent) {
124 final Vector<TmfBaseColumnData> columnDataList = getColumnDataProvider().getColumnData();
125 parent.setLayout(new FillLayout());
126
127 fTreeViewer = new TreeViewer(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
128 fTreeViewer.setContentProvider(new TmfTreeContentProvider());
129 fTreeViewer.getTree().setHeaderVisible(true);
130 fTreeViewer.setUseHashlookup(true);
131
132 for (final TmfBaseColumnData columnData : columnDataList) {
133 final TreeViewerColumn treeColumn = new TreeViewerColumn(fTreeViewer, columnData.getAlignment());
134 treeColumn.getColumn().setText(columnData.getHeader());
135 treeColumn.getColumn().setWidth(columnData.getWidth());
136 treeColumn.getColumn().setToolTipText(columnData.getTooltip());
137
138 if (columnData.getComparator() != null) {
139 treeColumn.getColumn().addSelectionListener(new SelectionAdapter() {
140 @Override
141 public void widgetSelected(SelectionEvent e) {
142 if (fTreeViewer.getTree().getSortDirection() == SWT.UP || fTreeViewer.getTree().getSortColumn() != treeColumn.getColumn()) {
143 fTreeViewer.setComparator(columnData.getComparator());
144 fTreeViewer.getTree().setSortDirection(SWT.DOWN);
145 } else {
146 fTreeViewer.setComparator(new ViewerComparator() {
147 @Override
148 public int compare(Viewer viewer, Object e1, Object e2) {
149 return -1 * columnData.getComparator().compare(viewer, e1, e2);
150 }
151 });
152 fTreeViewer.getTree().setSortDirection(SWT.UP);
153 }
154 fTreeViewer.getTree().setSortColumn(treeColumn.getColumn());
155 }
156 });
157 }
158 treeColumn.setLabelProvider(columnData.getLabelProvider());
159 }
160
161 // Handler that will draw the bar charts.
162 fTreeViewer.getTree().addListener(SWT.EraseItem, new Listener() {
163 @Override
164 public void handleEvent(Event event) {
165 if (columnDataList.get(event.index).getPercentageProvider() != null) {
166 TmfStatisticsTreeNode node = (TmfStatisticsTreeNode) event.item.getData();
167
168 double percentage = columnDataList.get(event.index).getPercentageProvider().getPercentage(node);
169 if (percentage == 0) {
170 return;
171 }
172
173 if ((event.detail & SWT.SELECTED) > 0) {
174 Color oldForeground = event.gc.getForeground();
175 event.gc.setForeground(event.item.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION));
176 event.gc.fillRectangle(event.x, event.y, event.width, event.height);
177 event.gc.setForeground(oldForeground);
178 event.detail &= ~SWT.SELECTED;
179 }
180
181 int barWidth = (int) ((fTreeViewer.getTree().getColumn(1).getWidth() - 8) * percentage);
182 int oldAlpha = event.gc.getAlpha();
183 Color oldForeground = event.gc.getForeground();
184 Color oldBackground = event.gc.getBackground();
185 event.gc.setAlpha(64);
186 event.gc.setForeground(event.item.getDisplay().getSystemColor(SWT.COLOR_BLUE));
187 event.gc.setBackground(event.item.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
188 event.gc.fillGradientRectangle(event.x, event.y, barWidth, event.height, true);
189 event.gc.drawRectangle(event.x, event.y, barWidth, event.height);
190 event.gc.setForeground(oldForeground);
191 event.gc.setBackground(oldBackground);
192 event.gc.setAlpha(oldAlpha);
193 event.detail &= ~SWT.BACKGROUND;
194 }
195 }
196 });
197
198 fTreeViewer.setComparator(columnDataList.get(0).getComparator());
199 fTreeViewer.getTree().setSortColumn(fTreeViewer.getTree().getColumn(0));
200 fTreeViewer.getTree().setSortDirection(SWT.DOWN);
201
202 // Read current data if any available
203 TmfExperiment<?> experiment = TmfExperiment.getCurrentExperiment();
204 if (experiment != null) {
205 // Insert the statistics data into the tree
206 @SuppressWarnings({ "rawtypes", "unchecked" })
207 TmfExperimentSelectedSignal<?> signal = new TmfExperimentSelectedSignal(this, experiment);
208 experimentSelected(signal);
209 }
210 }
211
212 /*
213 * (non-Javadoc)
214 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
215 */
216 @Override
217 public void dispose() {
218 super.dispose();
219 if (fWaitCursor != null) {
220 fWaitCursor.dispose();
221 }
222
223 // clean the model
224 TmfStatisticsTreeRootFactory.removeAll();
225 }
226
227 /*
228 * (non-Javadoc)
229 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
230 */
231 @Override
232 public void setFocus() {
233 fTreeViewer.getTree().setFocus();
234 }
235
236 /**
237 * Refresh the view.
238 */
239 public void modelInputChanged(boolean complete) {
240 // Ignore update if disposed
241 if (fTreeViewer.getTree().isDisposed())
242 return;
243
244 fTreeViewer.getTree().getDisplay().asyncExec(new Runnable() {
245 @Override
246 public void run() {
247 if (!fTreeViewer.getTree().isDisposed())
248 fTreeViewer.refresh();
249 }
250 });
251
252 if (complete) {
253 sendPendingUpdate();
254 }
255 }
256
257 /**
258 * Called when an experiment request has failed or has been canceled Remove the data retrieved from the experiment from the statistics tree.
259 *
260 * @param name
261 * the experiment name
262 */
263 public void modelIncomplete(String name) {
264 Object input = fTreeViewer.getInput();
265 if (input != null && input instanceof TmfStatisticsTreeNode) {
266 // The data from this experiment is invalid and shall be removed to
267 // refresh upon next selection
268 TmfStatisticsTreeRootFactory.removeStatTreeRoot(getTreeID(name));
269
270 // Reset synchronization information
271 resetUpdateSynchronization();
272 modelInputChanged(false);
273 }
274 waitCursor(false);
275 }
276
277 /**
278 * If the user choose another experiment, the current must be disposed.
279 *
280 * @param signal
281 */
282 @TmfSignalHandler
283 public void experimentDisposed(TmfExperimentDisposedSignal<? extends TmfEvent> signal) {
284 cancelOngoingRequest();
285 }
286
287 /**
288 * Handler called when an experiment is selected. Checks if the experiment has changed and requests the selected experiment if it has not yet been
289 * cached.
290 *
291 * @param signal
292 * contains the information about the selection.
293 */
294 @TmfSignalHandler
295 public void experimentSelected(TmfExperimentSelectedSignal<? extends TmfEvent> signal) {
296 if (signal != null) {
297 TmfExperiment<?> experiment = signal.getExperiment();
298 String experimentName = experiment.getName();
299
300 if (TmfStatisticsTreeRootFactory.containsTreeRoot(getTreeID(experimentName))) {
301 // The experiment root is already present
302 TmfStatisticsTreeNode experimentTreeNode = TmfStatisticsTreeRootFactory.getStatTreeRoot(getTreeID(experimentName));
303
304 ITmfTrace[] traces = experiment.getTraces();
305
306 // check if there is partial data loaded in the experiment
307 int numTraces = experiment.getTraces().length;
308 int numNodeTraces = experimentTreeNode.getNbChildren();
309
310 if (numTraces == numNodeTraces) {
311 boolean same = true;
312 // Detect if the experiment contains the same traces as when
313 // previously selected
314 for (int i = 0; i < numTraces; i++) {
315 String traceName = traces[i].getName();
316 if (!experimentTreeNode.containsChild(traceName)) {
317 same = false;
318 break;
319 }
320 }
321
322 if (same) {
323 // no need to reload data, all traces are already loaded
324 fTreeViewer.setInput(experimentTreeNode);
325
326 resetUpdateSynchronization();
327
328 return;
329 }
330 experimentTreeNode.reset();
331 }
332 } else {
333 TmfStatisticsTreeRootFactory.addStatsTreeRoot(getTreeID(experimentName), getStatisticData());
334 }
335
336 resetUpdateSynchronization();
337
338 TmfStatisticsTreeNode treeModelRoot = TmfStatisticsTreeRootFactory.getStatTreeRoot(getTreeID(experiment.getName()));
339
340 // if the model has contents, clear to start over
341 if (treeModelRoot.hasChildren()) {
342 treeModelRoot.reset();
343 }
344
345 // set input to a clean data model
346 fTreeViewer.setInput(treeModelRoot);
347
348 // if the data is not available or has changed, reload it
349 requestData(experiment, experiment.getTimeRange());
350 }
351 }
352
353 /**
354 * @param signal
355 */
356 @SuppressWarnings("unchecked")
357 @TmfSignalHandler
358 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal) {
359 TmfExperiment<TmfEvent> experiment = (TmfExperiment<TmfEvent>) signal.getExperiment();
360 // validate
361 if (! experiment.equals(TmfExperiment.getCurrentExperiment())) {
362 return;
363 }
364
365 requestData(experiment, signal.getRange());
366 }
367
368
369 /**
370 * Return the size of the request when performing background request.
371 *
372 * @return the block size for background request.
373 */
374 protected int getIndexPageSize() {
375 return PAGE_SIZE;
376 }
377
378 /**
379 *
380 * @return the quantity of data to retrieve before a refresh of the view is performed.
381 */
382 protected long getInputChangedRefresh() {
383 return STATS_INPUT_CHANGED_REFRESH;
384 }
385
386 /**
387 * This method can be overridden to implement another way to represent the statistics data and to retrieve the information for display.
388 *
389 * @return a TmfStatisticsData object.
390 */
391 protected AbsTmfStatisticsTree getStatisticData() {
392 return new TmfBaseStatisticsTree();
393 }
394
395 /**
396 * This method can be overridden to change the representation of the data in the columns.
397 *
398 * @return an object implementing ITmfBaseColumnDataProvider.
399 */
400 protected ITmfColumnDataProvider getColumnDataProvider() {
401 return new TmfBaseColumnDataProvider();
402 }
403
404 /**
405 * Construct the ID based on the experiment name
406 * @param experimentName the name of the trace name to show in the view
407 * @return a view ID
408 */
409 protected String getTreeID(String experimentName) {
410 return experimentName + fInstanceNb;
411 }
412
413 /**
414 * When the experiment is loading the cursor will be different so the user know the processing is not finished yet.
415 *
416 * @param waitInd
417 * indicates if we need to show the waiting cursor, or the default one
418 */
419 protected void waitCursor(final boolean waitInd) {
420 if ((fTreeViewer == null) || (fTreeViewer.getTree().isDisposed())) {
421 return;
422 }
423
424 Display display = fTreeViewer.getControl().getDisplay();
425 if (fWaitCursor == null) {
426 fWaitCursor = new Cursor(display, SWT.CURSOR_WAIT);
427 }
428
429 // Perform the updates on the UI thread
430 display.asyncExec(new Runnable() {
431 @Override
432 public void run() {
433 if ((fTreeViewer != null) && (!fTreeViewer.getTree().isDisposed())) {
434 Cursor cursor = null; /* indicates default */
435 if (waitInd) {
436 cursor = fWaitCursor;
437 }
438 fTreeViewer.getControl().setCursor(cursor);
439 }
440 }
441 });
442 }
443
444 /**
445 * Perform the request for an experiment and populates the statistics tree with event.
446 * @param experiment experiment for which we need the statistics data.
447 * @param time range to request
448 */
449 @SuppressWarnings("unchecked")
450 protected void requestData(final TmfExperiment<?> experiment, TmfTimeRange timeRange) {
451 if (experiment != null) {
452
453 // Check if update is already ongoing
454 if (checkUpdateBusy(timeRange)) {
455 return;
456 }
457
458 int index = 0;
459 for (TmfStatisticsTreeNode node : ((TmfStatisticsTreeNode) fTreeViewer.getInput()).getChildren()) {
460 index += (int) node.getValue().nbEvents;
461 }
462
463 // Preparation of the event request
464 fRequest = new TmfEventRequest<TmfEvent>(TmfEvent.class, timeRange, index, TmfDataRequest.ALL_DATA, getIndexPageSize(), ExecutionType.BACKGROUND) {
465
466 @Override
467 public void handleData(TmfEvent data) {
468 super.handleData(data);
469 if (data != null) {
470 AbsTmfStatisticsTree statisticsData = TmfStatisticsTreeRootFactory.getStatTree(getTreeID(experiment.getName()));
471
472 final String traceName = data.getParentTrace().getName();
473 ITmfExtraEventInfo extraInfo = new ITmfExtraEventInfo() {
474 @Override
475 public String getTraceName() {
476 if (traceName == null) {
477 return Messages.TmfStatisticsView_UnknownTraceName;
478 }
479 return traceName;
480 }
481 };
482 statisticsData.registerEvent(data, extraInfo);
483 statisticsData.increase(data, extraInfo, 1);
484 // Refresh View
485 if ((getNbRead() % getInputChangedRefresh()) == 0) {
486 modelInputChanged(false);
487 }
488 }
489 }
490
491 @Override
492 public void handleSuccess() {
493 super.handleSuccess();
494 modelInputChanged(true);
495 waitCursor(false);
496 }
497
498 @Override
499 public void handleFailure() {
500 super.handleFailure();
501 modelIncomplete(experiment.getName());
502 }
503
504 @Override
505 public void handleCancel() {
506 super.handleCancel();
507 modelIncomplete(experiment.getName());
508 }
509 };
510 ((TmfExperiment<TmfEvent>) experiment).sendRequest((ITmfDataRequest<TmfEvent>) fRequest);
511 waitCursor(true);
512 }
513 }
514
515 /**
516 * Cancels the current ongoing request
517 */
518 protected void cancelOngoingRequest() {
519 if (fRequest != null && !fRequest.isCompleted()) {
520 fRequest.cancel();
521 }
522 }
523
524 /**
525 * Reset update synchronization information
526 */
527 protected void resetUpdateSynchronization() {
528 synchronized (fStatisticsUpdateSyncObj) {
529 fStatisticsUpdateBusy = false;
530 fStatisticsUpdatePending = false;
531 }
532 }
533
534 /**
535 * Checks if statistic update is ongoing. If it is ongoing the new time range is stored as pending
536 *
537 * @param timeRange - new time range
538 * @return true if statistic update is ongoing else false
539 */
540 protected boolean checkUpdateBusy(TmfTimeRange timeRange) {
541 synchronized (fStatisticsUpdateSyncObj) {
542 if (fStatisticsUpdateBusy) {
543 fStatisticsUpdatePending = true;
544 fStatisticsUpdateRange = timeRange;
545 return true;
546 }
547 fStatisticsUpdateBusy = true;
548 return false;
549 }
550 }
551
552 /**
553 * Sends pending request (if any)
554 */
555 protected void sendPendingUpdate() {
556 synchronized (fStatisticsUpdateSyncObj) {
557 fStatisticsUpdateBusy = false;
558 if (fStatisticsUpdatePending) {
559 fStatisticsUpdatePending = false;
560 requestData(TmfExperiment.getCurrentExperiment(), fStatisticsUpdateRange);
561 }
562 }
563 }
564
565 }
This page took 0.050699 seconds and 5 git commands to generate.