blob: 1a169f4a2a7b99f4e7a5c1158e983ee52d7848cb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2018 Ericsson
*
* 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:
* Bernd Hufmann - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.ctf.core.trace;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.StandardOpenOption;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.ctf.core.CTFException;
import org.eclipse.tracecompass.ctf.core.CTFStrings;
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.Definition;
import org.eclipse.tracecompass.ctf.core.event.types.FloatDeclaration;
import org.eclipse.tracecompass.ctf.core.event.types.FloatDefinition;
import org.eclipse.tracecompass.ctf.core.event.types.ICompositeDefinition;
import org.eclipse.tracecompass.ctf.core.event.types.IDeclaration;
import org.eclipse.tracecompass.ctf.core.event.types.ISimpleDatatypeDeclaration;
import org.eclipse.tracecompass.ctf.core.event.types.SimpleDatatypeDefinition;
import org.eclipse.tracecompass.ctf.core.event.types.StringDefinition;
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.CTFPacketReader;
import org.eclipse.tracecompass.internal.ctf.core.trace.StreamInputPacketIndexEntry;
/**
* CTF trace packet writer.
*
* @author Bernd Hufmann
* @since 1.0
*/
public class CTFStreamPacketOutputWriter {
private CTFStreamInput fStreamInput;
/**
* Default constructor
*/
public CTFStreamPacketOutputWriter() {
throw new UnsupportedOperationException();
}
/**
* Writer output
*
* @param streamInput
* the reference input stream to copy
* @since 3.0
*/
public CTFStreamPacketOutputWriter(@NonNull CTFStreamInput streamInput) {
fStreamInput = streamInput;
}
/**
* Writes a stream packet to the output file channel based on the packet
* descriptor information.
*
* @param byteBuffer
* a byte buffer with packet to write
* @param output
* a file channel
* @throws IOException
* if a reading or writing error occurs
*/
public void writePacket(ByteBuffer byteBuffer, FileChannel output) throws IOException {
output.write(byteBuffer);
}
/**
* Writes a stream packet to the output file channel based on the packet
* descriptor information.
*
* @param entry
* the packet descriptor
* @param output
* the file channel to write to
* @param initialLost
* the offset initial lost events as the field is cumulative
*
* @throws IOException
* if a reading or writing error occurs
* @throws CTFException
* ctf reading error
* @since 3.0
*/
public void writePacket(ICTFPacketDescriptor entry, FileChannel output, long initialLost) throws IOException, CTFException {
long startOffsetBits = entry.getPayloadStartBits();
long endOffsetBits = entry.getContentSizeBits();
StructDefinition context = null;
File file = fStreamInput.getFile();
try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ);) {
ByteBuffer bb = SafeMappedByteBuffer.map(fc, FileChannel.MapMode.READ_ONLY, entry.getOffsetBytes(), (long) Math.ceil(entry.getContentSizeBits() / (double) Byte.SIZE));
BitBuffer bbInput = new BitBuffer(bb);
bbInput.position(entry.getPayloadStartBits());
ICTFStream stream = fStreamInput.getStream();
CTFTrace trace = stream.getTrace();
CTFPacketReader currentPacketReader = new CTFPacketReader(
bbInput,
entry,
stream.getEventDeclarations(),
stream.getEventHeaderDeclaration(),
stream.getEventContextDecl(),
trace.getPacketHeaderDef(),
trace);
if (entry instanceof StreamInputPacketIndexEntry) {
context = ((StreamInputPacketIndexEntry) entry).getStreamPacketContextDef();
}
if (context == null) {
return;
}
ICompositeDefinition tracePacketHeader = currentPacketReader.getTracePacketHeader();
writeCustomPacket(entry, fc, output, startOffsetBits, endOffsetBits, context, tracePacketHeader, entry.getTimestampBegin(), initialLost);
}
}
/**
* Make a packet and write it to the fileChannel
*
* @param entry
* the reference packet descriptor
* @param startTime
* startTime of the packet
* @param endTime
* endTime of the packet
* @param initialLost
* Initial quantity of lost events
* @param output
* the file channel to write to
* @throws IOException
* if a reading or writing error occurs
* @throws CTFException
* ctf reading error
* @since 3.0
*/
public void writePacket(ICTFPacketDescriptor entry, long startTime, long endTime, long initialLost, FileChannel output) throws IOException, CTFException {
if (entry.getTimestampBegin() > endTime || entry.getTimestampEnd() < startTime || startTime > endTime) {
throw new IllegalStateException();
}
long startOffsetBits = entry.getPayloadStartBits();
long endOffsetBits = entry.getContentSizeBits();
StructDefinition context = null;
File file = fStreamInput.getFile();
try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ);) {
ByteBuffer bb = SafeMappedByteBuffer.map(fc, FileChannel.MapMode.READ_ONLY, entry.getOffsetBytes(), bitsToBytes(entry.getContentSizeBits()));
BitBuffer bbInput = new BitBuffer(bb);
bbInput.position(entry.getPayloadStartBits());
ICTFStream stream = fStreamInput.getStream();
CTFTrace trace = stream.getTrace();
CTFPacketReader currentPacketReader = new CTFPacketReader(
bbInput,
entry,
stream.getEventDeclarations(),
stream.getEventHeaderDeclaration(),
stream.getEventContextDecl(),
trace.getPacketHeaderDef(),
trace);
long lastLargeTimestamp = entry.getTimestampBegin();
if (entry instanceof StreamInputPacketIndexEntry) {
context = ((StreamInputPacketIndexEntry) entry).getStreamPacketContextDef();
}
if (context == null) {
return;
}
boolean startIsSet = false;
while (currentPacketReader.hasMoreEvents()) {
IEventDefinition event = currentPacketReader.readNextEvent();
long timestampInCycles = event.getTimestamp();
if (!startIsSet) {
/*
* handle "P" and "I" framed timestamps. overwrite the
* timestamp if there is one before the trim
*/
ICompositeDefinition eventHeader = event.getEventHeader();
Definition def = null;
if (eventHeader != null) {
def = eventHeader.getDefinition(CTFStrings.TIMESTAMP);
}
if (def == null && eventHeader instanceof StructDefinition) {
StructDefinition structDefinition = (StructDefinition) eventHeader;
def = structDefinition.lookupDefinition(CTFStrings.TIMESTAMP);
}
Definition definition = ((StreamInputPacketIndexEntry) entry).getStreamPacketContextDef().getDefinition(CTFStrings.TIMESTAMP_BEGIN);
if (definition != null && def != null && def.size() == definition.size()) {
lastLargeTimestamp = timestampInCycles;
}
}
if (timestampInCycles >= startTime && !startIsSet) {
startOffsetBits = currentPacketReader.getLocation();
startIsSet = true;
}
if (timestampInCycles > endTime + 1) {
endOffsetBits = currentPacketReader.getLocation();
break;
}
}
if (startOffsetBits >= endOffsetBits) {
return;
}
ICompositeDefinition tracePacketHeader = currentPacketReader.getTracePacketHeader();
writeCustomPacket(entry, fc, output, startOffsetBits, endOffsetBits, context, tracePacketHeader, lastLargeTimestamp, initialLost);
}
}
private static void writeCustomPacket(ICTFPacketDescriptor entry, FileChannel source, FileChannel output, long startOffsetBits, long endOffsetBits, StructDefinition packetContext, ICompositeDefinition tracePacketHeader, long startTime,
long initialLost)
throws IOException, CTFException {
ByteBuffer inBuffer = SafeMappedByteBuffer.map(source, MapMode.READ_ONLY, entry.getOffsetBytes(), bitsToBytes(entry.getContentSizeBits()));
int headerSizeBits = (int) (tracePacketHeader == null ? 0 : tracePacketHeader.size());
int packetSizeBits = (int) (headerSizeBits + packetContext.size() + endOffsetBits - startOffsetBits);
int alignmentSize = Byte.SIZE;
int lastBitsOfBody = (int) (endOffsetBits % alignmentSize);
int contentSizeBits = (lastBitsOfBody != 0) ? (packetSizeBits - alignmentSize + lastBitsOfBody) : packetSizeBits;
byte[] toWrite = new byte[(int) bitsToBytes(packetSizeBits)];
ByteBuffer buffer = ByteBuffer.wrap(toWrite);
byte[] header = new byte[(int) bitsToBytes(headerSizeBits)];
byte[] body = new byte[(int) bitsToBytes(endOffsetBits - startOffsetBits)];
inBuffer.get(header);
inBuffer.position((int) bitsToBytes(startOffsetBits));
inBuffer.get(body);
buffer.put(header);
writeContext(startTime, packetContext, contentSizeBits, (int) bitsToBytes(packetSizeBits) * Byte.SIZE, initialLost, buffer);
buffer.position((int) bitsToBytes(headerSizeBits + packetContext.size()));
buffer.put(body);
output.write(ByteBuffer.wrap(toWrite));
}
private static long bitsToBytes(long bits) {
return (long) Math.ceil(bits / (double) Byte.SIZE);
}
private static void writeContext(long startTime, StructDefinition context, int newContentSize, int newPacketSize, long initialLost, @NonNull ByteBuffer buffer) throws CTFException {
BitBuffer bb = new BitBuffer(buffer);
bb.position(buffer.position() * Byte.SIZE);
for (String field : context.getFieldNames()) {
Definition def = context.getDefinition(field);
IDeclaration declaration = def.getDeclaration();
align(declaration.getAlignment(), bb);
if (declaration instanceof ISimpleDatatypeDeclaration) {
bb.setByteOrder(((ISimpleDatatypeDeclaration) declaration).getByteOrder());
}
if (def instanceof SimpleDatatypeDefinition) {
SimpleDatatypeDefinition simpleDef = (SimpleDatatypeDefinition) def;
int size = (int) simpleDef.size();
if (field.equals(CTFStrings.PACKET_SIZE)) {
bb.putLong(size, newPacketSize);
} else if (field.equals(CTFStrings.CONTENT_SIZE)) {
bb.putLong(size, newContentSize);
} else if (field.equals(CTFStrings.TIMESTAMP_BEGIN)) {
bb.putLong(size, startTime);
} else if (field.equals(CTFStrings.EVENTS_DISCARDED)) {
bb.putLong(size, simpleDef.getIntegerValue() - initialLost);
} else {
bb.putLong(size, simpleDef.getIntegerValue());
}
} else if (def instanceof StringDefinition) {
writeString(bb, def);
} else if (def instanceof FloatDefinition) {
writeFloat(bb, def);
} else {
buffer.put(new byte[(int) bitsToBytes(def.size())]);
bb.position(buffer.position() * 8L);
}
}
buffer.position((int) bitsToBytes(bb.position()));
}
private static void align(long align, BitBuffer input) throws CTFException {
long mask = align - 1;
/*
* The alignment is a power of 2
*/
long pos = input.position();
if ((pos & mask) == 0) {
return;
}
pos = (pos + mask) & ~mask;
input.position(pos);
}
private static void writeFloat(BitBuffer bb, Definition def) throws CTFException {
FloatDefinition floatDefinition = (FloatDefinition) def;
double value = floatDefinition.getValue();
FloatDeclaration declaration = floatDefinition.getDeclaration();
int exponent = declaration.getExponent();
int mantissa = declaration.getMantissa();
if (mantissa == 23 && exponent == 8) {
bb.putInt(Float.SIZE, Float.floatToIntBits((float) value));
} else if (mantissa == 52 && exponent == 11) {
bb.putLong(Double.SIZE, Double.doubleToLongBits(value));
} else {
// Inaccurate, but won't break the trace
bb.putLong((int) floatDefinition.size(), Double.doubleToRawLongBits(value));
}
}
private static void writeString(BitBuffer bb, Definition def) throws CTFException {
StringDefinition stringDefinition = (StringDefinition) def;
// may be encoded differently, but play it safe first.
byte[] bytes = stringDefinition.toString().getBytes();
try {
bytes = stringDefinition.toString().getBytes(stringDefinition.getDeclaration().getEncoding().toString());
} catch (UnsupportedEncodingException e) {
Activator.log("Writing packet made an encoding error! " + e.getMessage()); //$NON-NLS-1$
}
bb.put(bytes);
bb.put((byte) 0);
}
}