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