1 /*******************************************************************************
2 * Copyright (c) 2011, 20112 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
13 *******************************************************************************/
15 package org
.eclipse
.linuxtools
.tmf
.ui
.views
.statistics
;
17 import java
.util
.List
;
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
.TmfSignalHandler
;
33 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.ITmfTrace
;
34 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.TmfExperiment
;
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
;
56 * The generic Statistics View displays statistics for any kind of traces.
58 * It is implemented according to the MVC pattern. - The model is a TmfStatisticsTreeNode built by the State Manager. - The view is built with a
59 * TreeViewer. - The controller that keeps model and view synchronized is an observer of the model.
63 * @author @author Mathieu Denis
65 public class TmfStatisticsView
extends TmfView
{
67 * The ID correspond to the package in which this class is embedded
69 public static final String ID
= "org.eclipse.linuxtools.tmf.ui.views.statistics"; //$NON-NLS-1$
73 public static final String TMF_STATISTICS_VIEW
= "StatisticsView"; //$NON-NLS-1$
77 protected static final Long STATS_INPUT_CHANGED_REFRESH
= 5000L;
79 * Default PAGE_SIZE for background requests
81 protected static final int PAGE_SIZE
= 50000;
83 * The actual tree viewer to display
85 protected TreeViewer fTreeViewer
;
87 * Stores the request to the experiment
89 protected ITmfEventRequest
<ITmfEvent
> fRequest
= null;
91 * Update synchronization parameter (used for streaming): Update busy indicator
93 protected boolean fStatisticsUpdateBusy
= false;
95 * Update synchronization parameter (used for streaming): Update pending indicator
97 protected boolean fStatisticsUpdatePending
= false;
99 * Update synchronization parameter (used for streaming): Pending Update time range
101 protected TmfTimeRange fStatisticsUpdateRange
= null;
103 * Update synchronization object.
105 protected final Object fStatisticsUpdateSyncObj
= new Object();
107 * Flag to force request the data from trace
109 protected boolean fRequestData
= false;
111 * Object to store the cursor while waiting for the experiment to load
113 private Cursor fWaitCursor
= null;
115 * View instance counter (for multiple statistic views)
117 private static int fCountInstance
= 0;
119 * Number of this instance. Used as an instance ID.
121 private final int fInstanceNb
;
124 * Constructor of a statistics view.
127 * The name to give to the view.
129 public TmfStatisticsView(String viewName
) {
132 fInstanceNb
= fCountInstance
;
136 * Default constructor.
138 public TmfStatisticsView() {
139 this(TMF_STATISTICS_VIEW
);
144 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
147 public void createPartControl(Composite parent
) {
148 final List
<TmfBaseColumnData
> columnDataList
= getColumnDataProvider().getColumnData();
149 parent
.setLayout(new FillLayout());
151 fTreeViewer
= new TreeViewer(parent
, SWT
.BORDER
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
152 fTreeViewer
.setContentProvider(new TmfTreeContentProvider());
153 fTreeViewer
.getTree().setHeaderVisible(true);
154 fTreeViewer
.setUseHashlookup(true);
156 for (final TmfBaseColumnData columnData
: columnDataList
) {
157 final TreeViewerColumn treeColumn
= new TreeViewerColumn(fTreeViewer
, columnData
.getAlignment());
158 treeColumn
.getColumn().setText(columnData
.getHeader());
159 treeColumn
.getColumn().setWidth(columnData
.getWidth());
160 treeColumn
.getColumn().setToolTipText(columnData
.getTooltip());
162 if (columnData
.getComparator() != null) {
163 treeColumn
.getColumn().addSelectionListener(new SelectionAdapter() {
165 public void widgetSelected(SelectionEvent e
) {
166 if (fTreeViewer
.getTree().getSortDirection() == SWT
.UP
|| fTreeViewer
.getTree().getSortColumn() != treeColumn
.getColumn()) {
167 fTreeViewer
.setComparator(columnData
.getComparator());
168 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
170 fTreeViewer
.setComparator(new ViewerComparator() {
172 public int compare(Viewer viewer
, Object e1
, Object e2
) {
173 return -1 * columnData
.getComparator().compare(viewer
, e1
, e2
);
176 fTreeViewer
.getTree().setSortDirection(SWT
.UP
);
178 fTreeViewer
.getTree().setSortColumn(treeColumn
.getColumn());
182 treeColumn
.setLabelProvider(columnData
.getLabelProvider());
185 // Handler that will draw the bar charts.
186 fTreeViewer
.getTree().addListener(SWT
.EraseItem
, new Listener() {
188 public void handleEvent(Event event
) {
189 if (columnDataList
.get(event
.index
).getPercentageProvider() != null) {
190 TmfStatisticsTreeNode node
= (TmfStatisticsTreeNode
) event
.item
.getData();
192 double percentage
= columnDataList
.get(event
.index
).getPercentageProvider().getPercentage(node
);
193 if (percentage
== 0) {
197 if ((event
.detail
& SWT
.SELECTED
) > 0) {
198 Color oldForeground
= event
.gc
.getForeground();
199 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_SELECTION
));
200 event
.gc
.fillRectangle(event
.x
, event
.y
, event
.width
, event
.height
);
201 event
.gc
.setForeground(oldForeground
);
202 event
.detail
&= ~SWT
.SELECTED
;
205 int barWidth
= (int) ((fTreeViewer
.getTree().getColumn(1).getWidth() - 8) * percentage
);
206 int oldAlpha
= event
.gc
.getAlpha();
207 Color oldForeground
= event
.gc
.getForeground();
208 Color oldBackground
= event
.gc
.getBackground();
209 event
.gc
.setAlpha(64);
210 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_BLUE
));
211 event
.gc
.setBackground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_BACKGROUND
));
212 event
.gc
.fillGradientRectangle(event
.x
, event
.y
, barWidth
, event
.height
, true);
213 event
.gc
.drawRectangle(event
.x
, event
.y
, barWidth
, event
.height
);
214 event
.gc
.setForeground(oldForeground
);
215 event
.gc
.setBackground(oldBackground
);
216 event
.gc
.setAlpha(oldAlpha
);
217 event
.detail
&= ~SWT
.BACKGROUND
;
222 fTreeViewer
.setComparator(columnDataList
.get(0).getComparator());
223 fTreeViewer
.getTree().setSortColumn(fTreeViewer
.getTree().getColumn(0));
224 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
226 // Read current data if any available
227 TmfExperiment
<?
> experiment
= TmfExperiment
.getCurrentExperiment();
228 if (experiment
!= null) {
230 // Insert the statistics data into the tree
231 @SuppressWarnings({ "rawtypes", "unchecked" })
232 TmfExperimentSelectedSignal
<?
> signal
= new TmfExperimentSelectedSignal(this, experiment
);
233 experimentSelected(signal
);
239 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
242 public void dispose() {
244 if (fWaitCursor
!= null) {
245 fWaitCursor
.dispose();
248 // Make sure there is no request running before removing the statistics tree
249 cancelOngoingRequest();
251 TmfStatisticsTreeRootFactory
.removeAll();
256 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
259 public void setFocus() {
260 fTreeViewer
.getTree().setFocus();
267 * Should a pending update be sent afterwards or not
269 public void modelInputChanged(boolean complete
) {
270 // Ignore update if disposed
271 if (fTreeViewer
.getTree().isDisposed()) {
275 fTreeViewer
.getTree().getDisplay().asyncExec(new Runnable() {
278 if (!fTreeViewer
.getTree().isDisposed()) {
279 fTreeViewer
.refresh();
290 * Called when an experiment request has failed or has been canceled Remove the data retrieved from the experiment from the statistics tree.
292 * @param name The experiment name
294 public void modelIncomplete(String name
) {
295 Object input
= fTreeViewer
.getInput();
296 if (input
!= null && input
instanceof TmfStatisticsTreeNode
) {
297 // The data from this experiment is invalid and shall be removed to
298 // refresh upon next selection
299 TmfStatisticsTreeRootFactory
.removeStatTreeRoot(getTreeID(name
));
301 // Reset synchronization information
302 resetUpdateSynchronization();
303 modelInputChanged(false);
309 * Handles the signal about disposal of the current experiment.
311 * @param signal The disposed signal
314 public void experimentDisposed(TmfExperimentDisposedSignal
<?
extends ITmfEvent
> signal
) {
315 if (signal
.getExperiment() != TmfExperiment
.getCurrentExperiment()) {
318 cancelOngoingRequest();
322 * Handler called when an experiment is selected. Checks if the experiment has changed
323 * and requests the selected experiment if it has not yet been cached.
325 * @param signal Contains the information about the selection.
328 public void experimentSelected(TmfExperimentSelectedSignal
<?
extends ITmfEvent
> signal
) {
329 if (signal
!= null) {
330 TmfExperiment
<?
> experiment
= signal
.getExperiment();
331 String experimentName
= experiment
.getName();
333 if (TmfStatisticsTreeRootFactory
.containsTreeRoot(getTreeID(experimentName
))) {
334 // The experiment root is already present
335 TmfStatisticsTreeNode experimentTreeNode
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experimentName
));
337 @SuppressWarnings("rawtypes")
338 ITmfTrace
[] traces
= experiment
.getTraces();
340 // check if there is partial data loaded in the experiment
341 int numTraces
= experiment
.getTraces().length
;
342 int numNodeTraces
= experimentTreeNode
.getNbChildren();
344 if (numTraces
== numNodeTraces
) {
346 // Detect if the experiment contains the same traces as when
347 // previously selected
348 for (int i
= 0; i
< numTraces
; i
++) {
349 String traceName
= traces
[i
].getName();
350 if (!experimentTreeNode
.containsChild(traceName
)) {
357 // no need to reload data, all traces are already loaded
358 fTreeViewer
.setInput(experimentTreeNode
);
360 resetUpdateSynchronization();
364 experimentTreeNode
.reset();
367 TmfStatisticsTreeRootFactory
.addStatsTreeRoot(getTreeID(experimentName
), getStatisticData());
370 resetUpdateSynchronization();
372 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experiment
.getName()));
374 // if the model has contents, clear to start over
375 if (treeModelRoot
.hasChildren()) {
376 treeModelRoot
.reset();
379 // set input to a clean data model
380 fTreeViewer
.setInput(treeModelRoot
);
383 requestData(experiment
, experiment
.getTimeRange());
384 fRequestData
= false;
390 * Handles the signal about new experiment range.
391 * @param signal The experiment range updated signal
393 @SuppressWarnings("unchecked")
395 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal
) {
396 TmfExperiment
<ITmfEvent
> experiment
= (TmfExperiment
<ITmfEvent
>) signal
.getExperiment();
398 if (! experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
402 requestData(experiment
, signal
.getRange());
407 * Return the size of the request when performing background request.
409 * @return the block size for background request.
411 protected int getIndexPageSize() {
416 * Returns the quantity of data to retrieve before a refresh of the view is performed
418 * @return the quantity of data to retrieve before a refresh of the view is performed.
420 protected long getInputChangedRefresh() {
421 return STATS_INPUT_CHANGED_REFRESH
;
425 * This method can be overridden to implement another way to represent the statistics data and to retrieve the information for display.
427 * @return a TmfStatisticsData object.
429 protected AbsTmfStatisticsTree
getStatisticData() {
430 return new TmfBaseStatisticsTree();
434 * This method can be overridden to change the representation of the data in the columns.
436 * @return an object implementing ITmfBaseColumnDataProvider.
438 protected ITmfColumnDataProvider
getColumnDataProvider() {
439 return new TmfBaseColumnDataProvider();
443 * Constructs the ID based on the experiment name and <code>fInstanceNb</code>
445 * @param experimentName the name of the trace name to show in the view
448 protected String
getTreeID(String experimentName
) {
449 return experimentName
+ fInstanceNb
;
453 * When the experiment is loading the cursor will be different so the user know the processing is not finished yet.
455 * @param waitInd Indicates if we need to show the waiting cursor, or the default one
457 protected void waitCursor(final boolean waitInd
) {
458 if ((fTreeViewer
== null) || (fTreeViewer
.getTree().isDisposed())) {
462 Display display
= fTreeViewer
.getControl().getDisplay();
463 if (fWaitCursor
== null) {
464 fWaitCursor
= new Cursor(display
, SWT
.CURSOR_WAIT
);
467 // Perform the updates on the UI thread
468 display
.asyncExec(new Runnable() {
471 if ((fTreeViewer
!= null) && (!fTreeViewer
.getTree().isDisposed())) {
472 Cursor cursor
= null; /* indicates default */
474 cursor
= fWaitCursor
;
476 fTreeViewer
.getControl().setCursor(cursor
);
483 * Perform the request for an experiment and populates the statistics tree with event.
485 * @param experiment experiment for which we need the statistics data.
486 * @param timeRange to request
488 @SuppressWarnings("unchecked")
489 protected void requestData(final TmfExperiment
<?
> experiment
, TmfTimeRange timeRange
) {
490 if (experiment
!= null) {
492 // Check if update is already ongoing
493 if (checkUpdateBusy(timeRange
)) {
498 for (TmfStatisticsTreeNode node
: ((TmfStatisticsTreeNode
) fTreeViewer
.getInput()).getChildren()) {
499 index
+= (int) node
.getValue().nbEvents
;
502 // Preparation of the event request
503 fRequest
= new TmfEventRequest
<ITmfEvent
>(ITmfEvent
.class, timeRange
, index
, TmfDataRequest
.ALL_DATA
, getIndexPageSize(), ExecutionType
.BACKGROUND
) {
505 private final AbsTmfStatisticsTree statisticsData
= TmfStatisticsTreeRootFactory
.getStatTree(getTreeID(experiment
.getName()));
508 public void handleData(ITmfEvent data
) {
509 super.handleData(data
);
511 final String traceName
= data
.getTrace().getName();
512 ITmfExtraEventInfo extraInfo
= new ITmfExtraEventInfo() {
514 public String
getTraceName() {
515 if (traceName
== null) {
516 return Messages
.TmfStatisticsView_UnknownTraceName
;
521 statisticsData
.registerEvent(data
, extraInfo
);
522 statisticsData
.increase(data
, extraInfo
, 1);
524 if ((getNbRead() % getInputChangedRefresh()) == 0) {
525 modelInputChanged(false);
531 public void handleSuccess() {
532 super.handleSuccess();
533 modelInputChanged(true);
538 public void handleFailure() {
539 super.handleFailure();
540 modelIncomplete(experiment
.getName());
544 public void handleCancel() {
545 super.handleCancel();
546 modelIncomplete(experiment
.getName());
549 ((TmfExperiment
<ITmfEvent
>) experiment
).sendRequest(fRequest
);
555 * Cancels the current ongoing request
557 protected void cancelOngoingRequest() {
558 if (fRequest
!= null && !fRequest
.isCompleted()) {
564 * Reset update synchronization information
566 protected void resetUpdateSynchronization() {
567 synchronized (fStatisticsUpdateSyncObj
) {
568 fStatisticsUpdateBusy
= false;
569 fStatisticsUpdatePending
= false;
574 * Checks if statistic update is ongoing. If it is ongoing the new time range is stored as pending
576 * @param timeRange - new time range
577 * @return true if statistic update is ongoing else false
579 protected boolean checkUpdateBusy(TmfTimeRange timeRange
) {
580 synchronized (fStatisticsUpdateSyncObj
) {
581 if (fStatisticsUpdateBusy
) {
582 fStatisticsUpdatePending
= true;
583 fStatisticsUpdateRange
= timeRange
;
586 fStatisticsUpdateBusy
= true;
592 * Sends pending request (if any)
594 protected void sendPendingUpdate() {
595 synchronized (fStatisticsUpdateSyncObj
) {
596 fStatisticsUpdateBusy
= false;
597 if (fStatisticsUpdatePending
) {
598 fStatisticsUpdatePending
= false;
599 requestData(TmfExperiment
.getCurrentExperiment(), fStatisticsUpdateRange
);