radiate_core/stats/
metrics.rs

1use super::Statistic;
2use crate::{
3    Distribution, TimeStatistic, intern,
4    stats::{ToSnakeCase, defaults},
5};
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use std::{fmt::Debug, time::Duration};
9
10#[macro_export]
11macro_rules! metric {
12    ($name:expr, $update:expr) => {{
13        let mut metric = $crate::Metric::new($name);
14        metric.apply_update($update);
15        metric
16    }};
17    ($scope:expr, $name:expr, $value:expr) => {{ $crate::Metric::new_scoped($name, $scope).upsert($value) }};
18    ($name:expr) => {{ $crate::Metric::new($name).upsert(1) }};
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23pub enum MetricScope {
24    #[default]
25    Generation,
26    Lifetime,
27    Step,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32pub enum Rollup {
33    #[default]
34    Sum,
35    Mean,
36    Last,
37    Min,
38    Max,
39}
40
41#[derive(Clone, PartialEq, Default)]
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43pub struct MetricInner {
44    pub(crate) value_statistic: Option<Statistic>,
45    pub(crate) time_statistic: Option<TimeStatistic>,
46    pub(crate) distribution: Option<Distribution>,
47}
48
49#[derive(Clone, PartialEq, Default)]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51pub struct Metric {
52    pub(super) name: &'static str,
53    pub(super) inner: MetricInner,
54    pub(super) scope: MetricScope,
55    pub(super) rollup: Rollup,
56}
57
58impl Metric {
59    pub fn new(name: &'static str) -> Self {
60        let name = intern!(name.to_snake_case());
61        let scope = defaults::default_scope(name);
62        let rollup = defaults::default_rollup(name);
63        Self {
64            name,
65            inner: MetricInner {
66                value_statistic: None,
67                time_statistic: None,
68                distribution: None,
69            },
70            scope,
71            rollup,
72        }
73    }
74
75    pub fn new_scoped(name: &'static str, scope: MetricScope) -> Self {
76        let rollup = defaults::default_rollup(name);
77        Self {
78            scope,
79            rollup,
80            ..Self::new(intern!(name.to_snake_case()))
81        }
82    }
83
84    pub fn with_rollup(mut self, rollup: Rollup) -> Self {
85        self.rollup = rollup;
86        self
87    }
88
89    pub fn scope(&self) -> MetricScope {
90        self.scope
91    }
92    pub fn rollup(&self) -> Rollup {
93        self.rollup
94    }
95
96    pub fn clear_values(&mut self) {
97        self.inner = MetricInner::default();
98    }
99
100    pub fn inner(&self) -> &MetricInner {
101        &self.inner
102    }
103
104    #[inline(always)]
105    pub fn upsert<'a>(mut self, update: impl Into<MetricUpdate<'a>>) -> Self {
106        self.apply_update(update);
107        self
108    }
109
110    pub fn update_from(&mut self, other: &Metric) {
111        if let Some(stat) = &other.inner.value_statistic {
112            let v = (stat.last_value(), stat.min(), stat.max(), stat.mean());
113            match self.rollup() {
114                Rollup::Sum => self.apply_update(stat.sum()),
115                Rollup::Mean => self.apply_update(v.3),
116                Rollup::Last => self.apply_update(v.0),
117                Rollup::Min => self.apply_update(v.1),
118                Rollup::Max => self.apply_update(v.2),
119            }
120        }
121
122        if let Some(time) = &other.inner.time_statistic {
123            let t = (
124                time.last_time(),
125                time.min(),
126                time.max(),
127                time.mean(),
128                time.sum(),
129            );
130            match self.rollup() {
131                Rollup::Sum => self.apply_update(t.4),
132                Rollup::Mean => self.apply_update(t.3),
133                Rollup::Last => self.apply_update(t.0),
134                Rollup::Min => self.apply_update(t.1),
135                Rollup::Max => self.apply_update(t.2),
136            }
137        }
138
139        // Distributions — append most recent sequence if present.
140        if let Some(d) = &other.inner.distribution {
141            self.apply_update(d.last_sequence().as_slice());
142        }
143    }
144
145    #[inline(always)]
146    pub fn apply_update<'a>(&mut self, update: impl Into<MetricUpdate<'a>>) {
147        let update = update.into();
148        match update {
149            MetricUpdate::Float(value) => {
150                if let Some(stat) = &mut self.inner.value_statistic {
151                    stat.add(value);
152                } else {
153                    self.inner.value_statistic = Some(Statistic::from(value));
154                }
155            }
156            MetricUpdate::Usize(value) => {
157                if let Some(stat) = &mut self.inner.value_statistic {
158                    stat.add(value as f32);
159                } else {
160                    self.inner.value_statistic = Some(Statistic::from(value as f32));
161                }
162            }
163            MetricUpdate::Duration(value) => {
164                if let Some(stat) = &mut self.inner.time_statistic {
165                    stat.add(value);
166                } else {
167                    self.inner.time_statistic = Some(TimeStatistic::from(value));
168                }
169            }
170            MetricUpdate::Distribution(values) => {
171                if let Some(stat) = &mut self.inner.distribution {
172                    stat.add(values);
173                } else {
174                    self.inner.distribution = Some(Distribution::from(values));
175                }
176            }
177            MetricUpdate::FloatOperation(value, time) => {
178                if let Some(stat) = &mut self.inner.value_statistic {
179                    stat.add(value);
180                } else {
181                    self.inner.value_statistic = Some(Statistic::from(value));
182                }
183
184                if let Some(time_stat) = &mut self.inner.time_statistic {
185                    time_stat.add(time);
186                } else {
187                    self.inner.time_statistic = Some(TimeStatistic::from(time));
188                }
189            }
190            MetricUpdate::UsizeOperation(value, time) => {
191                if let Some(stat) = &mut self.inner.value_statistic {
192                    stat.add(value as f32);
193                } else {
194                    self.inner.value_statistic = Some(Statistic::from(value as f32));
195                }
196
197                if let Some(time_stat) = &mut self.inner.time_statistic {
198                    time_stat.add(time);
199                } else {
200                    self.inner.time_statistic = Some(TimeStatistic::from(time));
201                }
202            }
203            MetricUpdate::DistributionRef(values) => {
204                if let Some(stat) = &mut self.inner.distribution {
205                    stat.add(values);
206                } else {
207                    self.inner.distribution = Some(Distribution::from(values.as_slice()));
208                }
209            }
210            MetricUpdate::DistributionOwned(values) => {
211                if let Some(stat) = &mut self.inner.distribution {
212                    stat.add(&values);
213                } else {
214                    self.inner.distribution = Some(Distribution::from(values.as_slice()));
215                }
216            }
217        }
218    }
219
220    ///
221    /// --- Common statistic getters ---
222    ///
223    pub fn name(&self) -> &'static str {
224        self.name
225    }
226
227    pub fn last_value(&self) -> f32 {
228        self.inner
229            .value_statistic
230            .as_ref()
231            .map_or(0.0, |stat| stat.last_value())
232    }
233
234    pub fn distribution(&self) -> Option<&Distribution> {
235        self.inner.distribution.as_ref()
236    }
237
238    pub fn statistic(&self) -> Option<&Statistic> {
239        self.inner.value_statistic.as_ref()
240    }
241
242    pub fn time_statistic(&self) -> Option<&TimeStatistic> {
243        self.inner.time_statistic.as_ref()
244    }
245
246    pub fn last_time(&self) -> Duration {
247        self.time_statistic()
248            .map_or(Duration::ZERO, |stat| stat.last_time())
249    }
250
251    pub fn count(&self) -> i32 {
252        self.statistic().map(|stat| stat.count()).unwrap_or(0)
253    }
254
255    ///
256    /// --- Get the value statistics ---
257    ///
258    pub fn value_mean(&self) -> Option<f32> {
259        self.statistic().map(|stat| stat.mean())
260    }
261
262    pub fn value_variance(&self) -> Option<f32> {
263        self.statistic().map(|stat| stat.variance())
264    }
265
266    pub fn value_std_dev(&self) -> Option<f32> {
267        self.statistic().map(|stat| stat.std_dev())
268    }
269
270    pub fn value_skewness(&self) -> Option<f32> {
271        self.statistic().map(|stat| stat.skewness())
272    }
273
274    pub fn value_min(&self) -> Option<f32> {
275        self.statistic().map(|stat| stat.min())
276    }
277
278    pub fn value_max(&self) -> Option<f32> {
279        self.statistic().map(|stat| stat.max())
280    }
281
282    ///
283    /// --- Get the time statistics ---
284    ///
285    pub fn time_mean(&self) -> Option<Duration> {
286        self.time_statistic().map(|stat| stat.mean())
287    }
288
289    pub fn time_variance(&self) -> Option<Duration> {
290        self.time_statistic().map(|stat| stat.variance())
291    }
292
293    pub fn time_std_dev(&self) -> Option<Duration> {
294        self.time_statistic().map(|stat| stat.standard_deviation())
295    }
296
297    pub fn time_min(&self) -> Option<Duration> {
298        self.time_statistic().map(|stat| stat.min())
299    }
300
301    pub fn time_max(&self) -> Option<Duration> {
302        self.time_statistic().map(|stat| stat.max())
303    }
304
305    pub fn time_sum(&self) -> Option<Duration> {
306        self.time_statistic().map(|stat| stat.sum())
307    }
308
309    ///
310    /// --- Get the distribution statistics ---
311    ///
312    pub fn last_sequence(&self) -> Option<&Vec<f32>> {
313        self.distribution().map(|dist| dist.last_sequence())
314    }
315
316    pub fn distribution_mean(&self) -> Option<f32> {
317        self.distribution().map(|dist| dist.mean())
318    }
319
320    pub fn distribution_variance(&self) -> Option<f32> {
321        self.distribution().map(|dist| dist.variance())
322    }
323
324    pub fn distribution_std_dev(&self) -> Option<f32> {
325        self.distribution().map(|dist| dist.standard_deviation())
326    }
327
328    pub fn distribution_skewness(&self) -> Option<f32> {
329        self.distribution().map(|dist| dist.skewness())
330    }
331
332    pub fn distribution_kurtosis(&self) -> Option<f32> {
333        self.distribution().map(|dist| dist.kurtosis())
334    }
335
336    pub fn distribution_min(&self) -> Option<f32> {
337        self.distribution().map(|dist| dist.min())
338    }
339
340    pub fn distribution_max(&self) -> Option<f32> {
341        self.distribution().map(|dist| dist.max())
342    }
343
344    pub fn distribution_entropy(&self) -> Option<f32> {
345        self.distribution().map(|dist| dist.entropy())
346    }
347}
348
349#[derive(Debug, Clone, PartialEq)]
350pub enum MetricUpdate<'a> {
351    Float(f32),
352    Usize(usize),
353    Duration(Duration),
354    Distribution(&'a [f32]),
355    DistributionRef(&'a Vec<f32>),
356    DistributionOwned(Vec<f32>),
357    FloatOperation(f32, Duration),
358    UsizeOperation(usize, Duration),
359}
360
361impl From<f32> for MetricUpdate<'_> {
362    fn from(value: f32) -> Self {
363        MetricUpdate::Float(value)
364    }
365}
366
367impl From<usize> for MetricUpdate<'_> {
368    fn from(value: usize) -> Self {
369        MetricUpdate::Usize(value)
370    }
371}
372
373impl From<Duration> for MetricUpdate<'_> {
374    fn from(value: Duration) -> Self {
375        MetricUpdate::Duration(value)
376    }
377}
378
379impl<'a> From<&'a [f32]> for MetricUpdate<'a> {
380    fn from(value: &'a [f32]) -> Self {
381        MetricUpdate::Distribution(value)
382    }
383}
384
385impl From<(f32, Duration)> for MetricUpdate<'_> {
386    fn from(value: (f32, Duration)) -> Self {
387        MetricUpdate::FloatOperation(value.0, value.1)
388    }
389}
390
391impl From<(usize, Duration)> for MetricUpdate<'_> {
392    fn from(value: (usize, Duration)) -> Self {
393        MetricUpdate::UsizeOperation(value.0, value.1)
394    }
395}
396
397impl<'a> From<&'a Vec<f32>> for MetricUpdate<'a> {
398    fn from(value: &'a Vec<f32>) -> Self {
399        MetricUpdate::DistributionRef(value)
400    }
401}
402
403impl From<Vec<f32>> for MetricUpdate<'_> {
404    fn from(value: Vec<f32>) -> Self {
405        MetricUpdate::DistributionOwned(value)
406    }
407}
408
409impl std::fmt::Debug for Metric {
410    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411        write!(f, "Metric {{ name: {}, }}", self.name)
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418
419    #[test]
420    fn test_metric() {
421        let mut metric = Metric::new("test");
422        metric.apply_update(1.0);
423        metric.apply_update(2.0);
424        metric.apply_update(3.0);
425        metric.apply_update(4.0);
426        metric.apply_update(5.0);
427
428        assert_eq!(metric.count(), 5);
429        assert_eq!(metric.last_value(), 5.0);
430        assert_eq!(metric.value_mean().unwrap(), 3.0);
431        assert_eq!(metric.value_variance().unwrap(), 2.5);
432        assert_eq!(metric.value_std_dev().unwrap(), 1.5811388);
433        assert_eq!(metric.value_min().unwrap(), 1.0);
434        assert_eq!(metric.value_max().unwrap(), 5.0);
435        assert_eq!(metric.name(), "test");
436    }
437
438    #[test]
439    fn test_metric_labels() {
440        let mut metric = Metric::new("test");
441
442        metric.apply_update(1.0);
443        metric.apply_update(2.0);
444        metric.apply_update(3.0);
445        metric.apply_update(4.0);
446        metric.apply_update(5.0);
447
448        assert_eq!(metric.count(), 5);
449        assert_eq!(metric.last_value(), 5.0);
450        assert_eq!(metric.value_mean().unwrap(), 3.0);
451        assert_eq!(metric.value_variance().unwrap(), 2.5);
452        assert_eq!(metric.value_std_dev().unwrap(), 1.5811388);
453        assert_eq!(metric.value_min().unwrap(), 1.0);
454        assert_eq!(metric.value_max().unwrap(), 5.0);
455    }
456}