analysis.lami: Implementation of LAMI plugins
[deliverable/tracecompass.git] / analysis / org.eclipse.tracecompass.analysis.lami.ui / src / org / eclipse / tracecompass / internal / provisional / analysis / lami / ui / views / LamiReportView.java
1 /*******************************************************************************
2 * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
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
10 package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views;
11
12 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
13 import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
14
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;
20 import java.util.Set;
21 import java.util.function.Predicate;
22 import java.util.stream.Collectors;
23
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;
56
57 import com.google.common.collect.Iterables;
58
59 /**
60 * Base view showing output of Babeltrace scripts.
61 *
62 * Implementations can specify which analysis modules to use, which will define
63 * the scripts and parameters to use accordingly.
64 *
65 * @author Alexandre Montplaisir
66 */
67 public final class LamiReportView extends TmfView {
68
69 // ------------------------------------------------------------------------
70 // Attributes
71 // ------------------------------------------------------------------------
72
73 /** View ID */
74 public static final String VIEW_ID = "org.eclipse.tracecompass.analysis.lami.views.reportview"; //$NON-NLS-1$
75
76 private final @Nullable LamiResultTable fResultTable;
77
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;
83
84 // ------------------------------------------------------------------------
85 // Constructor
86 // ------------------------------------------------------------------------
87
88 /**
89 * Constructor
90 */
91 public LamiReportView() {
92 super(VIEW_ID);
93 fResultTable = LamiReportViewFactory.getCurrentResultTable();
94 fSelectionIndexes = new HashSet<>();
95 if (fResultTable != null) {
96 fSelectionIndexes = getIndexOfEntriesIntersectingTimerange(checkNotNull(fResultTable), TmfTraceManager.getInstance().getCurrentTraceContext().getSelectionRange());
97 }
98 }
99
100 // ------------------------------------------------------------------------
101 // ViewPart
102 // ------------------------------------------------------------------------
103
104 @Override
105 public void createPartControl(@Nullable Composite parent) {
106 LamiResultTable resultTable = fResultTable;
107 if (resultTable == null || parent == null) {
108 return;
109 }
110
111 SashForm sf = new SashForm(parent, SWT.NONE);
112 fSashForm = sf;
113 setPartName(resultTable.getTableClass().getTableTitle());
114
115 /* Prepare the table viewer, which is always present */
116 LamiViewerControl tableViewerControl = new LamiViewerControl(sf, resultTable);
117 fTableViewerControl = tableViewerControl;
118
119 /* Prepare the predefined graph viewers, if any */
120 resultTable.getTableClass().getPredefinedViews()
121 .forEach(graphModel -> fPredefGraphViewerControls.add(new LamiViewerControl(sf, resultTable, graphModel)));
122
123 /* Automatically open the table viewer initially */
124 tableViewerControl.getToggleAction().run();
125
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);
132
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);
136
137 newBarChartAction.setText(Messages.LamiReportView_NewCustomBarChart);
138 newXYScatterAction.setText(Messages.LamiReportView_NewCustomScatterChart);
139
140
141 IAction clearCustomViewsAction = new Action() {
142 @Override
143 public void run() {
144 fCustomGraphViewerControls.forEach(LamiViewerControl::dispose);
145 fCustomGraphViewerControls.clear();
146 sf.layout();
147
148 }
149 };
150 clearCustomViewsAction.setText(Messages.LamiReportView_ClearAllCustomViews);
151
152 menuMgr.add(newBarChartAction);
153 menuMgr.add(newXYScatterAction);
154 menuMgr.add(new Separator());
155 menuMgr.add(clearCustomViewsAction);
156
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);
160 }
161
162 // ------------------------------------------------------------------------
163 // Operations
164 // ------------------------------------------------------------------------
165
166 @Override
167 public void setFocus() {
168 }
169
170 @Override
171 public void dispose() {
172 super.dispose();
173 if (fSashForm != null) {
174 fSashForm.dispose();
175 }
176 if (fTableViewerControl != null) {
177 fTableViewerControl.dispose();
178 }
179 fPredefGraphViewerControls.forEach(LamiViewerControl::dispose);
180 fCustomGraphViewerControls.forEach(LamiViewerControl::dispose);
181 }
182
183 private class NewChartAction extends Action {
184
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;
191
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;
200 }
201
202 @Override
203 public void run() {
204 int xLogScaleOptionIndex = -1;
205 int yLogScaleOptionIndex = -1;
206
207 List<LamiTableEntryAspect> xStringColumn = icfResultTable.getTableClass().getAspects().stream()
208 .filter(aspect -> !(aspect instanceof LamiEmptyAspect))
209 .collect(Collectors.toList());
210
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());
215
216 switch (icfChartType) {
217 case BAR_CHART:
218 /* Y value must strictly continous and non timestamp */
219 yStringColumn = yStringColumn.stream()
220 .filter(aspect -> !aspect.isTimeStamp() && aspect.isContinuous())
221 .collect(Collectors.toList());
222 break;
223 case PIE_CHART:
224 break;
225 case XY_SCATTER:
226 break;
227 default:
228 break;
229 }
230
231 IStructuredContentProvider contentProvider = checkNotNull(ArrayContentProvider.getInstance());
232
233 LamiSeriesDialog dialog = new LamiSeriesDialog(icfDialogParentShell,
234 icfChartType,
235 xStringColumn,
236 yStringColumn,
237 contentProvider,
238 new LabelProvider() {
239 @Override
240 public String getText(@Nullable Object element) {
241 return ((LamiTableEntryAspect) checkNotNull(element)).getLabel();
242 }
243 },
244 contentProvider,
245 new LabelProvider() {
246 @Override
247 public String getText(@Nullable Object element) {
248 return ((LamiTableEntryAspect) checkNotNull(element)).getLabel();
249 }
250 });
251 dialog.setTitle(icfChartType.toString() + ' ' + Messages.LamiSeriesDialog_creation);
252
253 /* X options per chart type */
254 switch (icfChartType) {
255 case XY_SCATTER:
256 xLogScaleOptionIndex = dialog.addXCheckBoxOption(
257 Messages.LamiSeriesDialog_x_axis + ' ' + Messages.LamiReportView_LogScale,
258 false, new Predicate<LamiTableEntryAspect>() {
259 @Override
260 public boolean test(@NonNull LamiTableEntryAspect t) {
261 return t.isContinuous() && !t.isTimeStamp();
262 }
263 });
264 break;
265 case BAR_CHART:
266 case PIE_CHART:
267 default:
268 break;
269 }
270
271 /* Y options per chart type */
272 switch (icfChartType) {
273 case BAR_CHART:
274 case XY_SCATTER:
275 yLogScaleOptionIndex = dialog.addYCheckBoxOption(
276 Messages.LamiSeriesDialog_y_axis + ' ' + Messages.LamiReportView_LogScale,
277 false, new Predicate<LamiTableEntryAspect>() {
278 @Override
279 public boolean test(@NonNull LamiTableEntryAspect t) {
280 return t.isContinuous() && !t.isTimeStamp();
281 }
282 });
283 break;
284
285 case PIE_CHART:
286 default:
287 break;
288 }
289
290 if (dialog.open() != Window.OK) {
291 return;
292 }
293
294 List<LamiXYSeriesDescription> results = Arrays.stream(dialog.getResult())
295 .map(serie -> (LamiXYSeriesDescription) serie)
296 .collect(Collectors.toList());
297
298 boolean[] xCheckBoxOptionsResults = dialog.getXCheckBoxOptionValues();
299 boolean[] yCheckBoxOptionsResults = dialog.getYCheckBoxOptionValues();
300
301 /* Get X log scale option */
302 if (xLogScaleOptionIndex > -1 && xLogScaleOptionIndex < xCheckBoxOptionsResults.length) {
303 icfXLogScale = xCheckBoxOptionsResults[xLogScaleOptionIndex];
304 }
305 /* Get Y log scale option */
306 if (yLogScaleOptionIndex > -1 && yLogScaleOptionIndex < yCheckBoxOptionsResults.length) {
307 icfYLogScale = yCheckBoxOptionsResults[yLogScaleOptionIndex];
308 }
309
310 List<String> xAxisColString = new ArrayList<>();
311 List<String> yAxisColString = new ArrayList<>();
312
313 /* Specific chart type result fetching */
314 switch (icfChartType) {
315 case PIE_CHART:
316 case BAR_CHART:
317 /* Validate that we only have 1 X aspect */
318 if (results.stream()
319 .map(element -> element.getXAspect().getLabel())
320 .distinct()
321 .count() != 1) {
322 throw new IllegalStateException("No unique X axis label for results"); //$NON-NLS-1$
323 }
324 xAxisColString = results.stream()
325 .map(element -> element.getXAspect().getLabel())
326 .distinct()
327 .collect(Collectors.toList());
328 break;
329 case XY_SCATTER:
330 xAxisColString = results.stream()
331 .map(element -> element.getXAspect().getLabel())
332 .collect(Collectors.toList());
333 break;
334 default:
335 break;
336 }
337
338 yAxisColString = results.stream()
339 .map(element -> element.getYAspect().getLabel())
340 .collect(Collectors.toList());
341
342 LamiChartModel model = new LamiChartModel(icfChartType,
343 nullToEmptyString(Messages.LamiReportView_Custom),
344 xAxisColString,
345 yAxisColString,
346 icfXLogScale,
347 icfYLogScale);
348
349 LamiViewerControl viewerControl = new LamiViewerControl(icfChartViewerParent, icfResultTable, model);
350 fCustomGraphViewerControls.add(viewerControl);
351 viewerControl.getToggleAction().run();
352
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);
356 }
357 }
358
359 // ------------------------------------------------------------------------
360 // Signals
361 // ------------------------------------------------------------------------
362
363 /**
364 * Signal handler for selection update.
365 * Propagate a TmfSelectionRangeUpdatedSignal if possible.
366 *
367 * @param signal
368 * The selection update signal
369 */
370 @TmfSignalHandler
371 public void updateSelection(LamiSelectionUpdateSignal signal) {
372 LamiResultTable table = fResultTable;
373 if (table == null) {
374 return;
375 }
376
377 if (table.hashCode() != signal.getSignalHash() || equals(signal.getSource())) {
378 /* The signal is not for us */
379 return;
380 }
381
382 Set<Integer> entryIndex = signal.getEntryIndex();
383
384 /*
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.
388 */
389
390 if (entryIndex.isEmpty()) {
391 /*
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.
396 */
397 return;
398 }
399
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));
408 }
409 }
410
411 fSelectionIndexes = entryIndex;
412 }
413
414 /**
415 * Signal handler for time range selections
416 *
417 * @param signal
418 * The received signal
419 */
420 @TmfSignalHandler
421 public void externalUpdateSelection(TmfSelectionRangeUpdatedSignal signal) {
422 LamiResultTable table = fResultTable;
423 if (table == null) {
424 return;
425 }
426
427 if (signal.getSource() == this) {
428 /* We are the source */
429 return;
430 }
431 TmfTimeRange range = new TmfTimeRange(signal.getBeginTime(), signal.getEndTime());
432
433 Set<Integer> selections = getIndexOfEntriesIntersectingTimerange(table, range);
434
435 /* Update all LamiViewer */
436 LamiSelectionUpdateSignal signal1 = new LamiSelectionUpdateSignal(LamiReportView.this, selections, table.hashCode());
437 TmfSignalManager.dispatchSignal(signal1);
438 }
439
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 */
446 return selections;
447 }
448
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));
452 }
453 }
454 return selections;
455 }
456 }
This page took 0.040774 seconds and 5 git commands to generate.