Commit | Line | Data |
---|---|---|
c2fa72bc | 1 | /******************************************************************************* |
4eebea10 | 2 | * Copyright (c) 2013, 2017 Ericsson |
c2fa72bc MK |
3 | * |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made 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 | |
8 | * | |
9 | * Contributors: | |
10 | * Matthew Khouzam - Initial API and implementation | |
ea0e5a5b | 11 | * Marc-Andre Laperle - Move generation to traces folder |
c2fa72bc MK |
12 | *******************************************************************************/ |
13 | ||
c4d57ac1 | 14 | package org.eclipse.tracecompass.ctf.core.tests.shared; |
c2fa72bc MK |
15 | |
16 | import java.io.File; | |
17 | import java.io.FileNotFoundException; | |
18 | import java.io.FileOutputStream; | |
19 | import java.io.IOException; | |
20 | import java.nio.ByteBuffer; | |
21 | import java.nio.ByteOrder; | |
22 | import java.nio.channels.FileChannel; | |
83967946 PT |
23 | import java.nio.file.FileVisitResult; |
24 | import java.nio.file.Files; | |
c4d57ac1 AM |
25 | import java.nio.file.Path; |
26 | import java.nio.file.Paths; | |
83967946 PT |
27 | import java.nio.file.SimpleFileVisitor; |
28 | import java.nio.file.attribute.BasicFileAttributes; | |
c2fa72bc MK |
29 | import java.util.Arrays; |
30 | import java.util.List; | |
31 | import java.util.Random; | |
32 | ||
f357bcd4 | 33 | import org.eclipse.tracecompass.ctf.core.tests.CtfCoreTestPlugin; |
ea0e5a5b | 34 | |
c2fa72bc | 35 | /** |
4eebea10 | 36 | * Generate a lttng trace (kernel or ust) |
c2fa72bc MK |
37 | * |
38 | * @author Matthew Khouzam | |
39 | */ | |
4eebea10 | 40 | public class LttngTraceGenerator { |
c2fa72bc | 41 | |
4eebea10 | 42 | private static final String metadataKernel = "/* CTF 1.8 */ \n" + |
c2fa72bc MK |
43 | "typealias integer { size = 8; align = 8; signed = false; } := uint8_t;\n" + |
44 | "typealias integer { size = 16; align = 8; signed = false; } := uint16_t;\n" + | |
45 | "typealias integer { size = 32; align = 8; signed = false; } := uint32_t;\n" + | |
46 | "typealias integer { size = 64; align = 8; signed = false; } := uint64_t;\n" + | |
47 | "typealias integer { size = 32; align = 8; signed = false; } := unsigned long;\n" + | |
48 | "typealias integer { size = 5; align = 1; signed = false; } := uint5_t;\n" + | |
49 | "typealias integer { size = 27; align = 1; signed = false; } := uint27_t;\n" + | |
50 | "\n" + | |
51 | "trace {\n" + | |
52 | " major = 1;\n" + | |
53 | " minor = 8;\n" + | |
54 | " uuid = \"11111111-1111-1111-1111-111111111111\";\n" + | |
55 | " byte_order = le;\n" + | |
56 | " packet.header := struct {\n" + | |
57 | " uint32_t magic;\n" + | |
58 | " uint8_t uuid[16];\n" + | |
59 | " uint32_t stream_id;\n" + | |
60 | " };\n" + | |
61 | "};\n" + | |
62 | "\n" + | |
63 | "env {\n" + | |
64 | " hostname = \"synthetic-host\";\n" + | |
65 | " domain = \"kernel\";\n" + | |
66 | " sysname = \"FakeLinux\";\n" + | |
67 | " kernel_release = \"1.0\";\n" + | |
68 | " kernel_version = \"Fake Os Synthetic Trace\";\n" + | |
69 | " tracer_name = \"lttng-modules\";\n" + | |
70 | " tracer_major = 2;\n" + | |
71 | " tracer_minor = 1;\n" + | |
72 | " tracer_patchlevel = 0;\n" + | |
73 | "};\n" + | |
74 | "\n" + | |
75 | "clock {\n" + | |
76 | " name = monotonic;\n" + | |
77 | " uuid = \"bbff68f0-c633-4ea1-92cd-bd11024ec4de\";\n" + | |
78 | " description = \"Monotonic Clock\";\n" + | |
79 | " freq = 1000000000; /* Frequency, in Hz */\n" + | |
80 | " /* clock value offset from Epoch is: offset * (1/freq) */\n" + | |
81 | " offset = 1368000272650993664;\n" + | |
82 | "};\n" + | |
83 | "\n" + | |
84 | "typealias integer {\n" + | |
85 | " size = 27; align = 1; signed = false;\n" + | |
86 | " map = clock.monotonic.value;\n" + | |
87 | "} := uint27_clock_monotonic_t;\n" + | |
88 | "\n" + | |
89 | "typealias integer {\n" + | |
90 | " size = 32; align = 8; signed = false;\n" + | |
91 | " map = clock.monotonic.value;\n" + | |
92 | "} := uint32_clock_monotonic_t;\n" + | |
93 | "\n" + | |
94 | "typealias integer {\n" + | |
95 | " size = 64; align = 8; signed = false;\n" + | |
96 | " map = clock.monotonic.value;\n" + | |
97 | "} := uint64_clock_monotonic_t;\n" + | |
98 | "\n" + | |
99 | "struct packet_context {\n" + | |
100 | " uint64_clock_monotonic_t timestamp_begin;\n" + | |
101 | " uint64_clock_monotonic_t timestamp_end;\n" + | |
102 | " uint64_t content_size;\n" + | |
103 | " uint64_t packet_size;\n" + | |
104 | " unsigned long events_discarded;\n" + | |
105 | " uint32_t cpu_id;\n" + | |
106 | "};\n" + | |
107 | "\n" + | |
108 | "struct event_header_compact {\n" + | |
109 | " enum : uint5_t { compact = 0 ... 30, extended = 31 } id;\n" + | |
110 | " variant <id> {\n" + | |
111 | " struct {\n" + | |
112 | " uint27_clock_monotonic_t timestamp;\n" + | |
113 | " } compact;\n" + | |
114 | " struct {\n" + | |
115 | " uint32_t id;\n" + | |
116 | " uint64_clock_monotonic_t timestamp;\n" + | |
117 | " } extended;\n" + | |
118 | " } v;\n" + | |
119 | "} align(8);\n" + | |
120 | "\n" + | |
121 | "struct event_header_large {\n" + | |
122 | " enum : uint16_t { compact = 0 ... 65534, extended = 65535 } id;\n" + | |
123 | " variant <id> {\n" + | |
124 | " struct {\n" + | |
125 | " uint32_clock_monotonic_t timestamp;\n" + | |
126 | " } compact;\n" + | |
127 | " struct {\n" + | |
128 | " uint32_t id;\n" + | |
129 | " uint64_clock_monotonic_t timestamp;\n" + | |
130 | " } extended;\n" + | |
131 | " } v;\n" + | |
132 | "} align(8);\n" + | |
133 | "\n" + | |
134 | "stream {\n" + | |
135 | " id = 0;\n" + | |
136 | " event.header := struct event_header_compact;\n" + | |
137 | " packet.context := struct packet_context;\n" + | |
138 | "};\n" + | |
139 | "\n" + | |
140 | "event {\n" + | |
141 | " name = sched_switch;\n" + | |
142 | " id = 0;\n" + | |
143 | " stream_id = 0;\n" + | |
144 | " fields := struct {\n" + | |
145 | " integer { size = 8; align = 8; signed = 1; encoding = UTF8; base = 10; } _prev_comm[16];\n" + | |
146 | " integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _prev_tid;\n" + | |
147 | " integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _prev_prio;\n" + | |
148 | " integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _prev_state;\n" + | |
149 | " integer { size = 8; align = 8; signed = 1; encoding = UTF8; base = 10; } _next_comm[16];\n" + | |
150 | " integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _next_tid;\n" + | |
151 | " integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _next_prio;\n" + | |
152 | " };\n" + | |
153 | "};\n" + | |
154 | "\n"; | |
155 | ||
156 | private final List<String> fProcesses; | |
157 | private final long fDuration; | |
158 | private final long fNbEvents; | |
159 | private final int fNbChans; | |
c2fa72bc | 160 | |
4eebea10 MAL |
161 | private final String metadata; |
162 | ||
c2fa72bc MK |
163 | private static final String[] sfProcesses = { |
164 | "IDLE", | |
165 | "gnuplot", | |
166 | "starcraft 2:pt3", | |
167 | "bash", | |
168 | "smash", | |
169 | "thrash", | |
170 | "fireball", | |
171 | "Half-life 3", | |
172 | "ST: The game" | |
173 | }; | |
174 | ||
175 | ||
ea0e5a5b MAL |
176 | private static final String TRACES_DIRECTORY = "traces"; |
177 | private static final String TRACE_NAME = "synthetic-trace"; | |
c2fa72bc MK |
178 | |
179 | /** | |
180 | * Main, not always needed | |
181 | * | |
182 | * @param args | |
183 | * args | |
184 | */ | |
185 | public static void main(String[] args) { | |
ea0e5a5b | 186 | // not using createTempFile as this is a directory |
3ec38c4c | 187 | String path = CtfCoreTestPlugin.getTemporaryDirPath() + File.separator + TRACE_NAME; |
4eebea10 | 188 | generateLttngTrace(new File(path)); |
c2fa72bc MK |
189 | } |
190 | ||
191 | /** | |
192 | * Gets the name of the trace (top directory name) | |
193 | * | |
194 | * @return the name of the trace | |
195 | */ | |
196 | public static String getName() { | |
ea0e5a5b | 197 | return TRACE_NAME; |
c2fa72bc MK |
198 | } |
199 | ||
200 | /** | |
201 | * Get the path | |
202 | * | |
203 | * @return the path | |
204 | */ | |
205 | public static String getPath() { | |
e9504bf6 AM |
206 | CtfCoreTestPlugin plugin = CtfCoreTestPlugin.getDefault(); |
207 | if (plugin == null) { | |
208 | return null; | |
209 | } | |
c4d57ac1 AM |
210 | Path tracePath = Paths.get("..", "..", "ctf", "org.eclipse.tracecompass.ctf.core.tests", TRACES_DIRECTORY, TRACE_NAME); |
211 | tracePath = tracePath.toAbsolutePath(); | |
212 | File file = tracePath.toFile(); | |
4eebea10 | 213 | generateLttngTrace(file); |
c2fa72bc MK |
214 | return file.getAbsolutePath(); |
215 | } | |
216 | ||
217 | /** | |
218 | * Generate a trace | |
ea0e5a5b MAL |
219 | * |
220 | * @param file | |
221 | * the file to write the trace to | |
c2fa72bc | 222 | */ |
4eebea10 | 223 | public static void generateLttngTrace(File file) { |
c2fa72bc | 224 | final int cpus = 25; |
4eebea10 | 225 | LttngTraceGenerator gt = new LttngTraceGenerator(2l * Integer.MAX_VALUE - 100, 500000, cpus); |
ea0e5a5b | 226 | gt.writeTrace(file); |
c2fa72bc MK |
227 | } |
228 | ||
229 | /** | |
4eebea10 MAL |
230 | * Make a lttng trace |
231 | * | |
232 | * @param duration | |
233 | * the duration of the trace | |
234 | * @param events | |
235 | * the number of events in a trace | |
236 | * @param nbChannels | |
237 | * the number of channels in the trace | |
238 | */ | |
239 | public LttngTraceGenerator(long duration, long events, int nbChannels) { | |
240 | this(duration, events, nbChannels, true); | |
241 | } | |
242 | ||
243 | /** | |
244 | * Make a lttng trace | |
c2fa72bc MK |
245 | * |
246 | * @param duration | |
247 | * the duration of the trace | |
248 | * @param events | |
249 | * the number of events in a trace | |
250 | * @param nbChannels | |
251 | * the number of channels in the trace | |
4eebea10 MAL |
252 | * @param isKernel |
253 | * true for kernel, false for ust | |
c2fa72bc | 254 | */ |
4eebea10 | 255 | public LttngTraceGenerator(long duration, long events, int nbChannels, boolean isKernel) { |
c2fa72bc MK |
256 | fProcesses = Arrays.asList(sfProcesses); |
257 | fDuration = duration; | |
258 | fNbEvents = events; | |
259 | fNbChans = nbChannels; | |
4eebea10 | 260 | metadata = isKernel ? metadataKernel : getMetadataUST(); |
c2fa72bc MK |
261 | } |
262 | ||
263 | /** | |
264 | * Write the trace to a file | |
265 | * | |
ea0e5a5b MAL |
266 | * @param file |
267 | * the file to write the trace to | |
c2fa72bc | 268 | */ |
ea0e5a5b | 269 | public void writeTrace(File file) { |
c2fa72bc | 270 | |
83967946 PT |
271 | if (file.exists()) { |
272 | deleteDirectory(file); | |
c2fa72bc | 273 | } |
83967946 | 274 | file.mkdir(); |
c2fa72bc | 275 | |
ea0e5a5b | 276 | File metadataFile = new File(file.getPath() + File.separator + "metadata"); |
c2fa72bc MK |
277 | File[] streams = new File[fNbChans]; |
278 | FileChannel[] channels = new FileChannel[fNbChans]; | |
c2fa72bc MK |
279 | |
280 | try { | |
281 | for (int i = 0; i < fNbChans; i++) { | |
ea0e5a5b | 282 | streams[i] = new File(file.getPath() + File.separator + "channel" + i); |
c2fa72bc | 283 | channels[i] = new FileOutputStream(streams[i]).getChannel(); |
c2fa72bc MK |
284 | } |
285 | } catch (FileNotFoundException e) { | |
286 | } | |
287 | // determine the number of events per channel | |
288 | long evPerChan = fNbEvents / fNbChans; | |
9709972d | 289 | final int evPerPacket = PacketWriter.CONTENT_SIZE / EventWriter.SIZE; |
6e1f0a91 PT |
290 | long delta = (int) (fDuration / evPerChan); |
291 | long offsetTime = 0; | |
9709972d | 292 | Random rndLost = new Random(1337); |
c2fa72bc MK |
293 | for (int chan = 0; chan < fNbChans; chan++) { |
294 | int currentSpace = 0; | |
295 | ByteBuffer bb = ByteBuffer.allocate(65536); | |
296 | bb.order(ByteOrder.LITTLE_ENDIAN); | |
297 | Random rnd = new Random(1337); | |
298 | int rnd0 = rnd.nextInt(fProcesses.size()); | |
299 | String prevComm = fProcesses.get(rnd0); | |
300 | int prevPID = rnd0 + chan * fProcesses.size(); | |
301 | if (rnd0 == 0) { | |
302 | prevPID = 0; | |
303 | } | |
304 | int prevPrio = 0; | |
305 | int prevPos = -1; | |
9709972d PT |
306 | int discarded = 0; |
307 | int discardedTotal = 0; | |
c2fa72bc | 308 | for (int eventNb = 0; eventNb < evPerChan; eventNb++) { |
9709972d PT |
309 | if (EventWriter.SIZE > currentSpace) { |
310 | eventNb += discarded; | |
311 | } | |
6e1f0a91 | 312 | long ts = eventNb * delta + delta / (fNbChans + 1) * chan; |
c2fa72bc MK |
313 | |
314 | int pos = rnd.nextInt((int) (fProcesses.size() * 1.5)); | |
315 | if (pos >= fProcesses.size()) { | |
316 | pos = 0; | |
317 | } | |
318 | while (pos == prevPos) { | |
319 | pos = rnd.nextInt((int) (fProcesses.size() * 1.5)); | |
320 | if (pos >= fProcesses.size()) { | |
321 | pos = 0; | |
322 | } | |
323 | } | |
324 | String nextComm = fProcesses.get(pos); | |
325 | int nextPID = pos + fProcesses.size() * chan; | |
326 | if (pos == 0) { | |
327 | nextPID = 0; | |
328 | } | |
329 | int nextPrio = 0; | |
330 | if (EventWriter.SIZE > currentSpace) { | |
331 | // pad to end | |
332 | for (int i = 0; i < currentSpace; i++) { | |
333 | bb.put((byte) 0x00); | |
334 | } | |
335 | // write new packet | |
336 | PacketWriter pw = new PacketWriter(bb); | |
6e1f0a91 | 337 | long tsBegin = ts; |
c2fa72bc | 338 | offsetTime = ts; |
9709972d PT |
339 | int eventCount = Math.min(evPerPacket, (int) evPerChan - eventNb); |
340 | discarded = rndLost.nextInt(10 * fNbChans) == 0 ? rndLost.nextInt(evPerPacket) : 0; | |
341 | discarded = Math.min(discarded, (int) evPerChan - eventNb - eventCount); | |
342 | discardedTotal += discarded; | |
343 | long tsEnd = (eventNb + eventCount + discarded) * delta; | |
344 | pw.writeNewHeader(tsBegin, tsEnd, chan, eventCount, discardedTotal); | |
c2fa72bc MK |
345 | currentSpace = PacketWriter.CONTENT_SIZE; |
346 | } | |
347 | EventWriter ew = new EventWriter(bb); | |
348 | int prev_state = rnd.nextInt(100); | |
349 | if (prev_state != 0) { | |
350 | prev_state = 1; | |
351 | } | |
6e1f0a91 | 352 | final long shrunkenTimestamp = ts - offsetTime; |
c2fa72bc MK |
353 | final int tsMask = (1 << 27) - 1; |
354 | if (shrunkenTimestamp > ((1 << 27) + tsMask)) { | |
6e1f0a91 PT |
355 | /* allow only one compact timestamp overflow per packet */ |
356 | throw new IllegalStateException("Invalid timestamp overflow:" + shrunkenTimestamp); | |
c2fa72bc | 357 | } |
6e1f0a91 | 358 | final int clampedTs = (int) (ts & tsMask); |
c2fa72bc MK |
359 | int evSize = ew.writeEvent(clampedTs, prevComm, prevPID, prevPrio, prev_state, nextComm, nextPID, nextPrio); |
360 | currentSpace -= evSize; | |
361 | prevComm = nextComm; | |
362 | prevPID = nextPID; | |
363 | prevPrio = nextPrio; | |
364 | if (bb.position() > 63000) { | |
365 | writeToDisk(channels, chan, bb); | |
366 | } | |
367 | } | |
368 | for (int i = 0; i < currentSpace; i++) { | |
369 | bb.put((byte) 0x00); | |
370 | } | |
371 | writeToDisk(channels, chan, bb); | |
372 | try { | |
373 | channels[chan].close(); | |
374 | } catch (IOException e) { | |
375 | e.printStackTrace(); | |
376 | } | |
377 | } | |
05ce5fef | 378 | try (FileOutputStream fos = new FileOutputStream(metadataFile);) { |
c2fa72bc | 379 | fos.write(metadata.getBytes()); |
c2fa72bc MK |
380 | } catch (IOException e) { |
381 | } | |
382 | } | |
383 | ||
83967946 PT |
384 | private static void deleteDirectory(File directory) { |
385 | try { | |
386 | Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() { | |
387 | @Override | |
388 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { | |
389 | Files.delete(file); | |
390 | return FileVisitResult.CONTINUE; | |
391 | } | |
392 | ||
393 | @Override | |
394 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { | |
4bbf01d7 MAL |
395 | // If a file failed to delete, it's more useful to throw this instead |
396 | if (exc != null) { | |
397 | throw exc; | |
398 | } | |
83967946 PT |
399 | Files.delete(dir); |
400 | return FileVisitResult.CONTINUE; | |
401 | } | |
402 | }); | |
403 | } catch (IOException e) { | |
404 | e.printStackTrace(); | |
405 | } | |
406 | } | |
407 | ||
c2fa72bc MK |
408 | private static void writeToDisk(FileChannel[] channels, int chan, ByteBuffer bb) { |
409 | try { | |
410 | bb.flip(); | |
411 | channels[chan].write(bb); | |
412 | bb.clear(); | |
413 | } catch (IOException e) { | |
414 | e.printStackTrace(); | |
415 | } | |
416 | } | |
417 | ||
4eebea10 MAL |
418 | private static String getMetadataUST() { |
419 | String metadata = metadataKernel.replace("\"kernel\"", "\"ust\""); | |
420 | return metadata.replace("lttng-modules", "lttng-ust"); | |
421 | } | |
422 | ||
c2fa72bc MK |
423 | private class EventWriter { |
424 | public static final int SIZE = | |
425 | 4 + // timestamp | |
426 | 16 + // prev_comm | |
427 | 4 + // prev_tid | |
428 | 4 + // prev_prio | |
429 | 4 + // prev_state | |
430 | 16 + // current_comm | |
431 | 4 + // next_tid | |
432 | 4; // next_prio | |
433 | private final ByteBuffer data; | |
434 | ||
435 | public EventWriter(ByteBuffer bb) { | |
436 | data = bb; | |
437 | } | |
438 | ||
439 | 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) { | |
440 | byte[] bOut = new byte[16]; | |
441 | byte[] bIn = new byte[16]; | |
442 | byte[] temp = prev_comm.getBytes(); | |
443 | for (int i = 0; i < Math.min(temp.length, 16); i++) { | |
444 | bOut[i] = temp[i]; | |
445 | } | |
446 | temp = next_comm.getBytes(); | |
447 | for (int i = 0; i < Math.min(temp.length, 16); i++) { | |
448 | bIn[i] = temp[i]; | |
449 | } | |
450 | ||
451 | int timestamp = ts << 5; | |
452 | ||
453 | data.putInt(timestamp); | |
454 | data.put(bOut); | |
455 | data.putInt(prev_tid); | |
456 | data.putInt(prev_prio); | |
457 | data.putInt(prev_state); | |
458 | data.put(bIn); | |
459 | data.putInt(next_tid); | |
460 | data.putInt(next_prio); | |
461 | return SIZE; | |
462 | } | |
463 | ||
464 | } | |
465 | ||
466 | private class PacketWriter { | |
467 | private static final int SIZE = 4096; | |
468 | private static final int HEADER_SIZE = 64; | |
469 | private static final int CONTENT_SIZE = SIZE - HEADER_SIZE; | |
470 | ||
471 | private final ByteBuffer data; | |
472 | ||
473 | public PacketWriter(ByteBuffer bb) { | |
474 | data = bb; | |
475 | } | |
476 | ||
9709972d | 477 | public void writeNewHeader(long tsBegin, long tsEnd, int cpu, int eventCount, int discarded) { |
c2fa72bc MK |
478 | final int magicLE = 0xC1FC1FC1; |
479 | byte uuid[] = { | |
480 | 0x11, 0x11, 0x11, 0x11, | |
481 | 0x11, 0x11, 0x11, 0x11, | |
482 | 0x11, 0x11, 0x11, 0x11, | |
483 | 0x11, 0x11, 0x11, 0x11 }; | |
484 | // packet header | |
485 | ||
486 | // magic number 4 | |
487 | data.putInt(magicLE); | |
488 | // uuid 16 | |
489 | data.put(uuid); | |
490 | // stream ID 4 | |
491 | data.putInt(0); | |
492 | ||
493 | // packet context | |
494 | // timestamp_begin 8 | |
495 | data.putLong(tsBegin); | |
496 | ||
497 | // timestamp_end 8 | |
498 | data.putLong(tsEnd); | |
499 | ||
500 | // content_size 8 | |
9709972d | 501 | data.putLong((eventCount * EventWriter.SIZE + HEADER_SIZE)* 8); |
c2fa72bc MK |
502 | |
503 | // packet_size 8 | |
504 | data.putLong((SIZE) * 8); | |
505 | ||
506 | // events_discarded 4 | |
9709972d | 507 | data.putInt(discarded); |
c2fa72bc MK |
508 | |
509 | // cpu_id 4 | |
510 | data.putInt(cpu); | |
511 | ||
512 | } | |
513 | ||
514 | } | |
515 | ||
516 | ||
517 | ||
518 | } |