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 }