Skip to content

Commit ac3878d

Browse files
committed
feat(metrics): add Summary implementation
This commit introduces the `Summary` metric type for the Rust Prometheus client, addressing the requirements outlined in prometheus#40. - Implements the `Summary` type to represent summary statistics such as sum, count, and quantiles. - The implementation does not include a specific quantile calculation algorithm. Users are expected to provide precomputed quantiles based on their chosen algorithm. - Supports encoding summary metrics following the OpenMetrics standard. - `Summary` provides: - `reset` method to update its internal state with sum, count, and quantiles. - Integration with the `TypedMetric` and `EncodeMetric` traits to support encoding and registration with the Prometheus client. - Documentation and tests have been added to ensure correct usage and behavior. - This implementation is designed to give users flexibility in handling quantile computation. - Future work could explore integrating popular quantile calculation algorithms to offer built-in support while retaining the option for custom implementations. Signed-off-by: zth <[email protected]>
1 parent 12923ca commit ac3878d

File tree

5 files changed

+500
-1
lines changed

5 files changed

+500
-1
lines changed

src/encoding.rs

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub use prometheus_client_derive_encode::*;
44

55
use crate::metrics::exemplar::Exemplar;
66
use crate::metrics::MetricType;
7+
use crate::metrics::summary::Numeric;
78
use crate::registry::{Prefix, Unit};
89
use std::borrow::Cow;
910
use std::collections::HashMap;
@@ -186,6 +187,21 @@ impl MetricEncoder<'_> {
186187
)
187188
}
188189

190+
/// Encode a summary.
191+
pub fn encode_summary<T: ToString + Numeric>(
192+
&mut self,
193+
sum: f64,
194+
count: u64,
195+
quantiles: &[(f64, T)],
196+
) -> Result<(), std::fmt::Error> {
197+
for_both_mut!(
198+
self,
199+
MetricEncoderInner,
200+
e,
201+
e.encode_summary(sum, count, quantiles)
202+
)
203+
}
204+
189205
/// Encode a metric family.
190206
pub fn encode_family<'s, S: EncodeLabelSet>(
191207
&'s mut self,

src/encoding/protobuf.rs

+125
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub mod openmetrics_data_model {
3333
use std::{borrow::Cow, collections::HashMap};
3434

3535
use crate::metrics::MetricType;
36+
use crate::metrics::summary::Numeric;
3637
use crate::registry::{Registry, Unit};
3738
use crate::{metrics::exemplar::Exemplar, registry::Prefix};
3839

@@ -53,6 +54,7 @@ impl From<MetricType> for openmetrics_data_model::MetricType {
5354
MetricType::Counter => openmetrics_data_model::MetricType::Counter,
5455
MetricType::Gauge => openmetrics_data_model::MetricType::Gauge,
5556
MetricType::Histogram => openmetrics_data_model::MetricType::Histogram,
57+
MetricType::Summary => openmetrics_data_model::MetricType::Summary,
5658
MetricType::Info => openmetrics_data_model::MetricType::Info,
5759
MetricType::Unknown => openmetrics_data_model::MetricType::Unknown,
5860
}
@@ -288,6 +290,41 @@ impl MetricEncoder<'_> {
288290

289291
Ok(())
290292
}
293+
294+
pub fn encode_summary<T: Numeric>(
295+
&mut self,
296+
sum: f64,
297+
count: u64,
298+
quantiles: &[(f64, T)],
299+
) -> Result<(), std::fmt::Error> {
300+
let quantiles = quantiles.iter()
301+
.map(|(q, v)| {
302+
openmetrics_data_model::summary_value::Quantile {
303+
quantile: *q,
304+
value: v.as_f64(),
305+
}
306+
})
307+
.collect::<Vec<_>>();
308+
309+
self.family.push(openmetrics_data_model::Metric {
310+
labels: self.labels.clone(),
311+
metric_points: vec![openmetrics_data_model::MetricPoint {
312+
value: Some(openmetrics_data_model::metric_point::Value::SummaryValue(
313+
openmetrics_data_model::SummaryValue {
314+
count,
315+
created: None,
316+
quantile: quantiles,
317+
sum: Some(openmetrics_data_model::summary_value::Sum::DoubleValue(
318+
sum,
319+
)),
320+
},
321+
)),
322+
..Default::default()
323+
}],
324+
});
325+
326+
Ok(())
327+
}
291328
}
292329

