diff --git a/CHANGELOG.md b/CHANGELOG.md index c429a118..65875346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.22.0] - unreleased + +### Changed + +- Simplify `Collector` `trait` by enabling `Collector::collect` to encode metrics directly with a `DescriptorEncoder`. + See [PR 149] for details. + +[PR 149]: https://github.com/prometheus/client_rust/pull/149 + ## [0.21.2] ### Added diff --git a/Cargo.toml b/Cargo.toml index a8bf0fee..8dcc2243 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prometheus-client" -version = "0.21.2" +version = "0.22.0" authors = ["Max Inden "] edition = "2021" description = "Open Metrics client library allowing users to natively instrument applications." diff --git a/src/collector.rs b/src/collector.rs index f55c4232..eb5bdeb0 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -2,12 +2,7 @@ //! //! See [`Collector`] for details. -use std::borrow::Cow; - -use crate::{ - registry::{Descriptor, LocalMetric}, - MaybeOwned, -}; +use crate::encoding::DescriptorEncoder; /// The [`Collector`] abstraction allows users to provide additional metrics and /// their description on each scrape. @@ -17,13 +12,30 @@ use crate::{ /// /// Register a [`Collector`] with a [`Registry`](crate::registry::Registry) via /// [`Registry::register_collector`](crate::registry::Registry::register_collector). +/// +/// ``` +/// # use prometheus_client::metrics::counter::ConstCounter; +/// # use prometheus_client::collector::Collector; +/// # use prometheus_client::encoding::{DescriptorEncoder, EncodeMetric}; +/// # +/// #[derive(Debug)] +/// struct MyCollector {} +/// +/// impl Collector for MyCollector { +/// fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> { +/// let counter = ConstCounter::new(42); +/// let metric_encoder = encoder.encode_descriptor( +/// "my_counter", +/// "some help", +/// None, +/// counter.metric_type(), +/// )?; +/// counter.encode(metric_encoder)?; +/// Ok(()) +/// } +/// } +/// ``` pub trait Collector: std::fmt::Debug + Send + Sync + 'static { /// Once the [`Collector`] is registered, this method is called on each scrape. - /// - /// Note that the return type allows you to either return owned (convenient) - /// or borrowed (performant) descriptions and metrics. - #[allow(clippy::type_complexity)] - fn collect<'a>( - &'a self, - ) -> Box, MaybeOwned<'a, Box>)> + 'a>; + fn encode(&self, encoder: DescriptorEncoder) -> Result<(), std::fmt::Error>; } diff --git a/src/encoding.rs b/src/encoding.rs index ac758e61..a8ad8615 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -4,6 +4,7 @@ pub use prometheus_client_derive_encode::*; use crate::metrics::exemplar::Exemplar; use crate::metrics::MetricType; +use crate::registry::{Prefix, Unit}; use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Write; @@ -13,12 +14,33 @@ use std::ops::Deref; pub mod protobuf; pub mod text; +macro_rules! for_both_mut { + ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { + match &mut $self.0 { + $inner::Text($pattern) => $fn, + #[cfg(feature = "protobuf")] + $inner::Protobuf($pattern) => $fn, + } + }; +} + +macro_rules! for_both { + ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { + match $self.0 { + $inner::Text($pattern) => $fn, + #[cfg(feature = "protobuf")] + $inner::Protobuf($pattern) => $fn, + } + }; +} + /// Trait implemented by each metric type, e.g. /// [`Counter`](crate::metrics::counter::Counter), to implement its encoding in /// the OpenMetric text format. pub trait EncodeMetric { /// Encode the given instance in the OpenMetrics text encoding. - fn encode(&self, encoder: MetricEncoder<'_, '_>) -> Result<(), std::fmt::Error>; + // TODO: Lifetimes on MetricEncoder needed? + fn encode(&self, encoder: MetricEncoder) -> Result<(), std::fmt::Error>; /// The OpenMetrics metric type of the instance. // One can not use [`TypedMetric`] directly, as associated constants are not @@ -36,59 +58,89 @@ impl EncodeMetric for Box { } } +/// Encoder for a Metric Descriptor. +#[derive(Debug)] +pub struct DescriptorEncoder<'a>(DescriptorEncoderInner<'a>); + +#[derive(Debug)] +enum DescriptorEncoderInner<'a> { + Text(text::DescriptorEncoder<'a>), + + #[cfg(feature = "protobuf")] + Protobuf(protobuf::DescriptorEncoder<'a>), +} + +impl<'a> From> for DescriptorEncoder<'a> { + fn from(e: text::DescriptorEncoder<'a>) -> Self { + Self(DescriptorEncoderInner::Text(e)) + } +} + +#[cfg(feature = "protobuf")] +impl<'a> From> for DescriptorEncoder<'a> { + fn from(e: protobuf::DescriptorEncoder<'a>) -> Self { + Self(DescriptorEncoderInner::Protobuf(e)) + } +} + +impl DescriptorEncoder<'_> { + pub(crate) fn with_prefix_and_labels<'s>( + &'s mut self, + prefix: Option<&'s Prefix>, + labels: &'s [(Cow<'static, str>, Cow<'static, str>)], + // TODO: result needed? + ) -> DescriptorEncoder<'s> { + for_both_mut!( + self, + DescriptorEncoderInner, + e, + e.with_prefix_and_labels(prefix, labels).into() + ) + } + + /// Encode a descriptor. + pub fn encode_descriptor<'s>( + &'s mut self, + name: &'s str, + help: &str, + unit: Option<&'s Unit>, + metric_type: MetricType, + ) -> Result, std::fmt::Error> { + for_both_mut!( + self, + DescriptorEncoderInner, + e, + Ok(e.encode_descriptor(name, help, unit, metric_type)?.into()) + ) + } +} + /// Encoder for a metric. -/// -// `MetricEncoder` does not take a trait parameter for `writer` and `labels` -// because `EncodeMetric` which uses `MetricEncoder` needs to be usable as a -// trait object in order to be able to register different metric types with a -// `Registry`. Trait objects can not use type parameters. -// -// TODO: Alternative solutions to the above are very much appreciated. #[derive(Debug)] -pub struct MetricEncoder<'a, 'b>(MetricEncoderInner<'a, 'b>); +pub struct MetricEncoder<'a>(MetricEncoderInner<'a>); #[derive(Debug)] -enum MetricEncoderInner<'a, 'b> { - Text(text::MetricEncoder<'a, 'b>), +enum MetricEncoderInner<'a> { + Text(text::MetricEncoder<'a>), #[cfg(feature = "protobuf")] Protobuf(protobuf::MetricEncoder<'a>), } -impl<'a, 'b> From> for MetricEncoder<'a, 'b> { - fn from(e: text::MetricEncoder<'a, 'b>) -> Self { +impl<'a> From> for MetricEncoder<'a> { + fn from(e: text::MetricEncoder<'a>) -> Self { Self(MetricEncoderInner::Text(e)) } } #[cfg(feature = "protobuf")] -impl<'a, 'b> From> for MetricEncoder<'a, 'b> { +impl<'a> From> for MetricEncoder<'a> { fn from(e: protobuf::MetricEncoder<'a>) -> Self { Self(MetricEncoderInner::Protobuf(e)) } } -macro_rules! for_both_mut { - ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { - match &mut $self.0 { - $inner::Text($pattern) => $fn, - #[cfg(feature = "protobuf")] - $inner::Protobuf($pattern) => $fn, - } - }; -} - -macro_rules! for_both { - ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { - match $self.0 { - $inner::Text($pattern) => $fn, - #[cfg(feature = "protobuf")] - $inner::Protobuf($pattern) => $fn, - } - }; -} - -impl<'a, 'b> MetricEncoder<'a, 'b> { +impl MetricEncoder<'_> { /// Encode a counter. pub fn encode_counter< S: EncodeLabelSet, @@ -132,10 +184,10 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { } /// Encode a metric family. - pub fn encode_family<'c, 'd, S: EncodeLabelSet>( - &'c mut self, - label_set: &'d S, - ) -> Result, std::fmt::Error> { + pub fn encode_family<'s, S: EncodeLabelSet>( + &'s mut self, + label_set: &'s S, + ) -> Result, std::fmt::Error> { for_both_mut!( self, MetricEncoderInner, diff --git a/src/encoding/protobuf.rs b/src/encoding/protobuf.rs index f37197e0..53b30c3a 100644 --- a/src/encoding/protobuf.rs +++ b/src/encoding/protobuf.rs @@ -30,67 +30,21 @@ pub mod openmetrics_data_model { include!(concat!(env!("OUT_DIR"), "/openmetrics.rs")); } -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; -use crate::metrics::exemplar::Exemplar; use crate::metrics::MetricType; -use crate::registry::Registry; +use crate::registry::{Registry, Unit}; +use crate::{metrics::exemplar::Exemplar, registry::Prefix}; use super::{EncodeCounterValue, EncodeExemplarValue, EncodeGaugeValue, EncodeLabelSet}; /// Encode the metrics registered with the provided [`Registry`] into MetricSet /// using the OpenMetrics protobuf format. pub fn encode(registry: &Registry) -> Result { - Ok(openmetrics_data_model::MetricSet { - metric_families: registry - .iter_metrics() - .map(|(desc, metric)| encode_metric(desc, metric.as_ref())) - .chain( - registry - .iter_collectors() - .map(|(desc, metric)| encode_metric(desc.as_ref(), metric.as_ref())), - ) - .collect::>()?, - }) -} - -fn encode_metric( - desc: &crate::registry::Descriptor, - metric: &(impl super::EncodeMetric + ?Sized), -) -> Result { - let mut family = openmetrics_data_model::MetricFamily { - name: desc.name().to_string(), - r#type: { - let metric_type: openmetrics_data_model::MetricType = - super::EncodeMetric::metric_type(metric).into(); - metric_type as i32 - }, - unit: if let Some(unit) = desc.unit() { - unit.as_str().to_string() - } else { - String::new() - }, - help: desc.help().to_string(), - ..Default::default() - }; - - let mut labels = vec![]; - desc.labels().encode( - LabelSetEncoder { - labels: &mut labels, - } - .into(), - )?; - - let encoder = MetricEncoder { - family: &mut family.metrics, - metric_type: super::EncodeMetric::metric_type(metric), - labels, - }; - - super::EncodeMetric::encode(metric, encoder.into())?; - - Ok(family) + let mut metric_set = openmetrics_data_model::MetricSet::default(); + let mut descriptor_encoder = DescriptorEncoder::new(&mut metric_set.metric_families).into(); + registry.encode(&mut descriptor_encoder)?; + Ok(metric_set) } impl From for openmetrics_data_model::MetricType { @@ -105,20 +59,100 @@ impl From for openmetrics_data_model::MetricType { } } +/// Metric Descriptor encoder for protobuf encoding. +/// +/// This is an inner type for [`super::DescriptorEncoder`]. +#[derive(Debug)] +pub(crate) struct DescriptorEncoder<'a> { + metric_families: &'a mut Vec, + prefix: Option<&'a Prefix>, + labels: &'a [(Cow<'static, str>, Cow<'static, str>)], +} + +impl DescriptorEncoder<'_> { + pub(crate) fn new( + metric_families: &mut Vec, + ) -> DescriptorEncoder { + DescriptorEncoder { + metric_families, + prefix: Default::default(), + labels: Default::default(), + } + } + + pub(crate) fn with_prefix_and_labels<'s>( + &'s mut self, + prefix: Option<&'s Prefix>, + labels: &'s [(Cow<'static, str>, Cow<'static, str>)], + ) -> DescriptorEncoder<'s> { + DescriptorEncoder { + prefix, + labels, + metric_families: self.metric_families, + } + } + + pub fn encode_descriptor<'s>( + &'s mut self, + name: &str, + help: &str, + unit: Option<&Unit>, + metric_type: MetricType, + ) -> Result, std::fmt::Error> { + let family = openmetrics_data_model::MetricFamily { + name: { + match self.prefix { + Some(prefix) => prefix.as_str().to_string() + "_" + name, + None => name.to_string(), + } + }, + r#type: { + let metric_type: openmetrics_data_model::MetricType = metric_type.into(); + metric_type as i32 + }, + unit: if let Some(unit) = unit { + unit.as_str().to_string() + } else { + String::new() + }, + help: help.to_string(), + ..Default::default() + }; + let mut labels = vec![]; + self.labels.encode( + LabelSetEncoder { + labels: &mut labels, + } + .into(), + )?; + self.metric_families.push(family); + + Ok(MetricEncoder { + family: &mut self + .metric_families + .last_mut() + .expect("previous push") + .metrics, + metric_type, + labels, + }) + } +} + /// Encoder for protobuf encoding. /// /// This is an inner type for [`super::MetricEncoder`]. #[derive(Debug)] -pub(crate) struct MetricEncoder<'a> { +pub(crate) struct MetricEncoder<'f> { /// OpenMetrics metric type of the metric. metric_type: MetricType, /// Vector of OpenMetrics metrics to which encoded metrics are added. - family: &'a mut Vec, + family: &'f mut Vec, /// Labels to be added to each metric. labels: Vec, } -impl<'a> MetricEncoder<'a> { +impl MetricEncoder<'_> { pub fn encode_counter< S: EncodeLabelSet, CounterValue: EncodeCounterValue, @@ -195,10 +229,10 @@ impl<'a> MetricEncoder<'a> { Ok(()) } - pub fn encode_family<'b, S: EncodeLabelSet>( - &'b mut self, + pub fn encode_family( + &mut self, label_set: &S, - ) -> Result, std::fmt::Error> { + ) -> Result { let mut labels = self.labels.clone(); label_set.encode( LabelSetEncoder { diff --git a/src/encoding/text.rs b/src/encoding/text.rs index dea22e05..adf42415 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -24,9 +24,10 @@ //! assert_eq!(expected, buffer); //! ``` -use crate::encoding::{EncodeExemplarValue, EncodeLabelSet, EncodeMetric}; +use crate::encoding::{EncodeExemplarValue, EncodeLabelSet}; use crate::metrics::exemplar::Exemplar; -use crate::registry::{Descriptor, Registry, Unit}; +use crate::metrics::MetricType; +use crate::registry::{Prefix, Registry, Unit}; use std::borrow::Cow; use std::collections::HashMap; @@ -38,80 +39,123 @@ pub fn encode(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Er where W: Write, { - for (desc, metric) in registry.iter_metrics() { - encode_metric(writer, desc, metric.as_ref())?; + registry.encode(&mut DescriptorEncoder::new(writer).into())?; + writer.write_str("# EOF\n")?; + Ok(()) +} + +pub(crate) struct DescriptorEncoder<'a> { + writer: &'a mut dyn Write, + prefix: Option<&'a Prefix>, + labels: &'a [(Cow<'static, str>, Cow<'static, str>)], +} + +impl<'a> std::fmt::Debug for DescriptorEncoder<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DescriptorEncoder").finish() } - for (desc, metric) in registry.iter_collectors() { - encode_metric(writer, desc.as_ref(), metric.as_ref())?; +} + +impl DescriptorEncoder<'_> { + pub(crate) fn new(writer: &mut dyn Write) -> DescriptorEncoder { + DescriptorEncoder { + writer, + prefix: Default::default(), + labels: Default::default(), + } } - writer.write_str("# EOF\n")?; + pub(crate) fn with_prefix_and_labels<'s>( + &'s mut self, + prefix: Option<&'s Prefix>, + labels: &'s [(Cow<'static, str>, Cow<'static, str>)], + ) -> DescriptorEncoder<'s> { + DescriptorEncoder { + prefix, + labels, + writer: self.writer, + } + } - Ok(()) -} + pub fn encode_descriptor<'s>( + &'s mut self, + name: &'s str, + help: &str, + unit: Option<&'s Unit>, + metric_type: MetricType, + ) -> Result, std::fmt::Error> { + self.writer.write_str("# HELP ")?; + if let Some(prefix) = self.prefix { + self.writer.write_str(prefix.as_str())?; + self.writer.write_str("_")?; + } + self.writer.write_str(name)?; + if let Some(unit) = unit { + self.writer.write_str("_")?; + self.writer.write_str(unit.as_str())?; + } + self.writer.write_str(" ")?; + self.writer.write_str(help)?; + self.writer.write_str("\n")?; -fn encode_metric( - writer: &mut W, - desc: &Descriptor, - metric: &(impl EncodeMetric + ?Sized), -) -> Result<(), std::fmt::Error> -where - W: Write, -{ - writer.write_str("# HELP ")?; - writer.write_str(desc.name())?; - if let Some(unit) = desc.unit() { - writer.write_str("_")?; - writer.write_str(unit.as_str())?; - } - writer.write_str(" ")?; - writer.write_str(desc.help())?; - writer.write_str("\n")?; - - writer.write_str("# TYPE ")?; - writer.write_str(desc.name())?; - if let Some(unit) = desc.unit() { - writer.write_str("_")?; - writer.write_str(unit.as_str())?; - } - writer.write_str(" ")?; - writer.write_str(EncodeMetric::metric_type(metric).as_str())?; - writer.write_str("\n")?; - - if let Some(unit) = desc.unit() { - writer.write_str("# UNIT ")?; - writer.write_str(desc.name())?; - writer.write_str("_")?; - writer.write_str(unit.as_str())?; - writer.write_str(" ")?; - writer.write_str(unit.as_str())?; - writer.write_str("\n")?; - } - - let encoder = MetricEncoder { - writer, - name: desc.name(), - unit: desc.unit(), - const_labels: desc.labels(), - family_labels: None, - } - .into(); - - EncodeMetric::encode(metric, encoder)?; + self.writer.write_str("# TYPE ")?; + if let Some(prefix) = self.prefix { + self.writer.write_str(prefix.as_str())?; + self.writer.write_str("_")?; + } + self.writer.write_str(name)?; + if let Some(unit) = unit { + self.writer.write_str("_")?; + self.writer.write_str(unit.as_str())?; + } + self.writer.write_str(" ")?; + self.writer.write_str(metric_type.as_str())?; + self.writer.write_str("\n")?; + + if let Some(unit) = unit { + self.writer.write_str("# UNIT ")?; + if let Some(prefix) = self.prefix { + self.writer.write_str(prefix.as_str())?; + self.writer.write_str("_")?; + } + self.writer.write_str(name)?; + self.writer.write_str("_")?; + self.writer.write_str(unit.as_str())?; + self.writer.write_str(" ")?; + self.writer.write_str(unit.as_str())?; + self.writer.write_str("\n")?; + } - Ok(()) + Ok(MetricEncoder { + writer: self.writer, + prefix: self.prefix, + name, + unit, + const_labels: self.labels, + family_labels: None, + }) + } } -/// Helper type for [`EncodeMetric`], see [`EncodeMetric::encode`]. -pub(crate) struct MetricEncoder<'a, 'b> { +/// Helper type for [`EncodeMetric`](super::EncodeMetric), see +/// [`EncodeMetric::encode`](super::EncodeMetric::encode). +/// +// `MetricEncoder` does not take a trait parameter for `writer` and `labels` +// because `EncodeMetric` which uses `MetricEncoder` needs to be usable as a +// trait object in order to be able to register different metric types with a +// `Registry`. Trait objects can not use type parameters. +// +// TODO: Alternative solutions to the above are very much appreciated. +pub(crate) struct MetricEncoder<'a> { writer: &'a mut dyn Write, + prefix: Option<&'a Prefix>, name: &'a str, - unit: &'a Option, + unit: Option<&'a Unit>, const_labels: &'a [(Cow<'static, str>, Cow<'static, str>)], - family_labels: Option<&'b dyn super::EncodeLabelSet>, + family_labels: Option<&'a dyn super::EncodeLabelSet>, } -impl<'a, 'b> std::fmt::Debug for MetricEncoder<'a, 'b> { +impl<'a> std::fmt::Debug for MetricEncoder<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut labels = String::new(); if let Some(l) = self.family_labels { @@ -120,6 +164,7 @@ impl<'a, 'b> std::fmt::Debug for MetricEncoder<'a, 'b> { f.debug_struct("Encoder") .field("name", &self.name) + .field("prefix", &self.prefix) .field("unit", &self.unit) .field("const_labels", &self.const_labels) .field("labels", &labels.as_str()) @@ -127,7 +172,7 @@ impl<'a, 'b> std::fmt::Debug for MetricEncoder<'a, 'b> { } } -impl<'a, 'b> MetricEncoder<'a, 'b> { +impl<'a> MetricEncoder<'a> { pub fn encode_counter< S: EncodeLabelSet, CounterValue: super::EncodeCounterValue, @@ -137,7 +182,7 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { v: &CounterValue, exemplar: Option<&Exemplar>, ) -> Result<(), std::fmt::Error> { - self.write_name_and_unit()?; + self.write_prefix_name_unit()?; self.write_suffix("total")?; @@ -163,7 +208,7 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { &mut self, v: &GaugeValue, ) -> Result<(), std::fmt::Error> { - self.write_name_and_unit()?; + self.write_prefix_name_unit()?; self.encode_labels::<()>(None)?; @@ -180,7 +225,7 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { } pub fn encode_info(&mut self, label_set: &S) -> Result<(), std::fmt::Error> { - self.write_name_and_unit()?; + self.write_prefix_name_unit()?; self.write_suffix("info")?; @@ -196,14 +241,15 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { /// Encode a set of labels. Used by wrapper metric types like /// [`Family`](crate::metrics::family::Family). - pub fn encode_family<'c, 'd, S: EncodeLabelSet>( - &'c mut self, - label_set: &'d S, - ) -> Result, std::fmt::Error> { + pub fn encode_family<'s, S: EncodeLabelSet>( + &'s mut self, + label_set: &'s S, + ) -> Result, std::fmt::Error> { debug_assert!(self.family_labels.is_none()); Ok(MetricEncoder { writer: self.writer, + prefix: self.prefix, name: self.name, unit: self.unit, const_labels: self.const_labels, @@ -218,14 +264,14 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { buckets: &[(f64, u64)], exemplars: Option<&HashMap>>, ) -> Result<(), std::fmt::Error> { - self.write_name_and_unit()?; + self.write_prefix_name_unit()?; self.write_suffix("sum")?; self.encode_labels::<()>(None)?; self.writer.write_str(" ")?; self.writer.write_str(dtoa::Buffer::new().format(sum))?; self.newline()?; - self.write_name_and_unit()?; + self.write_prefix_name_unit()?; self.write_suffix("count")?; self.encode_labels::<()>(None)?; self.writer.write_str(" ")?; @@ -236,7 +282,7 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { for (i, (upper_bound, count)) in buckets.iter().enumerate() { cummulative += count; - self.write_name_and_unit()?; + self.write_prefix_name_unit()?; self.write_suffix("bucket")?; if *upper_bound == f64::MAX { @@ -281,7 +327,11 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { fn newline(&mut self) -> Result<(), std::fmt::Error> { self.writer.write_str("\n") } - fn write_name_and_unit(&mut self) -> Result<(), std::fmt::Error> { + fn write_prefix_name_unit(&mut self) -> Result<(), std::fmt::Error> { + if let Some(prefix) = self.prefix { + self.writer.write_str(prefix.as_str())?; + self.writer.write_str("_")?; + } self.writer.write_str(self.name)?; if let Some(unit) = self.unit { self.writer.write_str("_")?; @@ -723,6 +773,139 @@ mod tests { parse_with_python_client(encoded); } + #[test] + fn sub_registry_with_prefix_and_label() { + let top_level_metric_name = "my_top_level_metric"; + let mut registry = Registry::default(); + let counter: Counter = Counter::default(); + registry.register(top_level_metric_name, "some help", counter.clone()); + + let prefix_1 = "prefix_1"; + let prefix_1_metric_name = "my_prefix_1_metric"; + let sub_registry = registry.sub_registry_with_prefix(prefix_1); + sub_registry.register(prefix_1_metric_name, "some help", counter.clone()); + + let prefix_1_1 = "prefix_1_1"; + let prefix_1_1_metric_name = "my_prefix_1_1_metric"; + let sub_sub_registry = sub_registry.sub_registry_with_prefix(prefix_1_1); + sub_sub_registry.register(prefix_1_1_metric_name, "some help", counter.clone()); + + let label_1_2 = (Cow::Borrowed("registry"), Cow::Borrowed("1_2")); + let prefix_1_2_metric_name = "my_prefix_1_2_metric"; + let sub_sub_registry = sub_registry.sub_registry_with_label(label_1_2.clone()); + sub_sub_registry.register(prefix_1_2_metric_name, "some help", counter.clone()); + + let labels_1_3 = vec![ + (Cow::Borrowed("label_1_3_1"), Cow::Borrowed("value_1_3_1")), + (Cow::Borrowed("label_1_3_2"), Cow::Borrowed("value_1_3_2")), + ]; + let prefix_1_3_metric_name = "my_prefix_1_3_metric"; + let sub_sub_registry = + sub_registry.sub_registry_with_labels(labels_1_3.clone().into_iter()); + sub_sub_registry.register(prefix_1_3_metric_name, "some help", counter.clone()); + + let prefix_1_3_1 = "prefix_1_3_1"; + let prefix_1_3_1_metric_name = "my_prefix_1_3_1_metric"; + let sub_sub_sub_registry = sub_sub_registry.sub_registry_with_prefix(prefix_1_3_1); + sub_sub_sub_registry.register(prefix_1_3_1_metric_name, "some help", counter.clone()); + + let prefix_2 = "prefix_2"; + let _ = registry.sub_registry_with_prefix(prefix_2); + + let prefix_3 = "prefix_3"; + let prefix_3_metric_name = "my_prefix_3_metric"; + let sub_registry = registry.sub_registry_with_prefix(prefix_3); + sub_registry.register(prefix_3_metric_name, "some help", counter); + + let mut encoded = String::new(); + encode(&mut encoded, ®istry).unwrap(); + + let expected = "# HELP my_top_level_metric some help.\n".to_owned() + + "# TYPE my_top_level_metric counter\n" + + "my_top_level_metric_total 0\n" + + "# HELP prefix_1_my_prefix_1_metric some help.\n" + + "# TYPE prefix_1_my_prefix_1_metric counter\n" + + "prefix_1_my_prefix_1_metric_total 0\n" + + "# HELP prefix_1_prefix_1_1_my_prefix_1_1_metric some help.\n" + + "# TYPE prefix_1_prefix_1_1_my_prefix_1_1_metric counter\n" + + "prefix_1_prefix_1_1_my_prefix_1_1_metric_total 0\n" + + "# HELP prefix_1_my_prefix_1_2_metric some help.\n" + + "# TYPE prefix_1_my_prefix_1_2_metric counter\n" + + "prefix_1_my_prefix_1_2_metric_total{registry=\"1_2\"} 0\n" + + "# HELP prefix_1_my_prefix_1_3_metric some help.\n" + + "# TYPE prefix_1_my_prefix_1_3_metric counter\n" + + "prefix_1_my_prefix_1_3_metric_total{label_1_3_1=\"value_1_3_1\",label_1_3_2=\"value_1_3_2\"} 0\n" + + "# HELP prefix_1_prefix_1_3_1_my_prefix_1_3_1_metric some help.\n" + + "# TYPE prefix_1_prefix_1_3_1_my_prefix_1_3_1_metric counter\n" + + "prefix_1_prefix_1_3_1_my_prefix_1_3_1_metric_total{label_1_3_1=\"value_1_3_1\",label_1_3_2=\"value_1_3_2\"} 0\n" + + "# HELP prefix_3_my_prefix_3_metric some help.\n" + + "# TYPE prefix_3_my_prefix_3_metric counter\n" + + "prefix_3_my_prefix_3_metric_total 0\n" + + "# EOF\n"; + assert_eq!(expected, encoded); + + parse_with_python_client(encoded); + } + + #[test] + fn sub_registry_collector() { + use crate::encoding::EncodeMetric; + + #[derive(Debug)] + struct Collector { + name: String, + } + + impl Collector { + fn new(name: impl Into) -> Self { + Self { name: name.into() } + } + } + + impl crate::collector::Collector for Collector { + fn encode( + &self, + mut encoder: crate::encoding::DescriptorEncoder, + ) -> Result<(), std::fmt::Error> { + let counter = crate::metrics::counter::ConstCounter::new(42); + let metric_encoder = encoder.encode_descriptor( + &self.name, + "some help", + None, + counter.metric_type(), + )?; + counter.encode(metric_encoder)?; + Ok(()) + } + } + + let mut registry = Registry::default(); + registry.register_collector(Box::new(Collector::new("top_level"))); + + let sub_registry = registry.sub_registry_with_prefix("prefix_1"); + sub_registry.register_collector(Box::new(Collector::new("sub_level"))); + + let sub_sub_registry = sub_registry.sub_registry_with_prefix("prefix_1_2"); + sub_sub_registry.register_collector(Box::new(Collector::new("sub_sub_level"))); + + let mut encoded = String::new(); + encode(&mut encoded, ®istry).unwrap(); + + let expected = "# HELP top_level some help\n".to_owned() + + "# TYPE top_level counter\n" + + "top_level_total 42\n" + + "# HELP prefix_1_sub_level some help\n" + + "# TYPE prefix_1_sub_level counter\n" + + "prefix_1_sub_level_total 42\n" + + "# HELP prefix_1_prefix_1_2_sub_sub_level some help\n" + + "# TYPE prefix_1_prefix_1_2_sub_sub_level counter\n" + + "prefix_1_prefix_1_2_sub_sub_level_total 42\n" + + "# EOF\n"; + assert_eq!(expected, encoded); + + parse_with_python_client(encoded); + } + fn parse_with_python_client(input: String) { pyo3::prepare_freethreaded_python(); diff --git a/src/lib.rs b/src/lib.rs index 69bac37a..cf39dc1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,28 +82,3 @@ pub mod collector; pub mod encoding; pub mod metrics; pub mod registry; - -/// Represents either borrowed or owned data. -/// -/// In contrast to [`std::borrow::Cow`] does not require -/// [`std::borrow::ToOwned`] or [`Clone`]respectively. -/// -/// Needed for [`collector::Collector`]. -#[derive(Debug)] -pub enum MaybeOwned<'a, T> { - /// Owned data - Owned(T), - /// Borrowed data - Borrowed(&'a T), -} - -impl<'a, T> std::ops::Deref for MaybeOwned<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - match self { - Self::Owned(t) => t, - Self::Borrowed(t) => t, - } - } -} diff --git a/src/metrics/family.rs b/src/metrics/family.rs index 15c3c9ab..2f23b198 100644 --- a/src/metrics/family.rs +++ b/src/metrics/family.rs @@ -6,7 +6,6 @@ use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder}; use super::{MetricType, TypedMetric}; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use std::cell::RefCell; use std::collections::HashMap; use std::sync::Arc; @@ -30,7 +29,7 @@ use std::sync::Arc; /// # use prometheus_client::encoding::text::encode; /// # use prometheus_client::metrics::counter::{Atomic, Counter}; /// # use prometheus_client::metrics::family::Family; -/// # use prometheus_client::registry::{Descriptor, Registry}; +/// # use prometheus_client::registry::Registry; /// # /// # let mut registry = Registry::default(); /// let family = Family::, Counter>::default(); @@ -65,7 +64,7 @@ use std::sync::Arc; /// # use prometheus_client::encoding::text::encode; /// # use prometheus_client::metrics::counter::{Atomic, Counter}; /// # use prometheus_client::metrics::family::Family; -/// # use prometheus_client::registry::{Descriptor, Registry}; +/// # use prometheus_client::registry::Registry; /// # use std::io::Write; /// # /// # let mut registry = Registry::default(); @@ -327,44 +326,6 @@ where } } -/// As a [`Family`], but constant, meaning it cannot change once created. -/// -/// Needed for advanced use-cases, e.g. in combination with [`Collector`](crate::collector::Collector). -/// -/// Note that a [`ConstFamily`], given that it is based on an [`Iterator`], can -/// only be [`EncodeMetric::encode`]d once. While consecutive -/// [`EncodeMetric::encode`] calls won't panic, they won't return any metrics as -/// the provided [`Iterator`] will return [`Iterator::next`] [`None`]. Thus you -/// should not return the same [`ConstFamily`] in more than one -/// [`Collector::collect`](crate::collector::Collector::collect) calls. -#[derive(Debug, Default)] -pub struct ConstFamily(RefCell); - -impl ConstFamily { - /// Creates a new [`ConstFamily`]. - pub fn new(iter: I) -> Self { - Self(RefCell::new(iter)) - } -} - -impl> EncodeMetric - for ConstFamily -{ - fn encode(&self, mut encoder: MetricEncoder<'_, '_>) -> Result<(), std::fmt::Error> { - let mut iter = self.0.borrow_mut(); - - for (label_set, m) in iter.by_ref() { - let encoder = encoder.encode_family(&label_set)?; - m.encode(encoder)?; - } - Ok(()) - } - - fn metric_type(&self) -> MetricType { - M::TYPE - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/registry.rs b/src/registry.rs index 0f252e3b..d2bb1972 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use crate::collector::Collector; -use crate::MaybeOwned; +use crate::encoding::{DescriptorEncoder, EncodeMetric}; /// A metric registry. /// @@ -168,9 +168,7 @@ impl Registry { metric: impl Metric, unit: Option, ) { - let descriptor = - Descriptor::new(name, help, unit, self.prefix.as_ref(), self.labels.clone()); - + let descriptor = Descriptor::new(name, help, unit); self.metrics.push((descriptor, Box::new(metric))); } @@ -178,26 +176,25 @@ impl Registry { /// /// ``` /// # use prometheus_client::metrics::counter::ConstCounter; - /// # use prometheus_client::registry::{Descriptor, Registry, LocalMetric}; + /// # use prometheus_client::registry::Registry; /// # use prometheus_client::collector::Collector; - /// # use prometheus_client::MaybeOwned; - /// # use std::borrow::Cow; + /// # use prometheus_client::encoding::{DescriptorEncoder, EncodeMetric}; /// # /// #[derive(Debug)] /// struct MyCollector {} /// /// impl Collector for MyCollector { - /// fn collect<'a>(&'a self) -> Box, MaybeOwned<'a, Box>)> + 'a> { - /// let c: Box = Box::new(ConstCounter::new(42)); - /// let descriptor = Descriptor::new( - /// "my_counter", - /// "This is my counter", - /// None, - /// None, - /// vec![], - /// ); - /// Box::new(std::iter::once((Cow::Owned(descriptor), MaybeOwned::Owned(c)))) - /// } + /// fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> { + /// let counter = ConstCounter::new(42); + /// let metric_encoder = encoder.encode_descriptor( + /// "my_counter", + /// "some help", + /// None, + /// counter.metric_type(), + /// )?; + /// counter.encode(metric_encoder)?; + /// Ok(()) + /// } /// } /// /// let my_collector = Box::new(MyCollector{}); @@ -289,149 +286,39 @@ impl Registry { .expect("sub_registries not to be empty.") } - pub(crate) fn iter_metrics(&self) -> MetricIterator { - let metrics = self.metrics.iter(); - let sub_registries = self.sub_registries.iter(); - MetricIterator { - metrics, - sub_registries, - sub_registry: None, + pub(crate) fn encode(&self, encoder: &mut DescriptorEncoder) -> Result<(), std::fmt::Error> { + for (descriptor, metric) in self.metrics.iter() { + let mut descriptor_encoder = + encoder.with_prefix_and_labels(self.prefix.as_ref(), &self.labels); + let metric_encoder = descriptor_encoder.encode_descriptor( + &descriptor.name, + &descriptor.help, + descriptor.unit.as_ref(), + EncodeMetric::metric_type(metric.as_ref()), + )?; + metric.encode(metric_encoder)?; } - } - - pub(crate) 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, + for collector in self.collectors.iter() { + let descriptor_encoder = + encoder.with_prefix_and_labels(self.prefix.as_ref(), &self.labels); + collector.encode(descriptor_encoder)?; } - } -} - -/// Iterator iterating both the metrics registered directly with the -/// [`Registry`] as well as all metrics registered with sub [`Registry`]s. -#[derive(Debug)] -pub struct MetricIterator<'a> { - metrics: std::slice::Iter<'a, (Descriptor, Box)>, - sub_registries: std::slice::Iter<'a, Registry>, - sub_registry: Option>>, -} - -impl<'a> Iterator for MetricIterator<'a> { - type Item = &'a (Descriptor, Box); - fn next(&mut self) -> Option { - loop { - if let Some(m) = self.metrics.next() { - return Some(m); - } - - 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_metrics())); - - if self.sub_registry.is_none() { - break; - } + for registry in self.sub_registries.iter() { + registry.encode(encoder)?; } - None - } -} - -/// Iterator iterating metrics retrieved from [`Collector`]s registered with the [`Registry`] or sub [`Registry`]s. -pub struct CollectorIterator<'a> { - prefix: Option<&'a Prefix>, - labels: &'a [(Cow<'static, str>, Cow<'static, str>)], - - #[allow(clippy::type_complexity)] - collector: Option< - Box, MaybeOwned<'a, Box>)> + 'a>, - >, - collectors: std::slice::Iter<'a, Box>, - - sub_collector_iter: Option>>, - sub_registries: std::slice::Iter<'a, Registry>, -} - -impl<'a> std::fmt::Debug for CollectorIterator<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CollectorIterator") - .field("prefix", &self.prefix) - .field("labels", &self.labels) - .finish() - } -} - -impl<'a> Iterator for CollectorIterator<'a> { - type Item = (Cow<'a, Descriptor>, MaybeOwned<'a, Box>); - - fn next(&mut self) -> Option { - loop { - if let Some(m) = self - .collector - .as_mut() - .and_then(|c| c.next()) - .or_else(|| self.sub_collector_iter.as_mut().and_then(|i| i.next())) - .map(|(descriptor, metric)| { - if self.prefix.is_some() || !self.labels.is_empty() { - let Descriptor { - name, - help, - unit, - labels, - } = descriptor.as_ref(); - let mut labels = labels.to_vec(); - labels.extend_from_slice(self.labels); - let enriched_descriptor = - Descriptor::new(name, help, unit.to_owned(), self.prefix, labels); - - Some((Cow::Owned(enriched_descriptor), metric)) - } else { - Some((descriptor, metric)) - } - }) - { - return m; - } - - 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; - } + Ok(()) } } /// Metric prefix #[derive(Clone, Debug)] -pub struct Prefix(String); +pub(crate) struct Prefix(String); impl Prefix { - fn as_str(&self) -> &str { + pub(crate) fn as_str(&self) -> &str { self.0.as_str() } } @@ -444,57 +331,21 @@ impl From for Prefix { /// OpenMetrics metric descriptor. #[derive(Debug, Clone)] -pub struct Descriptor { +struct Descriptor { name: String, help: String, unit: Option, - labels: Vec<(Cow<'static, str>, Cow<'static, str>)>, } impl Descriptor { /// Create new [`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(0, '_'); - name.insert_str(0, prefix.as_str()); - } - - let help = help.into() + "."; - - Descriptor { - name, - help, + fn new, H: Into>(name: N, help: H, unit: Option) -> Self { + Self { + name: name.into(), + help: help.into() + ".", unit, - labels, } } - - /// Returns the name of the OpenMetrics metric [`Descriptor`]. - pub fn name(&self) -> &str { - &self.name - } - - /// Returns the help text of the OpenMetrics metric [`Descriptor`]. - pub fn help(&self) -> &str { - &self.help - } - - /// Returns the unit of the OpenMetrics metric [`Descriptor`]. - pub fn unit(&self) -> &Option { - &self.unit - } - - /// Returns the label set of the OpenMetrics metric [`Descriptor`]. - pub fn labels(&self) -> &[(Cow<'static, str>, Cow<'static, str>)] { - &self.labels - } } /// Metric units recommended by Open Metrics. @@ -538,160 +389,3 @@ pub trait Metric: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug impl Metric for T where T: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug + 'static {} - -/// Similar to [`Metric`], but without the [`Send`] and [`Sync`] requirement. -pub trait LocalMetric: crate::encoding::EncodeMetric + std::fmt::Debug {} - -impl LocalMetric for T where T: crate::encoding::EncodeMetric + std::fmt::Debug {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::metrics::counter::Counter; - - #[test] - fn constructors() { - let counter_name = "test_counter"; - let prefix = "test_prefix"; - let labels = vec![ - (Cow::Borrowed("global_label_1"), Cow::Borrowed("value_1")), - (Cow::Borrowed("global_label_1"), Cow::Borrowed("value_2")), - ]; - // test with_prefix constructor - let mut registry = Registry::with_prefix(prefix); - let counter: Counter = Counter::default(); - registry.register(counter_name, "some help", counter); - - assert_eq!( - Some((prefix.to_string() + "_" + counter_name, vec![])), - registry - .iter_metrics() - .map(|(desc, _)| (desc.name.clone(), desc.labels.clone())) - .next() - ); - - // test with_labels constructor - let mut registry = Registry::with_labels(labels.clone().into_iter()); - let counter: Counter = Counter::default(); - registry.register(counter_name, "some help", counter); - assert_eq!( - Some((counter_name.to_string(), labels.clone())), - registry - .iter_metrics() - .map(|(desc, _)| (desc.name.clone(), desc.labels.clone())) - .next() - ); - - // test with_prefix_and_labels constructor - let mut registry = Registry::with_prefix_and_labels(prefix, labels.clone().into_iter()); - let counter: Counter = Counter::default(); - registry.register(counter_name, "some help", counter); - - assert_eq!( - Some((prefix.to_string() + "_" + counter_name, labels)), - registry - .iter_metrics() - .map(|(desc, _)| (desc.name.clone(), desc.labels.clone())) - .next() - ); - } - - #[test] - fn register_and_iterate() { - let mut registry = Registry::default(); - let counter: Counter = Counter::default(); - registry.register("my_counter", "My counter", counter); - - assert_eq!(1, registry.iter_metrics().count()) - } - - #[test] - fn sub_registry_with_prefix_and_label() { - let top_level_metric_name = "my_top_level_metric"; - let mut registry = Registry::default(); - let counter: Counter = Counter::default(); - registry.register(top_level_metric_name, "some help", counter.clone()); - - let prefix_1 = "prefix_1"; - let prefix_1_metric_name = "my_prefix_1_metric"; - let sub_registry = registry.sub_registry_with_prefix(prefix_1); - sub_registry.register(prefix_1_metric_name, "some help", counter.clone()); - - let prefix_1_1 = "prefix_1_1"; - let prefix_1_1_metric_name = "my_prefix_1_1_metric"; - let sub_sub_registry = sub_registry.sub_registry_with_prefix(prefix_1_1); - sub_sub_registry.register(prefix_1_1_metric_name, "some help", counter.clone()); - - let label_1_2 = (Cow::Borrowed("registry"), Cow::Borrowed("1_2")); - let prefix_1_2_metric_name = "my_prefix_1_2_metric"; - let sub_sub_registry = sub_registry.sub_registry_with_label(label_1_2.clone()); - sub_sub_registry.register(prefix_1_2_metric_name, "some help", counter.clone()); - - let labels_1_3 = vec![ - (Cow::Borrowed("label_1_3_1"), Cow::Borrowed("value_1_3_1")), - (Cow::Borrowed("label_1_3_2"), Cow::Borrowed("value_1_3_2")), - ]; - let prefix_1_3_metric_name = "my_prefix_1_3_metric"; - let sub_sub_registry = - sub_registry.sub_registry_with_labels(labels_1_3.clone().into_iter()); - sub_sub_registry.register(prefix_1_3_metric_name, "some help", counter.clone()); - - let prefix_1_3_1 = "prefix_1_3_1"; - let prefix_1_3_1_metric_name = "my_prefix_1_3_1_metric"; - let sub_sub_sub_registry = sub_sub_registry.sub_registry_with_prefix(prefix_1_3_1); - sub_sub_sub_registry.register(prefix_1_3_1_metric_name, "some help", counter.clone()); - - let prefix_2 = "prefix_2"; - let _ = registry.sub_registry_with_prefix(prefix_2); - - let prefix_3 = "prefix_3"; - let prefix_3_metric_name = "my_prefix_3_metric"; - let sub_registry = registry.sub_registry_with_prefix(prefix_3); - sub_registry.register(prefix_3_metric_name, "some help", counter); - - let mut metric_iter = registry - .iter_metrics() - .map(|(desc, _)| (desc.name.clone(), desc.labels.clone())); - assert_eq!( - Some((top_level_metric_name.to_string(), vec![])), - metric_iter.next() - ); - assert_eq!( - Some((prefix_1.to_string() + "_" + prefix_1_metric_name, vec![])), - metric_iter.next() - ); - assert_eq!( - Some(( - prefix_1.to_string() + "_" + prefix_1_1 + "_" + prefix_1_1_metric_name, - vec![] - )), - metric_iter.next() - ); - assert_eq!( - Some(( - prefix_1.to_string() + "_" + prefix_1_2_metric_name, - vec![label_1_2.clone()] - )), - metric_iter.next() - ); - assert_eq!( - Some(( - prefix_1.to_string() + "_" + prefix_1_3_metric_name, - labels_1_3.clone() - )), - metric_iter.next() - ); - assert_eq!( - Some(( - prefix_1.to_string() + "_" + prefix_1_3_1 + "_" + prefix_1_3_1_metric_name, - labels_1_3.clone() - )), - metric_iter.next() - ); - // No metric was registered with prefix 2. - assert_eq!( - Some((prefix_3.to_string() + "_" + prefix_3_metric_name, vec![])), - metric_iter.next() - ); - } -}