Skip to content

Commit cad9ddb

Browse files
authored
Add native histogram support (#304)
* Add native histogram support Add native-only histograms and histograms that expose both classic and native buckets. Encode native sparse bucket spans and deltas through the Prometheus protobuf encoder while keeping text and OpenMetrics protobuf behavior on the classic representation when available. Signed-off-by: John Howard <john.howard@solo.io> * Address comments Signed-off-by: John Howard <john.howard@solo.io> * Revert version/notes Signed-off-by: John Howard <john.howard@solo.io> * Address comments around tests Signed-off-by: John Howard <john.howard@solo.io> * Lint Signed-off-by: John Howard <john.howard@solo.io> --------- Signed-off-by: John Howard <john.howard@solo.io>
1 parent 0c3ffbd commit cad9ddb

9 files changed

Lines changed: 2342 additions & 53 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unrelreased]
8+
9+
### Added
10+
11+
- Added native histogram support, including native-only histograms and
12+
histograms with both classic and native buckets.
13+
714
## [0.25.0]
815

916
### Added

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ harness = false
6767
name = "family"
6868
harness = false
6969

70+
[[bench]]
71+
name = "histogram"
72+
harness = false
73+
7074
[[bench]]
7175
name = "text"
7276
path = "benches/encoding/text.rs"

benches/histogram.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2+
use prometheus_client::metrics::histogram::{
3+
exponential_buckets, Histogram, NativeHistogramConfig,
4+
};
5+
6+
const OBSERVATION: f64 = 64.0;
7+
8+
pub fn histogram(c: &mut Criterion) {
9+
let mut group = c.benchmark_group("observe");
10+
11+
group.bench_function("histogram", |b| {
12+
let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10));
13+
14+
b.iter(|| {
15+
histogram.observe(black_box(OBSERVATION));
16+
})
17+
});
18+
19+
group.bench_function("native histogram", |b| {
20+
let histogram = Histogram::new_native(NativeHistogramConfig::with_schema(0));
21+
22+
b.iter(|| {
23+
histogram.observe(black_box(OBSERVATION));
24+
})
25+
});
26+
27+
group.bench_function("classic and native histogram", |b| {
28+
let histogram = Histogram::new_classic_and_native(
29+
exponential_buckets(1.0, 2.0, 10),
30+
NativeHistogramConfig::with_schema(0),
31+
);
32+
33+
b.iter(|| {
34+
histogram.observe(black_box(OBSERVATION));
35+
})
36+
});
37+
38+
group.finish();
39+
}
40+
41+
criterion_group!(benches, histogram);
42+
criterion_main!(benches);

src/encoding.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,32 @@ pub mod text;
3333
#[deprecated(note = "Use openmetrics_protobuf instead.")]
3434
pub use openmetrics_protobuf as protobuf;
3535

36+
/// Native histogram fields shared by encoders.
37+
#[derive(Clone, Copy, Debug)]
38+
pub struct NativeHistogram<'a> {
39+
/// Native histogram schema.
40+
pub schema: i32,
41+
/// Breadth of the zero bucket.
42+
pub zero_threshold: f64,
43+
/// Count in the zero bucket.
44+
pub zero_count: u64,
45+
/// Negative sparse buckets.
46+
pub negative: NativeHistogramBuckets<'a>,
47+
/// Positive sparse buckets.
48+
pub positive: NativeHistogramBuckets<'a>,
49+
/// Native histogram creation timestamp.
50+
pub created: Option<SystemTime>,
51+
}
52+
53+
/// Sparse bucket span and delta encoding for one side of a native histogram.
54+
#[derive(Clone, Copy, Debug)]
55+
pub struct NativeHistogramBuckets<'a> {
56+
/// Bucket spans.
57+
pub spans: &'a [(i32, u32)],
58+
/// Bucket count deltas.
59+
pub deltas: &'a [i64],
60+
}
61+
3662
macro_rules! for_both_mut {
3763
($self:expr, $inner:ident, $pattern:pat, $fn:expr) => {
3864
match &mut $self.0 {
@@ -237,6 +263,26 @@ impl MetricEncoder<'_> {
237263
)
238264
}
239265

266+
/// Encode a histogram that may have native buckets.
267+
///
268+
/// Encoders without native histogram support encode the classic buckets when
269+
/// present and reject native-only histograms.
270+
pub fn encode_histogram_with_native<S: EncodeLabelSet>(
271+
&mut self,
272+
sum: f64,
273+
count: u64,
274+
buckets: &[(f64, u64)],
275+
exemplars: Option<&HashMap<usize, Exemplar<S, f64>>>,
276+
native: NativeHistogram<'_>,
277+
) -> Result<(), std::fmt::Error> {
278+
for_both_mut!(
279+
self,
280+
MetricEncoderInner,
281+
e,
282+
e.encode_histogram_with_native(sum, count, buckets, exemplars, native)
283+
)
284+
}
285+
240286
/// Encode a metric family.
241287
pub fn encode_family<'s, S: EncodeLabelSet>(
242288
&'s mut self,

src/encoding/openmetrics_protobuf.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ use crate::metrics::MetricType;
3636
use crate::registry::{Registry, Unit};
3737
use crate::{metrics::exemplar::Exemplar, registry::Prefix};
3838

39-
use super::{EncodeCounterValue, EncodeExemplarValue, EncodeGaugeValue, EncodeLabelSet};
39+
use super::{
40+
EncodeCounterValue, EncodeExemplarValue, EncodeGaugeValue, EncodeLabelSet, NativeHistogram,
41+
};
4042

4143
/// Encode the metrics registered with the provided [`Registry`] into MetricSet
4244
/// using the OpenMetrics protobuf format.
@@ -288,6 +290,21 @@ impl MetricEncoder<'_> {
288290

289291
Ok(())
290292
}
293+
294+
pub fn encode_histogram_with_native<S: EncodeLabelSet>(
295+
&mut self,
296+
sum: f64,
297+
count: u64,
298+
buckets: &[(f64, u64)],
299+
exemplars: Option<&HashMap<usize, Exemplar<S, f64>>>,
300+
_native: NativeHistogram<'_>,
301+
) -> Result<(), std::fmt::Error> {
302+
if buckets.is_empty() {
303+
return Err(std::fmt::Error);
304+
}
305+
306+
self.encode_histogram(sum, count, buckets, exemplars)
307+
}
291308
}
292309

293310
impl<S: EncodeLabelSet, V: EncodeExemplarValue> TryFrom<&Exemplar<S, V>>
@@ -454,7 +471,7 @@ mod tests {
454471
use crate::metrics::exemplar::{CounterWithExemplar, HistogramWithExemplars};
455472
use crate::metrics::family::Family;
456473
use crate::metrics::gauge::Gauge;
457-
use crate::metrics::histogram::{exponential_buckets, Histogram};
474+
use crate::metrics::histogram::{exponential_buckets, Histogram, NativeHistogramConfig};
458475
use crate::metrics::info::Info;
459476
use crate::registry::Unit;
460477
use std::borrow::Cow;
@@ -819,6 +836,16 @@ mod tests {
819836
}
820837
}
821838

839+
#[test]
840+
fn encode_native_only_histogram_errors() {
841+
let mut registry = Registry::default();
842+
let histogram = Histogram::new_native(NativeHistogramConfig::with_schema(0));
843+
registry.register("my_histogram", "My histogram", histogram.clone());
844+
histogram.observe(1.0);
845+
846+
assert!(encode(&registry).is_err());
847+
}
848+
822849
#[test]
823850
fn encode_histogram_with_exemplars() {
824851
let now = SystemTime::now();

0 commit comments

Comments
 (0)