1use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, AtomicUsize, Ordering};
18
19use crate::instrument::{Counter, NanosHistogram};
20
21pub const LANES: [&str; 3] = ["admit", "prefill", "decode"];
24#[cfg(feature = "nvtx")]
26const LANES_NVTX: [&std::ffi::CStr; LANES.len()] = [c"admit", c"prefill", c"decode"];
27
28#[cfg(feature = "nvtx")]
31pub fn nvtx_push_lane(lane: usize) {
32 if let Some(name) = LANES_NVTX.get(lane) {
33 metrale_gpu_sys::nvtx::push(name);
34 }
35}
36
37#[cfg(feature = "nvtx")]
39pub fn nvtx_pop() {
40 metrale_gpu_sys::nvtx::pop();
41}
42
43pub const KERNEL_TABLE_SLOTS: usize = 512;
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum SpanKey {
49 Lane(usize),
50 Kernel(u64),
52}
53
54const LANE_BIT: u64 = 1 << 63;
55
56impl SpanKey {
57 fn encode(self) -> u64 {
58 match self {
59 Self::Lane(i) => LANE_BIT | i as u64,
60 Self::Kernel(f) => f & !LANE_BIT,
61 }
62 }
63 fn decode(v: u64) -> Self {
64 if v & LANE_BIT != 0 {
65 Self::Lane((v & !LANE_BIT) as usize)
66 } else {
67 Self::Kernel(v)
68 }
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum Edge {
75 Start,
76 End,
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum SpanPoll {
82 Ready(u64),
84 Pending,
86 Failed,
88}
89
90pub trait EventOps: Send + Sync {
92 fn record(&self, slot: usize, edge: Edge, stream: u64) -> bool;
95 fn poll(&self, slot: usize) -> SpanPoll;
97}
98
99#[derive(Debug)]
101pub struct KernelInstruments {
102 pub lane: [NanosHistogram; LANES.len()],
103 keys: [AtomicU64; KERNEL_TABLE_SLOTS],
104 total_ns: [AtomicU64; KERNEL_TABLE_SLOTS],
105 samples: [AtomicU64; KERNEL_TABLE_SLOTS],
106 pub table_full: Counter,
109 pub spans_dropped: Counter,
112 pub span_failures: Counter,
114 pub(crate) armed: AtomicBool,
115 pub(crate) step: AtomicU64,
116}
117
118impl Default for KernelInstruments {
119 fn default() -> Self {
120 Self::new()
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub struct KernelTime {
127 pub func: u64,
128 pub total_ns: u64,
129 pub samples: u64,
130}
131
132impl KernelInstruments {
133 pub const fn new() -> Self {
134 Self {
135 lane: [const { NanosHistogram::new() }; LANES.len()],
136 keys: [const { AtomicU64::new(0) }; KERNEL_TABLE_SLOTS],
137 total_ns: [const { AtomicU64::new(0) }; KERNEL_TABLE_SLOTS],
138 samples: [const { AtomicU64::new(0) }; KERNEL_TABLE_SLOTS],
139 table_full: Counter::new(),
140 spans_dropped: Counter::new(),
141 span_failures: Counter::new(),
142 armed: AtomicBool::new(false),
143 step: AtomicU64::new(0),
144 }
145 }
146
147 pub fn record(&self, key: SpanKey, ns: u64) {
150 match key {
151 SpanKey::Lane(i) => {
152 if let Some(h) = self.lane.get(i) {
153 h.observe(ns);
154 }
155 }
156 SpanKey::Kernel(func) => self.record_kernel(func, ns),
157 }
158 }
159
160 fn record_kernel(&self, func: u64, ns: u64) {
161 if func == 0 {
163 self.table_full.add(1);
164 return;
165 }
166 let start = (func.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> 55) as usize;
167 for probe in 0..KERNEL_TABLE_SLOTS {
168 let i = (start + probe) % KERNEL_TABLE_SLOTS;
169 let k = self.keys[i].load(Ordering::Acquire);
170 let owned = k == func
171 || (k == 0
172 && match self.keys[i].compare_exchange(
173 0,
174 func,
175 Ordering::AcqRel,
176 Ordering::Acquire,
177 ) {
178 Ok(_) => true,
179 Err(now) => now == func,
180 });
181 if owned {
182 self.total_ns[i].fetch_add(ns, Ordering::Relaxed);
183 self.samples[i].fetch_add(1, Ordering::Relaxed);
184 return;
185 }
186 }
187 self.table_full.add(1);
188 }
189
190 pub fn kernels(&self) -> Vec<KernelTime> {
192 (0..KERNEL_TABLE_SLOTS)
193 .filter_map(|i| {
194 let func = self.keys[i].load(Ordering::Acquire);
195 (func != 0).then(|| KernelTime {
196 func,
197 total_ns: self.total_ns[i].load(Ordering::Relaxed),
198 samples: self.samples[i].load(Ordering::Relaxed),
199 })
200 })
201 .filter(|k| k.samples > 0)
202 .collect()
203 }
204}
205
206const FREE: u8 = 0;
207const OPEN: u8 = 1;
208const CLOSED: u8 = 2;
209
210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
212pub struct SpanToken(usize);
213
214impl SpanToken {
215 pub fn slot(self) -> usize {
218 self.0
219 }
220 pub fn from_slot(slot: usize) -> Self {
222 Self(slot)
223 }
224}
225
226pub struct SpanRing<E: EventOps> {
228 ops: E,
229 state: Box<[AtomicU8]>,
230 key: Box<[AtomicU64]>,
231 head: AtomicUsize,
232}
233
234impl<E: EventOps> SpanRing<E> {
235 pub fn new(ops: E, slots: usize) -> Self {
237 assert!(slots > 0, "a span ring needs at least one slot");
238 Self {
239 ops,
240 state: (0..slots).map(|_| AtomicU8::new(FREE)).collect(),
241 key: (0..slots).map(|_| AtomicU64::new(0)).collect(),
242 head: AtomicUsize::new(0),
243 }
244 }
245
246 pub fn slots(&self) -> usize {
247 self.state.len()
248 }
249
250 pub fn begin(&self, key: SpanKey, stream: u64, sink: &KernelInstruments) -> Option<SpanToken> {
253 let i = self.head.fetch_add(1, Ordering::Relaxed) % self.state.len();
254 if self.state[i]
255 .compare_exchange(FREE, OPEN, Ordering::Acquire, Ordering::Relaxed)
256 .is_err()
257 {
258 sink.spans_dropped.add(1);
259 return None;
260 }
261 self.key[i].store(key.encode(), Ordering::Relaxed);
262 if !self.ops.record(i, Edge::Start, stream) {
263 self.state[i].store(FREE, Ordering::Release);
264 sink.span_failures.add(1);
265 return None;
266 }
267 Some(SpanToken(i))
268 }
269
270 pub fn end(&self, tok: SpanToken, stream: u64, sink: &KernelInstruments) {
273 let i = tok.0;
274 if self.ops.record(i, Edge::End, stream) {
275 self.state[i].store(CLOSED, Ordering::Release);
276 } else {
277 self.state[i].store(FREE, Ordering::Release);
278 sink.span_failures.add(1);
279 }
280 }
281
282 pub fn harvest(&self, sink: &KernelInstruments) -> usize {
285 let mut landed = 0;
286 for i in 0..self.state.len() {
287 if self.state[i].load(Ordering::Acquire) != CLOSED {
288 continue;
289 }
290 match self.ops.poll(i) {
291 SpanPoll::Pending => continue,
292 SpanPoll::Ready(ns) => {
293 sink.record(SpanKey::decode(self.key[i].load(Ordering::Relaxed)), ns);
294 landed += 1;
295 }
296 SpanPoll::Failed => sink.span_failures.add(1),
297 }
298 self.state[i].store(FREE, Ordering::Release);
299 }
300 landed
301 }
302
303 pub fn ops(&self) -> &E {
304 &self.ops
305 }
306}
307
308#[cfg(test)]
309#[path = "kernel_tests.rs"]
310mod tests;