metrale_bench/benchmarks/scheduler_equivalence/
unmeasured.rs1use std::collections::BTreeMap;
14use std::fmt;
15use std::time::Duration;
16
17use super::compare::{Leg, Pass};
18use super::host::Lane;
19use crate::http::{FailureKind, RequestFailure};
20
21pub const CLASSES: [&str; 10] = [
24 "timeout",
25 "http_4xx",
26 "http_5xx",
27 "http_other",
28 "transport",
29 "malformed",
30 "truncated",
31 "server_error",
32 "missing",
33 "other",
34];
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum Cause {
38 Failed {
39 failure: RequestFailure,
40 elapsed: Duration,
41 },
42 Missing,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Unmeasured {
48 pub pass: Pass,
49 pub lane: Lane,
50 pub concurrency: usize,
51 pub sample_id: String,
52 pub cause: Cause,
53}
54
55impl Unmeasured {
56 pub fn class(&self) -> &'static str {
58 let Cause::Failed { failure, .. } = &self.cause else {
59 return "missing";
60 };
61 match failure.kind {
62 FailureKind::Timeout => "timeout",
63 FailureKind::Status(c) if (400..500).contains(&c) => "http_4xx",
64 FailureKind::Status(c) if (500..600).contains(&c) => "http_5xx",
65 FailureKind::Status(_) => "http_other",
66 FailureKind::Transport => "transport",
67 FailureKind::Malformed => "malformed",
68 FailureKind::Truncated => "truncated",
69 FailureKind::ServerError => "server_error",
70 FailureKind::Other => "other",
71 }
72 }
73}
74
75impl fmt::Display for Unmeasured {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(
80 f,
81 "unmeasured: leg={} lane={} C={} sample={:?} cause={}",
82 self.pass.label(),
83 self.lane.label(),
84 self.concurrency,
85 self.sample_id,
86 self.class()
87 )?;
88 match &self.cause {
89 Cause::Missing => write!(f, " error=\"no reply in this leg\""),
90 Cause::Failed { failure, elapsed } => {
91 let status = match failure.kind {
92 FailureKind::Status(c) => c.to_string(),
93 _ => "-".to_string(),
94 };
95 write!(
96 f,
97 " status={status} finish_reason={} elapsed={:.3}s error={:?} body={:?}",
98 failure.finish_reason.as_deref().unwrap_or("-"),
99 elapsed.as_secs_f64(),
100 failure.message,
101 failure.body
102 )
103 }
104 }
105 }
106}
107
108pub fn collect(reference: &Leg, candidate: Option<&Leg>, control: Option<&Leg>) -> Vec<Unmeasured> {
112 let entry = |pass, sample_id: &str, cause| Unmeasured {
113 pass,
114 lane: reference.lane,
115 concurrency: reference.concurrency,
116 sample_id: sample_id.to_string(),
117 cause,
118 };
119 let mut out: Vec<Unmeasured> = reference
120 .replies
121 .iter()
122 .filter_map(|r| {
123 let failure = r.outcome.as_ref().err()?;
124 Some(entry(Pass::Sync, &r.sample_id, failed(failure, r.elapsed)))
125 })
126 .collect();
127 if candidate.is_none() {
128 out.extend(
129 reference
130 .replies
131 .iter()
132 .map(|r| entry(Pass::Async, &r.sample_id, Cause::Missing)),
133 );
134 }
135 for other in [candidate, control].into_iter().flatten() {
136 for r in &reference.replies {
137 let cause = match other.find(&r.sample_id) {
138 None => Cause::Missing,
139 Some(o) => match &o.outcome {
140 Ok(_) => continue,
141 Err(failure) => failed(failure, o.elapsed),
142 },
143 };
144 out.push(entry(other.pass, &r.sample_id, cause));
145 }
146 }
147 out
148}
149
150fn failed(failure: &RequestFailure, elapsed: Duration) -> Cause {
151 Cause::Failed {
152 failure: failure.clone(),
153 elapsed,
154 }
155}
156
157pub fn breakdown(entries: &[Unmeasured]) -> String {
160 let mut tally: BTreeMap<(Pass, &str), usize> = BTreeMap::new();
161 for e in entries {
162 *tally.entry((e.pass, e.class())).or_default() += 1;
163 }
164 if tally.is_empty() {
165 return "none".to_string();
166 }
167 tally
168 .iter()
169 .map(|((pass, class), n)| format!("{} {class} {n}", pass.label()))
170 .collect::<Vec<_>>()
171 .join(", ")
172}
173
174pub fn metrics<'a>(entries: impl Iterator<Item = &'a Unmeasured>) -> BTreeMap<String, f64> {
177 let key = |class: &str| format!("unmeasured_cause_{class}");
178 let mut m: BTreeMap<String, f64> = CLASSES.iter().map(|c| (key(c), 0.0)).collect();
179 for e in entries {
180 *m.entry(key(e.class())).or_default() += 1.0;
181 }
182 m
183}