1 /*******************************************************************************
2 * Copyright (c) 2014 É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
.linuxtools
.tmf
.analysis
.xml
.ui
.views
.timegraph
;
16 import java
.util
.ArrayList
;
17 import java
.util
.Collections
;
18 import java
.util
.Comparator
;
19 import java
.util
.HashMap
;
20 import java
.util
.LinkedList
;
21 import java
.util
.List
;
24 import java
.util
.TreeSet
;
26 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
27 import org
.eclipse
.jdt
.annotation
.NonNull
;
28 import org
.eclipse
.jface
.dialogs
.IDialogSettings
;
29 import org
.eclipse
.jface
.util
.IPropertyChangeListener
;
30 import org
.eclipse
.jface
.util
.PropertyChangeEvent
;
31 import org
.eclipse
.linuxtools
.internal
.tmf
.analysis
.xml
.ui
.Activator
;
32 import org
.eclipse
.linuxtools
.internal
.tmf
.analysis
.xml
.ui
.TmfXmlUiStrings
;
33 import org
.eclipse
.linuxtools
.statesystem
.core
.ITmfStateSystem
;
34 import org
.eclipse
.linuxtools
.statesystem
.core
.exceptions
.AttributeNotFoundException
;
35 import org
.eclipse
.linuxtools
.statesystem
.core
.exceptions
.StateSystemDisposedException
;
36 import org
.eclipse
.linuxtools
.statesystem
.core
.exceptions
.StateValueTypeException
;
37 import org
.eclipse
.linuxtools
.statesystem
.core
.exceptions
.TimeRangeException
;
38 import org
.eclipse
.linuxtools
.statesystem
.core
.interval
.ITmfStateInterval
;
39 import org
.eclipse
.linuxtools
.tmf
.analysis
.xml
.core
.model
.ITmfXmlModelFactory
;
40 import org
.eclipse
.linuxtools
.tmf
.analysis
.xml
.core
.model
.ITmfXmlStateAttribute
;
41 import org
.eclipse
.linuxtools
.tmf
.analysis
.xml
.core
.model
.readonly
.TmfXmlReadOnlyModelFactory
;
42 import org
.eclipse
.linuxtools
.tmf
.analysis
.xml
.core
.module
.IXmlStateSystemContainer
;
43 import org
.eclipse
.linuxtools
.tmf
.analysis
.xml
.core
.module
.XmlUtils
;
44 import org
.eclipse
.linuxtools
.tmf
.analysis
.xml
.core
.stateprovider
.TmfXmlStrings
;
45 import org
.eclipse
.linuxtools
.tmf
.analysis
.xml
.ui
.module
.TmfXmlAnalysisOutputSource
;
46 import org
.eclipse
.linuxtools
.tmf
.analysis
.xml
.ui
.views
.timegraph
.XmlEntry
.EntryDisplayType
;
47 import org
.eclipse
.linuxtools
.tmf
.core
.statesystem
.ITmfAnalysisModuleWithStateSystems
;
48 import org
.eclipse
.linuxtools
.tmf
.core
.statesystem
.TmfStateSystemAnalysisModule
;
49 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.ITmfTrace
;
50 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.TmfTraceManager
;
51 import org
.eclipse
.linuxtools
.tmf
.ui
.views
.timegraph
.AbstractTimeGraphView
;
52 import org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
.ITimeGraphPresentationProvider2
;
53 import org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
.model
.ILinkEvent
;
54 import org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
.model
.ITimeEvent
;
55 import org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
.model
.ITimeGraphEntry
;
56 import org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
.model
.NullTimeEvent
;
57 import org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
.model
.TimeEvent
;
58 import org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
.model
.TimeGraphEntry
;
59 import org
.eclipse
.swt
.widgets
.Display
;
60 import org
.w3c
.dom
.Element
;
63 * This view displays state system data in a time graph view. It uses an XML
64 * {@link TmfXmlUiStrings#TIME_GRAPH_VIEW} element from an XML file. This
65 * element defines which entries from the state system will be shown and also
66 * gives additional information on the presentation of the view (states, colors,
69 * @author Florian Wininger
71 public class XmlTimeGraphView
extends AbstractTimeGraphView
{
74 public static final String ID
= "org.eclipse.linuxtools.tmf.analysis.xml.ui.views.timegraph"; //$NON-NLS-1$
76 private static final String XML_VIEW_ID_PROPERTY
= "XmlViewId"; //$NON-NLS-1$
77 private static final String XML_VIEW_FILE_PROPERTY
= "XmlViewFile"; //$NON-NLS-1$
79 private static final String
[] DEFAULT_COLUMN_NAMES
= new String
[] {
80 Messages
.XmlTimeGraphView_ColumnName
,
81 Messages
.XmlTimeGraphView_ColumnId
,
82 Messages
.XmlTimeGraphView_ColumnParentId
,
85 private static final String
[] DEFAULT_FILTER_COLUMN_NAMES
= new String
[] {
86 Messages
.XmlTimeGraphView_ColumnName
,
87 Messages
.XmlTimeGraphView_ColumnId
90 /** The relative weight of the sash */
91 private static final int[] fWeight
= { 1, 2 };
93 private static final String EMPTY_STRING
= ""; //$NON-NLS-1$
94 private static final String SPLIT_STRING
= "/"; //$NON-NLS-1$
96 private String fId
= null;
97 private String fFilePath
= null;
98 private final ITmfXmlModelFactory fFactory
;
100 // ------------------------------------------------------------------------
102 // ------------------------------------------------------------------------
105 * Default constructor
107 public XmlTimeGraphView() {
108 super(ID
, new XmlPresentationProvider());
110 setTreeColumns(DEFAULT_COLUMN_NAMES
);
111 setTreeLabelProvider(new XmlTreeLabelProvider());
112 setFilterColumns(DEFAULT_FILTER_COLUMN_NAMES
);
113 setFilterLabelProvider(new XmlTreeLabelProvider());
114 setEntryComparator(new XmlEntryComparator());
115 this.addPartPropertyListener(new IPropertyChangeListener() {
117 public void propertyChange(PropertyChangeEvent event
) {
118 if (event
.getProperty().equals(TmfXmlUiStrings
.XML_OUTPUT_DATA
)) {
119 String data
= (String
) event
.getNewValue();
120 String
[] idFile
= data
.split(TmfXmlAnalysisOutputSource
.DATA_SEPARATOR
);
121 fId
= (idFile
.length
> 0) ? idFile
[0] : null;
122 fFilePath
= (idFile
.length
> 1) ? idFile
[1] : null;
124 savePersistentData();
128 IDialogSettings settings
= getPersistentPropertyStore();
130 fId
= settings
.get(XML_VIEW_ID_PROPERTY
);
131 fFilePath
= settings
.get(XML_VIEW_FILE_PROPERTY
);
132 fFactory
= TmfXmlReadOnlyModelFactory
.getInstance();
136 private IDialogSettings
getPersistentPropertyStore() {
137 IDialogSettings settings
= Activator
.getDefault().getDialogSettings();
138 IDialogSettings section
= settings
.getSection(getClass().getName());
139 if (section
== null) {
140 section
= settings
.addNewSection(getClass().getName());
141 if (section
== null) {
142 throw new IllegalStateException();
148 private void savePersistentData() {
149 IDialogSettings settings
= getPersistentPropertyStore();
151 settings
.put(XML_VIEW_ID_PROPERTY
, fId
);
152 settings
.put(XML_VIEW_FILE_PROPERTY
, fFilePath
);
155 private void loadNewXmlView() {
159 private void setViewTitle(final String title
) {
160 Display
.getDefault().asyncExec(new Runnable() {
170 protected String
getNextText() {
171 return Messages
.XmlTimeGraphView_NextText
;
175 protected String
getNextTooltip() {
176 return Messages
.XmlTimeGraphView_NextTooltip
;
180 protected String
getPrevText() {
181 return Messages
.XmlTimeGraphView_PreviousText
;
185 protected String
getPrevTooltip() {
186 return Messages
.XmlTimeGraphView_PreviousInterval
;
190 * Default label provider, it shows name, id and parent columns
192 * TODO: There should be a way to define columns in the XML
194 private static class XmlTreeLabelProvider
extends TreeLabelProvider
{
197 public String
getColumnText(Object element
, int columnIndex
) {
198 XmlEntry entry
= (XmlEntry
) element
;
200 if (DEFAULT_COLUMN_NAMES
[columnIndex
].equals(Messages
.XmlTimeGraphView_ColumnName
)) {
201 return entry
.getName();
202 } else if (DEFAULT_COLUMN_NAMES
[columnIndex
].equals(Messages
.XmlTimeGraphView_ColumnId
)) {
203 return entry
.getId();
204 } else if (DEFAULT_COLUMN_NAMES
[columnIndex
].equals(Messages
.XmlTimeGraphView_ColumnParentId
)) {
205 return entry
.getParentId();
212 private static class XmlEntryComparator
implements Comparator
<ITimeGraphEntry
> {
215 public int compare(ITimeGraphEntry o1
, ITimeGraphEntry o2
) {
219 if ((o1
instanceof XmlEntry
) && (o2
instanceof XmlEntry
)) {
220 XmlEntry entry1
= (XmlEntry
) o1
;
221 XmlEntry entry2
= (XmlEntry
) o2
;
222 result
= entry1
.getTrace().getStartTime().compareTo(entry2
.getTrace().getStartTime());
224 result
= entry1
.getTrace().getName().compareTo(entry2
.getTrace().getName());
227 result
= entry1
.getName().compareTo(entry2
.getName());
232 result
= o1
.getStartTime() < o2
.getStartTime() ?
-1 : o1
.getStartTime() > o2
.getStartTime() ?
1 : 0;
239 // ------------------------------------------------------------------------
241 // ------------------------------------------------------------------------
244 protected void buildEventList(ITmfTrace trace
, ITmfTrace parentTrace
, IProgressMonitor monitor
) {
247 * Get the view element from the XML file. If the element can't be
254 Element viewElement
= XmlUtils
.getElementInFile(fFilePath
, TmfXmlUiStrings
.TIME_GRAPH_VIEW
, id
);
255 if (viewElement
== null) {
258 ITimeGraphPresentationProvider2 pres
= this.getPresentationProvider();
259 if (pres
instanceof XmlPresentationProvider
) {
261 * TODO: Each entry of a line could have their own states/color.
262 * That will require an update to the presentation provider
264 ((XmlPresentationProvider
) pres
).loadNewStates(viewElement
);
267 List
<Element
> heads
= XmlUtils
.getChildElements(viewElement
, TmfXmlStrings
.HEAD
);
269 List
<String
> analysisIds
= new LinkedList
<>();
270 if (!heads
.isEmpty()) {
271 Element head
= heads
.get(0);
272 /* Set the title of this view from the label in the header */
273 List
<Element
> labels
= XmlUtils
.getChildElements(head
, TmfXmlStrings
.LABEL
);
274 String title
= Messages
.XmlTimeGraphView_DefaultTitle
;
275 for (Element label
: labels
) {
276 if (!label
.getAttribute(TmfXmlStrings
.VALUE
).equals(EMPTY_STRING
)) {
277 title
= label
.getAttribute(TmfXmlStrings
.VALUE
);
283 /* Get the application analysis from the view's XML header */
284 List
<Element
> applicableAnalysis
= XmlUtils
.getChildElements(head
, TmfXmlStrings
.ANALYSIS
);
285 for (Element oneAnalysis
: applicableAnalysis
) {
286 analysisIds
.add(oneAnalysis
.getAttribute(TmfXmlStrings
.ID
));
289 List
<Element
> entries
= XmlUtils
.getChildElements(viewElement
, TmfXmlUiStrings
.ENTRY_ELEMENT
);
290 Set
<XmlEntry
> entryList
= new TreeSet
<>(getEntryComparator());
291 for (ITmfTrace aTrace
: TmfTraceManager
.getTraceSet(trace
)) {
292 if (monitor
.isCanceled()) {
296 List
<ITmfAnalysisModuleWithStateSystems
> stateSystemModules
= new LinkedList
<>();
297 if (analysisIds
.isEmpty()) {
299 * No analysis specified, take all state system analysis modules
301 for (ITmfAnalysisModuleWithStateSystems module
: aTrace
.getAnalysisModulesOfClass(ITmfAnalysisModuleWithStateSystems
.class)) {
302 stateSystemModules
.add(module
);
305 for (String moduleId
: analysisIds
) {
306 ITmfAnalysisModuleWithStateSystems module
= aTrace
.getAnalysisModuleOfClass(ITmfAnalysisModuleWithStateSystems
.class, moduleId
);
307 if (module
!= null) {
308 stateSystemModules
.add(module
);
313 for (ITmfAnalysisModuleWithStateSystems module
: stateSystemModules
) {
315 if (module
instanceof TmfStateSystemAnalysisModule
) {
316 ((TmfStateSystemAnalysisModule
) module
).waitForInitialization();
318 for (ITmfStateSystem ssq
: module
.getStateSystems()) {
322 ssq
.waitUntilBuilt();
324 long startTime
= ssq
.getStartTime();
325 long endTime
= ssq
.getCurrentEndTime();
326 XmlEntry groupEntry
= new XmlEntry(-1, aTrace
, aTrace
.getName(), ssq
);
327 entryList
.add(groupEntry
);
328 setStartTime(Math
.min(getStartTime(), startTime
));
329 setEndTime(Math
.max(getEndTime(), endTime
));
331 /* Add children entry of this entry for each line */
332 for (Element entry
: entries
) {
333 buildEntry(entry
, groupEntry
, -1);
338 putEntryList(trace
, new ArrayList
<TimeGraphEntry
>(entryList
));
340 if (trace
.equals(getTrace())) {
343 for (XmlEntry traceEntry
: entryList
) {
344 if (monitor
.isCanceled()) {
347 long startTime
= traceEntry
.getStateSystem().getStartTime();
348 long endTime
= traceEntry
.getStateSystem().getCurrentEndTime() + 1;
349 buildStatusEvent(traceEntry
, monitor
, startTime
, endTime
);
353 private void buildEntry(Element entryElement
, XmlEntry parentEntry
, int baseQuark
) {
354 /* Get the attribute string to display */
355 String path
= entryElement
.getAttribute(TmfXmlUiStrings
.PATH
);
356 if (path
.isEmpty()) {
357 path
= TmfXmlStrings
.WILDCARD
;
361 * Make sure the XML element has either a display attribute or entries,
362 * otherwise issue a warning
365 List
<Element
> displayElements
= XmlUtils
.getChildElements(entryElement
, TmfXmlUiStrings
.DISPLAY_ELEMENT
);
366 List
<Element
> entryElements
= XmlUtils
.getChildElements(entryElement
, TmfXmlUiStrings
.ENTRY_ELEMENT
);
368 if (displayElements
.isEmpty() && entryElements
.isEmpty()) {
369 Activator
.logWarning(String
.format("XML view: entry for %s should have either a display element or entry elements", path
)); //$NON-NLS-1$
373 ITmfStateSystem ss
= parentEntry
.getStateSystem();
375 /* Get the list of quarks to process with this path */
376 String
[] paths
= path
.split(SPLIT_STRING
);
378 List
<Integer
> quarks
= Collections
.singletonList(baseQuark
);
381 while (i
< paths
.length
) {
382 List
<Integer
> subQuarks
= new LinkedList
<>();
383 /* Replace * by .* to have a regex string */
384 String name
= paths
[i
].replaceAll("\\*", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
385 for (int relativeQuark
: quarks
) {
386 for (int quark
: ss
.getSubAttributes(relativeQuark
, false, name
)) {
387 subQuarks
.add(quark
);
394 /* Process each quark */
395 XmlEntry currentEntry
= parentEntry
;
396 Element displayElement
= null;
397 Map
<String
, XmlEntry
> entryMap
= new HashMap
<>();
398 if (!displayElements
.isEmpty()) {
399 displayElement
= displayElements
.get(0);
401 for (int quark
: quarks
) {
402 currentEntry
= parentEntry
;
403 /* Process the current entry, if specified */
404 if (displayElement
!= null) {
405 currentEntry
= processEntry(entryElement
, displayElement
, parentEntry
, quark
, ss
);
406 entryMap
.put(currentEntry
.getId(), currentEntry
);
408 /* Process the children entry of this entry */
409 for (Element subEntryEl
: entryElements
) {
410 buildEntry(subEntryEl
, currentEntry
, quark
);
413 if (!entryMap
.isEmpty()) {
414 buildTree(entryMap
, parentEntry
);
416 } catch (AttributeNotFoundException e
) {
420 private XmlEntry
processEntry(@NonNull Element entryElement
, @NonNull Element displayEl
,
421 XmlEntry parentEntry
, int quark
, ITmfStateSystem ss
) {
423 * Get the start time and end time of this entry from the display
426 ITmfXmlStateAttribute display
= fFactory
.createStateAttribute(displayEl
, parentEntry
);
427 int displayQuark
= display
.getAttributeQuark(quark
);
428 if (displayQuark
== IXmlStateSystemContainer
.ERROR_QUARK
) {
429 return new XmlEntry(quark
, parentEntry
.getTrace(),
430 String
.format("Unknown display quark for %s", ss
.getAttributeName(quark
)), ss
); //$NON-NLS-1$
433 long entryStart
= ss
.getStartTime();
434 long entryEnd
= ss
.getCurrentEndTime();
436 boolean first
= true;
437 List
<ITmfStateInterval
> execNameIntervals
= ss
.queryHistoryRange(displayQuark
, ss
.getStartTime(), ss
.getCurrentEndTime());
439 for (ITmfStateInterval execNameInterval
: execNameIntervals
) {
441 if (!execNameInterval
.getStateValue().isNull()) {
443 entryStart
= execNameInterval
.getStartTime();
446 entryEnd
= execNameInterval
.getEndTime();
449 } catch (AttributeNotFoundException
| StateSystemDisposedException e
) {
452 return new XmlEntry(quark
, displayQuark
, parentEntry
.getTrace(), ss
.getAttributeName(quark
),
453 entryStart
, entryEnd
, EntryDisplayType
.DISPLAY
, ss
, entryElement
);
456 private void buildStatusEvent(XmlEntry traceEntry
, IProgressMonitor monitor
, long start
, long end
) {
457 long resolution
= (end
- start
) / getDisplayWidth();
458 long startTime
= Math
.max(start
, traceEntry
.getStartTime());
459 long endTime
= Math
.min(end
+ 1, traceEntry
.getEndTime());
460 List
<ITimeEvent
> eventList
= getEventList(traceEntry
, startTime
, endTime
, resolution
, monitor
);
461 if (monitor
.isCanceled()) {
464 traceEntry
.setEventList(eventList
);
467 for (TimeGraphEntry entry
: traceEntry
.getChildren()) {
468 if (monitor
.isCanceled()) {
471 XmlEntry xmlEntry
= (XmlEntry
) entry
;
472 buildStatusEvent(xmlEntry
, monitor
, start
, end
);
476 /** Build a tree using getParentId() and getId() */
477 private static void buildTree(Map
<String
, XmlEntry
> entryMap
, XmlEntry rootEntry
) {
478 for (XmlEntry entry
: entryMap
.values()) {
480 if (!entry
.getParentId().isEmpty()) {
481 XmlEntry parent
= entryMap
.get(entry
.getParentId());
482 if (parent
!= null &&
483 entry
.getStartTime() >= parent
.getStartTime() &&
484 entry
.getStartTime() <= parent
.getEndTime()) {
485 parent
.addChild(entry
);
490 rootEntry
.addChild(entry
);
496 protected List
<ITimeEvent
> getEventList(TimeGraphEntry entry
, long startTime
, long endTime
, long resolution
, IProgressMonitor monitor
) {
497 if (!(entry
instanceof XmlEntry
)) {
498 return Collections
.EMPTY_LIST
;
500 XmlEntry xmlEntry
= (XmlEntry
) entry
;
501 ITmfStateSystem ssq
= xmlEntry
.getStateSystem();
502 final long realStart
= Math
.max(startTime
, entry
.getStartTime());
503 final long realEnd
= Math
.min(endTime
, entry
.getEndTime());
504 if (realEnd
<= realStart
) {
507 List
<ITimeEvent
> eventList
= null;
508 int quark
= xmlEntry
.getDisplayQuark();
511 if (xmlEntry
.getType() == EntryDisplayType
.DISPLAY
) {
513 List
<ITmfStateInterval
> statusIntervals
= ssq
.queryHistoryRange(quark
, realStart
, realEnd
- 1, resolution
, monitor
);
514 eventList
= new ArrayList
<>(statusIntervals
.size());
515 long lastEndTime
= -1;
516 for (ITmfStateInterval statusInterval
: statusIntervals
) {
517 if (monitor
.isCanceled()) {
520 int status
= statusInterval
.getStateValue().unboxInt();
521 long time
= statusInterval
.getStartTime();
522 long duration
= statusInterval
.getEndTime() - time
+ 1;
523 if (!statusInterval
.getStateValue().isNull()) {
524 if (lastEndTime
!= time
&& lastEndTime
!= -1) {
525 eventList
.add(new TimeEvent(entry
, lastEndTime
, time
- lastEndTime
));
527 eventList
.add(new TimeEvent(entry
, time
, duration
, status
));
528 } else if (lastEndTime
== -1 || time
+ duration
>= endTime
) {
529 // add null event if it intersects the start or end time
530 eventList
.add(new NullTimeEvent(entry
, time
, duration
));
532 lastEndTime
= time
+ duration
;
535 } catch (AttributeNotFoundException
| TimeRangeException
| StateValueTypeException
| StateSystemDisposedException e
) {
542 protected List
<ILinkEvent
> getLinkList(long startTime
, long endTime
, long resolution
, IProgressMonitor monitor
) {
543 /* TODO: not implemented yet, need XML to go along */
544 return Collections
.EMPTY_LIST
;