Skip to content

Commit 3586355

Browse files
authored
Emit native histograms only when OM 2.0.0 is requested (#1128)
* Emit NH only if OM 2.0.0 is requested Signed-off-by: Arianna Vespri <[email protected]> * Adjust logic, add version comparison tests for NH Signed-off-by: Arianna Vespri <[email protected]> * Skip nh sample earlier in the logic Signed-off-by: Arianna Vespri <[email protected]> --------- Signed-off-by: Arianna Vespri <[email protected]>
1 parent 9e3eb6c commit 3586355

File tree

3 files changed

+79
-27
lines changed

3 files changed

+79
-27
lines changed

prometheus_client/exposition.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,10 @@ def choose_encoder(accept_header: str) -> Tuple[Callable[[CollectorRegistry], by
345345
# Only return an escaping header if we have a good version and
346346
# mimetype.
347347
if not version:
348-
return (partial(openmetrics.generate_latest, escaping=openmetrics.UNDERSCORES), openmetrics.CONTENT_TYPE_LATEST)
348+
return (partial(openmetrics.generate_latest, escaping=openmetrics.UNDERSCORES, version="1.0.0"), openmetrics.CONTENT_TYPE_LATEST)
349349
if version and Version(version) >= Version('1.0.0'):
350-
return (partial(openmetrics.generate_latest, escaping=escaping),
351-
openmetrics.CONTENT_TYPE_LATEST + '; escaping=' + str(escaping))
350+
return (partial(openmetrics.generate_latest, escaping=escaping, version=version),
351+
f'application/openmetrics-text; version={version}; charset=utf-8; escaping=' + str(escaping))
352352
elif accepted.split(';')[0].strip() == 'text/plain':
353353
toks = accepted.split(';')
354354
version = _get_version(toks)

prometheus_client/openmetrics/exposition.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
from sys import maxunicode
55
from typing import Callable
66

7+
from packaging.version import Version
8+
79
from ..utils import floatToGoString
810
from ..validation import (
911
_is_valid_legacy_labelname, _is_valid_legacy_metric_name,
1012
)
1113

1214
CONTENT_TYPE_LATEST = 'application/openmetrics-text; version=1.0.0; charset=utf-8'
13-
"""Content type of the latest OpenMetrics text format"""
15+
"""Content type of the latest OpenMetrics 1.0 text format"""
16+
CONTENT_TYPE_LATEST_2_0 = 'application/openmetrics-text; version=2.0.0; charset=utf-8'
17+
"""Content type of the OpenMetrics 2.0 text format"""
1418
ESCAPING_HEADER_TAG = 'escaping'
1519

1620

@@ -53,7 +57,7 @@ def _compose_exemplar_string(metric, sample, exemplar):
5357
return exemplarstr
5458

5559

56-
def generate_latest(registry, escaping=UNDERSCORES):
60+
def generate_latest(registry, escaping=UNDERSCORES, version="1.0.0"):
5761
'''Returns the metrics from the registry in latest text format as a string.'''
5862
output = []
5963
for metric in registry.collect():
@@ -89,6 +93,10 @@ def generate_latest(registry, escaping=UNDERSCORES):
8993
if s.timestamp is not None:
9094
timestamp = f' {s.timestamp}'
9195

96+
# Skip native histogram samples entirely if version < 2.0.0
97+
if s.native_histogram and Version(version) < Version('2.0.0'):
98+
continue
99+
92100
native_histogram = ''
93101
negative_spans = ''
94102
negative_deltas = ''

tests/openmetrics/test_exposition.py

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def test_summary(self) -> None:
8383
ss_sum{a="c",b="d"} 17.0
8484
ss_created{a="c",b="d"} 123.456
8585
# EOF
86-
""", generate_latest(self.registry))
86+
""", generate_latest(self.registry, version="1.0.0"))
8787

8888
def test_histogram(self) -> None:
8989
s = Histogram('hh', 'A histogram', registry=self.registry)
@@ -109,7 +109,7 @@ def test_histogram(self) -> None:
109109
hh_sum 0.05
110110
hh_created 123.456
111111
# EOF
112-
""", generate_latest(self.registry))
112+
""", generate_latest(self.registry, version="1.0.0"))
113113

114114

115115
def test_native_histogram(self) -> None:
@@ -120,7 +120,7 @@ def test_native_histogram(self) -> None:
120120
# TYPE nh histogram
121121
nh {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,negative_spans:[0:2,1:2],negative_deltas:[2,1,-2,3],positive_spans:[0:2,1:2],positive_deltas:[2,1,-3,3]}
122122
# EOF
123-
""", generate_latest(self.registry))
123+
""", generate_latest(self.registry, version="2.0.0"))
124124

