Coverage Report

Created: 2025-11-16 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/prometheus-client-0.24.0/src/metrics/exemplar.rs
Line
Count
Source
1
//! Module implementing an Open Metrics exemplars for counters and histograms.
2
//!
3
//! See [`CounterWithExemplar`] and [`HistogramWithExemplars`] for details.
4
5
use crate::encoding::{
6
    EncodeCounterValue, EncodeExemplarValue, EncodeLabelSet, EncodeMetric, MetricEncoder,
7
};
8
9
use super::counter::{self, Counter};
10
use super::histogram::Histogram;
11
use super::{MetricType, TypedMetric};
12
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
13
use std::collections::HashMap;
14
#[cfg(not(target_has_atomic = "64"))]
15
use std::sync::atomic::AtomicU32;
16
#[cfg(target_has_atomic = "64")]
17
use std::sync::atomic::AtomicU64;
18
use std::sync::Arc;
19
use std::time::SystemTime;
20
21
/// An OpenMetrics exemplar.
22
#[derive(Debug)]
23
pub 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)]
73
pub struct CounterWithExemplar<S, N = u64, A = AtomicU64> {
74
    pub(crate) inner: Arc<RwLock<CounterWithExemplarInner<S, N, A>>>,
75
}
76
77
impl<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)]
85
pub struct CounterWithExemplar<S, N = u32, A = AtomicU32> {
86
    pub(crate) inner: Arc<RwLock<CounterWithExemplarInner<S, N, A>>>,
87
}
88
89
impl<S, N, A> Clone for CounterWithExemplar<S, N, A> {
90
0
    fn clone(&self) -> Self {
91
0
        CounterWithExemplar {
92
0
            inner: self.inner.clone(),
93
0
        }
94
0
    }
95
}
96
97
/// An OpenMetrics [`Counter`] in combination with an OpenMetrics [`Exemplar`].
98
#[derive(Debug)]
99
pub struct CounterWithExemplarInner<S, N, A> {
100
    pub(crate) exemplar: Option<Exemplar<S, N>>,
101
    pub(crate) counter: Counter<N, A>,
102
}
103
104
impl<S, N, A: Default> Default for CounterWithExemplar<S, N, A> {
105
0
    fn default() -> Self {
106
0
        Self {
107
0
            inner: Arc::new(RwLock::new(CounterWithExemplarInner {
108
0
                exemplar: None,
109
0
                counter: Default::default(),
110
0
            })),
111
0
        }
112
0
    }
113
}
114
115
impl<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
0
    pub fn inc_by(&self, v: N, label_set: Option<S>, timestamp: Option<SystemTime>) -> N {
122
0
        let mut inner = self.inner.write();
123
124
0
        inner.exemplar = label_set.map(|label_set| Exemplar {
125
0
            label_set,
126
0
            value: v.clone(),
127
0
            timestamp,
128
0
        });
129
130
0
        inner.counter.inc_by(v)
131
0
    }
132
133
    /// Get the current value of the [`CounterWithExemplar`] as well as its
134
    /// [`Exemplar`] if any.
135
0
    pub fn get(&self) -> (N, MappedRwLockReadGuard<'_, Option<Exemplar<S, N>>>) {
136
0
        let inner = self.inner.read();
137
0
        let value = inner.counter.get();
138
0
        let exemplar = RwLockReadGuard::map(inner, |inner| &inner.exemplar);
139
0
        (value, exemplar)
140
0
    }
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
0
    pub fn inner(&self) -> MappedRwLockReadGuard<'_, A> {
151
0
        RwLockReadGuard::map(self.inner.read(), |inner| inner.counter.inner())
152
0
    }
153
}
154
155
// TODO: S, V, N, A are hard to grasp.
156
impl<S, N, A> EncodeMetric for crate::metrics::exemplar::CounterWithExemplar<S, N, A>
157
where
158
    S: EncodeLabelSet,
159
    N: EncodeCounterValue + EncodeExemplarValue + Clone,
160
    A: counter::Atomic<N>,
161
{
162
0
    fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
163
0
        let (value, exemplar) = self.get();
164
0
        encoder.encode_counter(&value, exemplar.as_ref())
165
0
    }
166
167
0
    fn metric_type(&self) -> MetricType {
168
0
        Counter::<N, A>::TYPE
169
0
    }
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)]
218
pub struct HistogramWithExemplars<S> {
219
    // TODO: Not ideal, as Histogram has a Mutex as well.
220
    pub(crate) inner: Arc<RwLock<HistogramWithExemplarsInner<S>>>,
221
}
222
223
impl<S> TypedMetric for HistogramWithExemplars<S> {
224
    const TYPE: MetricType = MetricType::Histogram;
225
}
226
227
impl<S> Clone for HistogramWithExemplars<S> {
228
0
    fn clone(&self) -> Self {
229
0
        Self {
230
0
            inner: self.inner.clone(),
231
0
        }
232
0
    }
233
}
234
235
/// An OpenMetrics [`Histogram`] in combination with an OpenMetrics [`Exemplar`].
236
#[derive(Debug)]
237
pub struct HistogramWithExemplarsInner<S> {
238
    pub(crate) exemplars: HashMap<usize, Exemplar<S, f64>>,
239
    pub(crate) histogram: Histogram,
240
}
241
242
impl<S> HistogramWithExemplars<S> {
243
    /// Create a new [`HistogramWithExemplars`].
244
0
    pub fn new(buckets: impl Iterator<Item = f64>) -> Self {
245
0
        Self {
246
0
            inner: Arc::new(RwLock::new(HistogramWithExemplarsInner {
247
0
                exemplars: Default::default(),
248
0
                histogram: Histogram::new(buckets),
249
0
            })),
250
0
        }
251
0
    }
252
253
    /// Observe the given value, optionally providing a label set and thus
254
    /// setting the [`Exemplar`] value.
255
0
    pub fn observe(&self, v: f64, label_set: Option<S>, timestamp: Option<SystemTime>) {
256
0
        let mut inner = self.inner.write();
257
0
        let bucket = inner.histogram.observe_and_bucket(v);
258
0
        if let (Some(bucket), Some(label_set)) = (bucket, label_set) {
259
0
            inner.exemplars.insert(
260
0
                bucket,
261
0
                Exemplar {
262
0
                    label_set,
263
0
                    value: v,
264
0
                    timestamp,
265
0
                },
266
0
            );
267
0
        }
268
0
    }
269
270
0
    pub(crate) fn inner(&self) -> RwLockReadGuard<'_, HistogramWithExemplarsInner<S>> {
271
0
        self.inner.read()
272
0
    }
273
}
274
275
impl<S: EncodeLabelSet> EncodeMetric for HistogramWithExemplars<S> {
276
0
    fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
277
0
        let inner = self.inner();
278
0
        let (sum, count, buckets) = inner.histogram.get();
279
0
        encoder.encode_histogram(sum, count, &buckets, Some(&inner.exemplars))
280
0
    }
281
282
0
    fn metric_type(&self) -> MetricType {
283
0
        Histogram::TYPE
284
0
    }
285
}