lib/coz/src/thread.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3
4 const debug_info = @import("debug.zig");
5 const delay = @import("delay.zig");
6 const perf = sys.perf;
7 const profiler_mod = @import("profiler.zig");
8 const sampler_mod = @import("sampler.zig");
9 const timer = sys.timer;
10
11 pub const record_scratch_size = perf.data_size;
12 pub const callchain_scratch_len = 128;
13
14 pub const Thread = struct {
15 delay_state: delay.ThreadState = .{},
16 sampler: sampler_mod.Sampler = .{},
17 record_scratch: [record_scratch_size]u8 = undefined,
18 callchain_scratch: [callchain_scratch_len]usize = undefined,
19
20 pub fn openCurrent(options: sampler_mod.Options) !Thread {
21 return .{ .sampler = try sampler_mod.Sampler.openCurrentThread(options) };
22 }
23
24 pub fn openAndStartCurrent(options: sampler_mod.Options) !Thread {
25 var thread = try openCurrent(options);
26 errdefer thread.close();
27 try thread.sampler.start();
28 return thread;
29 }
30
31 pub fn close(self: *Thread) void {
32 self.sampler.close();
33 }
34
35 pub fn process(
36 self: *Thread,
37 profiler: *profiler_mod.Profiler,
38 record_scratch: []u8,
39 callchain_scratch: []usize,
40 wait: sampler_mod.WaitFn,
41 ) !usize {
42 if (!self.delay_state.acquireUse()) return 0;
43 defer self.delay_state.setInUse(false);
44 return profiler.processSampler(&self.delay_state, &self.sampler, record_scratch, callchain_scratch, wait);
45 }
46
47 pub fn processDefault(
48 self: *Thread,
49 profiler: *profiler_mod.Profiler,
50 wait: sampler_mod.WaitFn,
51 ) !usize {
52 return self.process(profiler, &self.record_scratch, &self.callchain_scratch, wait);
53 }
54
55 pub fn drain(
56 self: *Thread,
57 profiler: *profiler_mod.Profiler,
58 record_scratch: []u8,
59 callchain_scratch: []usize,
60 ) !usize {
61 if (!self.delay_state.acquireUse()) return 0;
62 defer self.delay_state.setInUse(false);
63 return profiler.drainSampler(
64 &self.delay_state,
65 &self.sampler,
66 record_scratch,
67 callchain_scratch,
68 );
69 }
70
71 pub fn drainDefault(self: *Thread, profiler: *profiler_mod.Profiler) !usize {
72 return self.drain(profiler, &self.record_scratch, &self.callchain_scratch);
73 }
74
75 pub fn drainResolved(
76 self: *Thread,
77 profiler: *profiler_mod.Profiler,
78 allocator: std.mem.Allocator,
79 scope: debug_info.Scope,
80 record_scratch: []u8,
81 callchain_scratch: []usize,
82 ) !usize {
83 if (!self.delay_state.acquireUse()) return 0;
84 defer self.delay_state.setInUse(false);
85 return profiler.drainResolvedSampler(allocator, &self.delay_state, &self.sampler, record_scratch, callchain_scratch, scope);
86 }
87
88 pub fn drainResolvedDefault(
89 self: *Thread,
90 profiler: *profiler_mod.Profiler,
91 allocator: std.mem.Allocator,
92 scope: debug_info.Scope,
93 ) !usize {
94 return self.drainResolved(profiler, allocator, scope, &self.record_scratch, &self.callchain_scratch);
95 }
96 };
97
98 fn exactWait(ns: u64) u64 {
99 return ns;
100 }
101
102 test "thread runtime close is idempotent without owned resources" {
103 var thread: Thread = .{};
104
105 thread.close();
106 thread.close();
107
108 try std.testing.expectEqual(perf.invalid_fd, thread.sampler.event.fd);
109 try std.testing.expectEqual(timer.invalid_timer_id, thread.sampler.wake_timer.id);
110 }
111
112 test "thread runtime skips processing while in use" {
113 var thread: Thread = .{};
114 var profiler: profiler_mod.Profiler = .{};
115 defer profiler.deinit(std.testing.allocator);
116 var record_scratch: [64]u8 = undefined;
117 var callchain_scratch: [8]usize = undefined;
118
119 profiler.delays.startExperiment(0);
120 profiler.delays.global_delay_ns.store(10, .monotonic);
121 thread.delay_state.setInUse(true);
122
123 const processed = try thread.process(&profiler, &record_scratch, &callchain_scratch, exactWait);
124
125 try std.testing.expectEqual(@as(usize, 0), processed);
126 try std.testing.expectEqual(@as(u64, 0), thread.delay_state.localDelay());
127 try std.testing.expect(thread.delay_state.checkInUse());
128 }
129
130 test "thread runtime processes sampler and clears in-use guard" {
131 var thread: Thread = .{};
132 var profiler: profiler_mod.Profiler = .{};
133 defer profiler.deinit(std.testing.allocator);
134 var record_scratch: [64]u8 = undefined;
135 var callchain_scratch: [8]usize = undefined;
136
137 profiler.delays.startExperiment(0);
138 profiler.delays.global_delay_ns.store(13, .monotonic);
139
140 const processed = try thread.process(&profiler, &record_scratch, &callchain_scratch, exactWait);
141
142 try std.testing.expectEqual(@as(usize, 0), processed);
143 try std.testing.expectEqual(@as(u64, 13), thread.delay_state.localDelay());
144 try std.testing.expect(!thread.delay_state.checkInUse());
145 }
146
147 test "thread runtime default processing uses owned scratch buffers" {
148 var thread: Thread = .{};
149 var profiler: profiler_mod.Profiler = .{};
150 defer profiler.deinit(std.testing.allocator);
151
152 profiler.delays.global_delay_ns.store(17, .monotonic);
153
154 const processed = try thread.processDefault(&profiler, exactWait);
155
156 try std.testing.expectEqual(@as(usize, 0), processed);
157 try std.testing.expectEqual(@as(u64, 17), thread.delay_state.localDelay());
158 try std.testing.expect(!thread.delay_state.checkInUse());
159 }
160
161 test "thread runtime final drain does not apply pending delays" {
162 var thread: Thread = .{};
163 var profiler: profiler_mod.Profiler = .{};
164 defer profiler.deinit(std.testing.allocator);
165
166 profiler.delays.global_delay_ns.store(17, .monotonic);
167
168 const drained = try thread.drainDefault(&profiler);
169
170 try std.testing.expectEqual(@as(usize, 0), drained);
171 try std.testing.expectEqual(@as(u64, 0), thread.delay_state.localDelay());
172 try std.testing.expect(!thread.delay_state.checkInUse());
173 }