Skip to main content

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, &registry).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, &registry).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    /// Returns `true` if the given label set exists within the metric family.
345    ///
346    /// ```
347    /// # use prometheus_client::metrics::counter::{Atomic, Counter};
348    /// # use prometheus_client::metrics::family::Family;
349    /// #
350    /// let family = Family::<Vec<(String, String)>, Counter>::default();
351    /// let get = vec![("method".to_owned(), "GET".to_owned())];
352    /// let post = vec![("method".to_owned(), "POST".to_owned())];
353    ///
354    /// // Create the metric with labels `method="GET"`.
355    /// family.get_or_create(&get).inc();
356    ///
357    /// assert!(family.contains(&get), "a `method=\"GET\"`-labeled metric exists");
358    /// assert!(!family.contains(&post), "a `method=\"POST\"`-labeled metric does NOT exist");
359    /// ```
360    #[cfg(any(test, feature = "test-util"))]
361    pub fn contains(&self, label_set: &S) -> bool {
362        self.metrics.read().get(label_set).is_some()
363    }
364
365    /// Returns the number of metrics in this family.
366    ///
367    /// ```
368    /// # use prometheus_client::metrics::counter::{Atomic, Counter};
369    /// # use prometheus_client::metrics::family::Family;
370    /// #
371    /// let family = Family::<Vec<(String, String)>, Counter>::default();
372    /// assert_eq!(family.len(), 0);
373    ///
374    /// // Will create the metric with label `method="GET"` on first call and
375    /// // return a reference.
376    /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
377    /// assert_eq!(family.len(), 1);
378    ///
379    /// // Clear the family of all label sets.
380    /// family.clear();
381    /// assert_eq!(family.len(), 0);
382    /// ```
383    #[cfg(any(test, feature = "test-util"))]
384    pub fn len(&self) -> usize {
385        self.metrics.read().len()
386    }
387
388    /// Returns `true` if the family contains no metrics.
389    #[cfg(any(test, feature = "test-util"))]
390    pub fn is_empty(&self) -> bool {
391        self.metrics.read().is_empty()
392    }
393
394    pub(crate) fn read(&self) -> RwLockReadGuard<'_, HashMap<S, M>> {
395        self.metrics.read()
396    }
397}
398
399impl<S, M, C: Clone> Clone for Family<S, M, C> {
400    fn clone(&self) -> Self {
401        Family {
402            metrics: self.metrics.clone(),
403            constructor: self.constructor.clone(),
404        }
405    }
406}
407
408impl<S, M: TypedMetric, C> TypedMetric for Family<S, M, C> {
409    const TYPE: MetricType = <M as TypedMetric>::TYPE;
410}
411
412impl<S, M, C> EncodeMetric for Family<S, M, C>
413where
414    S: Clone + std::hash::Hash + Eq + EncodeLabelSet,
415    M: EncodeMetric + TypedMetric,
416    C: MetricConstructor<M>,
417{
418    fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
419        let guard = self.read();
420        for (label_set, m) in guard.iter() {
421            let encoder = encoder.encode_family(label_set)?;
422            m.encode(encoder)?;
423        }
424        Ok(())
425    }
426
427    fn metric_type(&self) -> MetricType {
428        M::TYPE
429    }
430
431    fn is_empty(&self) -> bool {
432        self.metrics.read().is_empty()
433    }
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439    use crate::metrics::counter::Counter;
440    use crate::metrics::histogram::{exponential_buckets, Histogram};
441
442    #[test]
443    fn counter_family() {
444        let family = Family::<Vec<(String, String)>, Counter>::default();
445
446        family
447            .get_or_create(&vec![("method".to_string(), "GET".to_string())])
448            .inc();
449
450        assert_eq!(
451            1,
452            family
453                .get_or_create(&vec![("method".to_string(), "GET".to_string())])
454                .get()
455        );
456    }
457
458    #[test]
459    fn histogram_family() {
460        Family::<(), Histogram>::new_with_constructor(|| {
461            Histogram::new(exponential_buckets(1.0, 2.0, 10))
462        });
463    }
464
465    #[test]
466    fn histogram_family_with_struct_constructor() {
467        struct CustomBuilder {
468            custom_start: f64,
469        }
470        impl MetricConstructor<Histogram> for CustomBuilder {
471            fn new_metric(&self) -> Histogram {
472                Histogram::new(exponential_buckets(self.custom_start, 2.0, 10))
473            }
474        }
475
476        let custom_builder = CustomBuilder { custom_start: 1.0 };
477        Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder);
478    }
479
480    #[test]
481    fn counter_family_remove() {
482        let family = Family::<Vec<(String, String)>, Counter>::default();
483
484        family
485            .get_or_create(&vec![("method".to_string(), "GET".to_string())])
486            .inc();
487
488        assert_eq!(
489            1,
490            family
491                .get_or_create(&vec![("method".to_string(), "GET".to_string())])
492                .get()
493        );
494
495        family
496            .get_or_create(&vec![("method".to_string(), "POST".to_string())])
497            .inc_by(2);
498
499        assert_eq!(
500            2,
501            family
502                .get_or_create(&vec![("method".to_string(), "POST".to_string())])
503                .get()
504        );
505
506        // Attempt to remove it twice, showing it really was removed on the
507        // first attempt.
508        assert!(family.remove(&vec![("method".to_string(), "POST".to_string())]));
509        assert!(!family.remove(&vec![("method".to_string(), "POST".to_string())]));
510
511        // This should make a new POST label.
512        family
513            .get_or_create(&vec![("method".to_string(), "POST".to_string())])
514            .inc();
515
516        assert_eq!(
517            1,
518            family
519                .get_or_create(&vec![("method".to_string(), "POST".to_string())])
520                .get()
521        );
522
523        // GET label should have be untouched.
524        assert_eq!(
525            1,
526            family
527                .get_or_create(&vec![("method".to_string(), "GET".to_string())])
528                .get()
529        );
530    }
531
532    #[test]
533    fn counter_family_clear() {
534        let family = Family::<Vec<(String, String)>, Counter>::default();
535
536        // Create a label and check it.
537        family
538            .get_or_create(&vec![("method".to_string(), "GET".to_string())])
539            .inc();
540
541        assert_eq!(
542            1,
543            family
544                .get_or_create(&vec![("method".to_string(), "GET".to_string())])
545                .get()
546        );
547
548        // Clear it, then try recreating and checking it again.
549        family.clear();
550
551        family
552            .get_or_create(&vec![("method".to_string(), "GET".to_string())])
553            .inc();
554
555        assert_eq!(
556            1,
557            family
558                .get_or_create(&vec![("method".to_string(), "GET".to_string())])
559                .get()
560        );
561    }
562
563    #[test]
564    fn test_get() {
565        let family = Family::<Vec<(String, String)>, Counter>::default();
566
567        // Test getting a non-existent metric.
568        let non_existent = family.get(&vec![("method".to_string(), "GET".to_string())]);
569        assert!(non_existent.is_none());
570
571        // Create a metric.
572        family
573            .get_or_create(&vec![("method".to_string(), "GET".to_string())])
574            .inc();
575
576        // Test getting an existing metric.
577        let existing = family.get(&vec![("method".to_string(), "GET".to_string())]);
578        assert!(existing.is_some());
579        assert_eq!(existing.unwrap().get(), 1);
580
581        // Test getting a different non-existent metric.
582        let another_non_existent = family.get(&vec![("method".to_string(), "POST".to_string())]);
583        assert!(another_non_existent.is_none());
584
585        // Test modifying the metric through the returned reference.
586        if let Some(metric) = family.get(&vec![("method".to_string(), "GET".to_string())]) {
587            metric.inc();
588        }
589
590        // Verify the modification.
591        let modified = family.get(&vec![("method".to_string(), "GET".to_string())]);
592        assert_eq!(modified.unwrap().get(), 2);
593
594        // Test with a different label set type.
595        let string_family = Family::<String, Counter>::default();
596        string_family.get_or_create(&"test".to_string()).inc();
597
598        let string_metric = string_family.get(&"test".to_string());
599        assert!(string_metric.is_some());
600        assert_eq!(string_metric.unwrap().get(), 1);
601
602        let non_existent_string = string_family.get(&"non_existent".to_string());
603        assert!(non_existent_string.is_none());
604    }
605
606    /// Tests that [`Family::get_or_create_owned()`] does not cause deadlocks.
607    #[test]
608    fn counter_family_does_not_deadlock() {
609        /// A structure we'll place two counters into, within a single expression.
610        struct S {
611            apples: Counter,
612            oranges: Counter,
613        }
614
615        let family = Family::<(&str, &str), Counter>::default();
616        let s = S {
617            apples: family.get_or_create_owned(&("kind", "apple")),
618            oranges: family.get_or_create_owned(&("kind", "orange")),
619        };
620
621        s.apples.inc();
622        s.oranges.inc_by(2);
623    }
624}