1 /*******************************************************************************
2 * Copyright (c) 2011, 2014 Ericsson, Ecole Polytechnique de Montreal and others
4 * All rights reserved. This program and the accompanying materials are made
5 * 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 * Matthew Khouzam - Initial API and implementation
11 * Alexandre Montplaisir - Initial API and implementation
12 * Simon Delisle - Replace LinkedList by TreeSet in callsitesByName attribute
13 *******************************************************************************/
15 package org
.eclipse
.tracecompass
.ctf
.core
.trace
;
18 import java
.io
.FileFilter
;
19 import java
.io
.IOException
;
20 import java
.io
.Serializable
;
21 import java
.nio
.ByteBuffer
;
22 import java
.nio
.ByteOrder
;
23 import java
.nio
.channels
.FileChannel
;
24 import java
.nio
.channels
.FileChannel
.MapMode
;
25 import java
.nio
.file
.StandardOpenOption
;
26 import java
.util
.Arrays
;
27 import java
.util
.Collection
;
28 import java
.util
.Collections
;
29 import java
.util
.Comparator
;
30 import java
.util
.HashMap
;
33 import java
.util
.UUID
;
35 import org
.eclipse
.tracecompass
.ctf
.core
.CTFException
;
36 import org
.eclipse
.tracecompass
.ctf
.core
.CTFStrings
;
37 import org
.eclipse
.tracecompass
.ctf
.core
.event
.CTFClock
;
38 import org
.eclipse
.tracecompass
.ctf
.core
.event
.IEventDeclaration
;
39 import org
.eclipse
.tracecompass
.ctf
.core
.event
.io
.BitBuffer
;
40 import org
.eclipse
.tracecompass
.ctf
.core
.event
.metadata
.DeclarationScope
;
41 import org
.eclipse
.tracecompass
.ctf
.core
.event
.scope
.IDefinitionScope
;
42 import org
.eclipse
.tracecompass
.ctf
.core
.event
.scope
.ILexicalScope
;
43 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.AbstractArrayDefinition
;
44 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.Definition
;
45 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.IDefinition
;
46 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.IntegerDefinition
;
47 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.StructDeclaration
;
48 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.StructDefinition
;
49 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.SafeMappedByteBuffer
;
50 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.event
.metadata
.MetadataStrings
;
51 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.event
.metadata
.exceptions
.ParseException
;
52 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.trace
.Utils
;
55 * A CTF trace on the file system.
57 * Represents a trace on the filesystem. It is responsible of parsing the
58 * metadata, creating declarations data structures, indexing the event packets
59 * (in other words, all the work that can be shared between readers), but the
60 * actual reading of events is left to TraceReader.
62 * TODO: internalize CTFTrace and DeclarationScope
64 * @author Matthew Khouzam
65 * @version $Revision: 1.0 $
67 public class CTFTrace
implements IDefinitionScope
{
70 public String
toString() {
71 /* Only for debugging, shouldn't be externalized */
72 return "CTFTrace [path=" + fPath
+ ", major=" + fMajor
+ ", minor=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
73 + fMinor
+ ", uuid=" + fUuid
+ "]"; //$NON-NLS-1$ //$NON-NLS-2$
77 * The trace directory on the filesystem.
79 private final File fPath
;
82 * Major CTF version number
87 * Minor CTF version number
99 private ByteOrder fByteOrder
;
102 * Packet header structure declaration
104 private StructDeclaration fPacketHeaderDecl
= null;
107 * The clock of the trace
109 private CTFClock fSingleClock
= null;
112 * Packet header structure definition
114 * This is only used when opening the trace files, to read the first packet
115 * header and see if they are valid trace files.
117 private StructDefinition fPacketHeaderDef
;
120 * Collection of streams contained in the trace.
122 private final Map
<Long
, CTFStream
> fStreams
= new HashMap
<>();
125 * Collection of environment variables set by the tracer
127 private final Map
<String
, String
> fEnvironment
= new HashMap
<>();
130 * Collection of all the clocks in a system.
132 private final Map
<String
, CTFClock
> fClocks
= new HashMap
<>();
134 /** Handlers for the metadata files */
135 private static final FileFilter METADATA_FILE_FILTER
= new MetadataFileFilter();
136 private static final Comparator
<File
> METADATA_COMPARATOR
= new MetadataComparator();
138 private final DeclarationScope fScope
= new DeclarationScope(null, MetadataStrings
.TRACE
);
140 // ------------------------------------------------------------------------
142 // ------------------------------------------------------------------------
148 * Filesystem path of the trace directory
149 * @throws CTFException
150 * If no CTF trace was found at the path
152 public CTFTrace(String path
) throws CTFException
{
153 this(new File(path
));
160 * Filesystem path of the trace directory.
161 * @throws CTFException
162 * If no CTF trace was found at the path
164 public CTFTrace(File path
) throws CTFException
{
166 final Metadata metadata
= new Metadata(this);
168 /* Set up the internal containers for this trace */
169 if (!fPath
.exists()) {
170 throw new CTFException("Trace (" + path
.getPath() + ") doesn't exist. Deleted or moved?"); //$NON-NLS-1$ //$NON-NLS-2$
173 if (!fPath
.isDirectory()) {
174 throw new CTFException("Path must be a valid directory"); //$NON-NLS-1$
177 /* Open and parse the metadata file */
178 metadata
.parseFile();
184 * Streamed constructor
190 private void init(File path
) throws CTFException
{
192 /* Open all the trace files */
194 /* List files not called metadata and not hidden. */
195 File
[] files
= path
.listFiles(METADATA_FILE_FILTER
);
196 Arrays
.sort(files
, METADATA_COMPARATOR
);
198 /* Try to open each file */
199 for (File streamFile
: files
) {
200 openStreamInput(streamFile
);
203 /* Create their index */
204 for (CTFStream stream
: getStreams()) {
205 Set
<CTFStreamInput
> inputs
= stream
.getStreamInputs();
206 for (CTFStreamInput s
: inputs
) {
212 // ------------------------------------------------------------------------
213 // Getters/Setters/Predicates
214 // ------------------------------------------------------------------------
217 * Gets an event declaration list for a given streamID
220 * The ID of the stream from which to read
221 * @return The list of event declarations
223 public Collection
<IEventDeclaration
> getEventDeclarations(Long streamId
) {
224 CTFStream stream
= fStreams
.get(streamId
);
225 if (stream
== null) {
228 return stream
.getEventDeclarations();
232 * Method getStream gets the stream for a given id
235 * Long the id of the stream
236 * @return Stream the stream that we need
238 public CTFStream
getStream(Long id
) {
240 return fStreams
.get(0L);
242 return fStreams
.get(id
);
246 * Method nbStreams gets the number of available streams
248 * @return int the number of streams
250 public int nbStreams() {
251 return fStreams
.size();
255 * Method setMajor sets the major version of the trace (DO NOT USE)
258 * long the major version
260 public void setMajor(long major
) {
265 * Method setMinor sets the minor version of the trace (DO NOT USE)
268 * long the minor version
270 public void setMinor(long minor
) {
275 * Method setUUID sets the UUID of a trace
280 public void setUUID(UUID uuid
) {
285 * Method setByteOrder sets the byte order
288 * ByteOrder of the trace, can be little-endian or big-endian
290 public void setByteOrder(ByteOrder byteOrder
) {
291 fByteOrder
= byteOrder
;
295 * Method setPacketHeader sets the packet header of a trace (DO NOT USE)
297 * @param packetHeader
298 * StructDeclaration the header in structdeclaration form
300 public void setPacketHeader(StructDeclaration packetHeader
) {
301 fPacketHeaderDecl
= packetHeader
;
305 * Method majorIsSet is the major version number set?
307 * @return boolean is the major set?
309 public boolean majorIsSet() {
310 return fMajor
!= null;
314 * Method minorIsSet. is the minor version number set?
316 * @return boolean is the minor set?
318 public boolean minorIsSet() {
319 return fMinor
!= null;
323 * Method UUIDIsSet is the UUID set?
325 * @return boolean is the UUID set?
327 public boolean uuidIsSet() {
328 return fUuid
!= null;
332 * Method byteOrderIsSet is the byteorder set?
334 * @return boolean is the byteorder set?
336 public boolean byteOrderIsSet() {
337 return fByteOrder
!= null;
341 * Method packetHeaderIsSet is the packet header set?
343 * @return boolean is the packet header set?
345 public boolean packetHeaderIsSet() {
346 return fPacketHeaderDecl
!= null;
350 * Method getUUID gets the trace UUID
352 * @return UUID gets the trace UUID
354 public UUID
getUUID() {
359 * Method getMajor gets the trace major version
361 * @return long gets the trace major version
363 public long getMajor() {
368 * Method getMinor gets the trace minor version
370 * @return long gets the trace minor version
372 public long getMinor() {
377 * Method getByteOrder gets the trace byte order
379 * @return ByteOrder gets the trace byte order
381 public final ByteOrder
getByteOrder() {
386 * Method getPacketHeader gets the trace packet header
388 * @return StructDeclaration gets the trace packet header
390 public StructDeclaration
getPacketHeader() {
391 return fPacketHeaderDecl
;
395 * Method getTraceDirectory gets the trace directory
397 * @return File the path in "File" format.
399 public File
getTraceDirectory() {
404 * Get all the streams as an iterable.
406 * @return Iterable<Stream> an iterable over streams.
408 public Iterable
<CTFStream
> getStreams() {
409 return fStreams
.values();
413 * Method getPath gets the path of the trace directory
415 * @return String the path of the trace directory, in string format.
416 * @see java.io.File#getPath()
418 public String
getPath() {
419 return (fPath
!= null) ? fPath
.getPath() : ""; //$NON-NLS-1$
422 // ------------------------------------------------------------------------
424 // ------------------------------------------------------------------------
426 private void addStream(CTFStreamInput s
) {
431 CTFStream stream
= s
.getStream();
432 fStreams
.put(stream
.getId(), stream
);
441 * Tries to open the given file, reads the first packet header of the file
442 * and check its validity. This will add a file to a stream as a streaminput
445 * A trace file in the trace directory.
447 * Which index in the class' streamFileChannel array this file
449 * @throws CTFException
450 * if there is a file error
452 private CTFStream
openStreamInput(File streamFile
) throws CTFException
{
453 ByteBuffer byteBuffer
;
454 BitBuffer streamBitBuffer
;
457 if (!streamFile
.canRead()) {
458 throw new CTFException("Unreadable file : " //$NON-NLS-1$
459 + streamFile
.getPath());
461 if (streamFile
.length() == 0) {
464 try (FileChannel fc
= FileChannel
.open(streamFile
.toPath(), StandardOpenOption
.READ
)) {
465 /* Map one memory page of 4 kiB */
466 byteBuffer
= SafeMappedByteBuffer
.map(fc
, MapMode
.READ_ONLY
, 0, (int) Math
.min(fc
.size(), 4096L));
467 if (byteBuffer
== null) {
468 throw new IllegalStateException("Failed to allocate memory"); //$NON-NLS-1$
470 /* Create a BitBuffer with this mapping and the trace byte order */
471 streamBitBuffer
= new BitBuffer(byteBuffer
, this.getByteOrder());
472 if (fPacketHeaderDecl
!= null) {
473 /* Read the packet header */
474 fPacketHeaderDef
= fPacketHeaderDecl
.createDefinition(this, ILexicalScope
.PACKET_HEADER
, streamBitBuffer
);
476 } catch (IOException e
) {
477 /* Shouldn't happen at this stage if every other check passed */
478 throw new CTFException(e
);
480 if (fPacketHeaderDef
!= null) {
481 validateMagicNumber(fPacketHeaderDef
);
483 validateUUID(fPacketHeaderDef
);
485 /* Read the stream ID */
486 IDefinition streamIDDef
= fPacketHeaderDef
.lookupDefinition("stream_id"); //$NON-NLS-1$
488 if (streamIDDef
instanceof IntegerDefinition
) {
489 /* This doubles as a null check */
490 long streamID
= ((IntegerDefinition
) streamIDDef
).getValue();
491 stream
= fStreams
.get(streamID
);
493 /* No stream_id in the packet header */
494 stream
= getStream(null);
498 /* No packet header, we suppose there is only one stream */
499 stream
= getStream(null);
502 if (stream
== null) {
503 throw new CTFException("Unexpected end of stream"); //$NON-NLS-1$
507 * Create the stream input and add a reference to the streamInput in the
510 stream
.addInput(new CTFStreamInput(stream
, streamFile
));
515 private void validateUUID(StructDefinition packetHeaderDef
) throws CTFException
{
516 IDefinition lookupDefinition
= packetHeaderDef
.lookupDefinition("uuid"); //$NON-NLS-1$
517 AbstractArrayDefinition uuidDef
= (AbstractArrayDefinition
) lookupDefinition
;
518 if (uuidDef
!= null) {
519 UUID otheruuid
= Utils
.getUUIDfromDefinition(uuidDef
);
520 if (!fUuid
.equals(otheruuid
)) {
521 throw new CTFException("UUID mismatch"); //$NON-NLS-1$
526 private static void validateMagicNumber(StructDefinition packetHeaderDef
) throws CTFException
{
527 IntegerDefinition magicDef
= (IntegerDefinition
) packetHeaderDef
.lookupDefinition(CTFStrings
.MAGIC
);
528 if (magicDef
!= null) {
529 int magic
= (int) magicDef
.getValue();
530 if (magic
!= Utils
.CTF_MAGIC
) {
531 throw new CTFException("CTF magic mismatch"); //$NON-NLS-1$
536 // ------------------------------------------------------------------------
538 // ------------------------------------------------------------------------
544 public ILexicalScope
getScopePath() {
545 return ILexicalScope
.TRACE
;
549 * Looks up a definition from packet
554 * @see org.eclipse.tracecompass.ctf.core.event.scope.IDefinitionScope#lookupDefinition(String)
557 public Definition
lookupDefinition(String lookupPath
) {
558 if (lookupPath
.equals(ILexicalScope
.TRACE_PACKET_HEADER
.getPath())) {
559 return fPacketHeaderDef
;
564 // ------------------------------------------------------------------------
565 // Live trace reading
566 // ------------------------------------------------------------------------
569 * Add a new stream file to support new streams while the trace is being
573 * the file of the stream
574 * @throws CTFException
575 * A stream had an issue being read
577 public void addStreamFile(File streamFile
) throws CTFException
{
578 openStreamInput(streamFile
);
582 * Registers a new stream to the trace.
586 * @throws ParseException
587 * If there was some problem reading the metadata
589 public void addStream(CTFStream stream
) throws ParseException
{
591 * If there is already a stream without id (the null key), it must be
594 if (fStreams
.get(null) != null) {
595 throw new ParseException("Stream without id with multiple streams"); //$NON-NLS-1$
599 * If the stream we try to add has no key set, it must be the only one.
600 * Thus, if the streams container is not empty, it is not valid.
602 if ((!stream
.isIdSet()) && (!fStreams
.isEmpty())) {
603 throw new ParseException("Stream without id with multiple streams"); //$NON-NLS-1$
607 * If a stream with the same ID already exists, it is not valid.
609 CTFStream existingStream
= fStreams
.get(stream
.getId());
610 if (existingStream
!= null) {
611 throw new ParseException("Stream id already exists"); //$NON-NLS-1$
614 /* This stream is valid and has a unique id. */
615 fStreams
.put(stream
.getId(), stream
);
619 * Gets the Environment variables from the trace metadata (See CTF spec)
621 * @return The environment variables in the form of an unmodifiable map
624 public Map
<String
, String
> getEnvironment() {
625 return Collections
.unmodifiableMap(fEnvironment
);
629 * Add a variable to the environment variables
632 * the name of the variable
634 * the value of the variable
636 public void addEnvironmentVar(String varName
, String varValue
) {
637 fEnvironment
.put(varName
, varValue
);
641 * Add a clock to the clock list
644 * the name of the clock (full name with scope)
648 public void addClock(String nameValue
, CTFClock ctfClock
) {
649 fClocks
.put(nameValue
, ctfClock
);
653 * gets the clock with a specific name
656 * the name of the clock.
659 public CTFClock
getClock(String name
) {
660 return fClocks
.get(name
);
664 * gets the clock if there is only one. (this is 100% of the use cases as of
669 public final CTFClock
getClock() {
670 if (fSingleClock
!= null && fClocks
.size() == 1) {
673 if (fClocks
.size() == 1) {
674 fSingleClock
= fClocks
.get(fClocks
.keySet().iterator().next());
681 * gets the time offset of a clock with respect to UTC in nanoseconds
683 * @return the time offset of a clock with respect to UTC in nanoseconds
685 public final long getOffset() {
686 if (getClock() == null) {
689 return fSingleClock
.getClockOffset();
693 * gets the time offset of a clock with respect to UTC in nanoseconds
695 * @return the time offset of a clock with respect to UTC in nanoseconds
697 private double getTimeScale() {
698 if (getClock() == null) {
701 return fSingleClock
.getClockScale();
705 * Gets the current first packet start time
707 * @return the current start time
709 public long getCurrentStartTime() {
710 long currentStart
= Long
.MAX_VALUE
;
711 for (CTFStream stream
: fStreams
.values()) {
712 for (CTFStreamInput si
: stream
.getStreamInputs()) {
713 currentStart
= Math
.min(currentStart
, si
.getIndex().getElement(0).getTimestampBegin());
716 return timestampCyclesToNanos(currentStart
);
720 * Gets the current last packet end time
722 * @return the current end time
724 public long getCurrentEndTime() {
725 long currentEnd
= Long
.MIN_VALUE
;
726 for (CTFStream stream
: fStreams
.values()) {
727 for (CTFStreamInput si
: stream
.getStreamInputs()) {
728 currentEnd
= Math
.max(currentEnd
, si
.getTimestampEnd());
731 return timestampCyclesToNanos(currentEnd
);
735 * Does the trace need to time scale?
737 * @return if the trace is in ns or cycles.
739 private boolean clockNeedsScale() {
740 if (getClock() == null) {
743 return fSingleClock
.isClockScaled();
747 * the inverse clock for returning to a scale.
749 * @return 1.0 / scale
751 private double getInverseTimeScale() {
752 if (getClock() == null) {
755 return fSingleClock
.getClockAntiScale();
760 * clock cycles since boot
761 * @return time in nanoseconds UTC offset
763 public long timestampCyclesToNanos(long cycles
) {
764 long retVal
= cycles
+ getOffset();
766 * this fix is since quite often the offset will be > than 53 bits and
767 * therefore the conversion will be lossy
769 if (clockNeedsScale()) {
770 retVal
= (long) (retVal
* getTimeScale());
777 * time in nanoseconds UTC offset
778 * @return clock cycles since boot.
780 public long timestampNanoToCycles(long nanos
) {
783 * this fix is since quite often the offset will be > than 53 bits and
784 * therefore the conversion will be lossy
786 if (clockNeedsScale()) {
787 retVal
= (long) (nanos
* getInverseTimeScale());
791 return retVal
- getOffset();
798 * the ID of the stream
800 * new file in the stream
801 * @throws CTFException
802 * The file must exist
804 public void addStream(long id
, File streamFile
) throws CTFException
{
805 final File file
= streamFile
;
807 throw new CTFException("cannot create a stream with no file"); //$NON-NLS-1$
809 CTFStream stream
= fStreams
.get(id
);
810 if (stream
== null) {
811 stream
= new CTFStream(this);
812 fStreams
.put(id
, stream
);
814 stream
.addInput(new CTFStreamInput(stream
, file
));
818 * Gets the current trace scope
820 * @return the current declaration scope
824 public DeclarationScope
getScope() {
829 class MetadataFileFilter
implements FileFilter
{
832 public boolean accept(File pathname
) {
833 if (pathname
.isDirectory()) {
836 if (pathname
.isHidden()) {
839 if (pathname
.getName().equals("metadata")) { //$NON-NLS-1$
847 class MetadataComparator
implements Comparator
<File
>, Serializable
{
849 private static final long serialVersionUID
= 1L;
852 public int compare(File o1
, File o2
) {
853 return o1
.getName().compareTo(o2
.getName());