lib/memtrace/src/stack/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const observe = @import("alloc_observe");
  3 const memtrace = @import("../root.zig");
  4 const report = @import("report.zig");
  5 const roots = @import("roots.zig");
  6 
  7 noinline fn exactShared(allocator: std.mem.Allocator) ![]u8 {
  8     return try allocator.alloc(u8, 16);
  9 }
 10 
 11 noinline fn exactCallerA(allocator: std.mem.Allocator) ![]u8 {
 12     const bytes = try exactShared(allocator);
 13     std.mem.doNotOptimizeAway(bytes.ptr);
 14     return bytes;
 15 }
 16 
 17 noinline fn exactCallerB(allocator: std.mem.Allocator) ![]u8 {
 18     const bytes = try exactShared(allocator);
 19     std.mem.doNotOptimizeAway(bytes.len);
 20     return bytes;
 21 }
 22 
 23 test "exact allocation report verifies binary and separates shared callers" {
 24     var tracer = try memtrace.Tracer.init(std.testing.allocator, .{
 25         .record_events = true,
 26         .allocation_attribution = .stack,
 27     });
 28     defer tracer.deinit();
 29     var owned_observation: ?memtrace.OwnedObservation = if (comptime observe.enabled)
 30         try tracer.observeOwnedAllocators()
 31     else
 32         null;
 33     defer if (owned_observation) |*observation| observation.stop();
 34     var traced = try tracer.tracedAllocator(std.testing.allocator, "exact");
 35     const allocator = traced.allocator();
 36     var phase = try tracer.enter("phase");
 37     const first = try exactCallerA(allocator);
 38     phase.exit();
 39     const second = try exactCallerB(allocator);
 40     allocator.free(first);
 41     allocator.free(second);
 42 
 43     var temporary = std.testing.tmpDir(.{});
 44     defer temporary.cleanup();
 45     const root_path = try temporary.dir.realPathFileAlloc(
 46         std.Options.debug_io,
 47         ".",
 48         std.testing.allocator,
 49     );
 50     defer std.testing.allocator.free(root_path);
 51     const events_path = try std.fs.path.join(
 52         std.testing.allocator,
 53         &.{ root_path, "events.jsonl" },
 54     );
 55     defer std.testing.allocator.free(events_path);
 56     try tracer.writeEventsBundlePath(events_path);
 57 
 58     var output = std.Io.Writer.Allocating.init(std.testing.allocator);
 59     defer output.deinit();
 60     try report.writeFromPath(
 61         std.testing.allocator,
 62         events_path,
 63         &output.writer,
 64         .{ .top = 8, .frame_limit = 32 },
 65     );
 66     const expected_coverage = if (comptime observe.enabled)
 67         "status=process_memory_operation_incomplete " ++
 68             "universe=process_memory_operations_during_trace_epoch " ++
 69             "selection=allocations layer=backing calls=2"
 70     else
 71         "status=boundary_complete universe=registered_allocator_handles " ++
 72             "selection=allocations layer=backing calls=2";
 73     try std.testing.expect(std.mem.indexOf(
 74         u8,
 75         output.written(),
 76         expected_coverage,
 77     ) != null);
 78     try std.testing.expect(std.mem.indexOf(
 79         u8,
 80         output.written(),
 81         "exactCallerA",
 82     ) != null);
 83     try std.testing.expect(std.mem.indexOf(
 84         u8,
 85         output.written(),
 86         "exactCallerB",
 87     ) != null);
 88 
 89     var operations_output = std.Io.Writer.Allocating.init(std.testing.allocator);
 90     defer operations_output.deinit();
 91     try report.writeFromPath(
 92         std.testing.allocator,
 93         events_path,
 94         &operations_output.writer,
 95         .{ .top = 16, .frame_limit = 32, .selection = .all },
 96     );
 97     try std.testing.expect(std.mem.indexOf(
 98         u8,
 99         operations_output.written(),
100         "selection=all layer=backing calls=4 successful=4 failed=0",
101     ) != null);
102     try std.testing.expect(std.mem.indexOf(
103         u8,
104         operations_output.written(),
105         if (comptime observe.enabled)
106             "sys_memory_operations=observed"
107         else
108             "sys_memory_operations=opaque",
109     ) != null);
110 
111     var phase_output = std.Io.Writer.Allocating.init(std.testing.allocator);
112     defer phase_output.deinit();
113     try report.writeFromPath(
114         std.testing.allocator,
115         events_path,
116         &phase_output.writer,
117         .{
118             .top = 16,
119             .frame_limit = 32,
120             .window = .{ .scope = "root/phase" },
121         },
122     );
123     try std.testing.expect(std.mem.indexOf(
124         u8,
125         phase_output.written(),
126         "selection=allocations layer=backing calls=1",
127     ) != null);
128     try std.testing.expect(std.mem.indexOf(
129         u8,
130         phase_output.written(),
131         "window_scope=\"root/phase\"",
132     ) != null);
133     try std.testing.expect(std.mem.indexOf(
134         u8,
135         phase_output.written(),
136         "exactCallerA",
137     ) != null);
138     try std.testing.expect(std.mem.indexOf(
139         u8,
140         phase_output.written(),
141         "exactCallerB",
142     ) == null);
143 
144     if (comptime observe.enabled) {
145         var roots_output = std.Io.Writer.Allocating.init(std.testing.allocator);
146         defer roots_output.deinit();
147         try roots.writeFromPath(
148             std.testing.allocator,
149             events_path,
150             &roots_output.writer,
151             .{ .top = 16, .frame_limit = 32 },
152         );
153         try std.testing.expect(std.mem.indexOf(
154             u8,
155             roots_output.written(),
156             "selection=allocations sort=high_water roots=2 successful=2 " ++
157                 "failed=0",
158         ) != null);
159         try std.testing.expect(std.mem.indexOf(
160             u8,
161             roots_output.written(),
162             "exactCallerA",
163         ) != null);
164         try std.testing.expect(std.mem.indexOf(
165             u8,
166             roots_output.written(),
167             "exactCallerB",
168         ) != null);
169 
170         var phase_roots = std.Io.Writer.Allocating.init(std.testing.allocator);
171         defer phase_roots.deinit();
172         try roots.writeFromPath(
173             std.testing.allocator,
174             events_path,
175             &phase_roots.writer,
176             .{
177                 .top = 16,
178                 .frame_limit = 32,
179                 .window = .{ .scope = "root/phase" },
180             },
181         );
182         try std.testing.expect(std.mem.indexOf(
183             u8,
184             phase_roots.written(),
185             "selection=allocations sort=high_water roots=1",
186         ) != null);
187         try std.testing.expect(std.mem.indexOf(
188             u8,
189             phase_roots.written(),
190             "window_anchor=root_operation",
191         ) != null);
192     }
193 
194     const wrong_path = try std.fs.path.join(
195         std.testing.allocator,
196         &.{ root_path, "wrong.exe" },
197     );
198     defer std.testing.allocator.free(wrong_path);
199     try std.Io.Dir.cwd().writeFile(std.Options.debug_io, .{
200         .sub_path = wrong_path,
201         .data = "wrong executable",
202     });
203     try std.testing.expectError(
204         error.ExecutableIdentityMismatch,
205         report.writeFromPath(
206             std.testing.allocator,
207             events_path,
208             &output.writer,
209             .{ .binary_path = wrong_path },
210         ),
211     );
212 }