use std::collections::BTreeMap;
use crate::{Bucket, Error};
#[derive(Clone, Copy, Debug)]
pub struct Quantile(f64);
impl Quantile {
pub fn new(value: f64) -> Result<Self, Error> {
if (0.0..=1.0).contains(&value) {
Ok(Self(value))
} else {
Err(Error::InvalidQuantile)
}
}
pub fn as_f64(self) -> f64 {
self.0
}
}
impl TryFrom<f64> for Quantile {
type Error = Error;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl PartialEq for Quantile {
fn eq(&self, other: &Self) -> bool {
self.0.to_bits() == other.0.to_bits()
}
}
impl Eq for Quantile {}
impl PartialOrd for Quantile {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Quantile {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.partial_cmp(&other.0).unwrap()
}
}
impl std::fmt::Display for Quantile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, PartialEq)]
pub struct QuantilesResult {
entries: BTreeMap<Quantile, Bucket>,
total_count: u128,
min: Bucket,
max: Bucket,
}
impl QuantilesResult {
pub(crate) fn new(
entries: BTreeMap<Quantile, Bucket>,
total_count: u128,
min: Bucket,
max: Bucket,
) -> Self {
Self {
entries,
total_count,
min,
max,
}
}
pub fn entries(&self) -> &BTreeMap<Quantile, Bucket> {
&self.entries
}
pub fn get(&self, quantile: &Quantile) -> Option<&Bucket> {
self.entries.get(quantile)
}
pub fn total_count(&self) -> u128 {
self.total_count
}
pub fn min(&self) -> &Bucket {
&self.min
}
pub fn max(&self) -> &Bucket {
&self.max
}
}
pub trait SampleQuantiles {
fn quantiles(&self, quantiles: &[f64]) -> Result<Option<QuantilesResult>, Error>;
fn quantile(&self, quantile: f64) -> Result<Option<QuantilesResult>, Error> {
self.quantiles(&[quantile])
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Histogram, SparseHistogram};
#[test]
fn quantile_validation() {
assert!(Quantile::new(0.0).is_ok());
assert!(Quantile::new(0.5).is_ok());
assert!(Quantile::new(1.0).is_ok());
assert!(Quantile::new(-0.1).is_err());
assert!(Quantile::new(1.1).is_err());
assert!(Quantile::new(f64::NAN).is_err());
assert!(Quantile::new(f64::INFINITY).is_err());
}
#[test]
fn quantile_ordering() {
let q1 = Quantile::new(0.5).unwrap();
let q2 = Quantile::new(0.99).unwrap();
assert!(q1 < q2);
assert_eq!(q1, Quantile::new(0.5).unwrap());
}
#[test]
fn empty_histogram_returns_none() {
let h = Histogram::new(7, 64).unwrap();
assert_eq!(h.quantiles(&[0.5, 0.99]).unwrap(), None);
assert_eq!(h.quantile(0.5).unwrap(), None);
let s = SparseHistogram::from(&h);
assert_eq!(s.quantiles(&[0.5, 0.99]).unwrap(), None);
}
#[test]
fn invalid_quantile_returns_error() {
let h = Histogram::new(7, 64).unwrap();
assert_eq!(h.quantiles(&[1.5]), Err(Error::InvalidQuantile));
assert_eq!(h.quantiles(&[-0.1]), Err(Error::InvalidQuantile));
}
#[test]
fn basic_quantiles() {
let mut h = Histogram::new(7, 64).unwrap();
for v in 0..=100 {
h.increment(v).unwrap();
}
let result = h.quantiles(&[0.5, 0.9, 0.99]).unwrap().unwrap();
assert_eq!(result.entries().len(), 3);
assert_eq!(result.total_count(), 101);
assert_eq!(result.min().start(), 0);
assert_eq!(result.max().end(), 100);
let q50 = Quantile::new(0.5).unwrap();
assert_eq!(result.get(&q50).unwrap().end(), 50);
let q99 = Quantile::new(0.99).unwrap();
assert_eq!(result.get(&q99).unwrap().end(), 99);
}
#[test]
fn single_sample() {
let mut h = Histogram::new(7, 64).unwrap();
h.increment(42).unwrap();
let result = h.quantile(0.5).unwrap().unwrap();
assert_eq!(result.total_count(), 1);
assert_eq!(result.min().end(), 42);
assert_eq!(result.max().end(), 42);
}
#[test]
fn histogram_and_sparse_agree() {
let mut h = Histogram::new(4, 10).unwrap();
for v in 1..1024 {
h.increment(v).unwrap();
}
let s = SparseHistogram::from(&h);
let quantiles = &[0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99, 0.999];
let hr = h.quantiles(quantiles).unwrap().unwrap();
let sr = s.quantiles(quantiles).unwrap().unwrap();
assert_eq!(hr.total_count(), sr.total_count());
assert_eq!(hr.min().range(), sr.min().range());
assert_eq!(hr.max().range(), sr.max().range());
for (hq, sq) in hr.entries().iter().zip(sr.entries().iter()) {
assert_eq!(hq.0, sq.0);
assert_eq!(hq.1.range(), sq.1.range());
}
}
#[test]
fn duplicate_quantiles_deduped() {
let mut h = Histogram::new(7, 64).unwrap();
h.increment(50).unwrap();
let result = h.quantiles(&[0.5, 0.5, 0.99, 0.99]).unwrap().unwrap();
assert_eq!(result.entries().len(), 2);
}
}