1 /*******************************************************************************
2 * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
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 *******************************************************************************/
10 package org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.ui
.views
;
12 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
13 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.nullToEmptyString
;
15 import java
.util
.ArrayList
;
16 import java
.util
.Arrays
;
17 import java
.util
.HashSet
;
18 import java
.util
.LinkedHashSet
;
19 import java
.util
.List
;
21 import java
.util
.function
.Predicate
;
22 import java
.util
.stream
.Collectors
;
24 import org
.eclipse
.jdt
.annotation
.NonNull
;
25 import org
.eclipse
.jdt
.annotation
.Nullable
;
26 import org
.eclipse
.jface
.action
.Action
;
27 import org
.eclipse
.jface
.action
.IAction
;
28 import org
.eclipse
.jface
.action
.IMenuManager
;
29 import org
.eclipse
.jface
.action
.IToolBarManager
;
30 import org
.eclipse
.jface
.action
.Separator
;
31 import org
.eclipse
.jface
.viewers
.ArrayContentProvider
;
32 import org
.eclipse
.jface
.viewers
.IStructuredContentProvider
;
33 import org
.eclipse
.jface
.viewers
.LabelProvider
;
34 import org
.eclipse
.jface
.window
.Window
;
35 import org
.eclipse
.swt
.SWT
;
36 import org
.eclipse
.swt
.custom
.SashForm
;
37 import org
.eclipse
.swt
.widgets
.Composite
;
38 import org
.eclipse
.swt
.widgets
.Shell
;
39 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.aspect
.LamiEmptyAspect
;
40 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.aspect
.LamiTableEntryAspect
;
41 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.module
.LamiChartModel
;
42 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.module
.LamiChartModel
.ChartType
;
43 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.module
.LamiResultTable
;
44 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.module
.LamiTableEntry
;
45 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.module
.LamiXYSeriesDescription
;
46 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.types
.LamiTimeRange
;
47 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.ui
.signals
.LamiSelectionUpdateSignal
;
48 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSelectionRangeUpdatedSignal
;
49 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalHandler
;
50 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalManager
;
51 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.ITmfTimestamp
;
52 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimeRange
;
53 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimestamp
;
54 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfTraceManager
;
55 import org
.eclipse
.tracecompass
.tmf
.ui
.views
.TmfView
;
57 import com
.google
.common
.collect
.Iterables
;
60 * Base view showing output of Babeltrace scripts.
62 * Implementations can specify which analysis modules to use, which will define
63 * the scripts and parameters to use accordingly.
65 * @author Alexandre Montplaisir
67 public final class LamiReportView
extends TmfView
{
69 // ------------------------------------------------------------------------
71 // ------------------------------------------------------------------------
74 public static final String VIEW_ID
= "org.eclipse.tracecompass.analysis.lami.views.reportview"; //$NON-NLS-1$
76 private final @Nullable LamiResultTable fResultTable
;
78 private @Nullable LamiViewerControl fTableViewerControl
;
79 private final Set
<LamiViewerControl
> fPredefGraphViewerControls
= new LinkedHashSet
<>();
80 private final Set
<LamiViewerControl
> fCustomGraphViewerControls
= new LinkedHashSet
<>();
81 private @Nullable SashForm fSashForm
;
82 private Set
<Integer
> fSelectionIndexes
;
84 // ------------------------------------------------------------------------
86 // ------------------------------------------------------------------------
91 public LamiReportView() {
93 fResultTable
= LamiReportViewFactory
.getCurrentResultTable();
94 fSelectionIndexes
= new HashSet
<>();
95 if (fResultTable
!= null) {
96 fSelectionIndexes
= getIndexOfEntriesIntersectingTimerange(checkNotNull(fResultTable
), TmfTraceManager
.getInstance().getCurrentTraceContext().getSelectionRange());
100 // ------------------------------------------------------------------------
102 // ------------------------------------------------------------------------
105 public void createPartControl(@Nullable Composite parent
) {
106 LamiResultTable resultTable
= fResultTable
;
107 if (resultTable
== null || parent
== null) {
111 SashForm sf
= new SashForm(parent
, SWT
.NONE
);
113 setPartName(resultTable
.getTableClass().getTableTitle());
115 /* Prepare the table viewer, which is always present */
116 LamiViewerControl tableViewerControl
= new LamiViewerControl(sf
, resultTable
);
117 fTableViewerControl
= tableViewerControl
;
119 /* Prepare the predefined graph viewers, if any */
120 resultTable
.getTableClass().getPredefinedViews()
121 .forEach(graphModel
-> fPredefGraphViewerControls
.add(new LamiViewerControl(sf
, resultTable
, graphModel
)));
123 /* Automatically open the table viewer initially */
124 tableViewerControl
.getToggleAction().run();
126 /* Add toolbar buttons */
127 IToolBarManager toolbarMgr
= getViewSite().getActionBars().getToolBarManager();
128 toolbarMgr
.add(tableViewerControl
.getToggleAction());
129 fPredefGraphViewerControls
.stream()
130 .map(LamiViewerControl
::getToggleAction
)
131 .forEach(toolbarMgr
::add
);
133 IMenuManager menuMgr
= getViewSite().getActionBars().getMenuManager();
134 IAction newBarChartAction
= new NewChartAction(checkNotNull(parent
.getShell()), sf
, resultTable
, ChartType
.BAR_CHART
);
135 IAction newXYScatterAction
= new NewChartAction(checkNotNull(parent
.getShell()), sf
, resultTable
, ChartType
.XY_SCATTER
);
137 newBarChartAction
.setText(Messages
.LamiReportView_NewCustomBarChart
);
138 newXYScatterAction
.setText(Messages
.LamiReportView_NewCustomScatterChart
);
141 IAction clearCustomViewsAction
= new Action() {
144 fCustomGraphViewerControls
.forEach(LamiViewerControl
::dispose
);
145 fCustomGraphViewerControls
.clear();
150 clearCustomViewsAction
.setText(Messages
.LamiReportView_ClearAllCustomViews
);
152 menuMgr
.add(newBarChartAction
);
153 menuMgr
.add(newXYScatterAction
);
154 menuMgr
.add(new Separator());
155 menuMgr
.add(clearCustomViewsAction
);
157 /* Simulate a new external signal to the default viewer */
158 LamiSelectionUpdateSignal signal
= new LamiSelectionUpdateSignal(LamiReportView
.this, fSelectionIndexes
, checkNotNull(fResultTable
).hashCode());
159 TmfSignalManager
.dispatchSignal(signal
);
162 // ------------------------------------------------------------------------
164 // ------------------------------------------------------------------------
167 public void setFocus() {
171 public void dispose() {
173 if (fSashForm
!= null) {
176 if (fTableViewerControl
!= null) {
177 fTableViewerControl
.dispose();
179 fPredefGraphViewerControls
.forEach(LamiViewerControl
::dispose
);
180 fCustomGraphViewerControls
.forEach(LamiViewerControl
::dispose
);
183 private class NewChartAction
extends Action
{
185 private final Shell icfDialogParentShell
;
186 private final Composite icfChartViewerParent
;
187 private final LamiResultTable icfResultTable
;
188 private boolean icfXLogScale
;
189 private boolean icfYLogScale
;
190 private final ChartType icfChartType
;
192 public NewChartAction(Shell parentShell
, Composite chartViewerParent
,
193 LamiResultTable resultTable
, ChartType chartType
) {
194 icfDialogParentShell
= parentShell
;
195 icfChartViewerParent
= chartViewerParent
;
196 icfResultTable
= resultTable
;
197 icfXLogScale
= false;
198 icfYLogScale
= false;
199 icfChartType
= chartType
;
204 int xLogScaleOptionIndex
= -1;
205 int yLogScaleOptionIndex
= -1;
207 List
<LamiTableEntryAspect
> xStringColumn
= icfResultTable
.getTableClass().getAspects().stream()
208 .filter(aspect
-> !(aspect
instanceof LamiEmptyAspect
))
209 .collect(Collectors
.toList());
211 /* Get the flattened aspects for Y since mapping an aggregate aspect to y series make no sense so far */
212 List
<LamiTableEntryAspect
> yStringColumn
= icfResultTable
.getTableClass().getAspects().stream()
213 .filter(aspect
-> !(aspect
instanceof LamiEmptyAspect
))
214 .collect(Collectors
.toList());
216 switch (icfChartType
) {
218 /* Y value must strictly continous and non timestamp */
219 yStringColumn
= yStringColumn
.stream()
220 .filter(aspect
-> !aspect
.isTimeStamp() && aspect
.isContinuous())
221 .collect(Collectors
.toList());
231 IStructuredContentProvider contentProvider
= checkNotNull(ArrayContentProvider
.getInstance());
233 LamiSeriesDialog dialog
= new LamiSeriesDialog(icfDialogParentShell
,
238 new LabelProvider() {
240 public String
getText(@Nullable Object element
) {
241 return ((LamiTableEntryAspect
) checkNotNull(element
)).getLabel();
245 new LabelProvider() {
247 public String
getText(@Nullable Object element
) {
248 return ((LamiTableEntryAspect
) checkNotNull(element
)).getLabel();
251 dialog
.setTitle(icfChartType
.toString() + ' ' + Messages
.LamiSeriesDialog_creation
);
253 /* X options per chart type */
254 switch (icfChartType
) {
256 xLogScaleOptionIndex
= dialog
.addXCheckBoxOption(
257 Messages
.LamiSeriesDialog_x_axis
+ ' ' + Messages
.LamiReportView_LogScale
,
258 false, new Predicate
<LamiTableEntryAspect
>() {
260 public boolean test(@NonNull LamiTableEntryAspect t
) {
261 return t
.isContinuous() && !t
.isTimeStamp();
271 /* Y options per chart type */
272 switch (icfChartType
) {
275 yLogScaleOptionIndex
= dialog
.addYCheckBoxOption(
276 Messages
.LamiSeriesDialog_y_axis
+ ' ' + Messages
.LamiReportView_LogScale
,
277 false, new Predicate
<LamiTableEntryAspect
>() {
279 public boolean test(@NonNull LamiTableEntryAspect t
) {
280 return t
.isContinuous() && !t
.isTimeStamp();
290 if (dialog
.open() != Window
.OK
) {
294 List
<LamiXYSeriesDescription
> results
= Arrays
.stream(dialog
.getResult())
295 .map(serie
-> (LamiXYSeriesDescription
) serie
)
296 .collect(Collectors
.toList());
298 boolean[] xCheckBoxOptionsResults
= dialog
.getXCheckBoxOptionValues();
299 boolean[] yCheckBoxOptionsResults
= dialog
.getYCheckBoxOptionValues();
301 /* Get X log scale option */
302 if (xLogScaleOptionIndex
> -1 && xLogScaleOptionIndex
< xCheckBoxOptionsResults
.length
) {
303 icfXLogScale
= xCheckBoxOptionsResults
[xLogScaleOptionIndex
];
305 /* Get Y log scale option */
306 if (yLogScaleOptionIndex
> -1 && yLogScaleOptionIndex
< yCheckBoxOptionsResults
.length
) {
307 icfYLogScale
= yCheckBoxOptionsResults
[yLogScaleOptionIndex
];
310 List
<String
> xAxisColString
= new ArrayList
<>();
311 List
<String
> yAxisColString
= new ArrayList
<>();
313 /* Specific chart type result fetching */
314 switch (icfChartType
) {
317 /* Validate that we only have 1 X aspect */
319 .map(element
-> element
.getXAspect().getLabel())
322 throw new IllegalStateException("No unique X axis label for results"); //$NON-NLS-1$
324 xAxisColString
= results
.stream()
325 .map(element
-> element
.getXAspect().getLabel())
327 .collect(Collectors
.toList());
330 xAxisColString
= results
.stream()
331 .map(element
-> element
.getXAspect().getLabel())
332 .collect(Collectors
.toList());
338 yAxisColString
= results
.stream()
339 .map(element
-> element
.getYAspect().getLabel())
340 .collect(Collectors
.toList());
342 LamiChartModel model
= new LamiChartModel(icfChartType
,
343 nullToEmptyString(Messages
.LamiReportView_Custom
),
349 LamiViewerControl viewerControl
= new LamiViewerControl(icfChartViewerParent
, icfResultTable
, model
);
350 fCustomGraphViewerControls
.add(viewerControl
);
351 viewerControl
.getToggleAction().run();
353 /* Signal the current selection to the newly created graph */
354 LamiSelectionUpdateSignal signal
= new LamiSelectionUpdateSignal(LamiReportView
.this, fSelectionIndexes
, checkNotNull(fResultTable
).hashCode());
355 TmfSignalManager
.dispatchSignal(signal
);
359 // ------------------------------------------------------------------------
361 // ------------------------------------------------------------------------
364 * Signal handler for selection update.
365 * Propagate a TmfSelectionRangeUpdatedSignal if possible.
368 * The selection update signal
371 public void updateSelection(LamiSelectionUpdateSignal signal
) {
372 LamiResultTable table
= fResultTable
;
377 if (table
.hashCode() != signal
.getSignalHash() || equals(signal
.getSource())) {
378 /* The signal is not for us */
382 Set
<Integer
> entryIndex
= signal
.getEntryIndex();
385 * Since most of the external viewers deal only with continuous time
386 * ranges and do not allow multi-time range selection, simply signal
387 * only when one selection is present.
390 if (entryIndex
.isEmpty()) {
392 * In an ideal world we would send a null signal to reset all view
393 * and simply show no selection. But since this is Tracecompass
394 * there is no notion of "unselected state" in most of the viewers so
395 * we do not update/clear the last timerange and show false information to the user.
400 if (entryIndex
.size() == 1) {
401 int index
= Iterables
.getOnlyElement(entryIndex
).intValue();
402 LamiTimeRange timeRange
= table
.getEntries().get(index
).getCorrespondingTimeRange();
403 if (timeRange
!= null) {
404 /* Send Range update to other views */
405 ITmfTimestamp start
= TmfTimestamp
.fromNanos(timeRange
.getStart());
406 ITmfTimestamp end
= TmfTimestamp
.fromNanos(timeRange
.getEnd());
407 TmfSignalManager
.dispatchSignal(new TmfSelectionRangeUpdatedSignal(LamiReportView
.this, start
, end
));
411 fSelectionIndexes
= entryIndex
;
415 * Signal handler for time range selections
418 * The received signal
421 public void externalUpdateSelection(TmfSelectionRangeUpdatedSignal signal
) {
422 LamiResultTable table
= fResultTable
;
427 if (signal
.getSource() == this) {
428 /* We are the source */
431 TmfTimeRange range
= new TmfTimeRange(signal
.getBeginTime(), signal
.getEndTime());
433 Set
<Integer
> selections
= getIndexOfEntriesIntersectingTimerange(table
, range
);
435 /* Update all LamiViewer */
436 LamiSelectionUpdateSignal signal1
= new LamiSelectionUpdateSignal(LamiReportView
.this, selections
, table
.hashCode());
437 TmfSignalManager
.dispatchSignal(signal1
);
440 private static Set
<Integer
> getIndexOfEntriesIntersectingTimerange(LamiResultTable table
, TmfTimeRange range
) {
441 Set
<Integer
> selections
= new HashSet
<>();
442 for (LamiTableEntry entry
: table
.getEntries()) {
443 LamiTimeRange timerange
= entry
.getCorrespondingTimeRange();
444 if (timerange
== null) {
445 /* Return since the table have no timerange */
449 TmfTimeRange tempTimeRange
= new TmfTimeRange(TmfTimestamp
.fromNanos(timerange
.getStart()), TmfTimestamp
.fromNanos(timerange
.getEnd()));
450 if (tempTimeRange
.getIntersection(range
) != null) {
451 selections
.add(table
.getEntries().indexOf(entry
));