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