Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(collector): have Registry:encode do the encoding #149

Merged
merged 9 commits into from
Jul 12, 2023
Merged
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "prometheus-client"
version = "0.21.2"
version = "0.22.0"
authors = ["Max Inden <mail@max-inden.de>"]
edition = "2021"
description = "Open Metrics client library allowing users to natively instrument applications."
Expand Down
38 changes: 25 additions & 13 deletions src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<dyn Iterator<Item = (Cow<'a, Descriptor>, MaybeOwned<'a, Box<dyn LocalMetric>>)> + 'a>;
fn encode(&self, encoder: DescriptorEncoder) -> Result<(), std::fmt::Error>;
}
130 changes: 91 additions & 39 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -36,59 +58,89 @@ impl EncodeMetric for Box<dyn EncodeMetric> {
}
}

/// 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<text::DescriptorEncoder<'a>> for DescriptorEncoder<'a> {
fn from(e: text::DescriptorEncoder<'a>) -> Self {
Self(DescriptorEncoderInner::Text(e))
}
}

#[cfg(feature = "protobuf")]
impl<'a> From<protobuf::DescriptorEncoder<'a>> 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<MetricEncoder<'s>, 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<text::MetricEncoder<'a, 'b>> for MetricEncoder<'a, 'b> {
fn from(e: text::MetricEncoder<'a, 'b>) -> Self {
impl<'a> From<text::MetricEncoder<'a>> for MetricEncoder<'a> {
fn from(e: text::MetricEncoder<'a>) -> Self {
Self(MetricEncoderInner::Text(e))
}
}

#[cfg(feature = "protobuf")]
impl<'a, 'b> From<protobuf::MetricEncoder<'a>> for MetricEncoder<'a, 'b> {
impl<'a> From<protobuf::MetricEncoder<'a>> 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,
Expand Down Expand Up @@ -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<MetricEncoder<'c, 'd>, std::fmt::Error> {
pub fn encode_family<'s, S: EncodeLabelSet>(
&'s mut self,
label_set: &'s S,
) -> Result<MetricEncoder<'s>, std::fmt::Error> {
for_both_mut!(
self,
MetricEncoderInner,
Expand Down
Loading