Skip to content

Commit 1455fcc

Browse files
committed
Add Summary metric type
Signed-off-by: Palash Nigam <[email protected]>
1 parent 69e6674 commit 1455fcc

File tree

5 files changed

+199
-2
lines changed

5 files changed

+199
-2
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dtoa = "1.0"
1818
itoa = "1.0"
1919
owning_ref = "0.4"
2020
prometheus-client-derive-text-encode = { version = "0.3.0", path = "derive-text-encode" }
21+
quantiles = "0.7.1"
2122

2223
[dev-dependencies]
2324
async-std = { version = "1", features = ["attributes"] }

src/encoding/text.rs

+78
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::metrics::exemplar::{CounterWithExemplar, Exemplar, HistogramWithExemp
2929
use crate::metrics::family::{Family, MetricConstructor};
3030
use crate::metrics::gauge::{self, Gauge};
3131
use crate::metrics::histogram::Histogram;
32+
use crate::metrics::summary::Summary;
3233
use crate::metrics::info::Info;
3334
use crate::metrics::{MetricType, TypedMetric};
3435
use crate::registry::{Registry, Unit};
@@ -184,6 +185,7 @@ impl Encode for MetricType {
184185
MetricType::Histogram => "histogram",
185186
MetricType::Info => "info",
186187
MetricType::Unknown => "unknown",
188+
MetricType::Summary => "summary",
187189
};
188190

189191
writer.write_all(t.as_bytes())?;
@@ -331,6 +333,23 @@ impl<'a> BucketEncoder<'a> {
331333
})
332334
}
333335

336+
/// Encode a quantile. Used for the [`Summary`] metric type.
337+
pub fn encode_quantile(&mut self, quantile: f64) -> Result<ValueEncoder, std::io::Error> {
338+
if self.opened_curly_brackets {
339+
self.writer.write_all(b",")?;
340+
} else {
341+
self.writer.write_all(b"{")?;
342+
}
343+
344+
self.writer.write_all(b"quantile=\"")?;
345+
quantile.encode(self.writer)?;
346+
self.writer.write_all(b"\"}")?;
347+
348+
Ok(ValueEncoder {
349+
writer: self.writer,
350+
})
351+
}
352+
334353
/// Signal that the metric type has no bucket.
335354
pub fn no_bucket(&mut self) -> Result<ValueEncoder, std::io::Error> {
336355
if self.opened_curly_brackets {
@@ -580,6 +599,40 @@ fn encode_histogram_with_maybe_exemplars<S: Encode>(
580599
Ok(())
581600
}
582601

602+
/////////////////////////////////////////////////////////////////////////////////
603+
// Summary
604+
605+
impl EncodeMetric for Summary {
606+
fn encode(&self, mut encoder: Encoder) -> Result<(), std::io::Error> {
607+
let (sum, count, quantiles) = self.get();
608+
609+
encoder
610+
.encode_suffix("sum")?
611+
.no_bucket()?
612+
.encode_value(sum)?
613+
.no_exemplar()?;
614+
encoder
615+
.encode_suffix("count")?
616+
.no_bucket()?
617+
.encode_value(count)?
618+
.no_exemplar()?;
619+
620+
for (_, (quantile, result)) in quantiles.iter().enumerate() {
621+
let mut bucket_encoder = encoder.no_suffix()?;
622+
let mut value_encoder = bucket_encoder.encode_quantile(*quantile)?;
623+
let mut exemplar_encoder = value_encoder.encode_value(*result)?;
624+
exemplar_encoder.no_exemplar()?
625+
}
626+
627+
Result::Ok(())
628+
}
629+
630+
fn metric_type(&self) -> MetricType {
631+
Self::TYPE
632+
}
633+
}
634+
635+
583636
/////////////////////////////////////////////////////////////////////////////////
584637
// Info
585638

@@ -603,6 +656,7 @@ where
603656
}
604657
}
605658

