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