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            total_skipped: 0,
66            template_stats: None,
67            response_stats: None,
68            curve_stats: None,
69            scenario_stats: None,
70        };
71        RunReport::from_params(RunReportParams { stats: &stats })
72    }
73
74    #[test]
75    fn evaluate_all_pass() {
76        let report = make_report_with_latency(50, 0.01);
77        let thresholds = vec![
78            Threshold {
79                metric: Metric::LatencyP99,
80                operator: Operator::Lt,
81                value: 500.0,
82            },
83            Threshold {
84                metric: Metric::ErrorRate,
85                operator: Operator::Lte,
86                value: 0.05,
87            },
88        ];
89        let result = evaluate(EvaluateParams {
90            report: &report,
91            thresholds: &thresholds,
92        });
93        assert_eq!(result.total, 2);
94        assert_eq!(result.passed, 2);
95        assert_eq!(result.failed, 0);
96        assert!(result.all_passed());
97    }
98
99    #[test]
100    fn evaluate_all_fail() {
101        let report = make_report_with_latency(500, 0.5);
102        let thresholds = vec![
103            Threshold {
104                metric: Metric::LatencyP99,
105                operator: Operator::Lt,
106                value: 10.0,
107            },
108            Threshold {
109                metric: Metric::ErrorRate,
110                operator: Operator::Lt,
111                value: 0.01,
112            },
113        ];
114        let result = evaluate(EvaluateParams {
115            report: &report,
116            thresholds: &thresholds,
117        });
118        assert_eq!(result.total, 2);
119        assert_eq!(result.passed, 0);
120        assert_eq!(result.failed, 2);
121        assert!(!result.all_passed());
122    }
123
124    #[test]
125    fn evaluate_mixed_results() {
126        let report = make_report_with_latency(50, 0.5);
127        let thresholds = vec![
128            // passes: p99 < 500ms
129            Threshold {
130                metric: Metric::LatencyP99,
131                operator: Operator::Lt,
132                value: 500.0,
133            },
134            // fails: error_rate < 0.01 but actual is 0.5
135            Threshold {
136                metric: Metric::ErrorRate,
137                operator: Operator::Lt,
138                value: 0.01,
139            },
140        ];
141        let result = evaluate(EvaluateParams {
142            report: &report,
143            thresholds: &thresholds,
144        });
145        assert_eq!(result.total, 2);
146        assert_eq!(result.passed, 1);
147        assert_eq!(result.failed, 1);
148        assert!(!result.all_passed());
149    }
150
151    #[test]
152    fn evaluate_empty_thresholds_returns_empty_report() {
153        let report = make_report_with_latency(50, 0.01);
154        let thresholds: Vec<Threshold> = vec![];
155        let result = evaluate(EvaluateParams {
156            report: &report,
157            thresholds: &thresholds,
158        });
159        assert_eq!(result.total, 0);
160        assert_eq!(result.passed, 0);
161        assert_eq!(result.failed, 0);
162        assert!(result.results.is_empty());
163        assert!(result.all_passed());
164    }
165}