659+
606660
#[cfg(test)]
607661
mod tests {
608662
use super::*;
@@ -819,6 +873,30 @@ mod tests {
819873
parse_with_python_client(String::from_utf8(encoded).unwrap());
820874
}
821875

876+
#[test]
877+
fn encode_summary() {
878+
let mut registry = Registry::default();
879+
let summary = Summary::new(3, 10, vec![0.5, 0.9, 0.99], 0.0);
880+
registry.register("my_summary", "My summary", summary.clone());
881+
summary.observe(0.10);
882+
summary.observe(0.20);
883+
summary.observe(0.30);
884+
885+
let mut encoded = Vec::new();
886+
887+
encode(&mut encoded, &registry).unwrap();
888+
889+
let expected = "# HELP my_summary My summary.\n".to_owned()
890+
+ "# TYPE my_summary summary\n"
891+
+ "my_summary_sum 0.6000000000000001\n"
892+
+ "my_summary_count 3\n"
893+
+ "my_summary{quantile=\"0.5\"} 0.2\n"
894+
+ "my_summary{quantile=\"0.9\"} 0.3\n"
895+
+ "my_summary{quantile=\"0.99\"} 0.3\n"
896+
+ "# EOF\n";
897+
assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap());
898+
}
899+
822900
fn parse_with_python_client(input: String) {
823901
pyo3::prepare_freethreaded_python();
824902

src/metrics.rs

+2-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.
@@ -19,9 +20,9 @@ pub enum MetricType {
1920
Histogram,
2021
Info,
2122
Unknown,
23+
Summary,
2224
// Not (yet) supported metric types.
2325
//
2426
// GaugeHistogram,
2527
// StateSet,
26-
// Summary
2728
}

src/metrics/counter.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ mod tests {
185185
// Map infinite, subnormal and NaN to 0.0.
186186
.map(|f| if f.is_normal() { f } else { 0.0 })
187187
.collect();
188-
let sum = fs.iter().sum();
188+
let sum: f64 = fs.iter().sum();
189189
let counter = Counter::<f64, AtomicU64>::default();
190190
for f in fs {
191191
counter.inc_by(f);

src/metrics/summary.rs

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! Module implementing an Open Metrics histogram.
2+
//!
3+
//! See [`Summary`] for details.
4+
5+
use super::{MetricType, TypedMetric};
6+
//use owning_ref::OwningRef;
7+
//use std::iter::{self, once};
8+
use std::sync::{Arc, Mutex};
9+
10+
use quantiles::ckms::CKMS;
11+
12+
/// Open Metrics [`Summary`] to measure distributions of discrete events.
13+
pub struct Summary {
14+
target_quantile: Vec<f64>,
15+
target_error: f64,
16+
max_age_buckets: u64,
17+
max_age_seconds: u64,
18+
inner: Arc<Mutex<InnerSummary>>,
19+
}
20+
21+
impl Clone for Summary {
22+
fn clone(&self) -> Self {
23+
Summary {
24+
target_quantile: self.target_quantile.clone(),
25+
target_error: self.target_error,
26+
max_age_buckets: self.max_age_buckets,
27+
max_age_seconds: self.max_age_seconds,
28+
inner: self.inner.clone(),
29+
}
30+
}
31+
}
32+
33+
pub(crate) struct InnerSummary {
34+
sum: f64,
35+
count: u64,
36+
quantile_streams: Vec<CKMS<f64>>,
37+
// head_stream is like a cursor which carries the index
38+
// of the stream in the quantile_streams that we want to query
39+
head_stream: u64,
40+
}
41+
42+
impl Summary {
43+
pub fn new(max_age_buckets: u64, max_age_seconds: u64, target_quantile: Vec<f64>, target_error: f64) -> Self {
44+
let mut streams: Vec<CKMS<f64>> = Vec::new();
45+
for _ in 0..max_age_buckets {
46+
streams.push(CKMS::new(target_error));
47+
}
48+
49+
Summary{
50+
max_age_buckets,
51+
max_age_seconds,
52+
target_quantile,
53+
target_error,
54+
inner: Arc::new(Mutex::new(InnerSummary {
55+
sum: Default::default(),
56+
count: Default::default(),
57+
quantile_streams: streams,
58+
head_stream: 0,
59+
}))
60+
}
61+
}
62+
63+
pub fn observe(&self, v: f64) {
64+
let mut inner = self.inner.lock().unwrap();
65+
inner.sum += v;
66+
inner.count += 1;
67+
68+
// insert quantiles into all streams/buckets.
69+
for stream in inner.quantile_streams.iter_mut() {
70+
stream.insert(v);
71+
}
72+
}
73+
74+
pub fn get(&self) -> (f64, u64, Vec<(f64, f64)>) {
75+
let inner = self.inner.lock().unwrap();
76+
let sum = inner.sum;
77+
let count = inner.count;
78+
let head = inner.head_stream;
79+
let mut quantile_values: Vec<(f64, f64)> = Vec::new();
80+
81+
// TODO: add stream rotation
82+
for q in self.target_quantile.iter() {
83+
match inner.quantile_streams[head as usize].query(*q) {
84+
Some((_, v)) => quantile_values.push((*q, v)),
85+
None => continue, // TODO fix this
86+
};
87+
}
88+
(sum, count, quantile_values)
89+
}
90+
}
91+
92+
// TODO: should this type impl Default like Counter?
93+
94+
impl TypedMetric for Summary {
95+
const TYPE: MetricType = MetricType::Summary;
96+
}
97+
98+
#[cfg(test)]
99+
mod tests {
100+
use super::*;
101+
102+
#[test]
103+
fn basic() {
104+
let summary = Summary::new(5, 10, vec![0.5, 0.9, 0.99], 0.01);
105+
summary.observe(5.0);
106+
summary.observe(15.0);
107+
summary.observe(25.0);
108+
109+
let (s, c, q) = summary.get();
110+
assert_eq!(45.0, s);
111+
assert_eq!(3, c);
112+
113+
for elem in q.iter() {
114+
println!("Vec<{}, {}>", elem.0, elem.1);
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)