Skip to content

Commit

Permalink
feat(encoding)!: EncodeLabelSet::encode() uses reference
Browse files Browse the repository at this point in the history
this commit alters the signature of the `EncodeLabelSet::encode()` trait
method, such that it now accepts a mutable reference to its encoder.

this is related to prometheus#135, and is a second proposal following
previous work in prometheus#240.

this change permits distinct label sets to be composed together, now
that the label set encoder is not consumed. a new implementation for
tuples `(A, B)` is provided.

this commit includes a test case showing that a metric family can
compose two label sets together, and that such a family can successfully
be digested by the python client library.

`derive-encode` is altered to generate code matching this new trait
signature, and has been bumped to version 0.5.0 as a result of this
breaking change in the `prometheus-client` library.

Signed-off-by: katelyn martin <[email protected]>
  • Loading branch information
cratelyn committed Jan 21, 2025
1 parent 9a74e99 commit 1ca988b
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 17 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ 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.23.1] - unreleased
## [0.24.0] - unreleased

### Added

- `EncodeLabelSet` is now implemented for tuples `(A: EncodeLabelSet, B: EncodeLabelSet)`.

### Changed

- `EncodeLabelSet::encode()` now accepts a mutable reference to its encoder parameter.

## [0.23.1]

### Changed

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.23.1"
version = "0.24.0"
authors = ["Max Inden <[email protected]>"]
edition = "2021"
description = "Open Metrics client library allowing users to natively instrument applications."
Expand Down
2 changes: 1 addition & 1 deletion derive-encode/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "prometheus-client-derive-encode"
version = "0.4.2"
version = "0.5.0"
authors = ["Max Inden <[email protected]>"]
edition = "2021"
description = "Auxiliary crate to derive Encode trait from prometheus-client."
Expand Down
2 changes: 1 addition & 1 deletion derive-encode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {

let gen = quote! {
impl prometheus_client::encoding::EncodeLabelSet for #name {
fn encode(&self, mut encoder: prometheus_client::encoding::LabelSetEncoder) -> std::result::Result<(), std::fmt::Error> {
fn encode(&self, encoder: &mut prometheus_client::encoding::LabelSetEncoder) -> std::result::Result<(), std::fmt::Error> {
use prometheus_client::encoding::EncodeLabel;
use prometheus_client::encoding::EncodeLabelKey;
use prometheus_client::encoding::EncodeLabelValue;
Expand Down
28 changes: 22 additions & 6 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ impl MetricEncoder<'_> {
/// An encodable label set.
pub trait EncodeLabelSet {
/// Encode oneself into the given encoder.
fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error>;
fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error>;
}

/// Encoder for a label set.
Expand Down Expand Up @@ -238,37 +238,53 @@ impl LabelSetEncoder<'_> {
}

impl<T: EncodeLabel, const N: usize> EncodeLabelSet for [T; N] {
fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
self.as_ref().encode(encoder)
}
}

impl<T: EncodeLabel> EncodeLabelSet for &[T] {
fn encode(&self, mut encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
if self.is_empty() {
return Ok(());
}

for label in self.iter() {
label.encode(encoder.encode_label())?
let encoder = encoder.encode_label();
label.encode(encoder)?
}

Ok(())
}
}

impl<T: EncodeLabel> EncodeLabelSet for Vec<T> {
fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
self.as_slice().encode(encoder)
}
}

impl<A, B> EncodeLabelSet for (A, B)
where
A: EncodeLabelSet,
B: EncodeLabelSet,
{
fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
let (a, b) = self;

a.encode(encoder)?;
b.encode(encoder)?;

Ok(())
}
}

/// Uninhabited type to represent the lack of a label set for a metric
#[derive(Debug)]
pub enum NoLabelSet {}

impl EncodeLabelSet for NoLabelSet {
fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
fn encode(&self, _encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
Ok(())
}
}
Expand Down
77 changes: 70 additions & 7 deletions src/encoding/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,9 @@ pub(crate) struct MetricEncoder<'a> {
impl std::fmt::Debug for MetricEncoder<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut labels = String::new();
let mut encoder = LabelSetEncoder::new(&mut labels).into();
if let Some(l) = self.family_labels {
l.encode(LabelSetEncoder::new(&mut labels).into())?;
l.encode(&mut encoder)?;
}

