pub const COMPACT_DIAGNOSTIC_RECORD_BYTES: usize = 24;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DiagnosticAggregationPlan {
pub input_items: u32,
pub diagnostic_records: u32,
pub max_records: u32,
pub compact_readback_bytes: usize,
pub avoided_raw_readback_bytes: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DiagnosticAggregationError {
ZeroRecordBudget,
RecordBudgetExceeded {
diagnostic_records: u32,
max_records: u32,
},
ByteCountOverflow,
}
impl std::fmt::Display for DiagnosticAggregationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ZeroRecordBudget => f.write_str(
"diagnostic aggregation record budget is zero. Fix: choose a bounded nonzero diagnostic readback budget.",
),
Self::RecordBudgetExceeded {
diagnostic_records,
max_records,
} => write!(
f,
"device aggregated {diagnostic_records} diagnostics but budget allows {max_records}. Fix: increase max_records or fail with a truncated-diagnostics error before readback."
),
Self::ByteCountOverflow => f.write_str(
"diagnostic aggregation byte count overflowed. Fix: shard the input before compact diagnostic aggregation.",
),
}
}
}
impl std::error::Error for DiagnosticAggregationError {}
pub fn plan_compact_diagnostic_readback(
input_items: u32,
diagnostic_records: u32,
max_records: u32,
raw_record_bytes_per_item: usize,
) -> Result<DiagnosticAggregationPlan, DiagnosticAggregationError> {
if max_records == 0 {
return Err(DiagnosticAggregationError::ZeroRecordBudget);
}
if diagnostic_records > max_records {
return Err(DiagnosticAggregationError::RecordBudgetExceeded {
diagnostic_records,
max_records,
});
}
let compact_readback_bytes = (diagnostic_records as usize)
.checked_mul(COMPACT_DIAGNOSTIC_RECORD_BYTES)
.ok_or(DiagnosticAggregationError::ByteCountOverflow)?;
let raw_readback_bytes = (input_items as usize)
.checked_mul(raw_record_bytes_per_item)
.ok_or(DiagnosticAggregationError::ByteCountOverflow)?;
let avoided_raw_readback_bytes = raw_readback_bytes.saturating_sub(compact_readback_bytes);
Ok(DiagnosticAggregationPlan {
input_items,
diagnostic_records,
max_records,
compact_readback_bytes,
avoided_raw_readback_bytes,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compact_diagnostic_plan_bounds_readback_to_records() {
let plan = plan_compact_diagnostic_readback(1_000_000, 3, 16, 16)
.expect("Fix: bounded compact diagnostics should plan");
assert_eq!(plan.compact_readback_bytes, 72);
assert_eq!(plan.avoided_raw_readback_bytes, 15_999_928);
assert_eq!(plan.max_records, 16);
}
#[test]
fn compact_diagnostic_plan_accepts_zero_actual_records() {
let plan = plan_compact_diagnostic_readback(4096, 0, 8, 16)
.expect("Fix: zero diagnostics still has a bounded readback plan");
assert_eq!(plan.compact_readback_bytes, 0);
assert_eq!(plan.avoided_raw_readback_bytes, 65_536);
}
#[test]
fn compact_diagnostic_plan_rejects_unbounded_or_over_budget_records() {
assert_eq!(
plan_compact_diagnostic_readback(100, 1, 0, 16).expect_err("zero budget must fail"),
DiagnosticAggregationError::ZeroRecordBudget
);
assert_eq!(
plan_compact_diagnostic_readback(100, 9, 8, 16)
.expect_err("over budget records must fail"),
DiagnosticAggregationError::RecordBudgetExceeded {
diagnostic_records: 9,
max_records: 8,
}
);
}
}