Skip to content
Open
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.26.0]

### Added

- Added native histogram support, including native-only histograms and
histograms with both classic and native buckets.
- Added Prometheus protobuf encoding for native histogram sparse buckets.

## [0.25.0]

### Added
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "prometheus-client"
version = "0.25.0"
version = "0.26.0"
authors = ["Max Inden <mail@max-inden.de>"]
edition = "2021"
description = "Open Metrics client library allowing users to natively instrument applications."
Expand Down Expand Up @@ -67,6 +67,10 @@ harness = false
name = "family"
harness = false

[[bench]]
name = "histogram"
harness = false

[[bench]]
name = "text"
path = "benches/encoding/text.rs"
Expand Down
42 changes: 42 additions & 0 deletions benches/histogram.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use prometheus_client::metrics::histogram::{
exponential_buckets, Histogram, NativeHistogramConfig,
};

const OBSERVATION: f64 = 64.0;

pub fn histogram(c: &mut Criterion) {
let mut group = c.benchmark_group("observe");

group.bench_function("histogram", |b| {
let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10));

b.iter(|| {
histogram.observe(black_box(OBSERVATION));
})
});

group.bench_function("native histogram", |b| {
let histogram = Histogram::new_native(NativeHistogramConfig::with_schema(0));

b.iter(|| {
histogram.observe(black_box(OBSERVATION));
})
});

group.bench_function("classic and native histogram", |b| {
let histogram = Histogram::new_classic_and_native(
exponential_buckets(1.0, 2.0, 10),
NativeHistogramConfig::with_schema(0),
);

b.iter(|| {
histogram.observe(black_box(OBSERVATION));
})
});

group.finish();
}

criterion_group!(benches, histogram);
criterion_main!(benches);
46 changes: 46 additions & 0 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,32 @@ pub mod text;
#[deprecated(note = "Use openmetrics_protobuf instead.")]
pub use openmetrics_protobuf as protobuf;

/// Native histogram fields shared by encoders.
#[derive(Clone, Copy, Debug)]
pub struct NativeHistogram<'a> {
/// Native histogram schema.
pub schema: i32,
/// Breadth of the zero bucket.
pub zero_threshold: f64,
/// Count in the zero bucket.
pub zero_count: u64,
/// Negative sparse buckets.
pub negative: NativeHistogramBuckets<'a>,
/// Positive sparse buckets.
pub positive: NativeHistogramBuckets<'a>,
/// Native histogram creation timestamp.
pub created: Option<SystemTime>,
}

/// Sparse bucket span and delta encoding for one side of a native histogram.
#[derive(Clone, Copy, Debug)]
pub struct NativeHistogramBuckets<'a> {
/// Bucket spans.
pub spans: &'a [(i32, u32)],
/// Bucket count deltas.
pub deltas: &'a [i64],
}

macro_rules! for_both_mut {
($self:expr, $inner:ident, $pattern:pat, $fn:expr) => {
match &mut $self.0 {
Expand Down Expand Up @@ -237,6 +263,26 @@ impl MetricEncoder<'_> {
)
}

/// Encode a histogram that may have native buckets.
///
/// Encoders without native histogram support encode the classic buckets when
/// present and reject native-only histograms.
pub fn encode_histogram_with_native<S: EncodeLabelSet>(
&mut self,
sum: f64,
count: u64,
buckets: &[(f64, u64)],
exemplars: Option<&HashMap<usize, Exemplar<S, f64>>>,
native: NativeHistogram<'_>,
) -> Result<(), std::fmt::Error> {
for_both_mut!(
self,
MetricEncoderInner,
e,
e.encode_histogram_with_native(sum, count, buckets, exemplars, native)
)
}

/// Encode a metric family.
pub fn encode_family<'s, S: EncodeLabelSet>(
&'s mut self,
Expand Down
31 changes: 29 additions & 2 deletions src/encoding/openmetrics_protobuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ use crate::metrics::MetricType;
use crate::registry::{Registry, Unit};
use crate::{metrics::exemplar::Exemplar, registry::Prefix};

use super::{EncodeCounterValue, EncodeExemplarValue, EncodeGaugeValue, EncodeLabelSet};
use super::{
EncodeCounterValue, EncodeExemplarValue, EncodeGaugeValue, EncodeLabelSet, NativeHistogram,
};

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

Ok(())
}

pub fn encode_histogram_with_native<S: EncodeLabelSet>(
&mut self,
sum: f64,
count: u64,
buckets: &[(f64, u64)],
exemplars: Option<&HashMap<usize, Exemplar<S, f64>>>,
_native: NativeHistogram<'_>,
) -> Result<(), std::fmt::Error> {
if buckets.is_empty() {
return Err(std::fmt::Error);
}

self.encode_histogram(sum, count, buckets, exemplars)
}
}

impl<S: EncodeLabelSet, V: EncodeExemplarValue> TryFrom<&Exemplar<S, V>>
Expand Down Expand Up @@ -454,7 +471,7 @@ mod tests {
use crate::metrics::exemplar::{CounterWithExemplar, HistogramWithExemplars};
use crate::metrics::family::Family;
use crate::metrics::gauge::Gauge;
use crate::metrics::histogram::{exponential_buckets, Histogram};
use crate::metrics::histogram::{exponential_buckets, Histogram, NativeHistogramConfig};
use crate::metrics::info::Info;
use crate::registry::Unit;
use std::borrow::Cow;
Expand Down Expand Up @@ -819,6 +836,16 @@ mod tests {
}
}

#[test]
fn encode_native_only_histogram_errors() {
let mut registry = Registry::default();
let histogram = Histogram::new_native(NativeHistogramConfig::with_schema(0));
registry.register("my_histogram", "My histogram", histogram.clone());
histogram.observe(1.0);

assert!(encode(&registry).is_err());
}

#[test]
fn encode_histogram_with_exemplars() {
let now = SystemTime::now();
Expand Down
Loading
Loading