//! Ranking a set of samples, the way `dr-gpu`'s frame budget ranks them. //! //! Copied in spirit rather than shared, because the two live in different //! dependency worlds — `core/dr-gpu/examples/frame_budget.rs` is an example //! inside a crate this one deliberately does not depend on (see `Cargo.toml`). //! The arithmetic is identical on purpose: two percentile definitions in one //! repository is how two benchmarks come to disagree about the same machine. //! //! # Nearest-rank, not an interpolating definition //! //! The samples *are* the population. There is no distribution being estimated //! here, only a set of catalog opens or thumbnail encodes that either happened //! inside the target or did not. At 100 samples the 99th percentile is the //! second-worst, which is the honest reading of "one bad one in a hundred is //! one too many" without letting a single scheduler hiccup on an unrelated //! process decide the verdict. use std::time::Duration; /// Nearest-rank percentiles over a set of samples, in the caller's unit. #[derive(Debug, Clone, Copy)] pub struct Percentiles { pub p50: f64, pub p99: f64, pub max: f64, } impl Percentiles { /// Rank `samples`. Panics on an empty set, which is a harness bug rather /// than a measurement: a row with nothing in it must not print a zero that /// reads like a very fast result. pub fn of(mut samples: Vec) -> Self { assert!( !samples.is_empty(), "percentiles of an empty sample set — the measurement produced nothing" ); samples.sort_by(f64::total_cmp); let rank = |p: f64| { let n = samples.len(); let i = ((p * n as f64).ceil() as usize).clamp(1, n) - 1; samples[i] }; Percentiles { p50: rank(0.50), p99: rank(0.99), max: samples[samples.len() - 1], } } } /// A duration in milliseconds, which is the unit every timing here is stated /// in. One spelling, so no row is accidentally in seconds. pub fn ms(d: Duration) -> f64 { d.as_secs_f64() * 1e3 } /// A deterministic generator, so a fixture is reproducible from its seed. /// /// SplitMix64. Chosen because it is eight lines, has no dependency, and passes /// the only test that matters here — that the same seed produces the same /// catalog on the reference desktop and on the CI runner, so a number measured /// in one place describes the same workload as a number measured in the other. /// Nothing cryptographic depends on it. pub struct Rng(u64); impl Rng { pub fn new(seed: u64) -> Self { Rng(seed) } pub fn next_u64(&mut self) -> u64 { self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); let mut z = self.0; z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); z ^ (z >> 31) } /// A value in `0..n`. Modulo-biased, which does not matter for a fixture: /// nothing here is a statistical test, only a spread of plausible values. pub fn below(&mut self, n: u64) -> u64 { self.next_u64() % n.max(1) } } #[cfg(test)] mod tests { use super::*; #[test] fn the_ninety_ninth_of_a_hundred_is_the_second_worst() { // The property the whole suite's verdict rests on. Off by one here and // every threshold is judged against the worst sample instead. let samples: Vec = (1..=100).map(|n| n as f64).collect(); let p = Percentiles::of(samples); assert_eq!(p.p99, 99.0); assert_eq!(p.max, 100.0); assert_eq!(p.p50, 50.0); } #[test] fn a_single_sample_ranks_as_itself() { // A row measured once — a cold catalog open — must not divide by zero // or index off the end. let p = Percentiles::of(vec![7.5]); assert_eq!((p.p50, p.p99, p.max), (7.5, 7.5, 7.5)); } #[test] fn the_same_seed_gives_the_same_sequence() { // Reproducibility from a seed is what makes a committed baseline mean // anything: two runs must describe the same catalog. let mut a = Rng::new(20_260_829); let mut b = Rng::new(20_260_829); let mut c = Rng::new(20_260_830); let first: Vec = (0..8).map(|_| a.next_u64()).collect(); let same: Vec = (0..8).map(|_| b.next_u64()).collect(); let other: Vec = (0..8).map(|_| c.next_u64()).collect(); assert_eq!(first, same); assert_ne!(first, other); } }