Skip to content

Commit c619ad5

Browse files
authored
feat!: Introduce Collector abstraction (#82)
The `Collector` abstraction allows users to provide additional metrics and their description on each scrape. See also: - https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#hdr-Custom_Collectors_and_constant_Metrics - #49 - #29 Signed-off-by: Max Inden <[email protected]>
1 parent 2f72a1b commit c619ad5

File tree

10 files changed

+436
-110
lines changed

10 files changed

+436
-110
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.20.0] [unreleased]
8+
9+
### Added
10+
11+
- Introduce `Collector` abstraction allowing users to provide additional metrics
12+
and their description on each scrape. See [PR 82].
13+
14+
[PR 82]: https://github.com/prometheus/client_rust/pull/82
15+
716
## [0.19.0]
817

918
This is a large release including multiple breaking changes. Major user-facing

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "prometheus-client"
3-
version = "0.19.0"
3+
version = "0.20.0"
44
authors = ["Max Inden <[email protected]>"]
55
edition = "2021"
66
description = "Open Metrics client library allowing users to natively instrument applications."

src/collector.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//! Metric collector implementation.
2+
//!
3+
//! See [`Collector`] for details.
4+
5+
use std::borrow::Cow;
6+
7+
use crate::{
8+
registry::{Descriptor, LocalMetric},
9+
MaybeOwned,
10+
};
11+
12+
/// The [`Collector`] abstraction allows users to provide additional metrics and
13+
/// their description on each scrape.
14+
///
15+
/// An example use-case is an exporter that retrieves a set of operating system metrics
16+
/// ad-hoc on each scrape.
17+
///
18+
/// Register a [`Collector`] with a [`Registry`](crate::registry::Registry) via
19+
/// [`Registry::register_collector`](crate::registry::Registry::register_collector).
20+
pub trait Collector: std::fmt::Debug + Send + Sync + 'static {
21+
/// Once the [`Collector`] is registered, this method is called on each scrape.
22+
///
23+
/// Note that the return type allows you to either return owned (convenient)
24+
/// or borrowed (performant) descriptions and metrics.
25+
#[allow(clippy::type_complexity)]
26+
fn collect<'a>(
27+
&'a self,
28+
) -> Box<dyn Iterator<Item = (Cow<'a, Descriptor>, MaybeOwned<'a, Box<dyn LocalMetric>>)> + 'a>;
29+
}

src/encoding/protobuf.rs

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -41,45 +41,56 @@ use super::{EncodeCounterValue, EncodeExemplarValue, EncodeGaugeValue, EncodeLab
4141
/// Encode the metrics registered with the provided [`Registry`] into MetricSet
4242
/// using the OpenMetrics protobuf format.
4343
pub fn encode(registry: &Registry) -> Result<openmetrics_data_model::MetricSet, std::fmt::Error> {
44-
let mut metric_set = openmetrics_data_model::MetricSet::default();
45-
46-
for (desc, metric) in registry.iter() {
47-
let mut family = openmetrics_data_model::MetricFamily {
48-
name: desc.name().to_string(),
49-
r#type: {
50-
let metric_type: openmetrics_data_model::MetricType =
51-
super::EncodeMetric::metric_type(metric.as_ref()).into();
52-
metric_type as i32
53-
},
54-
unit: if let Some(unit) = desc.unit() {
55-
unit.as_str().to_string()
56-
} else {
57-
String::new()
58-
},
59-
help: desc.help().to_string(),
60-
..Default::default()
61-
};
62-
63-
let mut labels = vec![];
64-
desc.labels().encode(
65-
LabelSetEncoder {
66-
labels: &mut labels,
67-
}
68-
.into(),
69-
)?;
44+
Ok(openmetrics_data_model::MetricSet {
45+
metric_families: registry
46+
.iter_metrics()
47+
.map(|(desc, metric)| encode_metric(desc, metric.as_ref()))
48+
.chain(
49+
registry
50+
.iter_collectors()
51+
.map(|(desc, metric)| encode_metric(desc.as_ref(), metric.as_ref())),
52+
)
53+
.collect::<Result<_, std::fmt::Error>>()?,
54+
})
55+
}
7056

71-
let encoder = MetricEncoder {
72-
family: &mut family.metrics,
73-
metric_type: super::EncodeMetric::metric_type(metric.as_ref()),
57+
fn encode_metric(
58+
desc: &crate::registry::Descriptor,
59+
metric: &(impl super::EncodeMetric + ?Sized),
60+
) -> Result<openmetrics_data_model::MetricFamily, std::fmt::Error> {
61+
let mut family = openmetrics_data_model::MetricFamily {
62+
name: desc.name().to_string(),
63+
r#type: {
64+
let metric_type: openmetrics_data_model::MetricType =
65+
super::EncodeMetric::metric_type(metric).into();
66+
metric_type as i32
67+
},
68+
unit: if let Some(unit) = desc.unit() {
69+
unit.as_str().to_string()
70+
} else {
71+
String::new()
72+
},
73+
help: desc.help().to_string(),
74+
..Default::default()
75+
};
76+
77+
let mut labels = vec![];
78+
desc.labels().encode(
79+
LabelSetEncoder {
7480
labels: &mut labels,
75-
};
81+
}
82+
.into(),
83+
)?;
7684

77-
super::EncodeMetric::encode(metric.as_ref(), encoder.into())?;
85+
let encoder = MetricEncoder {
86+
family: &mut family.metrics,
87+
metric_type: super::EncodeMetric::metric_type(metric),
88+
labels: &mut labels,
89+
};
7890

79-
metric_set.metric_families.push(family);
80-
}
91+
super::EncodeMetric::encode(metric, encoder.into())?;
8192

