c39fe80775f0ed035c2aaf824e330cd2fdf5691d
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.analysis.xml.ui / src / org / eclipse / tracecompass / tmf / analysis / xml / ui / views / timegraph / XmlTimeGraphView.java
1 /*******************************************************************************
2 * Copyright (c) 2014 École Polytechnique de Montréal
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 * Florian Wininger - Initial API and implementation
11 * Geneviève Bastien - Review of the initial implementation
12 *******************************************************************************/
13
14 package org.eclipse.tracecompass.tmf.analysis.xml.ui.views.timegraph;
15
16 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
17
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Comparator;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.TreeSet;
28
29 import org.eclipse.core.runtime.IProgressMonitor;
30 import org.eclipse.core.runtime.IStatus;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.eclipse.jface.util.IPropertyChangeListener;
33 import org.eclipse.jface.util.PropertyChangeEvent;
34 import org.eclipse.swt.widgets.Display;
35 import org.eclipse.tracecompass.common.core.NonNullUtils;
36 import org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.Activator;
37 import org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.TmfXmlUiStrings;
38 import org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.views.XmlViewInfo;
39 import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
40 import org.eclipse.tracecompass.statesystem.core.StateSystemUtils;
41 import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
42 import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
43 import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
44 import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
45 import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
46 import org.eclipse.tracecompass.tmf.analysis.xml.core.model.ITmfXmlModelFactory;
47 import org.eclipse.tracecompass.tmf.analysis.xml.core.model.ITmfXmlStateAttribute;
48 import org.eclipse.tracecompass.tmf.analysis.xml.core.model.readonly.TmfXmlReadOnlyModelFactory;
49 import org.eclipse.tracecompass.tmf.analysis.xml.core.module.IXmlStateSystemContainer;
50 import org.eclipse.tracecompass.tmf.analysis.xml.core.module.XmlUtils;
51 import org.eclipse.tracecompass.tmf.analysis.xml.core.stateprovider.TmfXmlStrings;
52 import org.eclipse.tracecompass.tmf.analysis.xml.ui.views.timegraph.XmlEntry.EntryDisplayType;
53 import org.eclipse.tracecompass.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems;
54 import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
55 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
56 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
57 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
58 import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView;
59 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider2;
60 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent;
61 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
62 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
63 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.NullTimeEvent;
64 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeEvent;
65 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry;
66 import org.w3c.dom.Element;
67
68 import com.google.common.collect.Iterables;
69
70 /**
71 * This view displays state system data in a time graph view. It uses an XML
72 * {@link TmfXmlUiStrings#TIME_GRAPH_VIEW} element from an XML file. This
73 * element defines which entries from the state system will be shown and also
74 * gives additional information on the presentation of the view (states, colors,
75 * etc)
76 *
77 * @author Florian Wininger
78 */
79 public class XmlTimeGraphView extends AbstractTimeGraphView {
80
81 /** View ID. */
82 public static final @NonNull String ID = "org.eclipse.linuxtools.tmf.analysis.xml.ui.views.timegraph"; //$NON-NLS-1$
83
84 private static final String[] DEFAULT_COLUMN_NAMES = new String[] {
85 Messages.XmlTimeGraphView_ColumnName,
86 Messages.XmlTimeGraphView_ColumnId,
87 Messages.XmlTimeGraphView_ColumnParentId,
88 };
89
90 private static final String[] DEFAULT_FILTER_COLUMN_NAMES = new String[] {
91 Messages.XmlTimeGraphView_ColumnName,
92 Messages.XmlTimeGraphView_ColumnId
93 };
94
95 /** The relative weight of the sash */
96 private static final int[] fWeight = { 1, 2 };
97
98 private static final String EMPTY_STRING = ""; //$NON-NLS-1$
99 private static final @NonNull String SPLIT_STRING = "/"; //$NON-NLS-1$
100
101 private final @NonNull XmlViewInfo fViewInfo = new XmlViewInfo(ID);
102 private final ITmfXmlModelFactory fFactory;
103
104 // ------------------------------------------------------------------------
105 // Constructors
106 // ------------------------------------------------------------------------
107
108 /**
109 * Default constructor
110 */
111 public XmlTimeGraphView() {
112 super(ID, new XmlPresentationProvider());
113 setWeight(fWeight);
114 setTreeColumns(DEFAULT_COLUMN_NAMES);
115 setTreeLabelProvider(new XmlTreeLabelProvider());
116 setFilterColumns(DEFAULT_FILTER_COLUMN_NAMES);
117 setFilterLabelProvider(new XmlTreeLabelProvider());
118 setEntryComparator(new XmlEntryComparator());
119 this.addPartPropertyListener(new IPropertyChangeListener() {
120 @Override
121 public void propertyChange(PropertyChangeEvent event) {
122 if (event.getProperty().equals(TmfXmlUiStrings.XML_OUTPUT_DATA)) {
123 Object newValue = event.getNewValue();
124 if (newValue instanceof String) {
125 String data = (String) newValue;
126 fViewInfo.setViewData(data);
127 loadNewXmlView();
128 }
129 }
130 }
131 });
132
133 fFactory = TmfXmlReadOnlyModelFactory.getInstance();
134 }
135
136 private void loadNewXmlView() {
137 rebuild();
138 }
139
140 private void setViewTitle(final String title) {
141 Display.getDefault().asyncExec(new Runnable() {
142 @Override
143 public void run() {
144 setPartName(title);
145 }
146 });
147
148 }
149
150 @Override
151 protected String getNextText() {
152 return Messages.XmlTimeGraphView_NextText;
153 }
154
155 @Override
156 protected String getNextTooltip() {
157 return Messages.XmlTimeGraphView_NextTooltip;
158 }
159
160 @Override
161 protected String getPrevText() {
162 return Messages.XmlTimeGraphView_PreviousText;
163 }
164
165 @Override
166 protected String getPrevTooltip() {
167 return Messages.XmlTimeGraphView_PreviousInterval;
168 }
169
170 /**
171 * Default label provider, it shows name, id and parent columns
172 *
173 * TODO: There should be a way to define columns in the XML
174 */
175 private static class XmlTreeLabelProvider extends TreeLabelProvider {
176
177 @Override
178 public String getColumnText(Object element, int columnIndex) {
179 XmlEntry entry = (XmlEntry) element;
180
181 if (DEFAULT_COLUMN_NAMES[columnIndex].equals(Messages.XmlTimeGraphView_ColumnName)) {
182 return entry.getName();
183 } else if (DEFAULT_COLUMN_NAMES[columnIndex].equals(Messages.XmlTimeGraphView_ColumnId)) {
184 return entry.getId();
185 } else if (DEFAULT_COLUMN_NAMES[columnIndex].equals(Messages.XmlTimeGraphView_ColumnParentId)) {
186 return entry.getParentId();
187 }
188 return EMPTY_STRING;
189 }
190
191 }
192
193 private static class XmlEntryComparator implements Comparator<ITimeGraphEntry> {
194
195 @Override
196 public int compare(ITimeGraphEntry o1, ITimeGraphEntry o2) {
197
198 int result = 0;
199
200 if ((o1 instanceof XmlEntry) && (o2 instanceof XmlEntry)) {
201 XmlEntry entry1 = (XmlEntry) o1;
202 XmlEntry entry2 = (XmlEntry) o2;
203 result = entry1.getTrace().getStartTime().compareTo(entry2.getTrace().getStartTime());
204 if (result == 0) {
205 result = entry1.getTrace().getName().compareTo(entry2.getTrace().getName());
206 }
207 if (result == 0) {
208 result = entry1.getName().compareTo(entry2.getName());
209 }
210 }
211
212 if (result == 0) {
213 result = o1.getStartTime() < o2.getStartTime() ? -1 : o1.getStartTime() > o2.getStartTime() ? 1 : 0;
214 }
215
216 return result;
217 }
218 }
219
220 // ------------------------------------------------------------------------
221 // Internal
222 // ------------------------------------------------------------------------
223
224 @Override
225 protected void buildEventList(ITmfTrace trace, ITmfTrace parentTrace, IProgressMonitor monitor) {
226
227 /*
228 * Get the view element from the XML file. If the element can't be
229 * found, return.
230 */
231 Element viewElement = fViewInfo.getViewElement(TmfXmlUiStrings.TIME_GRAPH_VIEW);
232 if (viewElement == null) {
233 return;
234 }
235 ITimeGraphPresentationProvider2 pres = this.getPresentationProvider();
236 if (pres instanceof XmlPresentationProvider) {
237 /*
238 * TODO: Each entry of a line could have their own states/color.
239 * That will require an update to the presentation provider
240 */
241 ((XmlPresentationProvider) pres).loadNewStates(viewElement);
242 }
243
244 String title = fViewInfo.getViewTitle(viewElement);
245 if (title == null) {
246 title = Messages.XmlTimeGraphView_DefaultTitle;
247 }
248 setViewTitle(title);
249 Set<String> analysisIds = fViewInfo.getViewAnalysisIds(viewElement);
250
251 List<Element> entries = XmlUtils.getChildElements(viewElement, TmfXmlUiStrings.ENTRY_ELEMENT);
252 Set<XmlEntry> entryList = new TreeSet<>(getEntryComparator());
253 if (monitor.isCanceled()) {
254 return;
255 }
256
257 Set<@NonNull ITmfAnalysisModuleWithStateSystems> stateSystemModules = new HashSet<>();
258 if (analysisIds.isEmpty()) {
259 /*
260 * No analysis specified, take all state system analysis modules
261 */
262 Iterables.addAll(stateSystemModules, TmfTraceUtils.getAnalysisModulesOfClass(trace, ITmfAnalysisModuleWithStateSystems.class));
263 } else {
264 for (String moduleId : analysisIds) {
265 moduleId = checkNotNull(moduleId);
266 ITmfAnalysisModuleWithStateSystems module = TmfTraceUtils.getAnalysisModuleOfClass(trace, ITmfAnalysisModuleWithStateSystems.class, moduleId);
267 if (module != null) {
268 stateSystemModules.add(module);
269 }
270 }
271 }
272
273 for (ITmfAnalysisModuleWithStateSystems module : stateSystemModules) {
274 IStatus status = module.schedule();
275 if (!status.isOK()) {
276 return;
277 }
278 if (module instanceof TmfStateSystemAnalysisModule) {
279 ((TmfStateSystemAnalysisModule) module).waitForInitialization();
280 }
281 for (ITmfStateSystem ssq : module.getStateSystems()) {
282 ssq.waitUntilBuilt();
283
284 long startTime = ssq.getStartTime();
285 long endTime = ssq.getCurrentEndTime();
286 XmlEntry groupEntry = new XmlEntry(-1, trace, trace.getName(), ssq);
287 entryList.add(groupEntry);
288 setStartTime(Math.min(getStartTime(), startTime));
289 setEndTime(Math.max(getEndTime(), endTime));
290
291 /* Add children entry of this entry for each line */
292 for (Element entry : entries) {
293 buildEntry(entry, groupEntry, -1);
294 }
295 }
296 }
297
298 putEntryList(trace, new ArrayList<TimeGraphEntry>(entryList));
299
300 if (parentTrace.equals(getTrace())) {
301 refresh();
302 }
303 for (XmlEntry traceEntry : entryList) {
304 if (monitor.isCanceled()) {
305 return;
306 }
307 long startTime = traceEntry.getStateSystem().getStartTime();
308 long endTime = traceEntry.getStateSystem().getCurrentEndTime() + 1;
309 buildStatusEvent(traceEntry, monitor, startTime, endTime);
310 }
311 }
312
313 private void buildEntry(Element entryElement, XmlEntry parentEntry, int baseQuark) {
314 /* Get the attribute string to display */
315 String path = entryElement.getAttribute(TmfXmlUiStrings.PATH);
316 if (path.isEmpty()) {
317 path = TmfXmlStrings.WILDCARD;
318 }
319
320 /*
321 * Make sure the XML element has either a display attribute or entries,
322 * otherwise issue a warning
323 */
324
325 List<Element> displayElements = XmlUtils.getChildElements(entryElement, TmfXmlUiStrings.DISPLAY_ELEMENT);
326 List<Element> entryElements = XmlUtils.getChildElements(entryElement, TmfXmlUiStrings.ENTRY_ELEMENT);
327
328 if (displayElements.isEmpty() && entryElements.isEmpty()) {
329 Activator.logWarning(String.format("XML view: entry for %s should have either a display element or entry elements", path)); //$NON-NLS-1$
330 return;
331 }
332
333 ITmfStateSystem ss = parentEntry.getStateSystem();
334
335 /* Get the list of quarks to process with this path */
336 String[] paths = path.split(SPLIT_STRING);
337 int i = 0;
338 List<Integer> quarks = Collections.singletonList(baseQuark);
339
340 try {
341 while (i < paths.length) {
342 List<Integer> subQuarks = new LinkedList<>();
343 /* Replace * by .* to have a regex string */
344 String name = paths[i].replaceAll("\\*", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
345 for (int relativeQuark : quarks) {
346 for (int quark : ss.getSubAttributes(relativeQuark, false, name)) {
347 subQuarks.add(quark);
348 }
349 }
350 quarks = subQuarks;
351 i++;
352 }
353
354 /* Process each quark */
355 XmlEntry currentEntry = parentEntry;
356 Element displayElement = null;
357 Map<String, XmlEntry> entryMap = new HashMap<>();
358 if (!displayElements.isEmpty()) {
359 displayElement = displayElements.get(0);
360 }
361 for (int quark : quarks) {
362 currentEntry = parentEntry;
363 /* Process the current entry, if specified */
364 if (displayElement != null) {
365 currentEntry = processEntry(entryElement, displayElement, parentEntry, quark, ss);
366 entryMap.put(currentEntry.getId(), currentEntry);
367 }
368 /* Process the children entry of this entry */
369 for (Element subEntryEl : entryElements) {
370 buildEntry(subEntryEl, currentEntry, quark);
371 }
372 }
373 if (!entryMap.isEmpty()) {
374 buildTree(entryMap, parentEntry);
375 }
376 } catch (AttributeNotFoundException e) {
377 }
378 }
379
380 private XmlEntry processEntry(@NonNull Element entryElement, @NonNull Element displayEl,
381 @NonNull XmlEntry parentEntry, int quark, ITmfStateSystem ss) {
382 /*
383 * Get the start time and end time of this entry from the display
384 * attribute
385 */
386 ITmfXmlStateAttribute display = fFactory.createStateAttribute(displayEl, parentEntry);
387 int displayQuark = display.getAttributeQuark(quark);
388 if (displayQuark == IXmlStateSystemContainer.ERROR_QUARK) {
389 return new XmlEntry(quark, parentEntry.getTrace(),
390 String.format("Unknown display quark for %s", ss.getAttributeName(quark)), ss); //$NON-NLS-1$
391 }
392
393 long entryStart = ss.getStartTime();
394 long entryEnd = ss.getCurrentEndTime();
395
396 try {
397
398 ITmfStateInterval oneInterval = ss.querySingleState(entryStart, displayQuark);
399
400 /* The entry start is the first non-null interval */
401 while (oneInterval.getStateValue().isNull()) {
402 long ts = oneInterval.getEndTime() + 1;
403 if (ts > ss.getCurrentEndTime()) {
404 break;
405 }
406 oneInterval = ss.querySingleState(ts, displayQuark);
407 }
408 entryStart = oneInterval.getStartTime();
409
410 /* The entry end is the last non-null interval */
411 oneInterval = ss.querySingleState(entryEnd, displayQuark);
412 while (oneInterval.getStateValue().isNull()) {
413 long ts = oneInterval.getStartTime() - 1;
414 if (ts < ss.getStartTime()) {
415 break;
416 }
417 oneInterval = ss.querySingleState(ts, displayQuark);
418 }
419 entryEnd = oneInterval.getEndTime();
420
421 } catch (AttributeNotFoundException | StateSystemDisposedException e) {
422 }
423
424 return new XmlEntry(quark, displayQuark, parentEntry.getTrace(), ss.getAttributeName(quark),
425 entryStart, entryEnd, EntryDisplayType.DISPLAY, ss, entryElement);
426 }
427
428 private void buildStatusEvent(XmlEntry traceEntry, @NonNull IProgressMonitor monitor, long start, long end) {
429 long resolution = (end - start) / getDisplayWidth();
430 long startTime = Math.max(start, traceEntry.getStartTime());
431 long endTime = Math.min(end + 1, traceEntry.getEndTime());
432 List<ITimeEvent> eventList = getEventList(traceEntry, startTime, endTime, resolution, monitor);
433 if (monitor.isCanceled()) {
434 return;
435 }
436 traceEntry.setEventList(eventList);
437 redraw();
438
439 for (ITimeGraphEntry entry : traceEntry.getChildren()) {
440 if (monitor.isCanceled()) {
441 return;
442 }
443 XmlEntry xmlEntry = (XmlEntry) entry;
444 buildStatusEvent(xmlEntry, monitor, start, end);
445 }
446 }
447
448 /** Build a tree using getParentId() and getId() */
449 private static void buildTree(Map<String, XmlEntry> entryMap, XmlEntry rootEntry) {
450 for (XmlEntry entry : entryMap.values()) {
451 boolean root = true;
452 if (!entry.getParentId().isEmpty()) {
453 XmlEntry parent = entryMap.get(entry.getParentId());
454 /*
455 * Associate the parent entry only if their time overlap. A
456 * child entry may start before its parent, for example at the
457 * beginning of the trace if a parent has not yet appeared in
458 * the state system. We just want to make sure that the entry
459 * didn't start after the parent ended or ended before the
460 * parent started.
461 */
462 if (parent != null &&
463 !(entry.getStartTime() > parent.getEndTime() ||
464 entry.getEndTime() < parent.getStartTime())) {
465 parent.addChild(entry);
466 root = false;
467 }
468 }
469 if (root) {
470 rootEntry.addChild(entry);
471 }
472 }
473 }
474
475 @Override
476 protected List<ITimeEvent> getEventList(TimeGraphEntry entry, long startTime, long endTime, long resolution, IProgressMonitor monitor) {
477 if (!(entry instanceof XmlEntry)) {
478 return Collections.EMPTY_LIST;
479 }
480 XmlEntry xmlEntry = (XmlEntry) entry;
481 ITmfStateSystem ssq = xmlEntry.getStateSystem();
482 final long realStart = Math.max(startTime, entry.getStartTime());
483 final long realEnd = Math.min(endTime, entry.getEndTime());
484 if (realEnd <= realStart) {
485 return null;
486 }
487 List<ITimeEvent> eventList = null;
488 int quark = xmlEntry.getDisplayQuark();
489
490 try {
491 if (xmlEntry.getType() == EntryDisplayType.DISPLAY) {
492
493 List<ITmfStateInterval> statusIntervals = StateSystemUtils.queryHistoryRange(ssq, quark, realStart, realEnd - 1, resolution, monitor);
494 eventList = new ArrayList<>(statusIntervals.size());
495 long lastEndTime = -1;
496 for (ITmfStateInterval statusInterval : statusIntervals) {
497 if (monitor.isCanceled()) {
498 return null;
499 }
500 int status = statusInterval.getStateValue().unboxInt();
501 long time = statusInterval.getStartTime();
502 long duration = statusInterval.getEndTime() - time + 1;
503 if (!statusInterval.getStateValue().isNull()) {
504 if (lastEndTime != time && lastEndTime != -1) {
505 eventList.add(new TimeEvent(entry, lastEndTime, time - lastEndTime));
506 }
507 eventList.add(new TimeEvent(entry, time, duration, status));
508 } else if (lastEndTime == -1 || time + duration >= endTime) {
509 // add null event if it intersects the start or end time
510 eventList.add(new NullTimeEvent(entry, time, duration));
511 }
512 lastEndTime = time + duration;
513 }
514 }
515 } catch (AttributeNotFoundException | TimeRangeException | StateValueTypeException | StateSystemDisposedException e) {
516 /* Ignored */
517 }
518 return eventList;
519 }
520
521 @Override
522 protected List<ILinkEvent> getLinkList(long startTime, long endTime, long resolution, IProgressMonitor monitor) {
523 /* TODO: not implemented yet, need XML to go along */
524 return Collections.EMPTY_LIST;
525 }
526
527 /**
528 * This method will pre-filter the traces that contain analysis modules
529 * supported by this view, whether they are from a trace or an experiment.
530 */
531 @Override
532 protected Iterable<ITmfTrace> getTracesToBuild(ITmfTrace trace) {
533 /*
534 * Get the view element from the XML file. If the element can't be
535 * found, return.
536 */
537 Element viewElement = fViewInfo.getViewElement(TmfXmlUiStrings.TIME_GRAPH_VIEW);
538 if (viewElement == null) {
539 return super.getTracesToBuild(trace);
540 }
541
542 Set<String> analysisIds = fViewInfo.getViewAnalysisIds(viewElement);
543 Set<ITmfTrace> traces = new HashSet<>();
544
545 for (ITmfTrace aTrace : TmfTraceManager.getTraceSetWithExperiment(trace)) {
546 if (aTrace == null) {
547 continue;
548 }
549 if ((analysisIds.isEmpty() && TmfTraceUtils.getAnalysisModulesOfClass(aTrace, ITmfAnalysisModuleWithStateSystems.class).iterator().hasNext())) {
550 /*
551 * No analysis ID specified, so this trace will be built only if
552 * it has state system modules
553 */
554 traces.add(aTrace);
555 } else {
556 /* Build this trace only if it has one the requested modules */
557 for (String moduleId : analysisIds) {
558 if (TmfTraceUtils.getAnalysisModuleOfClass(aTrace, ITmfAnalysisModuleWithStateSystems.class, NonNullUtils.checkNotNull(moduleId)) != null) {
559 traces.add(aTrace);
560 }
561 }
562 }
563 }
564 if (traces.isEmpty()) {
565 return super.getTracesToBuild(trace);
566 }
567 return traces;
568 }
569 }
This page took 0.043847 seconds and 4 git commands to generate.