1 /*******************************************************************************
2 * Copyright (c) 2011-2012 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
9 * Contributors: Matthew Khouzam - Initial API and implementation
10 * Contributors: Alexandre Montplaisir - Initial API and implementation
11 *******************************************************************************/
13 package org
.eclipse
.linuxtools
.ctf
.core
.trace
;
16 import java
.io
.FileFilter
;
17 import java
.io
.FileInputStream
;
18 import java
.io
.IOException
;
19 import java
.io
.Serializable
;
20 import java
.nio
.ByteOrder
;
21 import java
.nio
.MappedByteBuffer
;
22 import java
.nio
.channels
.FileChannel
;
23 import java
.nio
.channels
.FileChannel
.MapMode
;
24 import java
.util
.Arrays
;
25 import java
.util
.Collections
;
26 import java
.util
.Comparator
;
27 import java
.util
.HashMap
;
28 import java
.util
.Iterator
;
29 import java
.util
.LinkedList
;
30 import java
.util
.List
;
31 import java
.util
.ListIterator
;
33 import java
.util
.Map
.Entry
;
35 import java
.util
.TreeSet
;
36 import java
.util
.UUID
;
38 import org
.eclipse
.linuxtools
.ctf
.core
.event
.CTFCallsite
;
39 import org
.eclipse
.linuxtools
.ctf
.core
.event
.CTFClock
;
40 import org
.eclipse
.linuxtools
.ctf
.core
.event
.EventDeclaration
;
41 import org
.eclipse
.linuxtools
.ctf
.core
.event
.EventDefinition
;
42 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.ArrayDefinition
;
43 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.Definition
;
44 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.IDefinitionScope
;
45 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.IntegerDefinition
;
46 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.StructDeclaration
;
47 import org
.eclipse
.linuxtools
.ctf
.core
.event
.types
.StructDefinition
;
48 import org
.eclipse
.linuxtools
.internal
.ctf
.core
.event
.io
.BitBuffer
;
49 import org
.eclipse
.linuxtools
.internal
.ctf
.core
.event
.metadata
.exceptions
.ParseException
;
50 import org
.eclipse
.linuxtools
.internal
.ctf
.core
.trace
.Stream
;
51 import org
.eclipse
.linuxtools
.internal
.ctf
.core
.trace
.StreamInput
;
52 import org
.eclipse
.linuxtools
.internal
.ctf
.core
.trace
.StreamInputPacketIndex
;
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 * @author Matthew Khouzam
63 * @version $Revision: 1.0 $
65 public class CTFTrace
implements IDefinitionScope
{
70 * @see java.lang.Object#toString()
72 @SuppressWarnings("nls")
74 public String
toString() {
75 /* Only for debugging, shouldn't be externalized */
76 return "CTFTrace [path=" + path
+ ", major=" + major
+ ", minor="
77 + minor
+ ", uuid=" + uuid
+ "]";
81 * The trace directory on the filesystem.
83 private final File path
;
86 * The metadata parsing object.
88 private final Metadata metadata
;
91 * Major CTF version number
96 * Minor CTF version number
108 private ByteOrder byteOrder
;
111 * Packet header structure declaration
113 private StructDeclaration packetHeaderDecl
= null;
116 * The clock of the trace
118 private CTFClock singleClock
;
121 * Packet header structure definition
123 * This is only used when opening the trace files, to read the first packet
124 * header and see if they are valid trace files.
126 private StructDefinition packetHeaderDef
;
129 * Collection of streams contained in the trace.
131 private final Map
<Long
, Stream
> streams
= new HashMap
<Long
, Stream
>();
134 * Collection of environment variables set by the tracer
136 private final Map
<String
, String
> environment
= new HashMap
<String
, String
>();
139 * Collection of all the clocks in a system.
141 private final Map
<String
, CTFClock
> clocks
= new HashMap
<String
, CTFClock
>();
143 /** FileChannels to the streams */
144 private final List
<FileChannel
> streamFileChannels
= new LinkedList
<FileChannel
>();
146 /** Handlers for the metadata files */
147 private final static FileFilter metadataFileFilter
= new MetadataFileFilter();
148 private final static Comparator
<File
> metadataComparator
= new MetadataComparator(); // $codepro.audit.disable
151 /** map of all the event types */
152 private final Map
<Long
,HashMap
<Long
, EventDeclaration
>> eventDecs
= new HashMap
<Long
, HashMap
<Long
,EventDeclaration
>>();
153 /** map of all the event types */
154 private final Map
<StreamInput
,HashMap
<Long
, EventDefinition
>> eventDefs
= new HashMap
<StreamInput
, HashMap
<Long
,EventDefinition
>>();
155 /** map of all the indexes */
156 private final Map
<StreamInput
, StreamInputPacketIndex
> indexes
= new HashMap
<StreamInput
, StreamInputPacketIndex
>();
158 /** Callsite helpers */
159 private Map
<String
, LinkedList
<CTFCallsite
>> callsitesByName
= new HashMap
<String
, LinkedList
<CTFCallsite
>>();
160 /** Callsite helpers */
161 private TreeSet
<CTFCallsite
> callsitesByIP
= new TreeSet
<CTFCallsite
>();
165 // ------------------------------------------------------------------------
167 // ------------------------------------------------------------------------
173 * Filesystem path of the trace directory
174 * @throws CTFReaderException
175 * If no CTF trace was found at the path
177 public CTFTrace(String path
) throws CTFReaderException
{
178 this(new File(path
));
186 * Filesystem path of the trace directory.
187 * @throws CTFReaderException
188 * If no CTF trace was found at the path
190 public CTFTrace(File path
) throws CTFReaderException
{
192 this.metadata
= new Metadata(this);
194 /* Set up the internal containers for this trace */
195 if (!this.path
.exists()) {
196 throw new CTFReaderException("Trace (" + path
.getPath() + ") doesn't exist. Deleted or moved?"); //$NON-NLS-1$ //$NON-NLS-2$
199 if (!this.path
.isDirectory()) {
200 throw new CTFReaderException("Path must be a valid directory"); //$NON-NLS-1$
203 /* Open and parse the metadata file */
206 /* Open all the trace files */
207 /* Create the definitions needed to read things from the files */
208 if (packetHeaderDecl
!= null) {
209 packetHeaderDef
= packetHeaderDecl
.createDefinition(this,
210 "packet.header"); //$NON-NLS-1$
213 /* List files not called metadata and not hidden. */
214 File
[] files
= path
.listFiles(metadataFileFilter
);
215 Arrays
.sort(files
, metadataComparator
);
216 /* Try to open each file */
217 for (File streamFile
: files
) {
218 openStreamInput(streamFile
);
221 /* Create their index */
222 for (Map
.Entry
<Long
, Stream
> stream
: streams
.entrySet()) {
223 Set
<StreamInput
> inputs
= stream
.getValue().getStreamInputs();
224 for (StreamInput s
: inputs
) {
228 Iterator
<Entry
<Long
, EventDeclaration
>> it
= s
.getStream()
229 .getEvents().entrySet().iterator();
230 while (it
.hasNext()) {
231 Map
.Entry
<Long
, EventDeclaration
> pairs
= it
.next();
232 Long eventNum
= pairs
.getKey();
233 EventDeclaration eventDec
= pairs
.getValue();
234 getEvents(s
.getStream().getId()).put(eventNum
, eventDec
);
246 protected void finalize() throws Throwable
{
247 /* If this trace gets closed, release the descriptors to the streams */
248 for (FileChannel fc
: streamFileChannels
) {
252 } catch (IOException e
) {
253 // do nothing it's ok, we tried to close it.
261 // ------------------------------------------------------------------------
262 // Getters/Setters/Predicates
263 // ------------------------------------------------------------------------
266 * Gets an event declaration hash map for a given streamID
269 * The ID of the stream from which to read
270 * @return The Hash map with the event declarations
272 public HashMap
<Long
, EventDeclaration
> getEvents(Long streamId
) {
273 return eventDecs
.get(streamId
);
277 * Gets an index for a given StreamInput
278 * @param id the StreamInput
281 public StreamInputPacketIndex
getIndex(StreamInput id
){
282 if(! indexes
.containsKey(id
)){
283 indexes
.put(id
, new StreamInputPacketIndex());
285 return indexes
.get(id
);
289 * Gets an event Declaration hashmap for a given StreamInput
290 * @param id the StreamInput
291 * @return the hashmap with the event definitions
293 public HashMap
<Long
, EventDefinition
> getEventDefs(StreamInput id
) {
294 if(! eventDefs
.containsKey(id
)){
295 eventDefs
.put(id
, new HashMap
<Long
, EventDefinition
>());
297 return eventDefs
.get(id
);
301 * Get an event by it's ID
304 * The ID of the stream from which to read
306 * the ID of the event
307 * @return the event declaration
309 public EventDeclaration
getEventType(long streamId
, long id
) {
310 return getEvents(streamId
).get(id
);
314 * Method getStream gets the stream for a given id
317 * Long the id of the stream
318 * @return Stream the stream that we need
320 public Stream
getStream(Long id
) {
321 return streams
.get(id
);
325 * Method nbStreams gets the number of available streams
327 * @return int the number of streams
329 public int nbStreams() {
330 return streams
.size();
334 * Method setMajor sets the major version of the trace (DO NOT USE)
337 * long the major version
339 public void setMajor(long major
) {
344 * Method setMinor sets the minor version of the trace (DO NOT USE)
347 * long the minor version
349 public void setMinor(long minor
) {
354 * Method setUUID sets the UUID of a trace
359 public void setUUID(UUID uuid
) {
364 * Method setByteOrder sets the byte order
367 * ByteOrder of the trace, can be little-endian or big-endian
369 public void setByteOrder(ByteOrder byteOrder
) {
370 this.byteOrder
= byteOrder
;
374 * Method setPacketHeader sets the packet header of a trace (DO NOT USE)
376 * @param packetHeader
377 * StructDeclaration the header in structdeclaration form
379 public void setPacketHeader(StructDeclaration packetHeader
) {
380 this.packetHeaderDecl
= packetHeader
;
384 * Method majortIsSet is the major version number set?
386 * @return boolean is the major set?
388 public boolean majortIsSet() {
389 return major
!= null;
393 * Method minorIsSet. is the minor version number set?
395 * @return boolean is the minor set?
397 public boolean minorIsSet() {
398 return minor
!= null;
402 * Method UUIDIsSet is the UUID set?
404 * @return boolean is the UUID set?
406 public boolean UUIDIsSet() {
411 * Method byteOrderIsSet is the byteorder set?
413 * @return boolean is the byteorder set?
415 public boolean byteOrderIsSet() {
416 return byteOrder
!= null;
420 * Method packetHeaderIsSet is the packet header set?
422 * @return boolean is the packet header set?
424 public boolean packetHeaderIsSet() {
425 return packetHeaderDecl
!= null;
429 * Method getUUID gets the trace UUID
431 * @return UUID gets the trace UUID
433 public UUID
getUUID() {
438 * Method getMajor gets the trace major version
440 * @return long gets the trace major version
442 public long getMajor() {
447 * Method getMinor gets the trace minor version
449 * @return long gets the trace minor version
451 public long getMinor() {
456 * Method getByteOrder gets the trace byte order
458 * @return ByteOrder gets the trace byte order
460 public ByteOrder
getByteOrder() {
465 * Method getPacketHeader gets the trace packet header
467 * @return StructDeclaration gets the trace packet header
469 public StructDeclaration
getPacketHeader() {
470 return packetHeaderDecl
;
474 * Method getTraceDirectory gets the trace directory
476 * @return File the path in "File" format.
478 public File
getTraceDirectory() {
483 * Method getStreams get all the streams in a map format.
485 * @return Map<Long,Stream> a map of all the streams.
487 public Map
<Long
, Stream
> getStreams() {
492 * Method getPath gets the path of the trace directory
494 * @return String the path of the trace directory, in string format.
495 * @see java.io.File#getPath()
498 public String
getPath() {
499 return path
.getPath();
502 // ------------------------------------------------------------------------
504 // ------------------------------------------------------------------------
507 * Tries to open the given file, reads the first packet header of the file
508 * and check its validity.
511 * A trace file in the trace directory.
513 * Which index in the class' streamFileChannel array this file
515 * @throws CTFReaderException
517 private void openStreamInput(File streamFile
) throws CTFReaderException
{
518 MappedByteBuffer byteBuffer
;
519 BitBuffer streamBitBuffer
;
523 if (!streamFile
.canRead()) {
524 throw new CTFReaderException("Unreadable file : " //$NON-NLS-1$
525 + streamFile
.getPath());
529 /* Open the file and get the FileChannel */
530 fc
= new FileInputStream(streamFile
).getChannel();
531 streamFileChannels
.add(fc
);
533 /* Map one memory page of 4 kiB */
534 byteBuffer
= fc
.map(MapMode
.READ_ONLY
, 0, 4096);
535 } catch (IOException e
) {
536 /* Shouldn't happen at this stage if every other check passed */
537 throw new CTFReaderException();
540 /* Create a BitBuffer with this mapping and the trace byte order */
541 streamBitBuffer
= new BitBuffer(byteBuffer
, this.getByteOrder());
543 if (packetHeaderDef
!= null) {
544 /* Read the packet header */
545 packetHeaderDef
.read(streamBitBuffer
);
547 /* Check the magic number */
548 IntegerDefinition magicDef
= (IntegerDefinition
) packetHeaderDef
549 .lookupDefinition("magic"); //$NON-NLS-1$
550 int magic
= (int) magicDef
.getValue();
551 if (magic
!= Utils
.CTF_MAGIC
) {
552 throw new CTFReaderException("CTF magic mismatch"); //$NON-NLS-1$
556 ArrayDefinition uuidDef
= (ArrayDefinition
) packetHeaderDef
557 .lookupDefinition("uuid"); //$NON-NLS-1$
558 if (uuidDef
!= null) {
559 byte[] uuidArray
= new byte[Utils
.UUID_LEN
];
561 for (int i
= 0; i
< Utils
.UUID_LEN
; i
++) {
562 IntegerDefinition uuidByteDef
= (IntegerDefinition
) uuidDef
564 uuidArray
[i
] = (byte) uuidByteDef
.getValue();
567 UUID otheruuid
= Utils
.makeUUID(uuidArray
);
569 if (!this.uuid
.equals(otheruuid
)) {
570 throw new CTFReaderException("UUID mismatch"); //$NON-NLS-1$
574 /* Read the stream ID */
575 Definition streamIDDef
= packetHeaderDef
.lookupDefinition("stream_id"); //$NON-NLS-1$
577 if (streamIDDef
instanceof IntegerDefinition
) { //this doubles as a null check
578 long streamID
= ((IntegerDefinition
) streamIDDef
).getValue();
579 stream
= streams
.get(streamID
);
581 /* No stream_id in the packet header */
582 stream
= streams
.get(null);
586 /* No packet header, we suppose there is only one stream */
587 stream
= streams
.get(null);
590 /* Create the stream input */
591 StreamInput streamInput
= new StreamInput(stream
, fc
, streamFile
);
593 /* Add a reference to the streamInput in the stream */
594 stream
.addInput(streamInput
);
598 * Looks up a definition from packet
603 * @see org.eclipse.linuxtools.ctf.core.event.types.IDefinitionScope#lookupDefinition(String)
606 public Definition
lookupDefinition(String lookupPath
) {
607 if (lookupPath
.equals("trace.packet.header")) { //$NON-NLS-1$
608 return packetHeaderDef
;
614 * Adds a new stream to the trace.
618 * @throws ParseException
619 * If there was some problem reading the metadata
621 public void addStream(Stream stream
) throws ParseException
{
624 * If there is already a stream without id (the null key), it must be
627 if (streams
.get(null) != null) {
628 throw new ParseException("Stream without id with multiple streams"); //$NON-NLS-1$
632 * If the stream we try to add has the null key, it must be the only
633 * one. Thus, if the streams container is not empty, it is not valid.
635 if ((stream
.getId() == null) && (streams
.size() != 0)) {
636 throw new ParseException("Stream without id with multiple streams"); //$NON-NLS-1$
639 /* If a stream with the same ID already exists, it is not valid. */
640 if (streams
.get(stream
.getId()) != null) {
641 throw new ParseException("Stream id already exists"); //$NON-NLS-1$
644 /* It should be ok now. */
645 streams
.put(stream
.getId(), stream
);
646 eventDecs
.put(stream
.getId(), new HashMap
<Long
,EventDeclaration
>());
650 * gets the Environment variables from the trace metadata (See CTF spec)
651 * @return the environment variables in a map form (key value)
653 public Map
<String
, String
> getEnvironment() {
658 * Look up a specific environment variable
659 * @param key the key to look for
660 * @return the value of the variable, can be null.
662 public String
lookupEnvironment(String key
) {
663 return environment
.get(key
);
667 * Add a variable to the environment variables
668 * @param varName the name of the variable
669 * @param varValue the value of the variable
671 public void addEnvironmentVar(String varName
, String varValue
) {
672 environment
.put(varName
, varValue
);
676 * Add a clock to the clock list
677 * @param nameValue the name of the clock (full name with scope)
678 * @param ctfClock the clock
680 public void addClock(String nameValue
, CTFClock ctfClock
) {
681 clocks
.put(nameValue
, ctfClock
);
685 * gets the clock with a specific name
686 * @param name the name of the clock.
689 public CTFClock
getClock(String name
) {
690 return clocks
.get(name
);
697 * gets the clock if there is only one. (this is 100% of the use cases as of
702 public final CTFClock
getClock() {
703 if (clocks
.size() == 1) {
704 singleClock
= clocks
.get(clocks
.keySet().iterator().next());
711 * gets the time offset of a clock with respect to UTC in nanoseconds
713 * @return the time offset of a clock with respect to UTC in nanoseconds
715 public final long getOffset() {
716 if (getClock() == null) {
719 return singleClock
.getClockOffset();
723 * gets the time offset of a clock with respect to UTC in nanoseconds
725 * @return the time offset of a clock with respect to UTC in nanoseconds
727 private final double getTimeScale() {
728 if (getClock() == null) {
731 return singleClock
.getClockScale();
735 * Does the trace need to time scale?
737 * @return if the trace is in ns or cycles.
739 private final boolean clockNeedsScale() {
740 if (getClock() == null) {
743 return singleClock
.isClockScaled();
747 * the inverse clock for returning to a scale.
749 * @return 1.0 / scale
751 private final double getInverseTimeScale() {
752 if (getClock() == null) {
755 return singleClock
.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();
795 * Does a given stream contain any events?
796 * @param id the stream ID
797 * @return true if the stream has events.
799 public boolean hasEvents(Long id
){
800 return eventDecs
.containsKey(id
);
804 * Add an event declaration map to the events map.
805 * @param id the id of a stream
806 * @return the hashmap containing events.
808 public HashMap
<Long
, EventDeclaration
> createEvents(Long id
){
809 HashMap
<Long
, EventDeclaration
> value
= eventDecs
.get(id
);
810 if( value
== null ) {
811 value
= new HashMap
<Long
, EventDeclaration
>();
812 eventDecs
.put(id
, value
);
821 * the event name of the callsite
823 * the name of the callsite function
825 * the ip of the callsite
827 * the filename of the callsite
829 * the line number of the callsite
831 public void addCallsite(String eventName
, String funcName
, long ip
,
832 String fileName
, long lineNumber
) {
833 final CTFCallsite cs
= new CTFCallsite(eventName
, funcName
, ip
,
834 fileName
, lineNumber
);
835 LinkedList
<CTFCallsite
> csl
= callsitesByName
.get(eventName
);
837 csl
= new LinkedList
<CTFCallsite
>();
838 callsitesByName
.put(eventName
, csl
);
841 ListIterator
<CTFCallsite
> iter
= csl
.listIterator();
843 for (; index
< csl
.size(); index
++) {
844 if (iter
.next().compareTo(cs
) < 0) {
851 callsitesByIP
.add(cs
);
855 * Gets the list of callsites associated to an event name. O(1)
859 * @return the callsite list can be empty
862 public List
<CTFCallsite
> getCallsiteCandidates(String eventName
) {
863 LinkedList
<CTFCallsite
> retVal
= callsitesByName
.get(eventName
);
864 if( retVal
== null ) {
865 retVal
= new LinkedList
<CTFCallsite
>();
871 * The I'm feeling lucky of getCallsiteCandidates O(1)
875 * @return the first callsite that has that event name, can be null
878 public CTFCallsite
getCallsite(String eventName
) {
879 return callsitesByName
.get(eventName
).getFirst();
883 * Gets a callsite from the instruction pointer O(log(n))
886 * the instruction pointer to lookup
887 * @return the callsite just before that IP in the list remember the IP is
888 * backwards on X86, can be null if no callsite is before the IP.
891 public CTFCallsite
getCallsite(long ip
) {
892 CTFCallsite cs
= new CTFCallsite(null, null, ip
, null, 0L);
893 return callsitesByIP
.ceiling(cs
);
897 * Gets a callsite using the event name and instruction pointer O(log(n))
900 * the name of the event
902 * the instruction pointer
903 * @return the closest matching callsite, can be null
905 public CTFCallsite
getCallsite(String eventName
, long ip
) {
906 final LinkedList
<CTFCallsite
> candidates
= callsitesByName
.get(eventName
);
907 final CTFCallsite dummyCs
= new CTFCallsite(null, null, ip
, null, -1);
908 final int pos
= Collections
.binarySearch(candidates
, dummyCs
)+1;
909 if( pos
>= candidates
.size()) {
912 return candidates
.get(pos
);
917 class MetadataFileFilter
implements FileFilter
{
920 public boolean accept(File pathname
) {
921 if (pathname
.isDirectory()) {
924 if (pathname
.isHidden()) {
927 if (pathname
.getName().equals("metadata")) { //$NON-NLS-1$
935 class MetadataComparator
implements Comparator
<File
>, Serializable
{
937 private static final long serialVersionUID
= 1L;
940 public int compare(File o1
, File o2
) {
941 return o1
.getName().compareTo(o2
.getName());