tmf: Generalization of the 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, 2012 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 * Mathieu Denis - New request added to update the statistics from the selected time range
13 * Mathieu Denis - Generalization of the view to instantiate a viewer specific to a trace type
14 *
15 *******************************************************************************/
16
17 package org.eclipse.linuxtools.tmf.ui.views.statistics;
18
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.InvocationTargetException;
21
22 import org.eclipse.core.resources.IResource;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.core.runtime.IConfigurationElement;
25 import org.eclipse.core.runtime.Platform;
26 import org.eclipse.linuxtools.internal.tmf.ui.Activator;
27 import org.eclipse.linuxtools.tmf.core.TmfCommonConstants;
28 import org.eclipse.linuxtools.tmf.core.event.TmfTimeRange;
29 import org.eclipse.linuxtools.tmf.core.event.TmfTimestamp;
30 import org.eclipse.linuxtools.tmf.core.request.ITmfDataRequest.ExecutionType;
31 import org.eclipse.linuxtools.tmf.core.request.ITmfEventRequest;
32 import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentDisposedSignal;
33 import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentRangeUpdatedSignal;
34 import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentSelectedSignal;
35 import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentUpdatedSignal;
36 import org.eclipse.linuxtools.tmf.core.signal.TmfRangeSynchSignal;
37 import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler;
38 import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace;
39 import org.eclipse.linuxtools.tmf.core.trace.TmfExperiment;
40 import org.eclipse.linuxtools.tmf.ui.project.model.TmfTraceType;
41 import org.eclipse.linuxtools.tmf.ui.viewers.statistics.TmfStatisticsViewer;
42 import org.eclipse.linuxtools.tmf.ui.viewers.statistics.model.TmfStatisticsTreeNode;
43 import org.eclipse.linuxtools.tmf.ui.viewers.statistics.model.TmfStatisticsTreeRootFactory;
44 import org.eclipse.linuxtools.tmf.ui.views.TmfView;
45 import org.eclipse.swt.widgets.Composite;
46 import org.eclipse.swt.widgets.Control;
47 import org.osgi.framework.Bundle;
48
49 /**
50 * The generic Statistics View displays statistics for any kind of traces.
51 *
52 * It is implemented according to the MVC pattern. - The model is a
53 * TmfStatisticsTreeNode built by the State Manager. - The view is built with a
54 * TreeViewer. - The controller that keeps model and view synchronized is an
55 * observer of the model.
56 *
57 * @version 2.0
58 * @author Mathieu Denis
59 */
60 public class TmfStatisticsView extends TmfView {
61
62 /**
63 * The ID correspond to the package in which this class is embedded
64 */
65 public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.statistics"; //$NON-NLS-1$
66
67 /**
68 * The view name.
69 */
70 public static final String TMF_STATISTICS_VIEW = "StatisticsView"; //$NON-NLS-1$
71
72 /**
73 * Stores the request to the experiment
74 */
75 protected ITmfEventRequest fRequest = null;
76
77 /**
78 * The viewer that builds the columns to show the statistics
79 */
80 private TmfStatisticsViewer fStatsViewer;
81
82 /**
83 * The initial window span (in nanoseconds)
84 *
85 * @since 2.0
86 */
87 public static final long INITIAL_WINDOW_SPAN = (1L * 100 * 1000 * 1000); // .1sec
88
89 /**
90 * Timestamp scale (nanosecond)
91 *
92 * @since 2.0
93 */
94 public static final byte TIME_SCALE = -9;
95
96 /**
97 * Stores a reference to the parent composite of this view
98 */
99 private Composite fParent;
100
101 /**
102 * Stores a reference to the experiment
103 */
104 private TmfExperiment fExperiment;
105
106 /**
107 * Flag to force request the data from trace
108 */
109 protected boolean fRequestData = false;
110
111 /**
112 * Default PAGE_SIZE for background requests
113 */
114 protected static final int PAGE_SIZE = 50000;
115
116 /**
117 * Stores the ranged request to the experiment
118 * @since 2.0
119 */
120 protected ITmfEventRequest fRequestRange = null;
121
122 /**
123 * Update synchronization parameter (used for streaming): Update busy
124 * indicator
125 */
126 protected boolean fStatisticsUpdateBusy = false;
127
128 /**
129 * Update synchronization parameter (used for streaming): Update pending
130 * indicator
131 */
132 protected boolean fStatisticsUpdatePending = false;
133
134 /**
135 * Update synchronization parameter (used for streaming): Pending Update
136 * time range
137 */
138 protected TmfTimeRange fStatisticsUpdateRange = null;
139
140 /**
141 * Update synchronization object.
142 */
143 protected final Object fStatisticsUpdateSyncObj = new Object();
144
145 /**
146 * Constructor of a statistics view.
147 *
148 * @param viewName The name to give to the view.
149 */
150 public TmfStatisticsView(String viewName) {
151 super(viewName);
152 }
153
154 /**
155 * Default constructor.
156 */
157 public TmfStatisticsView() {
158 this(TMF_STATISTICS_VIEW);
159 }
160
161 /*
162 * (non-Javadoc)
163 *
164 * @see
165 * org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
166 */
167 @Override
168 public void createPartControl(Composite parent) {
169 fParent = parent;
170 TmfExperiment currentExperiment = TmfExperiment.getCurrentExperiment();
171 // Read current data if any available
172 if (currentExperiment != null) {
173 fRequestData = true;
174 // Insert the statistics data into the tree
175 TmfExperimentSelectedSignal signal = new TmfExperimentSelectedSignal(this, currentExperiment);
176 experimentSelected(signal);
177 return;
178 }
179 fStatsViewer = createStatisticsViewer();
180 /*
181 * Updates the experiment field only at the end because
182 * experimentSelected signal verifies the old selected experiment to
183 * avoid reloading the same trace.
184 */
185 fExperiment = currentExperiment;
186 }
187
188 /**
189 * Handles the signal about disposal of the current experiment.
190 *
191 * @param signal
192 * The disposed signal
193 */
194 @TmfSignalHandler
195 public void experimentDisposed(TmfExperimentDisposedSignal signal) {
196 if (signal.getExperiment() != TmfExperiment.getCurrentExperiment()) {
197 return;
198 }
199
200 /*
201 * Make sure there is no request running before removing the statistics
202 * tree
203 */
204 cancelOngoingRequest(fRequestRange);
205 cancelOngoingRequest(fRequest);
206 }
207
208 /**
209 * Handler called when an experiment is selected. Checks if the experiment
210 * has changed and requests the selected experiment if it has not yet been
211 * cached.
212 *
213 * @param signal
214 * Contains the information about the selection.
215 */
216 @TmfSignalHandler
217 public void experimentSelected(TmfExperimentSelectedSignal signal) {
218 if (signal != null) {
219 // Does not reload the same trace if already opened
220 if (fExperiment == null
221 || signal.getExperiment().toString().compareTo(fExperiment.toString()) != 0) {
222 /*
223 * Dispose the current viewer and adapt the new one to the trace
224 * type of the experiment selected
225 */
226 if (fStatsViewer != null) {
227 fStatsViewer.dispose();
228 }
229 // Update the current experiment
230 fExperiment = signal.getExperiment();
231 fStatsViewer = createStatisticsViewer();
232 fParent.layout();
233
234 String experimentName = fExperiment.getName();
235 String treeID = fStatsViewer.getTreeID(experimentName);
236
237 setInput(treeID, fExperiment.getTraces());
238
239 if (fRequestData) {
240 requestData(fExperiment, fExperiment.getTimeRange());
241 fRequestData = false;
242 }
243 }
244 }
245 }
246
247 /**
248 * Initialize the viewer with the information received.
249 *
250 * @param treeID
251 * The unique ID of the tree that is returned by
252 * {@link TmfStatisticsViewer#getTreeID(String)}
253 * @param traces
254 * The list of the traces to add in the tree.
255 * @since 2.0
256 */
257 public void setInput(String treeID, ITmfTrace[] traces) {
258 if (TmfStatisticsTreeRootFactory.containsTreeRoot(treeID)) {
259 // The experiment root is already present
260 TmfStatisticsTreeNode experimentTreeNode = TmfStatisticsTreeRootFactory.getStatTreeRoot(treeID);
261
262 // check if there is partial data loaded in the experiment
263 int numTraces = traces.length;
264 int numNodeTraces = experimentTreeNode.getNbChildren();
265
266 if (numTraces == numNodeTraces) {
267 boolean same = true;
268 /*
269 * Detect if the experiment contains the same traces as when
270 * previously selected
271 */
272 for (int i = 0; i < numTraces; i++) {
273 String traceName = traces[i].getName();
274 if (!experimentTreeNode.containsChild(traceName)) {
275 same = false;
276 break;
277 }
278 }
279
280 if (same) {
281 // no need to reload data, all traces are already loaded
282 fStatsViewer.setInput(experimentTreeNode);
283
284 resetUpdateSynchronization();
285
286 return;
287 }
288 experimentTreeNode.reset();
289 }
290 } else {
291 TmfStatisticsTreeRootFactory.addStatsTreeRoot(treeID, fStatsViewer.getStatisticData());
292 }
293
294 resetUpdateSynchronization();
295
296 TmfStatisticsTreeNode treeModelRoot = TmfStatisticsTreeRootFactory.getStatTreeRoot(treeID);
297
298 // if the model has contents, clear to start over
299 if (treeModelRoot.hasChildren()) {
300 treeModelRoot.reset();
301 }
302
303 // set input to a clean data model
304 fStatsViewer.setInput(treeModelRoot);
305 }
306
307 /**
308 * Refresh the view.
309 *
310 * @param complete Should a pending update be sent afterwards or not
311 */
312 public void modelInputChanged(boolean complete) {
313 Control viewerControl = fStatsViewer.getControl();
314 // Ignore update if disposed
315 if (viewerControl.isDisposed()) {
316 return;
317 }
318
319 fStatsViewer.getControl().getDisplay().asyncExec(new Runnable() {
320 @Override
321 public void run() {
322 if (!fStatsViewer.getControl().isDisposed()) {
323 fStatsViewer.refresh();
324 }
325 }
326 });
327
328 if (complete) {
329 sendPendingUpdate();
330 }
331 }
332
333 /**
334 * Called when an experiment request has failed or has been cancelled.
335 * Remove the data retrieved from the experiment from the statistics tree.
336 *
337 * @param name
338 * The experiment name
339 */
340 public void modelIncomplete(String name) {
341 Object input = fStatsViewer.getInput();
342 if (input != null && input instanceof TmfStatisticsTreeNode) {
343 /*
344 * The data from this experiment is invalid and shall be removed to
345 * refresh upon next selection
346 */
347 TmfStatisticsTreeRootFactory.removeStatTreeRoot(fStatsViewer.getTreeID(name));
348
349 // Reset synchronization information
350 resetUpdateSynchronization();
351 modelInputChanged(false);
352 }
353 fStatsViewer.waitCursor(false);
354 }
355
356 /**
357 * Handles the signal about new experiment range.
358 *
359 * @param signal
360 * The experiment range updated signal
361 */
362 @TmfSignalHandler
363 public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal) {
364 TmfExperiment experiment = signal.getExperiment();
365 // validate
366 if (!experiment.equals(TmfExperiment.getCurrentExperiment())) {
367 return;
368 }
369
370 // Calculate the selected timerange to request
371 long startTime = signal.getRange().getStartTime().normalize(0, TIME_SCALE).getValue();
372 TmfTimestamp startTS = new TmfTimestamp(startTime, TIME_SCALE);
373 TmfTimestamp endTS = new TmfTimestamp(startTime + INITIAL_WINDOW_SPAN, TIME_SCALE);
374 TmfTimeRange timeRange = new TmfTimeRange(startTS, endTS);
375
376 requestTimeRangeData(experiment, timeRange);
377 requestData(experiment, signal.getRange());
378 }
379
380 /*
381 * (non-Javadoc)
382 *
383 * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose()
384 */
385 @Override
386 public void dispose() {
387 super.dispose();
388 fStatsViewer.dispose();
389
390 /*
391 * Make sure there is no request running before removing the statistics
392 * tree.
393 */
394 cancelOngoingRequest(fRequestRange);
395 cancelOngoingRequest(fRequest);
396 // clean the model
397 TmfStatisticsTreeRootFactory.removeAll();
398 }
399
400 /*
401 * (non-Javadoc)
402 *
403 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
404 */
405 @Override
406 public void setFocus() {
407 fStatsViewer.setFocus();
408 }
409
410 /**
411 * Handles the experiment updated signal. This will detect new events in
412 * case the indexing is not coalesced with a statistics request.
413 *
414 * @param signal
415 * The experiment updated signal
416 *
417 * @since 1.1
418 */
419 @TmfSignalHandler
420 public void experimentUpdated(TmfExperimentUpdatedSignal signal) {
421 TmfExperiment experiment = signal.getExperiment();
422 if (!experiment.equals(TmfExperiment.getCurrentExperiment())) {
423 return;
424 }
425
426 int nbEvents = 0;
427 for (TmfStatisticsTreeNode node : ((TmfStatisticsTreeNode) fStatsViewer.getInput()).getChildren()) {
428 nbEvents += (int) node.getValue().getTotal();
429 }
430
431 /*
432 * In the normal case, the statistics request is coalesced with indexing
433 * and the number of events are the same, there is nothing to do. But if
434 * it's not the case, trigger a new request to count the new events.
435 */
436 if (nbEvents < experiment.getNbEvents()) {
437 requestData(experiment, experiment.getTimeRange());
438 }
439 }
440
441 /**
442 * * Handles the time range updated signal. It updates the time range
443 * statistics.
444 *
445 * @param signal
446 * Contains the information about the new selected time range.
447 * @since 2.0
448 */
449 @TmfSignalHandler
450 public void timeRangeUpdated(TmfRangeSynchSignal signal) {
451 /*
452 * It is possible that the time range changes while a request is
453 * processing
454 */
455 cancelOngoingRequest(fRequestRange);
456
457 requestTimeRangeData(TmfExperiment.getCurrentExperiment(), signal.getCurrentRange());
458 }
459
460 /**
461 * Get the statistics viewer for an experiment. If all traces in the
462 * experiment are of the same type, use the extension point specified.
463 *
464 * @return a statistics viewer of the appropriate type
465 * @since 2.0
466 */
467 protected TmfStatisticsViewer createStatisticsViewer() {
468 if (fExperiment == null) {
469 return new TmfStatisticsViewer(fParent);
470 }
471 String commonTraceType = null;
472 try {
473 /*
474 * Determine if the traces of the experiment are of the same type.
475 * If not, it uses the most generic one.
476 */
477 for (ITmfTrace trace : fExperiment.getTraces()) {
478 IResource resource = trace.getResource();
479 if (resource == null) {
480 return new TmfStatisticsViewer(fParent);
481 }
482 String traceType = resource.getPersistentProperty(TmfCommonConstants.TRACETYPE);
483 if (commonTraceType != null && !commonTraceType.equals(traceType)) {
484 return new TmfStatisticsViewer(fParent);
485 }
486 commonTraceType = traceType;
487 }
488 if (commonTraceType == null) {
489 return new TmfStatisticsViewer(fParent);
490 }
491 /*
492 * Search in the configuration if there is any viewer specified for
493 * this kind of trace type.
494 */
495 for (IConfigurationElement ce : TmfTraceType.getTypeElements()) {
496 if (ce.getAttribute(TmfTraceType.ID_ATTR).equals(commonTraceType)) {
497 IConfigurationElement[] statisticsViewerCE = ce.getChildren(TmfTraceType.STATISTICS_VIEWER_ELEM);
498 if (statisticsViewerCE.length != 1) {
499 break;
500 }
501 String statisticsViewer = statisticsViewerCE[0].getAttribute(TmfTraceType.CLASS_ATTR);
502 if (statisticsViewer == null || statisticsViewer.length() == 0) {
503 break;
504 }
505 Bundle bundle = Platform.getBundle(ce.getContributor().getName());
506 Class<?> c = bundle.loadClass(statisticsViewer);
507 Class<?>[] constructorArgs = new Class[] { Composite.class };
508 Constructor<?> constructor = c.getConstructor(constructorArgs);
509 Object[] args = new Object[] { fParent };
510 return (TmfStatisticsViewer) constructor.newInstance(args);
511 }
512 }
513 } catch (CoreException e) {
514 Activator.getDefault().logError("Error creating statistics viewer : cannot find the property TmfCommonConstants.TRACETYPE", e); //$NON-NLS-1$
515 } catch (ClassNotFoundException e) {
516 Activator.getDefault().logError("Error creating statistics viewer : cannot load the statistics viewer class", e); //$NON-NLS-1$
517 } catch (NoSuchMethodException e) {
518 Activator.getDefault().logError("Error creating statistics viewer : constructor of the viewer doesn't exist", e); //$NON-NLS-1$
519 } catch (InstantiationException e) {
520 Activator.getDefault().logError("Error creating statistics viewer : cannot instantiate the statistics viewer", e); //$NON-NLS-1$
521 } catch (IllegalAccessException e) {
522 Activator.getDefault().logError("Error creating statistics viewer : cannot access the constructor of the viewer", e); //$NON-NLS-1$
523 } catch (IllegalArgumentException e) {
524 Activator.getDefault().logError("Error creating statistics viewer : argument(s) sent to the constructor are illegal", e); //$NON-NLS-1$
525 } catch (InvocationTargetException e) {
526 Activator.getDefault().logError("Error creating statistics viewer : the constructor of the viewer sent an exception", e); //$NON-NLS-1$
527 }
528 return new TmfStatisticsViewer(fParent);
529 }
530
531 /**
532 * Performs the request for an experiment and populates the statistics tree
533 * with events.
534 *
535 * @param experiment
536 * Experiment for which we need the statistics data.
537 * @param timeRange
538 * to request
539 */
540 protected void requestData(final TmfExperiment experiment, TmfTimeRange timeRange) {
541 if (experiment != null) {
542
543 // Check if an update is already ongoing
544 if (checkUpdateBusy(timeRange)) {
545 return;
546 }
547
548 int index = 0;
549 for (TmfStatisticsTreeNode node : ((TmfStatisticsTreeNode) fStatsViewer.getInput()).getChildren()) {
550 index += (int) node.getValue().getTotal();
551 }
552
553 // Prepare the global event request
554 fRequest = new TmfStatisticsRequest(this, fStatsViewer, experiment, timeRange, index, ExecutionType.BACKGROUND, true);
555
556 experiment.sendRequest(fRequest);
557 fStatsViewer.waitCursor(true);
558 }
559 }
560
561 /**
562 * Performs the time range request for an experiment and populates the
563 * statistics tree with events.
564 *
565 * @param experiment
566 * Experiment for which we need the statistics data.
567 * @param timeRange
568 * To request
569 * @since 2.0
570 */
571 protected void requestTimeRangeData(final TmfExperiment experiment, TmfTimeRange timeRange) {
572 if (experiment != null) {
573 resetTimeRangeValue();
574 // Prepare the partial event request
575 fRequestRange = new TmfStatisticsRequest(this, fStatsViewer, experiment, timeRange, 0, ExecutionType.FOREGROUND, false);
576 experiment.sendRequest(fRequestRange);
577 }
578 }
579
580 /**
581 * Reset the number of events within the time range
582 *
583 * @since 2.0
584 */
585 protected void resetTimeRangeValue() {
586 // Reset the number of events in the time range
587 String treeID = fStatsViewer.getTreeID(TmfExperiment.getCurrentExperiment().getName());
588 TmfStatisticsTreeNode treeModelRoot = TmfStatisticsTreeRootFactory.getStatTreeRoot(treeID);
589 if (treeModelRoot.hasChildren()) {
590 treeModelRoot.resetTimeRangeValue();
591 }
592 }
593
594 /**
595 * Return the size of the request when performing background request.
596 *
597 * @return the block size for background request.
598 */
599 protected int getIndexPageSize() {
600 return PAGE_SIZE;
601 }
602
603 /**
604 * Cancels the current ongoing request
605 *
606 * @param request
607 * The request to be canceled
608 * @since 2.0
609 */
610 protected void cancelOngoingRequest(ITmfEventRequest request) {
611 if (request != null && !request.isCompleted()) {
612 request.cancel();
613 }
614 }
615
616 /**
617 * Reset update synchronization information
618 */
619 protected void resetUpdateSynchronization() {
620 synchronized (fStatisticsUpdateSyncObj) {
621 fStatisticsUpdateBusy = false;
622 fStatisticsUpdatePending = false;
623 fStatisticsUpdateRange = null;
624 }
625 }
626
627 /**
628 * Checks if statistic update is ongoing. If it is ongoing the new time
629 * range is stored as pending
630 *
631 * @param timeRange
632 * - new time range
633 * @return true if statistic update is ongoing else false
634 */
635 protected boolean checkUpdateBusy(TmfTimeRange timeRange) {
636 synchronized (fStatisticsUpdateSyncObj) {
637 if (fStatisticsUpdateBusy) {
638 fStatisticsUpdatePending = true;
639 if (fStatisticsUpdateRange == null
640 || timeRange.getEndTime().compareTo(fStatisticsUpdateRange.getEndTime()) > 0) {
641 fStatisticsUpdateRange = timeRange;
642 }
643 return true;
644 }
645 fStatisticsUpdateBusy = true;
646 return false;
647 }
648 }
649
650 /**
651 * Sends pending request (if any)
652 */
653 protected void sendPendingUpdate() {
654 synchronized (fStatisticsUpdateSyncObj) {
655 fStatisticsUpdateBusy = false;
656 if (fStatisticsUpdatePending) {
657 fStatisticsUpdatePending = false;
658 requestData(TmfExperiment.getCurrentExperiment(), fStatisticsUpdateRange);
659 fStatisticsUpdateRange = null;
660 }
661 }
662 }
663 }
This page took 0.04551 seconds and 5 git commands to generate.