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
;
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
.request
.TmfDataRequest
;
29 import org
.eclipse
.linuxtools
.tmf
.core
.request
.TmfEventRequest
;
30 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentDisposedSignal
;
31 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentRangeUpdatedSignal
;
32 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfExperimentSelectedSignal
;
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 TmfStatisticsTreeNode built by the State Manager. - The view is built with a
60 * TreeViewer. - The controller that keeps model and view synchronized is an observer of the model.
64 * @author @author Mathieu Denis
66 public class TmfStatisticsView
extends TmfView
{
68 * The ID correspond to the package in which this class is embedded
70 public static final String ID
= "org.eclipse.linuxtools.tmf.ui.views.statistics"; //$NON-NLS-1$
74 public static final String TMF_STATISTICS_VIEW
= "StatisticsView"; //$NON-NLS-1$
78 protected static final Long STATS_INPUT_CHANGED_REFRESH
= 5000L;
80 * Default PAGE_SIZE for background requests
82 protected static final int PAGE_SIZE
= 50000;
84 * The actual tree viewer to display
86 protected TreeViewer fTreeViewer
;
88 * Stores the request to the experiment
90 protected ITmfEventRequest
<ITmfEvent
> fRequest
= null;
92 * Update synchronization parameter (used for streaming): Update busy indicator
94 protected boolean fStatisticsUpdateBusy
= false;
96 * Update synchronization parameter (used for streaming): Update pending indicator
98 protected boolean fStatisticsUpdatePending
= false;
100 * Update synchronization parameter (used for streaming): Pending Update time range
102 protected TmfTimeRange fStatisticsUpdateRange
= null;
104 * Update synchronization object.
106 protected final Object fStatisticsUpdateSyncObj
= new Object();
108 * Flag to force request the data from trace
110 protected boolean fRequestData
= false;
112 * Object to store the cursor while waiting for the experiment to load
114 private Cursor fWaitCursor
= null;
116 * View instance counter (for multiple statistic views)
118 private static int fCountInstance
= 0;
120 * Number of this instance. Used as an instance ID.
122 private final int fInstanceNb
;
125 * Constructor of a statistics view.
128 * The name to give to the view.
130 public TmfStatisticsView(String viewName
) {
133 fInstanceNb
= fCountInstance
;
137 * Default constructor.
139 public TmfStatisticsView() {
140 this(TMF_STATISTICS_VIEW
);
145 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
148 public void createPartControl(Composite parent
) {
149 final List
<TmfBaseColumnData
> columnDataList
= getColumnDataProvider().getColumnData();
150 parent
.setLayout(new FillLayout());
152 fTreeViewer
= new TreeViewer(parent
, SWT
.BORDER
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
153 fTreeViewer
.setContentProvider(new TmfTreeContentProvider());
154 fTreeViewer
.getTree().setHeaderVisible(true);
155 fTreeViewer
.setUseHashlookup(true);
157 for (final TmfBaseColumnData columnData
: columnDataList
) {
158 final TreeViewerColumn treeColumn
= new TreeViewerColumn(fTreeViewer
, columnData
.getAlignment());
159 treeColumn
.getColumn().setText(columnData
.getHeader());
160 treeColumn
.getColumn().setWidth(columnData
.getWidth());
161 treeColumn
.getColumn().setToolTipText(columnData
.getTooltip());
163 if (columnData
.getComparator() != null) {
164 treeColumn
.getColumn().addSelectionListener(new SelectionAdapter() {
166 public void widgetSelected(SelectionEvent e
) {
167 if (fTreeViewer
.getTree().getSortDirection() == SWT
.UP
|| fTreeViewer
.getTree().getSortColumn() != treeColumn
.getColumn()) {
168 fTreeViewer
.setComparator(columnData
.getComparator());
169 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
171 fTreeViewer
.setComparator(new ViewerComparator() {
173 public int compare(Viewer viewer
, Object e1
, Object e2
) {
174 return -1 * columnData
.getComparator().compare(viewer
, e1
, e2
);
177 fTreeViewer
.getTree().setSortDirection(SWT
.UP
);
179 fTreeViewer
.getTree().setSortColumn(treeColumn
.getColumn());
183 treeColumn
.setLabelProvider(columnData
.getLabelProvider());
186 // Handler that will draw the bar charts.
187 fTreeViewer
.getTree().addListener(SWT
.EraseItem
, new Listener() {
189 public void handleEvent(Event event
) {
190 if (columnDataList
.get(event
.index
).getPercentageProvider() != null) {
191 TmfStatisticsTreeNode node
= (TmfStatisticsTreeNode
) event
.item
.getData();
193 double percentage
= columnDataList
.get(event
.index
).getPercentageProvider().getPercentage(node
);
194 if (percentage
== 0) {
198 if ((event
.detail
& SWT
.SELECTED
) > 0) {
199 Color oldForeground
= event
.gc
.getForeground();
200 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_SELECTION
));
201 event
.gc
.fillRectangle(event
.x
, event
.y
, event
.width
, event
.height
);
202 event
.gc
.setForeground(oldForeground
);
203 event
.detail
&= ~SWT
.SELECTED
;
206 int barWidth
= (int) ((fTreeViewer
.getTree().getColumn(1).getWidth() - 8) * percentage
);
207 int oldAlpha
= event
.gc
.getAlpha();
208 Color oldForeground
= event
.gc
.getForeground();
209 Color oldBackground
= event
.gc
.getBackground();
210 event
.gc
.setAlpha(64);
211 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_BLUE
));
212 event
.gc
.setBackground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_BACKGROUND
));
213 event
.gc
.fillGradientRectangle(event
.x
, event
.y
, barWidth
, event
.height
, true);
214 event
.gc
.drawRectangle(event
.x
, event
.y
, barWidth
, event
.height
);
215 event
.gc
.setForeground(oldForeground
);
216 event
.gc
.setBackground(oldBackground
);
217 event
.gc
.setAlpha(oldAlpha
);
218 event
.detail
&= ~SWT
.BACKGROUND
;
223 fTreeViewer
.setComparator(columnDataList
.get(0).getComparator());
224 fTreeViewer
.getTree().setSortColumn(fTreeViewer
.getTree().getColumn(0));
225 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
227 // Read current data if any available
228 TmfExperiment
<?
> experiment
= TmfExperiment
.getCurrentExperiment();
229 if (experiment
!= null) {
231 // Insert the statistics data into the tree
232 @SuppressWarnings({ "rawtypes", "unchecked" })
233 TmfExperimentSelectedSignal
<?
> signal
= new TmfExperimentSelectedSignal(this, experiment
);
234 experimentSelected(signal
);
240 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
243 public void dispose() {
245 if (fWaitCursor
!= null) {
246 fWaitCursor
.dispose();
250 TmfStatisticsTreeRootFactory
.removeAll();
255 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
258 public void setFocus() {
259 fTreeViewer
.getTree().setFocus();
266 * Should a pending update be sent afterwards or not
268 public void modelInputChanged(boolean complete
) {
269 // Ignore update if disposed
270 if (fTreeViewer
.getTree().isDisposed()) {
274 fTreeViewer
.getTree().getDisplay().asyncExec(new Runnable() {
277 if (!fTreeViewer
.getTree().isDisposed()) {
278 fTreeViewer
.refresh();
289 * Called when an experiment request has failed or has been canceled Remove the data retrieved from the experiment from the statistics tree.
291 * @param name The experiment name
293 public void modelIncomplete(String name
) {
294 Object input
= fTreeViewer
.getInput();
295 if (input
!= null && input
instanceof TmfStatisticsTreeNode
) {
296 // The data from this experiment is invalid and shall be removed to
297 // refresh upon next selection
298 TmfStatisticsTreeRootFactory
.removeStatTreeRoot(getTreeID(name
));
300 // Reset synchronization information
301 resetUpdateSynchronization();
302 modelInputChanged(false);
308 * Handles the signal about disposal of the current experiment.
310 * @param signal The disposed signal
313 public void experimentDisposed(TmfExperimentDisposedSignal
<?
extends ITmfEvent
> signal
) {
314 if (signal
.getExperiment() != TmfExperiment
.getCurrentExperiment()) {
317 cancelOngoingRequest();
321 * Handler called when an experiment is selected. Checks if the experiment has changed
322 * and requests the selected experiment if it has not yet been cached.
324 * @param signal Contains the information about the selection.
327 public void experimentSelected(TmfExperimentSelectedSignal
<?
extends ITmfEvent
> signal
) {
328 if (signal
!= null) {
329 TmfExperiment
<?
> experiment
= signal
.getExperiment();
330 String experimentName
= experiment
.getName();
332 if (TmfStatisticsTreeRootFactory
.containsTreeRoot(getTreeID(experimentName
))) {
333 // The experiment root is already present
334 TmfStatisticsTreeNode experimentTreeNode
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experimentName
));
336 @SuppressWarnings("rawtypes")
337 ITmfTrace
[] traces
= experiment
.getTraces();
339 // check if there is partial data loaded in the experiment
340 int numTraces
= experiment
.getTraces().length
;
341 int numNodeTraces
= experimentTreeNode
.getNbChildren();
343 if (numTraces
== numNodeTraces
) {
345 // Detect if the experiment contains the same traces as when
346 // previously selected
347 for (int i
= 0; i
< numTraces
; i
++) {
348 String traceName
= traces
[i
].getName();
349 if (!experimentTreeNode
.containsChild(traceName
)) {
356 // no need to reload data, all traces are already loaded
357 fTreeViewer
.setInput(experimentTreeNode
);
359 resetUpdateSynchronization();
363 experimentTreeNode
.reset();
366 TmfStatisticsTreeRootFactory
.addStatsTreeRoot(getTreeID(experimentName
), getStatisticData());
369 resetUpdateSynchronization();
371 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experiment
.getName()));
373 // if the model has contents, clear to start over
374 if (treeModelRoot
.hasChildren()) {
375 treeModelRoot
.reset();
378 // set input to a clean data model
379 fTreeViewer
.setInput(treeModelRoot
);
382 requestData(experiment
, experiment
.getTimeRange());
383 fRequestData
= false;
389 * Handles the signal about new experiment range.
390 * @param signal The experiment range updated signal
392 @SuppressWarnings("unchecked")
394 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal
) {
395 TmfExperiment
<ITmfEvent
> experiment
= (TmfExperiment
<ITmfEvent
>) signal
.getExperiment();
397 if (! experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
401 requestData(experiment
, signal
.getRange());
406 * Return the size of the request when performing background request.
408 * @return the block size for background request.
410 protected int getIndexPageSize() {
415 * Returns the quantity of data to retrieve before a refresh of the view is performed
417 * @return the quantity of data to retrieve before a refresh of the view is performed.
419 protected long getInputChangedRefresh() {
420 return STATS_INPUT_CHANGED_REFRESH
;
424 * This method can be overridden to implement another way to represent the statistics data and to retrieve the information for display.
426 * @return a TmfStatisticsData object.
428 protected AbsTmfStatisticsTree
getStatisticData() {
429 return new TmfBaseStatisticsTree();
433 * This method can be overridden to change the representation of the data in the columns.
435 * @return an object implementing ITmfBaseColumnDataProvider.
437 protected ITmfColumnDataProvider
getColumnDataProvider() {
438 return new TmfBaseColumnDataProvider();
442 * Constructs the ID based on the experiment name and <code>fInstanceNb</code>
444 * @param experimentName the name of the trace name to show in the view
447 protected String
getTreeID(String experimentName
) {
448 return experimentName
+ fInstanceNb
;
452 * When the experiment is loading the cursor will be different so the user know the processing is not finished yet.
454 * @param waitInd Indicates if we need to show the waiting cursor, or the default one
456 protected void waitCursor(final boolean waitInd
) {
457 if ((fTreeViewer
== null) || (fTreeViewer
.getTree().isDisposed())) {
461 Display display
= fTreeViewer
.getControl().getDisplay();
462 if (fWaitCursor
== null) {
463 fWaitCursor
= new Cursor(display
, SWT
.CURSOR_WAIT
);
466 // Perform the updates on the UI thread
467 display
.asyncExec(new Runnable() {
470 if ((fTreeViewer
!= null) && (!fTreeViewer
.getTree().isDisposed())) {
471 Cursor cursor
= null; /* indicates default */
473 cursor
= fWaitCursor
;
475 fTreeViewer
.getControl().setCursor(cursor
);
482 * Perform the request for an experiment and populates the statistics tree with event.
484 * @param experiment experiment for which we need the statistics data.
485 * @param timeRange to request
487 @SuppressWarnings("unchecked")
488 protected void requestData(final TmfExperiment
<?
> experiment
, TmfTimeRange timeRange
) {
489 if (experiment
!= null) {
491 // Check if update is already ongoing
492 if (checkUpdateBusy(timeRange
)) {
497 for (TmfStatisticsTreeNode node
: ((TmfStatisticsTreeNode
) fTreeViewer
.getInput()).getChildren()) {
498 index
+= (int) node
.getValue().nbEvents
;
501 // Preparation of the event request
502 fRequest
= new TmfEventRequest
<ITmfEvent
>(ITmfEvent
.class, timeRange
, index
, TmfDataRequest
.ALL_DATA
, getIndexPageSize(), ExecutionType
.BACKGROUND
) {
505 public void handleData(ITmfEvent data
) {
506 super.handleData(data
);
508 AbsTmfStatisticsTree statisticsData
= TmfStatisticsTreeRootFactory
.getStatTree(getTreeID(experiment
.getName()));
510 final String traceName
= data
.getTrace().getName();
511 ITmfExtraEventInfo extraInfo
= new ITmfExtraEventInfo() {
513 public String
getTraceName() {
514 if (traceName
== null) {
515 return Messages
.TmfStatisticsView_UnknownTraceName
;
520 statisticsData
.registerEvent(data
, extraInfo
);
521 statisticsData
.increase(data
, extraInfo
, 1);
523 if ((getNbRead() % getInputChangedRefresh()) == 0) {
524 modelInputChanged(false);
530 public void handleSuccess() {
531 super.handleSuccess();
532 modelInputChanged(true);
537 public void handleFailure() {
538 super.handleFailure();
539 modelIncomplete(experiment
.getName());
543 public void handleCancel() {
544 super.handleCancel();
545 modelIncomplete(experiment
.getName());
548 ((TmfExperiment
<ITmfEvent
>) experiment
).sendRequest((ITmfDataRequest
<ITmfEvent
>) fRequest
);
554 * Cancels the current ongoing request
556 protected void cancelOngoingRequest() {
557 if (fRequest
!= null && !fRequest
.isCompleted()) {
563 * Reset update synchronization information
565 protected void resetUpdateSynchronization() {
566 synchronized (fStatisticsUpdateSyncObj
) {
567 fStatisticsUpdateBusy
= false;
568 fStatisticsUpdatePending
= false;
573 * Checks if statistic update is ongoing. If it is ongoing the new time range is stored as pending
575 * @param timeRange - new time range
576 * @return true if statistic update is ongoing else false
578 protected boolean checkUpdateBusy(TmfTimeRange timeRange
) {
579 synchronized (fStatisticsUpdateSyncObj
) {
580 if (fStatisticsUpdateBusy
) {
581 fStatisticsUpdatePending
= true;
582 fStatisticsUpdateRange
= timeRange
;
585 fStatisticsUpdateBusy
= true;
591 * Sends pending request (if any)
593 protected void sendPendingUpdate() {
594 synchronized (fStatisticsUpdateSyncObj
) {
595 fStatisticsUpdateBusy
= false;
596 if (fStatisticsUpdatePending
) {
597 fStatisticsUpdatePending
= false;
598 requestData(TmfExperiment
.getCurrentExperiment(), fStatisticsUpdateRange
);