Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
11 changes: 11 additions & 0 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn EncodeMetric> {
Expand Down
37 changes: 37 additions & 0 deletions src/encoding/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1278,4 +1278,41 @@ def parse(input):
.unwrap();
})
}

#[test]
fn encode_omit_empty() {
let mut registry = Registry::default();
let counter1: Family<Vec<(&'static str, &'static str)>, Counter> = Default::default();
let counter2: Family<Vec<(&'static str, &'static str)>, Counter> = Default::default();
let counter3: Family<Vec<(&'static str, &'static str)>, 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, &registry).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, &registry).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);
}
}
4 changes: 4 additions & 0 deletions src/metrics/family.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,10 @@ where
fn metric_type(&self) -> MetricType {
M::TYPE
}

fn is_empty(&self) -> bool {
self.metrics.read().is_empty()
}
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down