lib/memtrace/src/census/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const alloc_phase = @import("alloc_phase");
  3 const namespace_owner = @import("root.zig");
  4 
  5 const Capacity = namespace_owner.Capacity;
  6 const Census = namespace_owner.Census;
  7 const Limits = namespace_owner.Limits;
  8 const Storage = namespace_owner.Storage;
  9 
 10 fn testLimits() Limits {
 11     return .{
 12         .categories = 3,
 13         .label_bytes = 64,
 14         .joined_label_bytes = 64,
 15     };
 16 }
 17 
 18 test "Bounded Census summary and JSONL output preserve aggregate semantics" {
 19     comptime {
 20         @stardustClaim(
 21             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "memtrace_census_output"),
 22             null,
 23             null,
 24             null,
 25             null,
 26             null,
 27             null,
 28         );
 29     }
 30 
 31     var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{});
 32     const capacity = try Capacity.derive(testLimits());
 33     var storage = try Storage.init(counting.allocator(), testLimits());
 34     defer storage.deinit(counting.allocator());
 35     storage.activate();
 36     var census = try Census.init(&storage);
 37     defer census.deinit();
 38 
 39     try census.record("runtime.dictionary", 40);
 40     try census.record("runtime.dictionary", 2);
 41     try census.recordCount("runtime.string", 3, 9);
 42     try census.recordPrefixed("global.page.", "runtime.record", 4);
 43 
 44     var summary_bytes: [1024]u8 = undefined;
 45     var summary = std.Io.Writer.fixed(&summary_bytes);
 46     try census.writeSummary(&summary, .{ .top = 8 });
 47     const text = summary.buffered();
 48     const expected_summary =
 49         "memtrace census categories=3\n" ++
 50         "runtime.dictionary items=2 bytes=42\n" ++
 51         "runtime.string items=3 bytes=9\n" ++
 52         "global.page.runtime.record items=1 bytes=4\n";
 53     try std.testing.expectEqualStrings(expected_summary, text);
 54 
 55     var jsonl_bytes: [1024]u8 = undefined;
 56     var jsonl = std.Io.Writer.fixed(&jsonl_bytes);
 57     try census.writeJsonl(&jsonl);
 58     const expected_jsonl =
 59         "{\"v\":3,\"seq\":1,\"kind\":\"census\"," ++
 60         "\"label\":\"global.page.runtime.record\",\"items\":1,\"bytes\":4}\n" ++
 61         "{\"v\":3,\"seq\":2,\"kind\":\"census\"," ++
 62         "\"label\":\"runtime.string\",\"items\":3,\"bytes\":9}\n" ++
 63         "{\"v\":3,\"seq\":3,\"kind\":\"census\"," ++
 64         "\"label\":\"runtime.dictionary\",\"items\":2,\"bytes\":42}\n";
 65     try std.testing.expectEqualStrings(expected_jsonl, jsonl.buffered());
 66     try std.testing.expectEqual(@as(usize, 3), census.status().categories);
 67     try std.testing.expectEqual(@as(usize, 1), counting.alloc_index);
 68     try std.testing.expectEqual(capacity.storage_bytes, counting.allocated_bytes);
 69 }
 70 
 71 test "Census rejects each storage dimension at max plus one before mutation" {
 72     comptime {
 73         @stardustClaim(
 74             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "memtrace_census_boundary"),
 75             null,
 76             null,
 77             null,
 78             null,
 79             null,
 80             null,
 81         );
 82     }
 83 
 84     var storage = try Storage.init(std.testing.allocator, .{
 85         .categories = 2,
 86         .label_bytes = 5,
 87         .joined_label_bytes = 3,
 88     });
 89     defer storage.deinit(std.testing.allocator);
 90     storage.activate();
 91     var census = try Census.init(&storage);
 92     defer census.deinit();
 93 
 94     try census.record("ab", 1);
 95     try census.recordPrefixed("a", "bc", 2);
 96     const first = census.status();
 97     try std.testing.expectError(
 98         error.JoinedLabelCapacityExceeded,
 99         census.recordPrefixed("abc", "d", 1),
100     );
101     try std.testing.expectEqual(first, census.status());
102 
103     const full = census.status();
104     var full_bytes: [256]u8 = undefined;
105     var full_summary = std.Io.Writer.fixed(&full_bytes);
106     try census.writeSummary(&full_summary, .{});
107     try std.testing.expectError(error.CategoryCapacityExceeded, census.record("f", 3));
108     try std.testing.expectEqual(full, census.status());
109     var rejected_bytes: [256]u8 = undefined;
110     var rejected_summary = std.Io.Writer.fixed(&rejected_bytes);
111     try census.writeSummary(&rejected_summary, .{});
112     try std.testing.expectEqualSlices(u8, full_summary.buffered(), rejected_summary.buffered());
113 
114     var label_storage = try Storage.init(std.testing.allocator, .{
115         .categories = 2,
116         .label_bytes = 2,
117         .joined_label_bytes = 2,
118     });
119     defer label_storage.deinit(std.testing.allocator);
120     label_storage.activate();
121     var label_census = try Census.init(&label_storage);
122     defer label_census.deinit();
123     try label_census.record("ab", 1);
124     const label_full = label_census.status();
125     var label_full_bytes: [128]u8 = undefined;
126     var label_full_summary = std.Io.Writer.fixed(&label_full_bytes);
127     try label_census.writeSummary(&label_full_summary, .{});
128     try std.testing.expectError(error.LabelBytesCapacityExceeded, label_census.record("c", 1));
129     try std.testing.expectEqual(label_full, label_census.status());
130     var label_rejected_bytes: [128]u8 = undefined;
131     var label_rejected_summary = std.Io.Writer.fixed(&label_rejected_bytes);
132     try label_census.writeSummary(&label_rejected_summary, .{});
133     try std.testing.expectEqualSlices(
134         u8,
135         label_full_summary.buffered(),
136         label_rejected_summary.buffered(),
137     );
138 }
139 
140 test "Census rejects aggregate counter overflow before mutation" {
141     comptime {
142         @stardustClaim(
143             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "memtrace_census_overflow"),
144             null,
145             null,
146             null,
147             null,
148             null,
149             null,
150         );
151     }
152 
153     var storage = try Storage.init(std.testing.allocator, .{
154         .categories = 1,
155         .label_bytes = 1,
156         .joined_label_bytes = 0,
157     });
158     defer storage.deinit(std.testing.allocator);
159     storage.activate();
160     var census = try Census.init(&storage);
161     defer census.deinit();
162 
163     try census.recordCount("x", std.math.maxInt(usize), std.math.maxInt(usize));
164     const before = census.status();
165     var before_bytes: [128]u8 = undefined;
166     var before_summary = std.Io.Writer.fixed(&before_bytes);
167     try census.writeSummary(&before_summary, .{});
168     try std.testing.expectError(error.CensusCounterOverflow, census.record("x", 0));
169     try std.testing.expectEqual(before, census.status());
170     var after_bytes: [128]u8 = undefined;
171     var after_summary = std.Io.Writer.fixed(&after_bytes);
172     try census.writeSummary(&after_summary, .{});
173     try std.testing.expectEqualSlices(u8, before_summary.buffered(), after_summary.buffered());
174 }
175 
176 test "Census open addressing fills every admitted category" {
177     const category_capacity = 32;
178     var storage = try Storage.init(std.testing.allocator, .{
179         .categories = category_capacity,
180         .label_bytes = category_capacity * 4,
181         .joined_label_bytes = 0,
182     });
183     defer storage.deinit(std.testing.allocator);
184     storage.activate();
185     var census = try Census.init(&storage);
186     defer census.deinit();
187 
188     for (0..category_capacity) |index| {
189         var label_bytes: [4]u8 = undefined;
190         const label = try std.fmt.bufPrint(&label_bytes, "x{d:0>2}", .{index});
191         try census.record(label, index);
192     }
193     for (0..category_capacity) |index| {
194         var label_bytes: [4]u8 = undefined;
195         const label = try std.fmt.bufPrint(&label_bytes, "x{d:0>2}", .{index});
196         try census.record(label, 1);
197     }
198     try std.testing.expectEqual(@as(usize, category_capacity), census.status().categories);
199     try std.testing.expectEqual(
200         @as(usize, category_capacity * 3),
201         census.status().label_bytes_used,
202     );
203 }
204 
205 test "Census storage rejects concurrent acquisition and resets for reuse" {
206     comptime {
207         @stardustClaim(
208             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "memtrace_census_reuse"),
209             null,
210             null,
211             null,
212             null,
213             null,
214             null,
215         );
216     }
217 
218     var storage = try Storage.init(std.testing.allocator, testLimits());
219     defer storage.deinit(std.testing.allocator);
220     storage.activate();
221     var first = try Census.init(&storage);
222     try first.record("old", 7);
223     try std.testing.expectError(error.CensusStorageInUse, Census.init(&storage));
224     first.deinit();
225 
226     var reused = try Census.init(&storage);
227     defer reused.deinit();
228     try std.testing.expectEqual(@as(usize, 0), reused.status().categories);
229     try reused.record("new", 11);
230     try std.testing.expectEqual(@as(usize, 1), reused.status().categories);
231 }
232 
233 test "Maximum Census recording and output make no backing allocation after sealing" {
234     comptime {
235         @stardustClaim(
236             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "memtrace_census_sealed"),
237             null,
238             null,
239             null,
240             null,
241             null,
242             null,
243         );
244     }
245 
246     var sealed = try alloc_phase.SealedPhaseAllocator.init(std.testing.allocator);
247     var storage = try Storage.init(sealed.initializationAllocator(), .{
248         .categories = 3,
249         .label_bytes = 6,
250         .joined_label_bytes = 3,
251     });
252     storage.activate();
253     var census = try Census.init(&storage);
254     sealed.seal();
255 
256     try census.record("aa", 1);
257     try census.recordCount("bb", 2, 3);
258     try census.recordCountPrefixed("c", "c", 4, 5);
259     try census.record("aa", 7);
260     var summary_bytes: [512]u8 = undefined;
261     var summary = std.Io.Writer.fixed(&summary_bytes);
262     try census.writeSummary(&summary, .{});
263     var jsonl_bytes: [512]u8 = undefined;
264     var jsonl = std.Io.Writer.fixed(&jsonl_bytes);
265     try census.writeJsonl(&jsonl);
266     try std.testing.expectEqual(@as(usize, 3), census.status().categories);
267     try std.testing.expect(summary.buffered().len > 0);
268     try std.testing.expect(jsonl.buffered().len > 0);
269 
270     census.deinit();
271     sealed.beginTeardown();
272     storage.deinit(sealed.teardownAllocator());
273     sealed.deinit();
274 }