prometheus_client/metrics/
counter.rs

1//! Module implementing an Open Metrics counter.
2//!
3//! See [`Counter`] for details.
4
5use crate::encoding::{EncodeMetric, MetricEncoder, NoLabelSet};
6
7use super::{MetricType, TypedMetric};
8use std::marker::PhantomData;
9#[cfg(target_has_atomic = "64")]
10use std::sync::atomic::AtomicU64;
11use std::sync::atomic::{AtomicU32, Ordering};
12use std::sync::Arc;
13
14/// Open Metrics [`Counter`] to measure discrete events.
15///
16/// Single monotonically increasing value metric.
17///
18/// [`Counter`] is generic over the actual data type tracking the [`Counter`]
19/// state as well as the data type used to interact with the [`Counter`]. Out of
20/// convenience the generic type parameters are set to use an [`AtomicU64`] as a
21/// storage and [`u64`] on the interface by default.
22///
23/// # Examples
24///
25/// ## Using [`AtomicU64`] as storage and [`u64`] on the interface
26///
27/// ```
28/// # use prometheus_client::metrics::counter::Counter;
29/// let counter: Counter = Counter::default();
30/// counter.inc();
31/// let _value: u64 = counter.get();
32/// ```
33///
34/// ## Using [`AtomicU64`] as storage and [`f64`] on the interface
35///
36/// ```
37/// # use prometheus_client::metrics::counter::Counter;
38/// # use std::sync::atomic::AtomicU64;
39/// let counter = Counter::<f64, AtomicU64>::default();
40/// counter.inc();
41/// let _value: f64 = counter.get();
42/// ```
43#[cfg(target_has_atomic = "64")]
44#[derive(Debug)]
45pub struct Counter<N = u64, A = AtomicU64> {
46    value: Arc<A>,
47    phantom: PhantomData<N>,
48}
49
50/// Open Metrics [`Counter`] to measure discrete events.
51#[cfg(not(target_has_atomic = "64"))]
52#[derive(Debug)]
53pub struct Counter<N = u32, A = AtomicU32> {
54    value: Arc<A>,
55    phantom: PhantomData<N>,
56}
57
58impl<N, A> Clone for Counter<N, A> {
59    fn clone(&self) -> Self {
60        Self {
61            value: self.value.clone(),
62            phantom: PhantomData,
63        }
64    }
65}
66
67impl<N, A: Default> Default for Counter<N, A> {
68    fn default() -> Self {
69        Counter {
70            value: Arc::new(A::default()),
71            phantom: PhantomData,
72        }
73    }
74}
75
76impl<N, A: Atomic<N>> Counter<N, A> {
77    /// Increase the [`Counter`] by 1, returning the previous value.
78    pub fn inc(&self) -> N {
79        self.value.inc()
80    }
81
82    /// Increase the [`Counter`] by `v`, returning the previous value.
83    pub fn inc_by(&self, v: N) -> N {
84        self.value.inc_by(v)
85    }
86
87    /// Get the current value of the [`Counter`].
88    pub fn get(&self) -> N {
89        self.value.get()
90    }
91
92    /// Exposes the inner atomic type of the [`Counter`].
93    ///
94    /// This should only be used for advanced use-cases which are not directly
95    /// supported by the library.
96    ///
97    /// The caller of this function has to uphold the property of an Open
98    /// Metrics counter namely that the value is monotonically increasing, i.e.
99    /// either stays the same or increases.
100    pub fn inner(&self) -> &A {
101        &self.value
102    }
103}
104
105/// Atomic operations for a [`Counter`] value store.
106pub trait Atomic<N> {
107    /// Increase the value by `1`.
108    fn inc(&self) -> N;
109
110    /// Increase the value.
111    fn inc_by(&self, v: N) -> N;
112
113    /// Get the the value.
114    fn get(&self) -> N;
115}
116
117#[cfg(target_has_atomic = "64")]
118impl Atomic<u64> for AtomicU64 {
119    fn inc(&self) -> u64 {
120        self.inc_by(1)
121    }
122
123    fn inc_by(&self, v: u64) -> u64 {
124        self.fetch_add(v, Ordering::Relaxed)
125    }
126
127    fn get(&self) -> u64 {
128        self.load(Ordering::Relaxed)
129    }
130}
131
132impl Atomic<u32> for AtomicU32 {
133    fn inc(&self) -> u32 {
134        self.inc_by(1)
135    }
136
137    fn inc_by(&self, v: u32) -> u32 {
138        self.fetch_add(v, Ordering::Relaxed)
139    }
140
141    fn get(&self) -> u32 {
142        self.load(Ordering::Relaxed)
143    }
144}
145
146#[cfg(target_has_atomic = "64")]
147impl Atomic<f64> for AtomicU64 {
148    fn inc(&self) -> f64 {
149        self.inc_by(1.0)
150    }
151
152    fn inc_by(&self, v: f64) -> f64 {
153        let mut old_u64 = self.load(Ordering::Relaxed);
154        let mut old_f64;
155        loop {
156            old_f64 = f64::from_bits(old_u64);
157            let new = f64::to_bits(old_f64 + v);
158            match self.compare_exchange_weak(old_u64, new, Ordering::Relaxed, Ordering::Relaxed) {
159                Ok(_) => break,
160                Err(x) => old_u64 = x,
161            }
162        }
163
164        old_f64
165    }
166
167    fn get(&self) -> f64 {
168        f64::from_bits(self.load(Ordering::Relaxed))
169    }
170}
171
172impl Atomic<f32> for AtomicU32 {
173    fn inc(&self) -> f32 {
174        self.inc_by(1.0)
175    }
176
177    fn inc_by(&self, v: f32) -> f32 {
178        let mut old_u32 = self.load(Ordering::Relaxed);
179        let mut old_f32;
180        loop {
181            old_f32 = f32::from_bits(old_u32);
182            let new = f32::to_bits(old_f32 + v);
183            match self.compare_exchange_weak(old_u32, new, Ordering::Relaxed, Ordering::Relaxed) {
184                Ok(_) => break,
185                Err(x) => old_u32 = x,
186            }
187        }
188
189        old_f32
190    }
191
192    fn get(&self) -> f32 {
193        f32::from_bits(self.load(Ordering::Relaxed))
194    }
195}
196
197impl<N, A> TypedMetric for Counter<N, A> {
198    const TYPE: MetricType = MetricType::Counter;
199}
200
201impl<N, A> EncodeMetric for Counter<N, A>
202where
203    N: crate::encoding::EncodeCounterValue,
204    A: Atomic<N>,
205{
206    fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
207        encoder.encode_counter::<NoLabelSet, _, u64>(&self.get(), None)
208    }
209
210    fn metric_type(&self) -> MetricType {
211        Self::TYPE
212    }
213}
214
215/// As a [`Counter`], but constant, meaning it cannot change once created.
216///
217/// Needed for advanced use-cases, e.g. in combination with [`Collector`](crate::collector::Collector).
218#[derive(Debug, Default)]
219pub struct ConstCounter<N = u64> {
220    value: N,
221}
222
223impl<N> ConstCounter<N> {
224    /// Creates a new [`ConstCounter`].
225    pub fn new(value: N) -> Self {
226        Self { value }
227    }
228}
229
230impl<N> TypedMetric for ConstCounter<N> {
231    const TYPE: MetricType = MetricType::Counter;
232}
233
234impl<N> EncodeMetric for ConstCounter<N>
235where
236    N: crate::encoding::EncodeCounterValue,
237{
238    fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
239        encoder.encode_counter::<NoLabelSet, _, u64>(&self.value, None)
240    }
241
242    fn metric_type(&self) -> MetricType {
243        Self::TYPE
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use quickcheck::QuickCheck;
251
252    #[test]
253    fn inc_and_get() {
254        let counter: Counter = Counter::default();
255        assert_eq!(0, counter.inc());
256        assert_eq!(1, counter.get());
257    }
258
259    #[cfg(target_has_atomic = "64")]
260    #[test]
261    fn f64_stored_in_atomic_u64() {
262        fn prop(fs: Vec<f64>) {
263            let fs: Vec<f64> = fs
264                .into_iter()
265                // Map infinite, subnormal and NaN to 0.0.
266                .map(|f| if f.is_normal() { f } else { 0.0 })
267                .collect();
268            let sum = fs.iter().sum();
269            let counter = Counter::<f64, AtomicU64>::default();
270            for f in fs {
271                counter.inc_by(f);
272            }
273            assert_eq!(counter.get(), sum)
274        }
275
276        QuickCheck::new().tests(10).quickcheck(prop as fn(_))
277    }
278}