1 /*******************************************************************************
2 * Copyright (c) 2014, 2015 É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 * Geneviève Bastien - Initial implementation
11 * Patrick Tasse - Dispose wrapped trace
12 *******************************************************************************/
14 package org
.eclipse
.tracecompass
.tmf
.tests
.stubs
.trace
.xml
;
16 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
17 import static org
.junit
.Assert
.fail
;
20 import java
.io
.IOException
;
21 import java
.io
.InputStream
;
23 import java
.util
.Collection
;
24 import java
.util
.HashSet
;
25 import java
.util
.Optional
;
26 import java
.util
.stream
.StreamSupport
;
28 import javax
.xml
.XMLConstants
;
29 import javax
.xml
.transform
.Source
;
30 import javax
.xml
.transform
.stream
.StreamSource
;
31 import javax
.xml
.validation
.Schema
;
32 import javax
.xml
.validation
.SchemaFactory
;
33 import javax
.xml
.validation
.Validator
;
35 import org
.eclipse
.core
.resources
.IProject
;
36 import org
.eclipse
.core
.resources
.IResource
;
37 import org
.eclipse
.core
.runtime
.IPath
;
38 import org
.eclipse
.core
.runtime
.IStatus
;
39 import org
.eclipse
.core
.runtime
.Status
;
40 import org
.eclipse
.jdt
.annotation
.DefaultLocation
;
41 import org
.eclipse
.jdt
.annotation
.NonNull
;
42 import org
.eclipse
.jdt
.annotation
.NonNullByDefault
;
43 import org
.eclipse
.jdt
.annotation
.Nullable
;
44 import org
.eclipse
.osgi
.util
.NLS
;
45 import org
.eclipse
.tracecompass
.internal
.tmf
.core
.Activator
;
46 import org
.eclipse
.tracecompass
.tmf
.core
.analysis
.IAnalysisModule
;
47 import org
.eclipse
.tracecompass
.tmf
.core
.event
.ITmfEvent
;
48 import org
.eclipse
.tracecompass
.tmf
.core
.event
.ITmfEventField
;
49 import org
.eclipse
.tracecompass
.tmf
.core
.event
.ITmfEventType
;
50 import org
.eclipse
.tracecompass
.tmf
.core
.event
.TmfEvent
;
51 import org
.eclipse
.tracecompass
.tmf
.core
.event
.TmfEventField
;
52 import org
.eclipse
.tracecompass
.tmf
.core
.event
.TmfEventType
;
53 import org
.eclipse
.tracecompass
.tmf
.core
.event
.aspect
.ITmfEventAspect
;
54 import org
.eclipse
.tracecompass
.tmf
.core
.event
.aspect
.TmfBaseAspects
;
55 import org
.eclipse
.tracecompass
.tmf
.core
.event
.aspect
.TmfContentFieldAspect
;
56 import org
.eclipse
.tracecompass
.tmf
.core
.event
.aspect
.TmfCpuAspect
;
57 import org
.eclipse
.tracecompass
.tmf
.core
.exceptions
.TmfTraceException
;
58 import org
.eclipse
.tracecompass
.tmf
.core
.parsers
.custom
.CustomEventContent
;
59 import org
.eclipse
.tracecompass
.tmf
.core
.parsers
.custom
.CustomXmlEvent
;
60 import org
.eclipse
.tracecompass
.tmf
.core
.parsers
.custom
.CustomXmlTrace
;
61 import org
.eclipse
.tracecompass
.tmf
.core
.parsers
.custom
.CustomXmlTraceDefinition
;
62 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalManager
;
63 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.ITmfTimestamp
;
64 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimestamp
;
65 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.ITmfContext
;
66 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfContext
;
67 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfTrace
;
68 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.indexer
.ITmfTraceIndexer
;
69 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.indexer
.checkpoint
.TmfCheckpointIndexer
;
70 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.location
.ITmfLocation
;
71 import org
.xml
.sax
.SAXException
;
73 import com
.google
.common
.collect
.ImmutableList
;
74 import com
.google
.common
.collect
.Iterables
;
77 * An XML development trace using a custom XML trace definition and schema.
79 * This class will typically be used to build custom traces to unit test more
80 * complex functionalities like analyzes or to develop and test data-driven
83 * This class wraps a custom XML trace and rewrites the returned events in the
84 * getNext() method so that event's fields are the ones defined in <field ... />
85 * elements instead of those defined in the custom XML parser. This way, each
86 * event can have a different set of fields. This class can, for example, mimic
89 * @author Geneviève Bastien
91 public class TmfXmlTraceStub
extends TmfTrace
{
93 private static final String DEVELOPMENT_TRACE_PARSER_PATH
= "TmfXmlDevelopmentTrace.xml"; //$NON-NLS-1$
94 private static final String DEVELOPMENT_TRACE_XSD
= "TmfXmlDevelopmentTrace.xsd"; //$NON-NLS-1$
95 private static final String EMPTY
= ""; //$NON-NLS-1$
97 /* XML elements and attributes names */
98 private static final String EVENT_NAME_FIELD
= "Message"; //$NON-NLS-1$
99 private static final String FIELD_NAMES_FIELD
= "fields"; //$NON-NLS-1$
100 private static final String VALUES_FIELD
= "values"; //$NON-NLS-1$
101 private static final String TYPES_FIELD
= "type"; //$NON-NLS-1$
102 private static final String VALUES_SEPARATOR
= " \\| "; //$NON-NLS-1$
103 private static final String TYPE_INTEGER
= "int"; //$NON-NLS-1$
104 private static final String TYPE_LONG
= "long"; //$NON-NLS-1$
105 private static final String ASPECT_CPU
= "cpu";
107 private static final Long SECONDS_TO_NS
= 1000000000L;
109 private final CustomXmlTraceDefinition fDefinition
;
110 private CustomXmlTrace fTrace
;
112 private Collection
<ITmfEventAspect
<?
>> fAspects
= TmfTrace
.BASE_ASPECTS
;
113 private final Collection
<ITmfEventAspect
<?
>> fAdditionalAspects
= new HashSet
<>();
114 private final Collection
<IAnalysisModule
> fAdditionalModules
= new HashSet
<>();
117 * Validate and initialize a {@link TmfXmlTraceStub} object
119 * @param absolutePath
120 * The absolute file path of the trace file
123 public static TmfXmlTraceStub
setupTrace(IPath absolutePath
) {
124 TmfXmlTraceStub trace
= new TmfXmlTraceStub();
125 IStatus status
= trace
.validate(null, absolutePath
.toOSString());
126 if (!status
.isOK()) {
127 fail(status
.getException().getMessage());
130 trace
.initTrace(null, absolutePath
.toOSString(), TmfEvent
.class);
131 } catch (TmfTraceException e
) {
132 fail(e
.getMessage());
138 * Constructor. Constructs the custom XML trace with the appropriate
141 public TmfXmlTraceStub() {
143 /* Load custom XML definition */
144 try (InputStream in
= TmfXmlTraceStub
.class.getResourceAsStream(DEVELOPMENT_TRACE_PARSER_PATH
);) {
145 CustomXmlTraceDefinition
[] definitions
= CustomXmlTraceDefinition
.loadAll(in
);
146 if (definitions
.length
< 2) {
147 throw new IllegalStateException("The custom trace definition does not exist"); //$NON-NLS-1$
149 /* The first definition parses the 'set_aspects' event */
150 fTrace
= new CustomXmlTrace(definitions
[0]) {
152 protected ITmfTraceIndexer
createIndexer(int interval
) {
153 /* Use the in-memory checkpoint indexer */
154 return new TmfCheckpointIndexer(this, interval
);
157 /* The second definition parses 'event' trace events */
158 fDefinition
= checkNotNull(definitions
[1]);
159 } catch (IOException e
) {
160 throw new IllegalStateException("Cannot open the trace parser for development traces"); //$NON-NLS-1$
166 @NonNullByDefault({DefaultLocation
.TYPE_ARGUMENT
})
167 public void initTrace(@Nullable IResource resource
, @Nullable String path
, @Nullable Class
<?
extends ITmfEvent
> type
) throws TmfTraceException
{
168 super.initTrace(resource
, path
, type
);
171 /* Initialize and read the trace with the 'set_aspects' definition */
172 TmfSignalManager
.deregister(fTrace
);
173 fTrace
.initTrace(resource
, path
, type
);
175 /* If a set_aspects event exists, getNext() will process it */
180 /* Initialize a new trace with the trace events definition */
181 fTrace
= new CustomXmlTrace(fDefinition
);
182 TmfSignalManager
.deregister(fTrace
);
183 fTrace
.initTrace(resource
, path
, type
);
184 /* Set the start and (current) end times for this trace */
189 ITmfEvent event
= getNext(ctx
);
191 final ITmfTimestamp curTime
= event
.getTimestamp();
192 this.setStartTime(curTime
);
193 this.setEndTime(curTime
);
199 public synchronized void dispose() {
205 public @Nullable ITmfEvent
parseEvent(@Nullable ITmfContext context
) {
206 return fTrace
.parseEvent(context
);
210 public @Nullable ITmfLocation
getCurrentLocation() {
211 return fTrace
.getCurrentLocation();
215 public double getLocationRatio(@Nullable ITmfLocation location
) {
216 return fTrace
.getLocationRatio(location
);
220 public @Nullable ITmfContext
seekEvent(@Nullable ITmfLocation location
) {
221 return fTrace
.seekEvent(location
);
225 public @Nullable ITmfContext
seekEvent(double ratio
) {
226 return fTrace
.seekEvent(ratio
);
230 public IStatus
validate(@Nullable IProject project
, @Nullable String path
) {
231 File xmlFile
= new File(path
);
232 if (!xmlFile
.exists() || !xmlFile
.isFile() || !xmlFile
.canRead()) {
233 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, NLS
.bind(org
.eclipse
.tracecompass
.tmf
.tests
.stubs
.trace
.xml
.Messages
.TmfDevelopmentTrace_FileNotFound
, path
));
235 /* Does the XML file validate with the XSD */
236 SchemaFactory schemaFactory
= SchemaFactory
.newInstance(XMLConstants
.W3C_XML_SCHEMA_NS_URI
);
237 Source xmlSource
= new StreamSource(xmlFile
);
240 URL url
= TmfXmlTraceStub
.class.getResource(DEVELOPMENT_TRACE_XSD
);
241 Schema schema
= schemaFactory
.newSchema(url
);
243 Validator validator
= schema
.newValidator();
244 validator
.validate(xmlSource
);
245 } catch (SAXException e
) {
246 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, NLS
.bind(org
.eclipse
.tracecompass
.tmf
.tests
.stubs
.trace
.xml
.Messages
.TmfDevelopmentTrace_ValidationError
, path
), e
);
247 } catch (IOException e
) {
248 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, NLS
.bind(org
.eclipse
.tracecompass
.tmf
.tests
.stubs
.trace
.xml
.Messages
.TmfDevelopmentTrace_IoError
, path
), e
);
250 return Status
.OK_STATUS
;
253 private static String
getStringValue(ITmfEventField content
, String fieldName
) {
254 ITmfEventField field
= content
.getField(fieldName
);
258 Object val
= field
.getValue();
259 if (!(val
instanceof String
)) {
266 public synchronized @Nullable ITmfEvent
getNext(@Nullable ITmfContext context
) {
267 if (context
== null) {
270 final ITmfContext savedContext
= new TmfContext(context
.getLocation(), context
.getRank());
271 CustomXmlEvent event
= fTrace
.getNext(context
);
276 /* Translate the content of the event */
277 /* The "fields" field contains a | separated list of field names */
278 /* The "values" field contains a | separated list of field values */
279 /* the "type" field contains a | separated list of field types */
280 ITmfEventField content
= event
.getContent();
281 if (content
== null) {
285 String fieldString
= getStringValue(content
, FIELD_NAMES_FIELD
);
286 String valueString
= getStringValue(content
, VALUES_FIELD
);
287 String typeString
= getStringValue(content
, TYPES_FIELD
);
289 String
[] fields
= fieldString
.split(VALUES_SEPARATOR
);
290 String
[] values
= valueString
.split(VALUES_SEPARATOR
);
291 String
[] types
= typeString
.split(VALUES_SEPARATOR
);
292 ITmfEventField
[] fieldsArray
= new TmfEventField
[fields
.length
];
294 for (int i
= 0; i
< fields
.length
; i
++) {
295 String value
= EMPTY
;
296 if (values
.length
> i
) {
300 if (types
.length
> i
) {
308 val
= Integer
.valueOf(value
);
309 } catch (NumberFormatException e
) {
310 Activator
.logError(String
.format("Get next XML event: cannot cast value %s to integer", value
), e
); //$NON-NLS-1$
317 val
= Long
.valueOf(value
);
318 } catch (NumberFormatException e
) {
319 Activator
.logError(String
.format("Get next XML event: cannot cast value %s to long", value
), e
); //$NON-NLS-1$
328 fieldsArray
[i
] = new TmfEventField(checkNotNull(fields
[i
]), val
, null);
332 * Generate the aspects for this trace if it is the 'set_aspects'
335 if (fTrace
.getDefinition() != fDefinition
) {
336 generateAspects(fieldsArray
);
340 /* Create a new event with new fields and name */
341 ITmfEventType customEventType
= event
.getType();
342 String eventName
= getStringValue(content
, EVENT_NAME_FIELD
);
343 TmfEventType eventType
= new TmfEventType(eventName
, customEventType
.getRootField());
344 ITmfEventField eventFields
= new CustomEventContent(content
.getName(), content
.getValue(), fieldsArray
);
346 * TODO: Timestamps for these traces are in nanos, but since the
347 * CustomXmlTrace does not support this format, the timestamp of the
348 * original is in second and we need to convert it. We should do that at
349 * the source when it is supported
351 ITmfTimestamp timestamp
= TmfTimestamp
.fromNanos(event
.getTimestamp().getValue() / SECONDS_TO_NS
);
352 TmfEvent newEvent
= new TmfEvent(this, ITmfContext
.UNKNOWN_RANK
, timestamp
, eventType
, eventFields
);
353 updateAttributes(savedContext
, event
);
357 private void generateAspects(ITmfEventField
[] fieldsArray
) {
358 ImmutableList
.Builder
<ITmfEventAspect
<?
>> builder
= new ImmutableList
.Builder
<>();
360 /* Initialize the first default trace aspects */
361 builder
.add(TmfBaseAspects
.getTimestampAspect());
362 builder
.add(TmfBaseAspects
.getEventTypeAspect());
364 /* Add custom aspects in between */
365 for (ITmfEventField field
: fieldsArray
) {
366 String name
= field
.getName();
367 final ITmfEventAspect
<?
> aspect
= new TmfContentFieldAspect(name
, name
);
368 if (name
.equals(ASPECT_CPU
)) {
369 builder
.add(new TmfCpuAspect() {
371 public @Nullable Integer
resolve(ITmfEvent event
) {
372 Object result
= aspect
.resolve(event
);
373 if (result
instanceof Number
) {
374 return ((Number
) result
).intValue();
384 /* Add the big content aspect */
385 builder
.add(TmfBaseAspects
.getContentsAspect());
386 /* Add the additional aspects */
387 builder
.addAll(fAdditionalAspects
);
388 fAspects
= builder
.build();
392 public Iterable
<ITmfEventAspect
<?
>> getEventAspects() {
397 * Adds a new event aspect to this type of trace. Since this trace type is
398 * used to build custom traces that mimic the behavior of real traces, the
399 * required aspects may be missing and this method allows to add them. This
400 * method should be called before calling
401 * {@link #initTrace(IResource, String, Class)} otherwise the additional
402 * aspects will not be picked up when generating the aspects.
407 public void addEventAspect(ITmfEventAspect
<?
> aspect
) {
408 fAdditionalAspects
.add(aspect
);
412 * Add an additional new module
417 public void addAnalysisModule(IAnalysisModule module
) {
418 fAdditionalModules
.add(module
);
422 public Iterable
<@NonNull IAnalysisModule
> getAnalysisModules() {
423 @NonNull Iterable
<IAnalysisModule
> modules
= super.getAnalysisModules();
424 return checkNotNull(Iterables
.concat(modules
, fAdditionalModules
));
428 public @Nullable IAnalysisModule
getAnalysisModule(@Nullable String analysisId
) {
429 Iterable
<@NonNull IAnalysisModule
> modules
= getAnalysisModules();
430 Optional
<IAnalysisModule
> opt
= StreamSupport
.stream(modules
.spliterator(), false).filter(analysis
-> analysis
.getId().equals(analysisId
)).findFirst();
431 return opt
.isPresent() ? opt
.get() : null;