prometheus_utils/
labels.rs

1use crate::guards::DeferredAddWithLabels;
2use prometheus::{
3    register_histogram_vec, register_int_counter_vec, register_int_gauge_vec, HistogramTimer,
4    HistogramVec, IntCounterVec, IntGaugeVec,
5};
6use std::marker::PhantomData;
7
8/// A sequence of values for Prometheus labels
9pub type LabelValues<'a> = Vec<&'a str>;
10
11/// The `Labels` trait applies to values intended to generate Prometheus labels for a metric.
12///
13/// A metric in Prometheus can include any number of labels. Each label has a fixed name,
14/// and when events are emitted for the metric, those events must include values for each
15/// of the labels. Using labels makes it possible to easily see a metric in aggregate (i.e.,
16/// to see totals regardless of label values), or to query specific kinds of events by
17/// filtering label values.
18///
19/// Thus, for example, rather than having a separate metric for each kind of error that
20/// arises, we can produce one metric with an "err" label, whose value will reflect which
21/// error has occurred. That simplifies the top-level list of metrics, and makes it easier
22/// to build queries to aggregate specific kinds of error events.
23///
24/// This trait adds some extra guard rails on top of the prometheus-rs crate, so that when
25/// we emit a labeled metric we can use a custom type to represent the labels, rather than
26/// working directly with slices of string slices. When defining a labeled metric, you should
27/// also define a new type representing its labels that implements the `Labels` trait.
28/// Then, when emitting events for the metric, labels are passed in using this custom type,
29/// which rules out several kinds of bugs (like missing or incorrectly ordered label values).
30///
31/// You can define labeled metrics using types like [`IntCounterWithLabels`], which are
32/// parameterized by a type that implements `Labels`.
33///
34/// [`IntCounterWithLabels`]: struct.IntCounterWithLabels.html
35pub trait Labels {
36    /// The names of the labels that will be defined for the corresponding metric.
37    fn label_names() -> Vec<&'static str>;
38
39    /// Labels values to seed the metric with initially.
40    ///
41    /// Since Prometheus doesn't know the possible values a label will take on, when we set
42    /// up a labeled metric by default no values will appear until events are emitted. But
43    /// for discoverability, it's helpful to initialize a labeled metric with some possible
44    /// label values (at count 0) even if no events for the metric have occurred.
45    ///
46    /// The label values provided by this function are used to pre-populate the metric at
47    /// count 0. **The values do _not_ need to be exhaustive**; it's fine for events to emit
48    /// label values that are not included here.
49    fn possible_label_values() -> Vec<LabelValues<'static>>;
50
51    /// The actual label values to provide when emitting an event to Prometheus.
52    ///
53    /// The sequence of values should correspond to the names provided in `label_names`,
54    /// in order.
55    fn label_values(&self) -> LabelValues;
56}
57
58/// A Prometheus integer counter metric, with labels described by the type `L`.
59///
60/// The type `L` must implement the [`Labels`] trait; see the documentation for that trait
61/// for an overview of Prometheus metric labels.
62///
63/// [`Labels`]: trait.Labels.html
64pub struct IntCounterWithLabels<L: Labels> {
65    metric: IntCounterVec,
66    _labels: PhantomData<L>,
67}
68
69impl<L: Labels> IntCounterWithLabels<L> {
70    /// Construct and immediately register a new `IntCounterWithLabels` instance.
71    pub fn register_new(name: &str, help: &str) -> IntCounterWithLabels<L> {
72        let metric = register_int_counter_vec!(name, help, &L::label_names()).unwrap();
73
74        for vals in L::possible_label_values() {
75            metric.with_label_values(&vals).inc_by(0);
76        }
77
78        Self {
79            metric,
80            _labels: PhantomData,
81        }
82    }
83
84    /// Get the value of the counter with the provided `labels`.
85    pub fn get(&self, labels: &L) -> u64 {
86        self.metric.with_label_values(&labels.label_values()).get()
87    }
88
89    /// Increment the metric by `1`, using the provided `labels` for the event.
90    pub fn inc(&self, labels: &L) {
91        self.metric.with_label_values(&labels.label_values()).inc();
92    }
93
94    /// Increment the metric by `v`, using the provided `labels` for the event.
95    pub fn add(&self, v: u64, labels: &L) {
96        self.metric
97            .with_label_values(&labels.label_values())
98            .inc_by(v);
99    }
100
101    /// Creates a guard value that will increment the metric by `1`, using the provided `labels`,
102    /// once dropped.
103    ///
104    /// Prior to dropping, the labels can be altered using [`DeferredAddWithLabels::with_labels`].
105    #[must_use]
106    pub fn deferred_inc<'a>(&'a self, labels: L) -> DeferredAddWithLabels<'a, L> {
107        DeferredAddWithLabels::new(self, 1, labels)
108    }
109
110    /// Creates a guard value that will increment the metric by `v`, using the provided `labels`,
111    /// once dropped.
112    ///
113    /// Prior to dropping, the labels can be altered using [`DeferredAddWithLabels::with_labels`].
114    #[must_use]
115    pub fn deferred_add<'a>(&'a self, v: u64, labels: L) -> DeferredAddWithLabels<'a, L> {
116        DeferredAddWithLabels::new(self, v, labels)
117    }
118}
119
120/// A Prometheus integer gauge metric, with labels described by the type `L`.
121///
122/// The type `L` must implement the [`Labels`] trait; see the documentation for that trait
123/// for an overview of Prometheus metric labels.
124///
125/// [`Labels`]: trait.Labels.html
126pub struct IntGaugeWithLabels<L: Labels> {
127    metric: IntGaugeVec,
128    _labels: PhantomData<L>,
129}
130
131impl<L: Labels> IntGaugeWithLabels<L> {
132    /// Construct and immediately register a new `IntGaugeWithLabels` instance.
133    pub fn register_new(name: &str, help: &str) -> IntGaugeWithLabels<L> {
134        let metric = register_int_gauge_vec!(name, help, &L::label_names()).unwrap();
135
136        // Note: for gauges, unlike counters, we don't need to -- and should not! -- prepopulate
137        // the metric with the possible labels. Unlike counters, which are only updated when an
138        // event occurs, gauges _always_ have a value. Moreover, we cannot make assumptions about
139        // an initial gauge value (unlike an initial 0 value for counters).
140
141        Self {
142            metric,
143            _labels: PhantomData,
144        }
145    }
146
147    /// Get the value of the gauge with the provided `labels`.
148    pub fn get(&self, labels: &L) -> i64 {
149        self.metric.with_label_values(&labels.label_values()).get()
150    }
151
152    /// Set the value of the gauge with the provided `labels`.
153    pub fn set(&self, labels: &L, value: i64) {
154        self.metric
155            .with_label_values(&labels.label_values())
156            .set(value);
157    }
158
159    /// Add `value` to the gauge with the provided `labels`.
160    pub fn add(&self, labels: &L, value: i64) {
161        self.metric
162            .with_label_values(&labels.label_values())
163            .add(value);
164    }
165
166    /// Subtract `value` from the gauge with the provided `labels`.
167    pub fn sub(&self, labels: &L, value: i64) {
168        self.metric
169            .with_label_values(&labels.label_values())
170            .sub(value);
171    }
172
173    /// Increment the gauge by `1`, using the provided `labels` for the event.
174    pub fn inc(&self, labels: &L) {
175        self.metric.with_label_values(&labels.label_values()).inc();
176    }
177
178    /// Decrement the gauge by `1`, using the provided `labels` for the event.
179    pub fn dec(&self, labels: &L) {
180        self.metric.with_label_values(&labels.label_values()).dec();
181    }
182}
183
184/// A Prometheus histogram metric, with labels described by the type `L`.
185///
186/// The type `L` must implement the [`Labels`] trait; see the documentation for that trait
187/// for an overview of Prometheus metric labels.
188///
189/// [`Labels`]: trait.Labels.html
190pub struct HistogramWithLabels<L: Labels> {
191    metric: HistogramVec,
192    _labels: PhantomData<L>,
193}
194
195impl<L: Labels> HistogramWithLabels<L> {
196    /// Construct and immediately register a new `HistogramWithLabels` instance.
197    pub fn register_new(name: &str, help: &str) -> Self {
198        let metric = register_histogram_vec!(name, help, &L::label_names()).unwrap();
199
200        // Note: for histograms, like gauges, we don't need to -- and should not! -- prepopulate
201        // the metric with the possible labels.
202
203        Self {
204            metric,
205            _labels: PhantomData,
206        }
207    }
208
209    /// Construct and immediately register a new `HistogramWithLabels` instance.
210    ///
211    /// This will use the provided `buckets` when registering the underlying [`HistogramVec`].
212    pub fn register_new_with_buckets(name: &str, help: &str, buckets: Vec<f64>) -> Self {
213        let metric = register_histogram_vec!(name, help, &L::label_names(), buckets).unwrap();
214
215        // Note: for histograms, like gauges, we don't need to -- and should not! -- prepopulate
216        // the metric with the possible labels.
217
218        Self {
219            metric,
220            _labels: PhantomData,
221        }
222    }
223
224    /// Add a single observation to the histogram with the provided `labels`.
225    pub fn observe(&self, labels: &L, value: f64) {
226        self.metric
227            .with_label_values(&labels.label_values())
228            .observe(value);
229    }
230
231    /// Return a [`HistogramTimer`] to track a duration, using the provided `labels`.
232    pub fn start_timer(&self, labels: &L) -> HistogramTimer {
233        self.metric
234            .with_label_values(&labels.label_values())
235            .start_timer()
236    }
237
238    /// Observe execution time of a closure, in seconds.
239    pub fn observe_closure_duration<F, T>(&self, labels: &L, f: F) -> T
240    where
241        F: FnOnce() -> T,
242    {
243        self.metric
244            .with_label_values(&labels.label_values())
245            .observe_closure_duration(f)
246    }
247
248    /// Return accumulated sum of all samples, using the provided `labels`.
249    pub fn get_sample_sum(&self, labels: &L) -> f64 {
250        self.metric
251            .with_label_values(&labels.label_values())
252            .get_sample_sum()
253    }
254
255    /// Return count of all samples, using the provided `labels`.
256    pub fn get_sample_count(&self, labels: &L) -> u64 {
257        self.metric
258            .with_label_values(&labels.label_values())
259            .get_sample_count()
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266    use lazy_static::lazy_static;
267    use test_labels::*;
268
269    #[allow(dead_code)]
270    mod test_labels {
271        use crate::label_enum;
272        label_enum! {
273            pub enum Animal {
274                Bird,
275                Cat,
276                Dog,
277            }
278        }
279        label_enum! {
280            pub enum Size {
281                Small,
282                Large,
283            }
284        }
285    }
286
287    struct TestLabels {
288        animal: Animal,
289        size: Size,
290    }
291
292    impl Labels for TestLabels {
293        fn label_names() -> Vec<&'static str> {
294            vec!["animal", "size"]
295        }
296        fn possible_label_values() -> Vec<LabelValues<'static>> {
297            Default::default() // elide this for brevity.
298        }
299        fn label_values(&self) -> LabelValues {
300            let Self { animal, size } = self;
301            vec![animal.as_str(), size.as_str()]
302        }
303    }
304
305    /// Show that an [`IntCounterWithLabels`] can have its value accessed.
306    #[test]
307    fn int_counter_with_labels_get_works() {
308        lazy_static! {
309            static ref COUNTER: IntCounterWithLabels<TestLabels> =
310                IntCounterWithLabels::register_new(
311                    "test_label_counter",
312                    "a labeled counter for tests"
313                );
314        }
315
316        // Increment the counter for some various combinations of label values.
317        COUNTER.inc(&TestLabels {
318            animal: Animal::Bird,
319            size: Size::Large,
320        });
321        COUNTER.inc(&TestLabels {
322            animal: Animal::Bird,
323            size: Size::Small,
324        });
325        COUNTER.inc(&TestLabels {
326            animal: Animal::Cat,
327            size: Size::Large,
328        });
329
330        assert_eq!(
331            COUNTER.get(&TestLabels {
332                animal: Animal::Bird,
333                size: Size::Large,
334            }),
335            1
336        );
337        assert_eq!(
338            COUNTER.get(&TestLabels {
339                animal: Animal::Cat,
340                size: Size::Small,
341            }),
342            0
343        );
344    }
345
346    /// Show that an [`IntGaugeWithLabels`] can have its value accessed.
347    #[test]
348    fn int_gauge_with_labels_get_works() {
349        lazy_static! {
350            static ref GAUGE: IntGaugeWithLabels<TestLabels> =
351                IntGaugeWithLabels::register_new("test_label_gauge", "a labeled gauge for tests");
352        }
353
354        // Increment the gauge for some various combinations of label values.
355        GAUGE.inc(&TestLabels {
356            animal: Animal::Bird,
357            size: Size::Large,
358        });
359        GAUGE.inc(&TestLabels {
360            animal: Animal::Bird,
361            size: Size::Small,
362        });
363        GAUGE.inc(&TestLabels {
364            animal: Animal::Cat,
365            size: Size::Large,
366        });
367
368        assert_eq!(
369            GAUGE.get(&TestLabels {
370                animal: Animal::Bird,
371                size: Size::Large,
372            }),
373            1
374        );
375        assert_eq!(
376            GAUGE.get(&TestLabels {
377                animal: Animal::Cat,
378                size: Size::Small,
379            }),
380            0
381        );
382    }
383}