125125
def test_nh_histogram_with_exemplars(self) -> None:
126126
hfm = HistogramMetricFamily("nh", "nh")
@@ -130,7 +130,7 @@ def test_nh_histogram_with_exemplars(self) -> None:
130130
# TYPE nh histogram
131131
nh {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,negative_spans:[0:2,1:2],negative_deltas:[2,1,-2,3],positive_spans:[0:2,1:2],positive_deltas:[2,1,-3,3]} # {trace_id="KOO5S4vxi0o"} 0.67 # {trace_id="oHg5SJYRHA0"} 9.8 1520879607.789
132132
# EOF
133-
""", generate_latest(self.registry))
133+
""", generate_latest(self.registry, version="2.0.0"))
134134

135135
def test_nh_no_observation(self) -> None:
136136
hfm = HistogramMetricFamily("nhnoobs", "nhnoobs")
@@ -140,7 +140,7 @@ def test_nh_no_observation(self) -> None:
140140
# TYPE nhnoobs histogram
141141
nhnoobs {count:0,sum:0,schema:3,zero_threshold:2.938735877055719e-39,zero_count:0}
142142
# EOF
143-
""", generate_latest(self.registry))
143+
""", generate_latest(self.registry, version="2.0.0"))
144144

145145

146146
def test_nh_longer_spans(self) -> None:
@@ -151,7 +151,7 @@ def test_nh_longer_spans(self) -> None:
151151
# TYPE nhsp histogram
152152
nhsp {count:4,sum:6,schema:3,zero_threshold:2.938735877055719e-39,zero_count:1,positive_spans:[0:1,7:1,4:1],positive_deltas:[1,0,0]}
153153
# EOF
154-
""", generate_latest(self.registry))
154+
""", generate_latest(self.registry, version="2.0.0"))
155155

156156
def test_native_histogram_utf8(self) -> None:
157157
hfm = HistogramMetricFamily("native{histogram", "Is a basic example of a native histogram")
@@ -161,7 +161,7 @@ def test_native_histogram_utf8(self) -> None:
161161
# TYPE "native{histogram" histogram
162162
{"native{histogram"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,negative_spans:[0:2,1:2],negative_deltas:[2,1,-2,3],positive_spans:[0:2,1:2],positive_deltas:[2,1,-3,3]}
163163
# EOF
164-
""", generate_latest(self.registry, ALLOWUTF8))
164+
""", generate_latest(self.registry, ALLOWUTF8, version="2.0.0"))
165165

166166
def test_native_histogram_utf8_stress(self) -> None:
167167
hfm = HistogramMetricFamily("native{histogram", "Is a basic example of a native histogram")
@@ -171,7 +171,7 @@ def test_native_histogram_utf8_stress(self) -> None:
171171
# TYPE "native{histogram" histogram
172172
{"native{histogram", "xx{} # {}"=" EOF # {}}}"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,negative_spans:[0:2,1:2],negative_deltas:[2,1,-2,3],positive_spans:[0:2,1:2],positive_deltas:[2,1,-3,3]}
173173
# EOF
174-
""", generate_latest(self.registry, ALLOWUTF8))
174+
""", generate_latest(self.registry, ALLOWUTF8, version="2.0.0"))
175175

