| /******************************************************************************* |
| * Copyright (c) 2011, 2015 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.List; |
| |
| import org.eclipse.jdt.annotation.NonNullByDefault; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.tracecompass.common.core.NonNullUtils; |
| import org.eclipse.tracecompass.ctf.core.CTFException; |
| import org.eclipse.tracecompass.ctf.core.event.IEventDeclaration; |
| import org.eclipse.tracecompass.ctf.core.event.IEventDefinition; |
| import org.eclipse.tracecompass.ctf.core.event.io.BitBuffer; |
| import org.eclipse.tracecompass.ctf.core.event.types.IDeclaration; |
| import org.eclipse.tracecompass.ctf.core.event.types.StructDeclaration; |
| import org.eclipse.tracecompass.internal.ctf.core.Activator; |
| import org.eclipse.tracecompass.internal.ctf.core.SafeMappedByteBuffer; |
| import org.eclipse.tracecompass.internal.ctf.core.trace.CTFPacketReader; |
| import org.eclipse.tracecompass.internal.ctf.core.trace.NullPacketReader; |
| |
| /** |
| * A CTF trace event reader. Reads the events of a trace file. |
| * |
| * @author Matthew Khouzam |
| * @author Simon Marchi |
| * @since 2.0 |
| */ |
| @NonNullByDefault |
| public class CTFStreamInputReader implements AutoCloseable { |
| |
| private static final int BITS_PER_BYTE = Byte.SIZE; |
| |
| // ------------------------------------------------------------------------ |
| // Attributes |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * The StreamInput we are reading. |
| */ |
| private final File fFile; |
| |
| private final CTFStreamInput fStreamInput; |
| |
| private final @Nullable FileChannel fFileChannel; |
| |
| /** |
| * The packet reader used to read packets from this trace file. |
| */ |
| private IPacketReader fPacketReader; |
| |
| /** |
| * Iterator on the packet index |
| */ |
| private int fPacketIndex; |
| |
| /** |
| * Reference to the current event of this trace file (iow, the last on that |
| * was read, the next one to be returned) |
| */ |
| private @Nullable IEventDefinition fCurrentEvent = null; |
| |
| private int fId; |
| |
| /** |
| * Live trace reading |
| */ |
| private boolean fLive = false; |
| |
| // ------------------------------------------------------------------------ |
| // Constructors |
| // ------------------------------------------------------------------------ |
| /** |
| * Constructs a StreamInputReader that reads a StreamInput. |
| * |
| * @param streamInput |
| * The StreamInput to read. |
| * @throws CTFException |
| * If the file cannot be opened |
| */ |
| public CTFStreamInputReader(CTFStreamInput streamInput) throws CTFException { |
| fStreamInput = streamInput; |
| fFile = fStreamInput.getFile(); |
| try { |
| fFileChannel = FileChannel.open(fFile.toPath(), StandardOpenOption.READ); |
| } catch (IOException e) { |
| throw new CTFIOException(e); |
| } |
| try { |
| /* |
| * Get the iterator on the packet index. |
| */ |
| fPacketIndex = 0; |
| /* |
| * Make first packet the current one. |
| */ |
| // did we already index the packet? |
| if (getPacketSize() < (fPacketIndex + 1)) { |
| // go to the next packet if there is one, index it at the same |
| // time |
| if (fStreamInput.addPacketHeaderIndex()) { |
| fPacketIndex = getPacketSize() - 1; |
| } |
| } |
| ICTFPacketDescriptor packet = getPacket(); |
| fPacketReader = getCurrentPacketReader(packet); |
| } catch (Exception e) { |
| try { |
| close(); |
| } catch (IOException e1) { |
| // Ignore |
| } |
| throw e; |
| } |
| } |
| |
| private IPacketReader getCurrentPacketReader(@Nullable ICTFPacketDescriptor packet) throws CTFException { |
| IPacketReader ctfPacketReader = NullPacketReader.INSTANCE; |
| if (packet != null) { |
| long size = packet.getContentSizeBits(); |
| if (size < 0) { |
| throw new CTFIOException("Cannot have negative sized buffers."); //$NON-NLS-1$ |
| } |
| BitBuffer bitBuffer = new BitBuffer(getByteBufferAt(packet.getOffsetBits(), size)); |
| bitBuffer.position(packet.getPayloadStartBits()); |
| IDeclaration eventHeaderDeclaration = getStreamInput().getStream().getEventHeaderDeclaration(); |
| CTFTrace trace = getStreamInput().getStream().getTrace(); |
| ctfPacketReader = new CTFPacketReader(bitBuffer, packet, getEventDeclarations(), eventHeaderDeclaration, getStreamEventContextDecl(), trace.getPacketHeaderDef(), trace); |
| } |
| return ctfPacketReader; |
| } |
| |
| /** |
| * Get a bytebuffer map of the file |
| * |
| * @param position |
| * start offset in bits |
| * @param size |
| * size of the map in bits, use caution |
| * @return a byte buffer |
| * @throws CTFException |
| * if the map failed in its allocation |
| * |
| * @since 2.0 |
| */ |
| public ByteBuffer getByteBufferAt(long position, long size) throws CTFException { |
| try { |
| return SafeMappedByteBuffer.map(fFileChannel, MapMode.READ_ONLY, position / BITS_PER_BYTE, (size + BITS_PER_BYTE - 1) / BITS_PER_BYTE); |
| } catch (IOException e) { |
| throw new CTFIOException(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Dispose the StreamInputReader, closes the file channel and its packet |
| * reader |
| * |
| * @throws IOException |
| * If an I/O error occurs |
| */ |
| @Override |
| public void close() throws IOException { |
| if (fFileChannel != null) { |
| fFileChannel.close(); |
| } |
| fPacketReader = NullPacketReader.INSTANCE; |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Getters/Setters/Predicates |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Gets the current event in this stream |
| * |
| * @return the current event in the stream, null if the stream is |
| * finished/empty/malformed |
| * @since 2.0 |
| */ |
| public @Nullable IEventDefinition getCurrentEvent() { |
| return fCurrentEvent; |
| } |
| |
| /** |
| * Gets the name of the stream (it's an id and a number) |
| * |
| * @return gets the stream name (it's a number) |
| */ |
| public int getName() { |
| return fId; |
| } |
| |
| /** |
| * Sets the name of the stream |
| * |
| * @param name |
| * the name of the stream, (it's a number) |
| */ |
| public void setName(int name) { |
| fId = name; |
| } |
| |
| /** |
| * Gets the CPU of a stream. It's the same as the one in /proc or running |
| * the asm CPUID instruction |
| * |
| * @return The CPU id (a number) |
| */ |
| public int getCPU() { |
| return fPacketReader.getCPU(); |
| } |
| |
| /** |
| * Gets the filename of the stream being read |
| * |
| * @return The filename of the stream being read |
| */ |
| public String getFilename() { |
| return fStreamInput.getFilename(); |
| } |
| |
| /* |
| * for internal use only |
| */ |
| CTFStreamInput getStreamInput() { |
| return fStreamInput; |
| } |
| |
| /** |
| * Gets the event definition set for this StreamInput |
| * |
| * @return Unmodifiable set with the event definitions |
| * @since 2.0 |
| */ |
| public List<@Nullable IEventDeclaration> getEventDeclarations() { |
| return fStreamInput.getStream().getEventDeclarations(); |
| } |
| |
| /** |
| * Set the trace to live mode |
| * |
| * @param live |
| * whether the trace is read live or not |
| */ |
| public void setLive(boolean live) { |
| fLive = live; |
| } |
| |
| /** |
| * Get if the trace is to read live or not |
| * |
| * @return whether the trace is live or not |
| */ |
| public boolean isLive() { |
| return fLive; |
| } |
| |
| /** |
| * Get the event context of the stream |
| * |
| * @return the event context declaration of the stream |
| */ |
| public @Nullable StructDeclaration getStreamEventContextDecl() { |
| return getStreamInput().getStream().getEventContextDecl(); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Operations |
| // ------------------------------------------------------------------------ |
| /** |
| * Reads the next event in the current event variable. |
| * |
| * @return If an event has been successfully read. |
| * @throws CTFException |
| * if an error occurs |
| */ |
| public CTFResponse readNextEvent() throws CTFException { |
| |
| try { |
| /* |
| * Change packet if needed |
| */ |
| while (!fPacketReader.hasMoreEvents()) { |
| final ICTFPacketDescriptor prevPacket = fPacketReader.getCurrentPacket(); |
| if (prevPacket == null) { |
| if (fLive) { |
| goToNextPacket(); |
| } |
| break; |
| } |
| goToNextPacket(); |
| } |
| |
| /* |
| * If an event is available, read it. |
| */ |
| if (fPacketReader.hasMoreEvents()) { |
| setCurrentEvent(fPacketReader.readNextEvent()); |
| return CTFResponse.OK; |
| } |
| this.setCurrentEvent(null); |
| return fLive ? CTFResponse.WAIT : CTFResponse.FINISH; |
| } catch (CTFException e) { |
| throw new CTFException("Trace read error " + fStreamInput.getFilename(), e); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Change the current packet of the packet reader to the next one. |
| * |
| * @throws CTFException |
| * if an error occurs |
| */ |
| private void goToNextPacket() throws CTFException { |
| fPacketIndex++; |
| // did we already index the packet? |
| while (getPacketSize() < (fPacketIndex + 1)) { |
| // go to the next packet if there is one, index it at the same time |
| if (fStreamInput.addPacketHeaderIndex()) { |
| fPacketIndex = getPacketSize() - 1; |
| } else { |
| fPacketReader = NullPacketReader.INSTANCE; |
| return; |
| } |
| |
| } |
| ICTFPacketDescriptor packet = getPacket(); |
| fPacketReader = getCurrentPacketReader(packet); |
| } |
| |
| /** |
| * @return |
| */ |
| private int getPacketSize() { |
| return fStreamInput.getIndex().size(); |
| } |
| |
| /** |
| * Changes the location of the trace file reader so that the current event |
| * is the first event with a timestamp greater or equal the given timestamp. |
| * |
| * @param timestamp |
| * The timestamp to seek to. |
| * @return The offset compared to the current position |
| * @throws CTFException |
| * if an error occurs |
| */ |
| public long seek(long timestamp) throws CTFException { |
| long offset = 0; |
| |
| gotoPacket(timestamp); |
| |
| /* |
| * index up to the desired timestamp. |
| */ |
| while ((fPacketReader.getCurrentPacket() != null) |
| && (fPacketReader.getCurrentPacket().getTimestampEnd() < timestamp)) { |
| try { |
| fStreamInput.addPacketHeaderIndex(); |
| goToNextPacket(); |
| } catch (CTFException e) { |
| // do nothing here |
| Activator.log(e.getMessage()); |
| } |
| } |
| if (fPacketReader.getCurrentPacket() == null) { |
| gotoPacket(timestamp); |
| } |
| |
| /* |
| * Advance until either of these conditions are met: |
| * |
| * - reached the end of the trace file (the given timestamp is after the |
| * last event) |
| * |
| * - found the first event with a timestamp greater or equal the given |
| * timestamp. |
| */ |
| readNextEvent(); |
| IEventDefinition currentEvent = getCurrentEvent(); |
| while (currentEvent != null && (currentEvent.getTimestamp() < timestamp)) { |
| readNextEvent(); |
| currentEvent = getCurrentEvent(); |
| offset++; |
| } |
| return offset; |
| } |
| |
| /** |
| * @param timestamp |
| * the time to seek |
| * @throws CTFException |
| * if an error occurs |
| */ |
| private void gotoPacket(long timestamp) throws CTFException { |
| fPacketIndex = fStreamInput.getIndex().search(timestamp) - 1; |
| /* |
| * Switch to this packet. |
| */ |
| goToNextPacket(); |
| } |
| |
| /** |
| * Seeks the last event of a stream and returns it. |
| * |
| * @throws CTFException |
| * if an error occurs |
| */ |
| public void goToLastEvent() throws CTFException { |
| |
| /* |
| * Go to the beginning of the trace |
| */ |
| seek(0); |
| |
| /* |
| * Check that there is at least one event |
| */ |
| if ((fStreamInput.getIndex().isEmpty()) || (!fPacketReader.hasMoreEvents())) { |
| /* |
| * This means the trace is empty. abort. |
| */ |
| return; |
| } |
| |
| fPacketIndex = fStreamInput.getIndex().size() - 1; |
| /* |
| * Go to last indexed packet |
| */ |
| fPacketReader = getCurrentPacketReader(getPacket()); |
| |
| /* |
| * Keep going until you cannot |
| */ |
| while (fPacketReader.getCurrentPacket() != null) { |
| goToNextPacket(); |
| } |
| final int lastPacketIndex = fStreamInput.getIndex().size() - 1; |
| |
| /* |
| * Go to the last packet that contains events. |
| */ |
| for (int pos = lastPacketIndex; pos >= 0; pos--) { |
| fPacketIndex = pos; |
| fPacketReader = getCurrentPacketReader(getPacket()); |
| |
| if (fPacketReader.hasMoreEvents()) { |
| break; |
| } |
| } |
| |
| /* |
| * Go until the end of that packet |
| */ |
| IEventDefinition prevEvent = null; |
| while (fCurrentEvent != null) { |
| prevEvent = fCurrentEvent; |
| readNextEvent(); |
| } |
| /* |
| * Go back to the previous event |
| */ |
| setCurrentEvent(prevEvent); |
| } |
| |
| /** |
| * Sets the current event in a stream input reader |
| * |
| * @param currentEvent |
| * the event to set |
| * @since 2.0 |
| */ |
| public void setCurrentEvent(@Nullable IEventDefinition currentEvent) { |
| fCurrentEvent = currentEvent; |
| } |
| |
| /** |
| * @return the packetIndexIt |
| */ |
| private int getPacketIndex() { |
| return fPacketIndex; |
| } |
| |
| private @Nullable ICTFPacketDescriptor getPacket() { |
| int packetIndex = getPacketIndex(); |
| if (packetIndex >= fStreamInput.getIndex().size()) { |
| return null; |
| } |
| return fStreamInput.getIndex().getElement(packetIndex); |
| } |
| |
| /** |
| * Get the current packet reader |
| * |
| * @return the packetReader |
| * @since 2.0 |
| */ |
| public IPacketReader getCurrentPacketReader() { |
| return fPacketReader; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = (prime * result) + fId; |
| 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 CTFStreamInputReader)) { |
| return false; |
| } |
| CTFStreamInputReader other = (CTFStreamInputReader) obj; |
| if (fId != other.fId) { |
| return false; |
| } |
| return fFile.equals(other.fFile); |
| } |
| |
| @Override |
| public String toString() { |
| // this helps debugging |
| return fId + ' ' + NonNullUtils.nullToEmptyString(fCurrentEvent); |
| } |
| |
| } |