ic_metrics/summary/mod.rs
1//! Saturating arithmetic without sampling or attribution policy.
2
3#[cfg(test)]
4mod tests;
5
6/// Record one value into a sample count and total, saturating independently.
7///
8/// Zero is a completed sample. `value` and `total` must use the same unit.
9/// This primitive supports consumer-owned report shapes without serialization
10/// dependencies. It does not validate continuity or make saturated counters
11/// suitable for exact interval arithmetic.
12pub const fn record_sample(samples: &mut u64, total: &mut u64, value: u64) {
13 *samples = samples.saturating_add(1);
14 *total = total.saturating_add(value);
15}
16
17/// Saturating summary of observations in one consumer-selected unit.
18///
19/// Count and total saturate independently at `u64::MAX`. Treat either counter
20/// at that value as unavailable for exact interval arithmetic, including when
21/// reached exactly. Latest and maximum remain individual observations after
22/// saturation. Empty and measured zero are distinct states.
23///
24/// Consumers establish identity and reset boundaries before comparing snapshots.
25/// The summary does not decide whether observations overlap or are additive.
26#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
27pub struct MeasurementSummary {
28 samples: u64,
29 total: u64,
30 latest: u64,
31 maximum: u64,
32}
33
34impl MeasurementSummary {
35 /// Empty summary, with no latest or maximum observation.
36 pub const EMPTY: Self = Self {
37 samples: 0,
38 total: 0,
39 latest: 0,
40 maximum: 0,
41 };
42
43 /// Record one completed observation, including zero.
44 ///
45 /// Every observation must use the same unit as earlier observations.
46 pub const fn record(&mut self, value: u64) {
47 record_sample(&mut self.samples, &mut self.total, value);
48 self.latest = value;
49 if value > self.maximum {
50 self.maximum = value;
51 }
52 }
53
54 /// Number of completed samples, saturating independently of the total.
55 #[must_use]
56 pub const fn samples(self) -> u64 {
57 self.samples
58 }
59
60 /// Sum of all observations, saturating independently of the sample count.
61 #[must_use]
62 pub const fn total(self) -> u64 {
63 self.total
64 }
65
66 /// Latest observation, or `None` when no sample has been recorded.
67 #[must_use]
68 pub const fn latest(self) -> Option<u64> {
69 if self.samples == 0 {
70 None
71 } else {
72 Some(self.latest)
73 }
74 }
75
76 /// Largest observation, or `None` when no sample has been recorded.
77 #[must_use]
78 pub const fn maximum(self) -> Option<u64> {
79 if self.samples == 0 {
80 None
81 } else {
82 Some(self.maximum)
83 }
84 }
85}