1use 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 #[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 #[cfg(any(test, feature = "legacy-infallible"))]
65 pub fn decode_into(control_bytes: &[u8], out: &mut Self) {
66 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 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 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 #[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 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 #[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 #[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 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 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 #[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 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 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 #[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 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 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 #[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 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 #[cfg(any(test, feature = "legacy-infallible"))]
498 pub fn active_windows_into<'a>(&'a self, out: &mut Vec<&'a WindowTelemetry>) {
499 if let Err(error) = self.try_active_windows_into(out) {
503 panic!("vyre-runtime telemetry active-windows readback failed: {error}");
504 }
505 }
506
507 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 #[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 #[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 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 #[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 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 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#[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#[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}