1use super::slot;
2use rustc_hash::FxHashMap;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum RingStatus {
7 Empty,
9 Published,
11 Claimed,
13 Done,
15 WaitIo,
17 Yield,
19 Requeue,
21 Fault,
23 Unknown(u32),
25}
26
27impl RingStatus {
28 #[must_use]
29 pub(super) fn from_raw(raw: u32) -> Self {
30 match raw {
31 slot::EMPTY => Self::Empty,
32 slot::PUBLISHED => Self::Published,
33 slot::CLAIMED => Self::Claimed,
34 slot::DONE => Self::Done,
35 slot::WAIT_IO => Self::WaitIo,
36 slot::YIELD => Self::Yield,
37 slot::REQUEUE => Self::Requeue,
38 slot::FAULT => Self::Fault,
39 other => Self::Unknown(other),
40 }
41 }
42
43 #[must_use]
45 pub const fn raw(self) -> u32 {
46 match self {
47 Self::Empty => slot::EMPTY,
48 Self::Published => slot::PUBLISHED,
49 Self::Claimed => slot::CLAIMED,
50 Self::Done => slot::DONE,
51 Self::WaitIo => slot::WAIT_IO,
52 Self::Yield => slot::YIELD,
53 Self::Requeue => slot::REQUEUE,
54 Self::Fault => slot::FAULT,
55 Self::Unknown(raw) => raw,
56 }
57 }
58
59 #[must_use]
62 pub const fn is_active(self) -> bool {
63 matches!(
64 self,
65 Self::Published | Self::Claimed | Self::WaitIo | Self::Yield | Self::Requeue
66 )
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct RingSlotSnapshot {
73 pub slot_idx: u32,
75 pub status: RingStatus,
77 pub tenant_id: u32,
79 pub opcode: u32,
81 pub args_prefix: [u32; 3],
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct WindowTelemetry {
88 pub ticket: u32,
90 pub tenant_id: u32,
92 pub opcode: u32,
94 pub required_slots: u32,
96 pub lookahead_slots: u32,
98 pub published: u32,
100 pub claimed: u32,
102 pub done: u32,
104 pub wait_io: u32,
106 pub yield_count: u32,
108 pub requeue: u32,
110 pub fault: u32,
112}
113
114impl WindowTelemetry {
115 #[must_use]
117 pub const fn is_active(&self) -> bool {
118 self.published > 0
119 || self.claimed > 0
120 || self.wait_io > 0
121 || self.yield_count > 0
122 || self.requeue > 0
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
128pub struct RingOccupancy {
129 pub empty: u32,
131 pub published: u32,
133 pub claimed: u32,
135 pub done: u32,
137 pub wait_io: u32,
139 pub yield_count: u32,
141 pub requeue: u32,
143 pub fault: u32,
145 pub unknown: u32,
147}
148
149impl RingOccupancy {
150 #[must_use]
152 pub fn total_slots(&self) -> u32 {
153 checked_status_sum(
154 [
155 self.empty,
156 self.published,
157 self.claimed,
158 self.done,
159 self.wait_io,
160 self.yield_count,
161 self.requeue,
162 self.fault,
163 self.unknown,
164 ],
165 "total ring slots",
166 )
167 }
168
169 #[must_use]
171 pub fn queue_depth(&self) -> u32 {
172 checked_status_sum(
173 [
174 self.published,
175 self.claimed,
176 self.wait_io,
177 self.yield_count,
178 self.requeue,
179 self.fault,
180 self.unknown,
181 ],
182 "ring queue depth",
183 )
184 }
185}
186
187pub const RUNTIME_IO_EVIDENCE_SCHEMA_VERSION: u32 = 1;
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum RuntimeEvidenceMetricFamily {
193 Ring,
195 Control,
197 Copy,
199 Residency,
201}
202
203impl RuntimeEvidenceMetricFamily {
204 #[must_use]
206 pub const fn as_str(self) -> &'static str {
207 match self {
208 Self::Ring => "ring",
209 Self::Control => "control",
210 Self::Copy => "copy",
211 Self::Residency => "residency",
212 }
213 }
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
218pub struct RuntimeEvidenceMetricCoverage {
219 pub ring: bool,
221 pub control: bool,
223 pub copy: bool,
225 pub residency: bool,
227}
228
229impl RuntimeEvidenceMetricCoverage {
230 #[must_use]
232 pub const fn complete() -> Self {
233 Self {
234 ring: true,
235 control: true,
236 copy: true,
237 residency: true,
238 }
239 }
240
241 #[must_use]
243 pub fn missing_families(self) -> Vec<RuntimeEvidenceMetricFamily> {
244 let mut missing = Vec::new();
245 if !self.ring {
246 missing.push(RuntimeEvidenceMetricFamily::Ring);
247 }
248 if !self.control {
249 missing.push(RuntimeEvidenceMetricFamily::Control);
250 }
251 if !self.copy {
252 missing.push(RuntimeEvidenceMetricFamily::Copy);
253 }
254 if !self.residency {
255 missing.push(RuntimeEvidenceMetricFamily::Residency);
256 }
257 missing
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct MegakernelRuntimeEvidence {
264 pub schema_version: u32,
266 pub resident_device_bytes: u64,
268 pub host_copy_bytes: u64,
270 pub host_copy_avoided_bytes: u64,
272 pub ring_occupancy: RingOccupancy,
274 pub control_decode_ns: u64,
276 pub ring_decode_ns: u64,
278 pub coverage: RuntimeEvidenceMetricCoverage,
280}
281
282impl MegakernelRuntimeEvidence {
283 #[must_use]
285 pub const fn complete(
286 resident_device_bytes: u64,
287 host_copy_bytes: u64,
288 host_copy_avoided_bytes: u64,
289 ring_occupancy: RingOccupancy,
290 control_decode_ns: u64,
291 ring_decode_ns: u64,
292 ) -> Self {
293 Self {
294 schema_version: RUNTIME_IO_EVIDENCE_SCHEMA_VERSION,
295 resident_device_bytes,
296 host_copy_bytes,
297 host_copy_avoided_bytes,
298 ring_occupancy,
299 control_decode_ns,
300 ring_decode_ns,
301 coverage: RuntimeEvidenceMetricCoverage::complete(),
302 }
303 }
304
305 #[must_use]
307 pub fn missing_metric_families(&self) -> Vec<RuntimeEvidenceMetricFamily> {
308 self.coverage.missing_families()
309 }
310
311 #[must_use]
313 pub fn is_complete(&self) -> bool {
314 self.schema_version == RUNTIME_IO_EVIDENCE_SCHEMA_VERSION
315 && self.missing_metric_families().is_empty()
316 }
317
318 #[must_use]
320 pub fn host_copy_avoidance_bps(&self) -> u16 {
321 let total = u128::from(self.host_copy_bytes)
322 .saturating_add(u128::from(self.host_copy_avoided_bytes));
323 if total == 0 {
324 return 0;
325 }
326 let bps = u128::from(self.host_copy_avoided_bytes)
327 .saturating_mul(10_000)
328 / total;
329 bps.min(10_000) as u16
330 }
331}
332
333fn checked_status_sum<const N: usize>(values: [u32; N], label: &'static str) -> u32 {
334 let _ = label;
335 values
336 .into_iter()
337 .fold(0_u32, |acc, value| acc.saturating_add(value))
338}
339
340#[cfg(test)]
341mod evidence_tests {
342 use super::*;
343
344 #[test]
345 fn runtime_evidence_reports_missing_metric_families() {
346 let evidence = MegakernelRuntimeEvidence {
347 schema_version: RUNTIME_IO_EVIDENCE_SCHEMA_VERSION,
348 resident_device_bytes: 0,
349 host_copy_bytes: 0,
350 host_copy_avoided_bytes: 0,
351 ring_occupancy: RingOccupancy::default(),
352 control_decode_ns: 0,
353 ring_decode_ns: 0,
354 coverage: RuntimeEvidenceMetricCoverage {
355 ring: true,
356 control: false,
357 copy: true,
358 residency: false,
359 },
360 };
361
362 let missing = evidence
363 .missing_metric_families()
364 .into_iter()
365 .map(RuntimeEvidenceMetricFamily::as_str)
366 .collect::<Vec<_>>();
367
368 assert_eq!(missing, vec!["control", "residency"]);
369 assert!(!evidence.is_complete());
370 }
371
372 #[test]
373 fn runtime_evidence_records_copy_avoidance_and_occupancy() {
374 let evidence = MegakernelRuntimeEvidence::complete(
375 4096,
376 1024,
377 3072,
378 RingOccupancy {
379 empty: 1,
380 published: 2,
381 claimed: 3,
382 done: 4,
383 wait_io: 5,
384 yield_count: 6,
385 requeue: 7,
386 fault: 8,
387 unknown: 9,
388 },
389 11,
390 13,
391 );
392
393 assert!(evidence.is_complete());
394 assert_eq!(evidence.resident_device_bytes, 4096);
395 assert_eq!(evidence.ring_occupancy.total_slots(), 45);
396 assert_eq!(evidence.ring_occupancy.queue_depth(), 40);
397 assert_eq!(evidence.host_copy_avoidance_bps(), 7500);
398 }
399}
400
401#[derive(Debug, Clone, PartialEq, Eq, Default)]
403pub struct ControlSnapshot {
404 pub shutdown: bool,
406 pub done_count: u32,
408 pub epoch: u32,
410 pub metrics: Vec<(u32, u32)>,
412 pub tenant_fairness: Vec<u32>,
414 pub priority_fairness: Vec<u32>,
416}
417
418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420pub struct MegakernelRuntimeCounters {
421 pub total_slots: u32,
423 pub queue_depth: u32,
425 pub gpu_idle_slots: u32,
427 pub gpu_idle_ppm: u32,
429 pub frontier_density_bps: u16,
431 pub occupancy_proxy_bps: u16,
433 pub drained_slots: u32,
435 pub unreclaimed_done_slots: u32,
437 pub tenant_fairness_total: u64,
439 pub tenant_fairness_skew: u32,
441 pub priority_fairness_total: u64,
443 pub requeue_slots: u32,
445 pub fault_slots: u32,
447}
448
449#[derive(Debug, Clone, Copy, PartialEq, Eq)]
451pub struct MegakernelWatchdogSnapshot {
452 pub done_delta: u32,
454 pub queue_depth: u32,
456 pub fault_slots: u32,
458 pub requeue_slots: u32,
460 pub gpu_idle_ppm: u32,
462 pub suspected_stall: bool,
464}
465
466#[derive(Debug, Clone, PartialEq, Eq, Default)]
468pub struct RingTelemetry {
469 pub control: ControlSnapshot,
471 pub occupancy: RingOccupancy,
473 pub slots: Vec<RingSlotSnapshot>,
475 pub windows: Vec<WindowTelemetry>,
477}
478
479pub const TELEMETRY_DECODE_CAPACITY_SCHEMA_VERSION: u32 = 1;
481
482#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484pub struct TelemetryDecodeCapacityEvidence {
485 pub schema_version: u32,
487 pub decoded_slot_count: usize,
489 pub slot_output_capacity: usize,
491 pub decoded_window_count: usize,
493 pub window_output_capacity: usize,
495 pub window_opcode_scratch_capacity: usize,
497 pub window_accumulator_scratch_capacity: usize,
499 pub uses_caller_owned_scratch: bool,
501}
502
503impl TelemetryDecodeCapacityEvidence {
504 #[must_use]
506 pub fn is_complete(self) -> bool {
507 self.schema_version == TELEMETRY_DECODE_CAPACITY_SCHEMA_VERSION
508 && self.uses_caller_owned_scratch
509 && self.slot_output_capacity >= self.decoded_slot_count
510 && self.window_output_capacity >= self.decoded_window_count
511 && self.window_accumulator_scratch_capacity >= self.decoded_window_count
512 }
513}
514
515impl RingTelemetry {
516 #[must_use]
518 pub fn decode_capacity_evidence(
519 &self,
520 scratch: &TelemetryDecodeScratch,
521 ) -> TelemetryDecodeCapacityEvidence {
522 TelemetryDecodeCapacityEvidence {
523 schema_version: TELEMETRY_DECODE_CAPACITY_SCHEMA_VERSION,
524 decoded_slot_count: self.slots.len(),
525 slot_output_capacity: self.slots.capacity(),
526 decoded_window_count: self.windows.len(),
527 window_output_capacity: self.windows.capacity(),
528 window_opcode_scratch_capacity: scratch.window_opcodes.capacity(),
529 window_accumulator_scratch_capacity: scratch.windows.capacity(),
530 uses_caller_owned_scratch: true,
531 }
532 }
533}
534
535#[derive(Debug, Default)]
541pub struct TelemetryDecodeScratch {
542 pub(super) window_opcodes: Vec<u32>,
543 pub(super) windows: FxHashMap<(u32, u32), WindowAccumulator>,
544}
545
546impl TelemetryDecodeScratch {
547 #[must_use]
549 pub fn new() -> Self {
550 Self {
551 window_opcodes: Vec::new(),
552 windows: FxHashMap::default(),
553 }
554 }
555
556 pub fn clear(&mut self) {
558 self.window_opcodes.clear();
559 self.windows.clear();
560 }
561}
562
563#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
564pub(super) struct WindowAccumulator {
565 pub(super) tenant_id: u32,
566 pub(super) opcode: u32,
567 pub(super) required_slots: u32,
568 pub(super) lookahead_slots: u32,
569 pub(super) published: u32,
570 pub(super) claimed: u32,
571 pub(super) done: u32,
572 pub(super) wait_io: u32,
573 pub(super) yield_count: u32,
574 pub(super) requeue: u32,
575 pub(super) fault: u32,
576}