ic_metrics/aggregate/
mod.rs1use core::fmt;
4
5use crate::{MeasurementMeanError, add_count};
6
7#[cfg(test)]
8mod tests;
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum MeasurementIntervalError {
13 SaturatedSamples,
15 SaturatedTotal,
17 DecreasingSamples,
19 DecreasingTotal,
21 TotalWithoutSamples,
23}
24
25impl fmt::Display for MeasurementIntervalError {
26 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
27 formatter.write_str(match self {
28 Self::SaturatedSamples => "interval sample count has overflowed",
29 Self::SaturatedTotal => "interval total has overflowed",
30 Self::DecreasingSamples => "interval sample count decreased",
31 Self::DecreasingTotal => "interval total decreased",
32 Self::TotalWithoutSamples => "interval total changed without samples",
33 })
34 }
35}
36
37impl core::error::Error for MeasurementIntervalError {}
38
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45pub struct MeasurementInterval {
46 samples: u64,
47 total: u64,
48}
49
50impl MeasurementInterval {
51 #[must_use]
53 pub const fn samples(self) -> u64 {
54 self.samples
55 }
56
57 #[must_use]
59 pub const fn total(self) -> u64 {
60 self.total
61 }
62
63 #[must_use]
65 pub const fn mean(self) -> Option<u64> {
66 self.total.checked_div(self.samples)
67 }
68}
69
70#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
77pub struct MeasurementAggregate {
78 samples: u64,
79 total: u64,
80 maximum: u64,
81 samples_saturated: bool,
82 total_saturated: bool,
83}
84
85impl MeasurementAggregate {
86 pub const EMPTY: Self = Self {
88 samples: 0,
89 total: 0,
90 maximum: 0,
91 samples_saturated: false,
92 total_saturated: false,
93 };
94
95 pub const fn record(&mut self, value: u64) {
97 add_count(&mut self.samples, &mut self.samples_saturated, 1);
98 add_count(&mut self.total, &mut self.total_saturated, value);
99 if value > self.maximum {
100 self.maximum = value;
101 }
102 }
103
104 pub const fn merge(&mut self, other: Self) {
110 add_count(
111 &mut self.samples,
112 &mut self.samples_saturated,
113 other.samples,
114 );
115 add_count(&mut self.total, &mut self.total_saturated, other.total);
116 self.samples_saturated |= other.samples_saturated;
117 self.total_saturated |= other.total_saturated;
118 if other.maximum > self.maximum {
119 self.maximum = other.maximum;
120 }
121 }
122
123 #[must_use]
125 pub const fn samples(self) -> u64 {
126 self.samples
127 }
128
129 #[must_use]
131 pub const fn total(self) -> u64 {
132 self.total
133 }
134
135 #[must_use]
137 pub const fn samples_saturated(self) -> bool {
138 self.samples_saturated
139 }
140
141 #[must_use]
143 pub const fn total_saturated(self) -> bool {
144 self.total_saturated
145 }
146
147 #[must_use]
149 pub const fn maximum(self) -> Option<u64> {
150 if self.samples == 0 {
151 None
152 } else {
153 Some(self.maximum)
154 }
155 }
156
157 pub const fn mean(self) -> Result<Option<u64>, MeasurementMeanError> {
163 if self.samples_saturated {
164 return Err(MeasurementMeanError::SaturatedSamples);
165 }
166 if self.total_saturated {
167 return Err(MeasurementMeanError::SaturatedTotal);
168 }
169 Ok(self.total.checked_div(self.samples))
170 }
171
172 pub const fn interval_since(
183 self,
184 previous: Self,
185 ) -> Result<MeasurementInterval, MeasurementIntervalError> {
186 if self.samples_saturated || previous.samples_saturated {
187 return Err(MeasurementIntervalError::SaturatedSamples);
188 }
189 if self.total_saturated || previous.total_saturated {
190 return Err(MeasurementIntervalError::SaturatedTotal);
191 }
192 let Some(samples) = self.samples.checked_sub(previous.samples) else {
193 return Err(MeasurementIntervalError::DecreasingSamples);
194 };
195 let Some(total) = self.total.checked_sub(previous.total) else {
196 return Err(MeasurementIntervalError::DecreasingTotal);
197 };
198 if samples == 0 && total != 0 {
199 return Err(MeasurementIntervalError::TotalWithoutSamples);
200 }
201 Ok(MeasurementInterval { samples, total })
202 }
203}