176176
def test_native_histogram_with_labels(self) -> None:
177177
hfm = HistogramMetricFamily("hist_w_labels", "Is a basic example of a native histogram with labels")
@@ -181,7 +181,7 @@ def test_native_histogram_with_labels(self) -> None:
181181
# TYPE hist_w_labels histogram
182182
hist_w_labels{baz="qux",foo="bar"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,negative_spans:[0:2,1:2],negative_deltas:[2,1,-2,3],positive_spans:[0:2,1:2],positive_deltas:[2,1,-3,3]}
183183
# EOF
184-
""", generate_latest(self.registry))
184+
""", generate_latest(self.registry, version="2.0.0"))
185185

186186
def test_native_histogram_with_labels_utf8(self) -> None:
187187
hfm = HistogramMetricFamily("hist.w.labels", "Is a basic example of a native histogram with labels")
@@ -191,7 +191,7 @@ def test_native_histogram_with_labels_utf8(self) -> None:
191191
# TYPE "hist.w.labels" histogram
192192
{"hist.w.labels", baz="qux",foo="bar"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,negative_spans:[0:2,1:2],negative_deltas:[2,1,-2,3],positive_spans:[0:2,1:2],positive_deltas:[2,1,-3,3]}
193193
# EOF
194-
""", generate_latest(self.registry, ALLOWUTF8))
194+
""", generate_latest(self.registry, ALLOWUTF8, version="2.0.0"))
195195

196196
def test_native_histogram_with_classic_histogram(self) -> None:
197197
hfm = HistogramMetricFamily("hist_w_classic", "Is a basic example of a native histogram coexisting with a classic histogram")
@@ -209,7 +209,7 @@ def test_native_histogram_with_classic_histogram(self) -> None:
209209
hist_w_classic_count{foo="bar"} 24.0
210210
hist_w_classic_sum{foo="bar"} 100.0
211211
# EOF
212-
""", generate_latest(self.registry))
212+
""", generate_latest(self.registry, version="2.0.0"))
213213

