diff --git a/src/collector.rs b/src/collector.rs new file mode 100644 index 00000000..cb0fe427 --- /dev/null +++ b/src/collector.rs @@ -0,0 +1,9 @@ +// TODO: Document + +use std::borrow::Cow; + +use crate::registry::Descriptor; + +pub trait Collector { + fn collect<'a>(&'a self) -> Box, Cow)> + 'a>; +} diff --git a/src/encoding/text.rs b/src/encoding/text.rs index 57914c5e..c492fd52 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -40,7 +40,7 @@ use std::ops::Deref; pub use prometheus_client_derive_text_encode::*; -pub fn encode(writer: &mut W, registry: &Registry) -> Result<(), std::io::Error> +pub fn encode(writer: &mut W, registry: &Registry) -> Result<(), std::io::Error> where W: Write, M: EncodeMetric, diff --git a/src/lib.rs b/src/lib.rs index b5edaac2..058832f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ //! //! [examples]: https://github.com/prometheus/client_rust/tree/master/examples +pub mod collector; pub mod encoding; pub mod metrics; pub mod registry; diff --git a/src/registry.rs b/src/registry.rs index dcb01926..5c4dbfad 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -4,6 +4,8 @@ use std::borrow::Cow; +use crate::collector::Collector; + /// A metric registry. /// /// First off one registers metrics with the registry via @@ -57,26 +59,37 @@ use std::borrow::Cow; /// # "# EOF\n"; /// # assert_eq!(expected, String::from_utf8(buffer).unwrap()); /// ``` -#[derive(Debug)] pub struct Registry> { prefix: Option, labels: Vec<(Cow<'static, str>, Cow<'static, str>)>, metrics: Vec<(Descriptor, M)>, + collectors: Vec>>, sub_registries: Vec>, } +impl std::fmt::Debug for Registry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Registry") + .field("prefix", &self.prefix) + .field("labels", &self.labels) + .field("sub_registries", &self.sub_registries) + .finish() + } +} + impl Default for Registry { fn default() -> Self { Self { prefix: None, labels: Default::default(), metrics: Default::default(), + collectors: Default::default(), sub_registries: vec![], } } } -impl Registry { +impl Registry { /// Creates a new default [`Registry`] with the given prefix. pub fn with_prefix(prefix: impl Into) -> Self { Self { @@ -151,22 +164,45 @@ impl Registry { metric: M, unit: Option, ) { - let name = name.into(); - let help = help.into() + "."; - let descriptor = Descriptor { - name: self - .prefix - .as_ref() - .map(|p| (p.clone().0 + "_" + name.as_str())) - .unwrap_or(name), - help, - unit, - labels: self.labels.clone(), - }; + let descriptor = + Descriptor::new(name, help, unit, self.prefix.as_ref(), self.labels.clone()); self.metrics.push((descriptor, metric)); } + // TODO Document + /// ``` + /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; + /// # use prometheus_client::registry::{Descriptor, Registry}; + /// # use prometheus_client::collector::Collector; + /// # use std::borrow::Cow; + /// # + /// struct MyCollector {} + /// + /// impl Collector for MyCollector { + /// fn collect<'a>(&'a self) -> Box, Cow)> + 'a> { + /// let c = Counter::default(); + /// let descriptor = Descriptor::new( + /// "my_counter", + /// "This is my counter", + /// None, + /// None, + /// vec![], + /// ); + /// Box::new(std::iter::once((Cow::Owned(descriptor), Cow::Owned(c)))) + /// } + /// } + /// + /// let my_collector = Box::new(MyCollector{}); + /// + /// let mut registry: Registry = Registry::default(); + /// + /// registry.register_collector(my_collector); + /// ``` + pub fn register_collector(&mut self, collector: Box>) { + self.collectors.push(collector); + } + // TODO: Update doc. /// Create a sub-registry to register metrics with a common prefix. /// @@ -238,40 +274,62 @@ impl Registry { .expect("sub_registries not to be empty.") } - pub fn iter(&self) -> RegistryIterator { + pub fn iter(&self) -> std::iter::Chain, CollectorIterator> { + return self.iter_metrics().chain(self.iter_collectors()); + } + + fn iter_metrics(&self) -> MetricIterator { let metrics = self.metrics.iter(); let sub_registries = self.sub_registries.iter(); - RegistryIterator { + MetricIterator { metrics, sub_registries, sub_registry: None, } } + + fn iter_collectors(&self) -> CollectorIterator { + let collectors = self.collectors.iter(); + let sub_registries = self.sub_registries.iter(); + CollectorIterator { + prefix: self.prefix.as_ref(), + labels: &self.labels, + + collector: None, + collectors, + + sub_collector_iter: None, + sub_registries, + } + } } /// Iterator iterating both the metrics registered directly with the registry as /// well as all metrics registered with sub-registries. -#[derive(Debug)] -pub struct RegistryIterator<'a, M> { +pub struct MetricIterator<'a, M> { metrics: std::slice::Iter<'a, (Descriptor, M)>, sub_registries: std::slice::Iter<'a, Registry>, - sub_registry: Option>>, + sub_registry: Option>>, } -impl<'a, M> Iterator for RegistryIterator<'a, M> { - type Item = &'a (Descriptor, M); +impl<'a, M: Clone> Iterator for MetricIterator<'a, M> { + // TODO: Is the cow needed here? + type Item = (Cow<'a, Descriptor>, Cow<'a, M>); fn next(&mut self) -> Option { - if let Some(metric) = self.metrics.next() { - return Some(metric); - } - loop { + if let Some((descriptor, metric)) = self.metrics.next() { + return Some((Cow::Borrowed(descriptor), Cow::Borrowed(metric))); + } + if let Some(metric) = self.sub_registry.as_mut().and_then(|i| i.next()) { return Some(metric); } - self.sub_registry = self.sub_registries.next().map(|r| Box::new(r.iter())); + self.sub_registry = self + .sub_registries + .next() + .map(|r| Box::new(r.iter_metrics())); if self.sub_registry.is_none() { break; @@ -282,22 +340,76 @@ impl<'a, M> Iterator for RegistryIterator<'a, M> { } } +// TODO: Document that this one enriches the descriptors. +pub struct CollectorIterator<'a, M: Clone> { + prefix: Option<&'a Prefix>, + labels: &'a [(Cow<'static, str>, Cow<'static, str>)], + + collector: Option, Cow<'a, M>)> + 'a>>, + collectors: std::slice::Iter<'a, Box>>, + + sub_collector_iter: Option>>, + sub_registries: std::slice::Iter<'a, Registry>, +} + +impl<'a, M: Clone> Iterator for CollectorIterator<'a, M> { + type Item = (Cow<'a, Descriptor>, Cow<'a, M>); + + fn next(&mut self) -> Option { + loop { + if let Some((descriptor, metric)) = self + .collector + .as_mut() + .and_then(|c| c.next()) + .or_else(|| self.sub_collector_iter.as_mut().and_then(|i| i.next())) + { + let Descriptor { + name, + help, + unit, + mut labels, + } = descriptor.into_owned(); + labels.extend_from_slice(self.labels); + let enriched_descriptor = Descriptor::new(name, help, unit, self.prefix, labels); + + return Some((Cow::Owned(enriched_descriptor), metric)); + } + + if let Some(collector) = self.collectors.next() { + self.collector = Some(collector.collect()); + continue; + } + + if let Some(collector_iter) = self + .sub_registries + .next() + .map(|r| Box::new(r.iter_collectors())) + { + self.sub_collector_iter = Some(collector_iter); + continue; + } + + return None; + } + } +} + #[derive(Clone, Debug)] -struct Prefix(String); +pub struct Prefix(String); -impl From for Prefix { - fn from(s: String) -> Self { - Prefix(s) +impl Prefix { + fn as_str(&self) -> &str { + self.0.as_str() } } -impl From for String { - fn from(p: Prefix) -> Self { - p.0 +impl From for Prefix { + fn from(s: String) -> Self { + Prefix(s) } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Descriptor { name: String, help: String, @@ -306,6 +418,28 @@ pub struct Descriptor { } impl Descriptor { + pub fn new, H: Into>( + name: N, + help: H, + unit: Option, + prefix: Option<&Prefix>, + labels: Vec<(Cow<'static, str>, Cow<'static, str>)>, + ) -> Self { + let mut name = name.into(); + if let Some(prefix) = prefix { + name.insert_str(0, prefix.as_str()); + } + + let help = help.into() + "."; + + Descriptor { + name, + help, + unit, + labels, + } + } + pub fn name(&self) -> &str { &self.name } @@ -326,7 +460,7 @@ impl Descriptor { /// Metric units recommended by Open Metrics. /// /// See [`Unit::Other`] to specify alternative units. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Unit { Amperes, Bytes,