Commit | Line | Data |
---|---|---|
c2fa72bc | 1 | /******************************************************************************* |
6e1f0a91 | 2 | * Copyright (c) 2013, 2015 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 MK |
35 | /** |
36 | * Generate a kernel trace | |
37 | * | |
38 | * @author Matthew Khouzam | |
39 | */ | |
40 | public class LttngKernelTraceGenerator { | |
41 | ||
42 | private static final String metadata = "/* CTF 1.8 */ \n" + | |
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 MK |
160 | |
161 | private static final String[] sfProcesses = { | |
162 | "IDLE", | |
163 | "gnuplot", | |
164 | "starcraft 2:pt3", | |
165 | "bash", | |
166 | "smash", | |
167 | "thrash", | |
168 | "fireball", | |
169 | "Half-life 3", | |
170 | "ST: The game" | |
171 | }; | |
172 | ||
173 | ||
ea0e5a5b MAL |
174 | private static final String TRACES_DIRECTORY = "traces"; |
175 | private static final String TRACE_NAME = "synthetic-trace"; | |
c2fa72bc MK |
176 | |
177 | /** | |
178 | * Main, not always needed | |
179 | * | |
180 | * @param args | |
181 | * args | |
182 | */ | |
183 | public static void main(String[] args) { | |
ea0e5a5b | 184 | // not using createTempFile as this is a directory |
3ec38c4c | 185 | String path = CtfCoreTestPlugin.getTemporaryDirPath() + File.separator + TRACE_NAME; |
ea0e5a5b | 186 | generateLttngKernelTrace(new File(path)); |
c2fa72bc MK |
187 | } |
188 | ||
189 | /** | |
190 | * Gets the name of the trace (top directory name) | |
191 | * | |
192 | * @return the name of the trace | |
193 | */ | |
194 | public static String getName() { | |
ea0e5a5b | 195 | return TRACE_NAME; |
c2fa72bc MK |
196 | } |
197 | ||
198 | /** | |
199 | * Get the path | |
200 | * | |
201 | * @return the path | |
202 | */ | |
203 | public static String getPath() { | |
e9504bf6 AM |
204 | CtfCoreTestPlugin plugin = CtfCoreTestPlugin.getDefault(); |
205 | if (plugin == null) { | |
206 | return null; | |
207 | } | |
c4d57ac1 AM |
208 | Path tracePath = Paths.get("..", "..", "ctf", "org.eclipse.tracecompass.ctf.core.tests", TRACES_DIRECTORY, TRACE_NAME); |
209 | tracePath = tracePath.toAbsolutePath(); | |
210 | File file = tracePath.toFile(); | |
83967946 | 211 | generateLttngKernelTrace(file); |
c2fa72bc MK |
212 | return file.getAbsolutePath(); |
213 | } | |
214 | ||
215 | /** | |
216 | * Generate a trace | |
ea0e5a5b MAL |
217 | * |
218 | * @param file | |
219 | * the file to write the trace to | |
c2fa72bc | 220 | */ |
ea0e5a5b | 221 | public static void generateLttngKernelTrace(File file) { |
c2fa72bc MK |
222 | final int cpus = 25; |
223 | LttngKernelTraceGenerator gt = new LttngKernelTraceGenerator(2l * Integer.MAX_VALUE - 100, 500000, cpus); | |
ea0e5a5b | 224 | gt.writeTrace(file); |
c2fa72bc MK |
225 | } |
226 | ||
227 | /** | |
228 | * Make a kernel trace | |
229 | * | |
230 | * @param duration | |
231 | * the duration of the trace | |
232 | * @param events | |
233 | * the number of events in a trace | |
234 | * @param nbChannels | |
235 | * the number of channels in the trace | |
236 | */ | |
237 | public LttngKernelTraceGenerator(long duration, long events, int nbChannels) { | |
238 | fProcesses = Arrays.asList(sfProcesses); | |
239 | fDuration = duration; | |
240 | fNbEvents = events; | |
241 | fNbChans = nbChannels; | |
242 | } | |
243 | ||
244 | /** | |
245 | * Write the trace to a file | |
246 | * | |
ea0e5a5b MAL |
247 | * @param file |
248 | * the file to write the trace to | |
c2fa72bc | 249 | */ |
ea0e5a5b | 250 | public void writeTrace(File file) { |
c2fa72bc | 251 | |
83967946 PT |
252 | if (file.exists()) { |
253 | deleteDirectory(file); | |
c2fa72bc | 254 | } |
83967946 | 255 | file.mkdir(); |
c2fa72bc | 256 | |
ea0e5a5b | 257 | File metadataFile = new File(file.getPath() + File.separator + "metadata"); |
c2fa72bc MK |
258 | File[] streams = new File[fNbChans]; |
259 | FileChannel[] channels = new FileChannel[fNbChans]; | |
c2fa72bc MK |
260 | |
261 | try { | |
262 | for (int i = 0; i < fNbChans; i++) { | |
ea0e5a5b | 263 | streams[i] = new File(file.getPath() + File.separator + "channel" + i); |
c2fa72bc | 264 | channels[i] = new FileOutputStream(streams[i]).getChannel(); |
c2fa72bc MK |
265 | } |
266 | } catch (FileNotFoundException e) { | |
267 | } | |
268 | // determine the number of events per channel | |
269 | long evPerChan = fNbEvents / fNbChans; | |
9709972d | 270 | final int evPerPacket = PacketWriter.CONTENT_SIZE / EventWriter.SIZE; |
6e1f0a91 PT |
271 | long delta = (int) (fDuration / evPerChan); |
272 | long offsetTime = 0; | |
9709972d | 273 | Random rndLost = new Random(1337); |
c2fa72bc MK |
274 | for (int chan = 0; chan < fNbChans; chan++) { |
275 | int currentSpace = 0; | |
276 | ByteBuffer bb = ByteBuffer.allocate(65536); | |
277 | bb.order(ByteOrder.LITTLE_ENDIAN); | |
278 | Random rnd = new Random(1337); | |
279 | int rnd0 = rnd.nextInt(fProcesses.size()); | |
280 | String prevComm = fProcesses.get(rnd0); | |
281 | int prevPID = rnd0 + chan * fProcesses.size(); | |
282 | if (rnd0 == 0) { | |
283 | prevPID = 0; | |
284 | } | |
285 | int prevPrio = 0; | |
286 | int prevPos = -1; | |
9709972d PT |
287 | int discarded = 0; |
288 | int discardedTotal = 0; | |
c2fa72bc | 289 | for (int eventNb = 0; eventNb < evPerChan; eventNb++) { |
9709972d PT |
290 | if (EventWriter.SIZE > currentSpace) { |
291 | eventNb += discarded; | |
292 | } | |
6e1f0a91 | 293 | long ts = eventNb * delta + delta / (fNbChans + 1) * chan; |
c2fa72bc MK |
294 | |
295 | int pos = rnd.nextInt((int) (fProcesses.size() * 1.5)); | |
296 | if (pos >= fProcesses.size()) { | |
297 | pos = 0; | |
298 | } | |
299 | while (pos == prevPos) { | |
300 | pos = rnd.nextInt((int) (fProcesses.size() * 1.5)); | |
301 | if (pos >= fProcesses.size()) { | |
302 | pos = 0; | |
303 | } | |
304 | } | |
305 | String nextComm = fProcesses.get(pos); | |
306 | int nextPID = pos + fProcesses.size() * chan; | |
307 | if (pos == 0) { | |
308 | nextPID = 0; | |
309 | } | |
310 | int nextPrio = 0; | |
311 | if (EventWriter.SIZE > currentSpace) { | |
312 | // pad to end | |
313 | for (int i = 0; i < currentSpace; i++) { | |
314 | bb.put((byte) 0x00); | |
315 | } | |
316 | // write new packet | |
317 | PacketWriter pw = new PacketWriter(bb); | |
6e1f0a91 | 318 | long tsBegin = ts; |
c2fa72bc | 319 | offsetTime = ts; |
9709972d PT |
320 | int eventCount = Math.min(evPerPacket, (int) evPerChan - eventNb); |
321 | discarded = rndLost.nextInt(10 * fNbChans) == 0 ? rndLost.nextInt(evPerPacket) : 0; | |
322 | discarded = Math.min(discarded, (int) evPerChan - eventNb - eventCount); | |
323 | discardedTotal += discarded; | |
324 | long tsEnd = (eventNb + eventCount + discarded) * delta; | |
325 | pw.writeNewHeader(tsBegin, tsEnd, chan, eventCount, discardedTotal); | |
c2fa72bc MK |
326 | currentSpace = PacketWriter.CONTENT_SIZE; |
327 | } | |
328 | EventWriter ew = new EventWriter(bb); | |
329 | int prev_state = rnd.nextInt(100); | |
330 | if (prev_state != 0) { | |
331 | prev_state = 1; | |
332 | } | |
6e1f0a91 | 333 | final long shrunkenTimestamp = ts - offsetTime; |
c2fa72bc MK |
334 | final int tsMask = (1 << 27) - 1; |
335 | if (shrunkenTimestamp > ((1 << 27) + tsMask)) { | |
6e1f0a91 PT |
336 | /* allow only one compact timestamp overflow per packet */ |
337 | throw new IllegalStateException("Invalid timestamp overflow:" + shrunkenTimestamp); | |
c2fa72bc | 338 | } |
6e1f0a91 | 339 | final int clampedTs = (int) (ts & tsMask); |
c2fa72bc MK |
340 | int evSize = ew.writeEvent(clampedTs, prevComm, prevPID, prevPrio, prev_state, nextComm, nextPID, nextPrio); |
341 | currentSpace -= evSize; | |
342 | prevComm = nextComm; | |
343 | prevPID = nextPID; | |
344 | prevPrio = nextPrio; | |
345 | if (bb.position() > 63000) { | |
346 | writeToDisk(channels, chan, bb); | |
347 | } | |
348 | } | |
349 | for (int i = 0; i < currentSpace; i++) { | |
350 | bb.put((byte) 0x00); | |
351 | } | |
352 | writeToDisk(channels, chan, bb); | |
353 | try { | |
354 | channels[chan].close(); | |
355 | } catch (IOException e) { | |
356 | e.printStackTrace(); | |
357 | } | |
358 | } | |
05ce5fef | 359 | try (FileOutputStream fos = new FileOutputStream(metadataFile);) { |
c2fa72bc | 360 | fos.write(metadata.getBytes()); |
c2fa72bc MK |
361 | } catch (IOException e) { |
362 | } | |
363 | } | |
364 | ||
83967946 PT |
365 | private static void deleteDirectory(File directory) { |
366 | try { | |
367 | Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() { | |
368 | @Override | |
369 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { | |
370 | Files.delete(file); | |
371 | return FileVisitResult.CONTINUE; | |
372 | } | |
373 | ||
374 | @Override | |
375 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { | |
376 | Files.delete(dir); | |
377 | return FileVisitResult.CONTINUE; | |
378 | } | |
379 | }); | |
380 | } catch (IOException e) { | |
381 | e.printStackTrace(); | |
382 | } | |
383 | } | |
384 | ||
c2fa72bc MK |
385 | private static void writeToDisk(FileChannel[] channels, int chan, ByteBuffer bb) { |
386 | try { | |
387 | bb.flip(); | |
388 | channels[chan].write(bb); | |
389 | bb.clear(); | |
390 | } catch (IOException e) { | |
391 | e.printStackTrace(); | |
392 | } | |
393 | } | |
394 | ||
395 | private class EventWriter { | |
396 | public static final int SIZE = | |
397 | 4 + // timestamp | |
398 | 16 + // prev_comm | |
399 | 4 + // prev_tid | |
400 | 4 + // prev_prio | |
401 | 4 + // prev_state | |
402 | 16 + // current_comm | |
403 | 4 + // next_tid | |
404 | 4; // next_prio | |
405 | private final ByteBuffer data; | |
406 | ||
407 | public EventWriter(ByteBuffer bb) { | |
408 | data = bb; | |
409 | } | |
410 | ||
411 | 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) { | |
412 | byte[] bOut = new byte[16]; | |
413 | byte[] bIn = new byte[16]; | |
414 | byte[] temp = prev_comm.getBytes(); | |
415 | for (int i = 0; i < Math.min(temp.length, 16); i++) { | |
416 | bOut[i] = temp[i]; | |
417 | } | |
418 | temp = next_comm.getBytes(); | |
419 | for (int i = 0; i < Math.min(temp.length, 16); i++) { | |
420 | bIn[i] = temp[i]; | |
421 | } | |
422 | ||
423 | int timestamp = ts << 5; | |
424 | ||
425 | data.putInt(timestamp); | |
426 | data.put(bOut); | |
427 | data.putInt(prev_tid); | |
428 | data.putInt(prev_prio); | |
429 | data.putInt(prev_state); | |
430 | data.put(bIn); | |
431 | data.putInt(next_tid); | |
432 | data.putInt(next_prio); | |
433 | return SIZE; | |
434 | } | |
435 | ||
436 | } | |
437 | ||
438 | private class PacketWriter { | |
439 | private static final int SIZE = 4096; | |
440 | private static final int HEADER_SIZE = 64; | |
441 | private static final int CONTENT_SIZE = SIZE - HEADER_SIZE; | |
442 | ||
443 | private final ByteBuffer data; | |
444 | ||
445 | public PacketWriter(ByteBuffer bb) { | |
446 | data = bb; | |
447 | } | |
448 | ||
9709972d | 449 | public void writeNewHeader(long tsBegin, long tsEnd, int cpu, int eventCount, int discarded) { |
c2fa72bc MK |
450 | final int magicLE = 0xC1FC1FC1; |
451 | byte uuid[] = { | |
452 | 0x11, 0x11, 0x11, 0x11, | |
453 | 0x11, 0x11, 0x11, 0x11, | |
454 | 0x11, 0x11, 0x11, 0x11, | |
455 | 0x11, 0x11, 0x11, 0x11 }; | |
456 | // packet header | |
457 | ||
458 | // magic number 4 | |
459 | data.putInt(magicLE); | |
460 | // uuid 16 | |
461 | data.put(uuid); | |
462 | // stream ID 4 | |
463 | data.putInt(0); | |
464 | ||
465 | // packet context | |
466 | // timestamp_begin 8 | |
467 | data.putLong(tsBegin); | |
468 | ||
469 | // timestamp_end 8 | |
470 | data.putLong(tsEnd); | |
471 | ||
472 | // content_size 8 | |
9709972d | 473 | data.putLong((eventCount * EventWriter.SIZE + HEADER_SIZE)* 8); |
c2fa72bc MK |
474 | |
475 | // packet_size 8 | |
476 | data.putLong((SIZE) * 8); | |
477 | ||
478 | // events_discarded 4 | |
9709972d | 479 | data.putInt(discarded); |
c2fa72bc MK |
480 | |
481 | // cpu_id 4 | |
482 | data.putInt(cpu); | |
483 | ||
484 | } | |
485 | ||
486 | } | |
487 | ||
488 | ||
489 | ||
490 | } |