diff --git a/prometheus_client/openmetrics/exposition.py b/prometheus_client/openmetrics/exposition.py index 84600605..3a57d796 100644 --- a/prometheus_client/openmetrics/exposition.py +++ b/prometheus_client/openmetrics/exposition.py @@ -20,6 +20,29 @@ def _is_valid_exemplar_metric(metric, sample): return False +def _compose_exemplar_string(metric, sample, exemplar): + """Constructs an exemplar string.""" + if not _is_valid_exemplar_metric(metric, sample): + raise ValueError(f"Metric {metric.name} has exemplars, but is not a histogram bucket or counter") + labels = '{{{0}}}'.format(','.join( + ['{}="{}"'.format( + k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) + for k, v in sorted(exemplar.labels.items())])) + if exemplar.timestamp is not None: + exemplarstr = ' # {} {} {}'.format( + labels, + floatToGoString(exemplar.value), + exemplar.timestamp, + ) + else: + exemplarstr = ' # {} {}'.format( + labels, + floatToGoString(exemplar.value), + ) + + return exemplarstr + + def generate_latest(registry): '''Returns the metrics from the registry in latest text format as a string.''' output = [] @@ -38,7 +61,7 @@ def generate_latest(registry): labelstr += ', ' else: labelstr = '' - + if s.labels: items = sorted(s.labels.items()) labelstr += ','.join( @@ -47,44 +70,90 @@ def generate_latest(registry): for k, v in items]) if labelstr: labelstr = "{" + labelstr + "}" - + if s.exemplar: - if not _is_valid_exemplar_metric(metric, s): - raise ValueError(f"Metric {metric.name} has exemplars, but is not a histogram bucket or counter") - labels = '{{{0}}}'.format(','.join( - ['{}="{}"'.format( - k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) - for k, v in sorted(s.exemplar.labels.items())])) - if s.exemplar.timestamp is not None: - exemplarstr = ' # {} {} {}'.format( - labels, - floatToGoString(s.exemplar.value), - s.exemplar.timestamp, - ) - else: - exemplarstr = ' # {} {}'.format( - labels, - floatToGoString(s.exemplar.value), - ) + exemplarstr = _compose_exemplar_string(metric, s, s.exemplar) else: exemplarstr = '' + timestamp = '' if s.timestamp is not None: timestamp = f' {s.timestamp}' + + native_histogram = '' + positive_spans = '' + positive_deltas = '' + negative_spans = '' + negative_deltas = '' + pos = False + neg = False + + if s.native_histogram: + # Initialize basic nh template + nh_sample_template = '{{count:{},sum:{},schema:{},zero_threshold:{},zero_count:{}' + + args = [ + s.native_histogram.count_value, + s.native_histogram.sum_value, + s.native_histogram.schema, + s.native_histogram.zero_threshold, + s.native_histogram.zero_count, + ] + + # If there are pos spans, append them to the template and args + if s.native_histogram.pos_spans: + positive_spans = ','.join([f'{ps[0]}:{ps[1]}' for ps in s.native_histogram.pos_spans]) + positive_deltas = ','.join(f'{pd}' for pd in s.native_histogram.pos_deltas) + nh_sample_template += ',positive_spans:[{}]' + args.append(positive_spans) + + # If there are neg spans exist, append them to the template and args + if s.native_histogram.neg_spans: + negative_spans = ','.join([f'{ns[0]}:{ns[1]}' for ns in s.native_histogram.neg_spans]) + negative_deltas = ','.join(str(nd) for nd in s.native_histogram.neg_deltas) + nh_sample_template += ',negative_spans:[{}]' + args.append(negative_spans) + + # Append pos deltas if pos spans were added + if s.native_histogram.pos_spans: + nh_sample_template += ',positive_deltas:[{}]' + args.append(positive_deltas) + + # Append neg deltas if neg spans were added + if s.native_histogram.neg_spans: + nh_sample_template += ',negative_deltas:[{}]' + args.append(negative_deltas) + + # Add closing brace + nh_sample_template += '}}' + + # Format the template with the args + native_histogram = nh_sample_template.format(*args) + + if s.native_histogram.nh_exemplars: + for nh_ex in s.native_histogram.nh_exemplars: + nh_exemplarstr = _compose_exemplar_string(metric, s, nh_ex) + exemplarstr += nh_exemplarstr + + value = '' + if s.native_histogram: + value = native_histogram + elif s.value is not None: + value = floatToGoString(s.value) if _is_valid_legacy_metric_name(s.name): output.append('{}{} {}{}{}\n'.format( s.name, labelstr, - floatToGoString(s.value), + value, timestamp, - exemplarstr, + exemplarstr )) else: output.append('{} {}{}{}\n'.format( labelstr, - floatToGoString(s.value), + value, timestamp, - exemplarstr, + exemplarstr )) except Exception as exception: exception.args = (exception.args or ('',)) + (metric,) diff --git a/prometheus_client/samples.py b/prometheus_client/samples.py index 16e03c04..994d1281 100644 --- a/prometheus_client/samples.py +++ b/prometheus_client/samples.py @@ -40,6 +40,17 @@ class BucketSpan(NamedTuple): length: int +# Timestamp and exemplar are optional. +# Value can be an int or a float. +# Timestamp can be a float containing a unixtime in seconds, +# a Timestamp object, or None. +# Exemplar can be an Exemplar object, or None. +class Exemplar(NamedTuple): + labels: Dict[str, str] + value: float + timestamp: Optional[Union[float, Timestamp]] = None + + # NativeHistogram is experimental and subject to change at any time. class NativeHistogram(NamedTuple): count_value: float @@ -51,17 +62,7 @@ class NativeHistogram(NamedTuple): neg_spans: Optional[Sequence[BucketSpan]] = None pos_deltas: Optional[Sequence[int]] = None neg_deltas: Optional[Sequence[int]] = None - - -# Timestamp and exemplar are optional. -# Value can be an int or a float. -# Timestamp can be a float containing a unixtime in seconds, -# a Timestamp object, or None. -# Exemplar can be an Exemplar object, or None. -class Exemplar(NamedTuple): - labels: Dict[str, str] - value: float - timestamp: Optional[Union[float, Timestamp]] = None + nh_exemplars: Optional[Sequence[Exemplar]] = None class Sample(NamedTuple): diff --git a/tests/openmetrics/test_exposition.py b/tests/openmetrics/test_exposition.py index 124e55e9..1d86de66 100644 --- a/tests/openmetrics/test_exposition.py +++ b/tests/openmetrics/test_exposition.py @@ -5,7 +5,8 @@ CollectorRegistry, Counter, Enum, Gauge, Histogram, Info, Metric, Summary, ) from prometheus_client.core import ( - Exemplar, GaugeHistogramMetricFamily, Timestamp, + BucketSpan, Exemplar, GaugeHistogramMetricFamily, HistogramMetricFamily, + NativeHistogram, Timestamp, ) from prometheus_client.openmetrics.exposition import generate_latest @@ -21,43 +22,43 @@ def setUp(self): def tearDown(self): time.time = self.old_time - def custom_collector(self, metric_family): + def custom_collector(self, metric_family) -> None: class CustomCollector: def collect(self): return [metric_family] self.registry.register(CustomCollector()) - def test_counter(self): + def test_counter(self) -> None: c = Counter('cc', 'A counter', registry=self.registry) c.inc() self.assertEqual(b'# HELP cc A counter\n# TYPE cc counter\ncc_total 1.0\ncc_created 123.456\n# EOF\n', generate_latest(self.registry)) - def test_counter_utf8(self): + def test_counter_utf8(self) -> None: c = Counter('cc.with.dots', 'A counter', registry=self.registry) c.inc() self.assertEqual(b'# HELP "cc.with.dots" A counter\n# TYPE "cc.with.dots" counter\n{"cc.with.dots_total"} 1.0\n{"cc.with.dots_created"} 123.456\n# EOF\n', generate_latest(self.registry)) - def test_counter_total(self): + def test_counter_total(self) -> None: c = Counter('cc_total', 'A counter', registry=self.registry) c.inc() self.assertEqual(b'# HELP cc A counter\n# TYPE cc counter\ncc_total 1.0\ncc_created 123.456\n# EOF\n', generate_latest(self.registry)) - def test_counter_unit(self): + def test_counter_unit(self) -> None: c = Counter('cc_seconds', 'A counter', registry=self.registry, unit="seconds") c.inc() self.assertEqual(b'# HELP cc_seconds A counter\n# TYPE cc_seconds counter\n# UNIT cc_seconds seconds\ncc_seconds_total 1.0\ncc_seconds_created 123.456\n# EOF\n', generate_latest(self.registry)) - def test_gauge(self): + def test_gauge(self) -> None: g = Gauge('gg', 'A gauge', registry=self.registry) g.set(17) self.assertEqual(b'# HELP gg A gauge\n# TYPE gg gauge\ngg 17.0\n# EOF\n', generate_latest(self.registry)) - def test_summary(self): + def test_summary(self) -> None: s = Summary('ss', 'A summary', ['a', 'b'], registry=self.registry) s.labels('c', 'd').observe(17) self.assertEqual(b"""# HELP ss A summary @@ -68,7 +69,7 @@ def test_summary(self): # EOF """, generate_latest(self.registry)) - def test_histogram(self): + def test_histogram(self) -> None: s = Histogram('hh', 'A histogram', registry=self.registry) s.observe(0.05) self.assertEqual(b"""# HELP hh A histogram @@ -94,7 +95,136 @@ def test_histogram(self): # EOF """, generate_latest(self.registry)) - def test_histogram_negative_buckets(self): + + def test_native_histogram(self) -> None: + hfm = HistogramMetricFamily("nh", "nh") + hfm.add_sample("nh", {}, 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))) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP nh nh +# TYPE nh histogram +nh {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} +# EOF +""", generate_latest(self.registry)) + + + def test_nh_histogram_with_exemplars(self) -> None: + hfm = HistogramMetricFamily("nh", "nh") + hfm.add_sample("nh", {}, 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), (Exemplar({"trace_id": "KOO5S4vxi0o"}, 0.67), Exemplar({"trace_id": "oHg5SJYRHA0"}, 9.8, float(Timestamp(1520879607, 0.789 * 1e9)))))) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP nh nh +# TYPE nh histogram +nh {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} # {trace_id="KOO5S4vxi0o"} 0.67 # {trace_id="oHg5SJYRHA0"} 9.8 1520879607.789 +# EOF +""", generate_latest(self.registry)) + + def test_nh_no_observation(self) -> None: + hfm = HistogramMetricFamily("nhnoobs", "nhnoobs") + hfm.add_sample("nhnoobs", {}, 0, None, None, NativeHistogram(0, 0, 3, 2.938735877055719e-39, 0)) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP nhnoobs nhnoobs +# TYPE nhnoobs histogram +nhnoobs {count:0,sum:0,schema:3,zero_threshold:2.938735877055719e-39,zero_count:0} +# EOF +""", generate_latest(self.registry)) + + + def test_nh_longer_spans(self) -> None: + hfm = HistogramMetricFamily("nhsp", "Is a basic example of a native histogram with three spans") + hfm.add_sample("nhsp", {}, 0, None, None, NativeHistogram(4, 6, 3, 2.938735877055719e-39, 1, (BucketSpan(0, 1), BucketSpan(7, 1), BucketSpan(4, 1)), None, (1, 0, 0), None)) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP nhsp Is a basic example of a native histogram with three spans +# 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)) + + def test_native_histogram_utf8(self) -> None: + hfm = HistogramMetricFamily("native{histogram", "Is a basic example of a native histogram") + hfm.add_sample("native{histogram", {}, 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))) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP "native{histogram" Is a basic example of a native histogram +# TYPE "native{histogram" histogram +{"native{histogram"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} +# EOF +""", generate_latest(self.registry)) + + def test_native_histogram_utf8_stress(self) -> None: + hfm = HistogramMetricFamily("native{histogram", "Is a basic example of a native histogram") + hfm.add_sample("native{histogram", {'xx{} # {}': ' EOF # {}}}'}, 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))) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP "native{histogram" Is a basic example of a native histogram +# TYPE "native{histogram" histogram +{"native{histogram", "xx{} # {}"=" EOF # {}}}"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} +# EOF +""", generate_latest(self.registry)) + + def test_native_histogram_with_labels(self) -> None: + hfm = HistogramMetricFamily("hist_w_labels", "Is a basic example of a native histogram with labels") + hfm.add_sample("hist_w_labels", {"foo": "bar", "baz": "qux"}, 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))) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP hist_w_labels Is a basic example of a native histogram with labels +# 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,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} +# EOF +""", generate_latest(self.registry)) + + def test_native_histogram_with_labels_utf8(self) -> None: + hfm = HistogramMetricFamily("hist.w.labels", "Is a basic example of a native histogram with labels") + hfm.add_sample("hist.w.labels", {"foo": "bar", "baz": "qux"}, 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))) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP "hist.w.labels" Is a basic example of a native histogram with labels +# 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,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} +# EOF +""", generate_latest(self.registry)) + + 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") + hfm.add_sample("hist_w_classic", {"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_bucket", {"foo": "bar", "le": "0.001"}, 4.0, None, None, None) + hfm.add_sample("hist_w_classic_bucket", {"foo": "bar", "le": "+Inf"}, 24.0, None, None, None) + hfm.add_sample("hist_w_classic_count", {"foo": "bar"}, 24.0, None, None, None) + hfm.add_sample("hist_w_classic_sum", {"foo": "bar"}, 100.0, None, None, None) + self.custom_collector(hfm) + self.assertEqual(b"""# HELP hist_w_classic Is a basic example of a native histogram coexisting with a classic histogram +# TYPE hist_w_classic histogram +hist_w_classic{foo="bar"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} +hist_w_classic_bucket{foo="bar",le="0.001"} 4.0 +hist_w_classic_bucket{foo="bar",le="+Inf"} 24.0 +hist_w_classic_count{foo="bar"} 24.0 +hist_w_classic_sum{foo="bar"} 100.0 +# EOF +""", generate_latest(self.registry)) + + 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") + 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 +# TYPE hist_w_classic_two_sets histogram +hist_w_classic_two_sets{foo="bar"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} +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{foo="baz"} {count:24,sum:100,schema:0,zero_threshold:0.001,zero_count:4,positive_spans:[0:2,1:2],negative_spans:[0:2,1:2],positive_deltas:[2,1,-3,3],negative_deltas:[2,1,-2,3]} +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)) + + def test_histogram_negative_buckets(self) -> None: s = Histogram('hh', 'A histogram', buckets=[-1, -0.5, 0, 0.5, 1], registry=self.registry) s.observe(-0.5) self.assertEqual(b"""# HELP hh A histogram @@ -110,7 +240,7 @@ def test_histogram_negative_buckets(self): # EOF """, generate_latest(self.registry)) - def test_histogram_exemplar(self): + def test_histogram_exemplar(self) -> None: s = Histogram('hh', 'A histogram', buckets=[1, 2, 3, 4], registry=self.registry) s.observe(0.5, {'a': 'b'}) s.observe(1.5, {'le': '7'}) @@ -130,7 +260,7 @@ def test_histogram_exemplar(self): # EOF """, generate_latest(self.registry)) - def test_counter_exemplar(self): + def test_counter_exemplar(self) -> None: c = Counter('cc', 'A counter', registry=self.registry) c.inc(exemplar={'a': 'b'}) self.assertEqual(b"""# HELP cc A counter @@ -140,7 +270,7 @@ def test_counter_exemplar(self): # EOF """, generate_latest(self.registry)) - def test_untyped_exemplar(self): + def test_untyped_exemplar(self) -> None: class MyCollector: def collect(self): metric = Metric("hh", "help", 'untyped') @@ -152,7 +282,7 @@ def collect(self): with self.assertRaises(ValueError): generate_latest(self.registry) - def test_histogram_non_bucket_exemplar(self): + def test_histogram_non_bucket_exemplar(self) -> None: class MyCollector: def collect(self): metric = Metric("hh", "help", 'histogram') @@ -164,7 +294,7 @@ def collect(self): with self.assertRaises(ValueError): generate_latest(self.registry) - def test_counter_non_total_exemplar(self): + def test_counter_non_total_exemplar(self) -> None: class MyCollector: def collect(self): metric = Metric("cc", "A counter", 'counter') @@ -176,7 +306,7 @@ def collect(self): with self.assertRaises(ValueError): generate_latest(self.registry) - def test_gaugehistogram(self): + def test_gaugehistogram(self) -> None: self.custom_collector( GaugeHistogramMetricFamily('gh', 'help', buckets=[('1.0', 4), ('+Inf', (5))], gsum_value=7)) self.assertEqual(b"""# HELP gh help @@ -188,7 +318,7 @@ def test_gaugehistogram(self): # EOF """, generate_latest(self.registry)) - def test_gaugehistogram_negative_buckets(self): + def test_gaugehistogram_negative_buckets(self) -> None: self.custom_collector( GaugeHistogramMetricFamily('gh', 'help', buckets=[('-1.0', 4), ('+Inf', (5))], gsum_value=-7)) self.assertEqual(b"""# HELP gh help @@ -200,7 +330,7 @@ def test_gaugehistogram_negative_buckets(self): # EOF """, generate_latest(self.registry)) - def test_info(self): + def test_info(self) -> None: i = Info('ii', 'A info', ['a', 'b'], registry=self.registry) i.labels('c', 'd').info({'foo': 'bar'}) self.assertEqual(b"""# HELP ii A info @@ -209,7 +339,7 @@ def test_info(self): # EOF """, generate_latest(self.registry)) - def test_enum(self): + def test_enum(self) -> None: i = Enum('ee', 'An enum', ['a', 'b'], registry=self.registry, states=['foo', 'bar']) i.labels('c', 'd').state('bar') self.assertEqual(b"""# HELP ee An enum @@ -219,7 +349,7 @@ def test_enum(self): # EOF """, generate_latest(self.registry)) - def test_unicode(self): + def test_unicode(self) -> None: c = Counter('cc', '\u4500', ['l'], registry=self.registry) c.labels('\u4500').inc() self.assertEqual(b"""# HELP cc \xe4\x94\x80 @@ -229,7 +359,7 @@ def test_unicode(self): # EOF """, generate_latest(self.registry)) - def test_escaping(self): + def test_escaping(self) -> None: c = Counter('cc', 'A\ncount\\er\"', ['a'], registry=self.registry) c.labels('\\x\n"').inc(1) self.assertEqual(b"""# HELP cc A\\ncount\\\\er\\" @@ -239,7 +369,7 @@ def test_escaping(self): # EOF """, generate_latest(self.registry)) - def test_nonnumber(self): + def test_nonnumber(self) -> None: class MyNumber: def __repr__(self): return "MyNumber(123)" @@ -257,7 +387,7 @@ def collect(self): self.assertEqual(b'# HELP nonnumber Non number\n# TYPE nonnumber unknown\nnonnumber 123.0\n# EOF\n', generate_latest(self.registry)) - def test_timestamp(self): + def test_timestamp(self) -> None: class MyCollector: def collect(self): metric = Metric("ts", "help", 'unknown')