From ce8e4ec06d82a89e266f4cec23b85538fa2107c9 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Tue, 12 Aug 2025 11:10:21 +0200 Subject: [PATCH 1/2] Avoid encoding descriptor of empty family Signed-off-by: Jean-Baptiste Skutnik --- src/encoding.rs | 11 +++++++++++ src/metrics/family.rs | 4 ++++ src/registry.rs | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/encoding.rs b/src/encoding.rs index 9eb18bd2..46cc1b5c 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -50,6 +50,17 @@ pub trait EncodeMetric { // One can not use [`TypedMetric`] directly, as associated constants are not // object safe and thus can not be used with dynamic dispatching. fn metric_type(&self) -> MetricType; + + /// Check if the metric is empty. + /// + /// An empty metric is a metric that has no data to encode, and thus should not have any + /// descriptor in the final output. + /// + /// By default, this returns `false`, ensuring the metric and its description is always + /// encoded. + fn is_empty(&self) -> bool { + false + } } impl EncodeMetric for Box { diff --git a/src/metrics/family.rs b/src/metrics/family.rs index 1d0da87e..16c923fe 100644 --- a/src/metrics/family.rs +++ b/src/metrics/family.rs @@ -377,6 +377,10 @@ where fn metric_type(&self) -> MetricType { M::TYPE } + + fn is_empty(&self) -> bool { + self.metrics.read().is_empty() + } } #[cfg(test)] diff --git a/src/registry.rs b/src/registry.rs index c7dec3ba..c4478ef5 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -287,7 +287,7 @@ impl Registry { } pub(crate) fn encode(&self, encoder: &mut DescriptorEncoder) -> Result<(), std::fmt::Error> { - for (descriptor, metric) in self.metrics.iter() { + for (descriptor, metric) in self.metrics.iter().filter(|(_, m)| !m.is_empty()) { let mut descriptor_encoder = encoder.with_prefix_and_labels(self.prefix.as_ref(), &self.labels); let metric_encoder = descriptor_encoder.encode_descriptor( From 82e1d7c506918f4b057b5a6d6d474b9d1b951975 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Mon, 1 Sep 2025 09:45:27 +0200 Subject: [PATCH 2/2] Add test & changelog Signed-off-by: Jean-Baptiste Skutnik --- CHANGELOG.md | 2 ++ src/encoding/text.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 526489cc..4afd4ff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 fewer `as` casts in their implementation. This caught an issue where `EncodeGaugeValue` would not error when encoding some `u64`s that don't fit in a `i64`. See [PR 281]. +- Filter out empty metric families, to match the go client. See [PR 279]. +[PR 279]: https://github.com/prometheus/client_rust/pull/279 [PR 281]: https://github.com/prometheus/client_rust/pull/281 ## [0.24.0] diff --git a/src/encoding/text.rs b/src/encoding/text.rs index 90f6ff4c..dcd97b55 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -1278,4 +1278,41 @@ def parse(input): .unwrap(); }) } + + #[test] + fn encode_omit_empty() { + let mut registry = Registry::default(); + let counter1: Family, Counter> = Default::default(); + let counter2: Family, Counter> = Default::default(); + let counter3: Family, Counter> = Default::default(); + + registry.register("counter1", "First counter", counter1.clone()); + registry.register("counter2", "Second counter", counter2.clone()); + registry.register("counter3", "Third counter", counter3.clone()); + + counter1.get_or_create(&vec![("label", "value")]).inc(); + + let mut encoded = String::new(); + encode(&mut encoded, ®istry).unwrap(); + + let expected = "# HELP counter1 First counter.\n".to_owned() + + "# TYPE counter1 counter\n" + + "counter1_total{label=\"value\"} 1\n" + + "# EOF\n"; + assert_eq!(expected, encoded); + + counter2.get_or_create(&vec![("label", "value")]).inc(); + + let mut encoded = String::new(); + encode(&mut encoded, ®istry).unwrap(); + + let expected = "# HELP counter1 First counter.\n".to_owned() + + "# TYPE counter1 counter\n" + + "counter1_total{label=\"value\"} 1\n" + + "# HELP counter2 Second counter.\n" + + "# TYPE counter2 counter\n" + + "counter2_total{label=\"value\"} 1\n" + + "# EOF\n"; + assert_eq!(expected, encoded); + } }