open_metrics_client/metrics/
gauge.rs

1//! Module implementing an Open Metrics gauge.
2//!
3//! See [`Gauge`] for details.
4
5use super::{MetricType, TypedMetric};
6use std::marker::PhantomData;
7use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
8use std::sync::Arc;
9
10/// Open Metrics [`Gauge`] to record current measurements.
11///
12/// Single increasing, decreasing or constant value metric.
13///
14/// [`Gauge`] is generic over the actual data type tracking the [`Gauge`] state
15/// as well as the data type used to interact with the [`Gauge`]. Out of
16/// convenience the generic type parameters are set to use an [`AtomicU64`] as a
17/// storage and [`u64`] on the interface by default.
18///
19/// # Examples
20///
21/// ## Using [`AtomicU64`] as storage and [`u64`] on the interface
22///
23/// ```
24/// # use open_metrics_client::metrics::gauge::Gauge;
25/// let gauge: Gauge = Gauge::default();
26/// gauge.set(42u64);
27/// let _value: u64 = gauge.get();
28/// ```
29///
30/// ## Using [`AtomicU64`] as storage and [`f64`] on the interface
31///
32/// ```
33/// # use open_metrics_client::metrics::gauge::Gauge;
34/// # use std::sync::atomic::AtomicU64;
35/// let gauge = Gauge::<f64, AtomicU64>::default();
36/// gauge.set(42.0);
37/// let _value: f64 = gauge.get();
38/// ```
39pub struct Gauge<N = u64, A = AtomicU64> {
40    value: Arc<A>,
41    phantom: PhantomData<N>,
42}
43
44impl<N, A> Clone for Gauge<N, A> {
45    fn clone(&self) -> Self {
46        Self {
47            value: self.value.clone(),
48            phantom: PhantomData,
49        }
50    }
51}
52
53impl<N, A: Default> Default for Gauge<N, A> {
54    fn default() -> Self {
55        Self {
56            value: Arc::new(A::default()),
57            phantom: PhantomData,
58        }
59    }
60}
61
62impl<N, A: Atomic<N>> Gauge<N, A> {
63    /// Increase the [`Gauge`] by 1, returning the previous value.
64    pub fn inc(&self) -> N {
65        self.value.inc()
66    }
67
68    /// Increase the [`Gauge`] by `v`, returning the previous value.
69    pub fn inc_by(&self, v: N) -> N {
70        self.value.inc_by(v)
71    }
72
73    /// Decrease the [`Gauge`] by 1, returning the previous value.
74    pub fn dec(&self) -> N {
75        self.value.dec()
76    }
77
78    /// Decrease the [`Gauge`] by `v`, returning the previous value.
79    pub fn dec_by(&self, v: N) -> N {
80        self.value.dec_by(v)
81    }
82
83    /// Sets the [`Gauge`] to `v`, returning the previous value.
84    pub fn set(&self, v: N) -> N {
85        self.value.set(v)
86    }
87
88    /// Get the current value of the [`Gauge`].
89    pub fn get(&self) -> N {
90        self.value.get()
91    }
92
93    /// Exposes the inner atomic type of the [`Gauge`].
94    ///
95    /// This should only be used for advanced use-cases which are not directly
96    /// supported by the library.
97    pub fn inner(&self) -> &A {
98        &self.value
99    }
100}
101
102pub trait Atomic<N> {
103    fn inc(&self) -> N;
104
105    fn inc_by(&self, v: N) -> N;
106
107    fn dec(&self) -> N;
108
109    fn dec_by(&self, v: N) -> N;
110
111    fn set(&self, v: N) -> N;
112
113    fn get(&self) -> N;
114}
115
116impl Atomic<u64> for AtomicU64 {
117    fn inc(&self) -> u64 {
118        self.inc_by(1)
119    }
120
121    fn inc_by(&self, v: u64) -> u64 {
122        self.fetch_add(v, Ordering::Relaxed)
123    }
124
125    fn dec(&self) -> u64 {
126        self.dec_by(1)
127    }
128
129    fn dec_by(&self, v: u64) -> u64 {
130        self.fetch_sub(v, Ordering::Relaxed)
131    }
132
133    fn set(&self, v: u64) -> u64 {
134        self.swap(v, Ordering::Relaxed)
135    }
136
137    fn get(&self) -> u64 {
138        self.load(Ordering::Relaxed)
139    }
140}
141
142impl Atomic<u32> for AtomicU32 {
143    fn inc(&self) -> u32 {
144        self.inc_by(1)
145    }
146
147    fn inc_by(&self, v: u32) -> u32 {
148        self.fetch_add(v, Ordering::Relaxed)
149    }
150
151    fn dec(&self) -> u32 {
152        self.dec_by(1)
153    }
154
155    fn dec_by(&self, v: u32) -> u32 {
156        self.fetch_sub(v, Ordering::Relaxed)
157    }
158
159    fn set(&self, v: u32) -> u32 {
160        self.swap(v, Ordering::Relaxed)
161    }
162
163    fn get(&self) -> u32 {
164        self.load(Ordering::Relaxed)
165    }
166}
167
168impl Atomic<f64> for AtomicU64 {
169    fn inc(&self) -> f64 {
170        self.inc_by(1.0)
171    }
172
173    fn inc_by(&self, v: f64) -> f64 {
174        let mut old_u64 = self.load(Ordering::Relaxed);
175        let mut old_f64;
176        loop {
177            old_f64 = f64::from_bits(old_u64);
178            let new = f64::to_bits(old_f64 + v);
179            match self.compare_exchange_weak(old_u64, new, Ordering::Relaxed, Ordering::Relaxed) {
180                Ok(_) => break,
181                Err(x) => old_u64 = x,
182            }
183        }
184
185        old_f64
186    }
187
188    fn dec(&self) -> f64 {
189        self.dec_by(1.0)
190    }
191
192    fn dec_by(&self, v: f64) -> f64 {
193        let mut old_u64 = self.load(Ordering::Relaxed);
194        let mut old_f64;
195        loop {
196            old_f64 = f64::from_bits(old_u64);
197            let new = f64::to_bits(old_f64 - v);
198            match self.compare_exchange_weak(old_u64, new, Ordering::Relaxed, Ordering::Relaxed) {
199                Ok(_) => break,
200                Err(x) => old_u64 = x,
201            }
202        }
203
204        old_f64
205    }
206
207    fn set(&self, v: f64) -> f64 {
208        f64::from_bits(self.swap(f64::to_bits(v), Ordering::Relaxed))
209    }
210
211    fn get(&self) -> f64 {
212        f64::from_bits(self.load(Ordering::Relaxed))
213    }
214}
215
216impl<N, A> TypedMetric for Gauge<N, A> {
217    const TYPE: MetricType = MetricType::Gauge;
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn inc_dec_and_get() {
226        let gauge: Gauge = Gauge::default();
227        assert_eq!(0, gauge.inc());
228        assert_eq!(1, gauge.get());
229
230        assert_eq!(1, gauge.dec());
231        assert_eq!(0, gauge.get());
232
233        assert_eq!(0, gauge.set(10));
234        assert_eq!(10, gauge.get());
235    }
236}