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 }