Fix for bug 382438: null pointer exception when closing statistics view
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / views / statistics / TmfStatisticsView.java
1 /*******************************************************************************
2 * Copyright (c) 2011, 20112 Ericsson
3 *
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
8 *
9 * Contributors:
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 *
13 *******************************************************************************/
14
15 package org.eclipse.linuxtools.tmf.ui.views.statistics;
16
17 import java.util.List;
18
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;
54
55 /**
56 * The generic Statistics View displays statistics for any kind of traces.
57 *
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.
60 * </p>
61 *
62 * @version 1.0
63 * @author @author Mathieu Denis
64 */
65 public class TmfStatisticsView extends TmfView {
66 /**
67 * The ID correspond to the package in which this class is embedded
68 */
69 public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.statistics"; //$NON-NLS-1$
70 /**
71 * The view name.
72 */
73 public static final String TMF_STATISTICS_VIEW = "StatisticsView"; //$NON-NLS-1$
74 /**
75 * Refresh frequency
76 */
77 protected static final Long STATS_INPUT_CHANGED_REFRESH = 5000L;
78 /**
79 * Default PAGE_SIZE for background requests
80 */
81 protected static final int PAGE_SIZE = 50000;
82 /**
83 * The actual tree viewer to display
84 */
85 protected TreeViewer fTreeViewer;
86 /**
87 * Stores the request to the experiment
88 */
89 protected ITmfEventRequest<ITmfEvent> fRequest = null;
90 /**
91 * Update synchronization parameter (used for streaming): Update busy indicator
92 */
93 protected boolean fStatisticsUpdateBusy = false;
94 /**
95 * Update synchronization parameter (used for streaming): Update pending indicator
96 */
97 protected boolean fStatisticsUpdatePending = false;
98 /**
99 * Update synchronization parameter (used for streaming): Pending Update time range
100 */
101 protected TmfTimeRange fStatisticsUpdateRange = null;
102 /**
103 * Update synchronization object.
104 */
105 protected final Object fStatisticsUpdateSyncObj = new Object();
106 /**
107 * Flag to force request the data from trace
108 */
109 protected boolean fRequestData = false;
110 /**
111 * Object to store the cursor while waiting for the experiment to load
112 */
113 private Cursor fWaitCursor = null;
114 /**
115 * View instance counter (for multiple statistic views)
116 */
117 private static int fCountInstance = 0;
118 /**
119 * Number of this instance. Used as an instance ID.
120 */
121 private final int fInstanceNb;
122
123 /**
124 * Constructor of a statistics view.
125 *
126 * @param viewName
127 * The name to give to the view.
128 */
129 public TmfStatisticsView(String viewName) {
130 super(viewName);
131 fCountInstance++;
132 fInstanceNb = fCountInstance;
133 }
134
135 /**
136 * Default constructor.
137 */
138 public TmfStatisticsView() {
139 this(TMF_STATISTICS_VIEW);
140 }
141
142 /*
143 * (non-Javadoc)
144 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
145 */
146 @Override
147 public void createPartControl(Composite parent) {
148 final List<TmfBaseColumnData> columnDataList = getColumnDataProvider().getColumnData();
149 parent.setLayout(new FillLayout());
150
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);
155
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());
161
162 if (columnData.getComparator() != null) {
163 treeColumn.getColumn().addSelectionListener(new SelectionAdapter() {
164 @Override
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);
169 } else {
170 fTreeViewer.setComparator(new ViewerComparator() {
171 @Override
172 public int compare(Viewer viewer, Object e1, Object e2) {
173 return -1 * columnData.getComparator().compare(viewer, e1, e2);
174 }
175 });
176 fTreeViewer.getTree().setSortDirection(SWT.UP);
177 }
178 fTreeViewer.getTree().setSortColumn(treeColumn.getColumn());
179 }
180 });
181 }
182 treeColumn.setLabelProvider(columnData.getLabelProvider());
183 }
184
185 // Handler that will draw the bar charts.
186 fTreeViewer.getTree().addListener(SWT.EraseItem, new Listener() {
187 @Override
188 public void handleEvent(Event event) {
189 if (columnDataList.get(event.index).getPercentageProvider() != null) {
190 TmfStatisticsTreeNode node = (TmfStatisticsTreeNode) event.item.getData();
191
192 double percentage = columnDataList.get(event.index).getPercentageProvider().getPercentage(node);
193 if (percentage == 0) {
194 return;
195 }
196
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;
203 }
204
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;
218 }
219 }
220 });
221
222 fTreeViewer.setComparator(columnDataList.get(0).getComparator());
223 fTreeViewer.getTree().setSortColumn(fTreeViewer.getTree().getColumn(0));
224 fTreeViewer.getTree().setSortDirection(SWT.DOWN);
225
226 // Read current data if any available
227 TmfExperiment<?> experiment = TmfExperiment.getCurrentExperiment();
228 if (experiment != null) {
229 fRequestData = true;
230 // Insert the statistics data into the tree
231 @SuppressWarnings({ "rawtypes", "unchecked" })
232 TmfExperimentSelectedSignal<?> signal = new TmfExperimentSelectedSignal(this, experiment);
233 experimentSelected(signal);
234 }
235 }
236
237 /*
238 * (non-Javadoc)
239 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
240 */
241 @Override
242 public void dispose() {
243 super.dispose();
244 if (fWaitCursor != null) {
245 fWaitCursor.dispose();
246 }
247
248 // Make sure there is no request running before removing the statistics tree
249 cancelOngoingRequest();
250 // clean the model
251 TmfStatisticsTreeRootFactory.removeAll();
252 }
253
254 /*
255 * (non-Javadoc)
256 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
257 */
258 @Override
259 public void setFocus() {
260 fTreeViewer.getTree().setFocus();
261 }
262
263 /**
264 * Refresh the view.
265 *
266 * @param complete
267 * Should a pending update be sent afterwards or not
268 */
269 public void modelInputChanged(boolean complete) {
270 // Ignore update if disposed
271 if (fTreeViewer.getTree().isDisposed()) {
272 return;
273 }
274
275 fTreeViewer.getTree().getDisplay().asyncExec(new Runnable() {
276 @Override
277 public void run() {
278 if (!fTreeViewer.getTree().isDisposed()) {
279 fTreeViewer.refresh();
280 }
281 }
282 });
283
284 if (complete) {
285 sendPendingUpdate();
286 }
287 }
288
289 /**
290 * Called when an experiment request has failed or has been canceled Remove the data retrieved from the experiment from the statistics tree.
291 *
292 * @param name The experiment name
293 */
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));
300
301 // Reset synchronization information
302 resetUpdateSynchronization();
303 modelInputChanged(false);
304 }
305 waitCursor(false);
306 }
307
308 /**
309 * Handles the signal about disposal of the current experiment.
310 *
311 * @param signal The disposed signal
312 */
313 @TmfSignalHandler
314 public void experimentDisposed(TmfExperimentDisposedSignal<? extends ITmfEvent> signal) {
315 if (signal.getExperiment() != TmfExperiment.getCurrentExperiment()) {
316 return;
317 }
318 cancelOngoingRequest();
319 }
320
321 /**
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.
324 *
325 * @param signal Contains the information about the selection.
326 */
327 @TmfSignalHandler
328 public void experimentSelected(TmfExperimentSelectedSignal<? extends ITmfEvent> signal) {
329 if (signal != null) {
330 TmfExperiment<?> experiment = signal.getExperiment();
331 String experimentName = experiment.getName();
332
333 if (TmfStatisticsTreeRootFactory.containsTreeRoot(getTreeID(experimentName))) {
334 // The experiment root is already present
335 TmfStatisticsTreeNode experimentTreeNode = TmfStatisticsTreeRootFactory.getStatTreeRoot(getTreeID(experimentName));
336
337 @SuppressWarnings("rawtypes")
338 ITmfTrace[] traces = experiment.getTraces();
339
340 // check if there is partial data loaded in the experiment
341 int numTraces = experiment.getTraces().length;
342 int numNodeTraces = experimentTreeNode.getNbChildren();
343
344 if (numTraces == numNodeTraces) {
345 boolean same = true;
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)) {
351 same = false;
352 break;
353 }
354 }
355
356 if (same) {
357 // no need to reload data, all traces are already loaded
358 fTreeViewer.setInput(experimentTreeNode);
359
360 resetUpdateSynchronization();
361
362 return;
363 }
364 experimentTreeNode.reset();
365 }
366 } else {
367 TmfStatisticsTreeRootFactory.addStatsTreeRoot(getTreeID(experimentName), getStatisticData());
368 }
369
370 resetUpdateSynchronization();
371
372 TmfStatisticsTreeNode treeModelRoot = TmfStatisticsTreeRootFactory.getStatTreeRoot(getTreeID(experiment.getName()));
373
374 // if the model has contents, clear to start over
375 if (treeModelRoot.hasChildren()) {
376 treeModelRoot.reset();
377 }
378
379 // set input to a clean data model
380 fTreeViewer.setInput(treeModelRoot);
381
382 if (fRequestData) {
383 requestData(experiment, experiment.getTimeRange());
384 fRequestData = false;
385 }
386 }
387 }
388
389 /**
390 * Handles the signal about new experiment range.
391 * @param signal The experiment range updated signal
392 */
393 @SuppressWarnings("unchecked")
394 @TmfSignalHandler
395 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal) {
396 TmfExperiment<ITmfEvent> experiment = (TmfExperiment<ITmfEvent>) signal.getExperiment();
397 // validate
398 if (! experiment.equals(TmfExperiment.getCurrentExperiment())) {
399 return;
400 }
401
402 requestData(experiment, signal.getRange());
403 }
404
405
406 /**
407 * Return the size of the request when performing background request.
408 *
409 * @return the block size for background request.
410 */
411 protected int getIndexPageSize() {
412 return PAGE_SIZE;
413 }
414
415 /**
416 * Returns the quantity of data to retrieve before a refresh of the view is performed
417 *
418 * @return the quantity of data to retrieve before a refresh of the view is performed.
419 */
420 protected long getInputChangedRefresh() {
421 return STATS_INPUT_CHANGED_REFRESH;
422 }
423
424 /**
425 * This method can be overridden to implement another way to represent the statistics data and to retrieve the information for display.
426 *
427 * @return a TmfStatisticsData object.
428 */
429 protected AbsTmfStatisticsTree getStatisticData() {
430 return new TmfBaseStatisticsTree();
431 }
432
433 /**
434 * This method can be overridden to change the representation of the data in the columns.
435 *
436 * @return an object implementing ITmfBaseColumnDataProvider.
437 */
438 protected ITmfColumnDataProvider getColumnDataProvider() {
439 return new TmfBaseColumnDataProvider();
440 }
441
442 /**
443 * Constructs the ID based on the experiment name and <code>fInstanceNb</code>
444 *
445 * @param experimentName the name of the trace name to show in the view
446 * @return a view ID
447 */
448 protected String getTreeID(String experimentName) {
449 return experimentName + fInstanceNb;
450 }
451
452 /**
453 * When the experiment is loading the cursor will be different so the user know the processing is not finished yet.
454 *
455 * @param waitInd Indicates if we need to show the waiting cursor, or the default one
456 */
457 protected void waitCursor(final boolean waitInd) {
458 if ((fTreeViewer == null) || (fTreeViewer.getTree().isDisposed())) {
459 return;
460 }
461
462 Display display = fTreeViewer.getControl().getDisplay();
463 if (fWaitCursor == null) {
464 fWaitCursor = new Cursor(display, SWT.CURSOR_WAIT);
465 }
466
467 // Perform the updates on the UI thread
468 display.asyncExec(new Runnable() {
469 @Override
470 public void run() {
471 if ((fTreeViewer != null) && (!fTreeViewer.getTree().isDisposed())) {
472 Cursor cursor = null; /* indicates default */
473 if (waitInd) {
474 cursor = fWaitCursor;
475 }
476 fTreeViewer.getControl().setCursor(cursor);
477 }
478 }
479 });
480 }
481
482 /**
483 * Perform the request for an experiment and populates the statistics tree with event.
484 *
485 * @param experiment experiment for which we need the statistics data.
486 * @param timeRange to request
487 */
488 @SuppressWarnings("unchecked")
489 protected void requestData(final TmfExperiment<?> experiment, TmfTimeRange timeRange) {
490 if (experiment != null) {
491
492 // Check if update is already ongoing
493 if (checkUpdateBusy(timeRange)) {
494 return;
495 }
496
497 int index = 0;
498 for (TmfStatisticsTreeNode node : ((TmfStatisticsTreeNode) fTreeViewer.getInput()).getChildren()) {
499 index += (int) node.getValue().nbEvents;
500 }
501
502 // Preparation of the event request
503 fRequest = new TmfEventRequest<ITmfEvent>(ITmfEvent.class, timeRange, index, TmfDataRequest.ALL_DATA, getIndexPageSize(), ExecutionType.BACKGROUND) {
504
505 private final AbsTmfStatisticsTree statisticsData = TmfStatisticsTreeRootFactory.getStatTree(getTreeID(experiment.getName()));
506
507 @Override
508 public void handleData(ITmfEvent data) {
509 super.handleData(data);
510 if (data != null) {
511 final String traceName = data.getTrace().getName();
512 ITmfExtraEventInfo extraInfo = new ITmfExtraEventInfo() {
513 @Override
514 public String getTraceName() {
515 if (traceName == null) {
516 return Messages.TmfStatisticsView_UnknownTraceName;
517 }
518 return traceName;
519 }
520 };
521 statisticsData.registerEvent(data, extraInfo);
522 statisticsData.increase(data, extraInfo, 1);
523 // Refresh View
524 if ((getNbRead() % getInputChangedRefresh()) == 0) {
525 modelInputChanged(false);
526 }
527 }
528 }
529
530 @Override
531 public void handleSuccess() {
532 super.handleSuccess();
533 modelInputChanged(true);
534 waitCursor(false);
535 }
536
537 @Override
538 public void handleFailure() {
539 super.handleFailure();
540 modelIncomplete(experiment.getName());
541 }
542
543 @Override
544 public void handleCancel() {
545 super.handleCancel();
546 modelIncomplete(experiment.getName());
547 }
548 };
549 ((TmfExperiment<ITmfEvent>) experiment).sendRequest(fRequest);
550 waitCursor(true);
551 }
552 }
553
554 /**
555 * Cancels the current ongoing request
556 */
557 protected void cancelOngoingRequest() {
558 if (fRequest != null && !fRequest.isCompleted()) {
559 fRequest.cancel();
560 }
561 }
562
563 /**
564 * Reset update synchronization information
565 */
566 protected void resetUpdateSynchronization() {
567 synchronized (fStatisticsUpdateSyncObj) {
568 fStatisticsUpdateBusy = false;
569 fStatisticsUpdatePending = false;
570 }
571 }
572
573 /**
574 * Checks if statistic update is ongoing. If it is ongoing the new time range is stored as pending
575 *
576 * @param timeRange - new time range
577 * @return true if statistic update is ongoing else false
578 */
579 protected boolean checkUpdateBusy(TmfTimeRange timeRange) {
580 synchronized (fStatisticsUpdateSyncObj) {
581 if (fStatisticsUpdateBusy) {
582 fStatisticsUpdatePending = true;
583 fStatisticsUpdateRange = timeRange;
584 return true;
585 }
586 fStatisticsUpdateBusy = true;
587 return false;
588 }
589 }
590
591 /**
592 * Sends pending request (if any)
593 */
594 protected void sendPendingUpdate() {
595 synchronized (fStatisticsUpdateSyncObj) {
596 fStatisticsUpdateBusy = false;
597 if (fStatisticsUpdatePending) {
598 fStatisticsUpdatePending = false;
599 requestData(TmfExperiment.getCurrentExperiment(), fStatisticsUpdateRange);
600 }
601 }
602 }
603
604 }
This page took 0.044585 seconds and 5 git commands to generate.