lib/tldr/src/trace.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const coz = @import("coz");
3 const model = @import("model.zig");
4
5 const stage_count = @typeInfo(model.ProductStage).@"enum".field_names.len;
6
7 pub const PhaseTimers = struct {
8 totals_ns: [stage_count]u64 = @as([stage_count]u64, @splat(0)),
9 calls: [stage_count]u64 = @as([stage_count]u64, @splat(0)),
10
11 pub fn record(self: *PhaseTimers, stage: model.ProductStage, elapsed_ns: u64) void {
12 const index = @backingInt(stage);
13 _ = @atomicRmw(u64, &self.totals_ns[index], .Add, elapsed_ns, .monotonic);
14 _ = @atomicRmw(u64, &self.calls[index], .Add, 1, .monotonic);
15 }
16
17 pub fn totalNs(self: *const PhaseTimers, stage: model.ProductStage) u64 {
18 return self.totals_ns[@backingInt(stage)];
19 }
20
21 pub fn callCount(self: *const PhaseTimers, stage: model.ProductStage) u64 {
22 return self.calls[@backingInt(stage)];
23 }
24
25 pub fn sumNs(self: *const PhaseTimers) u64 {
26 var sum: u64 = 0;
27 for (self.totals_ns) |value| sum += value;
28 return sum;
29 }
30 };
31
32 pub const Clock = *const fn () i128;
33
34 var active_timers: ?*PhaseTimers = null;
35 var active_clock: ?Clock = null;
36
37 pub fn beginPhaseTiming(timers: *PhaseTimers, clock: Clock) void {
38 active_timers = timers;
39 active_clock = clock;
40 }
41
42 pub fn endPhaseTiming() void {
43 active_timers = null;
44 active_clock = null;
45 }
46
47 pub inline fn scope(comptime name: []const u8) coz.Scope("tldr." ++ name) {
48 return coz.scope("tldr." ++ name);
49 }
50
51 pub inline fn product(comptime stage: model.ProductStage) ProductScope(stage) {
52 return ProductScope(stage).begin();
53 }
54
55 fn ProductScope(comptime stage: model.ProductStage) type {
56 const trace_name = "tldr." ++ stage.traceName();
57 const CozScope = coz.Scope(trace_name);
58 return struct {
59 coz_scope: CozScope,
60 start_ns: i128,
61
62 const Self = @This();
63
64 inline fn begin() Self {
65 return .{
66 .coz_scope = coz.scope(trace_name),
67 .start_ns = if (active_clock) |clock| clock() else 0,
68 };
69 }
70
71 pub inline fn end(self: Self) void {
72 if (active_timers) |timers| {
73 if (active_clock) |clock| {
74 const elapsed = clock() - self.start_ns;
75 timers.record(stage, if (elapsed < 0) 0 else @intCast(elapsed));
76 }
77 }
78 self.coz_scope.end();
79 }
80 };
81 }
82
83 test "trace phase markers compile" {
84 const phase = scope("test");
85 defer phase.end();
86 const product_phase = product(.manifest_recording);
87 defer product_phase.end();
88 }
89
90 fn zeroClock() i128 {
91 return 0;
92 }
93
94 test "phase timing accumulates while active" {
95 var timers = PhaseTimers{};
96 beginPhaseTiming(&timers, zeroClock);
97 {
98 const phase = product(.output_writing);
99 defer phase.end();
100 }
101 endPhaseTiming();
102 try std.testing.expectEqual(@as(u64, 1), timers.callCount(.output_writing));
103 try std.testing.expectEqual(@as(u64, 0), timers.callCount(.input_discovery));
104 }