From 9462cd6a2f95c086b142c9f8d11683953e1bbd2a Mon Sep 17 00:00:00 2001 From: Palash Nigam Date: Sun, 29 Jan 2023 00:41:35 +0530 Subject: [PATCH] Add serde style encoding for Summary Signed-off-by: Palash Nigam --- src/encoding.rs | 15 +++++++++ src/encoding/protobuf.rs | 73 ++++++++++++++++++++++++++++++++++++++++ src/encoding/text.rs | 42 ++++++++++++++++++++--- src/metrics.rs | 1 + src/metrics/summary.rs | 14 ++++++++ 5 files changed, 141 insertions(+), 4 deletions(-) diff --git a/src/encoding.rs b/src/encoding.rs index b59f245..c912d14 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -131,6 +131,21 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { ) } + /// Encode a summary. + pub fn encode_summary( + &mut self, + sum: f64, + count: u64, + quantiles: &[(f64, f64)], + ) -> Result<(), std::fmt::Error> { + for_both_mut!( + self, + MetricEncoderInner, + e, + e.encode_summary::(sum, count, quantiles) + ) + } + /// Encode a metric family. pub fn encode_family<'c, 'd, S: EncodeLabelSet>( &'c mut self, diff --git a/src/encoding/protobuf.rs b/src/encoding/protobuf.rs index 500ded7..3951a7d 100644 --- a/src/encoding/protobuf.rs +++ b/src/encoding/protobuf.rs @@ -99,6 +99,7 @@ impl From for openmetrics_data_model::MetricType { MetricType::Counter => openmetrics_data_model::MetricType::Counter, MetricType::Gauge => openmetrics_data_model::MetricType::Gauge, MetricType::Histogram => openmetrics_data_model::MetricType::Histogram, + MetricType::Summary => openmetrics_data_model::MetricType::Summary, MetricType::Info => openmetrics_data_model::MetricType::Info, MetricType::Unknown => openmetrics_data_model::MetricType::Unknown, } @@ -247,6 +248,44 @@ impl<'a> MetricEncoder<'a> { Ok(()) } + + pub fn encode_summary( + &mut self, + sum: f64, + count: u64, + quantile: &[(f64, f64)], + ) -> Result<(), std::fmt::Error> { + let quantiles = quantiles + .iter() + .enumerate() + .map(|(_, (quantile, value))| { + Ok(openmetrics_data_model::summary_value::Quantile { + quantile: *quantile, + value: *value, + }) + }) + .collect::, std::fmt::Error>>()?; + + self.family.push(openmetrics_data_model::Metric { + labels: self.labels.clone(), + metric_points: vec![openmetrics_data_model::MetricPoint { + value: Some(openmetrics_data_model::metric_point::Value::SummaryValue( + openmetrics_data_model::SummaryValue { + count, + created: None, + quantiles, + sum: Some(openmetrics_data_model::summary_value::Sum::DoubleValue( + sum, + )), + }, + )), + ..Default::default() + }], + }); + + Ok(()) + } + } impl TryFrom<&Exemplar> @@ -403,6 +442,7 @@ mod tests { use crate::metrics::family::Family; use crate::metrics::gauge::Gauge; use crate::metrics::histogram::{exponential_buckets, Histogram}; + use crate::metrics::summary::Summary; use crate::metrics::info::Info; use crate::registry::Unit; use std::borrow::Cow; @@ -643,6 +683,39 @@ mod tests { } } + #[test] + fn encode_summary() { + let mut registry = Registry::default(); + let summary = Summary::new(5, 10, vec![0.5, 0.9, 0.99], 0.01); + registry.register("my_summary", "My summary", summary.clone()); + summary.observe(1.0); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_summary", family.name); + assert_eq!("My summary.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Summary as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(metric_set) { + openmetrics_data_model::metric_point::Value::SummaryValue(value) => { + assert_eq!( + Some(openmetrics_data_model::summary_value::Sum::DoubleValue( + 1.0 + )), + value.sum + ); + assert_eq!(1, value.count); + assert_eq!(11, value.quantile.len()); + } + _ => panic!("wrong value type"), + } + } + #[test] fn encode_histogram() { let mut registry = Registry::default(); diff --git a/src/encoding/text.rs b/src/encoding/text.rs index bcd24c8..1a26f10 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -211,6 +211,40 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { }) } + pub fn encode_summary( + &mut self, + sum: f64, + count: u64, + quantiles: &[(f64, f64)], + ) -> Result<(), std::fmt::Error> { + self.write_name_and_unit()?; + self.write_suffix("sum")?; + self.encode_labels::<()>(None)?; + self.writer.write_str(" ")?; + self.writer.write_str(dtoa::Buffer::new().format(sum))?; + self.newline()?; + + self.write_name_and_unit()?; + self.write_suffix("count")?; + self.encode_labels::<()>(None)?; + self.writer.write_str(" ")?; + self.writer.write_str(itoa::Buffer::new().format(count))?; + self.newline()?; + + for (_, (quantile, result)) in quantiles.iter().enumerate() { + self.write_name_and_unit()?; + self.encode_labels(Some(&[("quantile", *quantile)]))?; + + self.writer.write_str(" ")?; + self.writer + .write_str(result.to_string().as_str())?; + + self.newline()?; + } + + Ok(()) + } + pub fn encode_histogram( &mut self, sum: f64, @@ -511,6 +545,7 @@ mod tests { use crate::metrics::family::Family; use crate::metrics::gauge::Gauge; use crate::metrics::histogram::{exponential_buckets, Histogram}; + use crate::metrics::summary::Summary; use crate::metrics::info::Info; use crate::metrics::{counter::Counter, exemplar::CounterWithExemplar}; use pyo3::{prelude::*, types::PyModule}; @@ -732,8 +767,7 @@ mod tests { summary.observe(0.20); summary.observe(0.30); - let mut encoded = Vec::new(); - + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); let expected = "# HELP my_summary My summary.\n".to_owned() @@ -744,9 +778,9 @@ mod tests { + "my_summary{quantile=\"0.9\"} 0.3\n" + "my_summary{quantile=\"0.99\"} 0.3\n" + "# EOF\n"; - assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap()); + assert_eq!(expected, encoded); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } fn parse_with_python_client(input: String) { diff --git a/src/metrics.rs b/src/metrics.rs index a4683b5..5750072 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -38,6 +38,7 @@ impl MetricType { MetricType::Counter => "counter", MetricType::Gauge => "gauge", MetricType::Histogram => "histogram", + MetricType::Summary => "summary", MetricType::Info => "info", MetricType::Unknown => "unknown", } diff --git a/src/metrics/summary.rs b/src/metrics/summary.rs index b4a30c6..2b39b21 100644 --- a/src/metrics/summary.rs +++ b/src/metrics/summary.rs @@ -2,6 +2,8 @@ //! //! See [`Summary`] for details. +use crate::encoding::{EncodeMetric, MetricEncoder}; + use super::{MetricType, TypedMetric}; //use owning_ref::OwningRef; //use std::iter::{self, once}; @@ -126,6 +128,18 @@ impl TypedMetric for Summary { const TYPE: MetricType = MetricType::Summary; } +impl EncodeMetric for Summary { + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + let (sum, count, quantiles) = self.get(); + encoder.encode_summary::<()>(sum, count, &quantiles) + } + + fn metric_type(&self) -> MetricType { + Self::TYPE + } +} + + #[cfg(test)] mod tests { use super::*;