214214
def test_native_plus_classic_histogram_two_labelsets(self) -> None:
215215
hfm = HistogramMetricFamily("hist_w_classic_two_sets", "Is an example of a native histogram plus a classic histogram with two label sets")
@@ -237,7 +237,33 @@ def test_native_plus_classic_histogram_two_labelsets(self) -> None:
237237
hist_w_classic_two_sets_count{foo="baz"} 24.0
238238
hist_w_classic_two_sets_sum{foo="baz"} 100.0
239239
# EOF
240-
""", generate_latest(self.registry))
240+
""", generate_latest(self.registry, version="2.0.0"))
241+
242+
def test_native_plus_classic_histogram_two_labelsets_OM_1(self) -> None:
243+
hfm = HistogramMetricFamily("hist_w_classic_two_sets", "Is an example of a native histogram plus a classic histogram with two label sets in OM 1.0.0")
244+
hfm.add_sample("hist_w_classic_two_sets", {"foo": "bar"}, 0, None, None, NativeHistogram(24, 100, 0, 0.001, 4, (BucketSpan(0, 2), BucketSpan(1, 2)), (BucketSpan(0, 2), BucketSpan(1, 2)), (2, 1, -3, 3), (2, 1, -2, 3)))
245+
hfm.add_sample("hist_w_classic_two_sets_bucket", {"foo": "bar", "le": "0.001"}, 4.0, None, None, None)
246+
hfm.add_sample("hist_w_classic_two_sets_bucket", {"foo": "bar", "le": "+Inf"}, 24.0, None, None, None)
247+
hfm.add_sample("hist_w_classic_two_sets_count", {"foo": "bar"}, 24.0, None, None, None)
248+
hfm.add_sample("hist_w_classic_two_sets_sum", {"foo": "bar"}, 100.0, None, None, None)
249+
hfm.add_sample("hist_w_classic_two_sets", {"foo": "baz"}, 0, None, None, NativeHistogram(24, 100, 0, 0.001, 4, (BucketSpan(0, 2), BucketSpan(1, 2)), (BucketSpan(0, 2), BucketSpan(1, 2)), (2, 1, -3, 3), (2, 1, -2, 3)))
250+
hfm.add_sample("hist_w_classic_two_sets_bucket", {"foo": "baz", "le": "0.001"}, 4.0, None, None, None)
251+
hfm.add_sample("hist_w_classic_two_sets_bucket", {"foo": "baz", "le": "+Inf"}, 24.0, None, None, None)
252+
hfm.add_sample("hist_w_classic_two_sets_count", {"foo": "baz"}, 24.0, None, None, None)
253+
hfm.add_sample("hist_w_classic_two_sets_sum", {"foo": "baz"}, 100.0, None, None, None)
254+
self.custom_collector(hfm)
255+
self.assertEqual(b"""# HELP hist_w_classic_two_sets Is an example of a native histogram plus a classic histogram with two label sets in OM 1.0.0
256+
# TYPE hist_w_classic_two_sets histogram
257+
hist_w_classic_two_sets_bucket{foo="bar",le="0.001"} 4.0
258+
hist_w_classic_two_sets_bucket{foo="bar",le="+Inf"} 24.0
259+
hist_w_classic_two_sets_count{foo="bar"} 24.0
260+
hist_w_classic_two_sets_sum{foo="bar"} 100.0
261+
hist_w_classic_two_sets_bucket{foo="baz",le="0.001"} 4.0
262+
hist_w_classic_two_sets_bucket{foo="baz",le="+Inf"} 24.0
263+
hist_w_classic_two_sets_count{foo="baz"} 24.0
264+
hist_w_classic_two_sets_sum{foo="baz"} 100.0
265+
# EOF
266+
""", generate_latest(self.registry, version="1.0.0"))
241267

242268
def test_histogram_negative_buckets(self) -> None:
243269
s = Histogram('hh', 'A histogram', buckets=[-1, -0.5, 0, 0.5, 1], registry=self.registry)
@@ -253,7 +279,7 @@ def test_histogram_negative_buckets(self) -> None:
253279
hh_count 1.0
254280
hh_created 123.456
255281
# EOF
256-
""", generate_latest(self.registry))
282+
""", generate_latest(self.registry, version="2.0.0"))
257283

258284
def test_histogram_exemplar(self) -> None:
259285
s = Histogram('hh', 'A histogram', buckets=[1, 2, 3, 4], registry=self.registry)
@@ -273,7 +299,7 @@ def test_histogram_exemplar(self) -> None:
273299
hh_sum 8.0
274300
hh_created 123.456
275301
# EOF
276-
""", generate_latest(self.registry))
302+
""", generate_latest(self.registry, version="1.0.0"))
277303

278304
def test_counter_exemplar(self) -> None:
279305
c = Counter('cc', 'A counter', registry=self.registry)
@@ -283,7 +309,7 @@ def test_counter_exemplar(self) -> None:
283309
cc_total 1.0 # {a="b"} 1.0 123.456
284310
cc_created 123.456
285311
# EOF
286-
""", generate_latest(self.registry))
312+
""", generate_latest(self.registry, version="1.0.0"))
287313

288314
def test_untyped_exemplar(self) -> None:
289315
class MyCollector:
@@ -331,7 +357,7 @@ def test_gaugehistogram(self) -> None:
331357
gh_gcount 5.0
332358
gh_gsum 7.0
333359
# EOF
334-
""", generate_latest(self.registry))
360+
""", generate_latest(self.registry, version="1.0.0"))
335361

