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
.linuxtools
.ctf
.core
.trace
;
18 import java
.io
.FileFilter
;
19 import java
.io
.FileInputStream
;
20 import java
.io
.IOException
;
21 import java
.io
.Serializable
;
22 import java
.nio
.ByteOrder
;
23 import java
.nio
.MappedByteBuffer
;
24 import java
.nio
.channels
.FileChannel
;
25 import java
.nio
.channels
.FileChannel
.MapMode
;
26 import java
.util
.Arrays
;
27 import java
.util
.Collections
;
28 import java
.util
.Comparator
;
29 import java
.util
.HashMap
;
30 import java
.util
.Iterator
;
31 import java
.util
.LinkedList
;
32 import java
.util
.List
;
34 import java
.util
.Map
.Entry
;
36 import java
.util
.TreeSet
;
37 import java
.util
.UUID
;
39 import org
.eclipse
.linuxtools
.ctf
.core
.event
.CTFCallsite
;
40 import org
.eclipse
.linuxtools
.ctf
.core
.event
.CTFClock
;
41 import org
.eclipse
.linuxtools
.ctf
.core
.event
.IEventDeclaration
;
42 import org
.eclipse
.linuxtools
.ctf
.core
.event
.io
.BitBuffer
;
43 import org
.eclipse
.linuxtools
.ctf
.core
.event
.scope
.IDefinitionScope
;
44 import org
.eclipse
.linuxtools
.ctf
.core
.event
.scope
.LexicalScope
;
45 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.ArrayDefinition
;
46 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.Definition
;
47 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.IntegerDefinition
;
48 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.StructDeclaration
;
49 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.StructDefinition
;
50 import org
.eclipse
.linuxtools
.internal
.ctf
.core
.event
.CTFCallsiteComparator
;
51 import org
.eclipse
.linuxtools
.internal
.ctf
.core
.event
.metadata
.exceptions
.ParseException
;
54 * A CTF trace on the file system.
56 * Represents a trace on the filesystem. It is responsible of parsing the
57 * metadata, creating declarations data structures, indexing the event packets
58 * (in other words, all the work that can be shared between readers), but the
59 * actual reading of events is left to TraceReader.
61 * @author Matthew Khouzam
62 * @version $Revision: 1.0 $
64 public class CTFTrace
implements IDefinitionScope
, AutoCloseable
{
67 public String
toString() {
68 /* Only for debugging, shouldn't be externalized */
69 return "CTFTrace [path=" + fPath
+ ", major=" + fMajor
+ ", minor=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
70 + fMinor
+ ", uuid=" + fUuid
+ "]"; //$NON-NLS-1$ //$NON-NLS-2$
74 * The trace directory on the filesystem.
76 private final File fPath
;
79 * Major CTF version number
84 * Minor CTF version number
96 private ByteOrder fByteOrder
;
99 * Packet header structure declaration
101 private StructDeclaration fPacketHeaderDecl
= null;
104 * The clock of the trace
106 private CTFClock fSingleClock
;
109 * Packet header structure definition
111 * This is only used when opening the trace files, to read the first packet
112 * header and see if they are valid trace files.
114 private StructDefinition fPacketHeaderDef
;
117 * Collection of streams contained in the trace.
119 private final Map
<Long
, CTFStream
> fStreams
= new HashMap
<>();
122 * Collection of environment variables set by the tracer
124 private final Map
<String
, String
> fEnvironment
= new HashMap
<>();
127 * Collection of all the clocks in a system.
129 private final Map
<String
, CTFClock
> fClocks
= new HashMap
<>();
131 /** FileInputStreams to the streams */
132 private final List
<FileInputStream
> fFileInputStreams
= new LinkedList
<>();
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 /** Callsite helpers */
139 private CTFCallsiteComparator fCtfCallsiteComparator
= new CTFCallsiteComparator();
141 private Map
<String
, TreeSet
<CTFCallsite
>> fCallsitesByName
= new HashMap
<>();
143 /** Callsite helpers */
144 private TreeSet
<CTFCallsite
> fCallsitesByIP
= new TreeSet
<>();
146 // ------------------------------------------------------------------------
148 // ------------------------------------------------------------------------
154 * Filesystem path of the trace directory
155 * @throws CTFReaderException
156 * If no CTF trace was found at the path
158 public CTFTrace(String path
) throws CTFReaderException
{
159 this(new File(path
));
167 * Filesystem path of the trace directory.
168 * @throws CTFReaderException
169 * If no CTF trace was found at the path
171 public CTFTrace(File path
) throws CTFReaderException
{
173 final Metadata metadata
= new Metadata(this);
175 /* Set up the internal containers for this trace */
176 if (!fPath
.exists()) {
177 throw new CTFReaderException("Trace (" + path
.getPath() + ") doesn't exist. Deleted or moved?"); //$NON-NLS-1$ //$NON-NLS-2$
180 if (!fPath
.isDirectory()) {
181 throw new CTFReaderException("Path must be a valid directory"); //$NON-NLS-1$
184 /* Open and parse the metadata file */
185 metadata
.parseFile();
191 * Streamed constructor
199 private void init(File path
) throws CTFReaderException
{
201 /* Open all the trace files */
203 /* List files not called metadata and not hidden. */
204 File
[] files
= path
.listFiles(METADATA_FILE_FILTER
);
205 Arrays
.sort(files
, METADATA_COMPARATOR
);
207 /* Try to open each file */
208 for (File streamFile
: files
) {
209 openStreamInput(streamFile
);
212 /* Create their index */
213 for (CTFStream stream
: getStreams()) {
214 Set
<CTFStreamInput
> inputs
= stream
.getStreamInputs();
215 for (CTFStreamInput s
: inputs
) {
227 public void close() {
228 for (FileInputStream fis
: fFileInputStreams
) {
232 } catch (IOException e
) {
233 // do nothing it's ok, we tried to close it.
239 // ------------------------------------------------------------------------
240 // Getters/Setters/Predicates
241 // ------------------------------------------------------------------------
244 * Gets an event declaration hash map for a given streamID
247 * The ID of the stream from which to read
248 * @return The Hash map with the event declarations
251 public Map
<Long
, IEventDeclaration
> getEvents(Long streamId
) {
252 return fStreams
.get(streamId
).getEvents();
256 * Get an event by it's ID
259 * The ID of the stream from which to read
261 * the ID of the event
262 * @return the event declaration
265 public IEventDeclaration
getEventType(long streamId
, long id
) {
266 return getEvents(streamId
).get(id
);
270 * Method getStream gets the stream for a given id
273 * Long the id of the stream
274 * @return Stream the stream that we need
277 public CTFStream
getStream(Long id
) {
278 return fStreams
.get(id
);
282 * Method nbStreams gets the number of available streams
284 * @return int the number of streams
286 public int nbStreams() {
287 return fStreams
.size();
291 * Method setMajor sets the major version of the trace (DO NOT USE)
294 * long the major version
296 public void setMajor(long major
) {
301 * Method setMinor sets the minor version of the trace (DO NOT USE)
304 * long the minor version
306 public void setMinor(long minor
) {
311 * Method setUUID sets the UUID of a trace
316 public void setUUID(UUID uuid
) {
321 * Method setByteOrder sets the byte order
324 * ByteOrder of the trace, can be little-endian or big-endian
326 public void setByteOrder(ByteOrder byteOrder
) {
327 fByteOrder
= byteOrder
;
331 * Method setPacketHeader sets the packet header of a trace (DO NOT USE)
333 * @param packetHeader
334 * StructDeclaration the header in structdeclaration form
336 public void setPacketHeader(StructDeclaration packetHeader
) {
337 fPacketHeaderDecl
= packetHeader
;
341 * Method majorIsSet is the major version number set?
343 * @return boolean is the major set?
346 public boolean majorIsSet() {
347 return fMajor
!= null;
351 * Method minorIsSet. is the minor version number set?
353 * @return boolean is the minor set?
355 public boolean minorIsSet() {
356 return fMinor
!= null;
360 * Method UUIDIsSet is the UUID set?
362 * @return boolean is the UUID set?
365 public boolean uuidIsSet() {
366 return fUuid
!= null;
370 * Method byteOrderIsSet is the byteorder set?
372 * @return boolean is the byteorder set?
374 public boolean byteOrderIsSet() {
375 return fByteOrder
!= null;
379 * Method packetHeaderIsSet is the packet header set?
381 * @return boolean is the packet header set?
383 public boolean packetHeaderIsSet() {
384 return fPacketHeaderDecl
!= null;
388 * Method getUUID gets the trace UUID
390 * @return UUID gets the trace UUID
392 public UUID
getUUID() {
397 * Method getMajor gets the trace major version
399 * @return long gets the trace major version
401 public long getMajor() {
406 * Method getMinor gets the trace minor version
408 * @return long gets the trace minor version
410 public long getMinor() {
415 * Method getByteOrder gets the trace byte order
417 * @return ByteOrder gets the trace byte order
419 public final ByteOrder
getByteOrder() {
424 * Method getPacketHeader gets the trace packet header
426 * @return StructDeclaration gets the trace packet header
428 public StructDeclaration
getPacketHeader() {
429 return fPacketHeaderDecl
;
433 * Method getTraceDirectory gets the trace directory
435 * @return File the path in "File" format.
437 public File
getTraceDirectory() {
442 * Get all the streams as an iterable.
444 * @return Iterable<Stream> an iterable over streams.
447 public Iterable
<CTFStream
> getStreams() {
448 return fStreams
.values();
452 * Method getPath gets the path of the trace directory
454 * @return String the path of the trace directory, in string format.
455 * @see java.io.File#getPath()
457 public String
getPath() {
458 return (fPath
!= null) ? fPath
.getPath() : ""; //$NON-NLS-1$
465 public LexicalScope
getScopePath() {
466 return LexicalScope
.TRACE
;
469 // ------------------------------------------------------------------------
471 // ------------------------------------------------------------------------
473 private void addStream(CTFStreamInput s
) {
478 Iterator
<Entry
<Long
, IEventDeclaration
>> it
= s
.getStream()
479 .getEvents().entrySet().iterator();
480 while (it
.hasNext()) {
481 Entry
<Long
, IEventDeclaration
> pairs
= it
.next();
482 Long eventNum
= pairs
.getKey();
483 IEventDeclaration eventDec
= pairs
.getValue();
484 getEvents(s
.getStream().getId()).put(eventNum
, eventDec
);
494 * Tries to open the given file, reads the first packet header of the file
495 * and check its validity. This will add a file to a stream as a streaminput
498 * A trace file in the trace directory.
500 * Which index in the class' streamFileChannel array this file
502 * @throws CTFReaderException
503 * if there is a file error
505 private CTFStream
openStreamInput(File streamFile
) throws CTFReaderException
{
506 MappedByteBuffer byteBuffer
;
507 BitBuffer streamBitBuffer
;
511 if (!streamFile
.canRead()) {
512 throw new CTFReaderException("Unreadable file : " //$NON-NLS-1$
513 + streamFile
.getPath());
516 FileInputStream fis
= null;
518 /* Open the file and get the FileChannel */
519 fis
= new FileInputStream(streamFile
);
520 fFileInputStreams
.add(fis
);
521 fc
= fis
.getChannel();
523 /* Map one memory page of 4 kiB */
524 byteBuffer
= fc
.map(MapMode
.READ_ONLY
, 0, (int) Math
.min(fc
.size(), 4096L));
525 } catch (IOException e
) {
527 fFileInputStreams
.remove(fis
);
529 /* Shouldn't happen at this stage if every other check passed */
530 throw new CTFReaderException(e
);
533 /* Create a BitBuffer with this mapping and the trace byte order */
534 streamBitBuffer
= new BitBuffer(byteBuffer
, this.getByteOrder());
536 if (fPacketHeaderDecl
!= null) {
537 /* Read the packet header */
538 fPacketHeaderDef
= fPacketHeaderDecl
.createDefinition(null, LexicalScope
.PACKET_HEADER
.getName(), streamBitBuffer
);
540 /* Check the magic number */
541 IntegerDefinition magicDef
= (IntegerDefinition
) fPacketHeaderDef
.lookupDefinition("magic"); //$NON-NLS-1$
542 int magic
= (int) magicDef
.getValue();
543 if (magic
!= Utils
.CTF_MAGIC
) {
544 throw new CTFReaderException("CTF magic mismatch"); //$NON-NLS-1$
548 Definition lookupDefinition
= fPacketHeaderDef
.lookupDefinition("uuid"); //$NON-NLS-1$
549 ArrayDefinition uuidDef
= (ArrayDefinition
) lookupDefinition
;
550 if (uuidDef
!= null) {
551 UUID otheruuid
= Utils
.getUUIDfromDefinition(uuidDef
);
553 if (!fUuid
.equals(otheruuid
)) {
554 throw new CTFReaderException("UUID mismatch"); //$NON-NLS-1$
558 /* Read the stream ID */
559 Definition streamIDDef
= fPacketHeaderDef
.lookupDefinition("stream_id"); //$NON-NLS-1$
561 if (streamIDDef
instanceof IntegerDefinition
) { // this doubles as a
563 long streamID
= ((IntegerDefinition
) streamIDDef
).getValue();
564 stream
= fStreams
.get(streamID
);
566 /* No stream_id in the packet header */
567 stream
= fStreams
.get(null);
571 /* No packet header, we suppose there is only one stream */
572 stream
= fStreams
.get(null);
575 if (stream
== null) {
576 throw new CTFReaderException("Unexpected end of stream"); //$NON-NLS-1$
580 * Create the stream input and add a reference to the streamInput in the
583 stream
.addInput(new CTFStreamInput(stream
, streamFile
));
589 * Looks up a definition from packet
594 * @see org.eclipse.linuxtools.ctf.core.event.scope.IDefinitionScope#lookupDefinition(String)
597 public Definition
lookupDefinition(String lookupPath
) {
598 if (lookupPath
.equals("trace.packet.header")) { //$NON-NLS-1$
599 return fPacketHeaderDef
;
605 * Add a new stream file to support new streams while the trace is being
609 * the file of the stream
610 * @throws CTFReaderException
611 * A stream had an issue being read
614 public void addStreamFile(File streamFile
) throws CTFReaderException
{
615 openStreamInput(streamFile
);
619 * Registers a new stream to the trace.
623 * @throws ParseException
624 * If there was some problem reading the metadata
627 public void addStream(CTFStream stream
) throws ParseException
{
629 * If there is already a stream without id (the null key), it must be
632 if (fStreams
.get(null) != null) {
633 throw new ParseException("Stream without id with multiple streams"); //$NON-NLS-1$
637 * If the stream we try to add has the null key, it must be the only
638 * one. Thus, if the streams container is not empty, it is not valid.
640 if ((stream
.getId() == null) && (fStreams
.size() != 0)) {
641 throw new ParseException("Stream without id with multiple streams"); //$NON-NLS-1$
645 * If a stream with the same ID already exists, it is not valid.
647 CTFStream existingStream
= fStreams
.get(stream
.getId());
648 if (existingStream
!= null) {
649 throw new ParseException("Stream id already exists"); //$NON-NLS-1$
652 /* This stream is valid and has a unique id. */
653 fStreams
.put(stream
.getId(), stream
);
657 * Gets the Environment variables from the trace metadata (See CTF spec)
659 * @return The environment variables in the form of an unmodifiable map
663 public Map
<String
, String
> getEnvironment() {
664 return Collections
.unmodifiableMap(fEnvironment
);
668 * Add a variable to the environment variables
671 * the name of the variable
673 * the value of the variable
675 public void addEnvironmentVar(String varName
, String varValue
) {
676 fEnvironment
.put(varName
, varValue
);
680 * Add a clock to the clock list
683 * the name of the clock (full name with scope)
687 public void addClock(String nameValue
, CTFClock ctfClock
) {
688 fClocks
.put(nameValue
, ctfClock
);
692 * gets the clock with a specific name
695 * the name of the clock.
698 public CTFClock
getClock(String name
) {
699 return fClocks
.get(name
);
703 * gets the clock if there is only one. (this is 100% of the use cases as of
708 public final CTFClock
getClock() {
709 if (fClocks
.size() == 1) {
710 fSingleClock
= fClocks
.get(fClocks
.keySet().iterator().next());
717 * gets the time offset of a clock with respect to UTC in nanoseconds
719 * @return the time offset of a clock with respect to UTC in nanoseconds
721 public final long getOffset() {
722 if (getClock() == null) {
725 return fSingleClock
.getClockOffset();
729 * gets the time offset of a clock with respect to UTC in nanoseconds
731 * @return the time offset of a clock with respect to UTC in nanoseconds
733 private double getTimeScale() {
734 if (getClock() == null) {
737 return fSingleClock
.getClockScale();
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
770 public long timestampCyclesToNanos(long cycles
) {
771 long retVal
= cycles
+ getOffset();
773 * this fix is since quite often the offset will be > than 53 bits and
774 * therefore the conversion will be lossy
776 if (clockNeedsScale()) {
777 retVal
= (long) (retVal
* getTimeScale());
784 * time in nanoseconds UTC offset
785 * @return clock cycles since boot.
788 public long timestampNanoToCycles(long nanos
) {
791 * this fix is since quite often the offset will be > than 53 bits and
792 * therefore the conversion will be lossy
794 if (clockNeedsScale()) {
795 retVal
= (long) (nanos
* getInverseTimeScale());
799 return retVal
- getOffset();
806 * the event name of the callsite
808 * the name of the callsite function
810 * the ip of the callsite
812 * the filename of the callsite
814 * the line number of the callsite
816 public void addCallsite(String eventName
, String funcName
, long ip
,
817 String fileName
, long lineNumber
) {
818 final CTFCallsite cs
= new CTFCallsite(eventName
, funcName
, ip
,
819 fileName
, lineNumber
);
820 TreeSet
<CTFCallsite
> csl
= fCallsitesByName
.get(eventName
);
822 csl
= new TreeSet
<>(fCtfCallsiteComparator
);
823 fCallsitesByName
.put(eventName
, csl
);
828 fCallsitesByIP
.add(cs
);
832 * Gets the set of callsites associated to an event name. O(1)
836 * @return the callsite set can be empty
839 public TreeSet
<CTFCallsite
> getCallsiteCandidates(String eventName
) {
840 TreeSet
<CTFCallsite
> retVal
= fCallsitesByName
.get(eventName
);
841 if (retVal
== null) {
842 retVal
= new TreeSet
<>(fCtfCallsiteComparator
);
848 * The I'm feeling lucky of getCallsiteCandidates O(1)
852 * @return the first callsite that has that event name, can be null
855 public CTFCallsite
getCallsite(String eventName
) {
856 TreeSet
<CTFCallsite
> callsites
= fCallsitesByName
.get(eventName
);
857 if (callsites
!= null) {
858 return callsites
.first();
864 * Gets a callsite from the instruction pointer O(log(n))
867 * the instruction pointer to lookup
868 * @return the callsite just before that IP in the list remember the IP is
869 * backwards on X86, can be null if no callsite is before the IP.
872 public CTFCallsite
getCallsite(long ip
) {
873 CTFCallsite cs
= new CTFCallsite(null, null, ip
, null, 0L);
874 return fCallsitesByIP
.ceiling(cs
);
878 * Gets a callsite using the event name and instruction pointer O(log(n))
881 * the name of the event
883 * the instruction pointer
884 * @return the closest matching callsite, can be null
886 public CTFCallsite
getCallsite(String eventName
, long ip
) {
887 final TreeSet
<CTFCallsite
> candidates
= fCallsitesByName
.get(eventName
);
888 final CTFCallsite dummyCs
= new CTFCallsite(null, null, ip
, null, -1);
889 final CTFCallsite callsite
= candidates
.ceiling(dummyCs
);
890 if (callsite
== null) {
891 return candidates
.floor(dummyCs
);
900 * the ID of the stream
902 * new file in the stream
903 * @throws CTFReaderException
904 * The file must exist
907 public void addStream(long id
, File streamFile
) throws CTFReaderException
{
908 CTFStream stream
= null;
909 if (fStreams
.containsKey(id
)) {
910 stream
= fStreams
.get(id
);
912 stream
= new CTFStream(this);
913 fStreams
.put(id
, stream
);
915 stream
.addInput(new CTFStreamInput(stream
, streamFile
));
919 class MetadataFileFilter
implements FileFilter
{
922 public boolean accept(File pathname
) {
923 if (pathname
.isDirectory()) {
926 if (pathname
.isHidden()) {
929 if (pathname
.getName().equals("metadata")) { //$NON-NLS-1$
937 class MetadataComparator
implements Comparator
<File
>, Serializable
{
939 private static final long serialVersionUID
= 1L;
942 public int compare(File o1
, File o2
) {
943 return o1
.getName().compareTo(o2
.getName());