| /******************************************************************************* |
| * Copyright (c) 2011, 2014 Ericsson, Ecole Polytechnique de Montreal and others |
| * |
| * All rights reserved. This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: Matthew Khouzam - Initial API and implementation |
| * Contributors: Simon Marchi - Initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.ctf.core.trace; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.FileChannel; |
| import java.nio.channels.FileChannel.MapMode; |
| import java.nio.file.StandardOpenOption; |
| import java.util.UUID; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jdt.annotation.NonNullByDefault; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.tracecompass.ctf.core.CTFException; |
| import org.eclipse.tracecompass.ctf.core.event.io.BitBuffer; |
| import org.eclipse.tracecompass.ctf.core.event.scope.IDefinitionScope; |
| import org.eclipse.tracecompass.ctf.core.event.scope.ILexicalScope; |
| import org.eclipse.tracecompass.ctf.core.event.scope.LexicalScope; |
| import org.eclipse.tracecompass.ctf.core.event.types.AbstractArrayDefinition; |
| import org.eclipse.tracecompass.ctf.core.event.types.Definition; |
| import org.eclipse.tracecompass.ctf.core.event.types.IntegerDefinition; |
| import org.eclipse.tracecompass.ctf.core.event.types.StructDeclaration; |
| import org.eclipse.tracecompass.ctf.core.event.types.StructDefinition; |
| import org.eclipse.tracecompass.internal.ctf.core.Activator; |
| import org.eclipse.tracecompass.internal.ctf.core.SafeMappedByteBuffer; |
| import org.eclipse.tracecompass.internal.ctf.core.trace.StreamInputPacketIndex; |
| import org.eclipse.tracecompass.internal.ctf.core.trace.StreamInputPacketIndexEntry; |
| import org.eclipse.tracecompass.internal.ctf.core.utils.Utils; |
| |
| /** |
| * <b><u>StreamInput</u></b> |
| * <p> |
| * Represents a trace file that belongs to a certain stream. |
| */ |
| @NonNullByDefault |
| public class CTFStreamInput implements IDefinitionScope { |
| |
| // ------------------------------------------------------------------------ |
| // Attributes |
| // ------------------------------------------------------------------------ |
| |
| private static final int MAP_SIZE = 4096; |
| |
| /** |
| * The associated Stream |
| */ |
| private final ICTFStream fStream; |
| |
| /** |
| * Information on the file (used for debugging) |
| */ |
| private final File fFile; |
| |
| /** |
| * The file name |
| */ |
| private final String fFileName; |
| |
| /** |
| * The packet index of this input |
| */ |
| private final StreamInputPacketIndex fIndex; |
| |
| private long fTimestampEnd; |
| |
| /** |
| * Definition of trace packet header |
| */ |
| private final StructDeclaration fTracePacketHeaderDecl; |
| |
| /** |
| * Definition of trace stream packet context |
| */ |
| private final StructDeclaration fStreamPacketContextDecl; |
| |
| /** |
| * Total number of lost events in this stream |
| */ |
| private long fLostSoFar = 0; |
| |
| private boolean fUUIDMismatchWarning = false; |
| |
| // ------------------------------------------------------------------------ |
| // Constructors |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Constructs a StreamInput. |
| * |
| * @param stream |
| * The stream to which this StreamInput belongs to. |
| * @param file |
| * Information about the trace file (for debugging purposes). |
| * @since 2.0 |
| */ |
| public CTFStreamInput(ICTFStream stream, File file) { |
| fStream = stream; |
| fFile = file; |
| fFileName = fFile.getName(); |
| |
| fIndex = new StreamInputPacketIndex(); |
| /* |
| * Create the definitions we need to read the packet headers + contexts |
| */ |
| StructDeclaration packetHeader = getStream().getTrace().getPacketHeader(); |
| if (packetHeader != null) { |
| fTracePacketHeaderDecl = packetHeader; |
| } else { |
| fTracePacketHeaderDecl = new StructDeclaration(1); |
| } |
| StructDeclaration packetContextDecl = getStream().getPacketContextDecl(); |
| if (packetContextDecl != null) { |
| fStreamPacketContextDecl = packetContextDecl; |
| } else { |
| fStreamPacketContextDecl = new StructDeclaration(1); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Getters/Setters/Predicates |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Gets the stream the streamInput wrapper is wrapping |
| * |
| * @return the stream the streamInput wrapper is wrapping |
| * @since 2.0 |
| */ |
| public ICTFStream getStream() { |
| return fStream; |
| } |
| |
| /** |
| * The common streamInput Index |
| * |
| * @return the stream input Index |
| */ |
| StreamInputPacketIndex getIndex() { |
| return fIndex; |
| } |
| |
| /** |
| * Gets the filename of the streamInput file. |
| * |
| * @return the filename of the streaminput file. |
| */ |
| public String getFilename() { |
| return fFileName; |
| } |
| |
| /** |
| * Gets the last read timestamp of a stream. (this is not necessarily the |
| * last time in the stream.) |
| * |
| * @return the last read timestamp |
| */ |
| public long getTimestampEnd() { |
| return fTimestampEnd; |
| } |
| |
| /** |
| * Sets the last read timestamp of a stream. (this is not necessarily the |
| * last time in the stream.) |
| * |
| * @param timestampEnd |
| * the last read timestamp |
| */ |
| public void setTimestampEnd(long timestampEnd) { |
| fTimestampEnd = timestampEnd; |
| } |
| |
| /** |
| * Useless for streaminputs |
| */ |
| @Override |
| public LexicalScope getScopePath() { |
| return ILexicalScope.STREAM; |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Operations |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public @Nullable Definition lookupDefinition(@Nullable String lookupPath) { |
| /* TODO: lookup in different dynamic scopes is not supported yet. */ |
| return null; |
| } |
| |
| /** |
| * Create the index for this trace file. |
| */ |
| public void setupIndex() { |
| |
| /* |
| * The BitBuffer to extract data from the StreamInput |
| */ |
| BitBuffer bitBuffer = new BitBuffer(); |
| bitBuffer.setByteOrder(getStream().getTrace().getByteOrder()); |
| |
| } |
| |
| /** |
| * Adds the next packet header index entry to the index of a stream input. |
| * |
| * <strong>This method is slow and can corrupt data if not used |
| * properly</strong> |
| * |
| * @return true if there are more packets to add |
| * @throws CTFException |
| * If there was a problem reading the packed header |
| */ |
| public boolean addPacketHeaderIndex() throws CTFException { |
| long currentPosBits = 0L; |
| if (!fIndex.isEmpty()) { |
| ICTFPacketDescriptor pos = fIndex.lastElement(); |
| if (pos == null) { |
| throw new IllegalStateException("Index contains null packet entries"); //$NON-NLS-1$ |
| } |
| currentPosBits = pos.getOffsetBits() + pos.getPacketSizeBits(); |
| } |
| if (currentPosBits < getStreamSizeBits()) { |
| return fIndex.append(createPacketIndexEntry(currentPosBits)); |
| } |
| return false; |
| } |
| |
| private long getStreamSizeBits() { |
| return fFile.length() * Byte.SIZE; |
| } |
| |
| private ICTFPacketDescriptor createPacketIndexEntry(long dataOffsetbits) |
| throws CTFException { |
| |
| try (FileChannel fc = FileChannel.open(fFile.toPath(), StandardOpenOption.READ)) { |
| if (fc == null) { |
| throw new IOException("Failed to create FileChannel"); //$NON-NLS-1$ |
| } |
| BitBuffer bitBuffer = createBitBufferForPacketHeader(fc, dataOffsetbits); |
| /* |
| * Read the trace packet header if it exists. |
| */ |
| parseTracePacketHeader(bitBuffer); |
| |
| /* |
| * Read the stream packet context if it exists. |
| */ |
| long size = fc.size(); |
| ICTFPacketDescriptor packetIndex = parsePacketContext(dataOffsetbits, size, bitBuffer); |
| |
| /* Basic validation */ |
| if (packetIndex.getContentSizeBits() > packetIndex.getPacketSizeBits()) { |
| throw new CTFException("Content size (" + packetIndex.getContentSizeBits() + ") > packet size (" + packetIndex.getPacketSizeBits() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| |
| if (packetIndex.getPacketSizeBits() > ((size * Byte.SIZE - packetIndex.getOffsetBits()))) { |
| throw new CTFException("Not enough data remaining in the file for the size of this packet"); //$NON-NLS-1$ |
| } |
| return packetIndex; |
| } catch (IOException e) { |
| throw new CTFException("Failed to create packet index entry", e); //$NON-NLS-1$ |
| } catch (CTFException e) { |
| throw new CTFException("Cannot create packet entry at bit " + dataOffsetbits, e); //$NON-NLS-1$ |
| } |
| } |
| |
| private BitBuffer createBitBufferForPacketHeader(FileChannel fc, long dataOffsetbits) throws CTFException, IOException { |
| /* |
| * create a packet bit buffer to read the packet header |
| */ |
| int maximumSize = fStreamPacketContextDecl.getMaximumSize() + fTracePacketHeaderDecl.getMaximumSize(); |
| BitBuffer bitBuffer = new BitBuffer(createPacketBitBuffer(fc, dataOffsetbits/Byte.SIZE, maximumSize)); |
| bitBuffer.setByteOrder(getStream().getTrace().getByteOrder()); |
| return bitBuffer; |
| } |
| |
| private static ByteBuffer getByteBufferAt(FileChannel fc, long position, long size) throws CTFException, IOException { |
| return SafeMappedByteBuffer.map(fc, MapMode.READ_ONLY, position, size); |
| } |
| |
| private static ByteBuffer createPacketBitBuffer(FileChannel fc, |
| long packetOffsetBytes, long maxSize) throws CTFException, IOException { |
| /* |
| * If there is less data remaining than what we want to map, reduce the |
| * map size. |
| */ |
| long remain = fc.size() - packetOffsetBytes; |
| /* |
| * Initial size, it is the minimum of the the file size and the maximum |
| * possible size of the |
| */ |
| long mapSize = Math.min(remain, MAP_SIZE); |
| if (maxSize < mapSize) { |
| mapSize = maxSize; |
| } |
| |
| /* |
| * Map the packet. |
| */ |
| try { |
| return getByteBufferAt(fc, packetOffsetBytes, mapSize); |
| } catch (IllegalArgumentException | IOException e) { |
| throw new CTFException(e); |
| } |
| } |
| |
| private StructDefinition parseTracePacketHeader( |
| BitBuffer bitBuffer) throws CTFException { |
| |
| StructDefinition tracePacketHeaderDef = fTracePacketHeaderDecl.createDefinition(fStream.getTrace(), ILexicalScope.TRACE_PACKET_HEADER, bitBuffer); |
| |
| /* |
| * Check the CTF magic number |
| */ |
| IntegerDefinition magicDef = (IntegerDefinition) tracePacketHeaderDef |
| .lookupDefinition("magic"); //$NON-NLS-1$ |
| if (magicDef != null) { |
| int magic = (int) magicDef.getValue(); |
| if (magic != Utils.CTF_MAGIC) { |
| throw new CTFException( |
| "CTF magic mismatch " + Integer.toHexString(magic) + " vs " + Integer.toHexString(Utils.CTF_MAGIC)); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| } |
| |
| /* |
| * Check the trace UUID |
| */ |
| AbstractArrayDefinition uuidDef = |
| (AbstractArrayDefinition) tracePacketHeaderDef.lookupDefinition("uuid"); //$NON-NLS-1$ |
| if (uuidDef != null) { |
| UUID uuid = Utils.getUUIDfromDefinition(uuidDef); |
| |
| if (!getStream().getTrace().getUUID().equals(uuid) && !fUUIDMismatchWarning ) { |
| fUUIDMismatchWarning = true; |
| Activator.log(IStatus.WARNING, "Reading CTF trace: UUID mismatch for trace " + getStream().getTrace()); //$NON-NLS-1$ |
| } |
| } |
| |
| /* |
| * Check that the stream id did not change |
| */ |
| IntegerDefinition streamIDDef = (IntegerDefinition) tracePacketHeaderDef |
| .lookupDefinition("stream_id"); //$NON-NLS-1$ |
| if (streamIDDef != null) { |
| long streamID = streamIDDef.getValue(); |
| |
| if (streamID != getStream().getId()) { |
| throw new CTFException("Stream ID changing within a StreamInput"); //$NON-NLS-1$ |
| } |
| } |
| return tracePacketHeaderDef; |
| } |
| |
| private ICTFPacketDescriptor parsePacketContext(long dataOffsetBits, long fileSizeBytes, |
| BitBuffer bitBuffer) throws CTFException { |
| ICTFPacketDescriptor packetIndex; |
| StructDefinition streamPacketContextDef = fStreamPacketContextDecl.createDefinition(this, ILexicalScope.STREAM_PACKET_CONTEXT, bitBuffer); |
| packetIndex = new StreamInputPacketIndexEntry(dataOffsetBits, streamPacketContextDef, fileSizeBytes, fLostSoFar, bitBuffer.position()); |
| fLostSoFar = packetIndex.getLostEvents() + fLostSoFar; |
| setTimestampEnd(packetIndex.getTimestampEnd()); |
| return packetIndex; |
| } |
| |
| /** |
| * Get the file |
| * |
| * @return the file |
| * @since 1.0 |
| */ |
| public File getFile() { |
| return fFile; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = (prime * result) + fFile.hashCode(); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (!(obj instanceof CTFStreamInput)) { |
| return false; |
| } |
| CTFStreamInput other = (CTFStreamInput) obj; |
| return (fFile.equals(other.fFile)); |
| } |
| } |