use dev_report::{CheckResult, Evidence, Severity};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AllocationStats {
pub total_bytes: u64,
pub total_blocks: u64,
pub peak_bytes: u64,
pub peak_blocks: u64,
}
impl AllocationStats {
pub fn snapshot() -> Self {
let s = dhat::HeapStats::get();
Self {
total_bytes: s.total_bytes,
total_blocks: s.total_blocks,
peak_bytes: s.max_bytes as u64,
peak_blocks: s.max_blocks as u64,
}
}
pub fn compare_against_baseline(
&self,
name: &str,
baseline: Option<AllocationStats>,
pct_threshold: f64,
) -> CheckResult {
let check_name = format!("alloc::{}", name);
let mut evidence = vec![
Evidence::numeric("total_bytes", self.total_bytes as f64),
Evidence::numeric("total_blocks", self.total_blocks as f64),
Evidence::numeric("peak_bytes", self.peak_bytes as f64),
Evidence::numeric("peak_blocks", self.peak_blocks as f64),
];
let Some(base) = baseline else {
let mut c = CheckResult::skip(check_name).with_detail("no allocation baseline");
c.tags = vec!["alloc".to_string()];
c.evidence = evidence;
return c;
};
evidence.push(Evidence::numeric(
"baseline_total_bytes",
base.total_bytes as f64,
));
let allowed = base.total_bytes as f64 * (1.0 + pct_threshold / 100.0);
let regressed = (self.total_bytes as f64) > allowed;
let detail = format!(
"current_total_bytes={} baseline_total_bytes={} threshold_pct={}",
self.total_bytes, base.total_bytes, pct_threshold
);
if regressed {
let mut c = CheckResult::fail(check_name, Severity::Warning).with_detail(detail);
c.tags = vec!["alloc".to_string(), "regression".to_string()];
c.evidence = evidence;
c
} else {
let mut c = CheckResult::pass(check_name).with_detail(detail);
c.tags = vec!["alloc".to_string()];
c.evidence = evidence;
c
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use dev_report::Verdict;
fn synthetic(total_bytes: u64) -> AllocationStats {
AllocationStats {
total_bytes,
total_blocks: 10,
peak_bytes: total_bytes,
peak_blocks: 5,
}
}
#[test]
fn no_baseline_skips() {
let s = synthetic(1_000);
let c = s.compare_against_baseline("x", None, 10.0);
assert_eq!(c.verdict, Verdict::Skip);
assert!(c.has_tag("alloc"));
}
#[test]
fn within_threshold_passes() {
let curr = synthetic(105);
let base = synthetic(100);
let c = curr.compare_against_baseline("x", Some(base), 10.0);
assert_eq!(c.verdict, Verdict::Pass);
}
#[test]
fn over_threshold_fails() {
let curr = synthetic(120);
let base = synthetic(100);
let c = curr.compare_against_baseline("x", Some(base), 10.0);
assert_eq!(c.verdict, Verdict::Fail);
assert!(c.has_tag("regression"));
}
#[test]
fn evidence_includes_all_metrics() {
let curr = synthetic(100);
let c = curr.compare_against_baseline("x", None, 10.0);
let labels: Vec<&str> = c.evidence.iter().map(|e| e.label.as_str()).collect();
assert!(labels.contains(&"total_bytes"));
assert!(labels.contains(&"total_blocks"));
assert!(labels.contains(&"peak_bytes"));
assert!(labels.contains(&"peak_blocks"));
}
}