prometheus_client/
registry.rs

1//! Metric registry implementation.
2//!
3//! See [`Registry`] for details.
4
5use std::borrow::Cow;
6
7use crate::collector::Collector;
8use crate::encoding::{DescriptorEncoder, EncodeMetric};
9
10/// A metric registry.
11///
12/// First off one registers metrics with the registry via
13/// [`Registry::register`]. Later on the [`Registry`] is passed to an encoder
14/// collecting samples of each metric by iterating all metrics in the
15/// [`Registry`].
16///
17/// [`Registry`] is the core building block, generic over the metric type being
18/// registered. Out of convenience, the generic type parameter is set to use
19/// dynamic dispatching by default to be able to register different types of
20/// metrics (e.g. [`Counter`](crate::metrics::counter::Counter) and
21/// [`Gauge`](crate::metrics::gauge::Gauge)) with the same registry. Advanced
22/// users might want to use their custom types.
23///
24/// ```
25/// # use prometheus_client::encoding::text::encode;
26/// # use prometheus_client::metrics::counter::{Atomic as _, Counter};
27/// # use prometheus_client::metrics::gauge::{Atomic as _, Gauge};
28/// # use prometheus_client::registry::Registry;
29/// #
30/// // Create a metric registry.
31/// let mut registry = Registry::default();
32///
33/// let counter: Counter = Counter::default();
34/// let gauge: Gauge = Gauge::default();
35///
36/// registry.register(
37///   "my_counter",
38///   "This is my counter",
39///   counter.clone(),
40/// );
41/// registry.register(
42///   "my_gauge",
43///   "This is my gauge",
44///   gauge.clone(),
45/// );
46///
47/// # // Encode all metrics in the registry in the text format.
48/// # let mut buffer = String::new();
49/// # encode(&mut buffer, &registry).unwrap();
50/// #
51/// # let expected = "# HELP my_counter This is my counter.\n".to_owned() +
52/// #                "# TYPE my_counter counter\n" +
53/// #                "my_counter_total 0\n" +
54/// #                "# HELP my_gauge This is my gauge.\n" +
55/// #                "# TYPE my_gauge gauge\n" +
56/// #                "my_gauge 0\n" +
57/// #                "# EOF\n";
58/// # assert_eq!(expected, buffer);
59/// ```
60#[derive(Debug, Default)]
61pub struct Registry {
62    prefix: Option<Prefix>,
63    labels: Vec<(Cow<'static, str>, Cow<'static, str>)>,
64    metrics: Vec<(Descriptor, Box<dyn Metric>)>,
65    collectors: Vec<Box<dyn Collector>>,
66    sub_registries: Vec<Registry>,
67}
68
69impl Registry {
70    /// Creates a new default [`Registry`] with the given prefix.
71    pub fn with_prefix(prefix: impl Into<String>) -> Self {
72        Self {
73            prefix: Some(Prefix(prefix.into())),
74            ..Default::default()
75        }
76    }
77
78    /// Creates a new default [`Registry`] with the given labels.
79    pub fn with_labels(
80        labels: impl Iterator<Item = (Cow<'static, str>, Cow<'static, str>)>,
81    ) -> Self {
82        Self {
83            labels: labels.into_iter().collect(),
84            ..Default::default()
85        }
86    }
87
88    /// Creates a new default [`Registry`] with the given prefix and labels.
89    pub fn with_prefix_and_labels(
90        prefix: impl Into<String>,
91        labels: impl Iterator<Item = (Cow<'static, str>, Cow<'static, str>)>,
92    ) -> Self {
93        Self {
94            prefix: Some(Prefix(prefix.into())),
95            labels: labels.into_iter().collect(),
96            ..Default::default()
97        }
98    }
99
100    /// Register a metric with the [`Registry`].
101    ///
102    /// Note: In the Open Metrics text exposition format some metric types have
103    /// a special suffix, e.g. the
104    /// [`Counter`](crate::metrics::counter::Counter`) metric with `_total`.
105    /// These suffixes are inferred through the metric type and must not be
106    /// appended to the metric name manually by the user.
107    ///
108    /// Note: A full stop punctuation mark (`.`) is automatically added to the
109    /// passed help text.
110    ///
111    /// Use [`Registry::register_with_unit`] whenever a unit for the given
112    /// metric is known.
113    ///
114    /// ```
115    /// # use prometheus_client::metrics::counter::{Atomic as _, Counter};
116    /// # use prometheus_client::registry::{Registry, Unit};
117    /// #
118    /// let mut registry = Registry::default();
119    /// let counter: Counter = Counter::default();
120    ///
121    /// registry.register("my_counter", "This is my counter", counter.clone());
122    /// ```
123    pub fn register<N: Into<String>, H: Into<String>>(
124        &mut self,
125        name: N,
126        help: H,
127        metric: impl Metric,
128    ) {
129        self.priv_register(name, help, metric, None)
130    }
131
132    /// Register a metric with the [`Registry`] specifying the metric's unit.
133    ///
134    /// See [`Registry::register`] for additional documentation.
135    ///
136    /// Note: In the Open Metrics text exposition format units are appended to
137    /// the metric name. This is done automatically. Users must not append the
138    /// unit to the name manually.
139    ///
140    /// ```
141    /// # use prometheus_client::metrics::counter::{Atomic as _, Counter};
142    /// # use prometheus_client::registry::{Registry, Unit};
143    /// #
144    /// let mut registry = Registry::default();
145    /// let counter: Counter = Counter::default();
146    ///
147    /// registry.register_with_unit(
148    ///   "my_counter",
149    ///   "This is my counter",
150    ///   Unit::Seconds,
151    ///   counter.clone(),
152    /// );
153    /// ```
154    pub fn register_with_unit<N: Into<String>, H: Into<String>>(
155        &mut self,
156        name: N,
157        help: H,
158        unit: Unit,
159        metric: impl Metric,
160    ) {
161        self.priv_register(name, help, metric, Some(unit))
162    }
163
164    fn priv_register<N: Into<String>, H: Into<String>>(
165        &mut self,
166        name: N,
167        help: H,
168        metric: impl Metric,
169        unit: Option<Unit>,
170    ) {
171        let descriptor = Descriptor::new(name, help, unit);
172        self.metrics.push((descriptor, Box::new(metric)));
173    }
174
175    /// Register a [`Collector`].
176    ///
177    /// ```
178    /// # use prometheus_client::metrics::counter::ConstCounter;
179    /// # use prometheus_client::registry::Registry;
180    /// # use prometheus_client::collector::Collector;
181    /// # use prometheus_client::encoding::{DescriptorEncoder, EncodeMetric};
182    /// #
183    /// #[derive(Debug)]
184    /// struct MyCollector {}
185    ///
186    /// impl Collector for MyCollector {
187    ///     fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> {
188    ///         let counter = ConstCounter::new(42u64);
189    ///         let metric_encoder = encoder.encode_descriptor(
190    ///             "my_counter",
191    ///             "some help",
192    ///             None,
193    ///             counter.metric_type(),
194    ///         )?;
195    ///         counter.encode(metric_encoder)?;
196    ///         Ok(())
197    ///     }
198    /// }
199    ///
200    /// let my_collector = Box::new(MyCollector{});
201    ///
202    /// let mut registry = Registry::default();
203    ///
204    /// registry.register_collector(my_collector);
205    /// ```
206    pub fn register_collector(&mut self, collector: Box<dyn Collector>) {
207        self.collectors.push(collector);
208    }
209
210    /// Create a sub-registry to register metrics with a common prefix.
211    ///
212    /// Say you would like to prefix one set of metrics with `subsystem_a` and
213    /// one set of metrics with `subsystem_b`. Instead of prefixing each metric
214    /// with the corresponding subsystem string individually, you can create two
215    /// sub-registries like demonstrated below.
216    ///
217    /// This can be used to pass a prefixed sub-registry down to a subsystem of
218    /// your architecture automatically adding a prefix to each metric the
219    /// subsystem registers.
220    ///
221    /// ```
222    /// # use prometheus_client::metrics::counter::{Atomic as _, Counter};
223    /// # use prometheus_client::registry::{Registry, Unit};
224    /// #
225    /// let mut registry = Registry::default();
226    ///
227    /// let subsystem_a_counter_1: Counter = Counter::default();
228    /// let subsystem_a_counter_2: Counter = Counter::default();
229    ///
230    /// let subsystem_a_registry = registry.sub_registry_with_prefix("subsystem_a");
231    /// registry.register("counter_1", "", subsystem_a_counter_1.clone());
232    /// registry.register("counter_2", "", subsystem_a_counter_2.clone());
233    ///
234    /// let subsystem_b_counter_1: Counter = Counter::default();
235    /// let subsystem_b_counter_2: Counter = Counter::default();
236    ///
237    /// let subsystem_a_registry = registry.sub_registry_with_prefix("subsystem_b");
238    /// registry.register("counter_1", "", subsystem_b_counter_1.clone());
239    /// registry.register("counter_2", "", subsystem_b_counter_2.clone());
240    /// ```
241    ///
242    /// See [`Registry::sub_registry_with_label`] for the same functionality,
243    /// but namespacing with a label instead of a metric name prefix.
244    pub fn sub_registry_with_prefix<P: AsRef<str>>(&mut self, prefix: P) -> &mut Self {
245        let sub_registry = Registry {
246            prefix: Some(Prefix(
247                self.prefix.clone().map(|p| p.0 + "_").unwrap_or_default() + prefix.as_ref(),
248            )),
249            labels: self.labels.clone(),
250            ..Default::default()
251        };
252
253        self.priv_sub_registry(sub_registry)
254    }
255
256    /// Like [`Registry::sub_registry_with_prefix`] but with a label instead.
257    pub fn sub_registry_with_label(
258        &mut self,
259        label: (Cow<'static, str>, Cow<'static, str>),
260    ) -> &mut Self {
261        self.sub_registry_with_labels(std::iter::once(label))
262    }
263
264    /// Like [`Registry::sub_registry_with_prefix`] but with multiple labels instead.
265    pub fn sub_registry_with_labels(
266        &mut self,
267        labels: impl Iterator<Item = (Cow<'static, str>, Cow<'static, str>)>,
268    ) -> &mut Self {
269        let mut new_labels = self.labels.clone();
270        new_labels.extend(labels);
271
272        let sub_registry = Registry {
273            prefix: self.prefix.clone(),
274            labels: new_labels,
275            ..Default::default()
276        };
277
278        self.priv_sub_registry(sub_registry)
279    }
280
281    fn priv_sub_registry(&mut self, sub_registry: Self) -> &mut Self {
282        self.sub_registries.push(sub_registry);
283
284        self.sub_registries
285            .last_mut()
286            .expect("sub_registries not to be empty.")
287    }
288
289    pub(crate) fn encode(&self, encoder: &mut DescriptorEncoder) -> Result<(), std::fmt::Error> {
290        for (descriptor, metric) in self.metrics.iter() {
291            let mut descriptor_encoder =
292                encoder.with_prefix_and_labels(self.prefix.as_ref(), &self.labels);
293            let metric_encoder = descriptor_encoder.encode_descriptor(
294                &descriptor.name,
295                &descriptor.help,
296                descriptor.unit.as_ref(),
297                EncodeMetric::metric_type(metric.as_ref()),
298            )?;
299            metric.encode(metric_encoder)?;
300        }
301
302        for collector in self.collectors.iter() {
303            let descriptor_encoder =
304                encoder.with_prefix_and_labels(self.prefix.as_ref(), &self.labels);
305            collector.encode(descriptor_encoder)?;
306        }
307
308        for registry in self.sub_registries.iter() {
309            registry.encode(encoder)?;
310        }
311
312        Ok(())
313    }
314}
315
316/// Metric prefix
317#[derive(Clone, Debug)]
318pub(crate) struct Prefix(String);
319
320impl Prefix {
321    pub(crate) fn as_str(&self) -> &str {
322        self.0.as_str()
323    }
324}
325
326impl From<String> for Prefix {
327    fn from(s: String) -> Self {
328        Prefix(s)
329    }
330}
331
332/// OpenMetrics metric descriptor.
333#[derive(Debug, Clone)]
334struct Descriptor {
335    name: String,
336    help: String,
337    unit: Option<Unit>,
338}
339
340impl Descriptor {
341    /// Create new [`Descriptor`].
342    fn new<N: Into<String>, H: Into<String>>(name: N, help: H, unit: Option<Unit>) -> Self {
343        Self {
344            name: name.into(),
345            help: help.into() + ".",
346            unit,
347        }
348    }
349}
350
351/// Metric units recommended by Open Metrics.
352///
353/// See [`Unit::Other`] to specify alternative units.
354#[derive(Debug, Clone)]
355#[allow(missing_docs)]
356pub enum Unit {
357    Amperes,
358    Bytes,
359    Celsius,
360    Grams,
361    Joules,
362    Meters,
363    Ratios,
364    Seconds,
365    Volts,
366    Other(String),
367}
368
369impl Unit {
370    /// Returns the given Unit's str representation.
371    pub fn as_str(&self) -> &str {
372        match self {
373            Unit::Amperes => "amperes",
374            Unit::Bytes => "bytes",
375            Unit::Celsius => "celsius",
376            Unit::Grams => "grams",
377            Unit::Joules => "joules",
378            Unit::Meters => "meters",
379            Unit::Ratios => "ratios",
380            Unit::Seconds => "seconds",
381            Unit::Volts => "volts",
382            Unit::Other(other) => other.as_str(),
383        }
384    }
385}
386
387/// Super trait representing an abstract Prometheus metric.
388pub trait Metric: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug + 'static {}
389
390impl<T> Metric for T where T: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug + 'static
391{}