lib/trace/src/store/reader/capacity.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const format = @import("../format/root.zig");
  3 const block = format.block;
  4 const database = format.database;
  5 
  6 pub const default_json_nesting: usize = 32;
  7 pub const maximum_json_nesting: usize = 256;
  8 
  9 pub const Limits = struct {
 10     root_path_bytes: usize,
 11     event_bytes: usize = (format.Limits{}).max_event_bytes,
 12     chunk_bytes: usize = (format.Limits{}).max_chunk_bytes,
 13     json_nesting: usize = default_json_nesting,
 14 
 15     pub fn fromOpen(root_path: []const u8, trace_limits: format.Limits) Limits {
 16         return .{
 17             .root_path_bytes = root_path.len,
 18             .event_bytes = trace_limits.max_event_bytes,
 19             .chunk_bytes = trace_limits.max_chunk_bytes,
 20         };
 21     }
 22 };
 23 
 24 pub const DeriveError = error{
 25     CapacityOverflow,
 26     RootPathStorageTooSmall,
 27     EventStorageTooSmall,
 28     EventStorageTooLarge,
 29     ChunkAdmissionTooSmall,
 30     ChunkAdmissionTooLarge,
 31     JsonNestingTooSmall,
 32     JsonNestingTooLarge,
 33 };
 34 
 35 pub const Capacity = struct {
 36     root_path_bytes: usize,
 37     root_path_offset: usize,
 38     event_bytes: usize,
 39     chunk_bytes: usize,
 40     block_bytes: usize,
 41     input_bytes: usize,
 42     input_offset: usize,
 43     path_bytes: usize,
 44     path_offset: usize,
 45     json_nesting: usize,
 46     json_stack_bytes: usize,
 47     json_stack_offset: usize,
 48     database: database.Capacity,
 49     sql_bytes: usize,
 50     sql_offset: usize,
 51     storage_bytes: usize,
 52 
 53     pub fn derive(limits: Limits) DeriveError!Capacity {
 54         if (limits.root_path_bytes == 0) return error.RootPathStorageTooSmall;
 55         if (limits.event_bytes == 0) return error.EventStorageTooSmall;
 56         if (limits.event_bytes > format.Limits.maximum_event_bytes) return error.EventStorageTooLarge;
 57         if (limits.chunk_bytes < limits.event_bytes) return error.ChunkAdmissionTooSmall;
 58         if (limits.chunk_bytes > format.Limits.maximum_chunk_bytes) return error.ChunkAdmissionTooLarge;
 59         if (limits.json_nesting < 2) return error.JsonNestingTooSmall;
 60         if (limits.json_nesting > maximum_json_nesting) return error.JsonNestingTooLarge;
 61         const block_bytes = block.capacity(limits.event_bytes) catch return error.CapacityOverflow;
 62         const database_capacity = database.readerCapacity() catch return error.CapacityOverflow;
 63 
 64         var offset: usize = 0;
 65         const root_path_offset = offset;
 66         offset = try add(offset, limits.root_path_bytes);
 67         const input_offset = offset;
 68         const input_bytes = @max(block_bytes, format.max_manifest_bytes);
 69         offset = try add(offset, input_bytes);
 70         const path_offset = offset;
 71         const path_bytes = try add(
 72             try add(limits.root_path_bytes, 1),
 73             format.max_relative_path_bytes,
 74         );
 75         offset = try add(offset, path_bytes);
 76         const json_stack_offset = offset;
 77         const json_stack_bytes = (try add(limits.json_nesting, 7)) / 8;
 78         offset = try add(offset, json_stack_bytes);
 79         const sql_offset = offset;
 80         offset = try add(offset, database_capacity.heap_bytes);
 81         return .{
 82             .root_path_bytes = limits.root_path_bytes,
 83             .root_path_offset = root_path_offset,
 84             .event_bytes = limits.event_bytes,
 85             .chunk_bytes = limits.chunk_bytes,
 86             .block_bytes = block_bytes,
 87             .input_bytes = input_bytes,
 88             .input_offset = input_offset,
 89             .path_bytes = path_bytes,
 90             .path_offset = path_offset,
 91             .json_nesting = limits.json_nesting,
 92             .json_stack_bytes = json_stack_bytes,
 93             .json_stack_offset = json_stack_offset,
 94             .database = database_capacity,
 95             .sql_bytes = database_capacity.heap_bytes,
 96             .sql_offset = sql_offset,
 97             .storage_bytes = offset,
 98         };
 99     }
100 };
101 
102 fn add(left: usize, right: usize) DeriveError!usize {
103     return std.math.add(usize, left, right) catch error.CapacityOverflow;
104 }
105 
106 test "trace reader capacity matches independent byte arithmetic" {
107     comptime {
108         @stardustClaim(
109             @import("alloc_phase").capacity.witness(@import("./root.zig").TraceReaderStorage, "trace_trace_reader_capacity"),
110             null,
111             null,
112             null,
113             null,
114             null,
115             null,
116         );
117     }
118 
119     const capacity = try Capacity.derive(.{
120         .root_path_bytes = 13,
121         .event_bytes = 31,
122         .chunk_bytes = 37,
123         .json_nesting = 17,
124     });
125     const block_bytes = try block.capacity(31);
126     const input_bytes = @max(block_bytes, format.max_manifest_bytes);
127     const path_bytes = 13 + 1 + format.max_relative_path_bytes;
128     const json_stack_bytes = 3;
129     const independent = 13 + input_bytes + path_bytes + json_stack_bytes + capacity.database.heap_bytes;
130     try std.testing.expectEqual(input_bytes, capacity.input_bytes);
131     try std.testing.expectEqual(block_bytes, capacity.block_bytes);
132     try std.testing.expectEqual(path_bytes, capacity.path_bytes);
133     try std.testing.expectEqual(json_stack_bytes, capacity.json_stack_bytes);
134     try std.testing.expectEqual(capacity.database.heap_bytes, capacity.sql_bytes);
135     try std.testing.expectEqual(independent, capacity.storage_bytes);
136     try std.testing.expectEqual(capacity.storage_bytes, capacity.sql_offset + capacity.sql_bytes);
137 }
138 
139 test "trace reader capacity rejects unusable and overflowing limits" {
140     try std.testing.expectError(error.RootPathStorageTooSmall, Capacity.derive(.{ .root_path_bytes = 0 }));
141     try std.testing.expectError(error.EventStorageTooSmall, Capacity.derive(.{ .root_path_bytes = 1, .event_bytes = 0 }));
142     try std.testing.expectError(error.EventStorageTooLarge, Capacity.derive(.{
143         .root_path_bytes = 1,
144         .event_bytes = format.Limits.maximum_event_bytes + 1,
145         .chunk_bytes = format.Limits.maximum_chunk_bytes,
146     }));
147     try std.testing.expectError(error.ChunkAdmissionTooSmall, Capacity.derive(.{
148         .root_path_bytes = 1,
149         .event_bytes = 2,
150         .chunk_bytes = 1,
151     }));
152     try std.testing.expectError(error.ChunkAdmissionTooLarge, Capacity.derive(.{
153         .root_path_bytes = 1,
154         .chunk_bytes = format.Limits.maximum_chunk_bytes + 1,
155     }));
156     try std.testing.expectError(error.JsonNestingTooSmall, Capacity.derive(.{
157         .root_path_bytes = 1,
158         .json_nesting = 1,
159     }));
160     try std.testing.expectError(error.JsonNestingTooLarge, Capacity.derive(.{
161         .root_path_bytes = 1,
162         .json_nesting = maximum_json_nesting + 1,
163     }));
164     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
165         .root_path_bytes = std.math.maxInt(usize),
166     }));
167 }