lib/gpalloc/src/stats.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 const native_u64_atomics = @bitSizeOf(usize) >= 64;
4
5 const AtomicU32 = std.atomic.Value(u32);
6
7 pub const AtomicU64 = if (native_u64_atomics) std.atomic.Value(u64) else SplitAtomicU64;
8 pub const AtomicUsize = std.atomic.Value(usize);
9
10 const SplitAtomicU64 = struct {
11 low: AtomicU32,
12 high: AtomicU32,
13
14 pub fn init(value: u64) SplitAtomicU64 {
15 return .{
16 .low = AtomicU32.init(@truncate(value)),
17 .high = AtomicU32.init(@intCast(value >> 32)),
18 };
19 }
20
21 pub fn load(counter: *const SplitAtomicU64, comptime order: std.builtin.AtomicOrder) u64 {
22 const high = counter.high.load(order);
23 const low = counter.low.load(order);
24 return (@as(u64, high) << 32) | low;
25 }
26
27 pub fn store(counter: *SplitAtomicU64, value: u64, comptime order: std.builtin.AtomicOrder) void {
28 counter.low.store(@truncate(value), order);
29 counter.high.store(@intCast(value >> 32), order);
30 }
31
32 pub fn fetchAdd(counter: *SplitAtomicU64, amount: u64, comptime order: std.builtin.AtomicOrder) u64 {
33 const low_amount: u32 = @truncate(amount);
34 const high_amount: u32 = @intCast(amount >> 32);
35 const previous_low = counter.low.fetchAdd(low_amount, order);
36 const carry: u32 = if (@as(u64, previous_low) + low_amount > std.math.maxInt(u32)) 1 else 0;
37 const high_delta = high_amount +% carry;
38 const previous_high = if (high_delta == 0)
39 counter.high.load(order)
40 else
41 counter.high.fetchAdd(high_delta, order);
42 return (@as(u64, previous_high) << 32) | previous_low;
43 }
44 };
45
46 pub const Stats = struct {
47 small_allocations: u64 = 0,
48 small_frees: u64 = 0,
49 large_allocations: u64 = 0,
50 large_frees: u64 = 0,
51 pages_allocated: u64 = 0,
52 pages_freed: u64 = 0,
53 empty_page_discards: u64 = 0,
54 retained_empty_pages: usize = 0,
55 discarded_small_bytes: usize = 0,
56 active_small_bytes: usize = 0,
57 active_large_bytes: usize = 0,
58 mapped_small_bytes: usize = 0,
59 };
60
61 pub const AtomicStats = struct {
62 small_allocations: AtomicU64 = AtomicU64.init(0),
63 small_frees: AtomicU64 = AtomicU64.init(0),
64 large_allocations: AtomicU64 = AtomicU64.init(0),
65 large_frees: AtomicU64 = AtomicU64.init(0),
66 pages_allocated: AtomicU64 = AtomicU64.init(0),
67 pages_freed: AtomicU64 = AtomicU64.init(0),
68 empty_page_discards: AtomicU64 = AtomicU64.init(0),
69 retained_empty_pages: AtomicUsize = AtomicUsize.init(0),
70 discarded_small_bytes: AtomicUsize = AtomicUsize.init(0),
71 active_small_bytes: AtomicUsize = AtomicUsize.init(0),
72 active_large_bytes: AtomicUsize = AtomicUsize.init(0),
73 mapped_small_bytes: AtomicUsize = AtomicUsize.init(0),
74
75 pub fn snapshot(stats: *const AtomicStats) Stats {
76 return .{
77 .small_allocations = stats.small_allocations.load(.monotonic),
78 .small_frees = stats.small_frees.load(.monotonic),
79 .large_allocations = stats.large_allocations.load(.monotonic),
80 .large_frees = stats.large_frees.load(.monotonic),
81 .pages_allocated = stats.pages_allocated.load(.monotonic),
82 .pages_freed = stats.pages_freed.load(.monotonic),
83 .empty_page_discards = stats.empty_page_discards.load(.monotonic),
84 .retained_empty_pages = stats.retained_empty_pages.load(.monotonic),
85 .discarded_small_bytes = stats.discarded_small_bytes.load(.monotonic),
86 .active_small_bytes = stats.active_small_bytes.load(.monotonic),
87 .active_large_bytes = stats.active_large_bytes.load(.monotonic),
88 .mapped_small_bytes = stats.mapped_small_bytes.load(.monotonic),
89 };
90 }
91
92 pub fn reset(stats: *AtomicStats) void {
93 stats.small_allocations.store(0, .monotonic);
94 stats.small_frees.store(0, .monotonic);
95 stats.large_allocations.store(0, .monotonic);
96 stats.large_frees.store(0, .monotonic);
97 stats.pages_allocated.store(0, .monotonic);
98 stats.pages_freed.store(0, .monotonic);
99 stats.empty_page_discards.store(0, .monotonic);
100 stats.retained_empty_pages.store(0, .monotonic);
101 stats.discarded_small_bytes.store(0, .monotonic);
102 stats.active_small_bytes.store(0, .monotonic);
103 stats.active_large_bytes.store(0, .monotonic);
104 stats.mapped_small_bytes.store(0, .monotonic);
105 }
106 };
107
108 pub fn addU64(counter: *AtomicU64, amount: u64) void {
109 _ = counter.fetchAdd(amount, .monotonic);
110 }
111
112 pub fn addUsize(counter: *AtomicUsize, amount: usize) void {
113 _ = counter.fetchAdd(amount, .monotonic);
114 }
115
116 pub fn subUsize(counter: *AtomicUsize, amount: usize) void {
117 const previous = counter.fetchSub(amount, .monotonic);
118 std.debug.assert(previous >= amount);
119 }
120
121 test "split u64 counter carries across low word" {
122 var counter = SplitAtomicU64.init(std.math.maxInt(u32));
123 try std.testing.expectEqual(@as(u64, std.math.maxInt(u32)), counter.load(.monotonic));
124 _ = counter.fetchAdd(1, .monotonic);
125 try std.testing.expectEqual(@as(u64, std.math.maxInt(u32)) + 1, counter.load(.monotonic));
126 counter.store((@as(u64, 3) << 32) + 7, .monotonic);
127 try std.testing.expectEqual((@as(u64, 3) << 32) + 7, counter.load(.monotonic));
128 }