1 /*******************************************************************************
2 * Copyright (c) 2011, 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> - Generalized version based on LTTng
11 * Bernd Hufmann - Updated to use trace reference in TmfEvent and streaming
12 * Mathieu Denis - New request added to update the statistics from the selected time range
14 *******************************************************************************/
16 package org
.eclipse
.linuxtools
.tmf
.ui
.views
.statistics
;
18 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
.tmf
.core
.event
.TmfTimeRange
;
25 import org
.eclipse
.linuxtools
.tmf
.core
.event
.TmfTimestamp
;
26 import org
.eclipse
.linuxtools
.tmf
.core
.request
.ITmfDataRequest
.ExecutionType
;
27 import org
.eclipse
.linuxtools
.tmf
.core
.request
.ITmfEventRequest
;
28 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentDisposedSignal
;
29 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentRangeUpdatedSignal
;
30 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentSelectedSignal
;
31 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentUpdatedSignal
;
32 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfRangeSynchSignal
;
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
;
57 * The generic Statistics View displays statistics for any kind of traces.
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.
65 * @author Mathieu Denis
67 public class TmfStatisticsView
extends TmfView
{
70 * The ID correspond to the package in which this class is embedded
72 public static final String ID
= "org.eclipse.linuxtools.tmf.ui.views.statistics"; //$NON-NLS-1$
77 public static final String TMF_STATISTICS_VIEW
= "StatisticsView"; //$NON-NLS-1$
82 protected static final Long STATS_INPUT_CHANGED_REFRESH
= 5000L;
85 * Default PAGE_SIZE for background requests
87 protected static final int PAGE_SIZE
= 50000;
90 * The initial window span (in nanoseconds)
94 public static final long INITIAL_WINDOW_SPAN
= (1L * 100 * 1000 * 1000); // .1sec
97 * Timestamp scale (nanosecond)
101 public static final byte TIME_SCALE
= -9;
104 * The actual tree viewer to display
106 protected TreeViewer fTreeViewer
;
109 * Stores the global request to the experiment
111 protected ITmfEventRequest fRequest
= null;
114 * Stores the ranged request to the experiment
117 protected ITmfEventRequest fRequestRange
= null;
120 * Update synchronization parameter (used for streaming): Update busy
123 protected boolean fStatisticsUpdateBusy
= false;
126 * Update synchronization parameter (used for streaming): Update pending
129 protected boolean fStatisticsUpdatePending
= false;
132 * Update synchronization parameter (used for streaming): Pending Update
135 protected TmfTimeRange fStatisticsUpdateRange
= null;
138 * Update synchronization object.
140 protected final Object fStatisticsUpdateSyncObj
= new Object();
143 * Flag to force request the data from trace
145 protected boolean fRequestData
= false;
148 * Object to store the cursor while waiting for the experiment to load
150 private Cursor fWaitCursor
= null;
153 * View instance counter (for multiple statistic views)
155 private static int fCountInstance
= 0;
158 * Number of this instance. Used as an instance ID.
160 private final int fInstanceNb
;
163 * Constructor of a statistics view.
166 * The name to give to the view.
168 public TmfStatisticsView(String viewName
) {
171 fInstanceNb
= fCountInstance
;
175 * Default constructor.
177 public TmfStatisticsView() {
178 this(TMF_STATISTICS_VIEW
);
185 * org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
188 public void createPartControl(Composite parent
) {
189 final List
<TmfBaseColumnData
> columnDataList
= getColumnDataProvider().getColumnData();
190 parent
.setLayout(new FillLayout());
192 fTreeViewer
= new TreeViewer(parent
, SWT
.BORDER
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
193 fTreeViewer
.setContentProvider(new TmfTreeContentProvider());
194 fTreeViewer
.getTree().setHeaderVisible(true);
195 fTreeViewer
.setUseHashlookup(true);
197 for (final TmfBaseColumnData columnData
: columnDataList
) {
198 final TreeViewerColumn treeColumn
= new TreeViewerColumn(fTreeViewer
, columnData
.getAlignment());
199 treeColumn
.getColumn().setText(columnData
.getHeader());
200 treeColumn
.getColumn().setWidth(columnData
.getWidth());
201 treeColumn
.getColumn().setToolTipText(columnData
.getTooltip());
203 if (columnData
.getComparator() != null) {
204 treeColumn
.getColumn().addSelectionListener(new SelectionAdapter() {
206 public void widgetSelected(SelectionEvent e
) {
207 if (fTreeViewer
.getTree().getSortDirection() == SWT
.UP
|| fTreeViewer
.getTree().getSortColumn() != treeColumn
.getColumn()) {
208 fTreeViewer
.setComparator(columnData
.getComparator());
209 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
211 fTreeViewer
.setComparator(new ViewerComparator() {
213 public int compare(Viewer viewer
, Object e1
, Object e2
) {
214 return -1 * columnData
.getComparator().compare(viewer
, e1
, e2
);
217 fTreeViewer
.getTree().setSortDirection(SWT
.UP
);
219 fTreeViewer
.getTree().setSortColumn(treeColumn
.getColumn());
223 treeColumn
.setLabelProvider(columnData
.getLabelProvider());
226 // Handler that will draw the bar charts.
227 fTreeViewer
.getTree().addListener(SWT
.EraseItem
, new Listener() {
229 public void handleEvent(Event event
) {
230 if (columnDataList
.get(event
.index
).getPercentageProvider() != null) {
231 TmfStatisticsTreeNode node
= (TmfStatisticsTreeNode
) event
.item
.getData();
233 double percentage
= columnDataList
.get(event
.index
).getPercentageProvider().getPercentage(node
);
234 if (percentage
== 0) {
238 if ((event
.detail
& SWT
.SELECTED
) > 0) {
239 Color oldForeground
= event
.gc
.getForeground();
240 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_SELECTION
));
241 event
.gc
.fillRectangle(event
.x
, event
.y
, event
.width
, event
.height
);
242 event
.gc
.setForeground(oldForeground
);
243 event
.detail
&= ~SWT
.SELECTED
;
246 int barWidth
= (int) ((fTreeViewer
.getTree().getColumn(event
.index
).getWidth() - 8) * percentage
);
247 int oldAlpha
= event
.gc
.getAlpha();
248 Color oldForeground
= event
.gc
.getForeground();
249 Color oldBackground
= event
.gc
.getBackground();
250 event
.gc
.setAlpha(64);
251 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_BLUE
));
252 event
.gc
.setBackground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_BACKGROUND
));
253 event
.gc
.fillGradientRectangle(event
.x
, event
.y
, barWidth
, event
.height
, true);
254 event
.gc
.drawRectangle(event
.x
, event
.y
, barWidth
, event
.height
);
255 event
.gc
.setForeground(oldForeground
);
256 event
.gc
.setBackground(oldBackground
);
257 event
.gc
.setAlpha(oldAlpha
);
258 event
.detail
&= ~SWT
.BACKGROUND
;
263 fTreeViewer
.setComparator(columnDataList
.get(0).getComparator());
264 fTreeViewer
.getTree().setSortColumn(fTreeViewer
.getTree().getColumn(0));
265 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
267 // Read current data if any available
268 TmfExperiment experiment
= TmfExperiment
.getCurrentExperiment();
269 if (experiment
!= null) {
271 // Insert the statistics data into the tree
272 TmfExperimentSelectedSignal signal
= new TmfExperimentSelectedSignal(this, experiment
);
273 experimentSelected(signal
);
280 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
283 public void dispose() {
285 if (fWaitCursor
!= null) {
286 fWaitCursor
.dispose();
290 * Make sure there is no request running before removing the statistics
293 cancelOngoingRequest(fRequestRange
);
294 cancelOngoingRequest(fRequest
);
296 TmfStatisticsTreeRootFactory
.removeAll();
302 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
305 public void setFocus() {
306 fTreeViewer
.getTree().setFocus();
313 * Should a pending update be sent afterwards or not
315 public void modelInputChanged(boolean complete
) {
316 // Ignore update if disposed
317 if (fTreeViewer
.getTree().isDisposed()) {
321 fTreeViewer
.getTree().getDisplay().asyncExec(new Runnable() {
324 if (!fTreeViewer
.getTree().isDisposed()) {
325 fTreeViewer
.refresh();
336 * Called when an experiment request has failed or has been cancelled.
337 * Remove the data retrieved from the experiment from the statistics tree.
340 * The experiment name
342 public void modelIncomplete(String name
) {
343 Object input
= fTreeViewer
.getInput();
344 if (input
!= null && input
instanceof TmfStatisticsTreeNode
) {
346 * The data from this experiment is invalid and shall be removed to
347 * refresh upon next selection
349 TmfStatisticsTreeRootFactory
.removeStatTreeRoot(getTreeID(name
));
351 // Reset synchronization information
352 resetUpdateSynchronization();
353 modelInputChanged(false);
359 * Handles the signal about disposal of the current experiment.
362 * The disposed signal
365 public void experimentDisposed(TmfExperimentDisposedSignal signal
) {
366 if (signal
.getExperiment() != TmfExperiment
.getCurrentExperiment()) {
370 * The range request must be cancelled first, since the global one removes
371 * the statistics tree
373 cancelOngoingRequest(fRequestRange
);
374 cancelOngoingRequest(fRequest
);
375 resetTimeRangeValue();
379 * Handler called when an experiment is selected. Checks if the experiment
380 * has changed and requests the selected experiment if it has not yet been
384 * Contains the information about the selection.
387 public void experimentSelected(TmfExperimentSelectedSignal signal
) {
388 if (signal
!= null) {
389 TmfExperiment experiment
= signal
.getExperiment();
390 String experimentName
= experiment
.getName();
392 if (TmfStatisticsTreeRootFactory
.containsTreeRoot(getTreeID(experimentName
))) {
393 // The experiment root is already present
394 String treeID
= getTreeID(experimentName
);
395 TmfStatisticsTreeNode experimentTreeNode
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(treeID
);
397 ITmfTrace
[] traces
= experiment
.getTraces();
399 // check if there is partial data loaded in the experiment
400 int numTraces
= experiment
.getTraces().length
;
401 int numNodeTraces
= experimentTreeNode
.getNbChildren();
403 if (numTraces
== numNodeTraces
) {
406 * Detect if the experiment contains the same traces as when
407 * previously selected
409 for (int i
= 0; i
< numTraces
; i
++) {
410 String traceName
= traces
[i
].getName();
411 if (!experimentTreeNode
.containsChild(traceName
)) {
418 // no need to reload data, all traces are already loaded
419 fTreeViewer
.setInput(experimentTreeNode
);
421 resetUpdateSynchronization();
425 experimentTreeNode
.reset();
428 TmfStatisticsTreeRootFactory
.addStatsTreeRoot(getTreeID(experimentName
), getStatisticData());
431 resetUpdateSynchronization();
433 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experiment
.getName()));
435 // if the model has contents, clear to start over
436 if (treeModelRoot
.hasChildren()) {
437 treeModelRoot
.reset();
440 // set input to a clean data model
441 fTreeViewer
.setInput(treeModelRoot
);
444 requestData(experiment
, experiment
.getTimeRange());
445 fRequestData
= false;
451 * Handles the signal about new experiment range.
454 * The experiment range updated signal
457 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal
) {
458 TmfExperiment experiment
= signal
.getExperiment();
460 if (!experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
464 // Calculate the selected timerange for the request
465 long startTime
= signal
.getRange().getStartTime().normalize(0, TIME_SCALE
).getValue();
466 TmfTimestamp startTS
= new TmfTimestamp(startTime
, TIME_SCALE
);
467 TmfTimestamp endTS
= new TmfTimestamp(startTime
+ INITIAL_WINDOW_SPAN
, TIME_SCALE
);
468 TmfTimeRange timeRange
= new TmfTimeRange(startTS
, endTS
);
470 requestTimeRangeData(experiment
, timeRange
);
471 requestData(experiment
, signal
.getRange());
475 * Handles the experiment updated signal. This will detect new events in
476 * case the indexing is not coalesced with a statistics request.
479 * The experiment updated signal
484 public void experimentUpdated(TmfExperimentUpdatedSignal signal
) {
485 TmfExperiment experiment
= signal
.getExperiment();
486 if (!experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
491 for (TmfStatisticsTreeNode node
: ((TmfStatisticsTreeNode
) fTreeViewer
.getInput()).getChildren()) {
492 nbEvents
+= (int) node
.getValue().getTotal();
496 * In the normal case, the statistics request is coalesced with indexing
497 * and the number of events are the same, there is nothing to do. But if
498 * it's not the case, trigger a new request to count the new events.
500 if (nbEvents
< experiment
.getNbEvents()) {
501 requestData(experiment
, experiment
.getTimeRange());
506 * Handles the time range updated signal. It updates the time range
510 * Contains the information about the new selected time range.
514 public void timeRangeUpdated(TmfRangeSynchSignal signal
) {
516 * It is possible that the time range changes while a request is
519 cancelOngoingRequest(fRequestRange
);
520 resetTimeRangeValue();
522 requestTimeRangeData(TmfExperiment
.getCurrentExperiment(), signal
.getCurrentRange());
526 * Return the size of the request when performing background request.
528 * @return the block size for background request.
530 protected int getIndexPageSize() {
535 * Returns the quantity of data to retrieve before a refresh of the view is
538 * @return the quantity of data to retrieve before a refresh of the view is
541 protected long getInputChangedRefresh() {
542 return STATS_INPUT_CHANGED_REFRESH
;
546 * This method can be overridden to implement another way to represent the
547 * statistics data and to retrieve the information for display.
549 * @return a TmfStatisticsData object.
551 protected AbsTmfStatisticsTree
getStatisticData() {
552 return new TmfBaseStatisticsTree();
556 * This method can be overridden to change the representation of the data in
559 * @return an object implementing ITmfBaseColumnDataProvider.
561 protected ITmfColumnDataProvider
getColumnDataProvider() {
562 return new TmfBaseColumnDataProvider();
566 * Constructs the ID based on the experiment name and
567 * <code>fInstanceNb</code>
569 * @param experimentName
570 * the name of the trace name to show in the view
573 protected String
getTreeID(String experimentName
) {
574 return experimentName
+ fInstanceNb
;
578 * When the experiment is loading the cursor will be different so the user
579 * knows the processing is not finished yet.
582 * Indicates if we need to show the waiting cursor, or the
585 protected void waitCursor(final boolean waitInd
) {
586 if ((fTreeViewer
== null) || (fTreeViewer
.getTree().isDisposed())) {
590 Display display
= fTreeViewer
.getControl().getDisplay();
591 if (fWaitCursor
== null) {
592 fWaitCursor
= new Cursor(display
, SWT
.CURSOR_WAIT
);
595 // Perform the updates on the UI thread
596 display
.asyncExec(new Runnable() {
599 if ((fTreeViewer
!= null)
600 && (!fTreeViewer
.getTree().isDisposed())) {
601 Cursor cursor
= null; /* indicates default */
603 cursor
= fWaitCursor
;
605 fTreeViewer
.getControl().setCursor(cursor
);
612 * Performs the request for an experiment and populates the statistics tree
616 * Experiment for which we need the statistics data.
620 protected void requestData(final TmfExperiment experiment
, TmfTimeRange timeRange
) {
621 if (experiment
!= null) {
623 // Check if an update is already ongoing
624 if (checkUpdateBusy(timeRange
)) {
629 for (TmfStatisticsTreeNode node
: ((TmfStatisticsTreeNode
) fTreeViewer
.getInput()).getChildren()) {
630 index
+= (int) node
.getValue().getTotal();
633 // Prepare the global event request
634 fRequest
= new TmfStatisticsRequest(this, experiment
, timeRange
, index
, ExecutionType
.BACKGROUND
, true);
636 experiment
.sendRequest(fRequest
);
642 * Performs the time range request for an experiment and populates the
643 * statistics tree with events.
646 * Experiment for which we need the statistics data.
651 protected void requestTimeRangeData(final TmfExperiment experiment
, TmfTimeRange timeRange
) {
652 if (experiment
!= null) {
654 // Prepare the partial event request
655 fRequestRange
= new TmfStatisticsRequest(this, experiment
, timeRange
, 0, ExecutionType
.FOREGROUND
, false);
656 experiment
.sendRequest(fRequestRange
);
661 * Reset the number of events within the time range
665 protected void resetTimeRangeValue() {
666 // Reset the number of events in the time range
667 String treeID
= getTreeID(TmfExperiment
.getCurrentExperiment().getName());
668 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(treeID
);
669 if (treeModelRoot
.hasChildren()) {
670 treeModelRoot
.resetTimeRangeValue();
675 * Cancels the current ongoing request
678 * The request to be canceled
681 protected void cancelOngoingRequest(ITmfEventRequest request
) {
682 if (request
!= null && !request
.isCompleted()) {
688 * Reset update synchronization information
690 protected void resetUpdateSynchronization() {
691 synchronized (fStatisticsUpdateSyncObj
) {
692 fStatisticsUpdateBusy
= false;
693 fStatisticsUpdatePending
= false;
694 fStatisticsUpdateRange
= null;
699 * Checks if statistic update is ongoing. If it is ongoing the new time
700 * range is stored as pending
704 * @return true if statistic update is ongoing else false
706 protected boolean checkUpdateBusy(TmfTimeRange timeRange
) {
707 synchronized (fStatisticsUpdateSyncObj
) {
708 if (fStatisticsUpdateBusy
) {
709 fStatisticsUpdatePending
= true;
710 if (fStatisticsUpdateRange
== null
711 || timeRange
.getEndTime().compareTo(fStatisticsUpdateRange
.getEndTime()) > 0) {
712 fStatisticsUpdateRange
= timeRange
;
716 fStatisticsUpdateBusy
= true;
722 * Sends pending request (if any)
724 protected void sendPendingUpdate() {
725 synchronized (fStatisticsUpdateSyncObj
) {
726 fStatisticsUpdateBusy
= false;
727 if (fStatisticsUpdatePending
) {
728 fStatisticsUpdatePending
= false;
729 requestData(TmfExperiment
.getCurrentExperiment(), fStatisticsUpdateRange
);
730 fStatisticsUpdateRange
= null;