#![cfg(test)]
use std::{
fmt::{self, Display, Formatter},
ops::Deref,
time::Instant,
};
use ordered_float::OrderedFloat;
#[derive(Copy, Clone, Debug, Default)]
pub struct HistogramStats {
pub min: usize, pub max: usize, pub mode: usize, pub total: f64, pub mean: f64, }
#[derive(Clone, Debug, Default)]
pub struct Histogram {
totals: Vec<f64>,
}
impl Histogram {
pub fn add(&mut self, index: usize, amount: f64) {
if self.totals.len() <= index {
self.totals
.extend((self.totals.len()..index + 1).map(|_| 0.0));
}
self.totals[index] += amount;
}
pub fn stats(&self) -> Option<HistogramStats> {
let (min, max, mode, sum) = self.totals.iter().enumerate().fold(
(None, None, None, WeightedSum::default()),
|(mut min, mut max, mut mode, mut sum), (i, &total)| {
if total > 0.0 {
min = min.or(Some(i));
max = Some(i);
mode = Some(match mode {
None => (i, total),
Some((index, value)) => {
if value > total {
(index, value)
} else {
(i, total)
}
}
});
}
sum.add(i as f64, total);
(min, max, mode, sum)
},
);
min.map(|_| HistogramStats {
min: min.unwrap(),
max: max.unwrap(),
mode: mode.unwrap().0,
mean: sum.mean().unwrap(),
total: sum.weights,
})
}
}
impl Display for Histogram {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
match self.stats() {
None => write!(fmt, "[No stats]"),
Some(stats) => write!(
fmt,
"[min={}, max={}, mode={}, mean={}, total={}]",
stats.min, stats.max, stats.mode, stats.mean, stats.total
),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct TimeHistogram {
histogram: Histogram,
last_time: Option<Instant>,
}
impl TimeHistogram {
pub fn add(&mut self, index: usize, instant: Instant) {
if let Some(last) = self.last_time {
let duration = instant.saturating_duration_since(last).as_secs_f64();
self.histogram.add(index, duration);
}
self.last_time = Some(instant);
}
}
impl Deref for TimeHistogram {
type Target = Histogram;
fn deref(&self) -> &Self::Target {
&self.histogram
}
}
impl Display for TimeHistogram {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.histogram.fmt(fmt)
}
}
#[derive(Clone, Debug, Default)]
pub struct LevelTimeHistogram {
level: usize,
histogram: TimeHistogram,
}
impl LevelTimeHistogram {
pub fn adjust(&mut self, adjustment: isize, instant: Instant) -> usize {
self.histogram.add(self.level, instant);
self.level = ((self.level as isize) + adjustment) as usize;
self.level
}
pub const fn level(&self) -> usize {
self.level
}
}
impl Deref for LevelTimeHistogram {
type Target = TimeHistogram;
fn deref(&self) -> &Self::Target {
&self.histogram
}
}
impl Display for LevelTimeHistogram {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.histogram.fmt(fmt)
}
}
#[derive(Debug)]
pub struct VariableHistogram {
buckets: Vec<(f64, u64)>,
count: u64,
sum: f64,
}
impl VariableHistogram {
pub fn new(upper_limits: &[f64]) -> Self {
let mut buckets = upper_limits.iter().map(|v| (*v, 0)).collect::<Vec<_>>();
buckets.dedup_by_key(|(upper_limit, _)| OrderedFloat(*upper_limit));
buckets.sort_by_key(|(upper_limit, _)| OrderedFloat(*upper_limit));
Self {
buckets,
count: 0,
sum: 0.0,
}
}
pub fn record(&mut self, value: f64) {
for (bound, count) in self.buckets.iter_mut() {
if value <= *bound {
*count += 1;
break;
}
}
self.count += 1;
self.sum += value;
}
pub fn record_many(&mut self, values: &[f64]) {
for value in values {
self.record(*value);
}
}
pub const fn count(&self) -> u64 {
self.count
}
pub const fn sum(&self) -> f64 {
self.sum
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct WeightedSum {
total: f64,
weights: f64,
min: Option<f64>,
max: Option<f64>,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct WeightedSumStats {
pub min: f64,
pub max: f64,
pub mean: f64,
}
impl WeightedSum {
pub fn add(&mut self, value: f64, weight: f64) {
self.total += value * weight;
self.weights += weight;
self.max = Some(opt_max(self.max, value));
self.min = Some(opt_min(self.min, value));
}
pub fn mean(&self) -> Option<f64> {
if self.weights == 0.0 {
None
} else {
Some(self.total / self.weights)
}
}
pub fn stats(&self) -> Option<WeightedSumStats> {
self.mean().map(|mean| WeightedSumStats {
mean,
min: self.min.unwrap(),
max: self.max.unwrap(),
})
}
}
impl Display for WeightedSum {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
match self.stats() {
None => write!(fmt, "[No stats]"),
Some(stats) => write!(
fmt,
"[min={}, max={}, mean={}]",
stats.min, stats.max, stats.mean
),
}
}
}
fn opt_max(opt: Option<f64>, value: f64) -> f64 {
match opt {
None => value,
Some(s) if s > value => s,
_ => value,
}
}
fn opt_min(opt: Option<f64>, value: f64) -> f64 {
match opt {
None => value,
Some(s) if s < value => s,
_ => value,
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct TimeWeightedSum {
sum: WeightedSum,
last_observation: Option<Instant>,
}
impl TimeWeightedSum {
pub fn add(&mut self, value: f64, instant: Instant) {
if let Some(then) = self.last_observation {
let duration = instant.saturating_duration_since(then).as_secs_f64();
self.sum.add(value, duration);
}
self.last_observation = Some(instant);
}
}
impl Deref for TimeWeightedSum {
type Target = WeightedSum;
fn deref(&self) -> &Self::Target {
&self.sum
}
}
impl Display for TimeWeightedSum {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.sum.fmt(fmt)
}
}