metriki_core/metrics/
gauge.rs

1use std::fmt;
2use std::sync::Mutex;
3use std::time::{Duration, Instant};
4
5#[cfg(feature = "ser")]
6use serde::ser::SerializeMap;
7#[cfg(feature = "ser")]
8use serde::{Serialize, Serializer};
9
10/// Gauge value source that returns `f64`.
11pub trait GaugeFn: Send + Sync {
12    fn value(&self) -> f64;
13}
14
15impl<F: Fn() -> f64 + Send + Sync> GaugeFn for F {
16    fn value(&self) -> f64 {
17        self()
18    }
19}
20
21/// Gauges are used to measure the instantaneous value of something.
22pub struct Gauge {
23    func: Box<dyn GaugeFn>,
24}
25
26impl Gauge {
27    pub(crate) fn new(f: Box<dyn GaugeFn>) -> Gauge {
28        Gauge { func: f }
29    }
30
31    pub fn value(&self) -> f64 {
32        self.func.value()
33    }
34}
35
36impl fmt::Debug for Gauge {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
38        f.debug_struct("Gauge").finish()
39    }
40}
41
42#[cfg(feature = "ser")]
43impl Serialize for Gauge {
44    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45    where
46        S: Serializer,
47    {
48        let mut map = serializer.serialize_map(Some(1))?;
49        map.serialize_entry("value", &self.value())?;
50        map.end()
51    }
52}
53
54struct Cache<V> {
55    expiry: Instant,
56    value: V,
57}
58
59impl<V> Cache<V> {
60    fn expired(&self) -> bool {
61        self.expiry < Instant::now()
62    }
63
64    fn value(&self) -> &V {
65        &self.value
66    }
67}
68
69/// Gauge implementation that caches the result for given ttl.
70///
71/// This is designed for gauge functions that are expensive to call.
72///
73/// ```
74/// # use std::time::Duration;
75/// # use metriki_core::global::global_registry;
76/// # use metriki_core::metrics::CachedGauge;
77///
78/// global_registry().gauge("gauge_name", CachedGauge::boxed(Box::new(||
79///     // gauge function that returns a value
80///     42f64
81/// ), Duration::from_secs(60)));
82/// ```
83pub struct CachedGauge {
84    func: Box<dyn GaugeFn>,
85    cache: Mutex<Option<Cache<f64>>>,
86    ttl: Duration,
87}
88
89impl CachedGauge {
90    /// Create `CachedGauge` with gauge function and given ttl.
91    pub fn boxed(func: Box<dyn GaugeFn>, ttl: Duration) -> Box<CachedGauge> {
92        Box::new(CachedGauge {
93            func,
94            ttl,
95            cache: Mutex::new(None),
96        })
97    }
98}
99
100impl GaugeFn for CachedGauge {
101    fn value(&self) -> f64 {
102        let mut cache = self.cache.lock().unwrap();
103
104        if let Some(ref cache_inner) = *cache {
105            if !cache_inner.expired() {
106                return *cache_inner.value();
107            }
108        }
109
110        let value = self.func.value();
111        let new_cache = Cache {
112            expiry: Instant::now() + self.ttl,
113            value,
114        };
115
116        *cache = Some(new_cache);
117
118        value
119    }
120}
121
122/// A Gauge that holds a constant value
123pub struct StaticGauge(pub f64);
124
125impl GaugeFn for StaticGauge {
126    fn value(&self) -> f64 {
127        self.0
128    }
129}