use core::fmt;
use crate::{MeasurementStateError, add_count, add_total};
#[cfg(test)]
mod tests;
const fn mean_total(samples: u64, total: u128) -> Option<u128> {
if total <= u64::MAX as u128 {
#[expect(
clippy::cast_possible_truncation,
reason = "the branch admits a u64 total"
)]
let narrow = total as u64;
match narrow.checked_div(samples) {
Some(mean) => Some(mean as u128),
None => None,
}
} else {
total.checked_div(samples as u128)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MeasurementMeanError {
SampleCountOverflow,
}
impl fmt::Display for MeasurementMeanError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("measurement sample count has overflowed")
}
}
impl core::error::Error for MeasurementMeanError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MeasurementIntervalError {
SampleCountOverflow,
DecreasingSamples,
DecreasingTotal,
TotalWithoutSamples,
}
impl fmt::Display for MeasurementIntervalError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::SampleCountOverflow => "interval sample count 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: u128,
}
impl MeasurementInterval {
#[must_use]
pub const fn samples(self) -> u64 {
self.samples
}
#[must_use]
pub const fn total(self) -> u128 {
self.total
}
#[must_use]
pub const fn mean(self) -> Option<u128> {
mean_total(self.samples, self.total)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MeasurementAggregate {
samples: u64,
total: u128,
maximum: u64,
samples_overflowed: bool,
total_overflowed: bool,
}
impl MeasurementAggregate {
pub const fn from_parts(
samples: u64,
total: u128,
maximum: Option<u64>,
samples_overflowed: bool,
total_overflowed: bool,
) -> Result<Self, MeasurementStateError> {
if samples == 0 {
return if total == 0 && maximum.is_none() && !samples_overflowed && !total_overflowed {
Ok(Self::EMPTY)
} else {
Err(MeasurementStateError::InvalidEmpty)
};
}
let Some(maximum) = maximum else {
return Err(MeasurementStateError::MissingMaximum);
};
if (samples_overflowed && samples != u64::MAX)
|| (total_overflowed && (total != u128::MAX || !samples_overflowed))
{
return Err(MeasurementStateError::InvalidOverflowFlag);
}
if total < maximum as u128
|| (maximum == 0 && total != 0)
|| (!samples_overflowed && total > samples as u128 * maximum as u128)
{
return Err(MeasurementStateError::InvalidTotal);
}
Ok(Self {
samples,
total,
maximum,
samples_overflowed,
total_overflowed,
})
}
pub const EMPTY: Self = Self {
samples: 0,
total: 0,
maximum: 0,
samples_overflowed: false,
total_overflowed: false,
};
pub const fn record(&mut self, value: u64) {
add_count(&mut self.samples, &mut self.samples_overflowed, 1);
add_total(&mut self.total, &mut self.total_overflowed, value as u128);
if value > self.maximum {
self.maximum = value;
}
}
pub const fn merge(&mut self, other: Self) {
add_count(
&mut self.samples,
&mut self.samples_overflowed,
other.samples,
);
add_total(&mut self.total, &mut self.total_overflowed, other.total);
self.samples_overflowed |= other.samples_overflowed;
self.total_overflowed |= other.total_overflowed;
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) -> u128 {
self.total
}
#[must_use]
pub const fn samples_overflowed(self) -> bool {
self.samples_overflowed
}
#[must_use]
pub const fn total_overflowed(self) -> bool {
self.total_overflowed
}
#[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<u128>, MeasurementMeanError> {
if self.samples_overflowed {
return Err(MeasurementMeanError::SampleCountOverflow);
}
Ok(mean_total(self.samples, self.total))
}
pub const fn scaled_mean(self, scale: u64) -> Result<Option<u128>, MeasurementMeanError> {
if self.samples_overflowed {
return Err(MeasurementMeanError::SampleCountOverflow);
}
if self.samples == 0 {
return Ok(None);
}
let samples = self.samples as u128;
let scale = scale as u128;
Ok(Some(
(self.total / samples) * scale + (self.total % samples) * scale / samples,
))
}
pub const fn interval_since(
self,
previous: Self,
) -> Result<MeasurementInterval, MeasurementIntervalError> {
if self.samples_overflowed || previous.samples_overflowed {
return Err(MeasurementIntervalError::SampleCountOverflow);
}
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 })
}
}