1 /*******************************************************************************
2 * Copyright (c) 2012 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 *******************************************************************************/
13 package org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
;
15 import java
.util
.List
;
17 import org
.eclipse
.jface
.viewers
.TreeViewer
;
18 import org
.eclipse
.jface
.viewers
.TreeViewerColumn
;
19 import org
.eclipse
.jface
.viewers
.Viewer
;
20 import org
.eclipse
.jface
.viewers
.ViewerComparator
;
21 import org
.eclipse
.linuxtools
.tmf
.core
.component
.TmfComponent
;
22 import org
.eclipse
.linuxtools
.tmf
.core
.event
.TmfTimeRange
;
23 import org
.eclipse
.linuxtools
.tmf
.core
.event
.TmfTimestamp
;
24 import org
.eclipse
.linuxtools
.tmf
.core
.request
.ITmfDataRequest
;
25 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentRangeUpdatedSignal
;
26 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentUpdatedSignal
;
27 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfRangeSynchSignal
;
28 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfSignalHandler
;
29 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.ITmfTrace
;
30 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.TmfExperiment
;
31 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.TmfViewer
;
32 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.AbsTmfStatisticsTree
;
33 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.ITmfColumnDataProvider
;
34 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfBaseColumnData
;
35 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfBaseColumnDataProvider
;
36 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfBaseStatisticsTree
;
37 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfStatisticsTreeNode
;
38 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfStatisticsTreeRootFactory
;
39 import org
.eclipse
.linuxtools
.tmf
.ui
.viewers
.statistics
.model
.TmfTreeContentProvider
;
40 import org
.eclipse
.swt
.SWT
;
41 import org
.eclipse
.swt
.events
.SelectionAdapter
;
42 import org
.eclipse
.swt
.events
.SelectionEvent
;
43 import org
.eclipse
.swt
.graphics
.Color
;
44 import org
.eclipse
.swt
.graphics
.Cursor
;
45 import org
.eclipse
.swt
.widgets
.Composite
;
46 import org
.eclipse
.swt
.widgets
.Control
;
47 import org
.eclipse
.swt
.widgets
.Display
;
48 import org
.eclipse
.swt
.widgets
.Event
;
49 import org
.eclipse
.swt
.widgets
.Listener
;
52 * A basic viewer to display statistics in the statistics view.
54 * It is linked to a single ITmfTrace until its disposal.
56 * @author Mathieu Denis
60 public class TmfStatisticsViewer
extends TmfViewer
{
63 * The initial window span (in nanoseconds)
65 public static final long INITIAL_WINDOW_SPAN
= (1L * 100 * 1000 * 1000); // .1sec
68 * Timestamp scale (nanosecond)
70 public static final byte TIME_SCALE
= -9;
73 * Default PAGE_SIZE for background requests.
75 protected static final int PAGE_SIZE
= 50000;
80 protected final Long STATS_INPUT_CHANGED_REFRESH
= 5000L;
83 * Stores the request to the experiment
85 protected TmfStatisticsRequest fRequest
= null;
88 * Stores the ranged request to the experiment
90 protected TmfStatisticsRequest fRequestRange
= null;
93 * The actual tree viewer to display
95 protected TreeViewer fTreeViewer
;
98 * The statistics tree linked to this viewer
100 protected AbsTmfStatisticsTree fStatisticsData
;
103 * Update synchronization parameter (used for streaming): Update busy
106 protected boolean fStatisticsUpdateBusy
= false;
109 * Update synchronization parameter (used for streaming): Update pending
112 protected boolean fStatisticsUpdatePending
= false;
115 * Update synchronization parameter (used for streaming): Pending Update
118 protected TmfTimeRange fStatisticsUpdateRange
= null;
121 * Update synchronization object.
123 protected final Object fStatisticsUpdateSyncObj
= new Object();
126 * Update range synchronization object.
128 protected final Object fStatisticsRangeUpdateSyncObj
= new Object();
131 * The trace that is displayed by this viewer
133 protected ITmfTrace fTrace
;
136 * Indicates to process all events
138 private boolean fProcessAll
;
141 * View instance counter (for multiple statistics views)
143 private static int fCountInstance
= 0;
146 * Number of this instance. Used as an instance ID.
148 private int fInstanceNb
;
151 * Object to store the cursor while waiting for the experiment to load
153 private Cursor fWaitCursor
= null;
156 * Counts the number of times waitCursor() has been called. It avoids
157 * removing the waiting cursor, since there may be multiple requests running
160 private int fWaitCursorCount
= 0;
163 * Tells to send a time range request when the experiment gets updated.
165 private boolean fSendRangeRequest
= true;
168 * Empty constructor. To be used in conjunction with
169 * {@link TmfStatisticsViewer#init(Composite, String, ITmfTrace)}
171 public TmfStatisticsViewer() {
176 * Create a basic statistics viewer. To be used in conjunction with
177 * {@link TmfStatisticsViewer#init(Composite, String, ITmfTrace)}
180 * The parent composite that will hold the viewer
182 * The name that will be assigned to this viewer
184 * The trace that is displayed by this viewer
187 public TmfStatisticsViewer(Composite parent
, String viewerName
, ITmfTrace trace
) {
188 init(parent
, viewerName
, trace
);
192 * Initialize the statistics viewer.
195 * The parent component of the viewer.
197 * The name to give to the viewer.
199 * The trace that will be displayed by the viewer.
201 public void init(Composite parent
, String viewerName
, ITmfTrace trace
) {
202 super.init(parent
, viewerName
);
203 // Increment a counter to make sure the tree ID is unique.
205 fInstanceNb
= fCountInstance
;
208 // The viewer will process all events if he is assigned to the experiment
209 fProcessAll
= (trace
instanceof TmfExperiment
);
218 * @see org.eclipse.linuxtools.tmf.core.component.TmfComponent#dispose()
221 public void dispose() {
223 if (fWaitCursor
!= null) {
224 fWaitCursor
.dispose();
227 * Make sure there is no request running before removing the statistics
230 cancelOngoingRequest(fRequestRange
);
231 cancelOngoingRequest(fRequest
);
233 // Clean the model for this viewer
234 TmfStatisticsTreeRootFactory
.removeStatTreeRoot(getTreeID());
238 * Handles the signal about new experiment range.
241 * The experiment range updated signal
244 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal
) {
245 TmfExperiment experiment
= signal
.getExperiment();
247 if (!experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
251 synchronized (fStatisticsRangeUpdateSyncObj
) {
252 // Sends the time range request only once from this method.
253 if (fSendRangeRequest
) {
254 fSendRangeRequest
= false;
255 // Calculate the selected time range to request
256 long startTime
= signal
.getRange().getStartTime().normalize(0, TIME_SCALE
).getValue();
257 TmfTimestamp startTS
= new TmfTimestamp(startTime
, TIME_SCALE
);
258 TmfTimestamp endTS
= new TmfTimestamp(startTime
+ INITIAL_WINDOW_SPAN
, TIME_SCALE
);
259 TmfTimeRange timeRange
= new TmfTimeRange(startTS
, endTS
);
261 requestTimeRangeData(experiment
, timeRange
);
264 requestData(experiment
, signal
.getRange());
268 * Handles the experiment updated signal. This will detect new events in
269 * case the indexing is not coalesced with a statistics request.
272 * The experiment updated signal
275 public void experimentUpdated(TmfExperimentUpdatedSignal signal
) {
276 TmfExperiment experiment
= signal
.getExperiment();
277 if (!experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
282 if (fRequest
!= null) {
283 nbEvents
= fRequest
.getLastEventIndex();
286 * In the normal case, the statistics request is coalesced with indexing
287 * and the number of events are the same, there is nothing to do. But if
288 * it's not the case, trigger a new request to count the new events.
290 if (nbEvents
< experiment
.getNbEvents()) {
291 requestData(experiment
, experiment
.getTimeRange());
296 * * Handles the time range updated signal. It updates the time range
300 * Contains the information about the new selected time range.
303 public void timeRangeUpdated(TmfRangeSynchSignal signal
) {
305 * It is possible that the time range changes while a request is
308 cancelOngoingRequest(fRequestRange
);
310 requestTimeRangeData(TmfExperiment
.getCurrentExperiment(), signal
.getCurrentRange());
314 * Returns the primary control associated with this viewer.
316 * @return the SWT control which displays this viewer's content
319 public Control
getControl() {
320 return fTreeViewer
.getControl();
324 * Get the input of the viewer.
326 * @return an object representing the input of the statistics viewer.
328 public Object
getInput() {
329 return fTreeViewer
.getInput();
333 * Return the size of the request when performing background request.
335 * @return the block size for background request.
337 public int getPageSize() {
342 * Return the number of events to receive before a refresh of the viewer is
345 * @return the input refresh rate
347 public long getRefreshRate() {
348 return STATS_INPUT_CHANGED_REFRESH
;
352 * This method can be overridden to implement another way of representing
353 * the statistics data and to retrieve the information for display.
355 * @return a TmfStatisticsData object.
357 public AbsTmfStatisticsTree
getStatisticData() {
358 if (fStatisticsData
== null) {
359 fStatisticsData
= new TmfBaseStatisticsTree();
361 return fStatisticsData
;
365 * Returns a unique ID based on name to be associated with the statistics
366 * tree for this viewer. For a same name, it will always return the same ID.
368 * @return a unique statistics tree ID.
370 public String
getTreeID() {
371 return getName() + fInstanceNb
;
375 public void refresh() {
376 final Control viewerControl
= getControl();
377 // Ignore update if disposed
378 if (viewerControl
.isDisposed()) {
382 viewerControl
.getDisplay().asyncExec(new Runnable() {
385 if (!viewerControl
.isDisposed()) {
386 fTreeViewer
.refresh();
393 * Will force a request on the partial event count if one is needed.
395 public void sendPartialRequestOnNextUpdate() {
396 synchronized (fStatisticsRangeUpdateSyncObj
) {
397 fSendRangeRequest
= true;
402 * Focus on the statistics tree of the viewer
404 public void setFocus() {
405 fTreeViewer
.getTree().setFocus();
409 * Cancels the request if it is not already completed
412 * The request to be canceled
414 protected void cancelOngoingRequest(ITmfDataRequest request
) {
415 if (request
!= null && !request
.isCompleted()) {
421 * This method can be overridden to change the representation of the data in
424 * @return an object implementing ITmfBaseColumnDataProvider.
426 protected ITmfColumnDataProvider
getColumnDataProvider() {
427 return new TmfBaseColumnDataProvider();
431 * Initialize the content that will be drawn in this viewer
434 * The parent of the control to create
436 protected void initContent(Composite parent
) {
437 final List
<TmfBaseColumnData
> columnDataList
= getColumnDataProvider().getColumnData();
439 fTreeViewer
= new TreeViewer(parent
, SWT
.BORDER
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
440 fTreeViewer
.setContentProvider(new TmfTreeContentProvider());
441 fTreeViewer
.getTree().setHeaderVisible(true);
442 fTreeViewer
.setUseHashlookup(true);
444 // Creates the columns defined by the column data provider
445 for (final TmfBaseColumnData columnData
: columnDataList
) {
446 final TreeViewerColumn treeColumn
= new TreeViewerColumn(fTreeViewer
, columnData
.getAlignment());
447 treeColumn
.getColumn().setText(columnData
.getHeader());
448 treeColumn
.getColumn().setWidth(columnData
.getWidth());
449 treeColumn
.getColumn().setToolTipText(columnData
.getTooltip());
451 if (columnData
.getComparator() != null) { // A comparator is defined.
452 // Adds a listener on the columns header for sorting purpose.
453 treeColumn
.getColumn().addSelectionListener(new SelectionAdapter() {
455 private ViewerComparator reverseComparator
;
458 public void widgetSelected(SelectionEvent e
) {
459 // Initializes the reverse comparator once.
460 if (reverseComparator
== null) {
461 reverseComparator
= new ViewerComparator() {
463 public int compare(Viewer viewer
, Object e1
, Object e2
) {
464 return -1 * columnData
.getComparator().compare(viewer
, e1
, e2
);
469 if (fTreeViewer
.getTree().getSortDirection() == SWT
.UP
470 || fTreeViewer
.getTree().getSortColumn() != treeColumn
.getColumn()) {
472 * Puts the descendant order if the old order was
473 * up or if the selected column has changed.
475 fTreeViewer
.setComparator(columnData
.getComparator());
476 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
479 * Puts the ascendant ordering if the selected
480 * column hasn't changed.
482 fTreeViewer
.setComparator(reverseComparator
);
483 fTreeViewer
.getTree().setSortDirection(SWT
.UP
);
485 fTreeViewer
.getTree().setSortColumn(treeColumn
.getColumn());
489 treeColumn
.setLabelProvider(columnData
.getLabelProvider());
492 // Handler that will draw the bar charts.
493 fTreeViewer
.getTree().addListener(SWT
.EraseItem
, new Listener() {
495 public void handleEvent(Event event
) {
496 if (columnDataList
.get(event
.index
).getPercentageProvider() != null) {
497 TmfStatisticsTreeNode node
= (TmfStatisticsTreeNode
) event
.item
.getData();
499 double percentage
= columnDataList
.get(event
.index
).getPercentageProvider().getPercentage(node
);
500 if (percentage
== 0) { // No bar to draw
504 if ((event
.detail
& SWT
.SELECTED
) > 0) { // The item is selected.
505 // Draws our own background to avoid overwritten the bar.
506 event
.gc
.fillRectangle(event
.x
, event
.y
, event
.width
, event
.height
);
507 event
.detail
&= ~SWT
.SELECTED
;
510 int barWidth
= (int) ((fTreeViewer
.getTree().getColumn(event
.index
).getWidth() - 8) * percentage
);
511 int oldAlpha
= event
.gc
.getAlpha();
512 Color oldForeground
= event
.gc
.getForeground();
513 Color oldBackground
= event
.gc
.getBackground();
515 * Draws a transparent gradient rectangle from the color of
516 * foreground and background.
518 event
.gc
.setAlpha(64);
519 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_BLUE
));
520 event
.gc
.setBackground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_BACKGROUND
));
521 event
.gc
.fillGradientRectangle(event
.x
, event
.y
, barWidth
, event
.height
, true);
522 event
.gc
.drawRectangle(event
.x
, event
.y
, barWidth
, event
.height
);
523 // Restores old values
524 event
.gc
.setForeground(oldForeground
);
525 event
.gc
.setBackground(oldBackground
);
526 event
.gc
.setAlpha(oldAlpha
);
527 event
.detail
&= ~SWT
.BACKGROUND
;
532 // Initializes the comparator parameters
533 fTreeViewer
.setComparator(columnDataList
.get(0).getComparator());
534 fTreeViewer
.getTree().setSortColumn(fTreeViewer
.getTree().getColumn(0));
535 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
539 * Initializes the input for the tree viewer.
542 * The input of this viewer, or <code>null</code> if none
544 protected void initInput() {
545 String treeID
= getTreeID();
546 TmfStatisticsTreeNode experimentTreeNode
;
547 if (TmfStatisticsTreeRootFactory
.containsTreeRoot(treeID
)) {
548 // The experiment root is already present
549 experimentTreeNode
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(treeID
);
551 // Checks if the trace is already in the statistics tree.
552 int numNodeTraces
= experimentTreeNode
.getNbChildren();
555 ITmfTrace
[] trace
= { fTrace
};
556 // For experiment, gets all the traces within it
557 if (fTrace
instanceof TmfExperiment
) {
558 TmfExperiment experiment
= (TmfExperiment
) fTrace
;
559 numTraces
= experiment
.getTraces().length
;
560 trace
= experiment
.getTraces();
563 if (numTraces
== numNodeTraces
) {
566 * Checks if the experiment contains the same traces as when
567 * previously selected.
569 for (int i
= 0; i
< numTraces
; i
++) {
570 String traceName
= trace
[i
].getName();
571 if (!experimentTreeNode
.containsChild(traceName
)) {
578 // No need to reload data, all traces are already loaded
579 fTreeViewer
.setInput(experimentTreeNode
);
582 // Clears the old content to start over
583 experimentTreeNode
.reset();
586 // Creates a new tree
587 experimentTreeNode
= TmfStatisticsTreeRootFactory
.addStatsTreeRoot(treeID
, getStatisticData());
590 // Sets the input to a clean data model
591 fTreeViewer
.setInput(experimentTreeNode
);
592 resetUpdateSynchronization();
596 * Tells if the viewer is listening to a trace from the selected experiment.
599 * The trace that the viewer may be listening
600 * @return true if the viewer is listening to the trace, false otherwise
602 protected boolean isListeningTo(String traceName
) {
603 if (fProcessAll
|| traceName
.equals(fTrace
.getName())) {
610 * Called when an experiment request has been completed successfully.
613 * Tells if the request is a global or time range (partial)
616 protected void modelComplete(boolean global
) {
625 * Called when an experiment request has failed or has been cancelled.
627 * @param isGlobalRequest
628 * Tells if the request is a global or time range (partial)
631 protected void modelIncomplete(boolean isGlobalRequest
) {
632 if (isGlobalRequest
) { // Clean the global statistics
634 * No need to reset the global number of events, since the index of
635 * the last requested event is known.
637 resetUpdateSynchronization();
639 } else { // Clean the partial statistics
640 resetTimeRangeValue();
647 * Sends the request to the experiment for the whole trace
650 * The experiment used to send the request
652 * The range to request to the experiment
654 protected void requestData(TmfExperiment experiment
, TmfTimeRange range
) {
655 // Check if an update is already ongoing
656 if (checkUpdateBusy(range
)) {
662 * Sets the index to the last event retrieved from the experiment during
665 if (fRequest
!= null) {
666 index
= fRequest
.getLastEventIndex();
669 fRequest
= new TmfStatisticsRequest(this, range
, index
, true);
671 experiment
.sendRequest(fRequest
);
675 * Sends the time range request from the experiment
678 * The experiment used to send the request
680 * The range to request to the experiment
682 protected void requestTimeRangeData(TmfExperiment experiment
, TmfTimeRange range
) {
683 resetTimeRangeValue();
684 fRequestRange
= new TmfStatisticsRequest(this, range
, 0, false);
686 experiment
.sendRequest(fRequestRange
);
690 * Resets the number of events within the time range
692 protected void resetTimeRangeValue() {
693 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID());
694 if (treeModelRoot
!= null && treeModelRoot
.hasChildren()) {
695 treeModelRoot
.resetTimeRangeValue();
700 * When the experiment is loading the cursor will be different so the user
701 * knows that the processing is not finished yet.
703 * Calls to this method are stacked.
705 * @param waitRequested
706 * Indicates if we need to show the waiting cursor, or the
709 protected void waitCursor(final boolean waitRequested
) {
710 if ((fTreeViewer
== null) || (fTreeViewer
.getTree().isDisposed())) {
714 boolean needsUpdate
= false;
715 Display display
= fTreeViewer
.getControl().getDisplay();
718 if (fWaitCursor
== null) { // The cursor hasn't been initialized yet
719 fWaitCursor
= new Cursor(display
, SWT
.CURSOR_WAIT
);
721 if (fWaitCursorCount
== 1) { // The cursor is not in waiting mode
725 if (fWaitCursorCount
> 0) { // The cursor is in waiting mode
727 if (fWaitCursorCount
== 0) { // No more reason to wait
728 // Put back the default cursor
735 // Performs the updates on the UI thread
736 display
.asyncExec(new Runnable() {
739 if ((fTreeViewer
!= null)
740 && (!fTreeViewer
.getTree().isDisposed())) {
741 Cursor cursor
= null; // indicates default
743 cursor
= fWaitCursor
;
745 fTreeViewer
.getControl().setCursor(cursor
);
752 // ------------------------------------------------------------------------
753 // Methods reserved for the streaming functionality
754 // ------------------------------------------------------------------------
757 * Resets update synchronization information
759 protected void resetUpdateSynchronization() {
760 synchronized (fStatisticsUpdateSyncObj
) {
761 fStatisticsUpdateBusy
= false;
762 fStatisticsUpdatePending
= false;
763 fStatisticsUpdateRange
= null;
768 * Checks if statistics update is ongoing. If it is ongoing, the new time
769 * range is stored as pending
773 * @return true if statistic update is ongoing else false
775 protected boolean checkUpdateBusy(TmfTimeRange timeRange
) {
776 synchronized (fStatisticsUpdateSyncObj
) {
777 if (fStatisticsUpdateBusy
) {
778 fStatisticsUpdatePending
= true;
779 if (fStatisticsUpdateRange
== null
780 || timeRange
.getEndTime().compareTo(fStatisticsUpdateRange
.getEndTime()) > 0) {
781 fStatisticsUpdateRange
= timeRange
;
785 fStatisticsUpdateBusy
= true;
791 * Sends pending request (if any)
793 protected void sendPendingUpdate() {
794 synchronized (fStatisticsUpdateSyncObj
) {
795 fStatisticsUpdateBusy
= false;
796 if (fStatisticsUpdatePending
) {
797 fStatisticsUpdatePending
= false;
798 requestData(TmfExperiment
.getCurrentExperiment(), fStatisticsUpdateRange
);
799 fStatisticsUpdateRange
= null;