radiate_core/stats/
metrics.rs

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