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 }