293330
impl<S: EncodeLabelSet, V: EncodeExemplarValue> TryFrom<&Exemplar<S, V>>
@@ -448,6 +485,7 @@ mod tests {
448485
use crate::metrics::family::Family;
449486
use crate::metrics::gauge::Gauge;
450487
use crate::metrics::histogram::{exponential_buckets, Histogram};
488+
use crate::metrics::summary::Summary;
451489
use crate::metrics::info::Info;
452490
use crate::registry::Unit;
453491
use std::borrow::Cow;
@@ -817,6 +855,93 @@ mod tests {
817855
}
818856
}
819857

858+
#[test]
859+
fn encode_summary() {
860+
let mut registry = Registry::default();
861+
let summary = Summary::default();
862+
registry.register("my_summary", "My Summary", summary.clone());
863+
let quantiles = vec![
864+
(0.5, 100.1),
865+
(0.9, 300.2),
866+
(0.99, 700.3),
867+
];
868+
let _ = summary.reset(123.0, 10, quantiles);
869+
870+
let metric_set = encode(&registry).unwrap();
871+
872+
let family = metric_set.metric_families.first().unwrap();
873+
assert_eq!("my_summary", family.name);
874+
assert_eq!("My Summary.", family.help);
875+
assert_eq!(
876+
openmetrics_data_model::MetricType::Summary as i32,
877+
extract_metric_type(&metric_set)
878+
);
879+
match extract_metric_point_value(&metric_set) {
880+
openmetrics_data_model::metric_point::Value::SummaryValue(value) => {
881+
assert_eq!(
882+
Some(openmetrics_data_model::summary_value::Sum::DoubleValue(
883+
123.0
884+
)),
885+
value.sum
886+
);
887+
assert_eq!(10, value.count);
888+
assert_eq!(3, value.quantile.len());
889+
}
890+
_ => panic!("wrong value type"),
891+
}
892+
}
893+
894+
#[test]
895+
fn encode_summary_family() {
896+
let mut registry = Registry::default();
897+
let family =
898+
Family::new_with_constructor(|| Summary::default());
899+
registry.register("my_summary", "My Summary", family.clone());
900+
let quantiles = vec![
901+
(0.5, 100_u64),
902+
(0.9, 300),
903+
(0.99, 700),
904+
];
905+
let _ = family
906+
.get_or_create(&vec![
907+
("method".to_string(), "POST".to_string()),
908+
("status".to_string(), "200".to_string()),
909+
])
910+
.reset(123.0, 10, quantiles);
911+
912+
let metric_set = encode(&registry).unwrap();
913+
914+
let family = metric_set.metric_families.first().unwrap();
915+
assert_eq!("my_summary", family.name);
916+
assert_eq!("My Summary.", family.help);
917+
assert_eq!(
918+
openmetrics_data_model::MetricType::Summary as i32,
919+
extract_metric_type(&metric_set)
920+
);
921+
922+
let metric = family.metrics.first().unwrap();
923+
assert_eq!(2, metric.labels.len());
924+
assert_eq!("method", metric.labels[0].name);
925+
assert_eq!("POST", metric.labels[0].value);
926+
assert_eq!("status", metric.labels[1].name);
927+
assert_eq!("200", metric.labels[1].value);
928+
929+
match extract_metric_point_value(&metric_set) {
930+
openmetrics_data_model::metric_point::Value::SummaryValue(value) => {
931+
assert_eq!(None, value.created);
932+
assert_eq!(
933+
Some(openmetrics_data_model::summary_value::Sum::DoubleValue(
934+
123.0
935+
)),
936+
value.sum
937+
);
938+
assert_eq!(10, value.count);
939+
assert_eq!(3, value.quantile.len());
940+
}
941+
_ => panic!("wrong value type"),
942+
}
943+
}
944+
820945
#[test]
821946
fn encode_family_counter_histogram() {
822947
let mut registry = Registry::default();

src/encoding/text.rs

+133
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,41 @@ impl<'a> MetricEncoder<'a> {
462462
Ok(())
463463
}
464464

465+
pub fn encode_summary<T: ToString>(
466+
&mut self,
467+
sum: f64,
468+
count: u64,
469+
quantiles: &[(f64, T)],
470+
) -> Result<(), std::fmt::Error> {
471+
self.write_prefix_name_unit()?;
472+
self.write_suffix("sum")?;
473+
self.encode_labels::<NoLabelSet>(None)?;
474+
self.writer.write_str(" ")?;
475+
self.writer.write_str(dtoa::Buffer::new().format(sum))?;
476+
self.newline()?;
477+
478+
self.write_prefix_name_unit()?;
479+
self.write_suffix("count")?;
480+
self.encode_labels::<NoLabelSet>(None)?;
481+
self.writer.write_str(" ")?;
482+
self.writer.write_str(itoa::Buffer::new().format(count))?;
483+
self.newline()?;
484+
485+
for (quantile, value) in quantiles {
486+
self.write_prefix_name_unit()?;
487+
488+
self.encode_labels(Some(&[("quantile", *quantile)]))?;
489+
490+
self.writer.write_str(" ")?;
491+
self.writer
492+
.write_str(&value.to_string())?;
493+
494+
self.newline()?;
495+
}
496+
497+
Ok(())
498+
}
499+
465500
fn newline(&mut self) -> Result<(), std::fmt::Error> {
466501
self.writer.write_str("\n")
467502
}
@@ -730,6 +765,7 @@ mod tests {
730765
use crate::metrics::family::Family;
731766
use crate::metrics::gauge::Gauge;
732767
use crate::metrics::histogram::{exponential_buckets, Histogram};
768+
use crate::metrics::summary::Summary;
733769
use crate::metrics::info::Info;
734770
use crate::metrics::{counter::Counter, exemplar::CounterWithExemplar};
735771
use pyo3::{prelude::*, types::PyModule};
@@ -981,6 +1017,103 @@ mod tests {
9811017
parse_with_python_client(encoded);
9821018
}
9831019

1020+
#[test]
1021+
fn encode_summary() {
1022+
let mut registry = Registry::default();
1023+
let summary = Summary::default();
1024+
registry.register("my_summary", "My Summary", summary.clone());
1025+
let quantiles = vec![
1026+
(0.5, 100.1),
1027+
(0.9, 300.2),
1028+
(0.99, 700.3),
1029+
];
1030+
let _ = summary.reset(123.0, 10, quantiles);
1031+
1032+
let mut encoded = String::new();
1033+
encode(&mut encoded, &registry).unwrap();
1034+
1035+
let expected = "# HELP my_summary My Summary.\n".to_owned()
1036+
+ "# TYPE my_summary summary\n"
1037+
+ "my_summary_sum 123.0\n"
1038+
+ "my_summary_count 10\n"
1039+
+ "my_summary{quantile=\"0.5\"} 100.1\n"
1040+
+ "my_summary{quantile=\"0.9\"} 300.2\n"
1041+
+ "my_summary{quantile=\"0.99\"} 700.3\n"
1042+
+ "# EOF\n";
1043+
1044+
assert_eq!(expected, encoded);
1045+
}
1046+
1047+
#[test]
1048+
fn encode_summary_family() {
1049+
let mut registry = Registry::default();
1050+
let family =
1051+
Family::new_with_constructor(|| Summary::default());
1052+
registry.register("my_summary", "My Summary", family.clone());
1053+
let quantiles = vec![
1054+
(0.5, 100_u64),
1055+
(0.9, 300),
1056+
(0.99, 700),
1057+
];
1058+
let _ = family
1059+
.get_or_create(&vec![
1060+
("method".to_string(), "GET".to_string()),
1061+
("status".to_string(), "200".to_string()),
1062+
])
1063+
.reset(123.0, 10, quantiles);
1064+
1065+
let mut encoded = String::new();
1066+
encode(&mut encoded, &registry).unwrap();
1067+
1068+
let expected = "# HELP my_summary My Summary.\n".to_owned()
1069+
+ "# TYPE my_summary summary\n"
1070+
+ "my_summary_sum{method=\"GET\",status=\"200\"} 123.0\n"
1071+
+ "my_summary_count{method=\"GET\",status=\"200\"} 10\n"
1072+
+ "my_summary{quantile=\"0.5\",method=\"GET\",status=\"200\"} 100\n"
1073+
+ "my_summary{quantile=\"0.9\",method=\"GET\",status=\"200\"} 300\n"
1074+
+ "my_summary{quantile=\"0.99\",method=\"GET\",status=\"200\"} 700\n"
1075+
+ "# EOF\n";
1076+
1077+
assert_eq!(expected, encoded);
1078+
}
1079+
1080+
#[test]
1081+
fn encode_summary_family_with_empty_struct_family_labels() {
1082+
let mut registry = Registry::default();
1083+
let family =
1084+
Family::new_with_constructor(|| Summary::default());
1085+
registry.register("my_summary", "My Summary", family.clone());
1086+
1087+
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
1088+
struct EmptyLabels {}
1089+
1090+
impl EncodeLabelSet for EmptyLabels {
1091+
fn encode(&self, _encoder: crate::encoding::LabelSetEncoder) -> Result<(), Error> {
1092+
Ok(())
1093+
}
1094+
}
1095+
1096+
let quantiles = vec![
1097+
(0.5, 100.1),
1098+
(0.9, 300.2),
1099+
(0.99, 700.3),
1100+
];
1101+
let _ = family.get_or_create(&EmptyLabels {}).reset(123.0, 10, quantiles);
1102+
let mut encoded = String::new();
1103+
encode(&mut encoded, &registry).unwrap();
1104+
1105+
let expected = "# HELP my_summary My Summary.\n".to_owned()
1106+
+ "# TYPE my_summary summary\n"
1107+
+ "my_summary_sum{} 123.0\n"
1108+
+ "my_summary_count{} 10\n"
1109+
+ "my_summary{quantile=\"0.5\"} 100.1\n"
1110+
+ "my_summary{quantile=\"0.9\"} 300.2\n"
1111+
+ "my_summary{quantile=\"0.99\"} 700.3\n"
1112+
+ "# EOF\n";
1113+
1114+
assert_eq!(expected, encoded);
1115+
}
1116+
9841117
#[test]
9851118
fn sub_registry_with_prefix_and_label() {
9861119
let top_level_metric_name = "my_top_level_metric";

src/metrics.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod exemplar;
55
pub mod family;
66
pub mod gauge;
77
pub mod histogram;
8+
pub mod summary;
89
pub mod info;
910

1011
/// A metric that is aware of its Open Metrics metric type.
@@ -20,13 +21,13 @@ pub enum MetricType {
2021
Counter,
2122
Gauge,
2223
Histogram,
24+
Summary,
2325
Info,
2426
Unknown,
2527
// Not (yet) supported metric types.
2628
//
2729
// GaugeHistogram,
2830
// StateSet,
29-
// Summary
3031
}
3132

3233
impl MetricType {
@@ -36,6 +37,7 @@ impl MetricType {
3637
MetricType::Counter => "counter",
3738
MetricType::Gauge => "gauge",
3839
MetricType::Histogram => "histogram",
40+
MetricType::Summary => "summary",
3941
MetricType::Info => "info",
4042
MetricType::Unknown => "unknown",
4143
}

0 commit comments

Comments
 (0)