Skip to content
Merged
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
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
12 changes: 10 additions & 2 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 @@ -89,6 +93,10 @@ def generate_latest(registry, escaping=UNDERSCORES):
if s.timestamp is not None:
timestamp = f' {s.timestamp}'

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

native_histogram = ''
negative_spans = ''
negative_deltas = ''
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