lib/bench/src/recovery.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const pretty_json = @import("pretty").json;
3 const sys = @import("sys");
4
5 const metric_schema = "tiny.profiling.metric/v1";
6 const output_env = "BENCH_JSONL";
7
8 pub const ProcessState = enum {
9 process_cold,
10 shared_process,
11 };
12
13 pub const CacheState = enum {
14 warm,
15 externally_controlled,
16 uncontrolled,
17 };
18
19 pub const Phase = struct {
20 sequence: usize,
21 name: []const u8,
22 duration_ns: u64,
23 bytes: u64,
24 records: u64,
25 };
26
27 pub const Point = struct {
28 name: []const u8,
29 operation: []const u8,
30 state_bytes: u64,
31 records: u64,
32 process_state: ProcessState,
33 cache_state: CacheState,
34 correctness_admitted: bool,
35 sample_ns: []const u64,
36 median_ns: u64,
37 alloc_count: ?u64,
38 alloc_bytes: ?u64,
39 phases: []const Phase,
40 };
41
42 pub fn appendEnvPoint(allocator: std.mem.Allocator, point: Point) !void {
43 const path = try sys.env.getOwned(allocator, output_env);
44 defer if (path) |value| allocator.free(value);
45 const actual = path orelse return;
46
47 var output: std.Io.Writer.Allocating = .init(allocator);
48 defer output.deinit();
49 try writePoint(&output.writer, point);
50 try sys.fs.appendFile(actual, &.{output.written()});
51 }
52
53 pub fn writePoint(writer: *std.Io.Writer, point: Point) !void {
54 if (point.sample_ns.len == 0) return error.MissingRecoverySamples;
55 if (point.state_bytes == 0) return error.MissingRecoveryState;
56
57 var stream = pretty_json.Writer.init(writer, .minified);
58 const object = try stream.object();
59 try object.field("schema", metric_schema);
60 try object.field("event", "recovery_curve_point");
61 try object.field("family", "timing");
62 try object.field("metric", "duration_ns");
63 try object.field("unit", "ns");
64 try object.field("aggregation", "fresh_process_sample");
65 try object.field("name", point.name);
66 try object.field("operation", point.operation);
67 try object.field("state_bytes", point.state_bytes);
68 try object.field("records", point.records);
69 try object.field("process_state", @tagName(point.process_state));
70 try object.field("cache_state", @tagName(point.cache_state));
71 try object.field("correctness_admitted", point.correctness_admitted);
72 const samples = try object.array("sample_ns");
73 for (point.sample_ns) |sample| try samples.element(sample);
74 try samples.end();
75 try object.field("median_ns", point.median_ns);
76 try object.field("alloc_count", point.alloc_count);
77 try object.field("alloc_bytes", point.alloc_bytes);
78 try object.field("peak_rss", "captured_by_profile_driver");
79 const phases = try object.array("phase_receipts");
80 for (point.phases) |phase| {
81 const receipt = try phases.object();
82 try receipt.field("sequence", phase.sequence);
83 try receipt.field("name", phase.name);
84 try receipt.field("duration_ns", phase.duration_ns);
85 try receipt.field("bytes", phase.bytes);
86 try receipt.field("records", phase.records);
87 try receipt.end();
88 }
89 try phases.end();
90 try object.endLine();
91 }
92
93 test "recovery curve point preserves premises and ordered phases" {
94 var output: std.Io.Writer.Allocating = .init(std.testing.allocator);
95 defer output.deinit();
96 try writePoint(&output.writer, .{
97 .name = "sql recovery 1",
98 .operation = "sql.history.replay",
99 .state_bytes = 4096,
100 .records = 3,
101 .process_state = .process_cold,
102 .cache_state = .warm,
103 .correctness_admitted = true,
104 .sample_ns = &.{ 10, 20 },
105 .median_ns = 15,
106 .alloc_count = 2,
107 .alloc_bytes = 64,
108 .phases = &.{.{
109 .sequence = 0,
110 .name = "batch_load",
111 .duration_ns = 4,
112 .bytes = 4096,
113 .records = 3,
114 }},
115 });
116 const text = output.written();
117 try std.testing.expect(std.mem.indexOf(u8, text, "\"state_bytes\":4096") != null);
118 try std.testing.expect(std.mem.indexOf(u8, text, "\"process_state\":\"process_cold\"") != null);
119 try std.testing.expect(std.mem.indexOf(u8, text, "\"phase_receipts\":[{") != null);
120 }