lib/sql/src/history/instrumentation.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const Phase = enum {
4 batch_load,
5 hash_verification,
6 index_rebuild,
7 };
8
9 pub const Receipt = struct {
10 sequence: usize,
11 batch: usize,
12 phase: Phase,
13 duration_ns: u64,
14 bytes: usize,
15 records: usize,
16 };
17
18 pub const Recorder = struct {
19 storage: []Receipt,
20 len: usize = 0,
21 required: usize = 0,
22
23 pub fn init(storage: []Receipt) Recorder {
24 return .{ .storage = storage };
25 }
26
27 pub fn reset(self: *Recorder) void {
28 self.len = 0;
29 self.required = 0;
30 }
31
32 pub fn record(
33 self: *Recorder,
34 batch: usize,
35 phase: Phase,
36 duration_ns: u64,
37 bytes: usize,
38 records: usize,
39 ) void {
40 const sequence = self.required;
41 self.required += 1;
42 if (self.len == self.storage.len) return;
43 self.storage[self.len] = .{
44 .sequence = sequence,
45 .batch = batch,
46 .phase = phase,
47 .duration_ns = duration_ns,
48 .bytes = bytes,
49 .records = records,
50 };
51 self.len += 1;
52 }
53
54 pub fn receipts(self: *const Recorder) []const Receipt {
55 return self.storage[0..self.len];
56 }
57
58 pub fn complete(self: *const Recorder) bool {
59 return self.len != 0 and self.len == self.required;
60 }
61
62 pub fn ordered(self: *const Recorder) bool {
63 if (!self.complete() or self.len % 3 != 0) return false;
64 for (self.receipts(), 0..) |receipt, sequence| {
65 if (receipt.sequence != sequence) return false;
66 if (receipt.batch != sequence / 3) return false;
67 const expected: Phase = switch (sequence % 3) {
68 0 => .batch_load,
69 1 => .hash_verification,
70 2 => .index_rebuild,
71 else => unreachable,
72 };
73 if (receipt.phase != expected) return false;
74 }
75 return true;
76 }
77 };
78
79 pub fn elapsed(start_ns: i128, end_ns: i128) u64 {
80 if (end_ns <= start_ns) return 0;
81 return @intCast(@min(
82 @as(u128, @intCast(end_ns - start_ns)),
83 std.math.maxInt(u64),
84 ));
85 }
86
87 test "replay recorder rejects incomplete storage and preserves phase order" {
88 var complete_storage: [6]Receipt = undefined;
89 var complete = Recorder.init(&complete_storage);
90 inline for (0..2) |batch| {
91 complete.record(batch, .batch_load, 1, 16, 2);
92 complete.record(batch, .hash_verification, 2, 16, 2);
93 complete.record(batch, .index_rebuild, 3, 16, 2);
94 }
95 try std.testing.expect(complete.complete());
96 try std.testing.expect(complete.ordered());
97
98 var short_storage: [2]Receipt = undefined;
99 var short = Recorder.init(&short_storage);
100 short.record(0, .batch_load, 1, 16, 2);
101 short.record(0, .hash_verification, 2, 16, 2);
102 short.record(0, .index_rebuild, 3, 16, 2);
103 try std.testing.expect(!short.complete());
104 try std.testing.expect(!short.ordered());
105 }