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}