Skip to main content

koala_core/invariant/
perf.rs

1//! Performance baseline check: pass/fail decision for a measured p99
2//! against a stored baseline. The actual measurement comes from
3//! criterion (or any timing harness) — this module owns the policy.
4//!
5//! v1.0 spec: a measurement that exceeds `baseline + tolerance` is a
6//! Fail; under-or-equal is Pass; missing baseline is Skip (informational
7//! first run).
8
9use std::time::Duration;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Baseline {
13    pub p99: Duration,
14    /// Allowed slack above baseline. 5–10 % is typical to avoid flakes.
15    pub tolerance: Duration,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum PerfDecision {
20    Pass {
21        measured: Duration,
22        ceiling: Duration,
23    },
24    Fail {
25        measured: Duration,
26        ceiling: Duration,
27        excess: Duration,
28    },
29    Skip {
30        reason: &'static str,
31    },
32}
33
34pub fn evaluate_against_baseline(measured: Duration, baseline: Option<Baseline>) -> PerfDecision {
35    let Some(baseline) = baseline else {
36        return PerfDecision::Skip {
37            reason: "no baseline yet — record this run as the first sample",
38        };
39    };
40    let ceiling = baseline.p99 + baseline.tolerance;
41    if measured <= ceiling {
42        PerfDecision::Pass { measured, ceiling }
43    } else {
44        PerfDecision::Fail {
45            measured,
46            ceiling,
47            excess: measured - ceiling,
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn perf_baseline_regression_blocks() {
58        // Baseline 5 ms ± 1 ms tolerance → ceiling 6 ms.
59        let baseline = Baseline {
60            p99: Duration::from_millis(5),
61            tolerance: Duration::from_millis(1),
62        };
63        // 7 ms measurement is 1 ms above ceiling → must Fail.
64        let decision = evaluate_against_baseline(Duration::from_millis(7), Some(baseline));
65        match decision {
66            PerfDecision::Fail {
67                measured,
68                ceiling,
69                excess,
70            } => {
71                assert_eq!(measured, Duration::from_millis(7));
72                assert_eq!(ceiling, Duration::from_millis(6));
73                assert_eq!(excess, Duration::from_millis(1));
74            }
75            other => panic!("expected Fail, got {other:?}"),
76        }
77    }
78
79    #[test]
80    fn within_tolerance_passes() {
81        let baseline = Baseline {
82            p99: Duration::from_millis(5),
83            tolerance: Duration::from_millis(1),
84        };
85        assert!(matches!(
86            evaluate_against_baseline(Duration::from_millis(6), Some(baseline)),
87            PerfDecision::Pass { .. }
88        ));
89        assert!(matches!(
90            evaluate_against_baseline(Duration::from_millis(3), Some(baseline)),
91            PerfDecision::Pass { .. }
92        ));
93    }
94
95    #[test]
96    fn no_baseline_is_skip() {
97        let decision = evaluate_against_baseline(Duration::from_millis(42), None);
98        assert!(matches!(decision, PerfDecision::Skip { .. }));
99    }
100
101    #[test]
102    fn at_ceiling_passes() {
103        let baseline = Baseline {
104            p99: Duration::from_millis(10),
105            tolerance: Duration::from_millis(2),
106        };
107        // Exactly at ceiling — Pass (≤).
108        assert!(matches!(
109            evaluate_against_baseline(Duration::from_millis(12), Some(baseline)),
110            PerfDecision::Pass { .. }
111        ));
112    }
113}