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
.TreeSet
;
34 import java
.util
.UUID
;
36 import org
.eclipse
.tracecompass
.ctf
.core
.CTFException
;
37 import org
.eclipse
.tracecompass
.ctf
.core
.CTFStrings
;
38 import org
.eclipse
.tracecompass
.ctf
.core
.event
.CTFCallsite
;
39 import org
.eclipse
.tracecompass
.ctf
.core
.event
.CTFClock
;
40 import org
.eclipse
.tracecompass
.ctf
.core
.event
.IEventDeclaration
;
41 import org
.eclipse
.tracecompass
.ctf
.core
.event
.io
.BitBuffer
;
42 import org
.eclipse
.tracecompass
.ctf
.core
.event
.metadata
.DeclarationScope
;
43 import org
.eclipse
.tracecompass
.ctf
.core
.event
.scope
.IDefinitionScope
;
44 import org
.eclipse
.tracecompass
.ctf
.core
.event
.scope
.ILexicalScope
;
45 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.AbstractArrayDefinition
;
46 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.Definition
;
47 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.IDefinition
;
48 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.IntegerDefinition
;
49 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.StructDeclaration
;
50 import org
.eclipse
.tracecompass
.ctf
.core
.event
.types
.StructDefinition
;
51 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.SafeMappedByteBuffer
;
52 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.event
.CTFCallsiteComparator
;
53 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.event
.metadata
.MetadataStrings
;
54 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.event
.metadata
.exceptions
.ParseException
;
55 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.trace
.Utils
;
58 * A CTF trace on the file system.
60 * Represents a trace on the filesystem. It is responsible of parsing the
61 * metadata, creating declarations data structures, indexing the event packets
62 * (in other words, all the work that can be shared between readers), but the
63 * actual reading of events is left to TraceReader.
65 * TODO: internalize CTFTrace and DeclarationScope
67 * @author Matthew Khouzam
68 * @version $Revision: 1.0 $
70 public class CTFTrace
implements IDefinitionScope
{
73 public String
toString() {
74 /* Only for debugging, shouldn't be externalized */
75 return "CTFTrace [path=" + fPath
+ ", major=" + fMajor
+ ", minor=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
76 + fMinor
+ ", uuid=" + fUuid
+ "]"; //$NON-NLS-1$ //$NON-NLS-2$
80 * The trace directory on the filesystem.
82 private final File fPath
;
85 * Major CTF version number
90 * Minor CTF version number
102 private ByteOrder fByteOrder
;
105 * Packet header structure declaration
107 private StructDeclaration fPacketHeaderDecl
= null;
110 * The clock of the trace
112 private CTFClock fSingleClock
= null;
115 * Packet header structure definition
117 * This is only used when opening the trace files, to read the first packet
118 * header and see if they are valid trace files.
120 private StructDefinition fPacketHeaderDef
;
123 * Collection of streams contained in the trace.
125 private final Map
<Long
, CTFStream
> fStreams
= new HashMap
<>();
128 * Collection of environment variables set by the tracer
130 private final Map
<String
, String
> fEnvironment
= new HashMap
<>();
133 * Collection of all the clocks in a system.
135 private final Map
<String
, CTFClock
> fClocks
= new HashMap
<>();
137 /** Handlers for the metadata files */
138 private static final FileFilter METADATA_FILE_FILTER
= new MetadataFileFilter();
139 private static final Comparator
<File
> METADATA_COMPARATOR
= new MetadataComparator();
141 /** Callsite helpers */
142 private CTFCallsiteComparator fCtfCallsiteComparator
= new CTFCallsiteComparator();
144 private Map
<String
, TreeSet
<CTFCallsite
>> fCallsitesByName
= new HashMap
<>();
146 /** Callsite helpers */
147 private TreeSet
<CTFCallsite
> fCallsitesByIP
= new TreeSet
<>();
149 private final DeclarationScope fScope
= new DeclarationScope(null, MetadataStrings
.TRACE
);
151 // ------------------------------------------------------------------------
153 // ------------------------------------------------------------------------
159 * Filesystem path of the trace directory
160 * @throws CTFException
161 * If no CTF trace was found at the path
163 public CTFTrace(String path
) throws CTFException
{
164 this(new File(path
));
171 * Filesystem path of the trace directory.
172 * @throws CTFException
173 * If no CTF trace was found at the path
175 public CTFTrace(File path
) throws CTFException
{
177 final Metadata metadata
= new Metadata(this);
179 /* Set up the internal containers for this trace */
180 if (!fPath
.exists()) {
181 throw new CTFException("Trace (" + path
.getPath() + ") doesn't exist. Deleted or moved?"); //$NON-NLS-1$ //$NON-NLS-2$
184 if (!fPath
.isDirectory()) {
185 throw new CTFException("Path must be a valid directory"); //$NON-NLS-1$
188 /* Open and parse the metadata file */
189 metadata
.parseFile();
195 * Streamed constructor
201 private void init(File path
) throws CTFException
{
203 /* Open all the trace files */
205 /* List files not called metadata and not hidden. */
206 File
[] files
= path
.listFiles(METADATA_FILE_FILTER
);
207 Arrays
.sort(files
, METADATA_COMPARATOR
);
209 /* Try to open each file */
210 for (File streamFile
: files
) {
211 openStreamInput(streamFile
);
214 /* Create their index */
215 for (CTFStream stream
: getStreams()) {
216 Set
<CTFStreamInput
> inputs
= stream
.getStreamInputs();
217 for (CTFStreamInput s
: inputs
) {
223 // ------------------------------------------------------------------------
224 // Getters/Setters/Predicates
225 // ------------------------------------------------------------------------
228 * Gets an event declaration list for a given streamID
231 * The ID of the stream from which to read
232 * @return The list of event declarations
234 public Collection
<IEventDeclaration
> getEventDeclarations(Long streamId
) {
235 return fStreams
.get(streamId
).getEventDeclarations();
239 * Method getStream gets the stream for a given id
242 * Long the id of the stream
243 * @return Stream the stream that we need
245 public CTFStream
getStream(Long id
) {
247 return fStreams
.get(0L);
249 return fStreams
.get(id
);
253 * Method nbStreams gets the number of available streams
255 * @return int the number of streams
257 public int nbStreams() {
258 return fStreams
.size();
262 * Method setMajor sets the major version of the trace (DO NOT USE)
265 * long the major version
267 public void setMajor(long major
) {
272 * Method setMinor sets the minor version of the trace (DO NOT USE)
275 * long the minor version
277 public void setMinor(long minor
) {
282 * Method setUUID sets the UUID of a trace
287 public void setUUID(UUID uuid
) {
292 * Method setByteOrder sets the byte order
295 * ByteOrder of the trace, can be little-endian or big-endian
297 public void setByteOrder(ByteOrder byteOrder
) {
298 fByteOrder
= byteOrder
;
302 * Method setPacketHeader sets the packet header of a trace (DO NOT USE)
304 * @param packetHeader
305 * StructDeclaration the header in structdeclaration form
307 public void setPacketHeader(StructDeclaration packetHeader
) {
308 fPacketHeaderDecl
= packetHeader
;
312 * Method majorIsSet is the major version number set?
314 * @return boolean is the major set?
316 public boolean majorIsSet() {
317 return fMajor
!= null;
321 * Method minorIsSet. is the minor version number set?
323 * @return boolean is the minor set?
325 public boolean minorIsSet() {
326 return fMinor
!= null;
330 * Method UUIDIsSet is the UUID set?
332 * @return boolean is the UUID set?
334 public boolean uuidIsSet() {
335 return fUuid
!= null;
339 * Method byteOrderIsSet is the byteorder set?
341 * @return boolean is the byteorder set?
343 public boolean byteOrderIsSet() {
344 return fByteOrder
!= null;
348 * Method packetHeaderIsSet is the packet header set?
350 * @return boolean is the packet header set?
352 public boolean packetHeaderIsSet() {
353 return fPacketHeaderDecl
!= null;
357 * Method getUUID gets the trace UUID
359 * @return UUID gets the trace UUID
361 public UUID
getUUID() {
366 * Method getMajor gets the trace major version
368 * @return long gets the trace major version
370 public long getMajor() {
375 * Method getMinor gets the trace minor version
377 * @return long gets the trace minor version
379 public long getMinor() {
384 * Method getByteOrder gets the trace byte order
386 * @return ByteOrder gets the trace byte order
388 public final ByteOrder
getByteOrder() {
393 * Method getPacketHeader gets the trace packet header
395 * @return StructDeclaration gets the trace packet header
397 public StructDeclaration
getPacketHeader() {
398 return fPacketHeaderDecl
;
402 * Method getTraceDirectory gets the trace directory
404 * @return File the path in "File" format.
406 public File
getTraceDirectory() {
411 * Get all the streams as an iterable.
413 * @return Iterable<Stream> an iterable over streams.
415 public Iterable
<CTFStream
> getStreams() {
416 return fStreams
.values();
420 * Method getPath gets the path of the trace directory
422 * @return String the path of the trace directory, in string format.
423 * @see java.io.File#getPath()
425 public String
getPath() {
426 return (fPath
!= null) ? fPath
.getPath() : ""; //$NON-NLS-1$
429 // ------------------------------------------------------------------------
431 // ------------------------------------------------------------------------
433 private void addStream(CTFStreamInput s
) {
438 CTFStream stream
= s
.getStream();
439 fStreams
.put(stream
.getId(), stream
);
448 * Tries to open the given file, reads the first packet header of the file
449 * and check its validity. This will add a file to a stream as a streaminput
452 * A trace file in the trace directory.
454 * Which index in the class' streamFileChannel array this file
456 * @throws CTFException
457 * if there is a file error
459 private CTFStream
openStreamInput(File streamFile
) throws CTFException
{
460 ByteBuffer byteBuffer
;
461 BitBuffer streamBitBuffer
;
464 if (!streamFile
.canRead()) {
465 throw new CTFException("Unreadable file : " //$NON-NLS-1$
466 + streamFile
.getPath());
469 try (FileChannel fc
= FileChannel
.open(streamFile
.toPath(), StandardOpenOption
.READ
)) {
470 /* Map one memory page of 4 kiB */
471 byteBuffer
= SafeMappedByteBuffer
.map(fc
, MapMode
.READ_ONLY
, 0, (int) Math
.min(fc
.size(), 4096L));
472 if (byteBuffer
== null) {
473 throw new IllegalStateException("Failed to allocate memory"); //$NON-NLS-1$
475 /* Create a BitBuffer with this mapping and the trace byte order */
476 streamBitBuffer
= new BitBuffer(byteBuffer
, this.getByteOrder());
478 if (fPacketHeaderDecl
!= null) {
479 /* Read the packet header */
480 fPacketHeaderDef
= fPacketHeaderDecl
.createDefinition(this, ILexicalScope
.PACKET_HEADER
, streamBitBuffer
);
482 } catch (IOException e
) {
483 /* Shouldn't happen at this stage if every other check passed */
484 throw new CTFException(e
);
486 if (fPacketHeaderDef
!= null) {
487 validateMagicNumber(fPacketHeaderDef
);
489 validateUUID(fPacketHeaderDef
);
491 /* Read the stream ID */
492 IDefinition streamIDDef
= fPacketHeaderDef
.lookupDefinition("stream_id"); //$NON-NLS-1$
494 if (streamIDDef
instanceof IntegerDefinition
) {
495 /* This doubles as a null check */
496 long streamID
= ((IntegerDefinition
) streamIDDef
).getValue();
497 stream
= fStreams
.get(streamID
);
499 /* No stream_id in the packet header */
500 stream
= getStream(null);
504 /* No packet header, we suppose there is only one stream */
505 stream
= getStream(null);
508 if (stream
== null) {
509 throw new CTFException("Unexpected end of stream"); //$NON-NLS-1$
513 * Create the stream input and add a reference to the streamInput in the
516 stream
.addInput(new CTFStreamInput(stream
, streamFile
));
521 private void validateUUID(StructDefinition packetHeaderDef
) throws CTFException
{
522 IDefinition lookupDefinition
= packetHeaderDef
.lookupDefinition("uuid"); //$NON-NLS-1$
523 AbstractArrayDefinition uuidDef
= (AbstractArrayDefinition
) lookupDefinition
;
524 if (uuidDef
!= null) {
525 UUID otheruuid
= Utils
.getUUIDfromDefinition(uuidDef
);
526 if (!fUuid
.equals(otheruuid
)) {
527 throw new CTFException("UUID mismatch"); //$NON-NLS-1$
532 private static void validateMagicNumber(StructDefinition packetHeaderDef
) throws CTFException
{
533 IntegerDefinition magicDef
= (IntegerDefinition
) packetHeaderDef
.lookupDefinition(CTFStrings
.MAGIC
);
534 if (magicDef
!= null) {
535 int magic
= (int) magicDef
.getValue();
536 if (magic
!= Utils
.CTF_MAGIC
) {
537 throw new CTFException("CTF magic mismatch"); //$NON-NLS-1$
542 // ------------------------------------------------------------------------
544 // ------------------------------------------------------------------------
550 public ILexicalScope
getScopePath() {
551 return ILexicalScope
.TRACE
;
555 * Looks up a definition from packet
560 * @see org.eclipse.tracecompass.ctf.core.event.scope.IDefinitionScope#lookupDefinition(String)
563 public Definition
lookupDefinition(String lookupPath
) {
564 if (lookupPath
.equals(ILexicalScope
.TRACE_PACKET_HEADER
.getPath())) {
565 return fPacketHeaderDef
;
570 // ------------------------------------------------------------------------
571 // Live trace reading
572 // ------------------------------------------------------------------------
575 * Add a new stream file to support new streams while the trace is being
579 * the file of the stream
580 * @throws CTFException
581 * A stream had an issue being read
583 public void addStreamFile(File streamFile
) throws CTFException
{
584 openStreamInput(streamFile
);
588 * Registers a new stream to the trace.
592 * @throws ParseException
593 * If there was some problem reading the metadata
595 public void addStream(CTFStream stream
) throws ParseException
{
597 * If there is already a stream without id (the null key), it must be
600 if (fStreams
.get(null) != null) {
601 throw new ParseException("Stream without id with multiple streams"); //$NON-NLS-1$
605 * If the stream we try to add has no key set, it must be the only one.
606 * Thus, if the streams container is not empty, it is not valid.
608 if ((!stream
.isIdSet()) && (!fStreams
.isEmpty())) {
609 throw new ParseException("Stream without id with multiple streams"); //$NON-NLS-1$
613 * If a stream with the same ID already exists, it is not valid.
615 CTFStream existingStream
= fStreams
.get(stream
.getId());
616 if (existingStream
!= null) {
617 throw new ParseException("Stream id already exists"); //$NON-NLS-1$
620 /* This stream is valid and has a unique id. */
621 fStreams
.put(stream
.getId(), stream
);
625 * Gets the Environment variables from the trace metadata (See CTF spec)
627 * @return The environment variables in the form of an unmodifiable map
630 public Map
<String
, String
> getEnvironment() {
631 return Collections
.unmodifiableMap(fEnvironment
);
635 * Add a variable to the environment variables
638 * the name of the variable
640 * the value of the variable
642 public void addEnvironmentVar(String varName
, String varValue
) {
643 fEnvironment
.put(varName
, varValue
);
647 * Add a clock to the clock list
650 * the name of the clock (full name with scope)
654 public void addClock(String nameValue
, CTFClock ctfClock
) {
655 fClocks
.put(nameValue
, ctfClock
);
659 * gets the clock with a specific name
662 * the name of the clock.
665 public CTFClock
getClock(String name
) {
666 return fClocks
.get(name
);
670 * gets the clock if there is only one. (this is 100% of the use cases as of
675 public final CTFClock
getClock() {
676 if (fSingleClock
!= null && fClocks
.size() == 1) {
679 if (fClocks
.size() == 1) {
680 fSingleClock
= fClocks
.get(fClocks
.keySet().iterator().next());
687 * gets the time offset of a clock with respect to UTC in nanoseconds
689 * @return the time offset of a clock with respect to UTC in nanoseconds
691 public final long getOffset() {
692 if (getClock() == null) {
695 return fSingleClock
.getClockOffset();
699 * gets the time offset of a clock with respect to UTC in nanoseconds
701 * @return the time offset of a clock with respect to UTC in nanoseconds
703 private double getTimeScale() {
704 if (getClock() == null) {
707 return fSingleClock
.getClockScale();
711 * Gets the current first packet start time
713 * @return the current start time
715 public long getCurrentStartTime() {
716 long currentStart
= Long
.MAX_VALUE
;
717 for (CTFStream stream
: fStreams
.values()) {
718 for (CTFStreamInput si
: stream
.getStreamInputs()) {
719 currentStart
= Math
.min(currentStart
, si
.getIndex().getElement(0).getTimestampBegin());
722 return timestampCyclesToNanos(currentStart
);
726 * Gets the current last packet end time
728 * @return the current end time
730 public long getCurrentEndTime() {
731 long currentEnd
= Long
.MIN_VALUE
;
732 for (CTFStream stream
: fStreams
.values()) {
733 for (CTFStreamInput si
: stream
.getStreamInputs()) {
734 currentEnd
= Math
.max(currentEnd
, si
.getTimestampEnd());
737 return timestampCyclesToNanos(currentEnd
);
741 * Does the trace need to time scale?
743 * @return if the trace is in ns or cycles.
745 private boolean clockNeedsScale() {
746 if (getClock() == null) {
749 return fSingleClock
.isClockScaled();
753 * the inverse clock for returning to a scale.
755 * @return 1.0 / scale
757 private double getInverseTimeScale() {
758 if (getClock() == null) {
761 return fSingleClock
.getClockAntiScale();
766 * clock cycles since boot
767 * @return time in nanoseconds UTC offset
769 public long timestampCyclesToNanos(long cycles
) {
770 long retVal
= cycles
+ getOffset();
772 * this fix is since quite often the offset will be > than 53 bits and
773 * therefore the conversion will be lossy
775 if (clockNeedsScale()) {
776 retVal
= (long) (retVal
* getTimeScale());
783 * time in nanoseconds UTC offset
784 * @return clock cycles since boot.
786 public long timestampNanoToCycles(long nanos
) {
789 * this fix is since quite often the offset will be > than 53 bits and
790 * therefore the conversion will be lossy
792 if (clockNeedsScale()) {
793 retVal
= (long) (nanos
* getInverseTimeScale());
797 return retVal
- getOffset();
804 * the event name of the callsite
806 * the name of the callsite function
808 * the ip of the callsite
810 * the filename of the callsite
812 * the line number of the callsite
814 public void addCallsite(String eventName
, String funcName
, long ip
,
815 String fileName
, long lineNumber
) {
816 final CTFCallsite cs
= new CTFCallsite(eventName
, funcName
, ip
,
817 fileName
, lineNumber
);
818 TreeSet
<CTFCallsite
> csl
= fCallsitesByName
.get(eventName
);
820 csl
= new TreeSet
<>(fCtfCallsiteComparator
);
821 fCallsitesByName
.put(eventName
, csl
);
826 fCallsitesByIP
.add(cs
);
830 * Gets the set of callsites associated to an event name. O(1)
834 * @return the callsite set can be empty
836 public TreeSet
<CTFCallsite
> getCallsiteCandidates(String eventName
) {
837 TreeSet
<CTFCallsite
> retVal
= fCallsitesByName
.get(eventName
);
838 if (retVal
== null) {
839 retVal
= new TreeSet
<>(fCtfCallsiteComparator
);
845 * The I'm feeling lucky of getCallsiteCandidates O(1)
849 * @return the first callsite that has that event name, can be null
851 public CTFCallsite
getCallsite(String eventName
) {
852 TreeSet
<CTFCallsite
> callsites
= fCallsitesByName
.get(eventName
);
853 if (callsites
!= null) {
854 return callsites
.first();
860 * Gets a callsite from the instruction pointer O(log(n))
863 * the instruction pointer to lookup
864 * @return the callsite just before that IP in the list remember the IP is
865 * backwards on X86, can be null if no callsite is before the IP.
867 public CTFCallsite
getCallsite(long ip
) {
868 CTFCallsite cs
= new CTFCallsite(null, null, ip
, null, 0L);
869 return fCallsitesByIP
.ceiling(cs
);
873 * Gets a callsite using the event name and instruction pointer O(log(n))
876 * the name of the event
878 * the instruction pointer
879 * @return the closest matching callsite, can be null
881 public CTFCallsite
getCallsite(String eventName
, long ip
) {
882 final TreeSet
<CTFCallsite
> candidates
= fCallsitesByName
.get(eventName
);
883 if (candidates
== null) {
886 final CTFCallsite dummyCs
= new CTFCallsite(null, null, ip
, null, -1);
887 final CTFCallsite callsite
= candidates
.ceiling(dummyCs
);
888 if (callsite
== null) {
889 return candidates
.floor(dummyCs
);
898 * the ID of the stream
900 * new file in the stream
901 * @throws CTFException
902 * The file must exist
904 public void addStream(long id
, File streamFile
) throws CTFException
{
905 CTFStream stream
= null;
906 final File file
= streamFile
;
908 throw new CTFException("cannot create a stream with no file"); //$NON-NLS-1$
910 if (fStreams
.containsKey(id
)) {
911 stream
= fStreams
.get(id
);
913 stream
= new CTFStream(this);
914 fStreams
.put(id
, stream
);
916 stream
.addInput(new CTFStreamInput(stream
, file
));
920 * Gets the current trace scope
922 * @return the current declaration scope
926 public DeclarationScope
getScope() {
931 class MetadataFileFilter
implements FileFilter
{
934 public boolean accept(File pathname
) {
935 if (pathname
.isDirectory()) {
938 if (pathname
.isHidden()) {
941 if (pathname
.getName().equals("metadata")) { //$NON-NLS-1$
949 class MetadataComparator
implements Comparator
<File
>, Serializable
{
951 private static final long serialVersionUID
= 1L;
954 public int compare(File o1
, File o2
) {
955 return o1
.getName().compareTo(o2
.getName());