1 /*******************************************************************************
2 * Copyright (c) 2012, 2014 Ericsson
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
10 * Mathieu Denis <mathieu.denis@polymtl.ca> - Initial API and implementation
11 * Alexandre Montplaisir - Port to ITmfStatistics provider
12 * Patrick Tasse - Support selection range
13 *******************************************************************************/
15 package org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
;
17 import java
.util
.List
;
20 import org
.eclipse
.jface
.viewers
.TreeViewer
;
21 import org
.eclipse
.jface
.viewers
.TreeViewerColumn
;
22 import org
.eclipse
.jface
.viewers
.Viewer
;
23 import org
.eclipse
.jface
.viewers
.ViewerComparator
;
24 import org
.eclipse
.linuxtools
.statesystem
.core
.ITmfStateSystem
;
25 import org
.eclipse
.linuxtools
.tmf
.core
.component
.TmfComponent
;
26 import org
.eclipse
.linuxtools
.tmf
.core
.request
.ITmfEventRequest
;
27 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfSignalHandler
;
28 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfTimeSynchSignal
;
29 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfTraceRangeUpdatedSignal
;
30 import org
.eclipse
.linuxtools
.tmf
.core
.statistics
.ITmfStatistics
;
31 import org
.eclipse
.linuxtools
.tmf
.core
.statistics
.TmfStatisticsEventTypesModule
;
32 import org
.eclipse
.linuxtools
.tmf
.core
.statistics
.TmfStatisticsModule
;
33 import org
.eclipse
.linuxtools
.tmf
.core
.timestamp
.ITmfTimestamp
;
34 import org
.eclipse
.linuxtools
.tmf
.core
.timestamp
.TmfTimeRange
;
35 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.ITmfTrace
;
36 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.TmfExperiment
;
37 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.TmfTraceManager
;
38 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.TmfViewer
;
39 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.ITmfColumnDataProvider
;
40 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfBaseColumnData
;
41 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfBaseColumnDataProvider
;
42 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfStatisticsTree
;
43 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfStatisticsTreeManager
;
44 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfStatisticsTreeNode
;
45 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfTreeContentProvider
;
46 import org
.eclipse
.swt
.SWT
;
47 import org
.eclipse
.swt
.events
.SelectionAdapter
;
48 import org
.eclipse
.swt
.events
.SelectionEvent
;
49 import org
.eclipse
.swt
.graphics
.Color
;
50 import org
.eclipse
.swt
.graphics
.Cursor
;
51 import org
.eclipse
.swt
.widgets
.Composite
;
52 import org
.eclipse
.swt
.widgets
.Control
;
53 import org
.eclipse
.swt
.widgets
.Display
;
54 import org
.eclipse
.swt
.widgets
.Event
;
55 import org
.eclipse
.swt
.widgets
.Listener
;
58 * A basic viewer to display statistics in the statistics view.
60 * It is linked to a single ITmfTrace until its disposal.
62 * @author Mathieu Denis
65 public class TmfStatisticsViewer
extends TmfViewer
{
67 /** Timestamp scale used for all statistics (nanosecond) */
68 private static final byte TIME_SCALE
= ITmfTimestamp
.NANOSECOND_SCALE
;
70 /** The delay (in ms) between each update in live-reading mode */
71 private static final long LIVE_UPDATE_DELAY
= 1000;
73 /** The actual tree viewer to display */
74 private TreeViewer fTreeViewer
;
76 /** The statistics tree linked to this viewer */
77 private TmfStatisticsTree fStatisticsData
;
79 /** Update range synchronization object */
80 private final Object fStatisticsRangeUpdateSyncObj
= new Object();
82 /** The trace that is displayed by this viewer */
83 private ITmfTrace fTrace
;
85 /** Indicates to process all events */
86 private boolean fProcessAll
;
88 /** View instance counter (for multiple statistics views) */
89 private static int fCountInstance
= 0;
91 /** Number of this instance. Used as an instance ID. */
92 private int fInstanceNb
;
94 /** Object to store the cursor while waiting for the trace to load */
95 private Cursor fWaitCursor
= null;
98 * Counts the number of times waitCursor() has been called. It avoids
99 * removing the waiting cursor, since there may be multiple requests running
102 private int fWaitCursorCount
= 0;
104 /** Tells to send a time range request when the trace gets updated. */
105 private boolean fSendRangeRequest
= true;
107 /** Reference to the trace manager */
108 private final TmfTraceManager fTraceManager
;
111 * Create a basic statistics viewer. To be used in conjunction with
112 * {@link TmfStatisticsViewer#init(Composite, String, ITmfTrace)}
115 * The parent composite that will hold the viewer
117 * The name that will be assigned to this viewer
119 * The trace that is displayed by this viewer
122 public TmfStatisticsViewer(Composite parent
, String viewerName
, ITmfTrace trace
) {
123 init(parent
, viewerName
, trace
);
124 fTraceManager
= TmfTraceManager
.getInstance();
128 * Initialize the statistics viewer.
131 * The parent component of the viewer.
133 * The name to give to the viewer.
135 * The trace that will be displayed by the viewer.
137 public void init(Composite parent
, String viewerName
, ITmfTrace trace
) {
138 super.init(parent
, viewerName
);
139 // Increment a counter to make sure the tree ID is unique.
141 fInstanceNb
= fCountInstance
;
144 // The viewer will process all events if he is assigned to an experiment
145 fProcessAll
= (trace
instanceof TmfExperiment
);
152 public void dispose() {
154 if (fWaitCursor
!= null) {
155 fWaitCursor
.dispose();
158 // Clean the model for this viewer
159 TmfStatisticsTreeManager
.removeStatTreeRoot(getTreeID());
162 // ------------------------------------------------------------------------
164 // ------------------------------------------------------------------------
167 * Handles the signal about new trace range.
170 * The trace range updated signal
173 public void traceRangeUpdated(TmfTraceRangeUpdatedSignal signal
) {
174 ITmfTrace trace
= signal
.getTrace();
176 if (!isListeningTo(trace
)) {
180 synchronized (fStatisticsRangeUpdateSyncObj
) {
181 // Sends the time range request only once from this method.
182 if (fSendRangeRequest
) {
183 fSendRangeRequest
= false;
184 ITmfTimestamp begin
= fTraceManager
.getSelectionBeginTime();
185 ITmfTimestamp end
= fTraceManager
.getSelectionEndTime();
186 TmfTimeRange timeRange
= new TmfTimeRange(begin
, end
);
187 requestTimeRangeData(trace
, timeRange
);
190 requestData(trace
, signal
.getRange());
194 * Handles the time synch updated signal. It updates the time range
198 * Contains the information about the new selected time range.
202 public void timeSynchUpdated(TmfTimeSynchSignal signal
) {
203 if (fTrace
== null) {
206 ITmfTimestamp begin
= signal
.getBeginTime();
207 ITmfTimestamp end
= signal
.getEndTime();
208 TmfTimeRange timeRange
= new TmfTimeRange(begin
, end
);
209 requestTimeRangeData(fTrace
, timeRange
);
212 // ------------------------------------------------------------------------
214 // ------------------------------------------------------------------------
217 * Returns the primary control associated with this viewer.
219 * @return the SWT control which displays this viewer's content
222 public Control
getControl() {
223 return fTreeViewer
.getControl();
227 * Get the input of the viewer.
229 * @return an object representing the input of the statistics viewer.
231 public Object
getInput() {
232 return fTreeViewer
.getInput();
236 * This method can be overridden to implement another way of representing
237 * the statistics data and to retrieve the information for display.
239 * @return a TmfStatisticsData object.
241 public TmfStatisticsTree
getStatisticData() {
242 if (fStatisticsData
== null) {
243 fStatisticsData
= new TmfStatisticsTree();
245 return fStatisticsData
;
249 * Returns a unique ID based on name to be associated with the statistics
250 * tree for this viewer. For a same name, it will always return the same ID.
252 * @return a unique statistics tree ID.
254 public String
getTreeID() {
255 return getName() + fInstanceNb
;
259 public void refresh() {
260 final Control viewerControl
= getControl();
261 // Ignore update if disposed
262 if (viewerControl
.isDisposed()) {
266 viewerControl
.getDisplay().asyncExec(new Runnable() {
269 if (!viewerControl
.isDisposed()) {
270 fTreeViewer
.refresh();
277 * Will force a request on the partial event count if one is needed.
279 public void sendPartialRequestOnNextUpdate() {
280 synchronized (fStatisticsRangeUpdateSyncObj
) {
281 fSendRangeRequest
= true;
286 * Focus on the statistics tree of the viewer
288 public void setFocus() {
289 fTreeViewer
.getTree().setFocus();
293 * Cancels the request if it is not already completed
296 * The request to be canceled
299 protected void cancelOngoingRequest(ITmfEventRequest request
) {
300 if (request
!= null && !request
.isCompleted()) {
306 * This method can be overridden to change the representation of the data in
309 * @return an object implementing ITmfBaseColumnDataProvider.
311 protected ITmfColumnDataProvider
getColumnDataProvider() {
312 return new TmfBaseColumnDataProvider();
316 * Initialize the content that will be drawn in this viewer
319 * The parent of the control to create
321 protected void initContent(Composite parent
) {
322 final List
<TmfBaseColumnData
> columnDataList
= getColumnDataProvider().getColumnData();
324 fTreeViewer
= new TreeViewer(parent
, SWT
.BORDER
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
325 fTreeViewer
.setContentProvider(new TmfTreeContentProvider());
326 fTreeViewer
.getTree().setHeaderVisible(true);
327 fTreeViewer
.setUseHashlookup(true);
329 // Creates the columns defined by the column data provider
330 for (final TmfBaseColumnData columnData
: columnDataList
) {
331 final TreeViewerColumn treeColumn
= new TreeViewerColumn(fTreeViewer
, columnData
.getAlignment());
332 treeColumn
.getColumn().setText(columnData
.getHeader());
333 treeColumn
.getColumn().setWidth(columnData
.getWidth());
334 treeColumn
.getColumn().setToolTipText(columnData
.getTooltip());
336 if (columnData
.getComparator() != null) { // A comparator is defined.
337 // Adds a listener on the columns header for sorting purpose.
338 treeColumn
.getColumn().addSelectionListener(new SelectionAdapter() {
340 private ViewerComparator reverseComparator
;
343 public void widgetSelected(SelectionEvent e
) {
344 // Initializes the reverse comparator once.
345 if (reverseComparator
== null) {
346 reverseComparator
= new ViewerComparator() {
348 public int compare(Viewer viewer
, Object e1
, Object e2
) {
349 return -1 * columnData
.getComparator().compare(viewer
, e1
, e2
);
354 if (fTreeViewer
.getTree().getSortDirection() == SWT
.UP
355 || fTreeViewer
.getTree().getSortColumn() != treeColumn
.getColumn()) {
357 * Puts the descendant order if the old order was
358 * up or if the selected column has changed.
360 fTreeViewer
.setComparator(columnData
.getComparator());
361 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
364 * Puts the ascendant ordering if the selected
365 * column hasn't changed.
367 fTreeViewer
.setComparator(reverseComparator
);
368 fTreeViewer
.getTree().setSortDirection(SWT
.UP
);
370 fTreeViewer
.getTree().setSortColumn(treeColumn
.getColumn());
374 treeColumn
.setLabelProvider(columnData
.getLabelProvider());
377 // Handler that will draw the bar charts.
378 fTreeViewer
.getTree().addListener(SWT
.EraseItem
, new Listener() {
380 public void handleEvent(Event event
) {
381 if (columnDataList
.get(event
.index
).getPercentageProvider() != null) {
382 TmfStatisticsTreeNode node
= (TmfStatisticsTreeNode
) event
.item
.getData();
384 double percentage
= columnDataList
.get(event
.index
).getPercentageProvider().getPercentage(node
);
385 if (percentage
== 0) { // No bar to draw
389 if ((event
.detail
& SWT
.SELECTED
) > 0) { // The item is selected.
390 // Draws our own background to avoid overwritten the bar.
391 event
.gc
.fillRectangle(event
.x
, event
.y
, event
.width
, event
.height
);
392 event
.detail
&= ~SWT
.SELECTED
;
395 int barWidth
= (int) ((fTreeViewer
.getTree().getColumn(event
.index
).getWidth() - 8) * percentage
);
396 int oldAlpha
= event
.gc
.getAlpha();
397 Color oldForeground
= event
.gc
.getForeground();
398 Color oldBackground
= event
.gc
.getBackground();
400 * Draws a transparent gradient rectangle from the color of
401 * foreground and background.
403 event
.gc
.setAlpha(64);
404 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_BLUE
));
405 event
.gc
.setBackground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_BACKGROUND
));
406 event
.gc
.fillGradientRectangle(event
.x
, event
.y
, barWidth
, event
.height
, true);
407 event
.gc
.drawRectangle(event
.x
, event
.y
, barWidth
, event
.height
);
408 // Restores old values
409 event
.gc
.setForeground(oldForeground
);
410 event
.gc
.setBackground(oldBackground
);
411 event
.gc
.setAlpha(oldAlpha
);
412 event
.detail
&= ~SWT
.BACKGROUND
;
417 // Initializes the comparator parameters
418 fTreeViewer
.setComparator(columnDataList
.get(0).getComparator());
419 fTreeViewer
.getTree().setSortColumn(fTreeViewer
.getTree().getColumn(0));
420 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
424 * Initializes the input for the tree viewer.
426 protected void initInput() {
427 String treeID
= getTreeID();
428 TmfStatisticsTreeNode statisticsTreeNode
;
429 if (TmfStatisticsTreeManager
.containsTreeRoot(treeID
)) {
430 // The statistics root is already present
431 statisticsTreeNode
= TmfStatisticsTreeManager
.getStatTreeRoot(treeID
);
433 // Checks if the trace is already in the statistics tree.
434 int numNodeTraces
= statisticsTreeNode
.getNbChildren();
436 ITmfTrace
[] traces
= TmfTraceManager
.getTraceSet(fTrace
);
437 int numTraces
= traces
.length
;
439 if (numTraces
== numNodeTraces
) {
442 * Checks if the experiment contains the same traces as when
443 * previously selected.
445 for (int i
= 0; i
< numTraces
; i
++) {
446 String traceName
= traces
[i
].getName();
447 if (!statisticsTreeNode
.containsChild(traceName
)) {
454 // No need to reload data, all traces are already loaded
455 fTreeViewer
.setInput(statisticsTreeNode
);
458 // Clears the old content to start over
459 statisticsTreeNode
.reset();
462 // Creates a new tree
463 statisticsTreeNode
= TmfStatisticsTreeManager
.addStatsTreeRoot(treeID
, getStatisticData());
466 // Sets the input to a clean data model
467 fTreeViewer
.setInput(statisticsTreeNode
);
471 * Tells if the viewer is listening to a trace.
474 * The trace that the viewer may be listening
475 * @return true if the viewer is listening to the trace, false otherwise
477 protected boolean isListeningTo(ITmfTrace trace
) {
478 if (fProcessAll
|| trace
== fTrace
) {
485 * Called when an trace request has been completed successfully.
488 * Tells if the request is a global or time range (partial)
491 protected void modelComplete(boolean global
) {
497 * Called when an trace request has failed or has been cancelled.
499 * @param isGlobalRequest
500 * Tells if the request is a global or time range (partial)
503 protected void modelIncomplete(boolean isGlobalRequest
) {
504 if (isGlobalRequest
) { // Clean the global statistics
506 * No need to reset the global number of events, since the index of
507 * the last requested event is known.
509 } else { // Clean the partial statistics
510 resetTimeRangeValue();
517 * Sends the request to the trace for the whole trace
520 * The trace used to send the request
522 * The range to request to the trace
524 protected void requestData(final ITmfTrace trace
, final TmfTimeRange timeRange
) {
525 buildStatisticsTree(trace
, timeRange
, true);
529 * Sends the time range request from the trace
532 * The trace used to send the request
534 * The range to request to the trace
536 protected void requestTimeRangeData(final ITmfTrace trace
, final TmfTimeRange timeRange
) {
537 buildStatisticsTree(trace
, timeRange
, false);
541 * Requests all the data of the trace to the state system which
542 * contains information about the statistics.
544 * Since the viewer may be listening to multiple traces, it may receive
545 * an experiment rather than a single trace. The filtering is done with the
546 * method {@link #isListeningTo(String trace)}.
549 * The trace for which a request must be done
551 * The time range that will be requested to the state system
553 * Tells if the request is for the global event count or the
556 private void buildStatisticsTree(final ITmfTrace trace
, final TmfTimeRange timeRange
, final boolean isGlobal
) {
557 final TmfStatisticsTreeNode statTree
= TmfStatisticsTreeManager
.getStatTreeRoot(getTreeID());
558 final TmfStatisticsTree statsData
= TmfStatisticsTreeManager
.getStatTree(getTreeID());
559 if (statsData
== null) {
563 synchronized (statsData
) {
565 statTree
.resetGlobalValue();
567 statTree
.resetTimeRangeValue();
570 for (final ITmfTrace aTrace
: TmfTraceManager
.getTraceSet(trace
)) {
571 if (!isListeningTo(aTrace
)) {
575 /* Retrieve the statistics object */
576 final TmfStatisticsModule statsMod
= aTrace
.getAnalysisModuleOfClass(TmfStatisticsModule
.class, TmfStatisticsModule
.ID
);
577 if (statsMod
== null) {
578 /* No statistics module available for this trace */
582 /* Run the potentially long queries in a separate thread */
583 Thread statsThread
= new Thread("Statistics update") { //$NON-NLS-1$
586 /* Wait until the analysis is ready to be queried */
587 statsMod
.waitForInitialization();
588 ITmfStatistics stats
= statsMod
.getStatistics();
590 /* It should have worked, but didn't */
591 throw new IllegalStateException();
595 * The generic statistics are stored in nanoseconds, so
596 * we must make sure the time range is scaled correctly.
598 long start
= timeRange
.getStartTime().normalize(0, TIME_SCALE
).getValue();
599 long end
= timeRange
.getEndTime().normalize(0, TIME_SCALE
).getValue();
602 * Wait on the state system object we are going to query.
604 * TODO Eventually this could be exposed through the
605 * TmfStateSystemAnalysisModule directly.
607 ITmfStateSystem ss
= statsMod
.getStateSystem(TmfStatisticsEventTypesModule
.ID
);
609 /* It should be instantiated after the
610 * statsMod.waitForInitialization() above. */
611 throw new IllegalStateException();
615 * Periodically update the statistics while they are
616 * being built (or, if the back-end is already completely
617 * built, it will skip over the while() immediately.
619 while(!ss
.waitUntilBuilt(LIVE_UPDATE_DELAY
)) {
620 Map
<String
, Long
> map
= stats
.getEventTypesInRange(start
, end
);
621 updateStats(aTrace
, isGlobal
, map
);
623 /* Query one last time for the final values */
624 Map
<String
, Long
> map
= stats
.getEventTypesInRange(start
, end
);
625 updateStats(aTrace
, isGlobal
, map
);
634 * Update statistics for a given trace
636 private void updateStats(ITmfTrace trace
, boolean isGlobal
, Map
<String
, Long
> eventsPerType
) {
638 final TmfStatisticsTree statsData
= TmfStatisticsTreeManager
.getStatTree(getTreeID());
639 if (statsData
== null) {
640 /* The stat tree has been disposed, abort mission. */
644 Map
<String
, Long
> map
= eventsPerType
;
645 String name
= trace
.getName();
648 * "Global", "partial", "total", etc., it's all very confusing...
650 * The base view shows the total count for the trace and for
651 * each even types, organized in columns like this:
653 * | Global | Time range |
654 * trace name | A | B |
656 * <event 1> | C | D |
657 * <event 2> | ... | ... |
660 * Here, we called the cells like this:
663 * C : GlobalTypeCount(s)
664 * D : TimeRangeTypeCount(s)
667 /* Fill in an the event counts (either cells C or D) */
668 for (Map
.Entry
<String
, Long
> entry
: map
.entrySet()) {
669 statsData
.setTypeCount(name
, entry
.getKey(), isGlobal
, entry
.getValue());
673 * Calculate the totals (cell A or B, depending if isGlobal). We will
674 * use the results of the previous request instead of sending another
677 long globalTotal
= 0;
678 for (long val
: map
.values()) {
681 statsData
.setTotal(name
, isGlobal
, globalTotal
);
683 modelComplete(isGlobal
);
687 * Resets the number of events within the time range
689 protected void resetTimeRangeValue() {
690 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeManager
.getStatTreeRoot(getTreeID());
691 if (treeModelRoot
!= null && treeModelRoot
.hasChildren()) {
692 treeModelRoot
.resetTimeRangeValue();
697 * When the trace is loading the cursor will be different so the user
698 * knows that the processing is not finished yet.
700 * Calls to this method are stacked.
702 * @param waitRequested
703 * Indicates if we need to show the waiting cursor, or the
706 protected void waitCursor(final boolean waitRequested
) {
707 if ((fTreeViewer
== null) || (fTreeViewer
.getTree().isDisposed())) {
711 boolean needsUpdate
= false;
712 Display display
= fTreeViewer
.getControl().getDisplay();
715 if (fWaitCursor
== null) { // The cursor hasn't been initialized yet
716 fWaitCursor
= new Cursor(display
, SWT
.CURSOR_WAIT
);
718 if (fWaitCursorCount
== 1) { // The cursor is not in waiting mode
722 if (fWaitCursorCount
> 0) { // The cursor is in waiting mode
724 if (fWaitCursorCount
== 0) { // No more reason to wait
725 // Put back the default cursor
732 // Performs the updates on the UI thread
733 display
.asyncExec(new Runnable() {
736 if ((fTreeViewer
!= null)
737 && (!fTreeViewer
.getTree().isDisposed())) {
738 Cursor cursor
= null; // indicates default
740 cursor
= fWaitCursor
;
742 fTreeViewer
.getControl().setCursor(cursor
);