prometheus_client/metrics/family.rs
1//! Module implementing an Open Metrics metric family.
2//!
3//! See [`Family`] for details.
4
5use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder};
6
7use super::{MetricType, TypedMetric};
8use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
9use std::collections::HashMap;
10use std::sync::Arc;
11
12/// Representation of the OpenMetrics *MetricFamily* data type.
13///
14/// A [`Family`] is a set of metrics with the same name, help text and
15/// type, differentiated by their label values thus spanning a multidimensional
16/// space.
17///
18/// # Generic over the label set
19///
20/// A [`Family`] is generic over the label type. For convenience one might
21/// choose a `Vec<(String, String)>`, for performance and/or type safety one might
22/// define a custom type.
23///
24/// ## Examples
25///
26/// ### [`Family`] with `Vec<(String, String)>` for convenience
27///
28/// ```
29/// # use prometheus_client::encoding::text::encode;
30/// # use prometheus_client::metrics::counter::{Atomic, Counter};
31/// # use prometheus_client::metrics::family::Family;
32/// # use prometheus_client::registry::Registry;
33/// #
34/// # let mut registry = Registry::default();
35/// let family = Family::<Vec<(String, String)>, Counter>::default();
36/// # registry.register(
37/// # "my_counter",
38/// # "This is my counter",
39/// # family.clone(),
40/// # );
41///
42/// // Record a single HTTP GET request.
43/// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
44///
45/// # // Encode all metrics in the registry in the text format.
46/// # let mut buffer = String::new();
47/// # encode(&mut buffer, ®istry).unwrap();
48/// #
49/// # let expected = "# HELP my_counter This is my counter.\n".to_owned() +
50/// # "# TYPE my_counter counter\n" +
51/// # "my_counter_total{method=\"GET\"} 1\n" +
52/// # "# EOF\n";
53/// # assert_eq!(expected, buffer);
54/// ```
55///
56/// ### [`Family`] with custom type for performance and/or type safety
57///
58/// Using `EncodeLabelSet` and `EncodeLabelValue` derive macro to generate
59/// [`EncodeLabelSet`] for `struct`s and
60/// [`EncodeLabelValue`](crate::encoding::EncodeLabelValue) for `enum`s.
61///
62/// ```
63/// # use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue};
64/// # use prometheus_client::encoding::text::encode;
65/// # use prometheus_client::metrics::counter::{Atomic, Counter};
66/// # use prometheus_client::metrics::family::Family;
67/// # use prometheus_client::registry::Registry;
68/// # use std::io::Write;
69/// #
70/// # let mut registry = Registry::default();
71/// #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
72/// struct Labels {
73/// method: Method,
74/// };
75///
76/// #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
77/// enum Method {
78/// GET,
79/// PUT,
80/// };
81///
82/// let family = Family::<Labels, Counter>::default();
83/// # registry.register(
84/// # "my_counter",
85/// # "This is my counter",
86/// # family.clone(),
87/// # );
88///
89/// // Record a single HTTP GET request.
90/// family.get_or_create(&Labels { method: Method::GET }).inc();
91/// #
92/// # // Encode all metrics in the registry in the text format.
93/// # let mut buffer = String::new();
94/// # encode(&mut buffer, ®istry).unwrap();
95/// #
96/// # let expected = "# HELP my_counter This is my counter.\n".to_owned() +
97/// # "# TYPE my_counter counter\n" +
98/// # "my_counter_total{method=\"GET\"} 1\n" +
99/// # "# EOF\n";
100/// # assert_eq!(expected, buffer);
101/// ```
102// TODO: Consider exposing hash algorithm.
103pub struct Family<S, M, C = fn() -> M> {
104 metrics: Arc<RwLock<HashMap<S, M>>>,
105 /// Function that when called constructs a new metric.
106 ///
107 /// For most metric types this would simply be its [`Default`]
108 /// implementation set through [`Family::default`]. For metric types that
109 /// need custom construction logic like
110 /// [`Histogram`](crate::metrics::histogram::Histogram) in order to set
111 /// specific buckets, a custom constructor is set via
112 /// [`Family::new_with_constructor`].
113 constructor: C,
114}
115
116impl<S: std::fmt::Debug, M: std::fmt::Debug, C> std::fmt::Debug for Family<S, M, C> {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 f.debug_struct("Family")
119 .field("metrics", &self.metrics)
120 .finish()
121 }
122}
123
124/// A constructor for creating new metrics in a [`Family`] when calling
125/// [`Family::get_or_create`]. Such constructor is provided via
126/// [`Family::new_with_constructor`].
127///
128/// This is mostly used when creating histograms using constructors that need to
129/// capture variables.
130///
131/// ```
132/// # use prometheus_client::metrics::family::{Family, MetricConstructor};
133/// # use prometheus_client::metrics::histogram::Histogram;
134/// struct CustomBuilder {
135/// buckets: Vec<f64>,
136/// }
137///
138/// impl MetricConstructor<Histogram> for CustomBuilder {
139/// fn new_metric(&self) -> Histogram {
140/// // When a new histogram is created, this function will be called.
141/// Histogram::new(self.buckets.iter().cloned())
142/// }
143/// }
144///
145/// let custom_builder = CustomBuilder { buckets: vec![0.0, 10.0, 100.0] };
146/// let metric = Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder);
147/// ```
148pub trait MetricConstructor<M> {
149 /// Create a new instance of the metric type.
150 fn new_metric(&self) -> M;
151}
152
153/// In cases in which the explicit type of the metric is not required, it is
154/// posible to directly provide a closure even if it captures variables.
155///
156/// ```
157/// # use prometheus_client::metrics::family::{Family};
158/// # use prometheus_client::metrics::histogram::Histogram;
159/// let custom_buckets = [0.0, 10.0, 100.0];
160/// let metric = Family::<(), Histogram, _>::new_with_constructor(|| {
161/// Histogram::new(custom_buckets.into_iter())
162/// });
163/// # metric.get_or_create(&());
164/// ```
165impl<M, F: Fn() -> M> MetricConstructor<M> for F {
166 fn new_metric(&self) -> M {
167 self()
168 }
169}
170
171impl<S: Clone + std::hash::Hash + Eq, M: Default> Default for Family<S, M> {
172 fn default() -> Self {
173 Self {
174 metrics: Arc::new(RwLock::new(Default::default())),
175 constructor: M::default,
176 }
177 }
178}
179
180impl<S: Clone + std::hash::Hash + Eq, M, C> Family<S, M, C> {
181 /// Create a metric family using a custom constructor to construct new
182 /// metrics.
183 ///
184 /// When calling [`Family::get_or_create`] a [`Family`] needs to be able to
185 /// construct a new metric in case none exists for the given label set. In
186 /// most cases, e.g. for [`Counter`](crate::metrics::counter::Counter)
187 /// [`Family`] can just use the [`Default::default`] implementation for the
188 /// metric type. For metric types such as
189 /// [`Histogram`](crate::metrics::histogram::Histogram) one might want
190 /// [`Family`] to construct a
191 /// [`Histogram`](crate::metrics::histogram::Histogram) with custom buckets
192 /// (see example below). For such case one can use this method. For more
193 /// involved constructors see [`MetricConstructor`].
194 ///
195 /// ```
196 /// # use prometheus_client::metrics::family::Family;
197 /// # use prometheus_client::metrics::histogram::{exponential_buckets, Histogram};
198 /// Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
199 /// Histogram::new(exponential_buckets(1.0, 2.0, 10))
200 /// });
201 /// ```
202 pub fn new_with_constructor(constructor: C) -> Self {
203 Self {
204 metrics: Arc::new(RwLock::new(Default::default())),
205 constructor,
206 }
207 }
208}
209
210impl<S: Clone + std::hash::Hash + Eq, M: Clone, C: MetricConstructor<M>> Family<S, M, C>
211where
212 S: Clone + std::hash::Hash + Eq,
213 M: Clone,
214 C: MetricConstructor<M>,
215{
216 /// Access a metric with the given label set, creating it if one does not yet exist.
217 ///
218 /// ```
219 /// # use prometheus_client::metrics::counter::{Atomic, Counter};
220 /// # use prometheus_client::metrics::family::Family;
221 /// #
222 /// let family = Family::<Vec<(String, String)>, Counter>::default();
223 ///
224 /// // Will create and return the metric with label `method="GET"` when first called.
225 /// family.get_or_create_owned(&vec![("method".to_owned(), "GET".to_owned())]).inc();
226 ///
227 /// // Will return a clone of the existing metric on all subsequent calls.
228 /// family.get_or_create_owned(&vec![("method".to_owned(), "GET".to_owned())]).inc();
229 /// ```
230 ///
231 /// Callers wishing to avoid a clone of the metric `M` can call [`Family::get_or_create()`] to
232 /// return a reference to the metric instead.
233 pub fn get_or_create_owned(&self, label_set: &S) -> M {
234 use std::ops::Deref;
235
236 let guard = self.get_or_create(label_set);
237 let metric = guard.deref().to_owned();
238 drop(guard);
239
240 metric
241 }
242}
243
244impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C> {
245 /// Access a metric with the given label set, creating it if one does not
246 /// yet exist.
247 ///
248 /// ```
249 /// # use prometheus_client::metrics::counter::{Atomic, Counter};
250 /// # use prometheus_client::metrics::family::Family;
251 /// #
252 /// let family = Family::<Vec<(String, String)>, Counter>::default();
253 ///
254 /// // Will create the metric with label `method="GET"` on first call and
255 /// // return a reference.
256 /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
257 ///
258 /// // Will return a reference to the existing metric on all subsequent
259 /// // calls.
260 /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
261 /// ```
262 ///
263 /// NB: This method can cause deadlocks if multiple metrics within this family are read at
264 /// once. Use [`Family::get_or_create_owned()`] if you would like to avoid this by cloning the
265 /// metric `M`.
266 pub fn get_or_create(&self, label_set: &S) -> MappedRwLockReadGuard<'_, M> {
267 if let Some(metric) = self.get(label_set) {
268 return metric;
269 }
270
271 let mut write_guard = self.metrics.write();
272
273 write_guard
274 .entry(label_set.clone())
275 .or_insert_with(|| self.constructor.new_metric());
276
277 let read_guard = RwLockWriteGuard::downgrade(write_guard);
278
279 RwLockReadGuard::map(read_guard, |metrics| {
280 metrics
281 .get(label_set)
282 .expect("Metric to exist after creating it.")
283 })
284 }
285
286 /// Access a metric with the given label set, returning None if one
287 /// does not yet exist.
288 ///
289 /// ```
290 /// # use prometheus_client::metrics::counter::{Atomic, Counter};
291 /// # use prometheus_client::metrics::family::Family;
292 /// #
293 /// let family = Family::<Vec<(String, String)>, Counter>::default();
294 ///
295 /// if let Some(metric) = family.get(&vec![("method".to_owned(), "GET".to_owned())]) {
296 /// metric.inc();
297 /// };
298 /// ```
299 pub fn get(&self, label_set: &S) -> Option<MappedRwLockReadGuard<'_, M>> {
300 RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set)).ok()
301 }
302
303 /// Remove a label set from the metric family.
304 ///
305 /// Returns a bool indicating if a label set was removed or not.
306 ///
307 /// ```
308 /// # use prometheus_client::metrics::counter::{Atomic, Counter};
309 /// # use prometheus_client::metrics::family::Family;
310 /// #
311 /// let family = Family::<Vec<(String, String)>, Counter>::default();
312 ///
313 /// // Will create the metric with label `method="GET"` on first call and
314 /// // return a reference.
315 /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
316 ///
317 /// // Will return `true`, indicating that the `method="GET"` label set was
318 /// // removed.
319 /// assert!(family.remove(&vec![("method".to_owned(), "GET".to_owned())]));
320 /// ```
321 pub fn remove(&self, label_set: &S) -> bool {
322 self.metrics.write().remove(label_set).is_some()
323 }
324
325 /// Clear all label sets from the metric family.
326 ///
327 /// ```
328 /// # use prometheus_client::metrics::counter::{Atomic, Counter};
329 /// # use prometheus_client::metrics::family::Family;
330 /// #
331 /// let family = Family::<Vec<(String, String)>, Counter>::default();
332 ///
333 /// // Will create the metric with label `method="GET"` on first call and
334 /// // return a reference.
335 /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
336 ///
337 /// // Clear the family of all label sets.
338 /// family.clear();
339 /// ```
340 pub fn clear(&self) {
341 self.metrics.write().clear()
342 }
343
344 pub(crate) fn read(&self) -> RwLockReadGuard<'_, HashMap<S, M>> {
345 self.metrics.read()
346 }
347}
348
349impl<S, M, C: Clone> Clone for Family<S, M, C> {
350 fn clone(&self) -> Self {
351 Family {
352 metrics: self.metrics.clone(),
353 constructor: self.constructor.clone(),
354 }
355 }
356}
357
358impl<S, M: TypedMetric, C> TypedMetric for Family<S, M, C> {
359 const TYPE: MetricType = <M as TypedMetric>::TYPE;
360}
361
362impl<S, M, C> EncodeMetric for Family<S, M, C>
363where
364 S: Clone + std::hash::Hash + Eq + EncodeLabelSet,
365 M: EncodeMetric + TypedMetric,
366 C: MetricConstructor<M>,
367{
368 fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
369 let guard = self.read();
370 for (label_set, m) in guard.iter() {
371 let encoder = encoder.encode_family(label_set)?;
372 m.encode(encoder)?;
373 }
374 Ok(())
375 }
376
377 fn metric_type(&self) -> MetricType {
378 M::TYPE
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385 use crate::metrics::counter::Counter;
386 use crate::metrics::histogram::{exponential_buckets, Histogram};
387
388 #[test]
389 fn counter_family() {
390 let family = Family::<Vec<(String, String)>, Counter>::default();
391
392 family
393 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
394 .inc();
395
396 assert_eq!(
397 1,
398 family
399 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
400 .get()
401 );
402 }
403
404 #[test]
405 fn histogram_family() {
406 Family::<(), Histogram>::new_with_constructor(|| {
407 Histogram::new(exponential_buckets(1.0, 2.0, 10))
408 });
409 }
410
411 #[test]
412 fn histogram_family_with_struct_constructor() {
413 struct CustomBuilder {
414 custom_start: f64,
415 }
416 impl MetricConstructor<Histogram> for CustomBuilder {
417 fn new_metric(&self) -> Histogram {
418 Histogram::new(exponential_buckets(self.custom_start, 2.0, 10))
419 }
420 }
421
422 let custom_builder = CustomBuilder { custom_start: 1.0 };
423 Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder);
424 }
425
426 #[test]
427 fn counter_family_remove() {
428 let family = Family::<Vec<(String, String)>, Counter>::default();
429
430 family
431 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
432 .inc();
433
434 assert_eq!(
435 1,
436 family
437 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
438 .get()
439 );
440
441 family
442 .get_or_create(&vec![("method".to_string(), "POST".to_string())])
443 .inc_by(2);
444
445 assert_eq!(
446 2,
447 family
448 .get_or_create(&vec![("method".to_string(), "POST".to_string())])
449 .get()
450 );
451
452 // Attempt to remove it twice, showing it really was removed on the
453 // first attempt.
454 assert!(family.remove(&vec![("method".to_string(), "POST".to_string())]));
455 assert!(!family.remove(&vec![("method".to_string(), "POST".to_string())]));
456
457 // This should make a new POST label.
458 family
459 .get_or_create(&vec![("method".to_string(), "POST".to_string())])
460 .inc();
461
462 assert_eq!(
463 1,
464 family
465 .get_or_create(&vec![("method".to_string(), "POST".to_string())])
466 .get()
467 );
468
469 // GET label should have be untouched.
470 assert_eq!(
471 1,
472 family
473 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
474 .get()
475 );
476 }
477
478 #[test]
479 fn counter_family_clear() {
480 let family = Family::<Vec<(String, String)>, Counter>::default();
481
482 // Create a label and check it.
483 family
484 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
485 .inc();
486
487 assert_eq!(
488 1,
489 family
490 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
491 .get()
492 );
493
494 // Clear it, then try recreating and checking it again.
495 family.clear();
496
497 family
498 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
499 .inc();
500
501 assert_eq!(
502 1,
503 family
504 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
505 .get()
506 );
507 }
508
509 #[test]
510 fn test_get() {
511 let family = Family::<Vec<(String, String)>, Counter>::default();
512
513 // Test getting a non-existent metric.
514 let non_existent = family.get(&vec![("method".to_string(), "GET".to_string())]);
515 assert!(non_existent.is_none());
516
517 // Create a metric.
518 family
519 .get_or_create(&vec![("method".to_string(), "GET".to_string())])
520 .inc();
521
522 // Test getting an existing metric.
523 let existing = family.get(&vec![("method".to_string(), "GET".to_string())]);
524 assert!(existing.is_some());
525 assert_eq!(existing.unwrap().get(), 1);
526
527 // Test getting a different non-existent metric.
528 let another_non_existent = family.get(&vec![("method".to_string(), "POST".to_string())]);
529 assert!(another_non_existent.is_none());
530
531 // Test modifying the metric through the returned reference.
532 if let Some(metric) = family.get(&vec![("method".to_string(), "GET".to_string())]) {
533 metric.inc();
534 }
535
536 // Verify the modification.
537 let modified = family.get(&vec![("method".to_string(), "GET".to_string())]);
538 assert_eq!(modified.unwrap().get(), 2);
539
540 // Test with a different label set type.
541 let string_family = Family::<String, Counter>::default();
542 string_family.get_or_create(&"test".to_string()).inc();
543
544 let string_metric = string_family.get(&"test".to_string());
545 assert!(string_metric.is_some());
546 assert_eq!(string_metric.unwrap().get(), 1);
547
548 let non_existent_string = string_family.get(&"non_existent".to_string());
549 assert!(non_existent_string.is_none());
550 }
551
552 /// Tests that [`Family::get_or_create_owned()`] does not cause deadlocks.
553 #[test]
554 fn counter_family_does_not_deadlock() {
555 /// A structure we'll place two counters into, within a single expression.
556 struct S {
557 apples: Counter,
558 oranges: Counter,
559 }
560
561 let family = Family::<(&str, &str), Counter>::default();
562 let s = S {
563 apples: family.get_or_create_owned(&("kind", "apple")),
564 oranges: family.get_or_create_owned(&("kind", "orange")),
565 };
566
567 s.apples.inc();
568 s.oranges.inc_by(2);
569 }
570}