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
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
.TmfExperimentUpdatedSignal
;
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 actual tree viewer to display
92 protected TreeViewer fTreeViewer
;
95 * Stores the global request to the experiment
97 protected ITmfEventRequest fRequest
= null;
100 * Update synchronization parameter (used for streaming): Update busy
103 protected boolean fStatisticsUpdateBusy
= false;
106 * Update synchronization parameter (used for streaming): Update pending
109 protected boolean fStatisticsUpdatePending
= false;
112 * Update synchronization parameter (used for streaming): Pending Update
115 protected TmfTimeRange fStatisticsUpdateRange
= null;
118 * Update synchronization object.
120 protected final Object fStatisticsUpdateSyncObj
= new Object();
123 * Flag to force request the data from trace
125 protected boolean fRequestData
= false;
128 * Object to store the cursor while waiting for the experiment to load
130 private Cursor fWaitCursor
= null;
133 * View instance counter (for multiple statistic views)
135 private static int fCountInstance
= 0;
138 * Number of this instance. Used as an instance ID.
140 private final int fInstanceNb
;
143 * Constructor of a statistics view.
146 * The name to give to the view.
148 public TmfStatisticsView(String viewName
) {
151 fInstanceNb
= fCountInstance
;
155 * Default constructor.
157 public TmfStatisticsView() {
158 this(TMF_STATISTICS_VIEW
);
165 * org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
168 public void createPartControl(Composite parent
) {
169 final List
<TmfBaseColumnData
> columnDataList
= getColumnDataProvider().getColumnData();
170 parent
.setLayout(new FillLayout());
172 fTreeViewer
= new TreeViewer(parent
, SWT
.BORDER
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
173 fTreeViewer
.setContentProvider(new TmfTreeContentProvider());
174 fTreeViewer
.getTree().setHeaderVisible(true);
175 fTreeViewer
.setUseHashlookup(true);
177 for (final TmfBaseColumnData columnData
: columnDataList
) {
178 final TreeViewerColumn treeColumn
= new TreeViewerColumn(fTreeViewer
, columnData
.getAlignment());
179 treeColumn
.getColumn().setText(columnData
.getHeader());
180 treeColumn
.getColumn().setWidth(columnData
.getWidth());
181 treeColumn
.getColumn().setToolTipText(columnData
.getTooltip());
183 if (columnData
.getComparator() != null) {
184 treeColumn
.getColumn().addSelectionListener(new SelectionAdapter() {
186 public void widgetSelected(SelectionEvent e
) {
187 if (fTreeViewer
.getTree().getSortDirection() == SWT
.UP
|| fTreeViewer
.getTree().getSortColumn() != treeColumn
.getColumn()) {
188 fTreeViewer
.setComparator(columnData
.getComparator());
189 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
191 fTreeViewer
.setComparator(new ViewerComparator() {
193 public int compare(Viewer viewer
, Object e1
, Object e2
) {
194 return -1 * columnData
.getComparator().compare(viewer
, e1
, e2
);
197 fTreeViewer
.getTree().setSortDirection(SWT
.UP
);
199 fTreeViewer
.getTree().setSortColumn(treeColumn
.getColumn());
203 treeColumn
.setLabelProvider(columnData
.getLabelProvider());
206 // Handler that will draw the bar charts.
207 fTreeViewer
.getTree().addListener(SWT
.EraseItem
, new Listener() {
209 public void handleEvent(Event event
) {
210 if (columnDataList
.get(event
.index
).getPercentageProvider() != null) {
211 TmfStatisticsTreeNode node
= (TmfStatisticsTreeNode
) event
.item
.getData();
213 double percentage
= columnDataList
.get(event
.index
).getPercentageProvider().getPercentage(node
);
214 if (percentage
== 0) {
218 if ((event
.detail
& SWT
.SELECTED
) > 0) {
219 Color oldForeground
= event
.gc
.getForeground();
220 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_SELECTION
));
221 event
.gc
.fillRectangle(event
.x
, event
.y
, event
.width
, event
.height
);
222 event
.gc
.setForeground(oldForeground
);
223 event
.detail
&= ~SWT
.SELECTED
;
226 int barWidth
= (int) ((fTreeViewer
.getTree().getColumn(1).getWidth() - 8) * percentage
);
227 int oldAlpha
= event
.gc
.getAlpha();
228 Color oldForeground
= event
.gc
.getForeground();
229 Color oldBackground
= event
.gc
.getBackground();
230 event
.gc
.setAlpha(64);
231 event
.gc
.setForeground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_BLUE
));
232 event
.gc
.setBackground(event
.item
.getDisplay().getSystemColor(SWT
.COLOR_LIST_BACKGROUND
));
233 event
.gc
.fillGradientRectangle(event
.x
, event
.y
, barWidth
, event
.height
, true);
234 event
.gc
.drawRectangle(event
.x
, event
.y
, barWidth
, event
.height
);
235 event
.gc
.setForeground(oldForeground
);
236 event
.gc
.setBackground(oldBackground
);
237 event
.gc
.setAlpha(oldAlpha
);
238 event
.detail
&= ~SWT
.BACKGROUND
;
243 fTreeViewer
.setComparator(columnDataList
.get(0).getComparator());
244 fTreeViewer
.getTree().setSortColumn(fTreeViewer
.getTree().getColumn(0));
245 fTreeViewer
.getTree().setSortDirection(SWT
.DOWN
);
247 // Read current data if any available
248 TmfExperiment experiment
= TmfExperiment
.getCurrentExperiment();
249 if (experiment
!= null) {
251 // Insert the statistics data into the tree
252 TmfExperimentSelectedSignal signal
= new TmfExperimentSelectedSignal(this, experiment
);
253 experimentSelected(signal
);
260 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
263 public void dispose() {
265 if (fWaitCursor
!= null) {
266 fWaitCursor
.dispose();
270 * Make sure there is no request running before removing the statistics
273 cancelOngoingRequest();
275 TmfStatisticsTreeRootFactory
.removeAll();
281 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
284 public void setFocus() {
285 fTreeViewer
.getTree().setFocus();
292 * Should a pending update be sent afterwards or not
294 public void modelInputChanged(boolean complete
) {
295 // Ignore update if disposed
296 if (fTreeViewer
.getTree().isDisposed()) {
300 fTreeViewer
.getTree().getDisplay().asyncExec(new Runnable() {
303 if (!fTreeViewer
.getTree().isDisposed()) {
304 fTreeViewer
.refresh();
315 * Called when an experiment request has failed or has been cancelled.
316 * Remove the data retrieved from the experiment from the statistics tree.
319 * The experiment name
321 public void modelIncomplete(String name
) {
322 Object input
= fTreeViewer
.getInput();
323 if (input
!= null && input
instanceof TmfStatisticsTreeNode
) {
325 * The data from this experiment is invalid and shall be removed to
326 * refresh upon next selection
328 TmfStatisticsTreeRootFactory
.removeStatTreeRoot(getTreeID(name
));
330 // Reset synchronization information
331 resetUpdateSynchronization();
332 modelInputChanged(false);
338 * Handles the signal about disposal of the current experiment.
341 * The disposed signal
344 public void experimentDisposed(TmfExperimentDisposedSignal signal
) {
345 if (signal
.getExperiment() != TmfExperiment
.getCurrentExperiment()) {
348 cancelOngoingRequest();
352 * Handler called when an experiment is selected. Checks if the experiment
353 * has changed and requests the selected experiment if it has not yet been
357 * Contains the information about the selection.
360 public void experimentSelected(TmfExperimentSelectedSignal signal
) {
361 if (signal
!= null) {
362 TmfExperiment experiment
= signal
.getExperiment();
363 String experimentName
= experiment
.getName();
365 if (TmfStatisticsTreeRootFactory
.containsTreeRoot(getTreeID(experimentName
))) {
366 // The experiment root is already present
367 TmfStatisticsTreeNode experimentTreeNode
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experimentName
));
369 ITmfTrace
[] traces
= experiment
.getTraces();
371 // check if there is partial data loaded in the experiment
372 int numTraces
= experiment
.getTraces().length
;
373 int numNodeTraces
= experimentTreeNode
.getNbChildren();
375 if (numTraces
== numNodeTraces
) {
378 * Detect if the experiment contains the same traces as when
379 * previously selected
381 for (int i
= 0; i
< numTraces
; i
++) {
382 String traceName
= traces
[i
].getName();
383 if (!experimentTreeNode
.containsChild(traceName
)) {
390 // no need to reload data, all traces are already loaded
391 fTreeViewer
.setInput(experimentTreeNode
);
393 resetUpdateSynchronization();
397 experimentTreeNode
.reset();
400 TmfStatisticsTreeRootFactory
.addStatsTreeRoot(getTreeID(experimentName
), getStatisticData());
403 resetUpdateSynchronization();
405 TmfStatisticsTreeNode treeModelRoot
= TmfStatisticsTreeRootFactory
.getStatTreeRoot(getTreeID(experiment
.getName()));
407 // if the model has contents, clear to start over
408 if (treeModelRoot
.hasChildren()) {
409 treeModelRoot
.reset();
412 // set input to a clean data model
413 fTreeViewer
.setInput(treeModelRoot
);
416 requestData(experiment
, experiment
.getTimeRange());
417 fRequestData
= false;
423 * Handles the signal about new experiment range.
426 * The experiment range updated signal
429 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal
) {
430 TmfExperiment experiment
= signal
.getExperiment();
432 if (!experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
436 requestData(experiment
, signal
.getRange());
440 * Handles the experiment updated signal. This will detect new events in
441 * case the indexing is not coalesced with a statistics request.
444 * The experiment updated signal
449 public void experimentUpdated(TmfExperimentUpdatedSignal signal
) {
450 TmfExperiment experiment
= signal
.getExperiment();
451 if (!experiment
.equals(TmfExperiment
.getCurrentExperiment())) {
456 for (TmfStatisticsTreeNode node
: ((TmfStatisticsTreeNode
) fTreeViewer
.getInput()).getChildren()) {
457 nbEvents
+= (int) node
.getValue().nbEvents
;
461 * In the normal case, the statistics request is coalesced with indexing
462 * and the number of events are the same, there is nothing to do. But if
463 * it's not the case, trigger a new request to count the new events.
465 if (nbEvents
< experiment
.getNbEvents()) {
466 requestData(experiment
, experiment
.getTimeRange());
471 * Return the size of the request when performing background request.
473 * @return the block size for background request.
475 protected int getIndexPageSize() {
480 * Returns the quantity of data to retrieve before a refresh of the view is
483 * @return the quantity of data to retrieve before a refresh of the view is
486 protected long getInputChangedRefresh() {
487 return STATS_INPUT_CHANGED_REFRESH
;
491 * This method can be overridden to implement another way to represent the
492 * statistics data and to retrieve the information for display.
494 * @return a TmfStatisticsData object.
496 protected AbsTmfStatisticsTree
getStatisticData() {
497 return new TmfBaseStatisticsTree();
501 * This method can be overridden to change the representation of the data in
504 * @return an object implementing ITmfBaseColumnDataProvider.
506 protected ITmfColumnDataProvider
getColumnDataProvider() {
507 return new TmfBaseColumnDataProvider();
511 * Constructs the ID based on the experiment name and
512 * <code>fInstanceNb</code>
514 * @param experimentName
515 * the name of the trace name to show in the view
518 protected String
getTreeID(String experimentName
) {
519 return experimentName
+ fInstanceNb
;
523 * When the experiment is loading the cursor will be different so the user
524 * knows the processing is not finished yet.
527 * Indicates if we need to show the waiting cursor, or the
530 protected void waitCursor(final boolean waitInd
) {
531 if ((fTreeViewer
== null) || (fTreeViewer
.getTree().isDisposed())) {
535 Display display
= fTreeViewer
.getControl().getDisplay();
536 if (fWaitCursor
== null) {
537 fWaitCursor
= new Cursor(display
, SWT
.CURSOR_WAIT
);
540 // Perform the updates on the UI thread
541 display
.asyncExec(new Runnable() {
544 if ((fTreeViewer
!= null)
545 && (!fTreeViewer
.getTree().isDisposed())) {
546 Cursor cursor
= null; /* indicates default */
548 cursor
= fWaitCursor
;
550 fTreeViewer
.getControl().setCursor(cursor
);
557 * Perform the request for an experiment and populates the statistics tree
561 * Experiment for which we need the statistics data.
565 protected void requestData(final TmfExperiment experiment
, TmfTimeRange timeRange
) {
566 if (experiment
!= null) {
568 // Check if an update is already ongoing
569 if (checkUpdateBusy(timeRange
)) {
574 for (TmfStatisticsTreeNode node
: ((TmfStatisticsTreeNode
) fTreeViewer
.getInput()).getChildren()) {
575 index
+= (int) node
.getValue().nbEvents
;
578 // Preparation of the event request
579 fRequest
= new TmfEventRequest(ITmfEvent
.class, timeRange
, index
, TmfDataRequest
.ALL_DATA
, getIndexPageSize(), ExecutionType
.BACKGROUND
) {
581 private final AbsTmfStatisticsTree statisticsData
= TmfStatisticsTreeRootFactory
.getStatTree(getTreeID(experiment
.getName()));
584 public void handleData(ITmfEvent data
) {
585 super.handleData(data
);
587 final String traceName
= data
.getTrace().getName();
588 ITmfExtraEventInfo extraInfo
= new ITmfExtraEventInfo() {
590 public String
getTraceName() {
591 if (traceName
== null) {
592 return Messages
.TmfStatisticsView_UnknownTraceName
;
597 statisticsData
.registerEvent(data
, extraInfo
);
598 statisticsData
.increase(data
, extraInfo
, 1);
600 if ((getNbRead() % getInputChangedRefresh()) == 0) {
601 modelInputChanged(false);
607 public void handleSuccess() {
608 super.handleSuccess();
609 modelInputChanged(true);
614 public void handleFailure() {
615 super.handleFailure();
616 modelIncomplete(experiment
.getName());
620 public void handleCancel() {
621 super.handleCancel();
622 modelIncomplete(experiment
.getName());
625 experiment
.sendRequest(fRequest
);
631 * Cancels the current ongoing request
633 protected void cancelOngoingRequest() {
634 if (fRequest
!= null && !fRequest
.isCompleted()) {
640 * Reset update synchronization information
642 protected void resetUpdateSynchronization() {
643 synchronized (fStatisticsUpdateSyncObj
) {
644 fStatisticsUpdateBusy
= false;
645 fStatisticsUpdatePending
= false;
646 fStatisticsUpdateRange
= null;
651 * Checks if statistic update is ongoing. If it is ongoing the new time
652 * range is stored as pending
656 * @return true if statistic update is ongoing else false
658 protected boolean checkUpdateBusy(TmfTimeRange timeRange
) {
659 synchronized (fStatisticsUpdateSyncObj
) {
660 if (fStatisticsUpdateBusy
) {
661 fStatisticsUpdatePending
= true;
662 if (fStatisticsUpdateRange
== null
663 || timeRange
.getEndTime().compareTo(fStatisticsUpdateRange
.getEndTime()) > 0) {
664 fStatisticsUpdateRange
= timeRange
;
668 fStatisticsUpdateBusy
= true;
674 * Sends pending request (if any)
676 protected void sendPendingUpdate() {
677 synchronized (fStatisticsUpdateSyncObj
) {
678 fStatisticsUpdateBusy
= false;
679 if (fStatisticsUpdatePending
) {
680 fStatisticsUpdatePending
= false;
681 requestData(TmfExperiment
.getCurrentExperiment(), fStatisticsUpdateRange
);
682 fStatisticsUpdateRange
= null;