82-
Ok(metric_set)
93+
Ok(family)
8394
}
8495

8596
impl From<MetricType> for openmetrics_data_model::MetricType {

src/encoding/text.rs

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
2727
use crate::encoding::{EncodeExemplarValue, EncodeLabelSet, EncodeMetric};
2828
use crate::metrics::exemplar::Exemplar;
29-
use crate::registry::{Registry, Unit};
29+
use crate::registry::{Descriptor, Registry, Unit};
3030

3131
use std::borrow::Cow;
3232
use std::collections::HashMap;
@@ -38,50 +38,66 @@ pub fn encode<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Er
3838
where
3939
W: Write,
4040
{
41-
for (desc, metric) in registry.iter() {
42-
writer.write_str("# HELP ")?;
43-
writer.write_str(desc.name())?;
44-
if let Some(unit) = desc.unit() {
45-
writer.write_str("_")?;
46-
writer.write_str(unit.as_str())?;
47-
}
48-
writer.write_str(" ")?;
49-
writer.write_str(desc.help())?;
50-
writer.write_str("\n")?;
41+
for (desc, metric) in registry.iter_metrics() {
42+
encode_metric(writer, desc, metric.as_ref())?;
43+
}
44+
for (desc, metric) in registry.iter_collectors() {
45+
encode_metric(writer, desc.as_ref(), metric.as_ref())?;
46+
}
47+
48+
writer.write_str("# EOF\n")?;
49+
50+
Ok(())
51+
}
5152

52-
writer.write_str("# TYPE ")?;
53+
fn encode_metric<W>(
54+
writer: &mut W,
55+
desc: &Descriptor,
56+
metric: &(impl EncodeMetric + ?Sized),
57+
) -> Result<(), std::fmt::Error>
58+
where
59+
W: Write,
60+
{
61+
writer.write_str("# HELP ")?;
62+
writer.write_str(desc.name())?;
63+
if let Some(unit) = desc.unit() {
64+
writer.write_str("_")?;
65+
writer.write_str(unit.as_str())?;
66+
}
67+
writer.write_str(" ")?;
68+
writer.write_str(desc.help())?;
69+
writer.write_str("\n")?;
70+
71+
writer.write_str("# TYPE ")?;
72+
writer.write_str(desc.name())?;
73+
if let Some(unit) = desc.unit() {
74+
writer.write_str("_")?;
75+
writer.write_str(unit.as_str())?;
76+
}
77+
writer.write_str(" ")?;
78+
writer.write_str(EncodeMetric::metric_type(metric).as_str())?;
79+
writer.write_str("\n")?;
80+
81+
if let Some(unit) = desc.unit() {
82+
writer.write_str("# UNIT ")?;
5383
writer.write_str(desc.name())?;
54-
if let Some(unit) = desc.unit() {
55-
writer.write_str("_")?;
56-
writer.write_str(unit.as_str())?;
57-
}
84+
writer.write_str("_")?;
85+
writer.write_str(unit.as_str())?;
5886
writer.write_str(" ")?;
59-
writer.write_str(EncodeMetric::metric_type(metric.as_ref()).as_str())?;
87+
writer.write_str(unit.as_str())?;
6088
writer.write_str("\n")?;
89+
}
6190

62-
if let Some(unit) = desc.unit() {
63-
writer.write_str("# UNIT ")?;
64-
writer.write_str(desc.name())?;
65-
writer.write_str("_")?;
66-
writer.write_str(unit.as_str())?;
67-
writer.write_str(" ")?;
68-
writer.write_str(unit.as_str())?;
69-
writer.write_str("\n")?;
70-
}
71-
72-
let encoder = MetricEncoder {
73-
writer,
74-
name: desc.name(),
75-
unit: desc.unit(),
76-
const_labels: desc.labels(),
77-
family_labels: None,
78-
}
79-
.into();
80-
81-
EncodeMetric::encode(metric.as_ref(), encoder)?;
91+
let encoder = MetricEncoder {
92+
writer,
93+
name: desc.name(),
94+
unit: desc.unit(),
95+
const_labels: desc.labels(),
96+
family_labels: None,
8297
}
98+
.into();
8399

84-
writer.write_str("# EOF\n")?;
100+
EncodeMetric::encode(metric, encoder)?;
85101

86102
Ok(())
87103
}

src/lib.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,32 @@
7878
//!
7979
//! [examples]: https://github.com/prometheus/client_rust/tree/master/examples
8080
81+
pub mod collector;
8182
pub mod encoding;
8283
pub mod metrics;
8384
pub mod registry;
85+
86+
/// Represents either borrowed or owned data.
87+
///
88+
/// In contrast to [`std::borrow::Cow`] does not require
89+
/// [`std::borrow::ToOwned`] or [`Clone`]respectively.
90+
///
91+
/// Needed for [`collector::Collector`].
92+
#[derive(Debug)]
93+
pub enum MaybeOwned<'a, T> {
94+
/// Owned data
95+
Owned(T),
96+
/// Borrowed data
97+
Borrowed(&'a T),
98+
}
99+
100+
impl<'a, T> std::ops::Deref for MaybeOwned<'a, T> {
101+
type Target = T;
102+
103+
fn deref(&self) -> &Self::Target {
104+
match self {
105+
Self::Owned(t) => t,
106+
Self::Borrowed(t) => t,
107+
}
108+
}
109+
}

src/metrics/counter.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,38 @@ where
187187
}
188188
}
189189

