use std::cmp::Ordering; use std::collections::BTreeSet; pub const WINDOW: f64 = 60.0; #[derive(Copy, Clone, PartialEq)] struct RequestTime { time: f64, id: usize, } impl Eq for RequestTime {} impl Ord for RequestTime { fn cmp(&self, other: &Self) -> Ordering { self.time .total_cmp(&other.time) .then(self.id.cmp(&other.id)) } } impl PartialOrd for RequestTime { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } /// Nearest-rank p99 of response times for requests arriving in [t, t + WINDOW). /// Emit one point per distinct arrival time t <= time_limit. pub fn windowed_p99(mut requests: Vec<(f64, f64)>, time_limit: f64) -> Vec<(f64, f64)> { requests.sort_by(|a, b| a.0.total_cmp(&b.0)); let mut result = Vec::new(); let mut items = BTreeSet::new(); let mut end = 0; let mut start = 0; while start < requests.len() && requests[start].0 <= time_limit { let time = requests[start].0; while end < requests.len() && requests[end].0 < time + WINDOW { items.insert(RequestTime { time: requests[end].1, id: end, }); end += 1; } // ceil(0.99 * n), computed exactly, without floating-point rounding. let rank = items.len() - items.len() / 100; let p99 = items.iter().rev().nth(items.len() - rank).unwrap().time; result.push((time, p99)); // Equal response times remain distinct observations thanks to id. // Remove the entire arrival-time group before advancing the window. while start < requests.len() && requests[start].0 == time { items.remove(&RequestTime { time: requests[start].1, id: start, }); start += 1; } } result } #[cfg(test)] mod tests { use super::*; #[test] fn expires_old_requests_and_excludes_right_boundary() { let requests = vec![ (0.0, 100.0), (1.0, 10.0), (61.0, 1.0), (62.0, 1.0), (123.0, 3.0), ]; assert_eq!( windowed_p99(requests, 123.0), vec![ (0.0, 100.0), (1.0, 10.0), (61.0, 1.0), (62.0, 1.0), (123.0, 3.0) ] ); } #[test] fn uses_nearest_rank_and_counts_equal_response_times() { let mut requests = vec![(0.0, 1.0); 99]; requests.push((0.0, 100.0)); assert_eq!(windowed_p99(requests, 0.0), vec![(0.0, 1.0)]); let requests = (1..=100).map(|n| (0.0, n as f64)).collect(); assert_eq!(windowed_p99(requests, 0.0), vec![(0.0, 99.0)]); } #[test] fn sorts_arrivals_and_limits_reported_times() { assert_eq!( windowed_p99(vec![(120.0, 8.0), (60.0, 2.0), (0.0, 4.0)], 60.0), vec![(0.0, 4.0), (60.0, 2.0)] ); assert!(windowed_p99(Vec::new(), 60.0).is_empty()); } #[test] fn matches_independent_sort_per_window_reference() { let requests: Vec<_> = (0..400) .map(|i| (((i * 37) % 241) as f64, ((i * 17) % 31 + 1) as f64)) .collect(); let mut times: Vec<_> = requests .iter() .map(|r| r.0) .filter(|t| *t <= 180.0) .collect(); times.sort_by(f64::total_cmp); times.dedup(); let expected: Vec<_> = times .into_iter() .map(|t| { let mut values: Vec<_> = requests .iter() .filter(|r| r.0 >= t && r.0 < t + WINDOW) .map(|r| r.1) .collect(); values.sort_by(f64::total_cmp); let rank = (0.99 * values.len() as f64).ceil() as usize; (t, values[rank - 1]) }) .collect(); assert_eq!(windowed_p99(requests, 180.0), expected); } }