Skip to main content

rustev_eval/
optimization.rs

1//! Offline optimization measurements for spec 017.
2//!
3//! The host supplies named cold and warm cohorts plus measurements that a run
4//! record cannot contain. Absent measurements stay unknown. This module does
5//! no I/O and never consults a live cache or backend.
6
7use rustev_contract::eval_report::{Metric, MetricValue};
8use rustev_contract::optimization::OptimizationOutcome;
9use rustev_contract::run::{RequestResult, RunRecord};
10
11#[derive(Debug, Default, Clone, Copy)]
12pub struct ExternalMeasurements<'a> {
13    pub cache_bytes: Option<u64>,
14    pub evictions: Option<u64>,
15    pub invalidation_lag_ms: Option<&'a [u64]>,
16    pub erasure_completion_ms: Option<&'a [u64]>,
17    /// `(equivalent, compared)` over a named differential cohort.
18    pub judgment_equivalence: Option<(u64, u64)>,
19}
20
21#[derive(Debug, Clone, Copy)]
22pub struct OptimizationEvaluation<'a> {
23    pub cold: &'a [RunRecord],
24    pub warm: &'a [RunRecord],
25    pub cold_window_ms: Option<u64>,
26    pub warm_window_ms: Option<u64>,
27    pub external: ExternalMeasurements<'a>,
28}
29
30fn unknown(name: impl Into<String>, reason: &str) -> Metric {
31    Metric {
32        name: name.into(),
33        value: MetricValue::Unknown {
34            reason: reason.into(),
35        },
36    }
37}
38
39fn measured(name: impl Into<String>, value: f64, n: u64) -> Metric {
40    Metric {
41        name: name.into(),
42        value: MetricValue::Measured { value, n },
43    }
44}
45
46fn quantile(values: &[u64], percentile: u64) -> Option<u64> {
47    if values.is_empty() {
48        return None;
49    }
50    let mut values = values.to_vec();
51    values.sort_unstable();
52    let rank = (percentile
53        .saturating_mul(values.len() as u64)
54        .saturating_add(99)
55        / 100)
56        .max(1) as usize;
57    values.get(rank - 1).copied()
58}
59
60fn percentile_name(percentile: u64) -> String {
61    if percentile.is_multiple_of(10) {
62        format!("p0.{}", percentile / 10)
63    } else {
64        format!("p0.{percentile}")
65    }
66}
67
68fn phase_metrics(name: &str, runs: &[RunRecord], window_ms: Option<u64>) -> Vec<Metric> {
69    let service = runs
70        .iter()
71        .map(|run| run.timing.elapsed_ms.checked_sub(run.timing.queued_ms))
72        .collect::<Option<Vec<_>>>();
73    let requests = runs
74        .iter()
75        .flat_map(|run| run.requests.iter())
76        .collect::<Vec<_>>();
77    let denominator = requests.len() as u64;
78    let rate = |outcome| {
79        requests
80            .iter()
81            .filter(|request| {
82                request
83                    .optimization
84                    .as_ref()
85                    .is_some_and(|observation| observation.outcome == outcome)
86            })
87            .count() as f64
88            / denominator.max(1) as f64
89    };
90    let mut metrics = Vec::new();
91    for percentile in [50, 95, 99] {
92        let metric_name = format!("{name}/latency_ms/{}", percentile_name(percentile));
93        match service
94            .as_deref()
95            .and_then(|values| quantile(values, percentile))
96        {
97            Some(value) => metrics.push(measured(metric_name, value as f64, runs.len() as u64)),
98            None => metrics.push(unknown(metric_name, "latency was not measured")),
99        }
100    }
101    match window_ms.filter(|window| *window > 0) {
102        _ if runs.is_empty() => metrics.push(unknown(
103            format!("{name}/throughput_per_second"),
104            "zero runs",
105        )),
106        Some(window) => metrics.push(measured(
107            format!("{name}/throughput_per_second"),
108            runs.len() as f64 * 1_000.0 / window as f64,
109            runs.len() as u64,
110        )),
111        None => metrics.push(unknown(
112            format!("{name}/throughput_per_second"),
113            "measurement window is missing",
114        )),
115    }
116    if denominator == 0 {
117        metrics.push(unknown(format!("{name}/hit_rate"), "zero requests"));
118        metrics.push(unknown(format!("{name}/batch_rate"), "zero requests"));
119        metrics.push(unknown(format!("{name}/failures"), "zero requests"));
120    } else {
121        metrics.push(measured(
122            format!("{name}/hit_rate"),
123            rate(OptimizationOutcome::Hit),
124            denominator,
125        ));
126        metrics.push(measured(
127            format!("{name}/batch_rate"),
128            rate(OptimizationOutcome::Batched),
129            denominator,
130        ));
131        metrics.push(measured(
132            format!("{name}/failures"),
133            requests
134                .iter()
135                .filter(|request| matches!(request.result, RequestResult::Failed(_)))
136                .count() as f64,
137            denominator,
138        ));
139    }
140    // A zero over no runs would read as a measured zero.
141    if runs.is_empty() {
142        metrics.push(unknown(
143            format!("{name}/unknown_liability_units"),
144            "zero runs",
145        ));
146    } else {
147        metrics.push(measured(
148            format!("{name}/unknown_liability_units"),
149            runs.iter().map(|run| run.cost.liability).sum::<u64>() as f64,
150            runs.len() as u64,
151        ));
152    }
153    metrics
154}
155
156fn external_count(name: &str, value: Option<u64>) -> Metric {
157    match value {
158        Some(value) => measured(name, value as f64, 1),
159        None => unknown(name, "measurement was not supplied"),
160    }
161}
162
163fn external_quantile(name: &str, values: Option<&[u64]>, percentile: u64) -> Metric {
164    match values.and_then(|values| quantile(values, percentile)) {
165        Some(value) => measured(name, value as f64, values.unwrap_or_default().len() as u64),
166        None => unknown(name, "measurement was not supplied"),
167    }
168}
169
170pub fn optimization_metrics(input: &OptimizationEvaluation<'_>) -> Vec<Metric> {
171    let mut metrics = phase_metrics("cold", input.cold, input.cold_window_ms);
172    metrics.extend(phase_metrics("warm", input.warm, input.warm_window_ms));
173    metrics.push(external_count("cache_bytes", input.external.cache_bytes));
174    metrics.push(external_count("evictions", input.external.evictions));
175    for percentile in [50, 95, 99] {
176        metrics.push(external_quantile(
177            &format!("invalidation_lag_ms/{}", percentile_name(percentile)),
178            input.external.invalidation_lag_ms,
179            percentile,
180        ));
181        metrics.push(external_quantile(
182            &format!("erasure_completion_ms/{}", percentile_name(percentile)),
183            input.external.erasure_completion_ms,
184            percentile,
185        ));
186    }
187    metrics.push(match input.external.judgment_equivalence {
188        Some((equivalent, compared)) if compared > 0 && equivalent <= compared => measured(
189            "judgment_equivalence_rate",
190            equivalent as f64 / compared as f64,
191            compared,
192        ),
193        Some(_) => unknown("judgment_equivalence_rate", "invalid comparison counts"),
194        None => unknown("judgment_equivalence_rate", "comparison was not supplied"),
195    });
196    metrics
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn missing_measurements_remain_unknown() {
205        let metrics = optimization_metrics(&OptimizationEvaluation {
206            cold: &[],
207            warm: &[],
208            cold_window_ms: None,
209            warm_window_ms: None,
210            external: ExternalMeasurements::default(),
211        });
212        for name in [
213            "cold/latency_ms/p0.5",
214            "warm/throughput_per_second",
215            "cache_bytes",
216            "evictions",
217            "invalidation_lag_ms/p0.95",
218            "erasure_completion_ms/p0.99",
219            "judgment_equivalence_rate",
220            "cold/unknown_liability_units",
221            "warm/unknown_liability_units",
222            "cold/hit_rate",
223            "warm/batch_rate",
224        ] {
225            assert!(metrics.iter().any(|metric| {
226                metric.name == name && matches!(metric.value, MetricValue::Unknown { .. })
227            }));
228        }
229    }
230
231    #[test]
232    fn an_empty_cohort_with_a_window_is_still_unknown() {
233        let metrics = optimization_metrics(&OptimizationEvaluation {
234            cold: &[],
235            warm: &[],
236            cold_window_ms: Some(1_000),
237            warm_window_ms: Some(1_000),
238            external: ExternalMeasurements::default(),
239        });
240        for metric in &metrics {
241            assert!(
242                !matches!(metric.value, MetricValue::Measured { n: 0, .. }),
243                "{} is a measured value over n=0",
244                metric.name
245            );
246        }
247    }
248
249    #[test]
250    fn quantile_is_nearest_rank() {
251        assert_eq!(quantile(&[], 50), None);
252        assert_eq!(quantile(&[7], 50), Some(7));
253        assert_eq!(quantile(&[7], 99), Some(7));
254        let hundred = (1..=100).rev().collect::<Vec<u64>>();
255        assert_eq!(quantile(&hundred, 50), Some(50));
256        assert_eq!(quantile(&hundred, 95), Some(95));
257        // p99 of 100 values is exactly rank 99, not rank 100.
258        assert_eq!(quantile(&hundred, 99), Some(99));
259        // One past an exact boundary rounds the rank up.
260        let hundred_one = (1..=101).collect::<Vec<u64>>();
261        assert_eq!(quantile(&hundred_one, 99), Some(100));
262    }
263}