190+
/// As a [`Counter`], but constant, meaning it cannot change once created.
191+
///
192+
/// Needed for advanced use-cases, e.g. in combination with [`Collector`](crate::collector::Collector).
193+
#[derive(Debug, Default)]
194+
pub struct ConstCounter<N = u64> {
195+
value: N,
196+
}
197+
198+
impl<N> ConstCounter<N> {
199+
/// Creates a new [`ConstCounter`].
200+
pub fn new(value: N) -> Self {
201+
Self { value }
202+
}
203+
}
204+
205+
impl<N> TypedMetric for ConstCounter<N> {
206+
const TYPE: MetricType = MetricType::Counter;
207+
}
208+
209+
impl<N> EncodeMetric for ConstCounter<N>
210+
where
211+
N: crate::encoding::EncodeCounterValue,
212+
{
213+
fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
214+
encoder.encode_counter::<(), _, u64>(&self.value, None)
215+
}
216+
217+
fn metric_type(&self) -> MetricType {
218+
Self::TYPE
219+
}
220+
}
221+
190222
#[cfg(test)]
191223
mod tests {
192224
use super::*;

src/metrics/family.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder};
66

77
use super::{MetricType, TypedMetric};
88
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
9+
use std::cell::RefCell;
910
use std::collections::HashMap;
1011
use std::sync::Arc;
1112

@@ -326,6 +327,24 @@ where
326327
}
327328
}
328329

330+
impl<S: EncodeLabelSet, M: EncodeMetric + TypedMetric, T: Iterator<Item = (S, M)>> EncodeMetric
331+
for RefCell<T>
332+
{
333+
fn encode(&self, mut encoder: MetricEncoder<'_, '_>) -> Result<(), std::fmt::Error> {
334+
let mut iter = self.borrow_mut();
335+
336+
for (label_set, m) in iter.by_ref() {
337+
let encoder = encoder.encode_family(&label_set)?;
338+
m.encode(encoder)?;
339+
}
340+
Ok(())
341+
}
342+
343+
fn metric_type(&self) -> MetricType {
344+
M::TYPE
345+
}
346+
}
347+
329348
#[cfg(test)]
330349
mod tests {
331350
use super::*;

0 commit comments

Comments
 (0)