use core::fmt;
use crate::{MeasurementAggregate, MeasurementRatioError, MeasurementStateError};
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MeasurementMeanError {
SaturatedSamples,
SaturatedTotal,
Projection(MeasurementRatioError),
}
impl fmt::Display for MeasurementMeanError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SaturatedSamples => formatter.write_str("measurement sample count is saturated"),
Self::SaturatedTotal => formatter.write_str("measurement total is saturated"),
Self::Projection(error) => {
write!(formatter, "measurement mean projection failed: {error}")
}
}
}
}
impl core::error::Error for MeasurementMeanError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Projection(error) => Some(error),
_ => None,
}
}
}
#[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()
}
pub const fn scaled_mean(self, scale: u64) -> Result<Option<u128>, MeasurementMeanError> {
self.aggregate.scaled_mean(scale)
}
#[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()
}
}