lib/tracy/src/allocator.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const runtime = @import("runtime.zig");
3
4 pub const Options = struct {
5 name: []const u8 = "tracy.allocator",
6 };
7
8 pub const TracedAllocator = struct {
9 backing: std.mem.Allocator,
10 name: []const u8,
11
12 pub fn init(backing: std.mem.Allocator, options: Options) TracedAllocator {
13 return .{
14 .backing = backing,
15 .name = options.name,
16 };
17 }
18
19 pub fn allocator(self: *TracedAllocator) std.mem.Allocator {
20 return .{ .ptr = self, .vtable = &vtable };
21 }
22
23 const vtable: std.mem.Allocator.VTable = .{
24 .alloc = rawAlloc,
25 .resize = rawResize,
26 .remap = rawRemap,
27 .free = rawFree,
28 };
29
30 fn rawAlloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
31 const self: *TracedAllocator = @ptrCast(@alignCast(ctx));
32 const ptr = self.backing.rawAlloc(len, alignment, ret_addr) orelse return null;
33 runtime.allocAddress(addressOf(ptr), len, self.name);
34 return ptr;
35 }
36
37 fn rawResize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
38 const self: *TracedAllocator = @ptrCast(@alignCast(ctx));
39 const resized = self.backing.rawResize(memory, alignment, new_len, ret_addr);
40 if (resized) self.recordResize(memory.ptr, memory.len, new_len);
41 return resized;
42 }
43
44 fn rawRemap(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
45 const self: *TracedAllocator = @ptrCast(@alignCast(ctx));
46 const ptr = self.backing.rawRemap(memory, alignment, new_len, ret_addr) orelse return null;
47 runtime.freeAddress(addressOf(memory.ptr), memory.len, self.name);
48 if (new_len != 0) runtime.allocAddress(addressOf(ptr), new_len, self.name);
49 return ptr;
50 }
51
52 fn rawFree(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
53 const self: *TracedAllocator = @ptrCast(@alignCast(ctx));
54 runtime.freeAddress(addressOf(memory.ptr), memory.len, self.name);
55 self.backing.rawFree(memory, alignment, ret_addr);
56 }
57
58 fn recordResize(self: *TracedAllocator, ptr: [*]u8, old_len: usize, new_len: usize) void {
59 if (old_len == new_len) return;
60 runtime.freeAddress(addressOf(ptr), old_len, self.name);
61 if (new_len != 0) runtime.allocAddress(addressOf(ptr), new_len, self.name);
62 }
63 };
64
65 pub fn trace(backing: std.mem.Allocator) TracedAllocator {
66 return .init(backing, .{});
67 }
68
69 pub fn traceNamed(backing: std.mem.Allocator, name: []const u8) TracedAllocator {
70 return .init(backing, .{ .name = name });
71 }
72
73 fn addressOf(ptr: [*]u8) u64 {
74 return @intCast(@intFromPtr(ptr));
75 }
76
77 test "traced allocator records allocation pressure" {
78 if (!runtime.enabled) return;
79
80 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
81 defer out.deinit();
82
83 try std.testing.expect(try runtime.start(&out.writer, .{ .name = "allocator test" }));
84 defer runtime.stop();
85 var traced = traceNamed(std.testing.allocator, "test.alloc");
86 const allocator = traced.allocator();
87 const bytes = try allocator.alloc(u8, 64);
88 allocator.free(bytes);
89 runtime.stop();
90
91 const memory = @import("memory.zig");
92 var analyzer = memory.Analyzer.init(std.testing.allocator);
93 defer analyzer.deinit();
94 try analyzer.ingestJsonlBytes(out.written());
95 try std.testing.expectEqual(@as(u64, 1), analyzer.counters.allocations);
96 try std.testing.expectEqual(@as(u64, 1), analyzer.counters.frees);
97 try std.testing.expectEqual(@as(u64, 64), analyzer.counters.allocated_bytes);
98 try std.testing.expectEqual(@as(u64, 64), analyzer.counters.freed_bytes);
99 try std.testing.expectEqual(@as(u64, 0), analyzer.counters.live_bytes);
100 }
101
102 test "traced allocator records resize as free and alloc" {
103 if (!runtime.enabled) return;
104
105 var out = std.Io.Writer.Allocating.init(std.testing.allocator);
106 defer out.deinit();
107
108 try std.testing.expect(try runtime.start(&out.writer, .{ .name = "allocator resize test" }));
109 defer runtime.stop();
110 var traced = traceNamed(std.testing.allocator, "test.resize");
111 const allocator = traced.allocator();
112 var bytes = try allocator.alloc(u8, 32);
113 bytes = try allocator.realloc(bytes, 96);
114 allocator.free(bytes);
115 runtime.stop();
116
117 const memory = @import("memory.zig");
118 var analyzer = memory.Analyzer.init(std.testing.allocator);
119 defer analyzer.deinit();
120 try analyzer.ingestJsonlBytes(out.written());
121 try std.testing.expect(analyzer.counters.allocations >= 2);
122 try std.testing.expect(analyzer.counters.frees >= 2);
123 try std.testing.expectEqual(@as(u64, 0), analyzer.counters.live_bytes);
124 }