f.debug_struct("Encoder")
Expand Down Expand Up @@ -451,7 +452,7 @@ impl MetricEncoder<'_> {
self.writer.write_str(" # {")?;
exemplar
.label_set
.encode(LabelSetEncoder::new(self.writer).into())?;
.encode(&mut LabelSetEncoder::new(self.writer).into())?;
self.writer.write_str("} ")?;
exemplar.value.encode(
ExemplarValueEncoder {
Expand Down Expand Up @@ -502,14 +503,14 @@ impl MetricEncoder<'_> {
self.writer.write_str("{")?;

self.const_labels
.encode(LabelSetEncoder::new(self.writer).into())?;
.encode(&mut LabelSetEncoder::new(self.writer).into())?;

if let Some(additional_labels) = additional_labels {
if !self.const_labels.is_empty() {
self.writer.write_str(",")?;
}

additional_labels.encode(LabelSetEncoder::new(self.writer).into())?;
additional_labels.encode(&mut LabelSetEncoder::new(self.writer).into())?;
}

/// Writer impl which prepends a comma on the first call to write output to the wrapped writer
Expand Down Expand Up @@ -539,9 +540,9 @@ impl MetricEncoder<'_> {
writer: self.writer,
should_prepend: true,
};
labels.encode(LabelSetEncoder::new(&mut writer).into())?;
labels.encode(&mut LabelSetEncoder::new(&mut writer).into())?;
} else {
labels.encode(LabelSetEncoder::new(self.writer).into())?;
labels.encode(&mut LabelSetEncoder::new(self.writer).into())?;
};
}

Expand Down Expand Up @@ -936,7 +937,7 @@ mod tests {
struct EmptyLabels {}

impl EncodeLabelSet for EmptyLabels {
fn encode(&self, _encoder: crate::encoding::LabelSetEncoder) -> Result<(), Error> {
fn encode(&self, _encoder: &mut crate::encoding::LabelSetEncoder) -> Result<(), Error> {
Ok(())
}
}
Expand Down Expand Up @@ -1114,6 +1115,68 @@ mod tests {
parse_with_python_client(encoded);
}

#[test]
fn label_sets_can_be_composed() {
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct Color(&'static str);
impl EncodeLabelSet for Color {
fn encode(
&self,
encoder: &mut crate::encoding::LabelSetEncoder,
) -> Result<(), std::fmt::Error> {
use crate::encoding::EncodeLabel;
let Self(color) = *self;
let labels = ("color", color);
let encoder = encoder.encode_label();
labels.encode(encoder)
}
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct Size(&'static str);
impl EncodeLabelSet for Size {
fn encode(
&self,
encoder: &mut crate::encoding::LabelSetEncoder,
) -> Result<(), std::fmt::Error> {
use crate::encoding::EncodeLabel;
let Self(size) = *self;
let labels = ("size", size);
let encoder = encoder.encode_label();
labels.encode(encoder)
}
}

type Labels = (Color, Size);

let mut registry = Registry::default();
let family = Family::<Labels, Counter>::default();
registry.register("items", "Example metric", family.clone());

{
let labels = (Color("red"), Size("large"));
let counter = family.get_or_create(&labels);
counter.inc();
}
{
let labels = (Color("blue"), Size("small"));
let counter = family.get_or_create(&labels);
counter.inc_by(2);
}

let mut encoded = String::new();
encode(&mut encoded, &registry).unwrap();

let expected = "# HELP items Example metric.\n\
# TYPE items counter\n\
items_total{color=\"blue\",size=\"small\"} 2\n\
items_total{color=\"red\",size=\"large\"} 1\n\
# EOF\n";
assert_eq!(expected, encoded);

parse_with_python_client(encoded);
}

#[test]
fn encode_registry_eof() {
let mut orders_registry = Registry::default();
Expand Down

0 comments on commit 1ca988b

Please sign in to comment.