service_policy_kit/
bench.rs

1use crate::data::{Cause, Check, CheckResult, Context, Interaction, Sender, Violation};
2use histogram::Histogram;
3use std::time::{Duration, Instant};
4pub struct Bench<'a> {
5    sender: &'a dyn Sender,
6}
7impl<'a> Bench<'a> {
8    pub fn new(sender: &'a dyn Sender) -> Self {
9        Self { sender }
10    }
11}
12
13pub const NAME: &str = "bench";
14impl<'a> Check for Bench<'a> {
15    fn name(&self) -> &str {
16        NAME
17    }
18    fn perform(&self, context: &mut Context, inter: &Interaction) -> CheckResult {
19        let mut violations = vec![];
20        if let Some(benchmark) = &inter.benchmark {
21            let mut h = Histogram::new();
22            let mut total: u128 = 0;
23
24            let prepared = inter.prepare_with(context);
25            if prepared.is_err() {
26                return CheckResult {
27                    kind: NAME.to_string(),
28                    request: inter.request.clone(),
29                    violations: vec![],
30                    response: None,
31                    duration: Some(Duration::new(0, 0)),
32                    error: Some(prepared.err().unwrap().to_string()),
33                };
34            }
35
36            let inter = prepared.ok().unwrap();
37            for _ in 0..benchmark.times {
38                log::debug!("Bench request: {:?}", &inter.request);
39                let now = Instant::now();
40                let res = inter.send(self.sender);
41                match res {
42                    Ok(_) => {}
43                    Err(err) => {
44                        return CheckResult {
45                            kind: NAME.to_string(),
46                            request: inter.request,
47                            violations: vec![],
48                            response: None,
49                            duration: Some(now.elapsed()),
50                            error: Some(err.to_string()),
51                        };
52                    }
53                }
54                let t = now.elapsed();
55                let res = t.as_millis();
56                h.increment(res as u64).unwrap();
57                total += res;
58            }
59
60            let p95 = h.percentile(95.0).unwrap();
61            if p95 > benchmark.p95_ms {
62                violations.push(Violation {
63                    kind: NAME.to_string(),
64                    cause: Cause::Mismatch,
65                    on: None,
66                    subject: "p95".to_string(),
67                    wire: Some(p95.to_string()),
68                    recorded: benchmark.p95_ms.to_string(),
69                });
70            }
71            // verify matching before considering as bench candidate
72            let p99 = h.percentile(99.0).unwrap();
73            if p99 > benchmark.p99_ms {
74                violations.push(Violation {
75                    kind: NAME.to_string(),
76                    cause: Cause::Mismatch,
77                    on: None,
78                    subject: "p99".to_string(),
79                    wire: Some(p99.to_string()),
80                    recorded: benchmark.p99_ms.to_string(),
81                });
82            }
83
84            let avg = h.mean().unwrap();
85            if avg > benchmark.avg_ms {
86                violations.push(Violation {
87                    kind: NAME.to_string(),
88                    cause: Cause::Mismatch,
89                    on: None,
90                    subject: "avg".to_string(),
91                    wire: Some(avg.to_string()),
92                    recorded: benchmark.avg_ms.to_string(),
93                });
94            }
95
96            if total > u128::from(benchmark.time_ms) {
97                violations.push(Violation {
98                    kind: NAME.to_string(),
99                    cause: Cause::Mismatch,
100                    on: None,
101                    subject: "time".to_string(),
102                    wire: Some(total.to_string()),
103                    recorded: benchmark.time_ms.to_string(),
104                });
105            }
106
107            CheckResult {
108                kind: NAME.to_string(),
109                request: inter.request,
110                violations,
111                response: None,
112                duration: None,
113                error: None,
114            }
115        } else {
116            CheckResult::invalid(NAME, inter)
117        }
118    }
119}