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