From e281acedf1d8a436b0b435b951ef4ee2c74a3740 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Wed, 15 Jan 2025 00:00:00 +0000 Subject: [PATCH] feat(encoding)!: `EncodeLabelSet::encode()` uses reference 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 #135, and is a second proposal following previous work in #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 --- CHANGELOG.md | 12 ++++++- Cargo.toml | 4 +-- derive-encode/Cargo.toml | 2 +- derive-encode/src/lib.rs | 2 +- src/encoding.rs | 28 +++++++++++---- src/encoding/text.rs | 77 ++++++++++++++++++++++++++++++++++++---- 6 files changed, 107 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9d4f94..a0e32055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index d04c8999..a0f5e2a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prometheus-client" -version = "0.23.1" +version = "0.24.0" authors = ["Max Inden "] edition = "2021" description = "Open Metrics client library allowing users to natively instrument applications." @@ -21,7 +21,7 @@ members = ["derive-encode"] dtoa = "1.0" itoa = "1.0" parking_lot = "0.12" -prometheus-client-derive-encode = { version = "0.4.1", path = "derive-encode" } +prometheus-client-derive-encode = { version = "0.5.0", path = "derive-encode" } prost = { version = "0.12.0", optional = true } prost-types = { version = "0.12.0", optional = true } diff --git a/derive-encode/Cargo.toml b/derive-encode/Cargo.toml index f365b077..145c0159 100644 --- a/derive-encode/Cargo.toml +++ b/derive-encode/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prometheus-client-derive-encode" -version = "0.4.2" +version = "0.5.0" authors = ["Max Inden "] edition = "2021" description = "Auxiliary crate to derive Encode trait from prometheus-client." diff --git a/derive-encode/src/lib.rs b/derive-encode/src/lib.rs index 1858cf8d..5b3cbe32 100644 --- a/derive-encode/src/lib.rs +++ b/derive-encode/src/lib.rs @@ -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; diff --git a/src/encoding.rs b/src/encoding.rs index 9e2acac0..ae8420f6 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -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. @@ -238,19 +238,20 @@ impl LabelSetEncoder<'_> { } impl 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 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(()) @@ -258,17 +259,32 @@ impl EncodeLabelSet for &[T] { } impl EncodeLabelSet for Vec { - 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 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(()) } } diff --git a/src/encoding/text.rs b/src/encoding/text.rs index f10cf1d3..945124d7 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -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") @@ -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 { @@ -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 @@ -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())?; }; } @@ -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(()) } } @@ -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::::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, ®istry).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();