blob: 22369ab13773fbe36170ecd015ae9facaad969f8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2017 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:
* Matthew Khouzam - Initial API and implementation
* Marc-Andre Laperle - Move generation to traces folder
*******************************************************************************/
package org.eclipse.tracecompass.ctf.core.tests.shared;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.eclipse.tracecompass.ctf.core.tests.CtfCoreTestPlugin;
/**
* Generate a lttng trace (kernel or ust)
*
* @author Matthew Khouzam
*/
public class LttngTraceGenerator {
private static final String metadataKernel = "/* CTF 1.8 */ \n" +
"typealias integer { size = 8; align = 8; signed = false; } := uint8_t;\n" +
"typealias integer { size = 16; align = 8; signed = false; } := uint16_t;\n" +
"typealias integer { size = 32; align = 8; signed = false; } := uint32_t;\n" +
"typealias integer { size = 64; align = 8; signed = false; } := uint64_t;\n" +
"typealias integer { size = 32; align = 8; signed = false; } := unsigned long;\n" +
"typealias integer { size = 5; align = 1; signed = false; } := uint5_t;\n" +
"typealias integer { size = 27; align = 1; signed = false; } := uint27_t;\n" +
"\n" +
"trace {\n" +
" major = 1;\n" +
" minor = 8;\n" +
" uuid = \"11111111-1111-1111-1111-111111111111\";\n" +
" byte_order = le;\n" +
" packet.header := struct {\n" +
" uint32_t magic;\n" +
" uint8_t uuid[16];\n" +
" uint32_t stream_id;\n" +
" };\n" +
"};\n" +
"\n" +
"env {\n" +
" hostname = \"synthetic-host\";\n" +
" domain = \"kernel\";\n" +
" sysname = \"FakeLinux\";\n" +
" kernel_release = \"1.0\";\n" +
" kernel_version = \"Fake Os Synthetic Trace\";\n" +
" tracer_name = \"lttng-modules\";\n" +
" tracer_major = 2;\n" +
" tracer_minor = 1;\n" +
" tracer_patchlevel = 0;\n" +
"};\n" +
"\n" +
"clock {\n" +
" name = monotonic;\n" +
" uuid = \"bbff68f0-c633-4ea1-92cd-bd11024ec4de\";\n" +
" description = \"Monotonic Clock\";\n" +
" freq = 1000000000; /* Frequency, in Hz */\n" +
" /* clock value offset from Epoch is: offset * (1/freq) */\n" +
" offset = 1368000272650993664;\n" +
"};\n" +
"\n" +
"typealias integer {\n" +
" size = 27; align = 1; signed = false;\n" +
" map = clock.monotonic.value;\n" +
"} := uint27_clock_monotonic_t;\n" +
"\n" +
"typealias integer {\n" +
" size = 32; align = 8; signed = false;\n" +
" map = clock.monotonic.value;\n" +
"} := uint32_clock_monotonic_t;\n" +
"\n" +
"typealias integer {\n" +
" size = 64; align = 8; signed = false;\n" +
" map = clock.monotonic.value;\n" +
"} := uint64_clock_monotonic_t;\n" +
"\n" +
"struct packet_context {\n" +
" uint64_clock_monotonic_t timestamp_begin;\n" +
" uint64_clock_monotonic_t timestamp_end;\n" +
" uint64_t content_size;\n" +
" uint64_t packet_size;\n" +
" unsigned long events_discarded;\n" +
" uint32_t cpu_id;\n" +
"};\n" +
"\n" +
"struct event_header_compact {\n" +
" enum : uint5_t { compact = 0 ... 30, extended = 31 } id;\n" +
" variant <id> {\n" +
" struct {\n" +
" uint27_clock_monotonic_t timestamp;\n" +
" } compact;\n" +
" struct {\n" +
" uint32_t id;\n" +
" uint64_clock_monotonic_t timestamp;\n" +
" } extended;\n" +
" } v;\n" +
"} align(8);\n" +
"\n" +
"struct event_header_large {\n" +
" enum : uint16_t { compact = 0 ... 65534, extended = 65535 } id;\n" +
" variant <id> {\n" +
" struct {\n" +
" uint32_clock_monotonic_t timestamp;\n" +
" } compact;\n" +
" struct {\n" +
" uint32_t id;\n" +
" uint64_clock_monotonic_t timestamp;\n" +
" } extended;\n" +
" } v;\n" +
"} align(8);\n" +
"\n" +
"stream {\n" +
" id = 0;\n" +
" event.header := struct event_header_compact;\n" +
" packet.context := struct packet_context;\n" +
"};\n" +
"\n" +
"event {\n" +
" name = sched_switch;\n" +
" id = 0;\n" +
" stream_id = 0;\n" +
" fields := struct {\n" +
" integer { size = 8; align = 8; signed = 1; encoding = UTF8; base = 10; } _prev_comm[16];\n" +
" integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _prev_tid;\n" +
" integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _prev_prio;\n" +
" integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _prev_state;\n" +
" integer { size = 8; align = 8; signed = 1; encoding = UTF8; base = 10; } _next_comm[16];\n" +
" integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _next_tid;\n" +
" integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _next_prio;\n" +
" };\n" +
"};\n" +
"\n";
private final List<String> fProcesses;
private final long fDuration;
private final long fNbEvents;
private final int fNbChans;
private final String metadata;
private static final String[] sfProcesses = {
"IDLE",
"gnuplot",
"starcraft 2:pt3",
"bash",
"smash",
"thrash",
"fireball",
"Half-life 3",
"ST: The game"
};
private static final String TRACES_DIRECTORY = "traces";
private static final String TRACE_NAME = "synthetic-trace";
/**
* Main, not always needed
*
* @param args
* args
*/
public static void main(String[] args) {
// not using createTempFile as this is a directory
String path = CtfCoreTestPlugin.getTemporaryDirPath() + File.separator + TRACE_NAME;
generateLttngTrace(new File(path));
}
/**
* Gets the name of the trace (top directory name)
*
* @return the name of the trace
*/
public static String getName() {
return TRACE_NAME;
}
/**
* Get the path
*
* @return the path
*/
public static String getPath() {
CtfCoreTestPlugin plugin = CtfCoreTestPlugin.getDefault();
if (plugin == null) {
return null;
}
Path tracePath = Paths.get("..", "..", "ctf", "org.eclipse.tracecompass.ctf.core.tests", TRACES_DIRECTORY, TRACE_NAME);
tracePath = tracePath.toAbsolutePath();
File file = tracePath.toFile();
generateLttngTrace(file);
return file.getAbsolutePath();
}
/**
* Generate a trace
*
* @param file
* the file to write the trace to
*/
public static void generateLttngTrace(File file) {
final int cpus = 25;
LttngTraceGenerator gt = new LttngTraceGenerator(Integer.MAX_VALUE / 12L, 250000 / 12L, cpus);
gt.writeTrace(file);
}
/**
* Make a lttng trace
*
* @param duration
* the duration of the trace
* @param events
* the number of events in a trace
* @param nbChannels
* the number of channels in the trace
*/
public LttngTraceGenerator(long duration, long events, int nbChannels) {
this(duration, events, nbChannels, true);
}
/**
* Make a lttng trace
*
* @param duration
* the duration of the trace
* @param events
* the number of events in a trace
* @param nbChannels
* the number of channels in the trace
* @param isKernel
* true for kernel, false for ust
*/
public LttngTraceGenerator(long duration, long events, int nbChannels, boolean isKernel) {
fProcesses = Arrays.asList(sfProcesses);
fDuration = duration;
fNbEvents = events;
fNbChans = nbChannels;
metadata = isKernel ? metadataKernel : getMetadataUST();
}
/**
* Write the trace to a file
*
* @param file
* the file to write the trace to
*/
public void writeTrace(File file) {
if (file.exists()) {
deleteDirectory(file);
}
file.mkdir();
File metadataFile = new File(file.getPath() + File.separator + "metadata");
File[] streams = new File[fNbChans];
FileChannel[] channels = new FileChannel[fNbChans];
try {
for (int i = 0; i < fNbChans; i++) {
streams[i] = new File(file.getPath() + File.separator + "channel" + i);
channels[i] = new FileOutputStream(streams[i]).getChannel();
}
} catch (FileNotFoundException e) {
}
// determine the number of events per channel
long evPerChan = fNbEvents / fNbChans;
final int evPerPacket = PacketWriter.CONTENT_SIZE / EventWriter.SIZE;
long delta = (int) (fDuration / evPerChan);
long offsetTime = 0;
Random rndLost = new Random(1337);
for (int chan = 0; chan < fNbChans; chan++) {
int currentSpace = 0;
ByteBuffer bb = ByteBuffer.allocate(65536);
bb.order(ByteOrder.LITTLE_ENDIAN);
Random rnd = new Random(1337);
int rnd0 = rnd.nextInt(fProcesses.size());
String prevComm = fProcesses.get(rnd0);
int prevPID = rnd0 + chan * fProcesses.size();
if (rnd0 == 0) {
prevPID = 0;
}
int prevPrio = 0;
int prevPos = -1;
int discarded = 0;
int discardedTotal = 0;
for (int eventNb = 0; eventNb < evPerChan; eventNb++) {
if (EventWriter.SIZE > currentSpace) {
eventNb += discarded;
}
long ts = eventNb * delta + delta / (fNbChans + 1) * chan;
int pos = rnd.nextInt((int) (fProcesses.size() * 1.5));
if (pos >= fProcesses.size()) {
pos = 0;
}
while (pos == prevPos) {
pos = rnd.nextInt((int) (fProcesses.size() * 1.5));
if (pos >= fProcesses.size()) {
pos = 0;
}
}
String nextComm = fProcesses.get(pos);
int nextPID = pos + fProcesses.size() * chan;
if (pos == 0) {
nextPID = 0;
}
int nextPrio = 0;
if (EventWriter.SIZE > currentSpace) {
// pad to end
for (int i = 0; i < currentSpace; i++) {
bb.put((byte) 0x00);
}
// write new packet
PacketWriter pw = new PacketWriter(bb);
long tsBegin = ts;
offsetTime = ts;
int eventCount = Math.min(evPerPacket, (int) evPerChan - eventNb);
discarded = rndLost.nextInt(10 * fNbChans) == 0 ? rndLost.nextInt(evPerPacket) : 0;
discarded = Math.min(discarded, (int) evPerChan - eventNb - eventCount);
discardedTotal += discarded;
long tsEnd = (eventNb + eventCount + discarded) * delta;
pw.writeNewHeader(tsBegin, tsEnd, chan, eventCount, discardedTotal);
currentSpace = PacketWriter.CONTENT_SIZE;
}
EventWriter ew = new EventWriter(bb);
int prev_state = rnd.nextInt(100);
if (prev_state != 0) {
prev_state = 1;
}
final long shrunkenTimestamp = ts - offsetTime;
final int tsMask = (1 << 27) - 1;
if (shrunkenTimestamp > ((1 << 27) + tsMask)) {
/* allow only one compact timestamp overflow per packet */
throw new IllegalStateException("Invalid timestamp overflow:" + shrunkenTimestamp);
}
final int clampedTs = (int) (ts & tsMask);
int evSize = ew.writeEvent(clampedTs, prevComm, prevPID, prevPrio, prev_state, nextComm, nextPID, nextPrio);
currentSpace -= evSize;
prevComm = nextComm;
prevPID = nextPID;
prevPrio = nextPrio;
if (bb.position() > 63000) {
writeToDisk(channels, chan, bb);
}
}
for (int i = 0; i < currentSpace; i++) {
bb.put((byte) 0x00);
}
writeToDisk(channels, chan, bb);
try {
channels[chan].close();
} catch (IOException e) {
e.printStackTrace();
}
}
try (FileOutputStream fos = new FileOutputStream(metadataFile);) {
fos.write(metadata.getBytes());
} catch (IOException e) {
}
}
private static void deleteDirectory(File directory) {
try {
Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
// If a file failed to delete, it's more useful to throw
// this instead
if (exc != null) {
throw exc;
}
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeToDisk(FileChannel[] channels, int chan, ByteBuffer bb) {
try {
bb.flip();
channels[chan].write(bb);
bb.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
private static String getMetadataUST() {
String metadata = metadataKernel.replace("\"kernel\"", "\"ust\"");
return metadata.replace("lttng-modules", "lttng-ust");
}
private class EventWriter {
public static final int SIZE = 4 + // timestamp
16 + // prev_comm
4 + // prev_tid
4 + // prev_prio
4 + // prev_state
16 + // current_comm
4 + // next_tid
4; // next_prio
private final ByteBuffer data;
public EventWriter(ByteBuffer bb) {
data = bb;
}
public int writeEvent(int ts, String prev_comm, int prev_tid, int prev_prio, int prev_state, String next_comm, int next_tid, int next_prio) {
byte[] bOut = new byte[16];
byte[] bIn = new byte[16];
byte[] temp = prev_comm.getBytes();
for (int i = 0; i < Math.min(temp.length, 16); i++) {
bOut[i] = temp[i];
}
temp = next_comm.getBytes();
for (int i = 0; i < Math.min(temp.length, 16); i++) {
bIn[i] = temp[i];
}
int timestamp = ts << 5;
data.putInt(timestamp);
data.put(bOut);
data.putInt(prev_tid);
data.putInt(prev_prio);
data.putInt(prev_state);
data.put(bIn);
data.putInt(next_tid);
data.putInt(next_prio);
return SIZE;
}
}
private class PacketWriter {
private static final int SIZE = 4096;
private static final int HEADER_SIZE = 64;
private static final int CONTENT_SIZE = SIZE - HEADER_SIZE;
private final ByteBuffer data;
public PacketWriter(ByteBuffer bb) {
data = bb;
}
public void writeNewHeader(long tsBegin, long tsEnd, int cpu, int eventCount, int discarded) {
final int magicLE = 0xC1FC1FC1;
byte uuid[] = {
0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11 };
// packet header
// magic number 4
data.putInt(magicLE);
// uuid 16
data.put(uuid);
// stream ID 4
data.putInt(0);
// packet context
// timestamp_begin 8
data.putLong(tsBegin);
// timestamp_end 8
data.putLong(tsEnd);
// content_size 8
data.putLong((eventCount * EventWriter.SIZE + HEADER_SIZE) * 8);
// packet_size 8
data.putLong((SIZE) * 8);
// events_discarded 4
data.putInt(discarded);
// cpu_id 4
data.putInt(cpu);
}
}
}