336362
def test_gaugehistogram_negative_buckets(self) -> None:
337363
self.custom_collector(
@@ -343,7 +369,7 @@ def test_gaugehistogram_negative_buckets(self) -> None:
343369
gh_gcount 5.0
344370
gh_gsum -7.0
345371
# EOF
346-
""", generate_latest(self.registry))
372+
""", generate_latest(self.registry, version="1.0.0"))
347373

348374
def test_info(self) -> None:
349375
i = Info('ii', 'A info', ['a', 'b'], registry=self.registry)
@@ -352,7 +378,7 @@ def test_info(self) -> None:
352378
# TYPE ii info
353379
ii_info{a="c",b="d",foo="bar"} 1.0
354380
# EOF
355-
""", generate_latest(self.registry))
381+
""", generate_latest(self.registry, version="2.0.0"))
356382

357383
def test_enum(self) -> None:
358384
i = Enum('ee', 'An enum', ['a', 'b'], registry=self.registry, states=['foo', 'bar'])
@@ -362,7 +388,7 @@ def test_enum(self) -> None:
362388
ee{a="c",b="d",ee="foo"} 0.0
363389
ee{a="c",b="d",ee="bar"} 1.0
364390
# EOF
365-
""", generate_latest(self.registry))
391+
""", generate_latest(self.registry, version="2.0.0"))
366392

367393
def test_unicode(self) -> None:
368394
c = Counter('cc', '\u4500', ['l'], registry=self.registry)
@@ -372,7 +398,7 @@ def test_unicode(self) -> None:
372398
cc_total{l="\xe4\x94\x80"} 1.0
373399
cc_created{l="\xe4\x94\x80"} 123.456
374400
# EOF
375-
""", generate_latest(self.registry))
401+
""", generate_latest(self.registry, version="2.0.0"))
376402

377403
def test_escaping(self) -> None:
378404
c = Counter('cc', 'A\ncount\\er\"', ['a'], registry=self.registry)
@@ -382,7 +408,7 @@ def test_escaping(self) -> None:
382408
cc_total{a="\\\\x\\n\\""} 1.0
383409
cc_created{a="\\\\x\\n\\""} 123.456
384410
# EOF
385-
""", generate_latest(self.registry))
411+
""", generate_latest(self.registry, version="2.0.0"))
386412

387413
def test_nonnumber(self) -> None:
388414
class MyNumber:
@@ -424,7 +450,25 @@ def collect(self):
424450
ts{foo="e"} 0.0 123.000456000
425451
ts{foo="f"} 0.0 123.000000456
426452
# EOF
427-
""", generate_latest(self.registry))
453+
""", generate_latest(self.registry, version="1.0.0"))
454+
455+
def test_native_histogram_version_comparison(self) -> None:
456+
hfm = HistogramMetricFamily("nh_version", "nh version test")
457+
hfm.add_sample("nh_version", {}, 0, None, None, NativeHistogram(5, 10, 0, 0.01, 2, (BucketSpan(0, 1),), (BucketSpan(0, 1),), (3,), (4,)))
458+
self.custom_collector(hfm)
459+
460+
# Version 1.0.0 should omit native histogram samples entirely
461+
self.assertEqual(b"""# HELP nh_version nh version test
462+
# TYPE nh_version histogram
463+
# EOF
464+
""", generate_latest(self.registry, version="1.0.0"))
465+
466+
# Version 2.0.0 should emit native histogram format
467+
self.assertEqual(b"""# HELP nh_version nh version test
468+
# TYPE nh_version histogram
469+
nh_version {count:5,sum:10,schema:0,zero_threshold:0.01,zero_count:2,negative_spans:[0:1],negative_deltas:[4],positive_spans:[0:1],positive_deltas:[3]}
470+
# EOF
471+
""", generate_latest(self.registry, version="2.0.0"))
428472

429473

430474
@pytest.mark.parametrize("scenario", [

0 commit comments

Comments
 (0)