1 /*******************************************************************************
2 * Copyright (c) 2014, 2016 École Polytechnique de Montréal
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
10 * Florian Wininger - Initial API and implementation
11 * Geneviève Bastien - Review of the initial implementation
12 *******************************************************************************/
14 package org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.ui
.views
.timegraph
;
16 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
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
;
27 import java
.util
.TreeSet
;
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
.jdt
.annotation
.Nullable
;
33 import org
.eclipse
.jface
.util
.IPropertyChangeListener
;
34 import org
.eclipse
.jface
.util
.PropertyChangeEvent
;
35 import org
.eclipse
.swt
.widgets
.Composite
;
36 import org
.eclipse
.swt
.widgets
.Display
;
37 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
38 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.model
.ITmfXmlModelFactory
;
39 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.model
.ITmfXmlStateAttribute
;
40 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.model
.readonly
.TmfXmlReadOnlyModelFactory
;
41 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.module
.IXmlStateSystemContainer
;
42 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.module
.XmlUtils
;
43 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.stateprovider
.TmfXmlStrings
;
44 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.ui
.Activator
;
45 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.ui
.TmfXmlUiStrings
;
46 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.ui
.views
.XmlViewInfo
;
47 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.ui
.views
.timegraph
.XmlEntry
.EntryDisplayType
;
48 import org
.eclipse
.tracecompass
.statesystem
.core
.ITmfStateSystem
;
49 import org
.eclipse
.tracecompass
.statesystem
.core
.StateSystemUtils
;
50 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.AttributeNotFoundException
;
51 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.StateSystemDisposedException
;
52 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.StateValueTypeException
;
53 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.TimeRangeException
;
54 import org
.eclipse
.tracecompass
.statesystem
.core
.interval
.ITmfStateInterval
;
55 import org
.eclipse
.tracecompass
.statesystem
.core
.statevalue
.ITmfStateValue
;
56 import org
.eclipse
.tracecompass
.tmf
.core
.statesystem
.ITmfAnalysisModuleWithStateSystems
;
57 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.ITmfTrace
;
58 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfTraceUtils
;
59 import org
.eclipse
.tracecompass
.tmf
.ui
.views
.TmfViewFactory
;
60 import org
.eclipse
.tracecompass
.tmf
.ui
.views
.timegraph
.AbstractTimeGraphView
;
61 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.ITimeGraphPresentationProvider2
;
62 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ILinkEvent
;
63 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ITimeEvent
;
64 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ITimeGraphEntry
;
65 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.NullTimeEvent
;
66 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.TimeEvent
;
67 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.TimeGraphEntry
;
68 import org
.w3c
.dom
.Element
;
70 import com
.google
.common
.collect
.Iterables
;
73 * This view displays state system data in a time graph view. It uses an XML
74 * {@link TmfXmlUiStrings#TIME_GRAPH_VIEW} element from an XML file. This
75 * element defines which entries from the state system will be shown and also
76 * gives additional information on the presentation of the view (states, colors,
79 * @author Florian Wininger
81 public class XmlTimeGraphView
extends AbstractTimeGraphView
{
84 public static final @NonNull String ID
= "org.eclipse.linuxtools.tmf.analysis.xml.ui.views.timegraph"; //$NON-NLS-1$
86 private static final String
[] DEFAULT_COLUMN_NAMES
= new String
[] {
87 Messages
.XmlTimeGraphView_ColumnName
,
88 Messages
.XmlTimeGraphView_ColumnId
,
89 Messages
.XmlTimeGraphView_ColumnParentId
,
92 private static final String
[] DEFAULT_FILTER_COLUMN_NAMES
= new String
[] {
93 Messages
.XmlTimeGraphView_ColumnName
,
94 Messages
.XmlTimeGraphView_ColumnId
97 /** The relative weight of the sash */
98 private static final int[] fWeight
= { 1, 2 };
100 private static final String EMPTY_STRING
= ""; //$NON-NLS-1$
101 private static final @NonNull String SPLIT_STRING
= "/"; //$NON-NLS-1$
103 private final @NonNull XmlViewInfo fViewInfo
= new XmlViewInfo(ID
);
104 private final ITmfXmlModelFactory fFactory
;
105 private final Map
<String
, Integer
> fStringValueMap
= new HashMap
<>();
107 // ------------------------------------------------------------------------
109 // ------------------------------------------------------------------------
112 * Default constructor
114 public XmlTimeGraphView() {
115 super(ID
, new XmlPresentationProvider());
117 setTreeColumns(DEFAULT_COLUMN_NAMES
);
118 setTreeLabelProvider(new XmlTreeLabelProvider());
119 setFilterColumns(DEFAULT_FILTER_COLUMN_NAMES
);
120 setFilterLabelProvider(new XmlTreeLabelProvider());
121 setEntryComparator(new XmlEntryComparator());
122 this.addPartPropertyListener(new IPropertyChangeListener() {
124 public void propertyChange(PropertyChangeEvent event
) {
125 if (event
.getProperty().equals(TmfXmlUiStrings
.XML_OUTPUT_DATA
)) {
126 Object newValue
= event
.getNewValue();
127 if (newValue
instanceof String
) {
128 String data
= (String
) newValue
;
129 fViewInfo
.setViewData(data
);
135 fFactory
= TmfXmlReadOnlyModelFactory
.getInstance();
139 public void createPartControl(Composite parent
) {
140 super.createPartControl(parent
);
141 fViewInfo
.setName(NonNullUtils
.checkNotNull(TmfViewFactory
.getBaseSecId(getViewSite().getSecondaryId())));
144 private void loadNewXmlView() {
148 private void setViewTitle(final String title
) {
149 Display
.getDefault().asyncExec(new Runnable() {
159 protected String
getNextText() {
160 return Messages
.XmlTimeGraphView_NextText
;
164 protected String
getNextTooltip() {
165 return Messages
.XmlTimeGraphView_NextTooltip
;
169 protected String
getPrevText() {
170 return Messages
.XmlTimeGraphView_PreviousText
;
174 protected String
getPrevTooltip() {
175 return Messages
.XmlTimeGraphView_PreviousInterval
;
179 * Default label provider, it shows name, id and parent columns
181 * TODO: There should be a way to define columns in the XML
183 private static class XmlTreeLabelProvider
extends TreeLabelProvider
{
186 public String
getColumnText(Object element
, int columnIndex
) {
187 XmlEntry entry
= (XmlEntry
) element
;
189 if (DEFAULT_COLUMN_NAMES
[columnIndex
].equals(Messages
.XmlTimeGraphView_ColumnName
)) {
190 return entry
.getName();
191 } else if (DEFAULT_COLUMN_NAMES
[columnIndex
].equals(Messages
.XmlTimeGraphView_ColumnId
)) {
192 return entry
.getId();
193 } else if (DEFAULT_COLUMN_NAMES
[columnIndex
].equals(Messages
.XmlTimeGraphView_ColumnParentId
)) {
194 return entry
.getParentId();
201 private static class XmlEntryComparator
implements Comparator
<ITimeGraphEntry
> {
204 public int compare(ITimeGraphEntry o1
, ITimeGraphEntry o2
) {
208 if ((o1
instanceof XmlEntry
) && (o2
instanceof XmlEntry
)) {
209 XmlEntry entry1
= (XmlEntry
) o1
;
210 XmlEntry entry2
= (XmlEntry
) o2
;
211 result
= entry1
.getTrace().getStartTime().compareTo(entry2
.getTrace().getStartTime());
213 result
= entry1
.getTrace().getName().compareTo(entry2
.getTrace().getName());
216 result
= entry1
.getName().compareTo(entry2
.getName());
221 result
= o1
.getStartTime() < o2
.getStartTime() ?
-1 : o1
.getStartTime() > o2
.getStartTime() ?
1 : 0;
228 // ------------------------------------------------------------------------
230 // ------------------------------------------------------------------------
233 protected void buildEntryList(ITmfTrace trace
, ITmfTrace parentTrace
, IProgressMonitor monitor
) {
235 if (!fViewInfo
.waitForInitialization()) {
239 * Get the view element from the XML file. If the element can't be
242 Element viewElement
= fViewInfo
.getViewElement(TmfXmlUiStrings
.TIME_GRAPH_VIEW
);
243 if (viewElement
== null) {
246 ITimeGraphPresentationProvider2 pres
= this.getPresentationProvider();
247 if (pres
instanceof XmlPresentationProvider
) {
249 * TODO: Each entry of a line could have their own states/color.
250 * That will require an update to the presentation provider
252 ((XmlPresentationProvider
) pres
).loadNewStates(viewElement
);
255 String title
= fViewInfo
.getViewTitle(viewElement
);
257 title
= Messages
.XmlTimeGraphView_DefaultTitle
;
261 // Empty the additional state values
262 fStringValueMap
.clear();
264 Set
<String
> analysisIds
= fViewInfo
.getViewAnalysisIds(viewElement
);
266 List
<Element
> entries
= XmlUtils
.getChildElements(viewElement
, TmfXmlUiStrings
.ENTRY_ELEMENT
);
267 Set
<XmlEntry
> entryList
= new TreeSet
<>(getEntryComparator());
268 if (monitor
.isCanceled()) {
272 Set
<@NonNull ITmfAnalysisModuleWithStateSystems
> stateSystemModules
= new HashSet
<>();
273 if (analysisIds
.isEmpty()) {
275 * No analysis specified, take all state system analysis modules
277 Iterables
.addAll(stateSystemModules
, TmfTraceUtils
.getAnalysisModulesOfClass(trace
, ITmfAnalysisModuleWithStateSystems
.class));
279 for (String moduleId
: analysisIds
) {
280 moduleId
= checkNotNull(moduleId
);
281 ITmfAnalysisModuleWithStateSystems module
= TmfTraceUtils
.getAnalysisModuleOfClass(trace
, ITmfAnalysisModuleWithStateSystems
.class, moduleId
);
282 if (module
!= null) {
283 stateSystemModules
.add(module
);
288 for (ITmfAnalysisModuleWithStateSystems module
: stateSystemModules
) {
289 IStatus status
= module
.schedule();
290 if (!status
.isOK()) {
293 if (!module
.waitForInitialization()) {
296 for (ITmfStateSystem ssq
: module
.getStateSystems()) {
297 ssq
.waitUntilBuilt();
299 long startTime
= ssq
.getStartTime();
300 long endTime
= ssq
.getCurrentEndTime();
301 XmlEntry groupEntry
= new XmlEntry(-1, trace
, trace
.getName(), ssq
);
302 entryList
.add(groupEntry
);
303 setStartTime(Math
.min(getStartTime(), startTime
));
304 setEndTime(Math
.max(getEndTime(), endTime
));
306 /* Add children entry of this entry for each line */
307 for (Element entry
: entries
) {
308 buildEntry(entry
, groupEntry
, -1);
313 addToEntryList(parentTrace
, new ArrayList
<TimeGraphEntry
>(entryList
));
315 if (parentTrace
.equals(getTrace())) {
318 for (XmlEntry traceEntry
: entryList
) {
319 if (monitor
.isCanceled()) {
322 long startTime
= traceEntry
.getStateSystem().getStartTime();
323 long endTime
= traceEntry
.getStateSystem().getCurrentEndTime() + 1;
324 buildStatusEvent(traceEntry
, monitor
, startTime
, endTime
);
328 private void buildEntry(Element entryElement
, XmlEntry parentEntry
, int baseQuark
) {
329 /* Get the attribute string to display */
330 String path
= entryElement
.getAttribute(TmfXmlUiStrings
.PATH
);
331 if (path
.isEmpty()) {
332 path
= TmfXmlStrings
.WILDCARD
;
336 * Make sure the XML element has either a display attribute or entries,
337 * otherwise issue a warning
340 List
<Element
> displayElements
= XmlUtils
.getChildElements(entryElement
, TmfXmlUiStrings
.DISPLAY_ELEMENT
);
341 List
<Element
> entryElements
= XmlUtils
.getChildElements(entryElement
, TmfXmlUiStrings
.ENTRY_ELEMENT
);
343 if (displayElements
.isEmpty() && entryElements
.isEmpty()) {
344 Activator
.logWarning(String
.format("XML view: entry for %s should have either a display element or entry elements", path
)); //$NON-NLS-1$
348 ITmfStateSystem ss
= parentEntry
.getStateSystem();
350 /* Get the list of quarks to process with this path */
351 String
[] paths
= path
.split(SPLIT_STRING
);
353 List
<Integer
> quarks
= Collections
.singletonList(baseQuark
);
355 while (i
< paths
.length
) {
356 List
<Integer
> subQuarks
= new LinkedList
<>();
357 /* Replace * by .* to have a regex string */
358 String name
= paths
[i
].replaceAll("\\*", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
359 for (int relativeQuark
: quarks
) {
360 for (int quark
: ss
.getSubAttributes(relativeQuark
, false, name
)) {
361 subQuarks
.add(quark
);
368 /* Process each quark */
369 XmlEntry currentEntry
= parentEntry
;
370 Element displayElement
= null;
371 Map
<String
, XmlEntry
> entryMap
= new HashMap
<>();
372 if (!displayElements
.isEmpty()) {
373 displayElement
= displayElements
.get(0);
375 for (int quark
: quarks
) {
376 currentEntry
= parentEntry
;
377 /* Process the current entry, if specified */
378 if (displayElement
!= null) {
379 currentEntry
= processEntry(entryElement
, displayElement
, parentEntry
, quark
, ss
);
380 entryMap
.put(currentEntry
.getId(), currentEntry
);
382 /* Process the children entry of this entry */
383 for (Element subEntryEl
: entryElements
) {
384 buildEntry(subEntryEl
, currentEntry
, quark
);
387 if (!entryMap
.isEmpty()) {
388 buildTree(entryMap
, parentEntry
);
392 private XmlEntry
processEntry(@NonNull Element entryElement
, @NonNull Element displayEl
,
393 @NonNull XmlEntry parentEntry
, int quark
, ITmfStateSystem ss
) {
395 * Get the start time and end time of this entry from the display
398 ITmfXmlStateAttribute display
= fFactory
.createStateAttribute(displayEl
, parentEntry
);
399 int displayQuark
= display
.getAttributeQuark(quark
, null);
400 if (displayQuark
== IXmlStateSystemContainer
.ERROR_QUARK
) {
401 return new XmlEntry(quark
, parentEntry
.getTrace(),
402 String
.format("Unknown display quark for %s", ss
.getAttributeName(quark
)), ss
); //$NON-NLS-1$
405 long entryStart
= ss
.getStartTime();
406 long entryEnd
= ss
.getCurrentEndTime();
410 ITmfStateInterval oneInterval
= ss
.querySingleState(entryStart
, displayQuark
);
412 /* The entry start is the first non-null interval */
413 while (oneInterval
.getStateValue().isNull()) {
414 long ts
= oneInterval
.getEndTime() + 1;
415 if (ts
> ss
.getCurrentEndTime()) {
418 oneInterval
= ss
.querySingleState(ts
, displayQuark
);
420 entryStart
= oneInterval
.getStartTime();
422 /* The entry end is the last non-null interval */
423 oneInterval
= ss
.querySingleState(entryEnd
, displayQuark
);
424 while (oneInterval
.getStateValue().isNull()) {
425 long ts
= oneInterval
.getStartTime() - 1;
426 if (ts
< ss
.getStartTime()) {
429 oneInterval
= ss
.querySingleState(ts
, displayQuark
);
431 entryEnd
= oneInterval
.getEndTime();
433 } catch (StateSystemDisposedException e
) {
436 return new XmlEntry(quark
, displayQuark
, parentEntry
.getTrace(), ss
.getAttributeName(quark
),
437 entryStart
, entryEnd
, EntryDisplayType
.DISPLAY
, ss
, entryElement
);
440 private void buildStatusEvent(XmlEntry traceEntry
, @NonNull IProgressMonitor monitor
, long start
, long end
) {
441 long resolution
= (end
- start
) / getDisplayWidth();
442 long startTime
= Math
.max(start
, traceEntry
.getStartTime());
443 long endTime
= Math
.min(end
+ 1, traceEntry
.getEndTime());
444 List
<ITimeEvent
> eventList
= getEventList(traceEntry
, startTime
, endTime
, resolution
, monitor
);
445 if (monitor
.isCanceled()) {
448 traceEntry
.setEventList(eventList
);
451 for (ITimeGraphEntry entry
: traceEntry
.getChildren()) {
452 if (monitor
.isCanceled()) {
455 XmlEntry xmlEntry
= (XmlEntry
) entry
;
456 buildStatusEvent(xmlEntry
, monitor
, start
, end
);
460 /** Build a tree using getParentId() and getId() */
461 private static void buildTree(Map
<String
, XmlEntry
> entryMap
, XmlEntry rootEntry
) {
462 for (XmlEntry entry
: entryMap
.values()) {
464 if (!entry
.getParentId().isEmpty()) {
465 XmlEntry parent
= entryMap
.get(entry
.getParentId());
467 * Associate the parent entry only if their time overlap. A
468 * child entry may start before its parent, for example at the
469 * beginning of the trace if a parent has not yet appeared in
470 * the state system. We just want to make sure that the entry
471 * didn't start after the parent ended or ended before the
474 if (parent
!= null &&
475 !(entry
.getStartTime() > parent
.getEndTime() ||
476 entry
.getEndTime() < parent
.getStartTime())) {
477 parent
.addChild(entry
);
482 rootEntry
.addChild(entry
);
488 protected List
<ITimeEvent
> getEventList(TimeGraphEntry entry
, long startTime
, long endTime
, long resolution
, IProgressMonitor monitor
) {
489 if (!(entry
instanceof XmlEntry
)) {
490 return Collections
.EMPTY_LIST
;
492 XmlEntry xmlEntry
= (XmlEntry
) entry
;
493 ITmfStateSystem ssq
= xmlEntry
.getStateSystem();
494 final long realStart
= Math
.max(startTime
, entry
.getStartTime());
495 final long realEnd
= Math
.min(endTime
, entry
.getEndTime());
496 if (realEnd
<= realStart
) {
499 List
<ITimeEvent
> eventList
= null;
500 int quark
= xmlEntry
.getDisplayQuark();
503 if (xmlEntry
.getType() == EntryDisplayType
.DISPLAY
) {
505 List
<ITmfStateInterval
> statusIntervals
= StateSystemUtils
.queryHistoryRange(ssq
, quark
, realStart
, realEnd
- 1, resolution
, monitor
);
506 eventList
= new ArrayList
<>(statusIntervals
.size());
507 long lastEndTime
= -1;
508 for (ITmfStateInterval statusInterval
: statusIntervals
) {
509 if (monitor
.isCanceled()) {
512 int status
= getStatusFromInterval(statusInterval
);
513 long time
= statusInterval
.getStartTime();
514 long duration
= statusInterval
.getEndTime() - time
+ 1;
515 if (!statusInterval
.getStateValue().isNull()) {
516 if (lastEndTime
!= time
&& lastEndTime
!= -1) {
517 eventList
.add(new TimeEvent(entry
, lastEndTime
, time
- lastEndTime
));
519 eventList
.add(new TimeEvent(entry
, time
, duration
, status
));
520 } else if (lastEndTime
== -1 || time
+ duration
>= endTime
) {
521 // add null event if it intersects the start or end time
522 eventList
.add(new NullTimeEvent(entry
, time
, duration
));
524 lastEndTime
= time
+ duration
;
527 } catch (AttributeNotFoundException
| TimeRangeException
| StateValueTypeException
| StateSystemDisposedException e
) {
533 private int getStatusFromInterval(ITmfStateInterval statusInterval
) {
534 ITmfStateValue stateValue
= statusInterval
.getStateValue();
536 switch (stateValue
.getType()) {
539 status
= stateValue
.unboxInt();
542 status
= (int) stateValue
.unboxLong();
545 String statusStr
= stateValue
.unboxStr();
546 Integer statusInt
= fStringValueMap
.get(statusStr
);
547 if (statusInt
!= null) {
551 ITimeGraphPresentationProvider2 pres
= this.getPresentationProvider();
552 if (pres
instanceof XmlPresentationProvider
) {
553 // Add this new state to the presentation provider
554 status
= ((XmlPresentationProvider
) pres
).addState(statusStr
);
555 fStringValueMap
.put(statusStr
, status
);
559 status
= (int) stateValue
.unboxDouble();
570 protected List
<ILinkEvent
> getLinkList(long startTime
, long endTime
, long resolution
, IProgressMonitor monitor
) {
571 /* TODO: not implemented yet, need XML to go along */
572 return Collections
.EMPTY_LIST
;
576 protected @NonNull Iterable
<ITmfTrace
> getTracesToBuild(@Nullable ITmfTrace trace
) {
578 * Return the current trace only. Experiments will return their
579 * children's analyses
581 return (trace
!= null) ? Collections
.singleton(trace
) : Collections
.EMPTY_LIST
;