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 }