prometheus_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 crate::encoding::{
6    EncodeCounterValue, EncodeExemplarValue, EncodeLabelSet, EncodeMetric, MetricEncoder,
7};
8
9use super::counter::{self, Counter};
10use super::histogram::Histogram;
11use super::{MetricType, TypedMetric};
12use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
13use std::collections::HashMap;
14#[cfg(not(target_has_atomic = "64"))]
15use std::sync::atomic::AtomicU32;
16#[cfg(target_has_atomic = "64")]
17use std::sync::atomic::AtomicU64;
18use std::sync::Arc;
19use std::time::SystemTime;
20
21/// An OpenMetrics exemplar.
22#[derive(Debug)]
23pub struct Exemplar<S, V> {
24    pub(crate) label_set: S,
25    pub(crate) value: V,
26    pub(crate) timestamp: Option<SystemTime>,
27}
28
29/////////////////////////////////////////////////////////////////////////////////
30// Counter
31
32/// Open Metrics [`Counter`] with an [`Exemplar`] to both measure discrete
33/// events and track references to data outside of the metric set.
34///
35/// ```
36/// # use prometheus_client::metrics::exemplar::CounterWithExemplar;
37/// let counter_with_exemplar = CounterWithExemplar::<Vec<(String, String)>>::default();
38/// counter_with_exemplar.inc_by(1, Some(vec![("user_id".to_string(), "42".to_string())]), None);
39/// let _value: (u64, _) = counter_with_exemplar.get();
40/// ```
41/// You can also use exemplars with families. Just wrap the exemplar in a Family.
42/// ```
43/// # use prometheus_client::metrics::exemplar::CounterWithExemplar;
44/// # use prometheus_client::metrics::histogram::exponential_buckets;
45/// # use prometheus_client::metrics::family::Family;
46/// # use prometheus_client_derive_encode::EncodeLabelSet;
47/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)]
48/// pub struct ResultLabel {
49///     pub result: String,
50/// }
51///
52/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)]
53/// pub struct TraceLabel {
54///     pub trace_id: String,
55/// }
56///
57/// let latency: Family<ResultLabel, CounterWithExemplar<TraceLabel>> = Family::default();
58///
59/// latency
60///     .get_or_create(&ResultLabel {
61///         result: "success".to_owned(),
62///     })
63///     .inc_by(
64///         1,
65///         Some(TraceLabel {
66///             trace_id: "3a2f90c9f80b894f".to_owned(),
67///         }),
68///         None,
69///     );
70/// ```
71#[cfg(target_has_atomic = "64")]
72#[derive(Debug)]
73pub struct CounterWithExemplar<S, N = u64, A = AtomicU64> {
74    pub(crate) inner: Arc<RwLock<CounterWithExemplarInner<S, N, A>>>,
75}
76
77impl<S> TypedMetric for CounterWithExemplar<S> {
78    const TYPE: MetricType = MetricType::Counter;
79}
80
81/// Open Metrics [`Counter`] with an [`Exemplar`] to both measure discrete
82/// events and track references to data outside of the metric set.
83#[cfg(not(target_has_atomic = "64"))]
84#[derive(Debug)]
85pub struct CounterWithExemplar<S, N = u32, A = AtomicU32> {
86    pub(crate) inner: Arc<RwLock<CounterWithExemplarInner<S, N, A>>>,
87}
88
89impl<S, N, A> Clone for CounterWithExemplar<S, N, A> {
90    fn clone(&self) -> Self {
91        CounterWithExemplar {
92            inner: self.inner.clone(),
93        }
94    }
95}
96
97/// An OpenMetrics [`Counter`] in combination with an OpenMetrics [`Exemplar`].
98#[derive(Debug)]
99pub struct CounterWithExemplarInner<S, N, A> {
100    pub(crate) exemplar: Option<Exemplar<S, N>>,
101    pub(crate) counter: Counter<N, A>,
102}
103
104impl<S, N, A: Default> Default for CounterWithExemplar<S, N, A> {
105    fn default() -> Self {
106        Self {
107            inner: Arc::new(RwLock::new(CounterWithExemplarInner {
108                exemplar: None,
109                counter: Default::default(),
110            })),
111        }
112    }
113}
114
115impl<S, N: Clone, A: counter::Atomic<N>> CounterWithExemplar<S, N, A> {
116    // TODO: Implement `fn inc`. Problematic right now as one can not produce
117    // value `1` of type `N`.
118
119    /// Increase the [`CounterWithExemplar`] by `v`, updating the [`Exemplar`]
120    /// if a label set is provided, returning the previous value.
121    pub fn inc_by(&self, v: N, label_set: Option<S>, timestamp: Option<SystemTime>) -> N {
122        let mut inner = self.inner.write();
123
124        inner.exemplar = label_set.map(|label_set| Exemplar {
125            label_set,
126            value: v.clone(),
127            timestamp,
128        });
129
130        inner.counter.inc_by(v)
131    }
132
133    /// Get the current value of the [`CounterWithExemplar`] as well as its
134    /// [`Exemplar`] if any.
135    pub fn get(&self) -> (N, MappedRwLockReadGuard<'_, Option<Exemplar<S, N>>>) {
136        let inner = self.inner.read();
137        let value = inner.counter.get();
138        let exemplar = RwLockReadGuard::map(inner, |inner| &inner.exemplar);
139        (value, exemplar)
140    }
141
142    /// Exposes the inner atomic type of the [`CounterWithExemplar`].
143    ///
144    /// This should only be used for advanced use-cases which are not directly
145    /// supported by the library.
146    ///
147    /// The caller of this function has to uphold the property of an Open
148    /// Metrics counter namely that the value is monotonically increasing, i.e.
149    /// either stays the same or increases.
150    pub fn inner(&self) -> MappedRwLockReadGuard<'_, A> {
151        RwLockReadGuard::map(self.inner.read(), |inner| inner.counter.inner())
152    }
153}
154
155// TODO: S, V, N, A are hard to grasp.
156impl<S, N, A> EncodeMetric for crate::metrics::exemplar::CounterWithExemplar<S, N, A>
157where
158    S: EncodeLabelSet,
159    N: EncodeCounterValue + EncodeExemplarValue + Clone,
160    A: counter::Atomic<N>,
161{
162    fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
163        let (value, exemplar) = self.get();
164        encoder.encode_counter(&value, exemplar.as_ref())
165    }
166
167    fn metric_type(&self) -> MetricType {
168        Counter::<N, A>::TYPE
169    }
170}
171
172/////////////////////////////////////////////////////////////////////////////////
173// Histogram
174
175/// Open Metrics [`Histogram`] to both measure distributions of discrete events.
176/// and track references to data outside of the metric set.
177///
178/// ```
179/// # use prometheus_client::metrics::exemplar::HistogramWithExemplars;
180/// # use prometheus_client::metrics::histogram::exponential_buckets;
181/// let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10));
182/// histogram.observe(4.2, Some(vec![("user_id".to_string(), "42".to_string())]), None);
183/// ```
184/// You can also use exemplars with families. Just wrap the exemplar in a Family.
185/// ```
186/// # use prometheus_client::metrics::exemplar::HistogramWithExemplars;
187/// # use prometheus_client::metrics::histogram::exponential_buckets;
188/// # use prometheus_client::metrics::family::Family;
189/// # use prometheus_client::encoding::EncodeLabelSet;
190/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)]
191/// pub struct ResultLabel {
192///     pub result: String,
193/// }
194///
195/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)]
196/// pub struct TraceLabel {
197///     pub trace_id: String,
198/// }
199///
200/// let latency: Family<ResultLabel, HistogramWithExemplars<TraceLabel>> =
201///     Family::new_with_constructor(|| {
202///         HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10))
203///     });
204///
205/// latency
206///     .get_or_create(&ResultLabel {
207///         result: "success".to_owned(),
208///     })
209///     .observe(
210///         0.001345422,
211///         Some(TraceLabel {
212///             trace_id: "3a2f90c9f80b894f".to_owned(),
213///         }),
214///         None,
215///     );
216/// ```
217#[derive(Debug)]
218pub struct HistogramWithExemplars<S> {
219    // TODO: Not ideal, as Histogram has a Mutex as well.
220    pub(crate) inner: Arc<RwLock<HistogramWithExemplarsInner<S>>>,
221}
222
223impl<S> TypedMetric for HistogramWithExemplars<S> {
224    const TYPE: MetricType = MetricType::Histogram;
225}
226
227impl<S> Clone for HistogramWithExemplars<S> {
228    fn clone(&self) -> Self {
229        Self {
230            inner: self.inner.clone(),
231        }
232    }
233}
234
235/// An OpenMetrics [`Histogram`] in combination with an OpenMetrics [`Exemplar`].
236#[derive(Debug)]
237pub struct HistogramWithExemplarsInner<S> {
238    pub(crate) exemplars: HashMap<usize, Exemplar<S, f64>>,
239    pub(crate) histogram: Histogram,
240}
241
242impl<S> HistogramWithExemplars<S> {
243    /// Create a new [`HistogramWithExemplars`].
244    pub fn new(buckets: impl Iterator<Item = f64>) -> Self {
245        Self {
246            inner: Arc::new(RwLock::new(HistogramWithExemplarsInner {
247                exemplars: Default::default(),
248                histogram: Histogram::new(buckets),
249            })),
250        }
251    }
252
253    /// Observe the given value, optionally providing a label set and thus
254    /// setting the [`Exemplar`] value.
255    pub fn observe(&self, v: f64, label_set: Option<S>, timestamp: Option<SystemTime>) {
256        let mut inner = self.inner.write();
257        let bucket = inner.histogram.observe_and_bucket(v);
258        if let (Some(bucket), Some(label_set)) = (bucket, label_set) {
259            inner.exemplars.insert(
260                bucket,
261                Exemplar {
262                    label_set,
263                    value: v,
264                    timestamp,
265                },
266            );
267        }
268    }
269
270    pub(crate) fn inner(&self) -> RwLockReadGuard<'_, HistogramWithExemplarsInner<S>> {
271        self.inner.read()
272    }
273}
274
275impl<S: EncodeLabelSet> EncodeMetric for HistogramWithExemplars<S> {
276    fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
277        let inner = self.inner();
278        let (sum, count, buckets) = inner.histogram.get();
279        encoder.encode_histogram(sum, count, &buckets, Some(&inner.exemplars))
280    }
281
282    fn metric_type(&self) -> MetricType {
283        Histogram::TYPE
284    }
285}