Skip to content

Commit ca715c8

Browse files
committed
Keep histogram _sum in samples, drop it only in OpenMetrics 1.0 output
OpenMetrics 2.0 wants a sum on every histogram, so Histogram now keeps _sum even when the lowest bucket is negative, and multiprocess goes back to keeping it too. OpenMetrics 1.0 doesn't allow a sum with negative buckets, so generate_latest drops _sum for those histograms when the version is below 2.0.0. Signed-off-by: Haegan McGarry <haeganm@gmail.com>
1 parent 8426f96 commit ca715c8

6 files changed

Lines changed: 32 additions & 8 deletions

File tree

‎docs/content/instrumenting/histogram.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ A Histogram exposes three time series per metric:
1818
- `<name>_sum` — sum of all observed values
1919
- `<name>_count` — total number of observations
2020

21-
If the lowest bucket bound is negative, `_sum` is left out, because OpenMetrics
22-
1.0 does not allow a sum for histograms with negative buckets.
21+
If the lowest bucket bound is negative, OpenMetrics 1.0 output leaves `_sum`
22+
out, because OpenMetrics 1.0 does not allow a sum for histograms with negative
23+
buckets. Other formats, including OpenMetrics 2.0, keep it.
2324

2425
## Constructor
2526

‎prometheus_client/metrics.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -712,8 +712,7 @@ def _child_samples(self) -> Iterable[Sample]:
712712
acc += self._buckets[i].get()
713713
samples.append(Sample('_bucket', {'le': floatToGoString(bound)}, acc, None, self._buckets[i].get_exemplar()))
714714
samples.append(Sample('_count', {}, acc, None, None))
715-
if self._upper_bounds[0] >= 0:
716-
samples.append(Sample('_sum', {}, self._sum.get(), None, None))
715+
samples.append(Sample('_sum', {}, self._sum.get(), None, None))
717716
if _use_created:
718717
samples.append(Sample('_created', {}, self._created, None, None))
719718
return tuple(samples)

‎prometheus_client/multiprocess.py‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@ def _accumulate_metrics(metrics, accumulate):
158158
samples[labels][sample_key] = value
159159
if accumulate:
160160
samples[labels][(metric.name + '_count', labels)] = acc
161-
# Like Histogram, don't include sum if there are negative buckets.
162-
if min(values) < 0:
163-
samples[labels].pop((metric.name + '_sum', labels), None)
164161

165162
# Convert to correct sample format.
166163
metric.samples = []

‎prometheus_client/openmetrics/exposition.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,16 @@ def generate_latest(registry, escaping=UNDERSCORES, version="1.0.0"):
6666
output.append(f'# TYPE {escape_metric_name(mname, escaping)} {metric.type}\n')
6767
if metric.unit:
6868
output.append(f'# UNIT {escape_metric_name(mname, escaping)} {metric.unit}\n')
69+
# OpenMetrics 1.0 doesn't allow _sum on a histogram with negative buckets.
70+
negative_bucket_labels = set()
71+
if metric.type == 'histogram' and parse_version(version) < (2, 0, 0):
72+
for s in metric.samples:
73+
if s.name == mname + '_bucket' and float(s.labels['le']) < 0:
74+
negative_bucket_labels.add(frozenset((k, v) for k, v in s.labels.items() if k != 'le'))
6975
for s in metric.samples:
76+
if (negative_bucket_labels and s.name == mname + '_sum'
77+
and frozenset(s.labels.items()) in negative_bucket_labels):
78+
continue
7079
if escaping == ALLOWUTF8 and not _is_valid_legacy_metric_name(s.name):
7180
labelstr = escape_metric_name(s.name, escaping)
7281
if s.labels:

‎tests/openmetrics/test_exposition.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,19 @@ def test_histogram_negative_buckets(self) -> None:
318318
hh_count 1.0
319319
hh_created 123.456
320320
# EOF
321+
""", generate_latest(self.registry, version="1.0.0"))
322+
self.assertEqual(b"""# HELP hh A histogram
323+
# TYPE hh histogram
324+
hh_bucket{le="-1.0"} 0.0
325+
hh_bucket{le="-0.5"} 1.0
326+
hh_bucket{le="0.0"} 1.0
327+
hh_bucket{le="0.5"} 1.0
328+
hh_bucket{le="1.0"} 1.0
329+
hh_bucket{le="+Inf"} 1.0
330+
hh_count 1.0
331+
hh_sum -0.5
332+
hh_created 123.456
333+
# EOF
321334
""", generate_latest(self.registry, version="2.0.0"))
322335

323336
def test_histogram_exemplar(self) -> None:

‎tests/test_multiprocess.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from prometheus_client.multiprocess import (
1616
mark_process_dead, MultiProcessCollector,
1717
)
18+
from prometheus_client.openmetrics.exposition import \
19+
generate_latest as openmetrics_generate_latest
1820
from prometheus_client.values import (
1921
get_value_class, MultiProcessValue, MutexValue,
2022
)
@@ -133,7 +135,10 @@ def test_histogram_negative_buckets(self):
133135
self.assertEqual(1, self.registry.get_sample_value('h_bucket', {'l': 'x', 'le': '0.0'}))
134136
self.assertEqual(2, self.registry.get_sample_value('h_bucket', {'l': 'x', 'le': '+Inf'}))
135137
self.assertEqual(2, self.registry.get_sample_value('h_count', {'l': 'x'}))
136-
self.assertIsNone(self.registry.get_sample_value('h_sum', {'l': 'x'}))
138+
self.assertEqual(1.5, self.registry.get_sample_value('h_sum', {'l': 'x'}))
139+
# OpenMetrics 1.0 doesn't allow _sum with negative buckets, 2.0 does.
140+
self.assertNotIn(b'h_sum', openmetrics_generate_latest(self.registry, version='1.0.0'))
141+
self.assertIn(b'h_sum{l="x"} 1.5', openmetrics_generate_latest(self.registry, version='2.0.0'))
137142

138143
def test_gauge_all(self):
139144
g1 = Gauge('g', 'help', registry=None)

0 commit comments

Comments
 (0)