lib/bench/src/timing.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("sys");
  3 
  4 pub const clock_probe_reads: u32 = 256;
  5 
  6 pub const ClockProbe = struct {
  7     source: []const u8 = "monotonic_awake",
  8     reads: u32,
  9     positive_deltas: u32,
 10     minimum_delta_ns: u64,
 11     span_ns: u64,
 12 };
 13 
 14 pub fn nowInstant() sys.time.AwakeInstant {
 15     return readInstant() catch @panic("awake clock unavailable");
 16 }
 17 
 18 pub fn readInstant() !sys.time.AwakeInstant {
 19     return sys.time.awakeNow();
 20 }
 21 
 22 pub fn nowNs() i128 {
 23     return @intCast(nowInstant().asNanoseconds());
 24 }
 25 
 26 pub fn probeClock() !ClockProbe {
 27     std.debug.assert(clock_probe_reads >= 2);
 28     var timestamps: [clock_probe_reads]sys.time.AwakeInstant = undefined;
 29     for (&timestamps) |*timestamp| timestamp.* = try readInstant();
 30     return analyzeClockProbe(&timestamps);
 31 }
 32 
 33 pub fn realNowNs() u64 {
 34     return (sys.time.wallNow() catch @panic("wall clock unavailable")).asNanoseconds();
 35 }
 36 
 37 pub fn elapsedNs(start: i128) u64 {
 38     const end = nowNs();
 39     return elapsedNsBetween(start, end);
 40 }
 41 
 42 pub fn elapsedNsBetween(start: i128, end: i128) u64 {
 43     if (end <= start) return 0;
 44     return saturatingU64(end - start);
 45 }
 46 
 47 pub fn nsOrZero(value: ?i128) u64 {
 48     return saturatingU64(value orelse return 0);
 49 }
 50 
 51 pub fn saturatingU64(value_int: anytype) u64 {
 52     if (value_int <= 0) return 0;
 53     if (value_int > std.math.maxInt(u64)) return std.math.maxInt(u64);
 54     return @intCast(value_int);
 55 }
 56 
 57 pub fn saturatingI64(value: u64) i64 {
 58     return @intCast(@min(value, std.math.maxInt(i64)));
 59 }
 60 
 61 fn analyzeClockProbe(timestamps: []const sys.time.AwakeInstant) !ClockProbe {
 62     if (timestamps.len < 2) return error.InsufficientClockReads;
 63     var minimum_delta: u64 = std.math.maxInt(u64);
 64     var positive_deltas: u32 = 0;
 65     var previous = timestamps[0];
 66     for (timestamps[1..]) |current| {
 67         const delta = current.elapsedSince(previous) catch return error.ClockRegressed;
 68         if (!delta.isZero()) {
 69             minimum_delta = @min(minimum_delta, delta.asNanoseconds());
 70             positive_deltas += 1;
 71         }
 72         previous = current;
 73     }
 74     if (positive_deltas == 0) return error.ClockNotAdvancing;
 75     return .{
 76         .reads = @intCast(timestamps.len),
 77         .positive_deltas = positive_deltas,
 78         .minimum_delta_ns = minimum_delta,
 79         .span_ns = (try timestamps[timestamps.len - 1].elapsedSince(timestamps[0])).asNanoseconds(),
 80     };
 81 }
 82 
 83 test "clock probe analysis retains positive minimum delta" {
 84     const Instant = sys.time.AwakeInstant;
 85     const probe = try analyzeClockProbe(&.{
 86         Instant.fromNanoseconds(100),
 87         Instant.fromNanoseconds(100),
 88         Instant.fromNanoseconds(103),
 89         Instant.fromNanoseconds(108),
 90         Instant.fromNanoseconds(108),
 91     });
 92 
 93     try std.testing.expectEqual(@as(u32, 5), probe.reads);
 94     try std.testing.expectEqual(@as(u32, 2), probe.positive_deltas);
 95     try std.testing.expectEqual(@as(u64, 3), probe.minimum_delta_ns);
 96     try std.testing.expectEqual(@as(u64, 8), probe.span_ns);
 97 }
 98 
 99 test "clock probe analysis rejects invalid clocks" {
100     const Instant = sys.time.AwakeInstant;
101     try std.testing.expectError(
102         error.InsufficientClockReads,
103         analyzeClockProbe(&.{Instant.fromNanoseconds(1)}),
104     );
105     try std.testing.expectError(error.ClockNotAdvancing, analyzeClockProbe(&.{
106         Instant.fromNanoseconds(5),
107         Instant.fromNanoseconds(5),
108         Instant.fromNanoseconds(5),
109     }));
110     try std.testing.expectError(error.ClockRegressed, analyzeClockProbe(&.{
111         Instant.fromNanoseconds(5),
112         Instant.fromNanoseconds(4),
113     }));
114 }
115 
116 test "host clock probe advances" {
117     if (!sys.time.supportsAwakeClock()) return error.SkipZigTest;
118     const probe = try probeClock();
119 
120     try std.testing.expectEqual(clock_probe_reads, probe.reads);
121     try std.testing.expect(probe.positive_deltas > 0);
122     try std.testing.expect(probe.minimum_delta_ns > 0);
123     try std.testing.expect(probe.span_ns >= probe.minimum_delta_ns);
124 }