use core::fmt;
use crate::{MeasurementAggregate, MeasurementStateError, mean_total};
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MeasurementMeanError {
TotalWithoutSamples,
SaturatedSamples,
SaturatedTotal,
}
impl fmt::Display for MeasurementMeanError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::TotalWithoutSamples => "nonzero measurement total without samples",
Self::SaturatedSamples => "measurement sample count is saturated",
Self::SaturatedTotal => "measurement total is saturated",
})
}
}
impl core::error::Error for MeasurementMeanError {}
pub const fn checked_mean(samples: u64, total: u128) -> Result<Option<u128>, MeasurementMeanError> {
if samples == 0 {
return if total == 0 {
Ok(None)
} else {
Err(MeasurementMeanError::TotalWithoutSamples)
};
}
if samples == u64::MAX {
return Err(MeasurementMeanError::SaturatedSamples);
}
if total == u128::MAX {
return Err(MeasurementMeanError::SaturatedTotal);
}
Ok(mean_total(samples, total))
}
pub const fn record_sample(samples: &mut u64, total: &mut u128, value: u64) {
*samples = samples.saturating_add(1);
*total = total.saturating_add(value as u128);
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MeasurementSummary {
aggregate: MeasurementAggregate,
latest: u64,
}
impl MeasurementSummary {
pub const fn from_parts(
aggregate: MeasurementAggregate,
latest: Option<u64>,
) -> Result<Self, MeasurementStateError> {
if aggregate.samples() == 0 {
return if latest.is_none() {
Ok(Self::EMPTY)
} else {
Err(MeasurementStateError::InvalidLatest)
};
}
let (Some(latest), Some(maximum)) = (latest, aggregate.maximum()) else {
return Err(MeasurementStateError::InvalidLatest);
};
if latest > maximum
|| (aggregate.samples() == 1 && latest != maximum)
|| (latest < maximum && aggregate.total() < latest as u128 + maximum as u128)
{
return Err(MeasurementStateError::InvalidLatest);
}
Ok(Self { aggregate, latest })
}
pub const EMPTY: Self = Self {
aggregate: MeasurementAggregate::EMPTY,
latest: 0,
};
pub const fn record(&mut self, value: u64) {
self.aggregate.record(value);
self.latest = value;
}
#[must_use]
pub const fn aggregate(self) -> MeasurementAggregate {
self.aggregate
}
#[must_use]
pub const fn samples(self) -> u64 {
self.aggregate.samples()
}
#[must_use]
pub const fn total(self) -> u128 {
self.aggregate.total()
}
pub const fn mean(self) -> Result<Option<u128>, MeasurementMeanError> {
self.aggregate.mean()
}
#[must_use]
pub const fn latest(self) -> Option<u64> {
if self.samples() == 0 {
None
} else {
Some(self.latest)
}
}
#[must_use]
pub const fn maximum(self) -> Option<u64> {
self.aggregate.maximum()
}
}