use std::time::Duration;
use crate::http::RequestResult;
pub struct Distribution {
sorted: Vec<f64>,
}
impl Distribution {
pub fn from_unsorted(mut values: Vec<f64>) -> Self {
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Self { sorted: values }
}
pub fn from_sorted(values: Vec<f64>) -> Self {
Self { sorted: values }
}
pub fn quantile(&self, p: f64) -> f64 {
if self.sorted.is_empty() {
return 0.0;
}
let n = self.sorted.len();
let idx = ((n as f64 * p).floor() as usize).min(n - 1);
self.sorted[idx]
}
pub fn min(&self) -> f64 {
self.sorted.first().copied().unwrap_or(0.0)
}
pub fn max(&self) -> f64 {
self.sorted.last().copied().unwrap_or(0.0)
}
pub fn mean(&self) -> f64 {
if self.sorted.is_empty() {
return 0.0;
}
self.sorted.iter().sum::<f64>() / self.sorted.len() as f64
}
pub fn is_empty(&self) -> bool {
self.sorted.is_empty()
}
pub fn len(&self) -> usize {
self.sorted.len()
}
pub fn value_at(&self, idx: usize) -> f64 {
self.sorted[idx]
}
}
pub struct LatencyDistribution(Distribution);
impl LatencyDistribution {
pub fn from_results(results: &[RequestResult]) -> Self {
let ms_values: Vec<f64> = results
.iter()
.map(|r| r.duration.as_secs_f64() * 1000.0)
.collect();
Self(Distribution::from_unsorted(ms_values))
}
pub fn from_durations(durations: &[Duration]) -> Self {
let ms_values: Vec<f64> = durations.iter().map(|d| d.as_secs_f64() * 1000.0).collect();
Self(Distribution::from_unsorted(ms_values))
}
pub fn quantile_ms(&self, p: f64) -> f64 {
self.0.quantile(p)
}
pub fn min_ms(&self) -> f64 {
self.0.min()
}
pub fn max_ms(&self) -> f64 {
self.0.max()
}
pub fn mean_ms(&self) -> f64 {
self.0.mean()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distribution_quantile_known_values() {
let values: Vec<f64> = (1..=100).map(|i| i as f64).collect();
let dist = Distribution::from_sorted(values);
assert_eq!(dist.quantile(0.0), 1.0);
assert_eq!(dist.quantile(0.5), 51.0);
assert_eq!(dist.quantile(0.99), 100.0);
assert_eq!(dist.quantile(1.0), 100.0);
}
#[test]
fn distribution_empty_returns_zero() {
let dist = Distribution::from_sorted(vec![]);
assert_eq!(dist.quantile(0.5), 0.0);
assert_eq!(dist.min(), 0.0);
assert_eq!(dist.max(), 0.0);
assert_eq!(dist.mean(), 0.0);
}
#[test]
fn distribution_single_element() {
let dist = Distribution::from_sorted(vec![42.0]);
assert_eq!(dist.quantile(0.0), 42.0);
assert_eq!(dist.quantile(0.5), 42.0);
assert_eq!(dist.quantile(1.0), 42.0);
assert_eq!(dist.min(), 42.0);
assert_eq!(dist.max(), 42.0);
assert_eq!(dist.mean(), 42.0);
}
#[test]
fn distribution_quantile_boundary_p100() {
let values: Vec<f64> = (1..=10).map(|i| i as f64).collect();
let dist = Distribution::from_sorted(values);
assert_eq!(dist.quantile(1.0), 10.0);
}
#[test]
fn distribution_avg_is_arithmetic_mean() {
let dist = Distribution::from_sorted(vec![1.0, 2.0, 3.0]);
assert_eq!(dist.mean(), 2.0);
}
#[test]
fn distribution_is_sorted_on_construction() {
let dist = Distribution::from_unsorted(vec![5.0, 1.0, 3.0, 2.0, 4.0]);
assert_eq!(dist.min(), 1.0);
assert_eq!(dist.max(), 5.0);
assert_eq!(dist.quantile(0.5), 3.0);
}
#[test]
fn distribution_len_and_is_empty() {
let empty = Distribution::from_sorted(vec![]);
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
let dist = Distribution::from_sorted(vec![1.0, 2.0, 3.0]);
assert!(!dist.is_empty());
assert_eq!(dist.len(), 3);
}
#[test]
fn distribution_value_at_returns_correct_element() {
let dist = Distribution::from_sorted(vec![10.0, 20.0, 30.0, 40.0, 50.0]);
assert_eq!(dist.value_at(0), 10.0);
assert_eq!(dist.value_at(2), 30.0);
assert_eq!(dist.value_at(4), 50.0);
}
#[test]
fn latency_distribution_converts_duration_to_ms() {
let durations = vec![Duration::from_millis(42)];
let dist = LatencyDistribution::from_durations(&durations);
assert_eq!(dist.quantile_ms(0.5), 42.0);
assert_eq!(dist.min_ms(), 42.0);
assert_eq!(dist.max_ms(), 42.0);
assert_eq!(dist.mean_ms(), 42.0);
}
#[test]
fn latency_distribution_empty() {
let dist = LatencyDistribution::from_durations(&[]);
assert_eq!(dist.quantile_ms(0.5), 0.0);
assert_eq!(dist.min_ms(), 0.0);
assert_eq!(dist.max_ms(), 0.0);
assert_eq!(dist.mean_ms(), 0.0);
assert!(dist.is_empty());
}
#[test]
fn latency_distribution_from_results() {
let results: Vec<RequestResult> = vec![
RequestResult::new(Duration::from_millis(10), true, Some(200), None),
RequestResult::new(Duration::from_millis(20), true, Some(200), None),
RequestResult::new(Duration::from_millis(30), true, Some(200), None),
];
let dist = LatencyDistribution::from_results(&results);
assert_eq!(dist.min_ms(), 10.0);
assert_eq!(dist.max_ms(), 30.0);
assert!((dist.mean_ms() - 20.0).abs() < f64::EPSILON);
}
}