lib/tracy/src/runtime.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3 const build_options = @import("build_options");
4 const clock = @import("clock.zig");
5 const event = @import("event.zig");
6
7 pub const enabled = build_options.enabled;
8
9 pub const StartOptions = struct {
10 name: []const u8 = "tiny",
11 detail: ?[]const u8 = null,
12 };
13
14 pub const PlotUnit = union(enum) {
15 number,
16 nanoseconds,
17 bytes,
18 count,
19 percent,
20 hertz,
21 watts,
22 custom: []const u8,
23
24 pub fn tag(self: PlotUnit) []const u8 {
25 return switch (self) {
26 .number => "number",
27 .nanoseconds => "nanoseconds",
28 .bytes => "bytes",
29 .count => "count",
30 .percent => "percent",
31 .hertz => "hertz",
32 .watts => "watts",
33 .custom => |text| text,
34 };
35 }
36 };
37
38 pub const PlotConfig = struct {
39 unit: PlotUnit = .number,
40 step: bool = false,
41 fill: bool = false,
42 color: ?u32 = null,
43 };
44
45 pub const Zone = struct {
46 id: u64 = 0,
47 active: bool = false,
48
49 pub inline fn end(self: Zone) void {
50 if (enabled and self.active) global.endZone(self.id);
51 }
52
53 pub inline fn setText(self: Zone, text: []const u8) void {
54 if (enabled and self.active) global.zoneText(self.id, text);
55 }
56
57 pub inline fn setName(self: Zone, name: []const u8) void {
58 if (enabled and self.active) global.zoneName(self.id, name);
59 }
60
61 pub inline fn setColor(self: Zone, color: u32) void {
62 if (enabled and self.active) global.zoneColor(self.id, color);
63 }
64
65 pub inline fn setValue(self: Zone, value: u64) void {
66 if (enabled and self.active) global.zoneValue(self.id, value);
67 }
68 };
69
70 pub const LockKind = enum {
71 lock,
72 shared,
73
74 fn tag(self: LockKind) []const u8 {
75 return switch (self) {
76 .lock => "lock",
77 .shared => "shared",
78 };
79 }
80 };
81
82 pub const SampleKind = enum {
83 manual,
84 wall,
85 cpu,
86 context_switch,
87 hardware,
88
89 pub fn tag(self: SampleKind) []const u8 {
90 return switch (self) {
91 .manual => "manual",
92 .wall => "wall",
93 .cpu => "cpu",
94 .context_switch => "context-switch",
95 .hardware => "hardware",
96 };
97 }
98 };
99
100 pub const SampleFrame = struct {
101 name: ?[]const u8 = null,
102 function: ?[]const u8 = null,
103 file: ?[]const u8 = null,
104 line: u32 = 0,
105 column: u32 = 0,
106 address: u64 = 0,
107 };
108
109 pub const Fiber = struct {
110 id: u64 = 0,
111 name: ?[]const u8 = null,
112 group_hint: i32 = 0,
113 active: bool = false,
114
115 pub inline fn enter(self: Fiber) void {
116 if (enabled and self.active) global.enterFiber(self);
117 }
118
119 pub inline fn leave(self: Fiber) void {
120 if (enabled and self.active) global.leaveFiber(self.id);
121 }
122 };
123
124 pub const GpuContextType = enum {
125 unknown,
126 opengl,
127 vulkan,
128 direct3d11,
129 direct3d12,
130 metal,
131 custom,
132
133 pub fn tag(self: GpuContextType) ?[]const u8 {
134 return switch (self) {
135 .unknown => null,
136 .opengl => "opengl",
137 .vulkan => "vulkan",
138 .direct3d11 => "direct3d11",
139 .direct3d12 => "direct3d12",
140 .metal => "metal",
141 .custom => "custom",
142 };
143 }
144 };
145
146 pub const GpuContextOptions = struct {
147 name: ?[]const u8 = null,
148 context_type: GpuContextType = .unknown,
149 period: f64 = 1.0,
150 gpu_time: ?i64 = null,
151 has_calibration: bool = false,
152 serial: bool = false,
153 };
154
155 pub const GpuContext = struct {
156 id: u32 = 0,
157 active: bool = false,
158
159 pub inline fn zone(self: GpuContext, comptime name: [:0]const u8) GpuZone {
160 return self.zoneAt(name, @src());
161 }
162
163 pub inline fn zoneAt(self: GpuContext, comptime name: [:0]const u8, comptime src: std.builtin.SourceLocation) GpuZone {
164 if (enabled and self.active) return global.beginGpuZone(self.id, name, src, null, 0);
165 return .{};
166 }
167
168 pub inline fn zoneColor(self: GpuContext, comptime name: [:0]const u8, comptime color: u32) GpuZone {
169 return self.zoneColorAt(name, color, @src());
170 }
171
172 pub inline fn zoneColorAt(self: GpuContext, comptime name: [:0]const u8, comptime color: u32, comptime src: std.builtin.SourceLocation) GpuZone {
173 if (enabled and self.active) return global.beginGpuZone(self.id, name, src, color, 0);
174 return .{};
175 }
176
177 pub inline fn zoneQuery(self: GpuContext, comptime name: [:0]const u8, query: u32) GpuZone {
178 return self.zoneQueryAt(name, query, @src());
179 }
180
181 pub inline fn zoneQueryAt(self: GpuContext, comptime name: [:0]const u8, query: u32, comptime src: std.builtin.SourceLocation) GpuZone {
182 if (enabled and self.active) return global.beginGpuZone(self.id, name, src, null, query);
183 return .{};
184 }
185
186 pub inline fn zoneDynamic(self: GpuContext, name: []const u8) GpuZone {
187 if (enabled and self.active) return global.beginGpuZoneDynamic(self.id, name, 0);
188 return .{};
189 }
190
191 pub inline fn setName(self: GpuContext, name: []const u8) void {
192 if (enabled and self.active) global.gpuContextName(self.id, name);
193 }
194
195 pub inline fn nextQuery(self: GpuContext) u32 {
196 if (enabled and self.active) return global.nextGpuQuery();
197 return 0;
198 }
199
200 pub inline fn time(self: GpuContext, query: u32, gpu_time: i64) void {
201 if (enabled and self.active) global.gpuTime(self.id, query, gpu_time);
202 }
203
204 pub inline fn sync(self: GpuContext, gpu_time: i64) void {
205 if (enabled and self.active) global.gpuTimeSync(self.id, gpu_time);
206 }
207
208 pub inline fn calibrate(self: GpuContext, gpu_time: i64, cpu_delta_ns: i64) void {
209 if (enabled and self.active) global.gpuCalibration(self.id, gpu_time, cpu_delta_ns);
210 }
211
212 pub inline fn annotationName(self: GpuContext, id: i64, name: []const u8) void {
213 if (enabled and self.active) global.gpuAnnotationName(self.id, id, name);
214 }
215
216 pub inline fn annotate(self: GpuContext, id: i64, value: f64) void {
217 if (enabled and self.active) global.gpuAnnotation(self.id, 0, id, value);
218 }
219 };
220
221 pub const GpuZone = struct {
222 context: u32 = 0,
223 begin_query: u32 = 0,
224 active: bool = false,
225
226 pub inline fn end(self: GpuZone) void {
227 if (enabled and self.active) global.endGpuZone(self.context, 0, false);
228 }
229
230 pub inline fn endQuery(self: GpuZone, query: u32) void {
231 if (enabled and self.active) global.endGpuZone(self.context, query, false);
232 }
233
234 pub inline fn endSerial(self: GpuZone) void {
235 if (enabled and self.active) global.endGpuZone(self.context, 0, true);
236 }
237
238 pub inline fn endWithTimes(self: GpuZone, gpu_start: i64, gpu_end: i64) void {
239 if (enabled and self.active) global.endGpuZoneWithTimes(self.context, self.begin_query, 0, gpu_start, gpu_end, false);
240 }
241
242 pub inline fn endQueryWithTimes(self: GpuZone, query: u32, gpu_start: i64, gpu_end: i64) void {
243 if (enabled and self.active) global.endGpuZoneWithTimes(self.context, self.begin_query, query, gpu_start, gpu_end, false);
244 }
245
246 pub inline fn annotate(self: GpuZone, id: i64, value: f64) void {
247 if (enabled and self.active) global.gpuAnnotation(self.context, self.begin_query, id, value);
248 }
249 };
250
251 pub const HardwareSampleKind = enum {
252 cpu_cycle,
253 instruction_retired,
254 cache_reference,
255 cache_miss,
256 branch_retired,
257 branch_miss,
258
259 pub fn tag(self: HardwareSampleKind) []const u8 {
260 return switch (self) {
261 .cpu_cycle => "cpu-cycle",
262 .instruction_retired => "instruction-retired",
263 .cache_reference => "cache-reference",
264 .cache_miss => "cache-miss",
265 .branch_retired => "branch-retired",
266 .branch_miss => "branch-miss",
267 };
268 }
269 };
270
271 pub const CpuTopology = struct {
272 cpu: u32,
273 package: u32 = 0,
274 die: u32 = 0,
275 core: u32 = 0,
276 };
277
278 pub const ContextSwitchOptions = struct {
279 cpu: ?u32 = null,
280 prev_thread: u64 = 0,
281 next_thread: u64 = 0,
282 reason: ?[]const u8 = null,
283 state: ?[]const u8 = null,
284 previous_cstate: ?u32 = null,
285 prev_priority: ?i64 = null,
286 next_priority: ?i64 = null,
287 };
288
289 pub const ThreadWakeupOptions = struct {
290 thread: u64,
291 cpu: ?u32 = null,
292 reason: ?[]const u8 = null,
293 priority_increment: ?i64 = null,
294 };
295
296 pub const Lock = struct {
297 id: u64 = 0,
298 active: bool = false,
299
300 pub inline fn terminate(self: Lock) void {
301 if (enabled and self.active) global.lockEvent(self.id, .lock_terminate);
302 }
303
304 pub inline fn wait(self: Lock) void {
305 if (enabled and self.active) global.lockEvent(self.id, .lock_wait);
306 }
307
308 pub inline fn obtain(self: Lock) void {
309 if (enabled and self.active) global.lockEvent(self.id, .lock_obtain);
310 }
311
312 pub inline fn release(self: Lock) void {
313 if (enabled and self.active) global.lockEvent(self.id, .lock_release);
314 }
315
316 pub inline fn sharedWait(self: Lock) void {
317 if (enabled and self.active) global.lockEvent(self.id, .lock_shared_wait);
318 }
319
320 pub inline fn sharedObtain(self: Lock) void {
321 if (enabled and self.active) global.lockEvent(self.id, .lock_shared_obtain);
322 }
323
324 pub inline fn sharedRelease(self: Lock) void {
325 if (enabled and self.active) global.lockEvent(self.id, .lock_shared_release);
326 }
327
328 pub inline fn markAt(self: Lock, comptime src: std.builtin.SourceLocation) void {
329 if (enabled and self.active) global.lockMark(self.id, src);
330 }
331
332 pub inline fn setName(self: Lock, name: []const u8) void {
333 if (enabled and self.active) global.lockName(self.id, name);
334 }
335 };
336
337 const State = struct {
338 mutex: sys.thread.Mutex = .{},
339 writer: ?*std.Io.Writer = null,
340 active: bool = false,
341 failed: bool = false,
342 next_seq: u64 = 1,
343 next_zone_id: u64 = 1,
344 next_lock_id: u64 = 1,
345 next_sample_id: u64 = 1,
346 next_fiber_id: u64 = 1,
347 next_gpu_context_id: u32 = 1,
348 next_gpu_query_id: u32 = 1,
349
350 fn start(self: *State, writer: *std.Io.Writer, options: StartOptions) !bool {
351 self.lock();
352 defer self.unlock();
353 if (self.active) return error.TraceAlreadyStarted;
354 current_fiber_id = 0;
355 self.writer = writer;
356 self.active = true;
357 self.failed = false;
358 self.next_seq = 1;
359 self.next_zone_id = 1;
360 self.next_lock_id = 1;
361 self.next_sample_id = 1;
362 self.next_fiber_id = 1;
363 self.next_gpu_context_id = 1;
364 self.next_gpu_query_id = 1;
365 self.writePhysicalLocked(.{
366 .kind = .start,
367 .name = options.name,
368 .text = options.detail,
369 }) catch |err| {
370 self.disableLocked();
371 return err;
372 };
373 return true;
374 }
375
376 fn stop(self: *State) void {
377 self.lock();
378 defer self.unlock();
379 if (!self.active) return;
380 self.writePhysicalLocked(.{ .kind = .stop }) catch {};
381 if (self.writer) |writer| writer.flush() catch {};
382 self.writer = null;
383 self.active = false;
384 self.failed = false;
385 current_fiber_id = 0;
386 }
387
388 fn isActive(self: *State) bool {
389 self.lock();
390 defer self.unlock();
391 return self.active and !self.failed;
392 }
393
394 fn beginZone(
395 self: *State,
396 comptime name: [:0]const u8,
397 comptime src: std.builtin.SourceLocation,
398 color: ?u32,
399 ) Zone {
400 self.lock();
401 defer self.unlock();
402 if (!self.active or self.failed) return .{};
403 const id = self.next_zone_id;
404 self.next_zone_id +|= 1;
405 self.writeLocked(.{
406 .kind = .zone_begin,
407 .id = id,
408 .name = name,
409 .function = src.fn_name,
410 .file = src.file,
411 .line = src.line,
412 .column = src.column,
413 .color = color,
414 }) catch {
415 self.disableLocked();
416 return .{};
417 };
418 return .{ .id = id, .active = true };
419 }
420
421 fn announceLock(
422 self: *State,
423 comptime name: [:0]const u8,
424 comptime src: std.builtin.SourceLocation,
425 kind: LockKind,
426 ) Lock {
427 self.lock();
428 defer self.unlock();
429 if (!self.active or self.failed) return .{};
430 const id = self.next_lock_id;
431 self.next_lock_id +|= 1;
432 self.writeLocked(.{
433 .kind = .lock_announce,
434 .id = id,
435 .name = name,
436 .function = src.fn_name,
437 .file = src.file,
438 .line = src.line,
439 .column = src.column,
440 .lock_kind = kind.tag(),
441 }) catch {
442 self.disableLocked();
443 return .{};
444 };
445 return .{ .id = id, .active = true };
446 }
447
448 fn endZone(self: *State, id: u64) void {
449 self.emit(.{ .kind = .zone_end, .id = id });
450 }
451
452 fn zoneText(self: *State, id: u64, text: []const u8) void {
453 self.emit(.{ .kind = .zone_text, .id = id, .text = text });
454 }
455
456 fn zoneName(self: *State, id: u64, name: []const u8) void {
457 self.emit(.{ .kind = .zone_name, .id = id, .name = name });
458 }
459
460 fn zoneColor(self: *State, id: u64, color: u32) void {
461 self.emit(.{ .kind = .zone_color, .id = id, .color = color });
462 }
463
464 fn zoneValue(self: *State, id: u64, value: u64) void {
465 self.emit(.{ .kind = .zone_value, .id = id, .value_u64 = value });
466 }
467
468 fn lockEvent(self: *State, id: u64, kind: event.Kind) void {
469 self.emit(.{ .kind = kind, .id = id });
470 }
471
472 fn lockMark(self: *State, id: u64, comptime src: std.builtin.SourceLocation) void {
473 self.emit(.{
474 .kind = .lock_mark,
475 .id = id,
476 .function = src.fn_name,
477 .file = src.file,
478 .line = src.line,
479 .column = src.column,
480 });
481 }
482
483 fn lockName(self: *State, id: u64, name: []const u8) void {
484 self.emit(.{ .kind = .lock_name, .id = id, .name = name });
485 }
486
487 fn createFiber(self: *State, name: []const u8, group_hint: i32) Fiber {
488 self.lock();
489 defer self.unlock();
490 if (!self.active or self.failed) return .{};
491 const id = self.next_fiber_id;
492 self.next_fiber_id +|= 1;
493 const fiber_handle = Fiber{
494 .id = id,
495 .name = name,
496 .group_hint = group_hint,
497 .active = true,
498 };
499 self.writePhysicalLocked(.{
500 .kind = .fiber_name,
501 .id = id,
502 .name = name,
503 .group_hint = group_hint,
504 }) catch {
505 self.disableLocked();
506 return .{};
507 };
508 return fiber_handle;
509 }
510
511 fn enterFiber(self: *State, fiber_handle: Fiber) void {
512 self.lock();
513 defer self.unlock();
514 if (!self.active or self.failed) return;
515 self.writePhysicalLocked(.{
516 .kind = .fiber_enter,
517 .id = fiber_handle.id,
518 .name = fiber_handle.name,
519 .group_hint = fiber_handle.group_hint,
520 }) catch {
521 self.disableLocked();
522 return;
523 };
524 current_fiber_id = fiber_handle.id;
525 }
526
527 fn leaveFiber(self: *State, id: u64) void {
528 self.lock();
529 defer self.unlock();
530 if (!self.active or self.failed) return;
531 const actual_id = if (id != 0) id else current_fiber_id;
532 self.writePhysicalLocked(.{
533 .kind = .fiber_leave,
534 .id = actual_id,
535 }) catch {
536 self.disableLocked();
537 return;
538 };
539 if (current_fiber_id == actual_id) current_fiber_id = 0;
540 }
541
542 fn createGpuContext(self: *State, options: GpuContextOptions) GpuContext {
543 self.lock();
544 defer self.unlock();
545 if (!self.active or self.failed) return .{};
546 const id = self.next_gpu_context_id;
547 self.next_gpu_context_id +|= 1;
548 self.writePhysicalLocked(.{
549 .kind = .gpu_context,
550 .name = options.name,
551 .gpu_context = id,
552 .gpu_time = options.gpu_time,
553 .gpu_period = options.period,
554 .gpu_context_type = options.context_type.tag(),
555 .gpu_has_calibration = options.has_calibration,
556 .gpu_serial = options.serial,
557 }) catch {
558 self.disableLocked();
559 return .{};
560 };
561 return .{ .id = id, .active = true };
562 }
563
564 fn beginGpuZone(
565 self: *State,
566 context: u32,
567 comptime name: [:0]const u8,
568 comptime src: std.builtin.SourceLocation,
569 color: ?u32,
570 query: u32,
571 ) GpuZone {
572 self.lock();
573 defer self.unlock();
574 if (!self.active or self.failed) return .{};
575 const actual_query = self.gpuQueryLocked(query);
576 self.writePhysicalLocked(.{
577 .kind = .gpu_zone_begin,
578 .gpu_context = context,
579 .gpu_query = actual_query,
580 .name = name,
581 .function = src.fn_name,
582 .file = src.file,
583 .line = src.line,
584 .column = src.column,
585 .color = color,
586 }) catch {
587 self.disableLocked();
588 return .{};
589 };
590 return .{ .context = context, .begin_query = actual_query, .active = true };
591 }
592
593 fn beginGpuZoneDynamic(self: *State, context: u32, name: []const u8, query: u32) GpuZone {
594 self.lock();
595 defer self.unlock();
596 if (!self.active or self.failed) return .{};
597 const actual_query = self.gpuQueryLocked(query);
598 self.writePhysicalLocked(.{
599 .kind = .gpu_zone_begin,
600 .gpu_context = context,
601 .gpu_query = actual_query,
602 .name = name,
603 }) catch {
604 self.disableLocked();
605 return .{};
606 };
607 return .{ .context = context, .begin_query = actual_query, .active = true };
608 }
609
610 fn endGpuZone(self: *State, context: u32, query: u32, serial: bool) void {
611 self.lock();
612 defer self.unlock();
613 if (!self.active or self.failed) return;
614 const actual_query = self.gpuQueryLocked(query);
615 self.writePhysicalLocked(.{
616 .kind = .gpu_zone_end,
617 .gpu_context = context,
618 .gpu_query = actual_query,
619 .gpu_serial = serial,
620 }) catch self.disableLocked();
621 }
622
623 fn endGpuZoneWithTimes(self: *State, context: u32, begin_query: u32, end_query: u32, gpu_start: i64, gpu_end: i64, serial: bool) void {
624 self.lock();
625 defer self.unlock();
626 if (!self.active or self.failed) return;
627 const actual_end_query = self.gpuQueryLocked(end_query);
628 const end_time = clock.nowNs();
629 const thread = clock.threadId();
630 self.writeAtLocked(.{
631 .kind = .gpu_zone_end,
632 .gpu_context = context,
633 .gpu_query = actual_end_query,
634 .gpu_serial = serial,
635 }, end_time, thread) catch {
636 self.disableLocked();
637 return;
638 };
639 self.writeAtLocked(.{
640 .kind = .gpu_time,
641 .gpu_context = context,
642 .gpu_query = begin_query,
643 .gpu_time = gpu_start,
644 }, end_time, thread) catch {
645 self.disableLocked();
646 return;
647 };
648 self.writeAtLocked(.{
649 .kind = .gpu_time,
650 .gpu_context = context,
651 .gpu_query = actual_end_query,
652 .gpu_time = gpu_end,
653 }, end_time, thread) catch {
654 self.disableLocked();
655 return;
656 };
657 }
658
659 fn gpuContextName(self: *State, context: u32, name: []const u8) void {
660 self.emitPhysical(.{ .kind = .gpu_context_name, .gpu_context = context, .name = name });
661 }
662
663 fn gpuTime(self: *State, context: u32, query: u32, gpu_time: i64) void {
664 self.emitPhysical(.{ .kind = .gpu_time, .gpu_context = context, .gpu_query = query, .gpu_time = gpu_time });
665 }
666
667 fn gpuTimeSync(self: *State, context: u32, gpu_time: i64) void {
668 self.emitPhysical(.{ .kind = .gpu_time_sync, .gpu_context = context, .gpu_time = gpu_time });
669 }
670
671 fn gpuCalibration(self: *State, context: u32, gpu_time: i64, cpu_delta_ns: i64) void {
672 self.emitPhysical(.{
673 .kind = .gpu_calibration,
674 .gpu_context = context,
675 .gpu_time = gpu_time,
676 .gpu_cpu_delta_ns = cpu_delta_ns,
677 });
678 }
679
680 fn gpuAnnotationName(self: *State, context: u32, id: i64, name: []const u8) void {
681 self.emitPhysical(.{
682 .kind = .gpu_annotation_name,
683 .gpu_context = context,
684 .gpu_annotation_id = id,
685 .name = name,
686 });
687 }
688
689 fn gpuAnnotation(self: *State, context: u32, query: u32, id: i64, value: f64) void {
690 if (!std.math.isFinite(value)) return;
691 self.emitPhysical(.{
692 .kind = .gpu_annotation,
693 .gpu_context = context,
694 .gpu_query = if (query == 0) null else query,
695 .gpu_annotation_id = id,
696 .value_f64 = value,
697 });
698 }
699
700 fn nextGpuQuery(self: *State) u32 {
701 self.lock();
702 defer self.unlock();
703 if (!self.active or self.failed) return 0;
704 return self.gpuQueryLocked(0);
705 }
706
707 fn recordThreadContext(self: *State, thread: u64, name: ?[]const u8) void {
708 self.emitForThread(thread, .{
709 .kind = .thread_context,
710 .name = name,
711 });
712 }
713
714 fn recordThreadPid(self: *State, thread: u64, pid: u64) void {
715 self.emitForThread(thread, .{
716 .kind = .thread_pid,
717 .pid = pid,
718 });
719 }
720
721 fn recordCpuTopology(self: *State, topology: CpuTopology) void {
722 self.emitPhysical(.{
723 .kind = .cpu_topology,
724 .cpu = topology.cpu,
725 .cpu_package = topology.package,
726 .cpu_die = topology.die,
727 .cpu_core = topology.core,
728 });
729 }
730
731 fn recordSystemTime(self: *State, value: f64) void {
732 if (!std.math.isFinite(value)) return;
733 self.emitPhysical(.{
734 .kind = .sys_time,
735 .value_f64 = value,
736 });
737 }
738
739 fn recordSystemPower(self: *State, name: []const u8, delta_uj: u64) void {
740 self.emitPhysical(.{
741 .kind = .sys_power,
742 .name = name,
743 .power_delta_uj = delta_uj,
744 });
745 }
746
747 fn recordHardwareSample(self: *State, kind: HardwareSampleKind, address: u64) void {
748 self.emitPhysical(.{
749 .kind = .hw_sample,
750 .address = address,
751 .hw_sample_kind = kind.tag(),
752 });
753 }
754
755 fn recordSampleAt(
756 self: *State,
757 comptime name: [:0]const u8,
758 kind: SampleKind,
759 comptime src: std.builtin.SourceLocation,
760 weight: u64,
761 ) void {
762 self.lock();
763 defer self.unlock();
764 if (!self.active or self.failed) return;
765 const id = self.next_sample_id;
766 self.next_sample_id +|= 1;
767 const time_ns = clock.nowNs();
768 const thread = clock.threadId();
769 self.writeAtLocked(.{
770 .kind = .sample,
771 .id = id,
772 .name = name,
773 .function = src.fn_name,
774 .file = src.file,
775 .line = src.line,
776 .column = src.column,
777 .value_u64 = sampleWeight(weight),
778 .sample_kind = kind.tag(),
779 }, time_ns, thread) catch {
780 self.disableLocked();
781 return;
782 };
783 self.writeAtLocked(.{
784 .kind = .sample_frame,
785 .id = id,
786 .name = name,
787 .function = src.fn_name,
788 .file = src.file,
789 .line = src.line,
790 .column = src.column,
791 .sample_kind = kind.tag(),
792 }, time_ns, thread) catch {
793 self.disableLocked();
794 return;
795 };
796 }
797
798 fn recordSampleStack(self: *State, name: []const u8, kind: SampleKind, frames: []const SampleFrame, weight: u64) void {
799 self.lock();
800 defer self.unlock();
801 if (!self.active or self.failed) return;
802 const id = self.next_sample_id;
803 self.next_sample_id +|= 1;
804 const time_ns = clock.nowNs();
805 const thread = clock.threadId();
806 const leaf: ?SampleFrame = if (frames.len == 0) null else frames[frames.len - 1];
807 self.writeAtLocked(.{
808 .kind = .sample,
809 .id = id,
810 .name = name,
811 .function = if (leaf) |frame_info| frame_info.function else null,
812 .file = if (leaf) |frame_info| frame_info.file else null,
813 .line = if (leaf) |frame_info| frame_info.line else 0,
814 .column = if (leaf) |frame_info| frame_info.column else 0,
815 .address = if (leaf) |frame_info| frame_info.address else 0,
816 .value_u64 = sampleWeight(weight),
817 .sample_kind = kind.tag(),
818 }, time_ns, thread) catch {
819 self.disableLocked();
820 return;
821 };
822 for (frames, 0..) |frame_info, index| {
823 self.writeAtLocked(.{
824 .kind = .sample_frame,
825 .id = id,
826 .depth = @intCast(index),
827 .name = frame_info.name,
828 .function = frame_info.function,
829 .file = frame_info.file,
830 .line = frame_info.line,
831 .column = frame_info.column,
832 .address = frame_info.address,
833 .sample_kind = kind.tag(),
834 }, time_ns, thread) catch {
835 self.disableLocked();
836 return;
837 };
838 }
839 }
840
841 fn recordContextSwitch(self: *State, options: ContextSwitchOptions) void {
842 self.emit(.{
843 .kind = .context_switch,
844 .cpu = options.cpu,
845 .prev_thread = options.prev_thread,
846 .next_thread = options.next_thread,
847 .context_reason = options.reason,
848 .context_state = options.state,
849 .previous_cstate = options.previous_cstate,
850 .prev_priority = options.prev_priority,
851 .next_priority = options.next_priority,
852 });
853 }
854
855 fn recordThreadWakeup(self: *State, options: ThreadWakeupOptions) void {
856 self.emit(.{
857 .kind = .thread_wakeup,
858 .target_thread = options.thread,
859 .cpu = options.cpu,
860 .context_reason = options.reason,
861 .value_i64 = options.priority_increment,
862 });
863 }
864
865 fn emit(self: *State, trace_event: event.TraceEvent) void {
866 self.lock();
867 defer self.unlock();
868 if (!self.active or self.failed) return;
869 self.writeLocked(trace_event) catch self.disableLocked();
870 }
871
872 fn emitPhysical(self: *State, trace_event: event.TraceEvent) void {
873 self.lock();
874 defer self.unlock();
875 if (!self.active or self.failed) return;
876 self.writePhysicalLocked(trace_event) catch self.disableLocked();
877 }
878
879 fn emitForThread(self: *State, thread: u64, trace_event: event.TraceEvent) void {
880 self.lock();
881 defer self.unlock();
882 if (!self.active or self.failed) return;
883 self.writeAtLocked(trace_event, clock.nowNs(), thread) catch self.disableLocked();
884 }
885
886 fn gpuQueryLocked(self: *State, query: u32) u32 {
887 if (query != 0) return query;
888 const actual = self.next_gpu_query_id;
889 self.next_gpu_query_id +|= 1;
890 return actual;
891 }
892
893 fn writeLocked(self: *State, trace_event: event.TraceEvent) !void {
894 try self.writeAtLocked(trace_event, clock.nowNs(), logicalThreadId());
895 }
896
897 fn writePhysicalLocked(self: *State, trace_event: event.TraceEvent) !void {
898 try self.writeAtLocked(trace_event, clock.nowNs(), clock.threadId());
899 }
900
901 fn writeAtLocked(self: *State, trace_event: event.TraceEvent, time_ns: u64, thread: u64) !void {
902 const writer = self.writer orelse return error.TraceNotStarted;
903 var output = trace_event;
904 output.seq = self.next_seq;
905 output.time_ns = time_ns;
906 output.thread = thread;
907 self.next_seq +|= 1;
908 try output.writeJsonLine(writer);
909 }
910
911 fn disableLocked(self: *State) void {
912 self.failed = true;
913 self.active = false;
914 self.writer = null;
915 }
916
917 fn lock(self: *State) void {
918 self.mutex.lock();
919 }
920
921 fn unlock(self: *State) void {
922 self.mutex.unlock();
923 }
924 };
925
926 var global: State = .{};
927 threadlocal var current_fiber_id: u64 = 0;
928
929 pub fn start(writer: *std.Io.Writer, options: StartOptions) !bool {
930 if (!enabled) return false;
931 return try global.start(writer, options);
932 }
933
934 pub fn stop() void {
935 if (enabled) global.stop();
936 }
937
938 pub fn isActive() bool {
939 if (!enabled) return false;
940 return global.isActive();
941 }
942
943 pub inline fn zoneAt(
944 comptime name: [:0]const u8,
945 comptime src: std.builtin.SourceLocation,
946 color: ?u32,
947 ) Zone {
948 if (!enabled) return .{};
949 return global.beginZone(name, src, color);
950 }
951
952 pub inline fn lockableAt(
953 comptime name: [:0]const u8,
954 comptime src: std.builtin.SourceLocation,
955 ) Lock {
956 if (!enabled) return .{};
957 return global.announceLock(name, src, .lock);
958 }
959
960 pub inline fn sharedLockableAt(
961 comptime name: [:0]const u8,
962 comptime src: std.builtin.SourceLocation,
963 ) Lock {
964 if (!enabled) return .{};
965 return global.announceLock(name, src, .shared);
966 }
967
968 pub fn frame(name: ?[]const u8) void {
969 if (!enabled) return;
970 global.emit(.{ .kind = .frame, .name = name });
971 }
972
973 pub fn message(text: []const u8) void {
974 if (!enabled) return;
975 global.emit(.{ .kind = .message, .text = text });
976 }
977
978 pub fn appInfo(text: []const u8) void {
979 if (!enabled) return;
980 global.emit(.{ .kind = .app_info, .text = text });
981 }
982
983 pub fn plotConfig(name: []const u8, config: PlotConfig) void {
984 if (!enabled) return;
985 const unit = config.unit.tag();
986 std.debug.assert(unit.len > 0);
987 global.emit(.{
988 .kind = .plot_config,
989 .name = name,
990 .color = config.color,
991 .plot_unit = unit,
992 .plot_step = config.step,
993 .plot_fill = config.fill,
994 });
995 }
996
997 pub fn plotFloat(name: []const u8, value: f64) void {
998 if (!enabled or !std.math.isFinite(value)) return;
999 global.emit(.{
1000 .kind = .plot,
1001 .name = name,
1002 .value_f64 = value,
1003 .plot_kind = "float",
1004 });
1005 }
1006
1007 pub fn plotInt(name: []const u8, value: i64) void {
1008 if (!enabled) return;
1009 global.emit(.{
1010 .kind = .plot,
1011 .name = name,
1012 .value_i64 = value,
1013 .plot_kind = "int",
1014 });
1015 }
1016
1017 pub fn alloc(ptr: ?*const anyopaque, size: usize) void {
1018 if (!enabled) return;
1019 allocNamed(ptr, size, null);
1020 }
1021
1022 pub fn allocNamed(ptr: ?*const anyopaque, size: usize, name: ?[]const u8) void {
1023 if (!enabled) return;
1024 allocAddress(ptrAddress(ptr), size, name);
1025 }
1026
1027 pub fn allocAddress(address: u64, size: usize, name: ?[]const u8) void {
1028 if (!enabled) return;
1029 global.emit(.{
1030 .kind = .alloc,
1031 .name = name,
1032 .address = address,
1033 .size = @intCast(size),
1034 });
1035 }
1036
1037 pub fn free(ptr: ?*const anyopaque) void {
1038 if (!enabled) return;
1039 freeSizedNamed(ptr, 0, null);
1040 }
1041
1042 pub fn freeNamed(ptr: ?*const anyopaque, name: ?[]const u8) void {
1043 if (!enabled) return;
1044 freeSizedNamed(ptr, 0, name);
1045 }
1046
1047 pub fn freeSized(ptr: ?*const anyopaque, size: usize) void {
1048 if (!enabled) return;
1049 freeSizedNamed(ptr, size, null);
1050 }
1051
1052 pub fn freeSizedNamed(ptr: ?*const anyopaque, size: usize, name: ?[]const u8) void {
1053 if (!enabled) return;
1054 freeAddress(ptrAddress(ptr), size, name);
1055 }
1056
1057 pub fn freeAddress(address: u64, size: usize, name: ?[]const u8) void {
1058 if (!enabled) return;
1059 global.emit(.{
1060 .kind = .free,
1061 .name = name,
1062 .address = address,
1063 .size = @intCast(size),
1064 });
1065 }
1066
1067 pub fn threadName(name: []const u8) void {
1068 if (!enabled) return;
1069 global.lock();
1070 defer global.unlock();
1071 if (!global.active or global.failed) return;
1072 global.writePhysicalLocked(.{ .kind = .thread_name, .name = name }) catch global.disableLocked();
1073 }
1074
1075 pub fn fiber(name: []const u8, group_hint: i32) Fiber {
1076 if (!enabled) return .{};
1077 return global.createFiber(name, group_hint);
1078 }
1079
1080 pub fn gpuContext(options: GpuContextOptions) GpuContext {
1081 if (!enabled) return .{};
1082 return global.createGpuContext(options);
1083 }
1084
1085 fn ptrAddress(ptr: ?*const anyopaque) u64 {
1086 const actual = ptr orelse return 0;
1087 return @intCast(@intFromPtr(actual));
1088 }
1089
1090 fn sampleWeight(weight: u64) ?u64 {
1091 if (weight == 1) return null;
1092 return weight;
1093 }
1094
1095 pub inline fn sampleAt(
1096 comptime name: [:0]const u8,
1097 kind: SampleKind,
1098 comptime src: std.builtin.SourceLocation,
1099 ) void {
1100 if (!enabled) return;
1101 global.recordSampleAt(name, kind, src, 1);
1102 }
1103
1104 pub inline fn sampleWeightedAt(
1105 comptime name: [:0]const u8,
1106 kind: SampleKind,
1107 weight: u64,
1108 comptime src: std.builtin.SourceLocation,
1109 ) void {
1110 if (!enabled) return;
1111 global.recordSampleAt(name, kind, src, weight);
1112 }
1113
1114 pub fn sampleStack(name: []const u8, kind: SampleKind, frames: []const SampleFrame) void {
1115 if (!enabled) return;
1116 global.recordSampleStack(name, kind, frames, 1);
1117 }
1118
1119 pub fn sampleStackWeighted(name: []const u8, kind: SampleKind, frames: []const SampleFrame, weight: u64) void {
1120 if (!enabled) return;
1121 global.recordSampleStack(name, kind, frames, weight);
1122 }
1123
1124 pub fn contextSwitch(options: ContextSwitchOptions) void {
1125 if (!enabled) return;
1126 global.recordContextSwitch(options);
1127 }
1128
1129 pub fn threadWakeup(options: ThreadWakeupOptions) void {
1130 if (!enabled) return;
1131 global.recordThreadWakeup(options);
1132 }
1133
1134 pub fn threadContext(thread: u64, name: ?[]const u8) void {
1135 if (!enabled) return;
1136 global.recordThreadContext(thread, name);
1137 }
1138
1139 pub fn threadPid(thread: u64, pid: u64) void {
1140 if (!enabled) return;
1141 global.recordThreadPid(thread, pid);
1142 }
1143
1144 pub fn cpuTopology(topology: CpuTopology) void {
1145 if (!enabled) return;
1146 global.recordCpuTopology(topology);
1147 }
1148
1149 pub fn systemTime(value: f64) void {
1150 if (!enabled) return;
1151 global.recordSystemTime(value);
1152 }
1153
1154 pub fn systemPower(name: []const u8, delta_uj: u64) void {
1155 if (!enabled) return;
1156 global.recordSystemPower(name, delta_uj);
1157 }
1158
1159 pub fn hardwareSample(kind: HardwareSampleKind, address: u64) void {
1160 if (!enabled) return;
1161 global.recordHardwareSample(kind, address);
1162 }
1163
1164 fn logicalThreadId() u64 {
1165 if (current_fiber_id != 0) return current_fiber_id;
1166 return clock.threadId();
1167 }
1168
1169 test "disabled runtime start is inert" {
1170 if (enabled) return;
1171 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
1172 defer out.deinit();
1173 try std.testing.expect(!(try start(&out.writer, .{})));
1174 try std.testing.expectEqualStrings("", out.written());
1175 try std.testing.expect(!isActive());
1176 }