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
.core
.event
.ITmfEvent
;
23 import org
.eclipse
.linuxtools
.tmf
.core
.event
.TmfTimeRange
;
24 import org
.eclipse
.linuxtools
.tmf
.core
.request
.ITmfDataRequest
;
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 * <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 public 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
<ITmfEvent
> 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 // Flag to force request the data from trace
91 protected boolean fRequestData
= false;
94 // Object to store the cursor while waiting for the experiment to load
95 private Cursor fWaitCursor
= null;
97 // Stores the number of instance
98 private static int fCountInstance
= 0;
100 // Number of this instance. Used as an instance ID
101 private int fInstanceNb
;
105 * Constructor of a statistics view.
108 * The name to give to the view.
110 public TmfStatisticsView(String viewName
) {
113 fInstanceNb
= fCountInstance
;
117 * Default constructor.
119 public TmfStatisticsView() {
120 this(TMF_STATISTICS_VIEW
);
125 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
128 public void createPartControl(Composite parent
) {
129 final Vector
<TmfBaseColumnData
> columnDataList
= getColumnDataProvider().getColumnData();
130 parent
.setLayout(new FillLayout());
132 fTreeViewer
= new TreeViewer(parent
, SWT
.BORDER
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
133 fTreeViewer
.setContentProvider(new TmfTreeContentProvider());
134 fTreeViewer
.getTree().setHeaderVisible(true);
135 fTreeViewer
.setUseHashlookup(true);
137 for (final TmfBaseColumnData columnData
: columnDataList
) {
138 final TreeViewerColumn treeColumn
= new TreeViewerColumn(fTreeViewer
, columnData
.getAlignment());
139 treeColumn
.getColumn().setText(columnData
.getHeader());
140 treeColumn
.getColumn().setWidth(columnData
.getWidth());
141 treeColumn
.getColumn().setToolTipText(columnData
.getTooltip());
143 if (columnData
.getComparator() != null) {
144 treeColumn
.getColumn().addSelectionListener(new SelectionAdapter() {
146 public void widgetSelected(SelectionEvent e
) {
147 if (fTreeViewer
.getTree().getSortDirection() == SWT
.UP
|| fTreeViewer
.getTree().getSortColumn() != treeColumn
.getColumn()) {
148 fTreeViewer
.setComparator(columnData
.getComparator());
149 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
151 fTreeViewer
.setComparator(new ViewerComparator() {
153 public int compare(Viewer viewer
, Object e1
, Object e2
) {
154 return -1 * columnData
.getComparator().compare(viewer
, e1
, e2
);
157 fTreeViewer
.getTree().setSortDirection(SWT
.UP
);
159 fTreeViewer
.getTree().setSortColumn(treeColumn
.getColumn());
163 treeColumn
.setLabelProvider(columnData
.getLabelProvider());
166 // Handler that will draw the bar charts.
167 fTreeViewer
.getTree().addListener(SWT
.EraseItem
, new Listener() {
169 public void handleEvent(Event event
) {
170 if (columnDataList
.get(event
.index
).getPercentageProvider() != null) {
171 TmfStatisticsTreeNode node
= (TmfStatisticsTreeNode
) event
.item
.getData();
173 double percentage
= columnDataList
.get(event
.index
).getPercentageProvider().getPercentage(node
);
174 if (percentage
== 0) {
178 if ((event
.detail
& SWT
.SELECTED
) > 0) {
179 Color oldForeground
= event
.gc
.getForeground();
180 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_SELECTION
));
181 event
.gc
.fillRectangle(event
.x
, event
.y
, event
.width
, event
.height
);
182 event
.gc
.setForeground(oldForeground
);
183 event
.detail
&= ~SWT
.SELECTED
;
186 int barWidth
= (int) ((fTreeViewer
.getTree().getColumn(1).getWidth() - 8) * percentage
);
187 int oldAlpha
= event
.gc
.getAlpha();
188 Color oldForeground
= event
.gc
.getForeground();
189 Color oldBackground
= event
.gc
.getBackground();
190 event
.gc
.setAlpha(64);
191 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_BLUE
));
192 event
.gc
.setBackground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_BACKGROUND
));
193 event
.gc
.fillGradientRectangle(event
.x
, event
.y
, barWidth
, event
.height
, true);
194 event
.gc
.drawRectangle(event
.x
, event
.y
, barWidth
, event
.height
);
195 event
.gc
.setForeground(oldForeground
);
196 event
.gc
.setBackground(oldBackground
);
197 event
.gc
.setAlpha(oldAlpha
);
198 event
.detail
&= ~SWT
.BACKGROUND
;
203 fTreeViewer
.setComparator(columnDataList
.get(0).getComparator());
204 fTreeViewer
.getTree().setSortColumn(fTreeViewer
.getTree().getColumn(0));
205 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
207 // Read current data if any available
208 TmfExperiment
<?
> experiment
= TmfExperiment
.getCurrentExperiment();
209 if (experiment
!= null) {
211 // Insert the statistics data into the tree
212 @SuppressWarnings({ "rawtypes", "unchecked" })
213 TmfExperimentSelectedSignal
<?
> signal
= new TmfExperimentSelectedSignal(this, experiment
);
214 experimentSelected(signal
);
220 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
223 public void dispose() {
225 if (fWaitCursor
!= null) {
226 fWaitCursor
.dispose();
230 TmfStatisticsTreeRootFactory
.removeAll();
235 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
238 public void setFocus() {
239 fTreeViewer
.getTree().setFocus();
245 public void modelInputChanged(boolean complete
) {
246 // Ignore update if disposed
247 if (fTreeViewer
.getTree().isDisposed())
250 fTreeViewer
.getTree().getDisplay().asyncExec(new Runnable() {
253 if (!fTreeViewer
.getTree().isDisposed())
254 fTreeViewer
.refresh();
264 * Called when an experiment request has failed or has been canceled Remove the data retrieved from the experiment from the statistics tree.
267 * the experiment name
269 public void modelIncomplete(String name
) {
270 Object input
= fTreeViewer
.getInput();
271 if (input
!= null && input
instanceof TmfStatisticsTreeNode
) {
272 // The data from this experiment is invalid and shall be removed to
273 // refresh upon next selection
274 TmfStatisticsTreeRootFactory
.removeStatTreeRoot(getTreeID(name
));
276 // Reset synchronization information
277 resetUpdateSynchronization();
278 modelInputChanged(false);
284 * If the user choose another experiment, the current must be disposed.
289 public void experimentDisposed(TmfExperimentDisposedSignal
<?
extends ITmfEvent
> signal
) {
290 if (signal
.getExperiment() != TmfExperiment
.getCurrentExperiment()) {
293 cancelOngoingRequest();
297 * Handler called when an experiment is selected. Checks if the experiment has changed and requests the selected experiment if it has not yet been
301 * contains the information about the selection.
304 public void experimentSelected(TmfExperimentSelectedSignal
<?
extends ITmfEvent
> signal
) {
305 if (signal
!= null) {
306 TmfExperiment
<?
> experiment
= signal
.getExperiment();
307 String experimentName
= experiment
.getName();
309 if (TmfStatisticsTreeRootFactory
.containsTreeRoot(getTreeID(experimentName
))) {
310 // The experiment root is already present
311 TmfStatisticsTreeNode experimentTreeNode
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experimentName
));
313 @SuppressWarnings("rawtypes")
314 ITmfTrace
[] traces
= experiment
.getTraces();
316 // check if there is partial data loaded in the experiment
317 int numTraces
= experiment
.getTraces().length
;
318 int numNodeTraces
= experimentTreeNode
.getNbChildren();
320 if (numTraces
== numNodeTraces
) {
322 // Detect if the experiment contains the same traces as when
323 // previously selected
324 for (int i
= 0; i
< numTraces
; i
++) {
325 String traceName
= traces
[i
].getName();
326 if (!experimentTreeNode
.containsChild(traceName
)) {
333 // no need to reload data, all traces are already loaded
334 fTreeViewer
.setInput(experimentTreeNode
);
336 resetUpdateSynchronization();
340 experimentTreeNode
.reset();
343 TmfStatisticsTreeRootFactory
.addStatsTreeRoot(getTreeID(experimentName
), getStatisticData());
346 resetUpdateSynchronization();
348 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experiment
.getName()));
350 // if the model has contents, clear to start over
351 if (treeModelRoot
.hasChildren()) {
352 treeModelRoot
.reset();
355 // set input to a clean data model
356 fTreeViewer
.setInput(treeModelRoot
);
359 requestData(experiment
, experiment
.getTimeRange());
360 fRequestData
= false;
368 @SuppressWarnings("unchecked")
370 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal
) {
371 TmfExperiment
<ITmfEvent
> experiment
= (TmfExperiment
<ITmfEvent
>) signal
.getExperiment();
373 if (! experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
377 requestData(experiment
, signal
.getRange());
382 * Return the size of the request when performing background request.
384 * @return the block size for background request.
386 protected int getIndexPageSize() {
392 * @return the quantity of data to retrieve before a refresh of the view is performed.
394 protected long getInputChangedRefresh() {
395 return STATS_INPUT_CHANGED_REFRESH
;
399 * This method can be overridden to implement another way to represent the statistics data and to retrieve the information for display.
401 * @return a TmfStatisticsData object.
403 protected AbsTmfStatisticsTree
getStatisticData() {
404 return new TmfBaseStatisticsTree();
408 * This method can be overridden to change the representation of the data in the columns.
410 * @return an object implementing ITmfBaseColumnDataProvider.
412 protected ITmfColumnDataProvider
getColumnDataProvider() {
413 return new TmfBaseColumnDataProvider();
417 * Construct the ID based on the experiment name
418 * @param experimentName the name of the trace name to show in the view
421 protected String
getTreeID(String experimentName
) {
422 return experimentName
+ fInstanceNb
;
426 * When the experiment is loading the cursor will be different so the user know the processing is not finished yet.
429 * indicates if we need to show the waiting cursor, or the default one
431 protected void waitCursor(final boolean waitInd
) {
432 if ((fTreeViewer
== null) || (fTreeViewer
.getTree().isDisposed())) {
436 Display display
= fTreeViewer
.getControl().getDisplay();
437 if (fWaitCursor
== null) {
438 fWaitCursor
= new Cursor(display
, SWT
.CURSOR_WAIT
);
441 // Perform the updates on the UI thread
442 display
.asyncExec(new Runnable() {
445 if ((fTreeViewer
!= null) && (!fTreeViewer
.getTree().isDisposed())) {
446 Cursor cursor
= null; /* indicates default */
448 cursor
= fWaitCursor
;
450 fTreeViewer
.getControl().setCursor(cursor
);
457 * Perform the request for an experiment and populates the statistics tree with event.
459 * @param experiment experiment for which we need the statistics data.
460 * @param timeRange to request
462 @SuppressWarnings("unchecked")
463 protected void requestData(final TmfExperiment
<?
> experiment
, TmfTimeRange timeRange
) {
464 if (experiment
!= null) {
466 // Check if update is already ongoing
467 if (checkUpdateBusy(timeRange
)) {
472 for (TmfStatisticsTreeNode node
: ((TmfStatisticsTreeNode
) fTreeViewer
.getInput()).getChildren()) {
473 index
+= (int) node
.getValue().nbEvents
;
476 // Preparation of the event request
477 fRequest
= new TmfEventRequest
<ITmfEvent
>(ITmfEvent
.class, timeRange
, index
, TmfDataRequest
.ALL_DATA
, getIndexPageSize(), ExecutionType
.BACKGROUND
) {
480 public void handleData(ITmfEvent data
) {
481 super.handleData(data
);
483 AbsTmfStatisticsTree statisticsData
= TmfStatisticsTreeRootFactory
.getStatTree(getTreeID(experiment
.getName()));
485 final String traceName
= data
.getTrace().getName();
486 ITmfExtraEventInfo extraInfo
= new ITmfExtraEventInfo() {
488 public String
getTraceName() {
489 if (traceName
== null) {
490 return Messages
.TmfStatisticsView_UnknownTraceName
;
495 statisticsData
.registerEvent(data
, extraInfo
);
496 statisticsData
.increase(data
, extraInfo
, 1);
498 if ((getNbRead() % getInputChangedRefresh()) == 0) {
499 modelInputChanged(false);
505 public void handleSuccess() {
506 super.handleSuccess();
507 modelInputChanged(true);
512 public void handleFailure() {
513 super.handleFailure();
514 modelIncomplete(experiment
.getName());
518 public void handleCancel() {
519 super.handleCancel();
520 modelIncomplete(experiment
.getName());
523 ((TmfExperiment
<ITmfEvent
>) experiment
).sendRequest((ITmfDataRequest
<ITmfEvent
>) fRequest
);
529 * Cancels the current ongoing request
531 protected void cancelOngoingRequest() {
532 if (fRequest
!= null && !fRequest
.isCompleted()) {
538 * Reset update synchronization information
540 protected void resetUpdateSynchronization() {
541 synchronized (fStatisticsUpdateSyncObj
) {
542 fStatisticsUpdateBusy
= false;
543 fStatisticsUpdatePending
= false;
548 * Checks if statistic update is ongoing. If it is ongoing the new time range is stored as pending
550 * @param timeRange - new time range
551 * @return true if statistic update is ongoing else false
553 protected boolean checkUpdateBusy(TmfTimeRange timeRange
) {
554 synchronized (fStatisticsUpdateSyncObj
) {
555 if (fStatisticsUpdateBusy
) {
556 fStatisticsUpdatePending
= true;
557 fStatisticsUpdateRange
= timeRange
;
560 fStatisticsUpdateBusy
= true;
566 * Sends pending request (if any)
568 protected void sendPendingUpdate() {
569 synchronized (fStatisticsUpdateSyncObj
) {
570 fStatisticsUpdateBusy
= false;
571 if (fStatisticsUpdatePending
) {
572 fStatisticsUpdatePending
= false;
573 requestData(TmfExperiment
.getCurrentExperiment(), fStatisticsUpdateRange
);