open_metrics_client/metrics/
counter.rs

1//! Module implementing an Open Metrics counter.
2//!
3//! See [`Counter`] 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 [`Counter`] to measure discrete events.
11///
12/// Single monotonically increasing value metric.
13///
14/// [`Counter`] is generic over the actual data type tracking the [`Counter`]
15/// state as well as the data type used to interact with the [`Counter`]. 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::counter::Counter;
25/// let counter: Counter = Counter::default();
26/// counter.inc();
27/// let _value: u64 = counter.get();
28/// ```
29///
30/// ## Using [`AtomicU64`] as storage and [`f64`] on the interface
31///
32/// ```
33/// # use open_metrics_client::metrics::counter::Counter;
34/// # use std::sync::atomic::AtomicU64;
35/// let counter = Counter::<f64, AtomicU64>::default();
36/// counter.inc();
37/// let _value: f64 = counter.get();
38/// ```
39pub struct Counter<N = u64, A = AtomicU64> {
40    value: Arc<A>,
41    phantom: PhantomData<N>,
42}
43
44impl<N, A> Clone for Counter<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 Counter<N, A> {
54    fn default() -> Self {
55        Counter {
56            value: Arc::new(A::default()),
57            phantom: PhantomData,
58        }
59    }
60}
61
62impl<N, A: Atomic<N>> Counter<N, A> {
63    /// Increase the [`Counter`] by 1, returning the previous value.
64    pub fn inc(&self) -> N {
65        self.value.inc()
66    }
67
68    /// Increase the [`Counter`] by `v`, returning the previous value.
69    pub fn inc_by(&self, v: N) -> N {
70        self.value.inc_by(v)
71    }
72
73    /// Get the current value of the [`Counter`].
74    pub fn get(&self) -> N {
75        self.value.get()
76    }
77
78    /// Exposes the inner atomic type of the [`Counter`].
79    ///
80    /// This should only be used for advanced use-cases which are not directly
81    /// supported by the library.
82    ///
83    /// The caller of this function has to uphold the property of an Open
84    /// Metrics counter namely that the value is monotonically increasing, i.e.
85    /// either stays the same or increases.
86    pub fn inner(&self) -> &A {
87        &self.value
88    }
89}
90
91pub trait Atomic<N> {
92    fn inc(&self) -> N;
93
94    fn inc_by(&self, v: N) -> N;
95
96    fn get(&self) -> N;
97}
98
99impl Atomic<u64> for AtomicU64 {
100    fn inc(&self) -> u64 {
101        self.inc_by(1)
102    }
103
104    fn inc_by(&self, v: u64) -> u64 {
105        self.fetch_add(v, Ordering::Relaxed)
106    }
107
108    fn get(&self) -> u64 {
109        self.load(Ordering::Relaxed)
110    }
111}
112
113impl Atomic<u32> for AtomicU32 {
114    fn inc(&self) -> u32 {
115        self.inc_by(1)
116    }
117
118    fn inc_by(&self, v: u32) -> u32 {
119        self.fetch_add(v, Ordering::Relaxed)
120    }
121
122    fn get(&self) -> u32 {
123        self.load(Ordering::Relaxed)
124    }
125}
126
127impl Atomic<f64> for AtomicU64 {
128    fn inc(&self) -> f64 {
129        self.inc_by(1.0)
130    }
131
132    fn inc_by(&self, v: f64) -> f64 {
133        let mut old_u64 = self.load(Ordering::Relaxed);
134        let mut old_f64;
135        loop {
136            old_f64 = f64::from_bits(old_u64);
137            let new = f64::to_bits(old_f64 + v);
138            match self.compare_exchange_weak(old_u64, new, Ordering::Relaxed, Ordering::Relaxed) {
139                Ok(_) => break,
140                Err(x) => old_u64 = x,
141            }
142        }
143
144        old_f64
145    }
146
147    fn get(&self) -> f64 {
148        f64::from_bits(self.load(Ordering::Relaxed))
149    }
150}
151
152impl<N, A> TypedMetric for Counter<N, A> {
153    const TYPE: MetricType = MetricType::Counter;
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use quickcheck::QuickCheck;
160
161    #[test]
162    fn inc_and_get() {
163        let counter: Counter = Counter::default();
164        assert_eq!(0, counter.inc());
165        assert_eq!(1, counter.get());
166    }
167
168    #[test]
169    fn f64_stored_in_atomic_u64() {
170        fn prop(fs: Vec<f64>) {
171            let fs: Vec<f64> = fs
172                .into_iter()
173                // Map infinite, subnormal and NaN to 0.0.
174                .map(|f| if f.is_normal() { f } else { 0.0 })
175                .collect();
176            let sum = fs.iter().sum();
177            let counter = Counter::<f64, AtomicU64>::default();
178            for f in fs {
179                counter.inc_by(f);
180            }
181            assert_eq!(counter.get(), sum)
182        }
183
184        QuickCheck::new().tests(10).quickcheck(prop as fn(_))
185    }
186}