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 }