open_metrics_client/metrics/
exemplar.rs

1//! Module implementing an Open Metrics exemplars for counters and histograms.
2//!
3//! See [`CounterWithExemplar`] and [`HistogramWithExemplars`] for details.
4
5use super::counter::{self, Counter};
6use super::histogram::Histogram;
7use owning_ref::OwningRef;
8use std::collections::HashMap;
9use std::sync::atomic::AtomicU64;
10use std::sync::{Arc, RwLock, RwLockReadGuard};
11
12pub struct Exemplar<S, V> {
13    pub(crate) label_set: S,
14    pub(crate) value: V,
15}
16
17/////////////////////////////////////////////////////////////////////////////////
18// Counter
19
20/// Open Metrics [`Counter`] with an [`Exemplar`] to both measure discrete
21/// events and track references to data outside of the metric set.
22///
23/// ```
24/// # use open_metrics_client::metrics::exemplar::CounterWithExemplar;
25/// let counter_with_exemplar = CounterWithExemplar::<Vec<(String, String)>>::default();
26/// counter_with_exemplar.inc_by(1, Some(vec![("user_id".to_string(), "42".to_string())]));
27/// let _value: (u64, _) = counter_with_exemplar.get();
28/// ```
29pub struct CounterWithExemplar<S, N = u64, A = AtomicU64> {
30    pub(crate) inner: Arc<RwLock<CounterWithExemplarInner<S, N, A>>>,
31}
32
33impl<S, N, A> Clone for CounterWithExemplar<S, N, A> {
34    fn clone(&self) -> Self {
35        CounterWithExemplar {
36            inner: self.inner.clone(),
37        }
38    }
39}
40
41pub struct CounterWithExemplarInner<S, N, A> {
42    pub(crate) exemplar: Option<Exemplar<S, N>>,
43    pub(crate) counter: Counter<N, A>,
44}
45
46impl<S, N, A: Default> Default for CounterWithExemplar<S, N, A> {
47    fn default() -> Self {
48        Self {
49            inner: Arc::new(RwLock::new(CounterWithExemplarInner {
50                exemplar: None,
51                counter: Default::default(),
52            })),
53        }
54    }
55}
56
57impl<S, N: Clone, A: counter::Atomic<N>> CounterWithExemplar<S, N, A> {
58    // TODO: Implement `fn inc`. Problematic right now as one can not produce
59    // value `1` of type `N`.
60
61    /// Increase the [`CounterWithExemplar`] by `v`, updating the [`Exemplar`]
62    /// if a label set is provided, returning the previous value.
63    pub fn inc_by(&self, v: N, label_set: Option<S>) -> N {
64        let mut inner = self.inner.write().expect("Lock not to be poisoned.");
65
66        inner.exemplar = label_set.map(|label_set| Exemplar {
67            label_set,
68            value: v.clone(),
69        });
70
71        inner.counter.inc_by(v)
72    }
73
74    /// Get the current value of the [`CounterWithExemplar`] as well as its
75    /// [`Exemplar`] if any.
76    pub fn get(&self) -> (N, RwLockGuardedCounterWithExemplar<S, N, A>) {
77        let inner = self.inner.read().expect("Lock not to be poisoned.");
78        let value = inner.counter.get();
79        let exemplar = OwningRef::new(inner).map(|inner| &inner.exemplar);
80        (value, exemplar)
81    }
82
83    /// Exposes the inner atomic type of the [`CounterWithExemplar`].
84    ///
85    /// This should only be used for advanced use-cases which are not directly
86    /// supported by the library.
87    ///
88    /// The caller of this function has to uphold the property of an Open
89    /// Metrics counter namely that the value is monotonically increasing, i.e.
90    /// either stays the same or increases.
91    pub fn inner(&self) -> OwningRef<RwLockReadGuard<CounterWithExemplarInner<S, N, A>>, A> {
92        OwningRef::new(self.inner.read().expect("Lock not to be poisoned."))
93            .map(|inner| inner.counter.inner())
94    }
95}
96
97type RwLockGuardedCounterWithExemplar<'a, S, N, A> =
98    OwningRef<RwLockReadGuard<'a, CounterWithExemplarInner<S, N, A>>, Option<Exemplar<S, N>>>;
99
100/////////////////////////////////////////////////////////////////////////////////
101// Histogram
102
103/// Open Metrics [`Histogram`] to both measure distributions of discrete events.
104/// and track references to data outside of the metric set.
105///
106/// ```
107/// # use open_metrics_client::metrics::exemplar::HistogramWithExemplars;
108/// # use open_metrics_client::metrics::histogram::exponential_buckets;
109/// let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10));
110/// histogram.observe(4.2, Some(vec![("user_id".to_string(), "42".to_string())]));
111/// ```
112pub struct HistogramWithExemplars<S> {
113    // TODO: Not ideal, as Histogram has a Mutex as well.
114    pub(crate) inner: Arc<RwLock<HistogramWithExemplarsInner<S>>>,
115}
116
117impl<S> Clone for HistogramWithExemplars<S> {
118    fn clone(&self) -> Self {
119        Self {
120            inner: self.inner.clone(),
121        }
122    }
123}
124
125pub struct HistogramWithExemplarsInner<S> {
126    pub(crate) exemplars: HashMap<usize, Exemplar<S, f64>>,
127    pub(crate) histogram: Histogram,
128}
129
130impl<S> HistogramWithExemplars<S> {
131    pub fn new(buckets: impl Iterator<Item = f64>) -> Self {
132        Self {
133            inner: Arc::new(RwLock::new(HistogramWithExemplarsInner {
134                exemplars: Default::default(),
135                histogram: Histogram::new(buckets),
136            })),
137        }
138    }
139
140    pub fn observe(&self, v: f64, label_set: Option<S>) {
141        let mut inner = self.inner.write().expect("Lock not to be poisoned.");
142        let bucket = inner.histogram.observe_and_bucket(v);
143        if let (Some(bucket), Some(label_set)) = (bucket, label_set) {
144            inner.exemplars.insert(
145                bucket,
146                Exemplar {
147                    label_set,
148                    value: v,
149                },
150            );
151        }
152    }
153
154    pub(crate) fn inner(&self) -> RwLockReadGuard<HistogramWithExemplarsInner<S>> {
155        self.inner.read().expect("Lock not to be poisoned.")
156    }
157}