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, ®istry).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, ®istry).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}