Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions prometheus_client/exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,10 @@ def choose_encoder(accept_header: str) -> Tuple[Callable[[CollectorRegistry], by
# Only return an escaping header if we have a good version and
# mimetype.
if not version:
return (partial(openmetrics.generate_latest, escaping=openmetrics.UNDERSCORES), openmetrics.CONTENT_TYPE_LATEST)
return (partial(openmetrics.generate_latest, escaping=openmetrics.UNDERSCORES, version="1.0.0"), openmetrics.CONTENT_TYPE_LATEST)
if version and Version(version) >= Version('1.0.0'):
return (partial(openmetrics.generate_latest, escaping=escaping),
openmetrics.CONTENT_TYPE_LATEST + '; escaping=' + str(escaping))
return (partial(openmetrics.generate_latest, escaping=escaping, version=version),
f'application/openmetrics-text; version={version}; charset=utf-8; escaping=' + str(escaping))
elif accepted.split(';')[0].strip() == 'text/plain':
toks = accepted.split(';')
version = _get_version(toks)
Expand Down
16 changes: 12 additions & 4 deletions prometheus_client/openmetrics/exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
from sys import maxunicode
from typing import Callable

from packaging.version import Version

from ..utils import floatToGoString
from ..validation import (
_is_valid_legacy_labelname, _is_valid_legacy_metric_name,
)

CONTENT_TYPE_LATEST = 'application/openmetrics-text; version=1.0.0; charset=utf-8'
"""Content type of the latest OpenMetrics text format"""
"""Content type of the latest OpenMetrics 1.0 text format"""
CONTENT_TYPE_LATEST_2_0 = 'application/openmetrics-text; version=2.0.0; charset=utf-8'
"""Content type of the OpenMetrics 2.0 text format"""
ESCAPING_HEADER_TAG = 'escaping'


Expand Down Expand Up @@ -53,7 +57,7 @@ def _compose_exemplar_string(metric, sample, exemplar):
return exemplarstr


def generate_latest(registry, escaping=UNDERSCORES):
def generate_latest(registry, escaping=UNDERSCORES, version="1.0.0"):
'''Returns the metrics from the registry in latest text format as a string.'''
output = []
for metric in registry.collect():
Expand Down Expand Up @@ -95,7 +99,7 @@ def generate_latest(registry, escaping=UNDERSCORES):
positive_spans = ''
positive_deltas = ''

if s.native_histogram:
if s.native_histogram and Version(version) >= Version('2.0.0'):
# Initialize basic nh template
nh_sample_template = '{{count:{},sum:{},schema:{},zero_threshold:{},zero_count:{}'

Expand Down Expand Up @@ -136,8 +140,12 @@ def generate_latest(registry, escaping=UNDERSCORES):
nh_exemplarstr = _compose_exemplar_string(metric, s, nh_ex)
exemplarstr += nh_exemplarstr

# Skip native histogram samples entirely if version < 2.0.0
if s.native_histogram and Version(version) < Version('2.0.0'):
Copy link
Member

Choose a reason for hiding this comment

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

Couldn't we do this skip on line 95 and then not have to add and Version(version) >= Version('2.0.0') in other places?

Copy link
Member

Choose a reason for hiding this comment

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

or maybe even further up?

Copy link
Member

Choose a reason for hiding this comment

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

Agreed, if we're skipping anyway I think we could just do one check and the continue logic at the very beginning of native histogram processing. Otherwise if there are multiple entry points we want to check I think it would be nice to have a single variable like native_histograms_supported that we calculate based off the version at the top of the function, then we can add more flags for other open metrics 2.0 things like the inline created timestamp.

continue

value = ''
if s.native_histogram:
if s.native_histogram and Version(version) >= Version('2.0.0'):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if s.native_histogram and Version(version) >= Version('2.0.0'):
if native_histogram:

I think that instead of checking for both source and the flag we can just check to see if the constructed native_histogram variable is no longer empty.

value = native_histogram
elif s.value is not None:
value = floatToGoString(s.value)
Expand Down
88 changes: 66 additions & 22 deletions tests/openmetrics/test_exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def test_summary(self) -> None:
ss_sum{a="c",b="d"} 17.0
ss_created{a="c",b="d"} 123.456
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="1.0.0"))

def test_histogram(self) -> None:
s = Histogram('hh', 'A histogram', registry=self.registry)
Expand All @@ -109,7 +109,7 @@ def test_histogram(self) -> None:
hh_sum 0.05
hh_created 123.456
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="1.0.0"))


def test_native_histogram(self) -> None:
Expand All @@ -120,7 +120,7 @@ def test_native_histogram(self) -> None:
# TYPE nh histogram
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]}
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="2.0.0"))

def test_nh_histogram_with_exemplars(self) -> None:
hfm = HistogramMetricFamily("nh", "nh")
Expand All @@ -130,7 +130,7 @@ def test_nh_histogram_with_exemplars(self) -> None:
# TYPE nh histogram
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
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="2.0.0"))

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


def test_nh_longer_spans(self) -> None:
Expand All @@ -151,7 +151,7 @@ def test_nh_longer_spans(self) -> None:
# TYPE nhsp histogram
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]}
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="2.0.0"))

def test_native_histogram_utf8(self) -> None:
hfm = HistogramMetricFamily("native{histogram", "Is a basic example of a native histogram")
Expand All @@ -161,7 +161,7 @@ def test_native_histogram_utf8(self) -> None:
# TYPE "native{histogram" histogram
{"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]}
# EOF
""", generate_latest(self.registry, ALLOWUTF8))
""", generate_latest(self.registry, ALLOWUTF8, version="2.0.0"))

