1use 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 #[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 #[cfg(any(test, feature = "legacy-infallible"))]
76 pub fn decode_into(control_bytes: &[u8], out: &mut Self) {
77 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 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 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 #[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 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 #[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 #[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 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 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 #[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 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 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 #[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 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 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 #[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 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 #[cfg(any(test, feature = "legacy-infallible"))]
509 pub fn active_windows_into<'a>(&'a self, out: &mut Vec<&'a WindowTelemetry>) {
510 if let Err(error) = self.try_active_windows_into(out) {
514 panic!("vyre-runtime telemetry active-windows readback failed: {error}");
515 }
516 }
517
518 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 #[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 #[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 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 #[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 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 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#[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#[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}