open_metrics_client/
registry.rs

1//! Metric registry implementation.
2//!
3//! See [`Registry`] for details.
4
5use std::borrow::Cow;
6use std::ops::Add;
7
8/// A metric registry.
9///
10/// First off one registers metrics with the registry via
11/// [`Registry::register`]. Later on the [`Registry`] is passed to an encoder
12/// collecting samples of each metric by iterating all metrics in the
13/// [`Registry`] via [`Registry::iter`].
14///
15/// [`Registry`] is the core building block, generic over the metric type being
16/// registered. Out of convenience, the generic type parameter is set to use
17/// dynamic dispatching by default to be able to register different types of
18/// metrics (e.g. [`Counter`](crate::metrics::counter::Counter) and
19/// [`Gauge`](crate::metrics::gauge::Gauge)) with the same registry. Advanced
20/// users might want to use their custom types.
21///
22/// ```
23/// # use open_metrics_client::encoding::text::{encode, EncodeMetric};
24/// # use open_metrics_client::metrics::counter::{Atomic as _, Counter};
25/// # use open_metrics_client::metrics::gauge::{Atomic as _, Gauge};
26/// # use open_metrics_client::registry::Registry;
27/// #
28/// // Create a metric registry.
29/// //
30/// // Note the angle brackets to make sure to use the default (dynamic
31/// // dispatched boxed metric) for the generic type parameter.
32/// let mut registry = <Registry>::default();
33///
34/// let counter: Counter = Counter::default();
35/// let gauge: Gauge = Gauge::default();
36///
37/// registry.register(
38///   "my_counter",
39///   "This is my counter",
40///   Box::new(counter.clone()),
41/// );
42/// registry.register(
43///   "my_gauge",
44///   "This is my gauge",
45///   Box::new(gauge.clone()),
46/// );
47///
48/// # // Encode all metrics in the registry in the text format.
49/// # let mut buffer = vec![];
50/// # encode(&mut buffer, &registry).unwrap();
51/// #
52/// # let expected = "# HELP my_counter This is my counter.\n".to_owned() +
53/// #                "# TYPE my_counter counter\n" +
54/// #                "my_counter_total 0\n" +
55/// #                "# HELP my_gauge This is my gauge.\n" +
56/// #                "# TYPE my_gauge gauge\n" +
57/// #                "my_gauge 0\n" +
58/// #                "# EOF\n";
59/// # assert_eq!(expected, String::from_utf8(buffer).unwrap());
60/// ```
61pub struct Registry<M = Box<dyn crate::encoding::text::SendEncodeMetric>> {
62    prefix: Option<Prefix>,
63    labels: Vec<(Cow<'static, str>, Cow<'static, str>)>,
64    metrics: Vec<(Descriptor, M)>,
65    sub_registries: Vec<Registry<M>>,
66}
67
68impl<M> Default for Registry<M> {
69    fn default() -> Self {
70        Self {
71            prefix: None,
72            labels: Default::default(),
73            metrics: Default::default(),
74            sub_registries: vec![],
75        }
76    }
77}
78
79impl<M> Registry<M> {
80    /// Register a metric with the [`Registry`].
81    ///
82    /// Note: In the Open Metrics text exposition format some metric types have
83    /// a special suffix, e.g. the
84    /// [`Counter`](crate::metrics::counter::Counter`) metric with `_total`.
85    /// These suffixes are inferred through the metric type and must not be
86    /// appended to the metric name manually by the user.
87    ///
88    /// Note: A full stop punctuation mark (`.`) is automatically added to the
89    /// passed help text.
90    ///
91    /// Use [`Registry::register_with_unit`] whenever a unit for the given
92    /// metric is known.
93    ///
94    /// ```
95    /// # use open_metrics_client::metrics::counter::{Atomic as _, Counter};
96    /// # use open_metrics_client::registry::{Registry, Unit};
97    /// #
98    /// let mut registry: Registry<Counter> = Registry::default();
99    /// let counter = Counter::default();
100    ///
101    /// registry.register("my_counter", "This is my counter", counter.clone());
102    /// ```
103    pub fn register<N: Into<String>, H: Into<String>>(&mut self, name: N, help: H, metric: M) {
104        self.priv_register(name, help, metric, None)
105    }
106
107    /// Register a metric with the [`Registry`] specifying the metric's unit.
108    ///
109    /// See [`Registry::register`] for additional documentation.
110    ///
111    /// Note: In the Open Metrics text exposition format units are appended to
112    /// the metric name. This is done automatically. Users must not append the
113    /// unit to the name manually.
114    ///
115    /// ```
116    /// # use open_metrics_client::metrics::counter::{Atomic as _, Counter};
117    /// # use open_metrics_client::registry::{Registry, Unit};
118    /// #
119    /// let mut registry: Registry<Counter> = Registry::default();
120    /// let counter = Counter::default();
121    ///
122    /// registry.register_with_unit(
123    ///   "my_counter",
124    ///   "This is my counter",
125    ///   Unit::Seconds,
126    ///   counter.clone(),
127    /// );
128    /// ```
129    pub fn register_with_unit<N: Into<String>, H: Into<String>>(
130        &mut self,
131        name: N,
132        help: H,
133        unit: Unit,
134        metric: M,
135    ) {
136        self.priv_register(name, help, metric, Some(unit))
137    }
138
139    fn priv_register<N: Into<String>, H: Into<String>>(
140        &mut self,
141        name: N,
142        help: H,
143        metric: M,
144        unit: Option<Unit>,
145    ) {
146        let name = name.into();
147        let help = help.into() + ".";
148        let descriptor = Descriptor {
149            name: self
150                .prefix
151                .as_ref()
152                .map(|p| (p.clone() + "_" + name.as_str()).into())
153                .unwrap_or(name),
154            help,
155            unit,
156            labels: self.labels.clone(),
157        };
158
159        self.metrics.push((descriptor, metric));
160    }
161
162    // TODO: Update doc.
163    /// Create a sub-registry to register metrics with a common prefix.
164    ///
165    /// Say you would like to prefix one set of metrics with `subsystem_a` and
166    /// one set of metrics with `subsystem_b`. Instead of prefixing each metric
167    /// with the corresponding subsystem string individually, you can create two
168    /// sub-registries like demonstrated below.
169    ///
170    /// This can be used to pass a prefixed sub-registry down to a subsystem of
171    /// your architecture automatically adding a prefix to each metric the
172    /// subsystem registers.
173    ///
174    /// ```
175    /// # use open_metrics_client::metrics::counter::{Atomic as _, Counter};
176    /// # use open_metrics_client::registry::{Registry, Unit};
177    /// #
178    /// let mut registry: Registry<Counter> = Registry::default();
179    ///
180    /// let subsystem_a_counter_1 = Counter::default();
181    /// let subsystem_a_counter_2 = Counter::default();
182    ///
183    /// let subsystem_a_registry = registry.sub_registry_with_prefix("subsystem_a");
184    /// registry.register("counter_1", "", subsystem_a_counter_1.clone());
185    /// registry.register("counter_2", "", subsystem_a_counter_2.clone());
186    ///
187    /// let subsystem_b_counter_1 = Counter::default();
188    /// let subsystem_b_counter_2 = Counter::default();
189    ///
190    /// let subsystem_a_registry = registry.sub_registry_with_prefix("subsystem_b");
191    /// registry.register("counter_1", "", subsystem_b_counter_1.clone());
192    /// registry.register("counter_2", "", subsystem_b_counter_2.clone());
193    /// ```
194    ///
195    /// See [`Registry::sub_registry_with_label`] for the same functionality,
196    /// but namespacing with a label instead of a metric name prefix.
197    pub fn sub_registry_with_prefix<P: AsRef<str>>(&mut self, prefix: P) -> &mut Self {
198        let sub_registry = Registry {
199            prefix: Some(
200                self.prefix
201                    .clone()
202                    .map(|p| p + "_")
203                    .unwrap_or_else(|| String::new().into())
204                    + prefix.as_ref(),
205            ),
206            labels: self.labels.clone(),
207            ..Default::default()
208        };
209
210        self.priv_sub_registry(sub_registry)
211    }
212
213    /// Like [`Registry::sub_registry_with_prefix`] but with a label instead.
214    pub fn sub_registry_with_label(
215        &mut self,
216        label: (Cow<'static, str>, Cow<'static, str>),
217    ) -> &mut Self {
218        let mut labels = self.labels.clone();
219        labels.push(label);
220        let sub_registry = Registry {
221            prefix: self.prefix.clone(),
222            labels,
223            ..Default::default()
224        };
225
226        self.priv_sub_registry(sub_registry)
227    }
228
229    fn priv_sub_registry(&mut self, sub_registry: Self) -> &mut Self {
230        self.sub_registries.push(sub_registry);
231
232        self.sub_registries
233            .last_mut()
234            .expect("sub_registries not to be empty.")
235    }
236
237    pub fn iter(&self) -> RegistryIterator<M> {
238        let metrics = self.metrics.iter();
239        let sub_registries = self.sub_registries.iter();
240        RegistryIterator {
241            metrics,
242            sub_registries,
243            sub_registry: None,
244        }
245    }
246}
247
248/// Iterator iterating both the metrics registered directly with the registry as
249/// well as all metrics registered with sub-registries.
250pub struct RegistryIterator<'a, M> {
251    metrics: std::slice::Iter<'a, (Descriptor, M)>,
252    sub_registries: std::slice::Iter<'a, Registry<M>>,
253    sub_registry: Option<Box<RegistryIterator<'a, M>>>,
254}
255
256impl<'a, M> Iterator for RegistryIterator<'a, M> {
257    type Item = &'a (Descriptor, M);
258
259    fn next(&mut self) -> Option<Self::Item> {
260        if let Some(metric) = self.metrics.next() {
261            return Some(metric);
262        }
263
264        loop {
265            if let Some(metric) = self.sub_registry.as_mut().and_then(|i| i.next()) {
266                return Some(metric);
267            }
268
269            self.sub_registry = self.sub_registries.next().map(|r| Box::new(r.iter()));
270
271            if self.sub_registry.is_none() {
272                break;
273            }
274        }
275
276        None
277    }
278}
279
280#[derive(Clone)]
281struct Prefix(String);
282
283impl From<String> for Prefix {
284    fn from(s: String) -> Self {
285        Prefix(s)
286    }
287}
288
289impl From<Prefix> for String {
290    fn from(p: Prefix) -> Self {
291        p.0
292    }
293}
294
295impl Add<&str> for Prefix {
296    type Output = Self;
297    fn add(self, rhs: &str) -> Self::Output {
298        Prefix(self.0 + rhs)
299    }
300}
301
302impl Add<&Prefix> for String {
303    type Output = Self;
304    fn add(self, rhs: &Prefix) -> Self::Output {
305        self + rhs.0.as_str()
306    }
307}
308
309pub struct Descriptor {
310    name: String,
311    help: String,
312    unit: Option<Unit>,
313    labels: Vec<(Cow<'static, str>, Cow<'static, str>)>,
314}
315
316impl Descriptor {
317    pub fn name(&self) -> &str {
318        &self.name
319    }
320
321    pub fn help(&self) -> &str {
322        &self.help
323    }
324
325    pub fn unit(&self) -> &Option<Unit> {
326        &self.unit
327    }
328
329    pub fn labels(&self) -> &[(Cow<'static, str>, Cow<'static, str>)] {
330        &self.labels
331    }
332}
333
334/// Metric units recommended by Open Metrics.
335///
336/// See [`Unit::Other`] to specify alternative units.
337pub enum Unit {
338    Amperes,
339    Bytes,
340    Celsius,
341    Grams,
342    Joules,
343    Meters,
344    Ratios,
345    Seconds,
346    Volts,
347    Other(String),
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353    use crate::metrics::counter::Counter;
354
355    #[test]
356    fn register_and_iterate() {
357        let mut registry: Registry<Counter> = Registry::default();
358        let counter = Counter::default();
359        registry.register("my_counter", "My counter", counter.clone());
360
361        assert_eq!(1, registry.iter().count())
362    }
363
364    #[test]
365    fn sub_registry_with_prefix_and_label() {
366        let top_level_metric_name = "my_top_level_metric";
367        let mut registry = Registry::<Counter>::default();
368        registry.register(top_level_metric_name, "some help", Default::default());
369
370        let prefix_1 = "prefix_1";
371        let prefix_1_metric_name = "my_prefix_1_metric";
372        let sub_registry = registry.sub_registry_with_prefix(prefix_1);
373        sub_registry.register(prefix_1_metric_name, "some help", Default::default());
374
375        let prefix_1_1 = "prefix_1_1";
376        let prefix_1_1_metric_name = "my_prefix_1_1_metric";
377        let sub_sub_registry = sub_registry.sub_registry_with_prefix(prefix_1_1);
378        sub_sub_registry.register(prefix_1_1_metric_name, "some help", Default::default());
379
380        let label_1_2 = (Cow::Borrowed("registry"), Cow::Borrowed("1_2"));
381        let prefix_1_2_metric_name = "my_prefix_1_2_metric";
382        let sub_sub_registry = sub_registry.sub_registry_with_label(label_1_2.clone());
383        sub_sub_registry.register(prefix_1_2_metric_name, "some help", Default::default());
384
385        let prefix_1_2_1 = "prefix_1_2_1";
386        let prefix_1_2_1_metric_name = "my_prefix_1_2_1_metric";
387        let sub_sub_sub_registry = sub_sub_registry.sub_registry_with_prefix(prefix_1_2_1);
388        sub_sub_sub_registry.register(prefix_1_2_1_metric_name, "some help", Default::default());
389
390        let prefix_2 = "prefix_2";
391        let _ = registry.sub_registry_with_prefix(prefix_2);
392
393        let prefix_3 = "prefix_3";
394        let prefix_3_metric_name = "my_prefix_3_metric";
395        let sub_registry = registry.sub_registry_with_prefix(prefix_3);
396        sub_registry.register(prefix_3_metric_name, "some help", Default::default());
397
398        let mut metric_iter = registry
399            .iter()
400            .map(|(desc, _)| (desc.name.clone(), desc.labels.clone()));
401        assert_eq!(
402            Some((top_level_metric_name.to_string(), vec![])),
403            metric_iter.next()
404        );
405        assert_eq!(
406            Some((prefix_1.to_string() + "_" + prefix_1_metric_name, vec![])),
407            metric_iter.next()
408        );
409        assert_eq!(
410            Some((
411                prefix_1.to_string() + "_" + prefix_1_1 + "_" + prefix_1_1_metric_name,
412                vec![]
413            )),
414            metric_iter.next()
415        );
416        assert_eq!(
417            Some((
418                prefix_1.to_string() + "_" + prefix_1_2_metric_name,
419                vec![label_1_2.clone()]
420            )),
421            metric_iter.next()
422        );
423        assert_eq!(
424            Some((
425                prefix_1.to_string() + "_" + prefix_1_2_1 + "_" + prefix_1_2_1_metric_name,
426                vec![label_1_2]
427            )),
428            metric_iter.next()
429        );
430        // No metric was registered with prefix 2.
431        assert_eq!(
432            Some((prefix_3.to_string() + "_" + prefix_3_metric_name, vec![])),
433            metric_iter.next()
434        );
435    }
436}