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}