lib/trace/src/store/format/manifest.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const pretty_json = @import("pretty").json;
3 const sys = @import("sys");
4 const event = @import("../../root.zig").event;
5 const database = @import("database.zig");
6
7 pub const manifest_path = "manifest.json";
8 pub const max_manifest_bytes: usize = 64 * 1024;
9 pub const max_relative_path_bytes: usize = @max(
10 @max(manifest_path.len, "manifest.pending".len),
11 @max(database.file_name.len, database.wal_file_name.len),
12 );
13 pub const checksum_seed: u64 = 0x7472_6163_652d_7631;
14
15 comptime {
16 std.debug.assert(manifest_path.len <= max_relative_path_bytes);
17 std.debug.assert("manifest.pending".len <= max_relative_path_bytes);
18 std.debug.assert(database.file_name.len <= max_relative_path_bytes);
19 std.debug.assert(database.wal_file_name.len <= max_relative_path_bytes);
20 std.debug.assert("snapshots".len <= max_relative_path_bytes);
21 }
22
23 pub const Limits = struct {
24 max_event_bytes: usize = 256 * 1024,
25 max_chunk_bytes: usize = 4 * 1024 * 1024,
26
27 pub const maximum_event_bytes: usize = 4 * 1024 * 1024;
28 pub const maximum_chunk_bytes: usize = 64 * 1024 * 1024;
29
30 pub fn validate(self: Limits) !void {
31 if (self.max_event_bytes == 0 or self.max_event_bytes > maximum_event_bytes) {
32 return error.InvalidTraceLimits;
33 }
34 if (self.max_chunk_bytes < self.max_event_bytes or self.max_chunk_bytes > maximum_chunk_bytes) {
35 return error.InvalidTraceLimits;
36 }
37 }
38 };
39
40 pub const Manifest = struct {
41 format_version: u32 = event.trace_format_version,
42 target_triple: []const u8 = "unknown",
43 build_id: []const u8 = "unknown",
44 pointer_width: u16 = sys.capabilities.current.pointerWidthBits(),
45 endian: []const u8 = sys.capabilities.current.endianName(),
46 mode: []const u8 = "record",
47 status: []const u8 = "open",
48 branch_id: u64 = 0,
49 limits: Limits = .{},
50 chunk_count: u64 = 0,
51 event_count: u64 = 0,
52 event_bytes: u64 = 0,
53 event_checksum: u64 = 0,
54 block_count: u64 = 0,
55 block_bytes: u64 = 0,
56
57 pub fn writeJson(self: Manifest, writer: *std.Io.Writer) !void {
58 var stream = pretty_json.Writer.init(writer, .indent_2);
59 const object = try stream.object();
60 try object.field("format_version", self.format_version);
61 try object.field("target_triple", self.target_triple);
62 try object.field("build_id", self.build_id);
63 try object.field("pointer_width", self.pointer_width);
64 try object.field("endian", self.endian);
65 try object.field("mode", self.mode);
66 try object.field("status", self.status);
67 try object.field("branch_id", self.branch_id);
68 const limits = try object.object("limits");
69 try limits.field("max_event_bytes", self.limits.max_event_bytes);
70 try limits.field("max_chunk_bytes", self.limits.max_chunk_bytes);
71 try limits.end();
72 try object.field("chunk_count", self.chunk_count);
73 try object.field("event_count", self.event_count);
74 try object.field("event_bytes", self.event_bytes);
75 try object.field("event_checksum", self.event_checksum);
76 try object.field("block_count", self.block_count);
77 try object.field("block_bytes", self.block_bytes);
78 try object.endLine();
79 }
80 };
81
82 pub const Header = struct {
83 format_version: u32,
84 status_closed: bool,
85 limits: Limits,
86 chunk_count: u64,
87 event_count: u64,
88 event_bytes: u64,
89 event_checksum: u64,
90 block_count: u64,
91 block_bytes: u64,
92 };
93
94 pub fn writeManifest(
95 manifest: Manifest,
96 manifest_path_value: []const u8,
97 pending_path: []const u8,
98 manifest_buffer: []u8,
99 file_buffer: []u8,
100 ) !void {
101 var manifest_writer = std.Io.Writer.fixed(manifest_buffer);
102 manifest.writeJson(&manifest_writer) catch return error.TraceManifestTooLarge;
103 var file = try sys.fs.createFile(pending_path, .{ .truncate = true });
104 var file_open = true;
105 defer if (file_open) file.close(sys.fs.debugIo());
106 var writer = file.writer(sys.fs.debugIo(), file_buffer);
107 try writer.interface.writeAll(manifest_writer.buffered());
108 try writer.interface.flush();
109 try file.sync(sys.fs.debugIo());
110 file.close(sys.fs.debugIo());
111 file_open = false;
112 try sys.fs.rename(pending_path, manifest_path_value);
113 }
114
115 pub fn newHasher() std.hash.Wyhash {
116 return std.hash.Wyhash.init(checksum_seed);
117 }
118
119 test "limits admit only traces the reader can bound" {
120 try (Limits{}).validate();
121 try std.testing.expectError(error.InvalidTraceLimits, (Limits{ .max_event_bytes = 0 }).validate());
122 try std.testing.expectError(error.InvalidTraceLimits, (Limits{ .max_event_bytes = 1024, .max_chunk_bytes = 512 }).validate());
123 try std.testing.expectError(error.InvalidTraceLimits, (Limits{ .max_event_bytes = Limits.maximum_event_bytes + 1, .max_chunk_bytes = Limits.maximum_chunk_bytes }).validate());
124 }
125
126 test "trace paths fit the format-derived buffer" {
127 try std.testing.expect(database.wal_file_name.len <= max_relative_path_bytes);
128 }
129
130 test "trace checksums own the trace format identity" {
131 try std.testing.expectEqual(@as(u64, 0x7472_6163_652d_7631), checksum_seed);
132 }