1 /*******************************************************************************
2 * Copyright (c) 2011 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 *******************************************************************************/
14 package org
.eclipse
.linuxtools
.tmf
.ui
.views
.statistics
;
16 import java
.util
.Vector
;
18 import org
.eclipse
.jface
.viewers
.TreeViewer
;
19 import org
.eclipse
.jface
.viewers
.TreeViewerColumn
;
20 import org
.eclipse
.jface
.viewers
.Viewer
;
21 import org
.eclipse
.jface
.viewers
.ViewerComparator
;
22 import org
.eclipse
.linuxtools
.tmf
.event
.TmfEvent
;
23 import org
.eclipse
.linuxtools
.tmf
.event
.TmfTimeRange
;
24 import org
.eclipse
.linuxtools
.tmf
.experiment
.TmfExperiment
;
25 import org
.eclipse
.linuxtools
.tmf
.request
.ITmfDataRequest
;
26 import org
.eclipse
.linuxtools
.tmf
.request
.ITmfDataRequest
.ExecutionType
;
27 import org
.eclipse
.linuxtools
.tmf
.request
.ITmfEventRequest
;
28 import org
.eclipse
.linuxtools
.tmf
.request
.TmfDataRequest
;
29 import org
.eclipse
.linuxtools
.tmf
.request
.TmfEventRequest
;
30 import org
.eclipse
.linuxtools
.tmf
.signal
.TmfExperimentDisposedSignal
;
31 import org
.eclipse
.linuxtools
.tmf
.signal
.TmfExperimentRangeUpdatedSignal
;
32 import org
.eclipse
.linuxtools
.tmf
.signal
.TmfExperimentSelectedSignal
;
33 import org
.eclipse
.linuxtools
.tmf
.signal
.TmfSignalHandler
;
34 import org
.eclipse
.linuxtools
.tmf
.trace
.ITmfTrace
;
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 * <b><u>TmfStatisticsView</u></b>
58 * The generic Statistics View displays statistics for any kind of traces.
60 * It is implemented according to the MVC pattern. - The model is a TmfStatisticsTreeNode built by the State Manager. - The view is built with a
61 * TreeViewer. - The controller that keeps model and view synchronized is an observer of the model.
64 public class TmfStatisticsView
extends TmfView
{
66 * The ID correspond to the package in which this class is embedded
68 public static final String ID
= "org.eclipse.linuxtools.tmf.ui.views.statistics"; //$NON-NLS-1$
71 private static final String TMF_STATISTICS_VIEW
= "StatisticsView"; //$NON-NLS-1$
74 protected static final Long STATS_INPUT_CHANGED_REFRESH
= 5000L;
77 protected static final int PAGE_SIZE
= 50000; // For background request
79 // The actual tree to display
80 protected TreeViewer fTreeViewer
;
81 // Stores the request to the experiment
82 protected ITmfEventRequest
<TmfEvent
> fRequest
= null;
84 // Update synchronization parameters (used for streaming)
85 protected boolean fStatisticsUpdateBusy
= false;
86 protected boolean fStatisticsUpdatePending
= false;
87 protected TmfTimeRange fStatisticsUpdateRange
= null;
88 protected final Object fStatisticsUpdateSyncObj
= new Object();
90 // Object to store the cursor while waiting for the experiment to load
91 private Cursor fWaitCursor
= null;
93 // Stores the number of instance
94 private static int fCountInstance
= 0;
96 // Number of this instance. Used as an instance ID
97 private int fInstanceNb
;
100 * Constructor of a statistics view.
103 * The name to give to the view.
105 public TmfStatisticsView(String viewName
) {
108 fInstanceNb
= fCountInstance
;
112 * Default constructor.
114 public TmfStatisticsView() {
115 this(TMF_STATISTICS_VIEW
);
120 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
123 public void createPartControl(Composite parent
) {
124 final Vector
<TmfBaseColumnData
> columnDataList
= getColumnDataProvider().getColumnData();
125 parent
.setLayout(new FillLayout());
127 fTreeViewer
= new TreeViewer(parent
, SWT
.BORDER
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
128 fTreeViewer
.setContentProvider(new TmfTreeContentProvider());
129 fTreeViewer
.getTree().setHeaderVisible(true);
130 fTreeViewer
.setUseHashlookup(true);
132 for (final TmfBaseColumnData columnData
: columnDataList
) {
133 final TreeViewerColumn treeColumn
= new TreeViewerColumn(fTreeViewer
, columnData
.getAlignment());
134 treeColumn
.getColumn().setText(columnData
.getHeader());
135 treeColumn
.getColumn().setWidth(columnData
.getWidth());
136 treeColumn
.getColumn().setToolTipText(columnData
.getTooltip());
138 if (columnData
.getComparator() != null) {
139 treeColumn
.getColumn().addSelectionListener(new SelectionAdapter() {
141 public void widgetSelected(SelectionEvent e
) {
142 if (fTreeViewer
.getTree().getSortDirection() == SWT
.UP
|| fTreeViewer
.getTree().getSortColumn() != treeColumn
.getColumn()) {
143 fTreeViewer
.setComparator(columnData
.getComparator());
144 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
146 fTreeViewer
.setComparator(new ViewerComparator() {
148 public int compare(Viewer viewer
, Object e1
, Object e2
) {
149 return -1 * columnData
.getComparator().compare(viewer
, e1
, e2
);
152 fTreeViewer
.getTree().setSortDirection(SWT
.UP
);
154 fTreeViewer
.getTree().setSortColumn(treeColumn
.getColumn());
158 treeColumn
.setLabelProvider(columnData
.getLabelProvider());
161 // Handler that will draw the bar charts.
162 fTreeViewer
.getTree().addListener(SWT
.EraseItem
, new Listener() {
164 public void handleEvent(Event event
) {
165 if (columnDataList
.get(event
.index
).getPercentageProvider() != null) {
166 TmfStatisticsTreeNode node
= (TmfStatisticsTreeNode
) event
.item
.getData();
168 double percentage
= columnDataList
.get(event
.index
).getPercentageProvider().getPercentage(node
);
169 if (percentage
== 0) {
173 if ((event
.detail
& SWT
.SELECTED
) > 0) {
174 Color oldForeground
= event
.gc
.getForeground();
175 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_SELECTION
));
176 event
.gc
.fillRectangle(event
.x
, event
.y
, event
.width
, event
.height
);
177 event
.gc
.setForeground(oldForeground
);
178 event
.detail
&= ~SWT
.SELECTED
;
181 int barWidth
= (int) ((fTreeViewer
.getTree().getColumn(1).getWidth() - 8) * percentage
);
182 int oldAlpha
= event
.gc
.getAlpha();
183 Color oldForeground
= event
.gc
.getForeground();
184 Color oldBackground
= event
.gc
.getBackground();
185 event
.gc
.setAlpha(64);
186 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_BLUE
));
187 event
.gc
.setBackground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_BACKGROUND
));
188 event
.gc
.fillGradientRectangle(event
.x
, event
.y
, barWidth
, event
.height
, true);
189 event
.gc
.drawRectangle(event
.x
, event
.y
, barWidth
, event
.height
);
190 event
.gc
.setForeground(oldForeground
);
191 event
.gc
.setBackground(oldBackground
);
192 event
.gc
.setAlpha(oldAlpha
);
193 event
.detail
&= ~SWT
.BACKGROUND
;
198 fTreeViewer
.setComparator(columnDataList
.get(0).getComparator());
199 fTreeViewer
.getTree().setSortColumn(fTreeViewer
.getTree().getColumn(0));
200 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
202 // Read current data if any available
203 TmfExperiment
<?
> experiment
= TmfExperiment
.getCurrentExperiment();
204 if (experiment
!= null) {
205 // Insert the statistics data into the tree
206 @SuppressWarnings({ "rawtypes", "unchecked" })
207 TmfExperimentSelectedSignal
<?
> signal
= new TmfExperimentSelectedSignal(this, experiment
);
208 experimentSelected(signal
);
214 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
217 public void dispose() {
219 if (fWaitCursor
!= null) {
220 fWaitCursor
.dispose();
224 TmfStatisticsTreeRootFactory
.removeAll();
229 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
232 public void setFocus() {
233 fTreeViewer
.getTree().setFocus();
239 public void modelInputChanged(boolean complete
) {
240 // Ignore update if disposed
241 if (fTreeViewer
.getTree().isDisposed())
244 fTreeViewer
.getTree().getDisplay().asyncExec(new Runnable() {
247 if (!fTreeViewer
.getTree().isDisposed())
248 fTreeViewer
.refresh();
258 * Called when an experiment request has failed or has been canceled Remove the data retrieved from the experiment from the statistics tree.
261 * the experiment name
263 public void modelIncomplete(String name
) {
264 Object input
= fTreeViewer
.getInput();
265 if (input
!= null && input
instanceof TmfStatisticsTreeNode
) {
266 // The data from this experiment is invalid and shall be removed to
267 // refresh upon next selection
268 TmfStatisticsTreeRootFactory
.removeStatTreeRoot(getTreeID(name
));
270 // Reset synchronization information
271 resetUpdateSynchronization();
272 modelInputChanged(false);
278 * If the user choose another experiment, the current must be disposed.
283 public void experimentDisposed(TmfExperimentDisposedSignal
<?
extends TmfEvent
> signal
) {
284 cancelOngoingRequest();
288 * Handler called when an experiment is selected. Checks if the experiment has changed and requests the selected experiment if it has not yet been
292 * contains the information about the selection.
295 public void experimentSelected(TmfExperimentSelectedSignal
<?
extends TmfEvent
> signal
) {
296 if (signal
!= null) {
297 TmfExperiment
<?
> experiment
= signal
.getExperiment();
298 String experimentName
= experiment
.getName();
300 if (TmfStatisticsTreeRootFactory
.containsTreeRoot(getTreeID(experimentName
))) {
301 // The experiment root is already present
302 TmfStatisticsTreeNode experimentTreeNode
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experimentName
));
304 ITmfTrace
[] traces
= experiment
.getTraces();
306 // check if there is partial data loaded in the experiment
307 int numTraces
= experiment
.getTraces().length
;
308 int numNodeTraces
= experimentTreeNode
.getNbChildren();
310 if (numTraces
== numNodeTraces
) {
312 // Detect if the experiment contains the same traces as when
313 // previously selected
314 for (int i
= 0; i
< numTraces
; i
++) {
315 String traceName
= traces
[i
].getName();
316 if (!experimentTreeNode
.containsChild(traceName
)) {
323 // no need to reload data, all traces are already loaded
324 fTreeViewer
.setInput(experimentTreeNode
);
326 resetUpdateSynchronization();
330 experimentTreeNode
.reset();
333 TmfStatisticsTreeRootFactory
.addStatsTreeRoot(getTreeID(experimentName
), getStatisticData());
336 resetUpdateSynchronization();
338 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experiment
.getName()));
340 // if the model has contents, clear to start over
341 if (treeModelRoot
.hasChildren()) {
342 treeModelRoot
.reset();
345 // set input to a clean data model
346 fTreeViewer
.setInput(treeModelRoot
);
348 // if the data is not available or has changed, reload it
349 requestData(experiment
, experiment
.getTimeRange());
356 @SuppressWarnings("unchecked")
358 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal
) {
359 TmfExperiment
<TmfEvent
> experiment
= (TmfExperiment
<TmfEvent
>) signal
.getExperiment();
361 if (! experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
365 requestData(experiment
, signal
.getRange());
370 * Return the size of the request when performing background request.
372 * @return the block size for background request.
374 protected int getIndexPageSize() {
380 * @return the quantity of data to retrieve before a refresh of the view is performed.
382 protected long getInputChangedRefresh() {
383 return STATS_INPUT_CHANGED_REFRESH
;
387 * This method can be overridden to implement another way to represent the statistics data and to retrieve the information for display.
389 * @return a TmfStatisticsData object.
391 protected AbsTmfStatisticsTree
getStatisticData() {
392 return new TmfBaseStatisticsTree();
396 * This method can be overridden to change the representation of the data in the columns.
398 * @return an object implementing ITmfBaseColumnDataProvider.
400 protected ITmfColumnDataProvider
getColumnDataProvider() {
401 return new TmfBaseColumnDataProvider();
405 * Construct the ID based on the experiment name
406 * @param experimentName the name of the trace name to show in the view
409 protected String
getTreeID(String experimentName
) {
410 return experimentName
+ fInstanceNb
;
414 * When the experiment is loading the cursor will be different so the user know the processing is not finished yet.
417 * indicates if we need to show the waiting cursor, or the default one
419 protected void waitCursor(final boolean waitInd
) {
420 if ((fTreeViewer
== null) || (fTreeViewer
.getTree().isDisposed())) {
424 Display display
= fTreeViewer
.getControl().getDisplay();
425 if (fWaitCursor
== null) {
426 fWaitCursor
= new Cursor(display
, SWT
.CURSOR_WAIT
);
429 // Perform the updates on the UI thread
430 display
.asyncExec(new Runnable() {
433 if ((fTreeViewer
!= null) && (!fTreeViewer
.getTree().isDisposed())) {
434 Cursor cursor
= null; /* indicates default */
436 cursor
= fWaitCursor
;
438 fTreeViewer
.getControl().setCursor(cursor
);
445 * Perform the request for an experiment and populates the statistics tree with event.
446 * @param experiment experiment for which we need the statistics data.
447 * @param time range to request
449 @SuppressWarnings("unchecked")
450 protected void requestData(final TmfExperiment
<?
> experiment
, TmfTimeRange timeRange
) {
451 if (experiment
!= null) {
453 // Check if update is already ongoing
454 if (checkUpdateBusy(timeRange
)) {
459 for (TmfStatisticsTreeNode node
: ((TmfStatisticsTreeNode
) fTreeViewer
.getInput()).getChildren()) {
460 index
+= (int) node
.getValue().nbEvents
;
463 // Preparation of the event request
464 fRequest
= new TmfEventRequest
<TmfEvent
>(TmfEvent
.class, timeRange
, index
, TmfDataRequest
.ALL_DATA
, getIndexPageSize(), ExecutionType
.BACKGROUND
) {
467 public void handleData(TmfEvent data
) {
468 super.handleData(data
);
470 AbsTmfStatisticsTree statisticsData
= TmfStatisticsTreeRootFactory
.getStatTree(getTreeID(experiment
.getName()));
472 final String traceName
= data
.getParentTrace().getName();
473 ITmfExtraEventInfo extraInfo
= new ITmfExtraEventInfo() {
475 public String
getTraceName() {
476 if (traceName
== null) {
477 return Messages
.TmfStatisticsView_UnknownTraceName
;
482 statisticsData
.registerEvent(data
, extraInfo
);
483 statisticsData
.increase(data
, extraInfo
, 1);
485 if ((getNbRead() % getInputChangedRefresh()) == 0) {
486 modelInputChanged(false);
492 public void handleSuccess() {
493 super.handleSuccess();
494 modelInputChanged(true);
499 public void handleFailure() {
500 super.handleFailure();
501 modelIncomplete(experiment
.getName());
505 public void handleCancel() {
506 super.handleCancel();
507 modelIncomplete(experiment
.getName());
510 ((TmfExperiment
<TmfEvent
>) experiment
).sendRequest((ITmfDataRequest
<TmfEvent
>) fRequest
);
516 * Cancels the current ongoing request
518 protected void cancelOngoingRequest() {
519 if (fRequest
!= null && !fRequest
.isCompleted()) {
525 * Reset update synchronization information
527 protected void resetUpdateSynchronization() {
528 synchronized (fStatisticsUpdateSyncObj
) {
529 fStatisticsUpdateBusy
= false;
530 fStatisticsUpdatePending
= false;
535 * Checks if statistic update is ongoing. If it is ongoing the new time range is stored as pending
537 * @param timeRange - new time range
538 * @return true if statistic update is ongoing else false
540 protected boolean checkUpdateBusy(TmfTimeRange timeRange
) {
541 synchronized (fStatisticsUpdateSyncObj
) {
542 if (fStatisticsUpdateBusy
) {
543 fStatisticsUpdatePending
= true;
544 fStatisticsUpdateRange
= timeRange
;
547 fStatisticsUpdateBusy
= true;
553 * Sends pending request (if any)
555 protected void sendPendingUpdate() {
556 synchronized (fStatisticsUpdateSyncObj
) {
557 fStatisticsUpdateBusy
= false;
558 if (fStatisticsUpdatePending
) {
559 fStatisticsUpdatePending
= false;
560 requestData(TmfExperiment
.getCurrentExperiment(), fStatisticsUpdateRange
);