use core::fmt;
use crate::{MeasurementMeanError, add_count};
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MeasurementIntervalError {
SaturatedSamples,
SaturatedTotal,
DecreasingSamples,
DecreasingTotal,
TotalWithoutSamples,
}
impl fmt::Display for MeasurementIntervalError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::SaturatedSamples => "interval sample count has overflowed",
Self::SaturatedTotal => "interval total has overflowed",
Self::DecreasingSamples => "interval sample count decreased",
Self::DecreasingTotal => "interval total decreased",
Self::TotalWithoutSamples => "interval total changed without samples",
})
}
}
impl core::error::Error for MeasurementIntervalError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MeasurementInterval {
samples: u64,
total: u64,
}
impl MeasurementInterval {
#[must_use]
pub const fn samples(self) -> u64 {
self.samples
}
#[must_use]
pub const fn total(self) -> u64 {
self.total
}
#[must_use]
pub const fn mean(self) -> Option<u64> {
self.total.checked_div(self.samples)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MeasurementAggregate {
samples: u64,
total: u64,
maximum: u64,
samples_saturated: bool,
total_saturated: bool,
}
impl MeasurementAggregate {
pub const EMPTY: Self = Self {
samples: 0,
total: 0,
maximum: 0,
samples_saturated: false,
total_saturated: false,
};
pub const fn record(&mut self, value: u64) {
add_count(&mut self.samples, &mut self.samples_saturated, 1);
add_count(&mut self.total, &mut self.total_saturated, value);
if value > self.maximum {
self.maximum = value;
}
}
pub const fn merge(&mut self, other: Self) {
add_count(
&mut self.samples,
&mut self.samples_saturated,
other.samples,
);
add_count(&mut self.total, &mut self.total_saturated, other.total);
self.samples_saturated |= other.samples_saturated;
self.total_saturated |= other.total_saturated;
if other.maximum > self.maximum {
self.maximum = other.maximum;
}
}
#[must_use]
pub const fn samples(self) -> u64 {
self.samples
}
#[must_use]
pub const fn total(self) -> u64 {
self.total
}
#[must_use]
pub const fn samples_saturated(self) -> bool {
self.samples_saturated
}
#[must_use]
pub const fn total_saturated(self) -> bool {
self.total_saturated
}
#[must_use]
pub const fn maximum(self) -> Option<u64> {
if self.samples == 0 {
None
} else {
Some(self.maximum)
}
}
pub const fn mean(self) -> Result<Option<u64>, MeasurementMeanError> {
if self.samples_saturated {
return Err(MeasurementMeanError::SaturatedSamples);
}
if self.total_saturated {
return Err(MeasurementMeanError::SaturatedTotal);
}
Ok(self.total.checked_div(self.samples))
}
pub const fn interval_since(
self,
previous: Self,
) -> Result<MeasurementInterval, MeasurementIntervalError> {
if self.samples_saturated || previous.samples_saturated {
return Err(MeasurementIntervalError::SaturatedSamples);
}
if self.total_saturated || previous.total_saturated {
return Err(MeasurementIntervalError::SaturatedTotal);
}
let Some(samples) = self.samples.checked_sub(previous.samples) else {
return Err(MeasurementIntervalError::DecreasingSamples);
};
let Some(total) = self.total.checked_sub(previous.total) else {
return Err(MeasurementIntervalError::DecreasingTotal);
};
if samples == 0 && total != 0 {
return Err(MeasurementIntervalError::TotalWithoutSamples);
}
Ok(MeasurementInterval { samples, total })
}
}