lib/tracy/src/event.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const pretty_json = @import("pretty").json;
3
4 pub const schema = "tracy.event/v0";
5 pub const format_version: u32 = 0;
6
7 pub const Kind = enum {
8 start,
9 stop,
10 zone_begin,
11 zone_end,
12 zone_text,
13 zone_name,
14 zone_color,
15 zone_value,
16 frame,
17 message,
18 app_info,
19 plot_config,
20 plot,
21 alloc,
22 free,
23 thread_name,
24 fiber_enter,
25 fiber_leave,
26 fiber_name,
27 gpu_context,
28 gpu_context_name,
29 gpu_zone_begin,
30 gpu_zone_end,
31 gpu_time,
32 gpu_calibration,
33 gpu_time_sync,
34 gpu_annotation,
35 gpu_annotation_name,
36 thread_context,
37 thread_pid,
38 cpu_topology,
39 sys_time,
40 sys_power,
41 hw_sample,
42 sample,
43 sample_frame,
44 context_switch,
45 thread_wakeup,
46 lock_announce,
47 lock_terminate,
48 lock_wait,
49 lock_obtain,
50 lock_release,
51 lock_shared_wait,
52 lock_shared_obtain,
53 lock_shared_release,
54 lock_mark,
55 lock_name,
56
57 pub fn tag(self: Kind) []const u8 {
58 return switch (self) {
59 .start => "start",
60 .stop => "stop",
61 .zone_begin => "zone.begin",
62 .zone_end => "zone.end",
63 .zone_text => "zone.text",
64 .zone_name => "zone.name",
65 .zone_color => "zone.color",
66 .zone_value => "zone.value",
67 .frame => "frame",
68 .message => "message",
69 .app_info => "app.info",
70 .plot_config => "plot.config",
71 .plot => "plot",
72 .alloc => "alloc",
73 .free => "free",
74 .thread_name => "thread.name",
75 .fiber_enter => "fiber.enter",
76 .fiber_leave => "fiber.leave",
77 .fiber_name => "fiber.name",
78 .gpu_context => "gpu.context",
79 .gpu_context_name => "gpu.context.name",
80 .gpu_zone_begin => "gpu.zone.begin",
81 .gpu_zone_end => "gpu.zone.end",
82 .gpu_time => "gpu.time",
83 .gpu_calibration => "gpu.calibration",
84 .gpu_time_sync => "gpu.time_sync",
85 .gpu_annotation => "gpu.annotation",
86 .gpu_annotation_name => "gpu.annotation.name",
87 .thread_context => "thread.context",
88 .thread_pid => "thread.pid",
89 .cpu_topology => "cpu.topology",
90 .sys_time => "sys.time",
91 .sys_power => "sys.power",
92 .hw_sample => "hw.sample",
93 .sample => "sample",
94 .sample_frame => "sample.frame",
95 .context_switch => "context.switch",
96 .thread_wakeup => "thread.wakeup",
97 .lock_announce => "lock.announce",
98 .lock_terminate => "lock.terminate",
99 .lock_wait => "lock.wait",
100 .lock_obtain => "lock.obtain",
101 .lock_release => "lock.release",
102 .lock_shared_wait => "lock.shared.wait",
103 .lock_shared_obtain => "lock.shared.obtain",
104 .lock_shared_release => "lock.shared.release",
105 .lock_mark => "lock.mark",
106 .lock_name => "lock.name",
107 };
108 }
109
110 pub fn fromTag(text: []const u8) ?Kind {
111 if (std.mem.eql(u8, text, "start")) return .start;
112 if (std.mem.eql(u8, text, "stop")) return .stop;
113 if (std.mem.eql(u8, text, "zone.begin")) return .zone_begin;
114 if (std.mem.eql(u8, text, "zone.end")) return .zone_end;
115 if (std.mem.eql(u8, text, "zone.text")) return .zone_text;
116 if (std.mem.eql(u8, text, "zone.name")) return .zone_name;
117 if (std.mem.eql(u8, text, "zone.color")) return .zone_color;
118 if (std.mem.eql(u8, text, "zone.value")) return .zone_value;
119 if (std.mem.eql(u8, text, "frame")) return .frame;
120 if (std.mem.eql(u8, text, "message")) return .message;
121 if (std.mem.eql(u8, text, "app.info")) return .app_info;
122 if (std.mem.eql(u8, text, "plot.config")) return .plot_config;
123 if (std.mem.eql(u8, text, "plot")) return .plot;
124 if (std.mem.eql(u8, text, "alloc")) return .alloc;
125 if (std.mem.eql(u8, text, "free")) return .free;
126 if (std.mem.eql(u8, text, "thread.name")) return .thread_name;
127 if (std.mem.eql(u8, text, "fiber.enter")) return .fiber_enter;
128 if (std.mem.eql(u8, text, "fiber.leave")) return .fiber_leave;
129 if (std.mem.eql(u8, text, "fiber.name")) return .fiber_name;
130 if (std.mem.eql(u8, text, "gpu.context")) return .gpu_context;
131 if (std.mem.eql(u8, text, "gpu.context.name")) return .gpu_context_name;
132 if (std.mem.eql(u8, text, "gpu.zone.begin")) return .gpu_zone_begin;
133 if (std.mem.eql(u8, text, "gpu.zone.end")) return .gpu_zone_end;
134 if (std.mem.eql(u8, text, "gpu.time")) return .gpu_time;
135 if (std.mem.eql(u8, text, "gpu.calibration")) return .gpu_calibration;
136 if (std.mem.eql(u8, text, "gpu.time_sync")) return .gpu_time_sync;
137 if (std.mem.eql(u8, text, "gpu.annotation")) return .gpu_annotation;
138 if (std.mem.eql(u8, text, "gpu.annotation.name")) return .gpu_annotation_name;
139 if (std.mem.eql(u8, text, "thread.context")) return .thread_context;
140 if (std.mem.eql(u8, text, "thread.pid")) return .thread_pid;
141 if (std.mem.eql(u8, text, "cpu.topology")) return .cpu_topology;
142 if (std.mem.eql(u8, text, "sys.time")) return .sys_time;
143 if (std.mem.eql(u8, text, "sys.power")) return .sys_power;
144 if (std.mem.eql(u8, text, "hw.sample")) return .hw_sample;
145 if (std.mem.eql(u8, text, "sample")) return .sample;
146 if (std.mem.eql(u8, text, "sample.frame")) return .sample_frame;
147 if (std.mem.eql(u8, text, "context.switch")) return .context_switch;
148 if (std.mem.eql(u8, text, "thread.wakeup")) return .thread_wakeup;
149 if (std.mem.eql(u8, text, "lock.announce")) return .lock_announce;
150 if (std.mem.eql(u8, text, "lock.terminate")) return .lock_terminate;
151 if (std.mem.eql(u8, text, "lock.wait")) return .lock_wait;
152 if (std.mem.eql(u8, text, "lock.obtain")) return .lock_obtain;
153 if (std.mem.eql(u8, text, "lock.release")) return .lock_release;
154 if (std.mem.eql(u8, text, "lock.shared.wait")) return .lock_shared_wait;
155 if (std.mem.eql(u8, text, "lock.shared.obtain")) return .lock_shared_obtain;
156 if (std.mem.eql(u8, text, "lock.shared.release")) return .lock_shared_release;
157 if (std.mem.eql(u8, text, "lock.mark")) return .lock_mark;
158 if (std.mem.eql(u8, text, "lock.name")) return .lock_name;
159 return null;
160 }
161 };
162
163 pub const TraceEvent = struct {
164 seq: u64 = 0,
165 kind: Kind,
166 time_ns: u64 = 0,
167 thread: u64 = 0,
168 id: u64 = 0,
169 name: ?[]const u8 = null,
170 text: ?[]const u8 = null,
171 function: ?[]const u8 = null,
172 file: ?[]const u8 = null,
173 line: u32 = 0,
174 column: u32 = 0,
175 color: ?u32 = null,
176 value_u64: ?u64 = null,
177 value_i64: ?i64 = null,
178 value_f64: ?f64 = null,
179 address: u64 = 0,
180 size: u64 = 0,
181 depth: u32 = 0,
182 cpu: ?u32 = null,
183 prev_thread: u64 = 0,
184 next_thread: u64 = 0,
185 target_thread: u64 = 0,
186 pid: u64 = 0,
187 previous_cstate: ?u32 = null,
188 prev_priority: ?i64 = null,
189 next_priority: ?i64 = null,
190 group_hint: ?i64 = null,
191 gpu_context: ?u32 = null,
192 gpu_query: ?u32 = null,
193 gpu_time: ?i64 = null,
194 gpu_period: ?f64 = null,
195 gpu_cpu_delta_ns: ?i64 = null,
196 gpu_context_type: ?[]const u8 = null,
197 gpu_has_calibration: bool = false,
198 gpu_serial: bool = false,
199 gpu_annotation_id: ?i64 = null,
200 cpu_package: ?u32 = null,
201 cpu_die: ?u32 = null,
202 cpu_core: ?u32 = null,
203 power_delta_uj: ?u64 = null,
204 hw_sample_kind: ?[]const u8 = null,
205 plot_kind: ?[]const u8 = null,
206 plot_unit: ?[]const u8 = null,
207 plot_step: bool = false,
208 plot_fill: bool = false,
209 lock_kind: ?[]const u8 = null,
210 sample_kind: ?[]const u8 = null,
211 context_reason: ?[]const u8 = null,
212 context_state: ?[]const u8 = null,
213
214 pub fn writeJsonLine(self: TraceEvent, writer: *std.Io.Writer) !void {
215 var stream = pretty_json.Writer.init(writer, .minified);
216 const object = try stream.object();
217 try object.field("schema", schema);
218 try object.field("v", format_version);
219 try object.field("seq", self.seq);
220 try object.field("kind", self.kind.tag());
221 try object.field("time_ns", self.time_ns);
222 try object.field("thread", self.thread);
223 if (self.id != 0) try object.field("id", self.id);
224 if (self.name) |name| try object.field("name", name);
225 if (self.text) |text| try object.field("text", text);
226 if (self.function) |function| try object.field("function", function);
227 if (self.file) |file| try object.field("file", file);
228 if (self.line != 0) try object.field("line", self.line);
229 if (self.column != 0) try object.field("column", self.column);
230 if (self.color) |color| try object.field("color", color);
231 if (self.value_u64) |value| try object.field("value_u64", value);
232 if (self.value_i64) |value| try object.field("value_i64", value);
233 if (self.value_f64) |value| {
234 if (std.math.isFinite(value)) try object.field("value_f64", value);
235 }
236 if (self.address != 0) try object.field("address", self.address);
237 if (self.size != 0) try object.field("size", self.size);
238 if (self.depth != 0) try object.field("depth", self.depth);
239 if (self.cpu) |cpu| try object.field("cpu", cpu);
240 if (self.prev_thread != 0) try object.field("prev_thread", self.prev_thread);
241 if (self.next_thread != 0) try object.field("next_thread", self.next_thread);
242 if (self.target_thread != 0) try object.field("target_thread", self.target_thread);
243 if (self.pid != 0) try object.field("pid", self.pid);
244 if (self.previous_cstate) |previous_cstate| try object.field("previous_cstate", previous_cstate);
245 if (self.prev_priority) |prev_priority| try object.field("prev_priority", prev_priority);
246 if (self.next_priority) |next_priority| try object.field("next_priority", next_priority);
247 if (self.group_hint) |group_hint| try object.field("group_hint", group_hint);
248 if (self.gpu_context) |gpu_context| try object.field("gpu_context", gpu_context);
249 if (self.gpu_query) |gpu_query| try object.field("gpu_query", gpu_query);
250 if (self.gpu_time) |gpu_time| try object.field("gpu_time", gpu_time);
251 if (self.gpu_period) |gpu_period| {
252 if (std.math.isFinite(gpu_period)) try object.field("gpu_period", gpu_period);
253 }
254 if (self.gpu_cpu_delta_ns) |value| try object.field("gpu_cpu_delta_ns", value);
255 if (self.gpu_context_type) |value| try object.field("gpu_context_type", value);
256 if (self.gpu_has_calibration) try object.field("gpu_has_calibration", true);
257 if (self.gpu_serial) try object.field("gpu_serial", true);
258 if (self.gpu_annotation_id) |value| try object.field("gpu_annotation_id", value);
259 if (self.cpu_package) |value| try object.field("cpu_package", value);
260 if (self.cpu_die) |value| try object.field("cpu_die", value);
261 if (self.cpu_core) |value| try object.field("cpu_core", value);
262 if (self.power_delta_uj) |value| try object.field("power_delta_uj", value);
263 if (self.hw_sample_kind) |value| try object.field("hw_sample_kind", value);
264 if (self.plot_kind) |value| try object.field("plot_kind", value);
265 if (self.plot_unit) |value| try object.field("plot_unit", value);
266 if (self.plot_step) try object.field("plot_step", true);
267 if (self.plot_fill) try object.field("plot_fill", true);
268 if (self.lock_kind) |value| try object.field("lock_kind", value);
269 if (self.sample_kind) |value| try object.field("sample_kind", value);
270 if (self.context_reason) |value| try object.field("context_reason", value);
271 if (self.context_state) |value| try object.field("context_state", value);
272 try object.endLine();
273 }
274 };
275
276 pub const Parsed = struct {
277 tree: std.json.Parsed(std.json.Value),
278 kind: Kind,
279 seq: u64 = 0,
280 time_ns: u64 = 0,
281 thread: u64 = 0,
282 id: u64 = 0,
283 name: ?[]const u8 = null,
284 text: ?[]const u8 = null,
285 function: ?[]const u8 = null,
286 file: ?[]const u8 = null,
287 line: u32 = 0,
288 column: u32 = 0,
289 color: ?u32 = null,
290 value_u64: ?u64 = null,
291 value_i64: ?i64 = null,
292 value_f64: ?f64 = null,
293 address: u64 = 0,
294 size: u64 = 0,
295 depth: u32 = 0,
296 cpu: ?u32 = null,
297 prev_thread: u64 = 0,
298 next_thread: u64 = 0,
299 target_thread: u64 = 0,
300 pid: u64 = 0,
301 previous_cstate: ?u32 = null,
302 prev_priority: ?i64 = null,
303 next_priority: ?i64 = null,
304 group_hint: ?i64 = null,
305 gpu_context: ?u32 = null,
306 gpu_query: ?u32 = null,
307 gpu_time: ?i64 = null,
308 gpu_period: ?f64 = null,
309 gpu_cpu_delta_ns: ?i64 = null,
310 gpu_context_type: ?[]const u8 = null,
311 gpu_has_calibration: bool = false,
312 gpu_serial: bool = false,
313 gpu_annotation_id: ?i64 = null,
314 cpu_package: ?u32 = null,
315 cpu_die: ?u32 = null,
316 cpu_core: ?u32 = null,
317 power_delta_uj: ?u64 = null,
318 hw_sample_kind: ?[]const u8 = null,
319 plot_kind: ?[]const u8 = null,
320 plot_unit: ?[]const u8 = null,
321 plot_step: bool = false,
322 plot_fill: bool = false,
323 lock_kind: ?[]const u8 = null,
324 sample_kind: ?[]const u8 = null,
325 context_reason: ?[]const u8 = null,
326 context_state: ?[]const u8 = null,
327
328 pub fn deinit(self: *Parsed) void {
329 self.tree.deinit();
330 self.* = undefined;
331 }
332 };
333
334 pub fn parseLine(allocator: std.mem.Allocator, line: []const u8) !Parsed {
335 const text = std.mem.trim(u8, line, " \t\r\n");
336 if (text.len == 0) return error.EmptyEventLine;
337 var tree = std.json.parseFromSlice(std.json.Value, allocator, text, .{}) catch |err| {
338 if (err == error.OutOfMemory) return err;
339 return error.InvalidEventJson;
340 };
341 errdefer tree.deinit();
342 const object = switch (tree.value) {
343 .object => |object| object,
344 else => return error.InvalidEventJson,
345 };
346 if (object.get("schema")) |value| {
347 const actual_schema = try jsonString(value);
348 if (!std.mem.eql(u8, actual_schema, schema)) return error.UnsupportedTraceSchema;
349 }
350 const version = try jsonU64(object.get("v") orelse return error.InvalidEventJson);
351 if (version != format_version) return error.UnsupportedTraceVersion;
352 const kind_text = try jsonString(object.get("kind") orelse return error.InvalidEventJson);
353 return .{
354 .tree = tree,
355 .kind = Kind.fromTag(kind_text) orelse return error.InvalidEventJson,
356 .seq = try jsonU64Default(object, "seq", 0),
357 .time_ns = try jsonU64Default(object, "time_ns", 0),
358 .thread = try jsonU64Default(object, "thread", 0),
359 .id = try jsonU64Default(object, "id", 0),
360 .name = try jsonStringOptional(object, "name"),
361 .text = try jsonStringOptional(object, "text"),
362 .function = try jsonStringOptional(object, "function"),
363 .file = try jsonStringOptional(object, "file"),
364 .line = @intCast(try jsonU64Default(object, "line", 0)),
365 .column = @intCast(try jsonU64Default(object, "column", 0)),
366 .color = try jsonU32Optional(object, "color"),
367 .value_u64 = try jsonU64Optional(object, "value_u64"),
368 .value_i64 = try jsonI64Optional(object, "value_i64"),
369 .value_f64 = try jsonF64Optional(object, "value_f64"),
370 .address = try jsonU64Default(object, "address", 0),
371 .size = try jsonU64Default(object, "size", 0),
372 .depth = @intCast(try jsonU64Default(object, "depth", 0)),
373 .cpu = try jsonU32Optional(object, "cpu"),
374 .prev_thread = try jsonU64Default(object, "prev_thread", 0),
375 .next_thread = try jsonU64Default(object, "next_thread", 0),
376 .target_thread = try jsonU64Default(object, "target_thread", 0),
377 .pid = try jsonU64Default(object, "pid", 0),
378 .previous_cstate = try jsonU32Optional(object, "previous_cstate"),
379 .prev_priority = try jsonI64Optional(object, "prev_priority"),
380 .next_priority = try jsonI64Optional(object, "next_priority"),
381 .group_hint = try jsonI64Optional(object, "group_hint"),
382 .gpu_context = try jsonU32Optional(object, "gpu_context"),
383 .gpu_query = try jsonU32Optional(object, "gpu_query"),
384 .gpu_time = try jsonI64Optional(object, "gpu_time"),
385 .gpu_period = try jsonF64Optional(object, "gpu_period"),
386 .gpu_cpu_delta_ns = try jsonI64Optional(object, "gpu_cpu_delta_ns"),
387 .gpu_context_type = try jsonStringOptional(object, "gpu_context_type"),
388 .gpu_has_calibration = try jsonBoolDefault(object, "gpu_has_calibration", false),
389 .gpu_serial = try jsonBoolDefault(object, "gpu_serial", false),
390 .gpu_annotation_id = try jsonI64Optional(object, "gpu_annotation_id"),
391 .cpu_package = try jsonU32Optional(object, "cpu_package"),
392 .cpu_die = try jsonU32Optional(object, "cpu_die"),
393 .cpu_core = try jsonU32Optional(object, "cpu_core"),
394 .power_delta_uj = try jsonU64Optional(object, "power_delta_uj"),
395 .hw_sample_kind = try jsonStringOptional(object, "hw_sample_kind"),
396 .plot_kind = try jsonStringOptional(object, "plot_kind"),
397 .plot_unit = try jsonStringOptional(object, "plot_unit"),
398 .plot_step = try jsonBoolDefault(object, "plot_step", false),
399 .plot_fill = try jsonBoolDefault(object, "plot_fill", false),
400 .lock_kind = try jsonStringOptional(object, "lock_kind"),
401 .sample_kind = try jsonStringOptional(object, "sample_kind"),
402 .context_reason = try jsonStringOptional(object, "context_reason"),
403 .context_state = try jsonStringOptional(object, "context_state"),
404 };
405 }
406
407 fn jsonString(value: std.json.Value) ![]const u8 {
408 return switch (value) {
409 .string => |text| text,
410 else => error.InvalidEventJson,
411 };
412 }
413
414 fn jsonStringOptional(object: std.json.ObjectMap, key: []const u8) !?[]const u8 {
415 const value = object.get(key) orelse return null;
416 return try jsonString(value);
417 }
418
419 fn jsonU64(value: std.json.Value) !u64 {
420 return switch (value) {
421 .integer => |integer| if (integer < 0) error.InvalidEventJson else @intCast(integer),
422 else => error.InvalidEventJson,
423 };
424 }
425
426 fn jsonU64Default(object: std.json.ObjectMap, key: []const u8, default: u64) !u64 {
427 const value = object.get(key) orelse return default;
428 return try jsonU64(value);
429 }
430
431 fn jsonU64Optional(object: std.json.ObjectMap, key: []const u8) !?u64 {
432 const value = object.get(key) orelse return null;
433 return try jsonU64(value);
434 }
435
436 fn jsonU32Optional(object: std.json.ObjectMap, key: []const u8) !?u32 {
437 const value = object.get(key) orelse return null;
438 return @intCast(try jsonU64(value));
439 }
440
441 fn jsonI64(value: std.json.Value) !i64 {
442 return switch (value) {
443 .integer => |integer| @intCast(integer),
444 else => error.InvalidEventJson,
445 };
446 }
447
448 fn jsonI64Optional(object: std.json.ObjectMap, key: []const u8) !?i64 {
449 const value = object.get(key) orelse return null;
450 return try jsonI64(value);
451 }
452
453 fn jsonF64(value: std.json.Value) !f64 {
454 return switch (value) {
455 .integer => |integer| @floatFromInt(integer),
456 .float => |float| float,
457 else => error.InvalidEventJson,
458 };
459 }
460
461 fn jsonF64Optional(object: std.json.ObjectMap, key: []const u8) !?f64 {
462 const value = object.get(key) orelse return null;
463 return try jsonF64(value);
464 }
465
466 fn jsonBool(value: std.json.Value) !bool {
467 return switch (value) {
468 .bool => |boolean| boolean,
469 else => error.InvalidEventJson,
470 };
471 }
472
473 fn jsonBoolDefault(object: std.json.ObjectMap, key: []const u8, default: bool) !bool {
474 const value = object.get(key) orelse return default;
475 return try jsonBool(value);
476 }
477
478 test "event json parses written zone begin" {
479 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
480 defer out.deinit();
481
482 try (TraceEvent{
483 .seq = 7,
484 .kind = .zone_begin,
485 .time_ns = 100,
486 .thread = 2,
487 .id = 9,
488 .name = "phase",
489 .function = "run",
490 .file = "run.zig",
491 .line = 12,
492 .color = 0x44aa88,
493 }).writeJsonLine(&out.writer);
494
495 var parsed = try parseLine(std.testing.allocator, out.written());
496 defer parsed.deinit();
497 try std.testing.expectEqual(Kind.zone_begin, parsed.kind);
498 try std.testing.expectEqual(@as(u64, 9), parsed.id);
499 try std.testing.expectEqualStrings("phase", parsed.name.?);
500 try std.testing.expectEqualStrings("run.zig", parsed.file.?);
501 try std.testing.expectEqual(@as(?u32, 0x44aa88), parsed.color);
502 }
503
504 test "event json parses sample frame metadata" {
505 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
506 defer out.deinit();
507
508 try (TraceEvent{
509 .seq = 8,
510 .kind = .sample_frame,
511 .time_ns = 120,
512 .thread = 3,
513 .id = 4,
514 .depth = 2,
515 .name = "leaf",
516 .function = "run",
517 .file = "run.zig",
518 .line = 14,
519 .sample_kind = "cpu",
520 }).writeJsonLine(&out.writer);
521
522 var parsed = try parseLine(std.testing.allocator, out.written());
523 defer parsed.deinit();
524 try std.testing.expectEqual(Kind.sample_frame, parsed.kind);
525 try std.testing.expectEqual(@as(u32, 2), parsed.depth);
526 try std.testing.expectEqualStrings("cpu", parsed.sample_kind.?);
527 }
528
529 test "event json parses context switch metadata" {
530 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
531 defer out.deinit();
532
533 try (TraceEvent{
534 .seq = 9,
535 .kind = .context_switch,
536 .time_ns = 150,
537 .thread = 3,
538 .cpu = 0,
539 .prev_thread = 10,
540 .next_thread = 11,
541 .previous_cstate = 1,
542 .prev_priority = 8,
543 .next_priority = 9,
544 .context_reason = "WrPreempted",
545 .context_state = "Ready",
546 }).writeJsonLine(&out.writer);
547
548 var parsed = try parseLine(std.testing.allocator, out.written());
549 defer parsed.deinit();
550 try std.testing.expectEqual(Kind.context_switch, parsed.kind);
551 try std.testing.expectEqual(@as(?u32, 0), parsed.cpu);
552 try std.testing.expectEqual(@as(u64, 10), parsed.prev_thread);
553 try std.testing.expectEqual(@as(u64, 11), parsed.next_thread);
554 try std.testing.expectEqual(@as(?u32, 1), parsed.previous_cstate);
555 try std.testing.expectEqual(@as(?i64, 8), parsed.prev_priority);
556 try std.testing.expectEqual(@as(?i64, 9), parsed.next_priority);
557 try std.testing.expectEqualStrings("WrPreempted", parsed.context_reason.?);
558 try std.testing.expectEqualStrings("Ready", parsed.context_state.?);
559 }
560
561 test "event json parses fiber metadata" {
562 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
563 defer out.deinit();
564
565 try (TraceEvent{
566 .seq = 10,
567 .kind = .fiber_enter,
568 .time_ns = 200,
569 .thread = 42,
570 .id = 9,
571 .name = "job.apply",
572 .group_hint = 3,
573 }).writeJsonLine(&out.writer);
574
575 var parsed = try parseLine(std.testing.allocator, out.written());
576 defer parsed.deinit();
577 try std.testing.expectEqual(Kind.fiber_enter, parsed.kind);
578 try std.testing.expectEqual(@as(u64, 9), parsed.id);
579 try std.testing.expectEqual(@as(u64, 42), parsed.thread);
580 try std.testing.expectEqualStrings("job.apply", parsed.name.?);
581 try std.testing.expectEqual(@as(?i64, 3), parsed.group_hint);
582 }
583
584 test "event json parses gpu metadata" {
585 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
586 defer out.deinit();
587
588 try (TraceEvent{
589 .seq = 11,
590 .kind = .gpu_context,
591 .time_ns = 300,
592 .thread = 4,
593 .name = "render",
594 .gpu_context = 2,
595 .gpu_time = 1000,
596 .gpu_period = 2.0,
597 .gpu_context_type = "vulkan",
598 .gpu_has_calibration = true,
599 .gpu_serial = true,
600 }).writeJsonLine(&out.writer);
601
602 var parsed = try parseLine(std.testing.allocator, out.written());
603 defer parsed.deinit();
604 try std.testing.expectEqual(Kind.gpu_context, parsed.kind);
605 try std.testing.expectEqual(@as(?u32, 2), parsed.gpu_context);
606 try std.testing.expectEqual(@as(?i64, 1000), parsed.gpu_time);
607 try std.testing.expectEqual(@as(?f64, 2.0), parsed.gpu_period);
608 try std.testing.expectEqualStrings("vulkan", parsed.gpu_context_type.?);
609 try std.testing.expect(parsed.gpu_has_calibration);
610 try std.testing.expect(parsed.gpu_serial);
611 }
612
613 test "event json parses system metadata" {
614 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
615 defer out.deinit();
616
617 try (TraceEvent{
618 .seq = 12,
619 .kind = .hw_sample,
620 .time_ns = 400,
621 .thread = 9,
622 .cpu = 3,
623 .address = 0x1234,
624 .pid = 77,
625 .cpu_package = 1,
626 .cpu_die = 2,
627 .cpu_core = 4,
628 .power_delta_uj = 500,
629 .hw_sample_kind = "cache-miss",
630 }).writeJsonLine(&out.writer);
631
632 var parsed = try parseLine(std.testing.allocator, out.written());
633 defer parsed.deinit();
634 try std.testing.expectEqual(Kind.hw_sample, parsed.kind);
635 try std.testing.expectEqual(@as(u64, 9), parsed.thread);
636 try std.testing.expectEqual(@as(?u32, 3), parsed.cpu);
637 try std.testing.expectEqual(@as(u64, 0x1234), parsed.address);
638 try std.testing.expectEqual(@as(u64, 77), parsed.pid);
639 try std.testing.expectEqual(@as(?u32, 1), parsed.cpu_package);
640 try std.testing.expectEqual(@as(?u32, 2), parsed.cpu_die);
641 try std.testing.expectEqual(@as(?u32, 4), parsed.cpu_core);
642 try std.testing.expectEqual(@as(?u64, 500), parsed.power_delta_uj);
643 try std.testing.expectEqualStrings("cache-miss", parsed.hw_sample_kind.?);
644 }