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 }