Skip to main content

lmn_core/threshold/
mod.rs

1pub mod error;
2pub(crate) mod parse;
3mod types;
4
5pub use error::ThresholdError;
6pub use parse::parse_thresholds;
7pub use types::{EvaluateParams, Metric, Operator, Threshold, ThresholdReport, ThresholdResult};
8
9/// Evaluates all thresholds in `params.thresholds` against `params.report`.
10///
11/// Returns a `ThresholdReport` summarising the total, passed, and failed
12/// counts along with per-threshold results.
13pub fn evaluate(params: EvaluateParams<'_>) -> ThresholdReport {
14    let mut results = Vec::with_capacity(params.thresholds.len());
15
16    for threshold in params.thresholds {
17        let actual = threshold.metric.resolve(params.report);
18        let passed = threshold.operator.evaluate(actual, threshold.value);
19        results.push(ThresholdResult {
20            threshold: threshold.clone(),
21            actual,
22            passed,
23        });
24    }
25
26    let total = results.len();
27    let passed = results.iter().filter(|r| r.passed).count();
28    let failed = total - passed;
29
30    ThresholdReport {
31        total,
32        passed,
33        failed,
34        results,
35    }
36}
37
38// ── Tests ─────────────────────────────────────────────────────────────────────
39
40#[cfg(test)]
41mod tests {
42    use std::time::Duration;
43
44    use crate::execution::{RunMode, RunStats};
45    use crate::histogram::{LatencyHistogram, StatusCodeHistogram};
46    use crate::output::{RunReport, RunReportParams};
47
48    use super::*;
49
50    fn make_report_with_latency(latency_ms: u64, error_rate: f64) -> RunReport {
51        let total = 100u64;
52        let failed = (total as f64 * error_rate).round() as u64;
53        let mut latency = LatencyHistogram::new();
54        latency.record(Duration::from_millis(latency_ms));
55        let mut status_codes = StatusCodeHistogram::new();
56        status_codes.record(Some(200));
57
58        let stats = RunStats {
59            elapsed: Duration::from_secs(10),
60            mode: RunMode::Fixed,
61            latency,
62            status_codes,
63            total_requests: total,
64            total_failures: failed,
65            template_stats: None,
66            response_stats: None,
67            curve_stats: None,
68        };
69        RunReport::from_params(RunReportParams { stats: &stats })
70    }
71
72    #[test]
73    fn evaluate_all_pass() {
74        let report = make_report_with_latency(50, 0.01);
75        let thresholds = vec![
76            Threshold {
77                metric: Metric::LatencyP99,
78                operator: Operator::Lt,
79                value: 500.0,
80            },
81            Threshold {
82                metric: Metric::ErrorRate,
83                operator: Operator::Lte,
84                value: 0.05,
85            },
86        ];
87        let result = evaluate(EvaluateParams {
88            report: &report,
89            thresholds: &thresholds,
90        });
91        assert_eq!(result.total, 2);
92        assert_eq!(result.passed, 2);
93        assert_eq!(result.failed, 0);
94        assert!(result.all_passed());
95    }
96
97    #[test]
98    fn evaluate_all_fail() {
99        let report = make_report_with_latency(500, 0.5);
100        let thresholds = vec![
101            Threshold {
102                metric: Metric::LatencyP99,
103                operator: Operator::Lt,
104                value: 10.0,
105            },
106            Threshold {
107                metric: Metric::ErrorRate,
108                operator: Operator::Lt,
109                value: 0.01,
110            },
111        ];
112        let result = evaluate(EvaluateParams {
113            report: &report,
114            thresholds: &thresholds,
115        });
116        assert_eq!(result.total, 2);
117        assert_eq!(result.passed, 0);
118        assert_eq!(result.failed, 2);
119        assert!(!result.all_passed());
120    }
121
122    #[test]
123    fn evaluate_mixed_results() {
124        let report = make_report_with_latency(50, 0.5);
125        let thresholds = vec![
126            // passes: p99 < 500ms
127            Threshold {
128                metric: Metric::LatencyP99,
129                operator: Operator::Lt,
130                value: 500.0,
131            },
132            // fails: error_rate < 0.01 but actual is 0.5
133            Threshold {
134                metric: Metric::ErrorRate,
135                operator: Operator::Lt,
136                value: 0.01,
137            },
138        ];
139        let result = evaluate(EvaluateParams {
140            report: &report,
141            thresholds: &thresholds,
142        });
143        assert_eq!(result.total, 2);
144        assert_eq!(result.passed, 1);
145        assert_eq!(result.failed, 1);
146        assert!(!result.all_passed());
147    }
148
149    #[test]
150    fn evaluate_empty_thresholds_returns_empty_report() {
151        let report = make_report_with_latency(50, 0.01);
152        let thresholds: Vec<Threshold> = vec![];
153        let result = evaluate(EvaluateParams {
154            report: &report,
155            thresholds: &thresholds,
156        });
157        assert_eq!(result.total, 0);
158        assert_eq!(result.passed, 0);
159        assert_eq!(result.failed, 0);
160        assert!(result.results.is_empty());
161        assert!(result.all_passed());
162    }
163}