def test_counter_exemplars_empty_brackets(self): families = text_string_to_metric_families("""# TYPE a counter # HELP a help a_total{} 0 123 # {a="b"} 0.5 # EOF """) cfm = CounterMetricFamily("a", "help") cfm.add_sample("a_total", {}, 0.0, Timestamp(123, 0), Exemplar({"a": "b"}, 0.5)) self.assertEqual([cfm], list(families))
def test_uint64_counter(self): families = text_string_to_metric_families("""# TYPE a counter # HELP a help a_total 9223372036854775808 # EOF """) self.assertEqual( [CounterMetricFamily("a", "help", value=9223372036854775808)], list(families))
def test_simple_summary(self): families = text_string_to_metric_families("""# TYPE a summary # HELP a help a_count 1 a_sum 2 # EOF """) summary = SummaryMetricFamily("a", "help", count_value=1, sum_value=2) self.assertEqual([summary], list(families))
def test_labels_with_curly_braces(self): families = text_string_to_metric_families("""# TYPE a counter # HELP a help a_total{foo="bar",bar="b{a}z"} 1 # EOF """) metric_family = CounterMetricFamily("a", "help", labels=["foo", "bar"]) metric_family.add_metric(["bar", "b{a}z"], 1) self.assertEqual([metric_family], list(families))
def test_simple_gaugehistogram(self): families = text_string_to_metric_families("""# TYPE a gaugehistogram # HELP a help a_bucket{le="1"} 0 a_bucket{le="+Inf"} 3 a_gcount 3 a_gsum 2 # EOF """) self.assertEqual([GaugeHistogramMetricFamily("a", "help", gsum_value=2, buckets=[("1", 0.0), ("+Inf", 3.0)])], list(families))
def test_negative_bucket_histogram(self): families = text_string_to_metric_families("""# TYPE a histogram # HELP a help a_bucket{le="-1.0"} 0 a_bucket{le="1.0"} 1 a_bucket{le="+Inf"} 3 # EOF """) self.assertEqual([HistogramMetricFamily("a", "help", buckets=[("-1.0", 0.0), ("1.0", 1.0), ("+Inf", 3.0)])], list(families))
def test_unit_gauge(self): families = text_string_to_metric_families("""# TYPE a_seconds gauge # UNIT a_seconds seconds # HELP a_seconds help a_seconds 1 # EOF """) self.assertEqual( [GaugeMetricFamily("a_seconds", "help", value=1, unit='seconds')], list(families))
def test_labels_and_infinite(self): families = text_string_to_metric_families("""# TYPE a gauge # HELP a help a{foo="bar"} +Inf a{foo="baz"} -Inf # EOF """) metric_family = GaugeMetricFamily("a", "help", labels=["foo"]) metric_family.add_metric(["bar"], float('inf')) metric_family.add_metric(["baz"], float('-inf')) self.assertEqual([metric_family], list(families))
def test_escaping(self): families = text_string_to_metric_families("""# TYPE a counter # HELP a he\\n\\\\l\\tp a_total{foo="b\\"a\\nr"} 1 a_total{foo="b\\\\a\\z"} 2 # EOF """) metric_family = CounterMetricFamily("a", "he\n\\l\\tp", labels=["foo"]) metric_family.add_metric(["b\"a\nr"], 1) metric_family.add_metric(["b\\a\\z"], 2) self.assertEqual([metric_family], list(families))
def test_empty_label(self): families = text_string_to_metric_families("""# TYPE a counter # HELP a help a_total{foo="bar"} 1 a_total{foo=""} 2 # EOF """) metric_family = CounterMetricFamily("a", "help", labels=["foo"]) metric_family.add_metric(["bar"], 1) metric_family.add_metric([""], 2) self.assertEqual([metric_family], list(families))
def test_hash_in_label_value(self): families = text_string_to_metric_families("""# TYPE a counter # HELP a help a_total{foo="foo # bar"} 1 a_total{foo="} foo # bar # "} 1 # EOF """) a = CounterMetricFamily("a", "help", labels=["foo"]) a.add_metric(["foo # bar"], 1) a.add_metric(["} foo # bar # "], 1) self.assertEqual([a], list(families))
def test_simple_histogram_float_values(self): families = text_string_to_metric_families("""# TYPE a histogram # HELP a help a_bucket{le="1.0"} 0.0 a_bucket{le="+Inf"} 3.0 a_count 3.0 a_sum 2.0 # EOF """) self.assertEqual([HistogramMetricFamily("a", "help", sum_value=2, buckets=[("1.0", 0.0), ("+Inf", 3.0)])], list(families))
def test_info_timestamps(self): families = text_string_to_metric_families("""# TYPE a info # HELP a help a_info{a="1",foo="bar"} 1 1 a_info{a="2",foo="bar"} 1 0 # EOF """) imf = InfoMetricFamily("a", "help") imf.add_sample("a_info", {"a": "1", "foo": "bar"}, 1, Timestamp(1, 0)) imf.add_sample("a_info", {"a": "2", "foo": "bar"}, 1, Timestamp(0, 0)) self.assertEqual([imf], list(families))
def test_simple_stateset(self): families = text_string_to_metric_families("""# TYPE a stateset # HELP a help a{a="bar"} 0 a{a="foo"} 1.0 # EOF """) self.assertEqual( [StateSetMetricFamily("a", "help", { 'foo': True, 'bar': False })], list(families))
def test_negative_bucket_gaugehistogram(self): families = text_string_to_metric_families("""# TYPE a gaugehistogram # HELP a help a_bucket{le="-1.0"} 1 a_bucket{le="1.0"} 2 a_bucket{le="+Inf"} 3 a_gcount 3 a_gsum -5 # EOF """) self.assertEqual([GaugeHistogramMetricFamily("a", "help", gsum_value=-5, buckets=[("-1.0", 1.0), ("1.0", 2.0), ("+Inf", 3.0)])], list(families))
def test_histogram_noncanonical(self): families = text_string_to_metric_families("""# TYPE a histogram # HELP a help a_bucket{le="0.00000000001"} 0 a_bucket{le="1.1e-4"} 0 a_bucket{le="1.1e-3"} 0 a_bucket{le="100000000000.0"} 0 a_bucket{le="+Inf"} 3 a_count 3 a_sum 2 # EOF """) list(families)
def test_gaugehistogram_exemplars(self): families = text_string_to_metric_families("""# TYPE a gaugehistogram # HELP a help a_bucket{le="1.0"} 0 123 # {a="b"} 0.5 a_bucket{le="2.0"} 2 123 # {a="c"} 0.5 a_bucket{le="+Inf"} 3 123 # {a="d"} 4 123 # EOF """) hfm = GaugeHistogramMetricFamily("a", "help") hfm.add_sample("a_bucket", {"le": "1.0"}, 0.0, Timestamp(123, 0), Exemplar({"a": "b"}, 0.5)) hfm.add_sample("a_bucket", {"le": "2.0"}, 2.0, Timestamp(123, 0), Exemplar({"a": "c"}, 0.5)), hfm.add_sample("a_bucket", {"le": "+Inf"}, 3.0, Timestamp(123, 0), Exemplar({"a": "d"}, 4, Timestamp(123, 0))) self.assertEqual([hfm], list(families))
def test_histogram_exemplars(self): families = text_string_to_metric_families("""# TYPE a histogram # HELP a help a_bucket{le="1"} 0 # {a="b"} 0.5 a_bucket{le="2"} 2 # {a="c"} 0.5 a_bucket{le="+Inf"} 3 # {a="1234567890123456789012345678901234567890123456789012345678"} 4 123 # EOF """) hfm = HistogramMetricFamily("a", "help") hfm.add_sample("a_bucket", {"le": "1"}, 0.0, None, Exemplar({"a": "b"}, 0.5)) hfm.add_sample("a_bucket", {"le": "2"}, 2.0, None, Exemplar({"a": "c"}, 0.5)), hfm.add_sample("a_bucket", {"le": "+Inf"}, 3.0, None, Exemplar({"a": "1234567890123456789012345678901234567890123456789012345678"}, 4, Timestamp(123, 0))) self.assertEqual([hfm], list(families))
def test_exemplars_with_hash_in_label_values(self): families = text_string_to_metric_families("""# TYPE a histogram # HELP a help a_bucket{le="1.0",foo="bar # "} 0 # {a="b",foo="bar # bar"} 0.5 a_bucket{le="2.0",foo="bar # "} 2 # {a="c",foo="bar # bar"} 0.5 a_bucket{le="+Inf",foo="bar # "} 3 # {a="d",foo="bar # bar"} 4 # EOF """) hfm = HistogramMetricFamily("a", "help") hfm.add_sample("a_bucket", {"le": "1.0", "foo": "bar # "}, 0.0, None, Exemplar({"a": "b", "foo": "bar # bar"}, 0.5)) hfm.add_sample("a_bucket", {"le": "2.0", "foo": "bar # "}, 2.0, None, Exemplar({"a": "c", "foo": "bar # bar"}, 0.5)) hfm.add_sample("a_bucket", {"le": "+Inf", "foo": "bar # "}, 3.0, None, Exemplar({"a": "d", "foo": "bar # bar"}, 4)) self.assertEqual([hfm], list(families))
def test_label_escaping(self): for escaped_val, unescaped_val in [('foo', 'foo'), ('\\foo', '\\foo'), ('\\\\foo', '\\foo'), ('foo\\\\', 'foo\\'), ('\\\\', '\\'), ('\\n', '\n'), ('\\\\n', '\\n'), ('\\\\\\n', '\\\n'), ('\\"', '"'), ('\\\\\\"', '\\"')]: families = list(text_string_to_metric_families("""# TYPE a counter # HELP a help a_total{foo="%s",bar="baz"} 1 # EOF """ % escaped_val)) metric_family = CounterMetricFamily( "a", "help", labels=["foo", "bar"]) metric_family.add_metric([unescaped_val, "baz"], 1) self.assertEqual([metric_family], list(families))
def test_summary_quantiles(self): families = text_string_to_metric_families("""# TYPE a summary # HELP a help a_count 1 a_sum 2 a{quantile="0.5"} 0.7 # EOF """) # The Python client doesn't support quantiles, but we # still need to be able to parse them. metric_family = SummaryMetricFamily("a", "help", count_value=1, sum_value=2) metric_family.add_sample("a", {"quantile": "0.5"}, 0.7) self.assertEqual([metric_family], list(families))
def test_roundtrip(self): text = """# HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 0.013300656000000001 go_gc_duration_seconds{quantile="0.25"} 0.013638736 go_gc_duration_seconds{quantile="0.5"} 0.013759906 go_gc_duration_seconds{quantile="0.75"} 0.013962066 go_gc_duration_seconds{quantile="1"} 0.021383540000000003 go_gc_duration_seconds_sum 56.12904785 go_gc_duration_seconds_count 7476.0 # HELP go_goroutines Number of goroutines that currently exist. # TYPE go_goroutines gauge go_goroutines 166.0 # HELP prometheus_local_storage_indexing_batch_duration_milliseconds Quantiles for batch indexing duration in milliseconds. # TYPE prometheus_local_storage_indexing_batch_duration_milliseconds summary prometheus_local_storage_indexing_batch_duration_milliseconds{quantile="0.5"} NaN prometheus_local_storage_indexing_batch_duration_milliseconds{quantile="0.9"} NaN prometheus_local_storage_indexing_batch_duration_milliseconds{quantile="0.99"} NaN prometheus_local_storage_indexing_batch_duration_milliseconds_sum 871.5665949999999 prometheus_local_storage_indexing_batch_duration_milliseconds_count 229.0 # HELP process_cpu_seconds Total user and system CPU time spent in seconds. # TYPE process_cpu_seconds counter process_cpu_seconds_total 29323.4 # HELP process_virtual_memory_bytes Virtual memory size in bytes. # TYPE process_virtual_memory_bytes gauge process_virtual_memory_bytes 2478268416.0 # HELP prometheus_build_info A metric with a constant '1' value labeled by version, revision, and branch from which Prometheus was built. # TYPE prometheus_build_info gauge prometheus_build_info{branch="HEAD",revision="ef176e5",version="0.16.0rc1"} 1.0 # HELP prometheus_local_storage_chunk_ops The total number of chunk operations by their type. # TYPE prometheus_local_storage_chunk_ops counter prometheus_local_storage_chunk_ops_total{type="clone"} 28.0 prometheus_local_storage_chunk_ops_total{type="create"} 997844.0 prometheus_local_storage_chunk_ops_total{type="drop"} 1345758.0 prometheus_local_storage_chunk_ops_total{type="load"} 1641.0 prometheus_local_storage_chunk_ops_total{type="persist"} 981408.0 prometheus_local_storage_chunk_ops_total{type="pin"} 32662.0 prometheus_local_storage_chunk_ops_total{type="transcode"} 980180.0 prometheus_local_storage_chunk_ops_total{type="unpin"} 32662.0 # EOF """ families = list(text_string_to_metric_families(text)) class TextCollector(object): def collect(self): return families registry = CollectorRegistry() registry.register(TextCollector()) self.assertEqual(text.encode('utf-8'), generate_latest(registry))
def test_untyped(self): # https://github.com/prometheus/client_python/issues/79 families = text_string_to_metric_families("""# HELP redis_connected_clients Redis connected clients # TYPE redis_connected_clients unknown redis_connected_clients{instance="rough-snowflake-web",port="6380"} 10.0 redis_connected_clients{instance="rough-snowflake-web",port="6381"} 12.0 # EOF """) m = Metric("redis_connected_clients", "Redis connected clients", "untyped") m.samples = [ Sample("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6380"}, 10), Sample("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6381"}, 12), ] self.assertEqual([m], list(families))
def test_duplicate_timestamps(self): families = text_string_to_metric_families("""# TYPE a gauge # HELP a help a{a="1",foo="bar"} 1 0.0000000000 a{a="1",foo="bar"} 2 0.0000000001 a{a="1",foo="bar"} 3 0.0000000010 a{a="2",foo="bar"} 4 0.0000000000 a{a="2",foo="bar"} 5 0.0000000001 # EOF """) imf = GaugeMetricFamily("a", "help") imf.add_sample("a", {"a": "1", "foo": "bar"}, 1, Timestamp(0, 0)) imf.add_sample("a", {"a": "1", "foo": "bar"}, 3, Timestamp(0, 1)) imf.add_sample("a", {"a": "2", "foo": "bar"}, 4, Timestamp(0, 0)) self.assertEqual([imf], list(families))
def parses_as_openmetrics(): """ Create a matcher that matches a ``str`` string that can be parsed as an OpenMetrics response and includes a certain well-known value expected by the tests. :return: A matcher. """ # The parser throws if it does not like its input. # Wrapped in a list() to drain the generator. return AfterPreprocessing( lambda body: list(parser.text_string_to_metric_families(body)), AfterPreprocessing( lambda families: families[-1].name, Equals("tahoe_stats_storage_server_total_bucket_count"), ), )
def test_timestamps(self): families = text_string_to_metric_families("""# TYPE a counter # HELP a help a_total{foo="1"} 1 000 a_total{foo="2"} 1 0.0 a_total{foo="3"} 1 1.1 a_total{foo="4"} 1 12345678901234567890.1234567890 a_total{foo="5"} 1 1.5e3 # TYPE b counter # HELP b help b_total 2 1234567890 # EOF """) a = CounterMetricFamily("a", "help", labels=["foo"]) a.add_metric(["1"], 1, timestamp=Timestamp(0, 0)) a.add_metric(["2"], 1, timestamp=Timestamp(0, 0)) a.add_metric(["3"], 1, timestamp=Timestamp(1, 100000000)) a.add_metric(["4"], 1, timestamp=Timestamp(12345678901234567890, 123456789)) a.add_metric(["5"], 1, timestamp=1500.0) b = CounterMetricFamily("b", "help") b.add_metric([], 2, timestamp=Timestamp(1234567890, 0)) self.assertEqual([a, b], list(families))
def test_fallback_to_state_machine_label_parsing(self): from unittest.mock import patch from prometheus_client.openmetrics.parser import _parse_sample parse_sample_function = "prometheus_client.openmetrics.parser._parse_sample" parse_labels_function = "prometheus_client.openmetrics.parser._parse_labels" parse_remaining_function = "prometheus_client.openmetrics.parser._parse_remaining_text" state_machine_function = "prometheus_client.openmetrics.parser._parse_labels_with_state_machine" parse_sample_return_value = Sample("a_total", {"foo": "foo # bar"}, 1) with patch(parse_sample_function, return_value=parse_sample_return_value) as mock: families = text_string_to_metric_families("""# TYPE a counter # HELP a help a_total{foo="foo # bar"} 1 # EOF """) a = CounterMetricFamily("a", "help", labels=["foo"]) a.add_metric(["foo # bar"], 1) self.assertEqual([a], list(families)) mock.assert_called_once_with('a_total{foo="foo # bar"} 1') # First fallback case state_machine_return_values = [{ "foo": "foo # bar" }, len('foo="foo # bar"}')] parse_remaining_values = [1, None, None] with patch(parse_labels_function) as mock1: with patch(state_machine_function, return_value=state_machine_return_values) as mock2: with patch(parse_remaining_function, return_value=parse_remaining_values) as mock3: sample = _parse_sample('a_total{foo="foo # bar"} 1') s = Sample("a_total", {"foo": "foo # bar"}, 1) self.assertEqual(s, sample) mock1.assert_not_called() mock2.assert_called_once_with('foo="foo # bar"} 1') mock3.assert_called_once_with('1') # Second fallback case state_machine_return_values = [{"le": "1.0"}, len('le="1.0"}')] parse_remaining_values = [ 0.0, Timestamp(123, 0), Exemplar({"a": "b"}, 0.5) ] with patch(parse_labels_function) as mock1: with patch(state_machine_function, return_value=state_machine_return_values) as mock2: with patch(parse_remaining_function, return_value=parse_remaining_values) as mock3: sample = _parse_sample( 'a_bucket{le="1.0"} 0 123 # {a="b"} 0.5') s = Sample("a_bucket", {"le": "1.0"}, 0.0, Timestamp(123, 0), Exemplar({"a": "b"}, 0.5)) self.assertEqual(s, sample) mock1.assert_not_called() mock2.assert_called_once_with( 'le="1.0"} 0 123 # {a="b"} 0.5') mock3.assert_called_once_with('0 123 # {a="b"} 0.5') # No need to fallback case parse_labels_return_values = {"foo": "foo#bar"} parse_remaining_values = [1, None, None] with patch(parse_labels_function, return_value=parse_labels_return_values) as mock1: with patch(state_machine_function) as mock2: with patch(parse_remaining_function, return_value=parse_remaining_values) as mock3: sample = _parse_sample('a_total{foo="foo#bar"} 1') s = Sample("a_total", {"foo": "foo#bar"}, 1) self.assertEqual(s, sample) mock1.assert_called_once_with('foo="foo#bar"') mock2.assert_not_called() mock3.assert_called_once_with('1')
def test_no_newline_after_eof(self): families = text_string_to_metric_families("""# TYPE a gauge # HELP a help a 1 # EOF""") self.assertEqual([GaugeMetricFamily("a", "help", value=1)], list(families))
def test_nan(self): families = text_string_to_metric_families("""a NaN # EOF """) self.assertTrue(math.isnan(list(families)[0].samples[0][2]))
import sys from prometheus_client.openmetrics.parser import text_string_to_metric_families input = sys.stdin.read() families = text_string_to_metric_families(input) print(list(families))