fastmetrics/registry/
subsystem.rs

1use std::{
2    borrow::Cow,
3    collections::hash_map::{self, HashMap},
4};
5
6use crate::{
7    encoder::EncodeMetric,
8    raw::{Metadata, MetricType, Unit},
9    registry::{is_lowercase, is_snake_case, RegistryError},
10};
11
12/// A subsystem within a registry that provides metric organization and labeling.
13///
14/// RegistrySystem represents a logical grouping of metrics within a registry. It allows:
15///
16/// - Hierarchical organization of metrics using nested subsystems
17/// - Adding constant labels specific to the subsystem
18/// - Automatic prefix handling for metric names
19///
20/// # Example
21///
22/// ```rust
23/// # use fastmetrics::{
24/// #    metrics::counter::Counter,
25/// #    registry::{Registry, RegistryError},
26/// # };
27/// #
28/// # fn main() -> Result<(), RegistryError> {
29/// let mut registry = Registry::default();
30/// assert_eq!(registry.namespace(), None);
31///
32/// // Create a subsystem for database metrics
33/// let db = registry.subsystem("database");
34/// assert_eq!(db.namespace(), "database");
35///
36/// // Register metrics into the subsystem
37/// let queries = <Counter>::default();
38/// db.register(
39///     "queries_total",
40///     "Total number of database query operation",
41///     queries.clone(),
42/// )?;
43///
44/// // Update metrics
45/// queries.inc();
46/// # Ok(())
47/// # }
48/// ```
49pub struct RegistrySystem {
50    // namespace: prefix + system_name
51    namespace: Cow<'static, str>,
52    const_labels: Vec<(Cow<'static, str>, Cow<'static, str>)>,
53
54    pub(crate) metrics: HashMap<Metadata, Box<dyn EncodeMetric + 'static>>,
55    pub(crate) subsystems: HashMap<Cow<'static, str>, RegistrySystem>,
56}
57
58/// A builder for constructing [`RegistrySystem`] instances with custom configuration.
59pub struct RegistrySystemBuilder {
60    prefix: Option<Cow<'static, str>>,
61    pub(crate) system_name: Cow<'static, str>,
62    const_labels: Vec<(Cow<'static, str>, Cow<'static, str>)>,
63}
64
65// Public methods
66impl RegistrySystemBuilder {
67    /// Sets `constant labels` for this subsystem.
68    ///
69    /// This method allows you to add constant labels specific to this subsystem,
70    /// which will be included with all metrics registered in this subsystem and
71    /// its child subsystems.
72    pub fn with_const_labels<N, V>(mut self, labels: impl IntoIterator<Item = (N, V)>) -> Self
73    where
74        N: Into<Cow<'static, str>>,
75        V: Into<Cow<'static, str>>,
76    {
77        self.const_labels =
78            labels.into_iter().map(|(name, value)| (name.into(), value.into())).collect();
79        self
80    }
81}
82
83// Private methods
84impl RegistrySystemBuilder {
85    fn new(system_name: impl Into<Cow<'static, str>>) -> Self {
86        Self { prefix: None, system_name: system_name.into(), const_labels: vec![] }
87    }
88
89    pub(crate) fn with_prefix(mut self, prefix: Option<impl Into<Cow<'static, str>>>) -> Self {
90        self.prefix = prefix.map(|prefix| prefix.into());
91        self
92    }
93
94    pub(crate) fn with_inherited_const_labels<N, V>(
95        mut self,
96        inherited_labels: impl IntoIterator<Item = (N, V)>,
97    ) -> Self
98    where
99        N: Into<Cow<'static, str>>,
100        V: Into<Cow<'static, str>>,
101    {
102        let labels = inherited_labels
103            .into_iter()
104            .map(|(name, value)| (name.into(), value.into()))
105            .chain(self.const_labels)
106            .collect::<Vec<_>>();
107        self.const_labels = labels;
108        self
109    }
110
111    pub(crate) fn build(self) -> RegistrySystem {
112        let namespace = match self.prefix {
113            Some(prefix) => Cow::Owned(format!("{}_{}", prefix, self.system_name)),
114            None => self.system_name,
115        };
116        RegistrySystem {
117            namespace,
118            const_labels: self.const_labels,
119            metrics: HashMap::default(),
120            subsystems: HashMap::default(),
121        }
122    }
123}
124
125impl RegistrySystem {
126    /// Creates a [`RegistrySystemBuilder`] to build [`RegistrySystem`] instance.
127    pub fn builder(system_name: impl Into<Cow<'static, str>>) -> RegistrySystemBuilder {
128        let system_name = system_name.into();
129        assert!(is_lowercase(&system_name), "invalid subsystem name, must be lowercase");
130        RegistrySystemBuilder::new(system_name)
131    }
132
133    /// Returns the current `namespace` of [`RegistrySystem`].
134    pub fn namespace(&self) -> &str {
135        &self.namespace
136    }
137
138    /// Returns the `constant labels` of [`RegistrySystem`].
139    pub fn constant_labels(&self) -> &[(Cow<'static, str>, Cow<'static, str>)] {
140        &self.const_labels
141    }
142}
143
144// register
145impl RegistrySystem {
146    /// Registers a metric into [`RegistrySystem`], similar to [Registry::register] method.
147    ///
148    /// [Registry::register]: crate::registry::Registry::register
149    pub fn register(
150        &mut self,
151        name: impl Into<Cow<'static, str>>,
152        help: impl Into<Cow<'static, str>>,
153        metric: impl EncodeMetric + 'static,
154    ) -> Result<&mut Self, RegistryError> {
155        self.do_register(name, help, None, metric)
156    }
157
158    /// Registers a metric with the specified unit into [`RegistrySystem`], similar to
159    /// [Registry::register_with_unit] method.
160    ///
161    /// [Registry::register_with_unit]: crate::registry::Registry::register_with_unit
162    pub fn register_with_unit(
163        &mut self,
164        name: impl Into<Cow<'static, str>>,
165        help: impl Into<Cow<'static, str>>,
166        unit: Unit,
167        metric: impl EncodeMetric + 'static,
168    ) -> Result<&mut Self, RegistryError> {
169        match metric.metric_type() {
170            MetricType::StateSet | MetricType::Info | MetricType::Unknown => {
171                return Err(RegistryError::MustHaveAnEmptyUnitString)
172            },
173            _ => {},
174        }
175        self.do_register(name, help, Some(unit), metric)
176    }
177
178    fn do_register(
179        &mut self,
180        name: impl Into<Cow<'static, str>>,
181        help: impl Into<Cow<'static, str>>,
182        unit: Option<Unit>,
183        metric: impl EncodeMetric + 'static,
184    ) -> Result<&mut Self, RegistryError> {
185        let name = name.into();
186        if !is_snake_case(&name) {
187            return Err(RegistryError::InvalidNameFormat);
188        }
189
190        let metadata = Metadata::new(name, help, metric.metric_type(), unit);
191        match self.metrics.entry(metadata) {
192            hash_map::Entry::Vacant(entry) => {
193                entry.insert(Box::new(metric));
194                Ok(self)
195            },
196            hash_map::Entry::Occupied(_) => Err(RegistryError::AlreadyExists),
197        }
198    }
199}
200
201// subsystem
202impl RegistrySystem {
203    /// Creates a subsystem to register metrics with a subsystem `name` (as a part of prefix).
204    /// If the subsystem `name` already exists, the previous created subsystem will be returned.
205    ///
206    /// Similar to [Registry::subsystem] method.
207    ///
208    /// [Registry::subsystem]: crate::registry::Registry::subsystem
209    pub fn subsystem(&mut self, name: impl Into<Cow<'static, str>>) -> &mut Self {
210        let name = name.into();
211        self.subsystems.entry(name).or_insert_with_key(|name| {
212            RegistrySystem::builder(name.clone())
213                // inherit prefix from this subsystem
214                .with_prefix(Some(self.namespace.clone()))
215                // inherit constant labels from this subsystem
216                .with_inherited_const_labels(self.const_labels.clone())
217                .build()
218        })
219    }
220
221    /// Attach a configurable subsystem.
222    /// If the subsystem `name` already exists, the previous created subsystem will be returned.
223    ///
224    /// Similar to [Registry::attach_subsystem] method.
225    ///
226    /// [Registry::attach_subsystem]: crate::registry::Registry::attach_subsystem
227    pub fn attach_subsystem(&mut self, builder: RegistrySystemBuilder) -> &mut RegistrySystem {
228        let name = builder.system_name.clone();
229        self.subsystems.entry(name).or_insert_with(|| {
230            builder
231                // inherit prefix from this subsystem
232                .with_prefix(Some(self.namespace.clone()))
233                // inherit constant labels from this subsystem
234                .with_inherited_const_labels(self.const_labels.clone())
235                .build()
236        })
237    }
238}