Skip to main content

vyre_runtime/megakernel/
telemetry.rs

1//! Host-side telemetry decoders for the megakernel ring and control buffers.
2//!
3//! The runtime already exposes low-level helpers such as
4//! `read_done_count`, `read_epoch`, and `read_metrics`. This module adds a
5//! single structured snapshot surface useful for wrappers like VyreOffload.
6
7use super::protocol::{control, slot, ARG0_WORD, OPCODE_WORD, STATUS_WORD, TENANT_WORD};
8use super::scaling::{
9    MegakernelLaunchPolicy, MegakernelLaunchRecommendation, MegakernelLaunchRequest,
10    PriorityRequeueAccounting,
11};
12use super::staging_reserve::{
13    reserve_hash_map_capacity, reserve_vec_capacity as reserve_target_capacity,
14};
15use crate::PipelineError;
16
17mod sketch;
18mod types;
19mod errors;
20pub use sketch::{CountMinSketch, SketchTelemetry, SketchTelemetryScratch};
21use types::WindowAccumulator;
22pub use types::{
23    ControlSnapshot, MegakernelRuntimeCounters, MegakernelRuntimeEvidence,
24    MegakernelWatchdogSnapshot, RingOccupancy, RingSlotSnapshot, RingStatus, RingTelemetry,
25    RuntimeEvidenceMetricCoverage, RuntimeEvidenceMetricFamily, TelemetryDecodeCapacityEvidence,
26    TelemetryDecodeScratch, WindowTelemetry, RUNTIME_IO_EVIDENCE_SCHEMA_VERSION,
27    TELEMETRY_DECODE_CAPACITY_SCHEMA_VERSION,
28};
29
30const SLOT_WORDS_USIZE: usize = 16;
31
32fn read_word(buf: &[u8], word_idx: usize) -> Option<u32> {
33    let off = word_idx.checked_mul(4)?;
34    let end = off.checked_add(4)?;
35    let bytes = buf.get(off..end)?;
36    Some(u32::from_le_bytes(bytes.try_into().ok()?))
37}
38
39fn try_read_slot_chunk_word(slot_bytes: &[u8], word_idx: u32) -> Result<u32, PipelineError> {
40    let word_idx = telemetry_u32_to_usize(word_idx, "slot word index")?;
41    let off = word_idx.checked_mul(4).ok_or_else(|| {
42        errors::slot_word_offset_overflow()
43    })?;
44    let end = off.checked_add(4).ok_or_else(|| {
45        errors::slot_word_end_overflow()
46    })?;
47    let bytes = slot_bytes.get(off..end).ok_or_else(|| {
48        errors::missing_slot_word(word_idx, slot_bytes.len())
49    })?;
50    Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
51}
52
53fn is_sorted_unique_u32(values: &[u32]) -> bool {
54    values.windows(2).all(|pair| pair[0] < pair[1])
55}
56
57impl ControlSnapshot {
58    /// Decode a structured control-buffer view.
59    #[must_use]
60    #[cfg(any(test, feature = "legacy-infallible"))]
61    pub fn decode(control_bytes: &[u8]) -> Self {
62        match Self::try_decode(control_bytes) {
63            Ok(snapshot) => snapshot,
64            Err(_) => Self::default(),
65        }
66    }
67
68    /// Decode a structured control-buffer view into caller-owned storage.
69    #[cfg(any(test, feature = "legacy-infallible"))]
70    pub fn decode_into(control_bytes: &[u8], out: &mut Self) {
71        // Resetting to default on decode failure silently reports zeroed
72        // telemetry as if it were a real reading (Law 10). Fail loud; callers
73        // use try_decode_into.
74        if let Err(error) = Self::try_decode_into(control_bytes, out) {
75            panic!("vyre-runtime telemetry control-buffer decode failed: {error}");
76        }
77    }
78
79    /// Strictly decode a structured control-buffer view into owned storage.
80    ///
81    /// # Errors
82    ///
83    /// Returns [`PipelineError`] when any fixed control word is missing from
84    /// the control snapshot.
85    pub fn try_decode(control_bytes: &[u8]) -> Result<Self, PipelineError> {
86        let mut out = Self::default();
87        Self::try_decode_into(control_bytes, &mut out)?;
88        Ok(out)
89    }
90
91    /// Strictly decode a structured control-buffer view.
92    ///
93    /// # Errors
94    ///
95    /// Returns [`PipelineError`] when any fixed control word is missing from
96    /// the control snapshot.
97    pub fn try_decode_into(control_bytes: &[u8], out: &mut Self) -> Result<(), PipelineError> {
98        validate_control_snapshot(control_bytes)?;
99        out.shutdown =
100            read_required_control_word(control_bytes, control_word_index(control::SHUTDOWN)?)? != 0;
101        out.done_count =
102            read_required_control_word(control_bytes, control_word_index(control::DONE_COUNT)?)?;
103        out.epoch = read_required_control_word(control_bytes, control_word_index(control::EPOCH)?)?;
104        out.metrics.clear();
105        reserve_target_capacity(
106            &mut out.metrics,
107            telemetry_u32_to_usize(control::METRICS_SLOTS, "metrics slot count")?,
108            "metrics",
109        )?;
110        for i in 0..control::METRICS_SLOTS {
111            let count = read_required_control_word(
112                control_bytes,
113                control_offset_index(control::METRICS_BASE, i)?,
114            )?;
115            if count > 0 {
116                out.metrics.push((i, count));
117            }
118        }
119        out.tenant_fairness.clear();
120        reserve_target_capacity(
121            &mut out.tenant_fairness,
122            telemetry_u32_to_usize(control::TENANT_FAIRNESS_SLOTS, "tenant fairness slot count")?,
123            "tenant fairness",
124        )?;
125        for i in 0..control::TENANT_FAIRNESS_SLOTS {
126            out.tenant_fairness.push(read_required_control_word(
127                control_bytes,
128                control_offset_index(control::TENANT_FAIRNESS_BASE, i)?,
129            )?);
130        }
131        out.priority_fairness.clear();
132        reserve_target_capacity(
133            &mut out.priority_fairness,
134            telemetry_u32_to_usize(
135                control::PRIORITY_FAIRNESS_SLOTS,
136                "priority fairness slot count",
137            )?,
138            "priority fairness",
139        )?;
140        for i in 0..control::PRIORITY_FAIRNESS_SLOTS {
141            out.priority_fairness.push(read_required_control_word(
142                control_bytes,
143                control_offset_index(control::PRIORITY_FAIRNESS_BASE, i)?,
144            )?);
145        }
146        Ok(())
147    }
148}
149
150impl RingTelemetry {
151    /// Decode the ring and control buffers into one structured snapshot.
152    #[must_use]
153    #[cfg(any(test, feature = "legacy-infallible"))]
154    pub fn decode(control_bytes: &[u8], ring_bytes: &[u8]) -> Self {
155        Self::decode_with_window_opcodes(control_bytes, ring_bytes, &[])
156    }
157
158    /// Strictly decode ring and control bytes after validating ABI alignment.
159    ///
160    /// # Errors
161    ///
162    /// Returns [`PipelineError`] when buffers are truncated or not aligned to
163    /// the megakernel wire protocol.
164    pub fn try_decode(control_bytes: &[u8], ring_bytes: &[u8]) -> Result<Self, PipelineError> {
165        Self::try_decode_with_window_opcodes(control_bytes, ring_bytes, &[])
166    }
167
168    /// Decode the ring and control buffers, additionally grouping any slots
169    /// whose opcode is present in `window_opcodes` into ticketed route-window
170    /// telemetry records.
171    #[must_use]
172    #[cfg(any(test, feature = "legacy-infallible"))]
173    pub fn decode_with_window_opcodes(
174        control_bytes: &[u8],
175        ring_bytes: &[u8],
176        window_opcodes: &[u32],
177    ) -> Self {
178        match Self::try_decode_with_window_opcodes(control_bytes, ring_bytes, window_opcodes) {
179            Ok(telemetry) => telemetry,
180            Err(_) => Self::default(),
181        }
182    }
183
184    /// Decode the ring and control buffers into caller-owned telemetry and
185    /// scratch storage.
186    #[cfg(any(test, feature = "legacy-infallible"))]
187    pub fn decode_with_window_opcodes_into(
188        control_bytes: &[u8],
189        ring_bytes: &[u8],
190        window_opcodes: &[u32],
191        out: &mut Self,
192        scratch: &mut TelemetryDecodeScratch,
193    ) {
194        Self::try_decode_with_window_opcodes_into(
195            control_bytes,
196            ring_bytes,
197            window_opcodes,
198            out,
199            scratch,
200        )
201        .unwrap_or_else(|_| {
202            *out = Self::default();
203            scratch.clear();
204        });
205    }
206
207    fn try_decode_with_window_opcodes_into_unchecked(
208        control_bytes: &[u8],
209        ring_bytes: &[u8],
210        window_opcodes: &[u32],
211        out: &mut Self,
212        scratch: &mut TelemetryDecodeScratch,
213    ) -> Result<(), PipelineError> {
214        enum WindowOpcodeMatcher<'a> {
215            None,
216            Single(u32),
217            DenseBitmap(u128),
218            SmallSlice(&'a [u32]),
219            LargeSlice(&'a [u32]),
220        }
221
222        ControlSnapshot::try_decode_into(control_bytes, &mut out.control)?;
223        let slot_count = ring_bytes.len() / slot_byte_len()?;
224        out.occupancy = RingOccupancy::default();
225        out.slots.clear();
226        reserve_target_capacity(&mut out.slots, slot_count, "ring slots")?;
227        out.windows.clear();
228        scratch.window_opcodes.clear();
229        scratch.windows.clear();
230        let window_opcode_lookup = if window_opcodes.is_empty() {
231            &[][..]
232        } else if is_sorted_unique_u32(window_opcodes) {
233            window_opcodes
234        } else {
235            reserve_target_capacity(
236                &mut scratch.window_opcodes,
237                window_opcodes.len(),
238                "window opcode scratch",
239            )?;
240            scratch.window_opcodes.extend_from_slice(window_opcodes);
241            scratch.window_opcodes.sort_unstable();
242            scratch.window_opcodes.dedup();
243            scratch.window_opcodes.as_slice()
244        };
245        let window_opcode_matcher = match window_opcode_lookup {
246            [] => WindowOpcodeMatcher::None,
247            [opcode] => WindowOpcodeMatcher::Single(*opcode),
248            opcodes if opcodes.len() > 1 && opcodes.iter().all(|opcode| *opcode < 128) => {
249                let bitmap = opcodes
250                    .iter()
251                    .fold(0_u128, |acc, &opcode| acc | (1_u128 << opcode));
252                WindowOpcodeMatcher::DenseBitmap(bitmap)
253            }
254            opcodes if opcodes.len() <= 8 => WindowOpcodeMatcher::SmallSlice(opcodes),
255            opcodes => WindowOpcodeMatcher::LargeSlice(opcodes),
256        };
257        if !matches!(window_opcode_matcher, WindowOpcodeMatcher::None) {
258            reserve_hash_map_capacity(
259                &mut scratch.windows,
260                slot_count,
261                "window accumulator scratch",
262            )?;
263        }
264        let decode_windows = !matches!(window_opcode_matcher, WindowOpcodeMatcher::None);
265
266        let slot_byte_len = slot_byte_len()?;
267        for (slot_idx, slot_bytes) in ring_bytes.chunks_exact(slot_byte_len).enumerate() {
268            let slot_idx = u32::try_from(slot_idx).map_err(|source| {
269                PipelineError::Backend(format!(
270                    "megakernel telemetry slot index cannot fit u32: {source}. Fix: shard ring snapshots before host decode."
271                ))
272            })?;
273            let status_raw = try_read_slot_chunk_word(slot_bytes, STATUS_WORD)?;
274            let status = RingStatus::from_raw(status_raw);
275            match status {
276                RingStatus::Empty => out.occupancy.empty += 1,
277                RingStatus::Published => out.occupancy.published += 1,
278                RingStatus::Claimed => out.occupancy.claimed += 1,
279                RingStatus::Done => out.occupancy.done += 1,
280                RingStatus::WaitIo => out.occupancy.wait_io += 1,
281                RingStatus::Yield => out.occupancy.yield_count += 1,
282                RingStatus::Requeue => out.occupancy.requeue += 1,
283                RingStatus::Fault => out.occupancy.fault += 1,
284                RingStatus::Unknown(_) => out.occupancy.unknown += 1,
285            }
286            let tenant_id = try_read_slot_chunk_word(slot_bytes, TENANT_WORD)?;
287            let opcode = try_read_slot_chunk_word(slot_bytes, OPCODE_WORD)?;
288            let args_prefix = [
289                try_read_slot_chunk_word(slot_bytes, ARG0_WORD)?,
290                try_read_slot_chunk_word(slot_bytes, ARG0_WORD + 1)?,
291                try_read_slot_chunk_word(slot_bytes, ARG0_WORD + 2)?,
292            ];
293            let is_window_opcode = match window_opcode_matcher {
294                WindowOpcodeMatcher::None => false,
295                WindowOpcodeMatcher::Single(expected) => opcode == expected,
296                WindowOpcodeMatcher::DenseBitmap(bitmap) => {
297                    opcode < 128 && ((bitmap >> opcode) & 1) == 1
298                }
299                WindowOpcodeMatcher::SmallSlice(window_opcodes) => window_opcodes.contains(&opcode),
300                WindowOpcodeMatcher::LargeSlice(window_opcodes) => {
301                    window_opcodes.binary_search(&opcode).is_ok()
302                }
303            };
304            if decode_windows && is_window_opcode {
305                let ticket = args_prefix[0];
306                let class_tag = args_prefix[1];
307                let entry =
308                    scratch
309                        .windows
310                        .entry((ticket, opcode))
311                        .or_insert_with(|| WindowAccumulator {
312                            tenant_id,
313                            opcode,
314                            ..WindowAccumulator::default()
315                        });
316                match class_tag {
317                    0 => entry.required_slots += 1,
318                    1 => entry.lookahead_slots += 1,
319                    _ => {}
320                }
321                match status {
322                    RingStatus::Published => entry.published += 1,
323                    RingStatus::Claimed => entry.claimed += 1,
324                    RingStatus::Done => entry.done += 1,
325                    RingStatus::WaitIo => entry.wait_io += 1,
326                    RingStatus::Yield => entry.yield_count += 1,
327                    RingStatus::Requeue => entry.requeue += 1,
328                    RingStatus::Fault => entry.fault += 1,
329                    RingStatus::Empty | RingStatus::Unknown(_) => {}
330                }
331            }
332            out.slots.push(RingSlotSnapshot {
333                slot_idx,
334                status,
335                tenant_id,
336                opcode,
337                args_prefix,
338            });
339        }
340
341        reserve_target_capacity(&mut out.windows, scratch.windows.len(), "window output")?;
342        for (&(ticket, _), acc) in &scratch.windows {
343            out.windows.push(WindowTelemetry {
344                ticket,
345                tenant_id: acc.tenant_id,
346                opcode: acc.opcode,
347                required_slots: acc.required_slots,
348                lookahead_slots: acc.lookahead_slots,
349                published: acc.published,
350                claimed: acc.claimed,
351                done: acc.done,
352                wait_io: acc.wait_io,
353                yield_count: acc.yield_count,
354                requeue: acc.requeue,
355                fault: acc.fault,
356            });
357        }
358        out.windows
359            .sort_unstable_by_key(|window| (window.ticket, window.opcode));
360        Ok(())
361    }
362
363    /// Strictly decode ring/control bytes and group selected window opcodes.
364    ///
365    /// # Errors
366    ///
367    /// Returns [`PipelineError`] when buffers are truncated or not aligned to
368    /// the megakernel wire protocol.
369    pub fn try_decode_with_window_opcodes(
370        control_bytes: &[u8],
371        ring_bytes: &[u8],
372        window_opcodes: &[u32],
373    ) -> Result<Self, PipelineError> {
374        validate_telemetry_buffers(control_bytes, ring_bytes)?;
375        let mut out = Self::default();
376        let mut scratch = TelemetryDecodeScratch::new();
377        Self::try_decode_with_window_opcodes_into_unchecked(
378            control_bytes,
379            ring_bytes,
380            window_opcodes,
381            &mut out,
382            &mut scratch,
383        )?;
384        Ok(out)
385    }
386
387    /// Strictly decode ring/control bytes into caller-owned telemetry and
388    /// scratch storage.
389    ///
390    /// # Errors
391    ///
392    /// Returns [`PipelineError`] when buffers are truncated or not aligned to
393    /// the megakernel wire protocol.
394    pub fn try_decode_with_window_opcodes_into(
395        control_bytes: &[u8],
396        ring_bytes: &[u8],
397        window_opcodes: &[u32],
398        out: &mut Self,
399        scratch: &mut TelemetryDecodeScratch,
400    ) -> Result<(), PipelineError> {
401        validate_telemetry_buffers(control_bytes, ring_bytes)?;
402        Self::try_decode_with_window_opcodes_into_unchecked(
403            control_bytes,
404            ring_bytes,
405            window_opcodes,
406            out,
407            scratch,
408        )?;
409        Ok(())
410    }
411
412    /// Active slots matching a given opcode.
413    #[must_use]
414    #[cfg(any(test, feature = "legacy-infallible"))]
415    pub fn active_slots_for_opcode(&self, opcode: u32) -> Vec<&RingSlotSnapshot> {
416        match self.try_active_slots_for_opcode(opcode) {
417            Ok(slots) => slots,
418            Err(_) => Vec::default(),
419        }
420    }
421
422    /// Active slots matching a given opcode with fallible output staging.
423    ///
424    /// # Errors
425    ///
426    /// Returns [`PipelineError`] when output storage cannot be reserved.
427    pub fn try_active_slots_for_opcode(
428        &self,
429        opcode: u32,
430    ) -> Result<Vec<&RingSlotSnapshot>, PipelineError> {
431        let mut out = Vec::default();
432        self.try_active_slots_for_opcode_into(opcode, &mut out)?;
433        Ok(out)
434    }
435
436    /// Active slots matching a given opcode as an iterator.
437    pub fn active_slots_for_opcode_iter(
438        &self,
439        opcode: u32,
440    ) -> impl Iterator<Item = &RingSlotSnapshot> {
441        self.slots
442            .iter()
443            .filter(move |slot| slot.opcode == opcode && slot.status.is_active())
444    }
445
446    /// Active slots matching a given opcode into caller-owned storage.
447    #[cfg(any(test, feature = "legacy-infallible"))]
448    pub fn active_slots_for_opcode_into<'a>(
449        &'a self,
450        opcode: u32,
451        out: &mut Vec<&'a RingSlotSnapshot>,
452    ) {
453        // Clearing to empty on failure silently reports "no active slots" when
454        // the readback actually failed (Law 10). Fail loud; callers use
455        // try_active_slots_for_opcode_into.
456        if let Err(error) = self.try_active_slots_for_opcode_into(opcode, out) {
457            panic!("vyre-runtime telemetry active-slots readback failed: {error}");
458        }
459    }
460
461    /// Active slots matching a given opcode into caller-owned storage.
462    ///
463    /// # Errors
464    ///
465    /// Returns [`PipelineError`] when output storage cannot be reserved.
466    pub fn try_active_slots_for_opcode_into<'a>(
467        &'a self,
468        opcode: u32,
469        out: &mut Vec<&'a RingSlotSnapshot>,
470    ) -> Result<(), PipelineError> {
471        out.clear();
472        reserve_target_capacity(out, self.slots.len(), "active slot output")?;
473        self.slots
474            .iter()
475            .filter(|slot| slot.opcode == opcode && slot.status.is_active())
476            .for_each(|slot| out.push(slot));
477        Ok(())
478    }
479
480    /// Unfinished ticketed windows.
481    #[must_use]
482    #[cfg(any(test, feature = "legacy-infallible"))]
483    pub fn active_windows(&self) -> Vec<&WindowTelemetry> {
484        match self.try_active_windows() {
485            Ok(windows) => windows,
486            Err(_) => Vec::default(),
487        }
488    }
489
490    /// Unfinished ticketed windows with fallible output staging.
491    ///
492    /// # Errors
493    ///
494    /// Returns [`PipelineError`] when output storage cannot be reserved.
495    pub fn try_active_windows(&self) -> Result<Vec<&WindowTelemetry>, PipelineError> {
496        let mut out = Vec::default();
497        self.try_active_windows_into(&mut out)?;
498        Ok(out)
499    }
500
501    /// Unfinished ticketed windows into caller-owned storage.
502    #[cfg(any(test, feature = "legacy-infallible"))]
503    pub fn active_windows_into<'a>(&'a self, out: &mut Vec<&'a WindowTelemetry>) {
504        // Clearing to empty on failure silently reports "no active windows"
505        // when the readback actually failed (Law 10). Fail loud; callers use
506        // try_active_windows_into.
507        if let Err(error) = self.try_active_windows_into(out) {
508            panic!("vyre-runtime telemetry active-windows readback failed: {error}");
509        }
510    }
511
512    /// Unfinished ticketed windows into caller-owned storage.
513    ///
514    /// # Errors
515    ///
516    /// Returns [`PipelineError`] when output storage cannot be reserved.
517    pub fn try_active_windows_into<'a>(
518        &'a self,
519        out: &mut Vec<&'a WindowTelemetry>,
520    ) -> Result<(), PipelineError> {
521        out.clear();
522        reserve_target_capacity(out, self.windows.len(), "active window output")?;
523        self.windows
524            .iter()
525            .filter(|window| window.is_active())
526            .for_each(|window| out.push(window));
527        Ok(())
528    }
529
530    /// Summarize priority requeue/aging pressure visible in the ring snapshot.
531    #[must_use]
532    pub fn priority_accounting(&self) -> PriorityRequeueAccounting {
533        PriorityRequeueAccounting {
534            requeue_count: u64::from(self.occupancy.requeue),
535            aged_promotions: 0,
536            max_priority_age: 0,
537        }
538    }
539
540    /// Aggregate queue, idle, fairness, and drain counters into one cheap
541    /// runtime snapshot for SRE dashboards and launch-policy feedback.
542    #[must_use]
543    #[cfg(any(test, feature = "legacy-infallible"))]
544    pub fn runtime_counters(&self) -> MegakernelRuntimeCounters {
545        match self.try_runtime_counters() {
546            Ok(counters) => counters,
547            Err(_) => zero_runtime_counters(),
548        }
549    }
550
551    /// Fallibly aggregate queue, idle, fairness, and drain counters.
552    ///
553    /// # Errors
554    ///
555    /// Returns [`PipelineError`] when counter aggregation overflows or decoded
556    /// telemetry contains an impossible relationship.
557    pub fn try_runtime_counters(&self) -> Result<MegakernelRuntimeCounters, PipelineError> {
558        let total_slots = self.occupancy.total_slots();
559        let queue_depth = self.occupancy.queue_depth();
560        let gpu_idle_slots = self.occupancy.empty;
561        let gpu_idle_ppm = if total_slots == 0 {
562            0
563        } else {
564            let raw_idle_ppm = (u64::from(gpu_idle_slots) * 1_000_000) / u64::from(total_slots);
565            raw_idle_ppm.min(1_000_000) as u32
566        };
567        let frontier_density_bps = try_density_bps(queue_depth, total_slots)?;
568        let active_slots = total_slots.saturating_sub(gpu_idle_slots);
569        let occupancy_proxy_bps = try_density_bps(active_slots, total_slots)?;
570        let tenant_fairness_total = try_sum_u32_as_u64(
571            &self.control.tenant_fairness,
572            "tenant fairness total",
573            "shard tenant counters before telemetry aggregation",
574        )?;
575        let priority_fairness_total = try_sum_u32_as_u64(
576            &self.control.priority_fairness,
577            "priority fairness total",
578            "shard priority counters before telemetry aggregation",
579        )?;
580        let tenant_fairness_skew = try_fairness_skew(&self.control.tenant_fairness)?;
581        Ok(MegakernelRuntimeCounters {
582            total_slots,
583            queue_depth,
584            gpu_idle_slots,
585            gpu_idle_ppm,
586            frontier_density_bps,
587            occupancy_proxy_bps,
588            drained_slots: self.control.done_count,
589            unreclaimed_done_slots: self.occupancy.done,
590            tenant_fairness_total,
591            tenant_fairness_skew,
592            priority_fairness_total,
593            requeue_slots: self.occupancy.requeue,
594            fault_slots: self.occupancy.fault,
595        })
596    }
597
598    /// Derive persistent-kernel health from two snapshots without polling the
599    /// device or synchronizing with the GPU.
600    #[must_use]
601    #[cfg(any(test, feature = "legacy-infallible"))]
602    pub fn health_since(&self, previous: &RingTelemetry) -> MegakernelWatchdogSnapshot {
603        match self.try_health_since(previous) {
604            Ok(snapshot) => snapshot,
605            Err(_) => zero_watchdog_snapshot(),
606        }
607    }
608
609    /// Fallibly derive persistent-kernel health from two snapshots.
610    ///
611    /// # Errors
612    ///
613    /// Returns [`PipelineError`] when counters wrap, move backwards, or cannot
614    /// be aggregated without overflow.
615    pub fn try_health_since(
616        &self,
617        previous: &RingTelemetry,
618    ) -> Result<MegakernelWatchdogSnapshot, PipelineError> {
619        let counters = self.try_runtime_counters()?;
620        let done_delta = self
621            .control
622            .done_count
623            .checked_sub(previous.control.done_count)
624            .ok_or_else(|| {
625                errors::done_counter_backwards(previous.control.done_count, self.control.done_count)
626            })?;
627        let suspected_stall =
628            counters.queue_depth > 0 && done_delta == 0 && counters.fault_slots == 0;
629        Ok(MegakernelWatchdogSnapshot {
630            done_delta,
631            queue_depth: counters.queue_depth,
632            fault_slots: counters.fault_slots,
633            requeue_slots: counters.requeue_slots,
634            gpu_idle_ppm: counters.gpu_idle_ppm,
635            suspected_stall,
636        })
637    }
638
639    /// Feed telemetry into the shared launch policy.
640    ///
641    /// # Errors
642    ///
643    /// Returns a backend error when the supplied adapter limits are malformed.
644    pub fn recommend_launch(
645        &self,
646        mut request: MegakernelLaunchRequest,
647    ) -> Result<MegakernelLaunchRecommendation, vyre_driver::BackendError> {
648        let counters = self
649            .try_runtime_counters()
650            .map_err(errors::launch_telemetry_failed)?;
651        if request.graph_node_count == 0 {
652            request.graph_node_count = counters.total_slots;
653        }
654        if request.graph_edge_count == 0 {
655            request.graph_edge_count = counters.queue_depth;
656        }
657        if request.frontier_density_bps == 0 {
658            request.frontier_density_bps = counters.frontier_density_bps;
659        }
660        request.hot_opcode_count = self
661            .control
662            .metrics
663            .iter()
664            .filter(|(_, count)| *count > 0)
665            .count()
666            .try_into()
667            .map_err(errors::hot_opcode_count_overflow)?;
668        let mut hot_window_count = 0usize;
669        for window in &self.windows {
670            let demand = window
671                .required_slots
672                .checked_add(window.lookahead_slots)
673                .ok_or_else(|| {
674                    errors::route_window_demand_overflow()
675                })?;
676            if demand >= 4 {
677                hot_window_count = hot_window_count.checked_add(1).ok_or_else(|| {
678                    errors::hot_window_count_overflow()
679                })?;
680            }
681        }
682        request.hot_window_count = hot_window_count
683            .try_into()
684            .map_err(errors::hot_window_count_too_wide)?;
685        request.requeue_count = request
686            .requeue_count
687            .checked_add(u64::from(self.occupancy.requeue))
688            .ok_or_else(errors::requeue_count_overflow)?;
689        MegakernelLaunchPolicy::standard().recommend(request)
690    }
691}
692
693/// All-zero runtime counters, returned by the infallible `runtime_counters`
694/// accessor when the fallible decode path reports an error.
695#[cfg(any(test, feature = "legacy-infallible"))]
696fn zero_runtime_counters() -> MegakernelRuntimeCounters {
697    MegakernelRuntimeCounters {
698        total_slots: 0,
699        queue_depth: 0,
700        gpu_idle_slots: 0,
701        gpu_idle_ppm: 0,
702        frontier_density_bps: 0,
703        occupancy_proxy_bps: 0,
704        drained_slots: 0,
705        unreclaimed_done_slots: 0,
706        tenant_fairness_total: 0,
707        tenant_fairness_skew: 0,
708        priority_fairness_total: 0,
709        requeue_slots: 0,
710        fault_slots: 0,
711    }
712}
713
714/// All-zero watchdog snapshot, returned by the infallible `health_since`
715/// accessor when the fallible derivation path reports an error.
716#[cfg(any(test, feature = "legacy-infallible"))]
717fn zero_watchdog_snapshot() -> MegakernelWatchdogSnapshot {
718    MegakernelWatchdogSnapshot {
719        done_delta: 0,
720        queue_depth: 0,
721        fault_slots: 0,
722        requeue_slots: 0,
723        gpu_idle_ppm: 0,
724        suspected_stall: false,
725    }
726}
727
728fn read_required_control_word(control_bytes: &[u8], word_idx: usize) -> Result<u32, PipelineError> {
729    read_word(control_bytes, word_idx).ok_or_else(|| errors::missing_control_word(word_idx))
730}
731
732fn try_density_bps(numerator: u32, denominator: u32) -> Result<u16, PipelineError> {
733    if denominator == 0 {
734        return Ok(0);
735    }
736    let bps = (u64::from(numerator) * 10_000) / u64::from(denominator);
737    u16::try_from(bps.min(u64::from(u16::MAX))).map_err(errors::density_bps_overflow)
738}
739
740fn validate_telemetry_buffers(
741    control_bytes: &[u8],
742    ring_bytes: &[u8],
743) -> Result<(), PipelineError> {
744    validate_control_snapshot(control_bytes)?;
745    let slot_bytes = slot_byte_len()?;
746    if ring_bytes.len() % slot_bytes != 0 {
747        return Err(errors::ring_slot_alignment(ring_bytes.len(), slot_bytes));
748    }
749    let slot_count = ring_bytes.len() / slot_bytes;
750    if u32::try_from(slot_count).is_err() {
751        return Err(errors::ring_slot_count_too_wide(slot_count));
752    }
753    Ok(())
754}
755
756fn validate_control_snapshot(control_bytes: &[u8]) -> Result<(), PipelineError> {
757    let min_control = super::protocol::control_byte_len(0).ok_or_else(|| {
758        errors::control_length_overflow()
759    })?;
760    if control_bytes.len() < min_control || control_bytes.len() % 4 != 0 {
761        return Err(errors::bad_control_snapshot(
762            control_bytes.len(),
763            min_control,
764        ));
765    }
766    Ok(())
767}
768
769fn slot_byte_len() -> Result<usize, PipelineError> {
770    SLOT_WORDS_USIZE.checked_mul(4).ok_or_else(|| {
771        errors::slot_byte_width_overflow()
772    })
773}
774
775fn telemetry_u32_to_usize(value: u32, label: &'static str) -> Result<usize, PipelineError> {
776    usize::try_from(value).map_err(|source| errors::telemetry_u32_to_usize(value, label, source))
777}
778
779fn control_word_index(word: u32) -> Result<usize, PipelineError> {
780    usize::try_from(word).map_err(|source| errors::control_word_index(word, source))
781}
782
783fn control_offset_index(base: u32, offset: u32) -> Result<usize, PipelineError> {
784    let word = base.checked_add(offset).ok_or_else(|| {
785        errors::control_word_offset_overflow()
786    })?;
787    control_word_index(word)
788}
789
790fn try_sum_u32_as_u64(
791    counters: &[u32],
792    label: &'static str,
793    fix: &'static str,
794) -> Result<u64, PipelineError> {
795    counters.iter().try_fold(0u64, |acc, &count| {
796        acc.checked_add(u64::from(count)).ok_or_else(|| {
797            errors::counter_sum_overflow(label, fix)
798        })
799    })
800}
801
802fn try_fairness_skew(counters: &[u32]) -> Result<u32, PipelineError> {
803    let mut min_nonzero = u32::MAX;
804    let mut max = 0u32;
805    for &count in counters {
806        if count != 0 {
807            min_nonzero = min_nonzero.min(count);
808            max = max.max(count);
809        }
810    }
811    if min_nonzero == u32::MAX {
812        Ok(0)
813    } else {
814        max.checked_sub(min_nonzero).ok_or_else(|| {
815            errors::fairness_skew_invalid(max, min_nonzero)
816        })
817    }
818}
819
820#[cfg(test)]
821mod tests {
822    include!("telemetry_tests.rs");
823}