Skip to content

Add native histogram support#304

Open
howardjohn wants to merge 2 commits into
prometheus:masterfrom
howardjohn:histogram/native2
Open

Add native histogram support#304
howardjohn wants to merge 2 commits into
prometheus:masterfrom
howardjohn:histogram/native2

Conversation

@howardjohn
Copy link
Copy Markdown
Contributor

This PR adds support for native histograms using the new prometheus protobuf support recently merged.

The implementation is basically 1:1 lifted from the Golang implementation as much as possible, including tests.

Users can make any of the 3: classic only, native only, or classic + native histogram.

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>
@howardjohn howardjohn force-pushed the histogram/native2 branch from dc631bf to d4882eb Compare May 28, 2026 20:55
@f41gh7
Copy link
Copy Markdown

f41gh7 commented May 29, 2026

Related issue #150

Copy link
Copy Markdown
Collaborator

@krisztianfekete krisztianfekete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, I am planning to do some actual testing as well soon, but added a couple of comments/questions in the meantime!

Comment thread src/metrics/histogram.rs
Comment on lines +1171 to +1173
if inner.positive.len() + inner.negative.len() <= inner.max_buckets {
return false;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we follow what client_golang is doing here:
https://github.com/prometheus/client_golang/blob/02eaf492695d380c231404841c5652a39b06be84/prometheus/histogram.go#L923-L962

basically do one widening/doubling per call then reassess as

"very sparse buckets could lead to a low
reduction of the bucket count (or even no reduction at all)"

Comment thread src/metrics/histogram.rs Outdated
}

/// Create a new classic [`Histogram`].
pub fn new_classic(buckets: impl IntoIterator<Item = f64>) -> Self {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the same as new?

Comment thread src/metrics/histogram.rs Outdated
Comment on lines +590 to +592
fn native_histogram_bounds() -> &'static [&'static [f64]; 9] {
&NATIVE_HISTOGRAM_BOUNDS
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why we cannot just use NATIVE_HISTOGRAM_BOUNDS directly?

Comment thread src/metrics/histogram.rs Outdated
if frac == 0.5 {
key -= 1;
}
let offset = (1_i32 << (-(schema as i32) as u32)) - 1;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had #281 removing as casts, so maybe we should this pattern here and at a few other places? cc. @jalil-salame

Comment thread src/encoding/text.rs Outdated
_native: NativeHistogram<'_>,
) -> Result<(), std::fmt::Error> {
if buckets.is_empty() {
return Err(std::fmt::Error);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a typed error here and surface that the text encoder doesn't support native histograms?

Comment thread src/metrics/histogram.rs
Comment on lines +117 to +123
/// Set a custom zero threshold.
pub fn zero_threshold(mut self, zero_threshold: f64) -> Self {
assert!(zero_threshold.is_finite());
assert!(zero_threshold >= 0.0 || zero_threshold == NATIVE_HISTOGRAM_ZERO_THRESHOLD_ZERO);
self.zero_threshold = zero_threshold;
self
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/metrics/histogram.rs
@@ -173,15 +471,874 @@ pub fn linear_buckets(start: f64, width: f64, length: u16) -> impl Iterator<Item

impl EncodeMetric for Histogram {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using examplars this is skipped:

impl<S: EncodeLabelSet> EncodeMetric for HistogramWithExemplars<S> {
fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
let inner = self.inner();
let (sum, count, buckets) = inner.histogram.get();
encoder.encode_histogram(sum, count, &buckets, Some(&inner.exemplars))
}
fn metric_type(&self) -> MetricType {
Histogram::TYPE
}
}

and histogram.get() is classic only.

So maybe we can move this into a helper and use that from HistogramWithExemplars as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants