Skip to main content

iroh_metrics/
registry.rs

1//! Registry to register metrics groups and encode them in the OpenMetrics text format.
2
3use std::{
4    borrow::Cow,
5    fmt::{self, Write},
6    ops::Deref,
7    sync::{Arc, RwLock},
8};
9
10use portable_atomic::{AtomicU64, Ordering};
11
12use crate::{Error, MetricsGroup, MetricsGroupSet, encoding::write_eof};
13
14/// A registry for [`MetricsGroup`].
15#[derive(Debug, Default)]
16pub struct Registry {
17    schema_version: Arc<AtomicU64>,
18    metrics: Vec<Arc<dyn MetricsGroup>>,
19    prefix: Option<Cow<'static, str>>,
20    labels: Vec<(Cow<'static, str>, Cow<'static, str>)>,
21    sub_registries: Vec<Registry>,
22}
23
24impl Registry {
25    /// Creates a subregistry where all metrics are prefixed with `prefix`.
26    ///
27    /// Returns a mutable reference to the subregistry.
28    pub fn sub_registry_with_prefix(&mut self, prefix: impl Into<Cow<'static, str>>) -> &mut Self {
29        let prefix = self.prefix.to_owned().map(|p| p + "_").unwrap_or_default() + prefix.into();
30        self.schema_version.fetch_add(1, Ordering::Relaxed);
31        let sub_registry = Registry {
32            schema_version: self.schema_version.clone(),
33            metrics: Default::default(),
34            prefix: Some(prefix),
35            labels: self.labels.clone(),
36            sub_registries: Default::default(),
37        };
38        self.sub_registries.push(sub_registry);
39        self.sub_registries.last_mut().unwrap()
40    }
41
42    /// Creates a subregistry where all metrics are labeled.
43    ///
44    /// Returns a mutable reference to the subregistry.
45    pub fn sub_registry_with_labels(
46        &mut self,
47        labels: impl IntoIterator<Item = (impl Into<Cow<'static, str>>, impl Into<Cow<'static, str>>)>,
48    ) -> &mut Self {
49        let mut all_labels = self.labels.clone();
50        all_labels.extend(labels.into_iter().map(|(k, v)| (k.into(), v.into())));
51        self.schema_version.fetch_add(1, Ordering::Relaxed);
52        let sub_registry = Registry {
53            schema_version: self.schema_version.clone(),
54            prefix: self.prefix.clone(),
55            labels: all_labels,
56            metrics: Default::default(),
57            sub_registries: Default::default(),
58        };
59        self.sub_registries.push(sub_registry);
60        self.sub_registries.last_mut().unwrap()
61    }
62
63    /// Creates a subregistry where all metrics have a `key=value` label.
64    ///
65    /// Returns a mutable reference to the subregistry.
66    pub fn sub_registry_with_label(
67        &mut self,
68        key: impl Into<Cow<'static, str>>,
69        value: impl Into<Cow<'static, str>>,
70    ) -> &mut Self {
71        self.sub_registry_with_labels([(key, value)])
72    }
73
74    /// Registers a [`MetricsGroup`] into this registry.
75    pub fn register(&mut self, metrics_group: Arc<dyn MetricsGroup>) {
76        self.schema_version.fetch_add(1, Ordering::Relaxed);
77        self.metrics.push(metrics_group);
78    }
79
80    /// Registers a [`MetricsGroupSet`] into this registry.
81    pub fn register_all(&mut self, metrics_group_set: &impl MetricsGroupSet) {
82        for group in metrics_group_set.groups_cloned() {
83            self.register(group)
84        }
85    }
86
87    /// Registers a [`MetricsGroupSet`] into this registry, prefixing all metrics with the group set's name.
88    pub fn register_all_prefixed(&mut self, metrics_group_set: &impl MetricsGroupSet) {
89        let registry = self.sub_registry_with_prefix(metrics_group_set.name());
90        registry.register_all(metrics_group_set)
91    }
92
93    /// Encodes all metrics in the OpenMetrics text format.
94    ///
95    /// This does not write the terminal `# EOF\n` string to `writer`.
96    /// You can use [`encode_openmetrics_eof`] to do that.
97    ///
98    /// [`encode_openmetrics_eof`]: crate::encoding::encode_openmetrics_eof
99    pub fn encode_openmetrics_to_writer(&self, writer: &mut impl Write) -> fmt::Result {
100        for group in &self.metrics {
101            group.encode_openmetrics(writer, self.prefix.as_deref(), &self.labels)?;
102        }
103
104        for sub in self.sub_registries.iter() {
105            sub.encode_openmetrics_to_writer(writer)?;
106        }
107        Ok(())
108    }
109
110    /// Returns the current schema version of this registry.
111    pub fn schema_version(&self) -> u64 {
112        self.schema_version.load(Ordering::Relaxed)
113    }
114
115    /// Encodes the schema of all registered metrics into the provided schema builder.
116    pub fn encode_schema(&self, schema: &mut crate::encoding::Schema) {
117        for group in &self.metrics {
118            group.encode_schema(schema, self.prefix.as_deref(), &self.labels);
119        }
120
121        for sub in self.sub_registries.iter() {
122            sub.encode_schema(schema);
123        }
124    }
125
126    /// Encodes the current values of all registered metrics into the provided values builder.
127    pub fn encode_values(&self, values: &mut crate::encoding::Values) {
128        for group in &self.metrics {
129            group.encode_values(values);
130        }
131
132        for sub in self.sub_registries.iter() {
133            sub.encode_values(values);
134        }
135    }
136}
137
138/// Helper trait to abstract over different ways to access metrics.
139pub trait MetricsSource: Send + 'static {
140    /// Encodes all metrics into a string in the OpenMetrics text format.
141    ///
142    /// This is expected to also write the terminal `# EOF\n` string expected
143    /// by the OpenMetrics format.
144    fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error>;
145
146    /// Encodes the metrics in the OpenMetrics text format into a newly allocated string.
147    ///
148    /// See also [`Self::encode_openmetrics`].
149    fn encode_openmetrics_to_string(&self) -> Result<String, Error> {
150        let mut s = String::new();
151        self.encode_openmetrics(&mut s)?;
152        Ok(s)
153    }
154}
155
156impl MetricsSource for Registry {
157    fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> {
158        self.encode_openmetrics_to_writer(writer)?;
159        write_eof(writer)?;
160        Ok(())
161    }
162}
163
164/// A cloneable [`Registry`] in a read-write lock.
165///
166/// Useful if you need mutable access to a registry, while also using the services
167/// defined in [`crate::service`].
168pub type RwLockRegistry = Arc<RwLock<Registry>>;
169
170impl MetricsSource for RwLockRegistry {
171    fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> {
172        let inner = self.read().expect("poisoned");
173        inner.encode_openmetrics(writer)
174    }
175}
176
177impl MetricsSource for Arc<Registry> {
178    fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> {
179        Arc::deref(self).encode_openmetrics(writer)
180    }
181}