open_metrics_client/metrics/
family.rs

1//! Module implementing an Open Metrics metric family.
2//!
3//! See [`Family`] for details.
4
5use super::{MetricType, TypedMetric};
6use owning_ref::OwningRef;
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock, RwLockReadGuard};
9
10/// Representation of the OpenMetrics *MetricFamily* data type.
11///
12/// A [`Family`] is a set of metrics with the same name, help text and
13/// type, differentiated by their label values thus spanning a multidimensional
14/// space.
15///
16/// # Generic over the label set
17///
18/// A [`Family`] is generic over the label type. For convenience one might
19/// choose a `Vec<(String, String)>`, for performance and/or type safety one might
20/// define a custom type.
21///
22/// ## Examples
23///
24/// ### [`Family`] with `Vec<(String, String)>` for convenience
25///
26/// ```
27/// # use open_metrics_client::encoding::text::encode;
28/// # use open_metrics_client::metrics::counter::{Atomic, Counter};
29/// # use open_metrics_client::metrics::family::Family;
30/// # use open_metrics_client::registry::{Descriptor, Registry};
31/// #
32/// # let mut registry = Registry::default();
33/// let family = Family::<Vec<(String, String)>, Counter>::default();
34/// # registry.register(
35/// #   "my_counter",
36/// #   "This is my counter",
37/// #   family.clone(),
38/// # );
39///
40/// // Record a single HTTP GET request.
41/// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
42///
43/// # // Encode all metrics in the registry in the text format.
44/// # let mut buffer = vec![];
45/// # encode(&mut buffer, &registry).unwrap();
46/// #
47/// # let expected = "# HELP my_counter This is my counter.\n".to_owned() +
48/// #                "# TYPE my_counter counter\n" +
49/// #                "my_counter_total{method=\"GET\"} 1\n" +
50/// #                "# EOF\n";
51/// # assert_eq!(expected, String::from_utf8(buffer).unwrap());
52/// ```
53///
54/// ### [`Family`] with custom type for performance and/or type safety
55///
56/// Using `Encode` derive macro to generate
57/// [`Encode`](crate::encoding::text::Encode) implementation.
58///
59/// ```
60/// # use open_metrics_client::encoding::text::Encode;
61/// # use open_metrics_client::encoding::text::encode;
62/// # use open_metrics_client::metrics::counter::{Atomic, Counter};
63/// # use open_metrics_client::metrics::family::Family;
64/// # use open_metrics_client::registry::{Descriptor, Registry};
65/// # use std::io::Write;
66/// #
67/// # let mut registry = Registry::default();
68/// #[derive(Clone, Hash, PartialEq, Eq, Encode)]
69/// struct Labels {
70///   method: Method,
71/// };
72///
73/// #[derive(Clone, Hash, PartialEq, Eq, Encode)]
74/// enum Method {
75///   GET,
76///   PUT,
77/// };
78///
79/// let family = Family::<Labels, Counter>::default();
80/// # registry.register(
81/// #   "my_counter",
82/// #   "This is my counter",
83/// #   family.clone(),
84/// # );
85///
86/// // Record a single HTTP GET request.
87/// family.get_or_create(&Labels { method: Method::GET }).inc();
88/// #
89/// # // Encode all metrics in the registry in the text format.
90/// # let mut buffer = vec![];
91/// # encode(&mut buffer, &registry).unwrap();
92/// #
93/// # let expected = "# HELP my_counter This is my counter.\n".to_owned() +
94/// #                "# TYPE my_counter counter\n" +
95/// #                "my_counter_total{method=\"GET\"} 1\n" +
96/// #                "# EOF\n";
97/// # assert_eq!(expected, String::from_utf8(buffer).unwrap());
98/// ```
99// TODO: Consider exposing hash algorithm.
100pub struct Family<S, M, C = fn() -> M> {
101    metrics: Arc<RwLock<HashMap<S, M>>>,
102    /// Function that when called constructs a new metric.
103    ///
104    /// For most metric types this would simply be its [`Default`]
105    /// implementation set through [`Family::default`]. For metric types that
106    /// need custom construction logic like
107    /// [`Histogram`](crate::metrics::histogram::Histogram) in order to set
108    /// specific buckets, a custom constructor is set via
109    /// [`Family::new_with_constructor`].
110    constructor: C,
111}
112
113/// A constructor for creating new metrics in a [`Family`] when calling
114/// [`Family::get_or_create`]. Such constructor is provided via
115/// [`Family::new_with_constructor`].
116///
117/// This is mostly used when creating histograms using constructors that need to
118/// capture variables.
119///
120/// ```
121/// # use open_metrics_client::metrics::family::{Family, MetricConstructor};
122/// # use open_metrics_client::metrics::histogram::Histogram;
123/// struct CustomBuilder {
124///     buckets: Vec<f64>,
125/// }
126///
127/// impl MetricConstructor<Histogram> for CustomBuilder {
128///     fn new_metric(&self) -> Histogram {
129///         // When a new histogram is created, this function will be called.
130///         Histogram::new(self.buckets.iter().cloned())
131///     }
132/// }
133///
134/// let custom_builder = CustomBuilder { buckets: vec![0.0, 10.0, 100.0] };
135/// let metric = Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder);
136/// ```
137pub trait MetricConstructor<M> {
138    fn new_metric(&self) -> M;
139}
140
141/// In cases in which the explicit type of the metric is not required, it is
142/// posible to directly provide a closure even if it captures variables.
143///
144/// ```
145/// # use open_metrics_client::metrics::family::{Family};
146/// # use open_metrics_client::metrics::histogram::Histogram;
147/// let custom_buckets = vec![0.0, 10.0, 100.0];
148/// let metric = Family::<(), Histogram, _>::new_with_constructor(|| {
149///     Histogram::new(custom_buckets.clone().into_iter())
150/// });
151/// # metric.get_or_create(&());
152/// ```
153impl<M, F: Fn() -> M> MetricConstructor<M> for F {
154    fn new_metric(&self) -> M {
155        self()
156    }
157}
158
159impl<S: Clone + std::hash::Hash + Eq, M: Default> Default for Family<S, M> {
160    fn default() -> Self {
161        Self {
162            metrics: Arc::new(RwLock::new(Default::default())),
163            constructor: M::default,
164        }
165    }
166}
167
168impl<S: Clone + std::hash::Hash + Eq, M, C> Family<S, M, C> {
169    /// Create a metric family using a custom constructor to construct new
170    /// metrics.
171    ///
172    /// When calling [`Family::get_or_create`] a [`Family`] needs to be able to
173    /// construct a new metric in case none exists for the given label set. In
174    /// most cases, e.g. for [`Counter`](crate::metrics::counter::Counter)
175    /// [`Family`] can just use the [`Default::default`] implementation for the
176    /// metric type. For metric types such as
177    /// [`Histogram`](crate::metrics::histogram::Histogram) one might want
178    /// [`Family`] to construct a
179    /// [`Histogram`](crate::metrics::histogram::Histogram) with custom buckets
180    /// (see example below). For such case one can use this method. For more
181    /// involved constructors see [`MetricConstructor`].
182    ///
183    /// ```
184    /// # use open_metrics_client::metrics::family::Family;
185    /// # use open_metrics_client::metrics::histogram::{exponential_buckets, Histogram};
186    /// Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
187    ///     Histogram::new(exponential_buckets(1.0, 2.0, 10))
188    /// });
189    /// ```
190    pub fn new_with_constructor(constructor: C) -> Self {
191        Self {
192            metrics: Arc::new(RwLock::new(Default::default())),
193            constructor,
194        }
195    }
196}
197
198impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C> {
199    /// Access a metric with the given label set, creating it if one does not
200    /// yet exist.
201    ///
202    /// ```
203    /// # use open_metrics_client::metrics::counter::{Atomic, Counter};
204    /// # use open_metrics_client::metrics::family::Family;
205    /// #
206    /// let family = Family::<Vec<(String, String)>, Counter>::default();
207    ///
208    /// // Will create the metric with label `method="GET"` on first call and
209    /// // return a reference.
210    /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
211    ///
212    /// // Will return a reference to the existing metric on all subsequent
213    /// // calls.
214    /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
215    /// ```
216    pub fn get_or_create(&self, label_set: &S) -> OwningRef<RwLockReadGuard<HashMap<S, M>>, M> {
217        let read_guard = self.metrics.read().expect("Lock not to be poisoned.");
218        if let Ok(metric) =
219            OwningRef::new(read_guard).try_map(|metrics| metrics.get(label_set).ok_or(()))
220        {
221            return metric;
222        }
223
224        let mut write_guard = self.metrics.write().unwrap();
225        write_guard.insert(label_set.clone(), self.constructor.new_metric());
226
227        drop(write_guard);
228
229        let read_guard = self.metrics.read().unwrap();
230        OwningRef::new(read_guard).map(|metrics| {
231            metrics
232                .get(label_set)
233                .expect("Metric to exist after creating it.")
234        })
235    }
236
237    pub(crate) fn read(&self) -> RwLockReadGuard<HashMap<S, M>> {
238        self.metrics.read().unwrap()
239    }
240}
241
242impl<S, M, C: Clone> Clone for Family<S, M, C> {
243    fn clone(&self) -> Self {
244        Family {
245            metrics: self.metrics.clone(),
246            constructor: self.constructor.clone(),
247        }
248    }
249}
250
251impl<S, M: TypedMetric, C> TypedMetric for Family<S, M, C> {
252    const TYPE: MetricType = <M as TypedMetric>::TYPE;
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use crate::metrics::counter::Counter;
259    use crate::metrics::histogram::{exponential_buckets, Histogram};
260
261    #[test]
262    fn counter_family() {
263        let family = Family::<Vec<(String, String)>, Counter>::default();
264
265        family
266            .get_or_create(&vec![("method".to_string(), "GET".to_string())])
267            .inc();
268
269        assert_eq!(
270            1,
271            family
272                .get_or_create(&vec![("method".to_string(), "GET".to_string())])
273                .get()
274        );
275    }
276
277    #[test]
278    fn histogram_family() {
279        Family::<(), Histogram>::new_with_constructor(|| {
280            Histogram::new(exponential_buckets(1.0, 2.0, 10))
281        });
282    }
283
284    #[test]
285    fn histogram_family_with_struct_constructor() {
286        struct CustomBuilder {
287            custom_start: f64,
288        }
289        impl MetricConstructor<Histogram> for CustomBuilder {
290            fn new_metric(&self) -> Histogram {
291                Histogram::new(exponential_buckets(self.custom_start, 2.0, 10))
292            }
293        }
294
295        let custom_builder = CustomBuilder { custom_start: 1.0 };
296        Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder);
297    }
298}