use std::collections::BTreeMap;
use crate::quantile::{Quantile, QuantilesResult, SampleQuantiles};
use crate::{
Bucket, Config, Count, Error, Histogram, Histogram32, SparseHistogram, SparseHistogram32,
};
macro_rules! define_cumulative_histogram {
($name:ident, $ref_name:ident, $iter:ident, $qr_iter:ident, $hist:ident, $sparse:ident, $count:ty) => {
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct $name {
config: Config,
index: Vec<u32>,
count: Vec<$count>,
}
impl $name {
pub fn from_parts(
config: Config,
index: Vec<u32>,
count: Vec<$count>,
) -> Result<Self, Error> {
$ref_name::validate(&config, &index, &count)?;
Ok(Self {
config,
index,
count,
})
}
pub fn into_parts(self) -> (Config, Vec<u32>, Vec<$count>) {
(self.config, self.index, self.count)
}
pub fn config(&self) -> Config {
self.config
}
pub fn index(&self) -> &[u32] {
&self.index
}
pub fn count(&self) -> &[$count] {
&self.count
}
pub fn total_count(&self) -> u64 {
self.as_ref().total_count()
}
pub fn len(&self) -> usize {
self.index.len()
}
pub fn is_empty(&self) -> bool {
self.index.is_empty()
}
pub fn bucket_quantile_range(&self, bucket_idx: usize) -> Option<(f64, f64)> {
self.as_ref().bucket_quantile_range(bucket_idx)
}
pub fn iter_with_quantiles(&self) -> $qr_iter<'_> {
self.as_ref().iter_with_quantiles()
}
pub fn iter(&self) -> $iter<'_> {
self.as_ref().iter()
}
pub fn quantiles(&self, quantiles: &[f64]) -> Result<Option<QuantilesResult>, Error> {
self.as_ref().quantiles(quantiles)
}
pub fn quantile(&self, quantile: f64) -> Result<Option<QuantilesResult>, Error> {
self.as_ref().quantile(quantile)
}
pub fn as_ref(&self) -> $ref_name<'_> {
$ref_name::from_parts_unchecked(self.config, &self.index, &self.count)
}
}
impl SampleQuantiles for $name {
fn quantiles(&self, quantiles: &[f64]) -> Result<Option<QuantilesResult>, Error> {
self.as_ref().quantiles(quantiles)
}
}
impl<'a> IntoIterator for &'a $name {
type Item = Bucket;
type IntoIter = $iter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct $iter<'a> {
position: usize,
config: Config,
index: &'a [u32],
count: &'a [$count],
}
impl Iterator for $iter<'_> {
type Item = Bucket;
fn next(&mut self) -> Option<Bucket> {
if self.position >= self.index.len() {
return None;
}
let i = self.position;
let individual_count = if i == 0 {
self.count[0].as_u128() as u64
} else {
self.count[i].wrapping_sub(self.count[i - 1]).as_u128() as u64
};
let bucket = Bucket {
count: individual_count,
range: self.config.index_to_range(self.index[i] as usize),
};
self.position += 1;
Some(bucket)
}
}
impl ExactSizeIterator for $iter<'_> {
fn len(&self) -> usize {
self.index.len() - self.position
}
}
impl std::iter::FusedIterator for $iter<'_> {}
pub struct $qr_iter<'a> {
position: usize,
config: Config,
index: &'a [u32],
count: &'a [$count],
total: f64,
}
impl Iterator for $qr_iter<'_> {
type Item = (Bucket, f64, f64);
fn next(&mut self) -> Option<Self::Item> {
if self.position >= self.index.len() {
return None;
}
let i = self.position;
let lower = if i == 0 {
0.0
} else {
self.count[i - 1].as_u128() as f64 / self.total
};
let upper = self.count[i].as_u128() as f64 / self.total;
let individual_count = if i == 0 {
self.count[0].as_u128() as u64
} else {
self.count[i].wrapping_sub(self.count[i - 1]).as_u128() as u64
};
let bucket = Bucket {
count: individual_count,
range: self.config.index_to_range(self.index[i] as usize),
};
self.position += 1;
Some((bucket, lower, upper))
}
}
impl ExactSizeIterator for $qr_iter<'_> {
fn len(&self) -> usize {
self.index.len() - self.position
}
}
impl std::iter::FusedIterator for $qr_iter<'_> {}
impl From<&$hist> for $name {
fn from(histogram: &$hist) -> Self {
let mut index = Vec::new();
let mut count = Vec::new();
let mut running_sum: $count = <$count as Count>::ZERO;
for (idx, &n) in histogram.as_slice().iter().enumerate() {
if n != <$count as Count>::ZERO {
running_sum = running_sum.wrapping_add(n);
index.push(idx as u32);
count.push(running_sum);
}
}
Self {
config: histogram.config(),
index,
count,
}
}
}
impl From<&$sparse> for $name {
fn from(histogram: &$sparse) -> Self {
let mut running_sum: $count = <$count as Count>::ZERO;
let cumulative: Vec<$count> = histogram
.count()
.iter()
.map(|&n| {
running_sum = running_sum.wrapping_add(n);
running_sum
})
.collect();
Self {
config: histogram.config(),
index: histogram.index().to_vec(),
count: cumulative,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct $ref_name<'a> {
config: Config,
index: &'a [u32],
count: &'a [$count],
}
impl<'a> $ref_name<'a> {
fn validate(config: &Config, index: &[u32], count: &[$count]) -> Result<(), Error> {
if index.len() != count.len() {
return Err(Error::IncompatibleParameters);
}
let total_buckets = config.total_buckets();
let mut prev_idx = None;
for &idx in index {
if idx as usize >= total_buckets {
return Err(Error::OutOfRange);
}
if let Some(p) = prev_idx {
if idx <= p {
return Err(Error::IncompatibleParameters);
}
}
prev_idx = Some(idx);
}
let mut prev_count = None;
for &c in count {
if c == <$count as Count>::ZERO {
return Err(Error::IncompatibleParameters);
}
if let Some(p) = prev_count {
if c < p {
return Err(Error::IncompatibleParameters);
}
}
prev_count = Some(c);
}
Ok(())
}
pub fn from_parts(
config: Config,
index: &'a [u32],
count: &'a [$count],
) -> Result<Self, Error> {
Self::validate(&config, index, count)?;
Ok(Self {
config,
index,
count,
})
}
pub fn from_parts_unchecked(
config: Config,
index: &'a [u32],
count: &'a [$count],
) -> Self {
Self {
config,
index,
count,
}
}
pub fn config(&self) -> Config {
self.config
}
pub fn index(&self) -> &'a [u32] {
self.index
}
pub fn count(&self) -> &'a [$count] {
self.count
}
pub fn len(&self) -> usize {
self.index.len()
}
pub fn is_empty(&self) -> bool {
self.index.is_empty()
}
pub fn total_count(&self) -> u64 {
self.count.last().map(|c| c.as_u128() as u64).unwrap_or(0)
}
pub fn bucket_quantile_range(&self, bucket_idx: usize) -> Option<(f64, f64)> {
if bucket_idx >= self.count.len() {
return None;
}
let total = self.count.last().map(|c| c.as_u128() as f64)?;
if total == 0.0 {
return None;
}
let lower = if bucket_idx == 0 {
0.0
} else {
self.count[bucket_idx - 1].as_u128() as f64 / total
};
let upper = self.count[bucket_idx].as_u128() as f64 / total;
Some((lower, upper))
}
pub fn iter(&self) -> $iter<'a> {
$iter {
position: 0,
config: self.config,
index: self.index,
count: self.count,
}
}
pub fn iter_with_quantiles(&self) -> $qr_iter<'a> {
let total = self.count.last().map(|c| c.as_u128() as f64).unwrap_or(0.0);
$qr_iter {
position: 0,
config: self.config,
index: self.index,
count: self.count,
total,
}
}
pub fn quantiles(&self, quantiles: &[f64]) -> Result<Option<QuantilesResult>, Error> {
<Self as SampleQuantiles>::quantiles(self, quantiles)
}
pub fn quantile(&self, quantile: f64) -> Result<Option<QuantilesResult>, Error> {
<Self as SampleQuantiles>::quantile(self, quantile)
}
fn individual_count(count: &[$count], position: usize) -> u64 {
if position == 0 {
count[0].as_u128() as u64
} else {
count[position].wrapping_sub(count[position - 1]).as_u128() as u64
}
}
fn find_quantile_position(count: &[$count], target: u128) -> usize {
const CACHE_LINE_ENTRIES: usize = 64 / std::mem::size_of::<$count>();
if count.len() <= CACHE_LINE_ENTRIES {
for (i, c) in count.iter().enumerate() {
if c.as_u128() >= target {
return i;
}
}
count.len() - 1
} else {
let pos = count.partition_point(|c| c.as_u128() < target);
pos.min(count.len() - 1)
}
}
}
impl SampleQuantiles for $ref_name<'_> {
fn quantiles(&self, quantiles: &[f64]) -> Result<Option<QuantilesResult>, Error> {
for q in quantiles {
if !(0.0..=1.0).contains(q) {
return Err(Error::InvalidQuantile);
}
}
if self.count.is_empty() {
return Ok(None);
}
let total_count = self.count.last().unwrap().as_u128();
if total_count == 0 {
return Ok(None);
}
let mut sorted: Vec<Quantile> = quantiles
.iter()
.map(|&q| Quantile::new(q).unwrap())
.collect();
sorted.sort();
sorted.dedup();
let min = Bucket {
count: Self::individual_count(self.count, 0),
range: self.config.index_to_range(self.index[0] as usize),
};
let last = self.count.len() - 1;
let max = Bucket {
count: Self::individual_count(self.count, last),
range: self.config.index_to_range(self.index[last] as usize),
};
let mut entries = BTreeMap::new();
for quantile in &sorted {
let target = std::cmp::max(
1u128,
(quantile.as_f64() * total_count as f64).ceil() as u128,
);
let pos = Self::find_quantile_position(self.count, target);
entries.insert(
*quantile,
Bucket {
count: Self::individual_count(self.count, pos),
range: self.config.index_to_range(self.index[pos] as usize),
},
);
}
Ok(Some(QuantilesResult::new(entries, total_count, min, max)))
}
}
impl<'a> From<&'a $name> for $ref_name<'a> {
fn from(h: &'a $name) -> Self {
Self::from_parts_unchecked(h.config(), h.index(), h.count())
}
}
impl<'a> IntoIterator for $ref_name<'a> {
type Item = Bucket;
type IntoIter = $iter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, 'b> IntoIterator for &'a $ref_name<'b> {
type Item = Bucket;
type IntoIter = $iter<'b>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
};
}
define_cumulative_histogram!(
CumulativeROHistogram,
CumulativeROHistogramRef,
CumulativeIter,
QuantileRangeIter,
Histogram,
SparseHistogram,
u64
);
define_cumulative_histogram!(
CumulativeROHistogram32,
CumulativeROHistogram32Ref,
CumulativeIter32,
QuantileRangeIter32,
Histogram32,
SparseHistogram32,
u32
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_histogram() {
let mut h = Histogram::new(7, 64).unwrap();
h.increment(1).unwrap();
h.increment(1).unwrap();
h.increment(5).unwrap();
h.increment(100).unwrap();
let croh = CumulativeROHistogram::from(&h);
assert_eq!(croh.config(), h.config());
assert_eq!(croh.index().len(), 3);
assert_eq!(croh.count(), &[2, 3, 4]);
assert_eq!(croh.total_count(), 4);
}
#[test]
fn from_sparse() {
let config = Config::new(7, 32).unwrap();
let sparse = SparseHistogram::from_parts(config, vec![1, 3, 5], vec![6, 12, 7]).unwrap();
let croh = CumulativeROHistogram::from(&sparse);
assert_eq!(croh.config(), config);
assert_eq!(croh.index(), &[1, 3, 5]);
assert_eq!(croh.count(), &[6, 18, 25]);
assert_eq!(croh.total_count(), 25);
}
#[test]
fn quantiles_match_histogram() {
let mut h = Histogram::new(4, 10).unwrap();
for v in 1..1024 {
h.increment(v).unwrap();
}
let sparse = SparseHistogram::from(&h);
let croh = CumulativeROHistogram::from(&h);
let quantiles = &[0.0, 0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99, 0.999, 1.0];
let hr = h.quantiles(quantiles).unwrap().unwrap();
let sr = sparse.quantiles(quantiles).unwrap().unwrap();
let cr = croh.quantiles(quantiles).unwrap().unwrap();
assert_eq!(hr.total_count(), cr.total_count());
assert_eq!(sr.total_count(), cr.total_count());
assert_eq!(hr.min().range(), cr.min().range());
assert_eq!(hr.max().range(), cr.max().range());
for ((hq, sq), cq) in hr
.entries()
.iter()
.zip(sr.entries().iter())
.zip(cr.entries().iter())
{
assert_eq!(hq.0, cq.0);
assert_eq!(sq.0, cq.0);
assert_eq!(hq.1.range(), cq.1.range());
assert_eq!(sq.1.range(), cq.1.range());
assert_eq!(hq.1.count(), cq.1.count());
}
}
#[test]
fn empty_histogram() {
let h = Histogram::new(7, 64).unwrap();
let croh = CumulativeROHistogram::from(&h);
assert!(croh.is_empty());
assert_eq!(croh.len(), 0);
assert_eq!(croh.total_count(), 0);
assert_eq!(croh.quantiles(&[0.5]).unwrap(), None);
assert_eq!(croh.quantile(0.5).unwrap(), None);
}
#[test]
fn single_sample() {
let mut h = Histogram::new(7, 64).unwrap();
h.increment(42).unwrap();
let croh = CumulativeROHistogram::from(&h);
assert_eq!(croh.len(), 1);
assert_eq!(croh.total_count(), 1);
let result = croh.quantile(0.0).unwrap().unwrap();
assert_eq!(result.min().end(), 42);
let result = croh.quantile(1.0).unwrap().unwrap();
assert_eq!(result.max().end(), 42);
let result = croh.quantile(0.5).unwrap().unwrap();
let q = Quantile::new(0.5).unwrap();
assert_eq!(result.get(&q).unwrap().end(), 42);
}
#[test]
fn from_parts_validation() {
let config = Config::new(7, 32).unwrap();
assert_eq!(
CumulativeROHistogram::from_parts(config, vec![1, 2], vec![1]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogram::from_parts(config, vec![u32::MAX], vec![1]),
Err(Error::OutOfRange)
);
assert_eq!(
CumulativeROHistogram::from_parts(config, vec![3, 1], vec![1, 2]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogram::from_parts(config, vec![1, 1], vec![1, 2]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogram::from_parts(config, vec![1, 3], vec![5, 3]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogram::from_parts(config, vec![1], vec![0]),
Err(Error::IncompatibleParameters)
);
assert!(CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![6, 18, 25]).is_ok());
assert!(CumulativeROHistogram::from_parts(config, vec![], vec![]).is_ok());
}
#[test]
fn quantile_ranges() {
let config = Config::new(7, 32).unwrap();
let croh =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10, 40, 100]).unwrap();
let (lo, hi) = croh.bucket_quantile_range(0).unwrap();
assert!((lo - 0.0).abs() < f64::EPSILON);
assert!((hi - 0.1).abs() < f64::EPSILON);
let (lo, hi) = croh.bucket_quantile_range(1).unwrap();
assert!((lo - 0.1).abs() < f64::EPSILON);
assert!((hi - 0.4).abs() < f64::EPSILON);
let (lo, hi) = croh.bucket_quantile_range(2).unwrap();
assert!((lo - 0.4).abs() < f64::EPSILON);
assert!((hi - 1.0).abs() < f64::EPSILON);
assert_eq!(croh.bucket_quantile_range(3), None);
let empty = CumulativeROHistogram::from_parts(config, vec![], vec![]).unwrap();
assert_eq!(empty.bucket_quantile_range(0), None);
}
#[test]
fn iter_with_quantiles() {
let config = Config::new(7, 32).unwrap();
let croh =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10, 40, 100]).unwrap();
let items: Vec<_> = croh.iter_with_quantiles().collect();
assert_eq!(items.len(), 3);
assert_eq!(items[0].0.count(), 10);
assert_eq!(items[1].0.count(), 30);
assert_eq!(items[2].0.count(), 60);
assert!((items[0].1 - 0.0).abs() < f64::EPSILON);
assert!((items[0].2 - 0.1).abs() < f64::EPSILON);
assert!((items[1].1 - 0.1).abs() < f64::EPSILON);
assert!((items[1].2 - 0.4).abs() < f64::EPSILON);
assert!((items[2].1 - 0.4).abs() < f64::EPSILON);
assert!((items[2].2 - 1.0).abs() < f64::EPSILON);
}
#[test]
fn iter_individual_counts() {
let mut h = Histogram::new(7, 64).unwrap();
h.increment(1).unwrap();
h.increment(1).unwrap();
h.increment(5).unwrap();
h.increment(100).unwrap();
let sparse = SparseHistogram::from(&h);
let croh = CumulativeROHistogram::from(&h);
let sparse_buckets: Vec<_> = sparse.iter().collect();
let croh_buckets: Vec<_> = croh.iter().collect();
assert_eq!(sparse_buckets.len(), croh_buckets.len());
for (sb, cb) in sparse_buckets.iter().zip(croh_buckets.iter()) {
assert_eq!(sb.count(), cb.count());
assert_eq!(sb.range(), cb.range());
}
}
#[test]
fn into_parts_roundtrip() {
let config = Config::new(7, 32).unwrap();
let original =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![6, 18, 25]).unwrap();
let (cfg, idx, cnt) = original.clone().into_parts();
let reconstructed = CumulativeROHistogram::from_parts(cfg, idx, cnt).unwrap();
assert_eq!(original, reconstructed);
}
#[test]
fn invalid_quantile_returns_error() {
let config = Config::new(7, 32).unwrap();
let croh = CumulativeROHistogram::from_parts(config, vec![1], vec![5]).unwrap();
assert_eq!(croh.quantiles(&[1.5]), Err(Error::InvalidQuantile));
assert_eq!(croh.quantiles(&[-0.1]), Err(Error::InvalidQuantile));
}
#[test]
fn from_histogram_u32() {
let mut h = Histogram32::new(7, 64).unwrap();
h.increment(1).unwrap();
h.increment(1).unwrap();
h.increment(5).unwrap();
h.increment(100).unwrap();
let croh = CumulativeROHistogram32::from(&h);
assert_eq!(croh.index().len(), 3);
assert_eq!(croh.count(), &[2u32, 3, 4]);
assert_eq!(croh.total_count(), 4);
}
#[test]
fn from_parts_u32() {
let config = Config::new(7, 32).unwrap();
let croh =
CumulativeROHistogram32::from_parts(config, vec![1, 3, 5], vec![6u32, 18, 25]).unwrap();
assert_eq!(croh.total_count(), 25);
}
#[test]
fn quantiles_u32_match_u64() {
let mut h32 = Histogram32::new(4, 10).unwrap();
let mut h64 = Histogram::new(4, 10).unwrap();
for v in 1..1024u64 {
h32.increment(v).unwrap();
h64.increment(v).unwrap();
}
let c32 = CumulativeROHistogram32::from(&h32);
let c64 = CumulativeROHistogram::from(&h64);
let qs = &[0.0, 0.5, 0.99, 1.0];
let r32 = c32.quantiles(qs).unwrap().unwrap();
let r64 = c64.quantiles(qs).unwrap().unwrap();
for ((q32, _), (q64, _)) in r32.entries().iter().zip(r64.entries().iter()) {
assert_eq!(q32, q64);
}
}
#[test]
fn ref_validation_parity_u64() {
let config = Config::new(7, 32).unwrap();
assert_eq!(
CumulativeROHistogramRef::from_parts(config, &[1u32, 2], &[1u64]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogramRef::from_parts(config, &[u32::MAX], &[1u64]),
Err(Error::OutOfRange)
);
assert_eq!(
CumulativeROHistogramRef::from_parts(config, &[3u32, 1], &[1u64, 2]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogramRef::from_parts(config, &[1u32, 1], &[1u64, 2]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogramRef::from_parts(config, &[1u32, 3], &[5u64, 3]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogramRef::from_parts(config, &[1u32], &[0u64]),
Err(Error::IncompatibleParameters)
);
assert!(
CumulativeROHistogramRef::from_parts(config, &[1u32, 3, 5], &[6u64, 18, 25]).is_ok()
);
assert!(CumulativeROHistogramRef::from_parts(config, &[], &[]).is_ok());
}
#[test]
fn ref_validation_parity_u32() {
let config = Config::new(7, 32).unwrap();
assert_eq!(
CumulativeROHistogram32Ref::from_parts(config, &[1u32, 2], &[1u32]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogram32Ref::from_parts(config, &[u32::MAX], &[1u32]),
Err(Error::OutOfRange)
);
assert_eq!(
CumulativeROHistogram32Ref::from_parts(config, &[3u32, 1], &[1u32, 2]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogram32Ref::from_parts(config, &[1u32, 3], &[5u32, 3]),
Err(Error::IncompatibleParameters)
);
assert_eq!(
CumulativeROHistogram32Ref::from_parts(config, &[1u32], &[0u32]),
Err(Error::IncompatibleParameters)
);
assert!(
CumulativeROHistogram32Ref::from_parts(config, &[1u32, 3, 5], &[6u32, 18, 25]).is_ok()
);
}
#[test]
fn ref_quantile_parity_u64() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r = CumulativeROHistogramRef::from_parts(config, owned.index(), owned.count()).unwrap();
let qs = &[0.0, 0.5, 0.99, 1.0];
assert_eq!(owned.quantiles(qs).unwrap(), r.quantiles(qs).unwrap());
}
#[test]
fn ref_quantile_parity_u32() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram32::from_parts(config, vec![1, 3, 5], vec![10u32, 40, 100])
.unwrap();
let r =
CumulativeROHistogram32Ref::from_parts(config, owned.index(), owned.count()).unwrap();
let qs = &[0.0, 0.5, 0.99, 1.0];
assert_eq!(owned.quantiles(qs).unwrap(), r.quantiles(qs).unwrap());
}
#[test]
fn ref_from_parts_unchecked_matches_owned() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r =
CumulativeROHistogramRef::from_parts_unchecked(config, owned.index(), owned.count());
let qs = &[0.25, 0.5, 0.75];
assert_eq!(owned.quantiles(qs).unwrap(), r.quantiles(qs).unwrap());
}
#[test]
fn ref_from_owned_round_trip() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r = CumulativeROHistogramRef::from(&owned);
let qs = &[0.0, 0.5, 1.0];
assert_eq!(r.quantiles(qs).unwrap(), owned.quantiles(qs).unwrap());
}
#[test]
fn ref_iter_agrees_with_owned() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r = CumulativeROHistogramRef::from(&owned);
let owned_buckets: Vec<_> = owned.iter().collect();
let ref_buckets: Vec<_> = r.iter().collect();
assert_eq!(owned_buckets, ref_buckets);
}
#[test]
fn ref_iter_with_quantiles_agrees_with_owned() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r = CumulativeROHistogramRef::from(&owned);
let owned_items: Vec<_> = owned.iter_with_quantiles().collect();
let ref_items: Vec<_> = r.iter_with_quantiles().collect();
assert_eq!(owned_items.len(), ref_items.len());
for (a, b) in owned_items.iter().zip(ref_items.iter()) {
assert_eq!(a.0, b.0);
assert!((a.1 - b.1).abs() < f64::EPSILON);
assert!((a.2 - b.2).abs() < f64::EPSILON);
}
}
#[test]
fn ref_bucket_quantile_range_parity() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r = CumulativeROHistogramRef::from(&owned);
for i in 0..=3 {
assert_eq!(owned.bucket_quantile_range(i), r.bucket_quantile_range(i));
}
}
#[test]
fn ref_empty_edge_case() {
let config = Config::new(7, 32).unwrap();
let r = CumulativeROHistogramRef::from_parts(config, &[], &[]).unwrap();
assert!(r.is_empty());
assert_eq!(r.len(), 0);
assert_eq!(r.total_count(), 0);
assert_eq!(r.quantiles(&[0.5]).unwrap(), None);
assert_eq!(r.bucket_quantile_range(0), None);
}
#[test]
fn ref_single_sample_edge_case() {
let mut h = Histogram::new(7, 64).unwrap();
h.increment(42).unwrap();
let owned = CumulativeROHistogram::from(&h);
let r = CumulativeROHistogramRef::from(&owned);
assert_eq!(r.len(), 1);
assert_eq!(r.total_count(), 1);
let result = r.quantile(0.5).unwrap().unwrap();
let q = Quantile::new(0.5).unwrap();
assert_eq!(result.get(&q).unwrap().end(), 42);
}
#[test]
fn ref_into_iterator() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r = CumulativeROHistogramRef::from(&owned);
let buckets_consumed: Vec<_> = r.into_iter().collect();
let buckets_borrowed: Vec<_> = (&r).into_iter().collect();
let owned_buckets: Vec<_> = owned.iter().collect();
assert_eq!(buckets_consumed, owned_buckets);
assert_eq!(buckets_borrowed, owned_buckets);
}
#[test]
fn ref_u32_symmetry() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram32::from_parts(config, vec![1, 3, 5], vec![10u32, 40, 100])
.unwrap();
let r = CumulativeROHistogram32Ref::from(&owned);
let owned_buckets: Vec<_> = owned.iter().collect();
let ref_buckets: Vec<_> = r.iter().collect();
assert_eq!(owned_buckets, ref_buckets);
let qs = &[0.0, 0.5, 0.99, 1.0];
assert_eq!(owned.quantiles(qs).unwrap(), r.quantiles(qs).unwrap());
for i in 0..=3 {
assert_eq!(owned.bucket_quantile_range(i), r.bucket_quantile_range(i));
}
}
#[test]
fn ref_as_ref_method() {
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r = owned.as_ref();
let qs = &[0.5, 0.99];
assert_eq!(owned.quantiles(qs).unwrap(), r.quantiles(qs).unwrap());
}
#[test]
fn ref_sample_quantiles_trait() {
use crate::quantile::SampleQuantiles;
let config = Config::new(7, 32).unwrap();
let owned =
CumulativeROHistogram::from_parts(config, vec![1, 3, 5], vec![10u64, 40, 100]).unwrap();
let r = CumulativeROHistogramRef::from(&owned);
let qs = &[0.25, 0.75];
assert_eq!(
SampleQuantiles::quantiles(&r, qs).unwrap(),
SampleQuantiles::quantiles(&owned, qs).unwrap()
);
}
}