def test_native_histogram_utf8_stress(self) -> None:
hfm = HistogramMetricFamily("native{histogram", "Is a basic example of a native histogram")
Expand All @@ -171,7 +171,7 @@ def test_native_histogram_utf8_stress(self) -> None:
# TYPE "native{histogram" histogram
{"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]}
# EOF
""", generate_latest(self.registry, ALLOWUTF8))
""", generate_latest(self.registry, ALLOWUTF8, version="2.0.0"))

def test_native_histogram_with_labels(self) -> None:
hfm = HistogramMetricFamily("hist_w_labels", "Is a basic example of a native histogram with labels")
Expand All @@ -181,7 +181,7 @@ def test_native_histogram_with_labels(self) -> None:
# TYPE hist_w_labels histogram
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]}
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="2.0.0"))

def test_native_histogram_with_labels_utf8(self) -> None:
hfm = HistogramMetricFamily("hist.w.labels", "Is a basic example of a native histogram with labels")
Expand All @@ -191,7 +191,7 @@ def test_native_histogram_with_labels_utf8(self) -> None:
# TYPE "hist.w.labels" histogram
{"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]}
# EOF
""", generate_latest(self.registry, ALLOWUTF8))
""", generate_latest(self.registry, ALLOWUTF8, version="2.0.0"))

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

def test_native_plus_classic_histogram_two_labelsets(self) -> None:
hfm = HistogramMetricFamily("hist_w_classic_two_sets", "Is an example of a native histogram plus a classic histogram with two label sets")
Expand Down Expand Up @@ -237,7 +237,33 @@ def test_native_plus_classic_histogram_two_labelsets(self) -> None:
hist_w_classic_two_sets_count{foo="baz"} 24.0
hist_w_classic_two_sets_sum{foo="baz"} 100.0
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="2.0.0"))

def test_native_plus_classic_histogram_two_labelsets_OM_1(self) -> None:
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")
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)))
hfm.add_sample("hist_w_classic_two_sets_bucket", {"foo": "bar", "le": "0.001"}, 4.0, None, None, None)
hfm.add_sample("hist_w_classic_two_sets_bucket", {"foo": "bar", "le": "+Inf"}, 24.0, None, None, None)
hfm.add_sample("hist_w_classic_two_sets_count", {"foo": "bar"}, 24.0, None, None, None)
hfm.add_sample("hist_w_classic_two_sets_sum", {"foo": "bar"}, 100.0, None, None, None)
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)))
hfm.add_sample("hist_w_classic_two_sets_bucket", {"foo": "baz", "le": "0.001"}, 4.0, None, None, None)
hfm.add_sample("hist_w_classic_two_sets_bucket", {"foo": "baz", "le": "+Inf"}, 24.0, None, None, None)
hfm.add_sample("hist_w_classic_two_sets_count", {"foo": "baz"}, 24.0, None, None, None)
hfm.add_sample("hist_w_classic_two_sets_sum", {"foo": "baz"}, 100.0, None, None, None)
self.custom_collector(hfm)
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
# TYPE hist_w_classic_two_sets histogram
hist_w_classic_two_sets_bucket{foo="bar",le="0.001"} 4.0
hist_w_classic_two_sets_bucket{foo="bar",le="+Inf"} 24.0
hist_w_classic_two_sets_count{foo="bar"} 24.0
hist_w_classic_two_sets_sum{foo="bar"} 100.0
hist_w_classic_two_sets_bucket{foo="baz",le="0.001"} 4.0
hist_w_classic_two_sets_bucket{foo="baz",le="+Inf"} 24.0
hist_w_classic_two_sets_count{foo="baz"} 24.0
hist_w_classic_two_sets_sum{foo="baz"} 100.0
# EOF
""", generate_latest(self.registry, version="1.0.0"))

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

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

def test_counter_exemplar(self) -> None:
c = Counter('cc', 'A counter', registry=self.registry)
Expand All @@ -283,7 +309,7 @@ def test_counter_exemplar(self) -> None:
cc_total 1.0 # {a="b"} 1.0 123.456
cc_created 123.456
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="1.0.0"))

def test_untyped_exemplar(self) -> None:
class MyCollector:
Expand Down Expand Up @@ -331,7 +357,7 @@ def test_gaugehistogram(self) -> None:
gh_gcount 5.0
gh_gsum 7.0
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="1.0.0"))

def test_gaugehistogram_negative_buckets(self) -> None:
self.custom_collector(
Expand All @@ -343,7 +369,7 @@ def test_gaugehistogram_negative_buckets(self) -> None:
gh_gcount 5.0
gh_gsum -7.0
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="1.0.0"))

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

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

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

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

def test_nonnumber(self) -> None:
class MyNumber:
Expand Down Expand Up @@ -424,7 +450,25 @@ def collect(self):
ts{foo="e"} 0.0 123.000456000
ts{foo="f"} 0.0 123.000000456
# EOF
""", generate_latest(self.registry))
""", generate_latest(self.registry, version="1.0.0"))

def test_native_histogram_version_comparison(self) -> None:
hfm = HistogramMetricFamily("nh_version", "nh version test")
hfm.add_sample("nh_version", {}, 0, None, None, NativeHistogram(5, 10, 0, 0.01, 2, (BucketSpan(0, 1),), (BucketSpan(0, 1),), (3,), (4,)))
self.custom_collector(hfm)

# Version 1.0.0 should omit native histogram samples entirely
self.assertEqual(b"""# HELP nh_version nh version test
# TYPE nh_version histogram
# EOF
""", generate_latest(self.registry, version="1.0.0"))

# Version 2.0.0 should emit native histogram format
self.assertEqual(b"""# HELP nh_version nh version test
# TYPE nh_version histogram
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]}
# EOF
""", generate_latest(self.registry, version="2.0.0"))


@pytest.mark.parametrize("scenario", [
Expand Down