use std::collections::HashMap;
use std::convert::TryFrom;
use serde::{Deserialize, Serialize};
use crate::error::{Error, ErrorKind};
pub use exponential::PrecomputedExponential;
pub use functional::Functional;
pub use linear::PrecomputedLinear;
mod exponential;
mod functional;
mod linear;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum HistogramType {
Linear,
Exponential,
}
impl TryFrom<i32> for HistogramType {
type Error = Error;
fn try_from(value: i32) -> Result<HistogramType, Self::Error> {
match value {
0 => Ok(HistogramType::Linear),
1 => Ok(HistogramType::Exponential),
e => Err(ErrorKind::HistogramType(e).into()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Histogram<B> {
values: HashMap<u64, u64>,
count: u64,
sum: u64,
bucketing: B,
}
pub trait Bucketing {
fn sample_to_bucket_minimum(&self, sample: u64) -> u64;
fn ranges(&self) -> &[u64];
}
impl Bucketing for Box<dyn Bucketing> {
fn sample_to_bucket_minimum(&self, sample: u64) -> u64 {
(**self).sample_to_bucket_minimum(sample)
}
fn ranges(&self) -> &[u64] {
(**self).ranges()
}
}
impl<B: Bucketing> Histogram<B> {
pub fn bucket_count(&self) -> usize {
self.values.len()
}
pub fn accumulate(&mut self, sample: u64) {
let bucket_min = self.bucketing.sample_to_bucket_minimum(sample);
let entry = self.values.entry(bucket_min).or_insert(0);
*entry += 1;
self.sum = self.sum.saturating_add(sample);
self.count += 1;
}
pub fn sum(&self) -> u64 {
self.sum
}
pub fn count(&self) -> u64 {
self.count
}
pub fn values(&self) -> &HashMap<u64, u64> {
&self.values
}
pub fn is_empty(&self) -> bool {
self.count() == 0
}
pub fn snapshot_values(&self) -> HashMap<u64, u64> {
let mut res = self.values.clone();
let max_bucket = self.values.keys().max().cloned().unwrap_or(0);
for &min_bucket in self.bucketing.ranges() {
let _ = res.entry(min_bucket).or_insert(0);
if min_bucket > max_bucket {
break;
}
}
res
}
}
impl<B: Bucketing + 'static> Histogram<B> {
pub fn boxed(self) -> Histogram<Box<dyn Bucketing>> {
Histogram {
values: self.values,
count: self.count,
sum: self.sum,
bucketing: Box::new(self.bucketing),
}
}
}