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).saturating_mul(10_000) / total;
327 bps.min(10_000) as u16
328 }
329}
330
331fn checked_status_sum<const N: usize>(values: [u32; N], label: &'static str) -> u32 {
332 let _ = label;
333 values
334 .into_iter()
335 .fold(0_u32, |acc, value| acc.saturating_add(value))
336}
337
338#[cfg(test)]
339mod evidence_tests {
340 use super::*;
341
342 #[test]
343 fn runtime_evidence_reports_missing_metric_families() {
344 let evidence = MegakernelRuntimeEvidence {
345 schema_version: RUNTIME_IO_EVIDENCE_SCHEMA_VERSION,
346 resident_device_bytes: 0,
347 host_copy_bytes: 0,
348 host_copy_avoided_bytes: 0,
349 ring_occupancy: RingOccupancy::default(),
350 control_decode_ns: 0,
351 ring_decode_ns: 0,
352 coverage: RuntimeEvidenceMetricCoverage {
353 ring: true,
354 control: false,
355 copy: true,
356 residency: false,
357 },
358 };
359
360 let missing = evidence
361 .missing_metric_families()
362 .into_iter()
363 .map(RuntimeEvidenceMetricFamily::as_str)
364 .collect::<Vec<_>>();
365
366 assert_eq!(missing, vec!["control", "residency"]);
367 assert!(!evidence.is_complete());
368 }
369
370 #[test]
371 fn runtime_evidence_records_copy_avoidance_and_occupancy() {
372 let evidence = MegakernelRuntimeEvidence::complete(
373 4096,
374 1024,
375 3072,
376 RingOccupancy {
377 empty: 1,
378 published: 2,
379 claimed: 3,
380 done: 4,
381 wait_io: 5,
382 yield_count: 6,
383 requeue: 7,
384 fault: 8,
385 unknown: 9,
386 },
387 11,
388 13,
389 );
390
391 assert!(evidence.is_complete());
392 assert_eq!(evidence.resident_device_bytes, 4096);
393 assert_eq!(evidence.ring_occupancy.total_slots(), 45);
394 assert_eq!(evidence.ring_occupancy.queue_depth(), 40);
395 assert_eq!(evidence.host_copy_avoidance_bps(), 7500);
396 }
397}
398
399#[derive(Debug, Clone, PartialEq, Eq, Default)]
401pub struct ControlSnapshot {
402 pub shutdown: bool,
404 pub done_count: u32,
406 pub epoch: u32,
408 pub metrics: Vec<(u32, u32)>,
410 pub tenant_fairness: Vec<u32>,
412 pub priority_fairness: Vec<u32>,
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub struct MegakernelRuntimeCounters {
419 pub total_slots: u32,
421 pub queue_depth: u32,
423 pub gpu_idle_slots: u32,
425 pub gpu_idle_ppm: u32,
427 pub frontier_density_bps: u16,
429 pub occupancy_proxy_bps: u16,
431 pub drained_slots: u32,
433 pub unreclaimed_done_slots: u32,
435 pub tenant_fairness_total: u64,
437 pub tenant_fairness_skew: u32,
439 pub priority_fairness_total: u64,
441 pub requeue_slots: u32,
443 pub fault_slots: u32,
445}
446
447#[derive(Debug, Clone, Copy, PartialEq, Eq)]
449pub struct MegakernelWatchdogSnapshot {
450 pub done_delta: u32,
452 pub queue_depth: u32,
454 pub fault_slots: u32,
456 pub requeue_slots: u32,
458 pub gpu_idle_ppm: u32,
460 pub suspected_stall: bool,
462}
463
464#[derive(Debug, Clone, PartialEq, Eq, Default)]
466pub struct RingTelemetry {
467 pub control: ControlSnapshot,
469 pub occupancy: RingOccupancy,
471 pub slots: Vec<RingSlotSnapshot>,
473 pub windows: Vec<WindowTelemetry>,
475}
476
477pub const TELEMETRY_DECODE_CAPACITY_SCHEMA_VERSION: u32 = 1;
479
480#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482pub struct TelemetryDecodeCapacityEvidence {
483 pub schema_version: u32,
485 pub decoded_slot_count: usize,
487 pub slot_output_capacity: usize,
489 pub decoded_window_count: usize,
491 pub window_output_capacity: usize,
493 pub window_opcode_scratch_capacity: usize,
495 pub window_accumulator_scratch_capacity: usize,
497 pub uses_caller_owned_scratch: bool,
499}
500
501impl TelemetryDecodeCapacityEvidence {
502 #[must_use]
504 pub fn is_complete(self) -> bool {
505 self.schema_version == TELEMETRY_DECODE_CAPACITY_SCHEMA_VERSION
506 && self.uses_caller_owned_scratch
507 && self.slot_output_capacity >= self.decoded_slot_count
508 && self.window_output_capacity >= self.decoded_window_count
509 && self.window_accumulator_scratch_capacity >= self.decoded_window_count
510 }
511}
512
513impl RingTelemetry {
514 #[must_use]
516 pub fn decode_capacity_evidence(
517 &self,
518 scratch: &TelemetryDecodeScratch,
519 ) -> TelemetryDecodeCapacityEvidence {
520 TelemetryDecodeCapacityEvidence {
521 schema_version: TELEMETRY_DECODE_CAPACITY_SCHEMA_VERSION,
522 decoded_slot_count: self.slots.len(),
523 slot_output_capacity: self.slots.capacity(),
524 decoded_window_count: self.windows.len(),
525 window_output_capacity: self.windows.capacity(),
526 window_opcode_scratch_capacity: scratch.window_opcodes.capacity(),
527 window_accumulator_scratch_capacity: scratch.windows.capacity(),
528 uses_caller_owned_scratch: true,
529 }
530 }
531}
532
533#[derive(Debug, Default)]
539pub struct TelemetryDecodeScratch {
540 pub(super) window_opcodes: Vec<u32>,
541 pub(super) windows: FxHashMap<(u32, u32), WindowAccumulator>,
542}
543
544impl TelemetryDecodeScratch {
545 #[must_use]
547 pub fn new() -> Self {
548 Self {
549 window_opcodes: Vec::new(),
550 windows: FxHashMap::default(),
551 }
552 }
553
554 pub fn clear(&mut self) {
556 self.window_opcodes.clear();
557 self.windows.clear();
558 }
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
562pub(super) struct WindowAccumulator {
563 pub(super) tenant_id: u32,
564 pub(super) opcode: u32,
565 pub(super) required_slots: u32,
566 pub(super) lookahead_slots: u32,
567 pub(super) published: u32,
568 pub(super) claimed: u32,
569 pub(super) done: u32,
570 pub(super) wait_io: u32,
571 pub(super) yield_count: u32,
572 pub(super) requeue: u32,
573 pub(super) fault: u32,
574}