diff --git a/nucypher/utilities/json_metrics_export.py b/nucypher/utilities/json_metrics_export.py deleted file mode 100644 index 8e7b657b9..000000000 --- a/nucypher/utilities/json_metrics_export.py +++ /dev/null @@ -1,75 +0,0 @@ -from __future__ import unicode_literals -from prometheus_client.utils import floatToGoString -from twisted.web.resource import Resource -from prometheus_client.registry import REGISTRY -import json -from prometheus_client.core import Timestamp - - -class MetricsEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, Timestamp): - return obj.__float__() - return json.JSONEncoder.default(self, obj) - - -class JSONMetricsResource(Resource): - """ - Twisted ``Resource`` that serves metrics in JSON. - """ - isLeaf = True - - def __init__(self, registry=REGISTRY): - self.registry = registry - - def render_GET(self, request): - request.setHeader(b'Content-Type', "text/json") - return self.generate_latest_json() - - @staticmethod - def get_exemplar(sample, metric): - if not sample.exemplar: - return {} - elif metric.type not in ('histogram', 'gaugehistogram') \ - or not sample.name.endswith('_bucket'): - raise ValueError( - "Metric {} has exemplars, but is not a " - "histogram bucket".format(metric.name) - ) - return { - "labels": sample.exemplar.labels, - "value": floatToGoString(sample.exemplar.value), - "timestamp": sample.exemplar.timestamp - } - - def get_sample(self, sample, metric): - return { - "sample_name": sample.name, - "labels": sample.labels, - "value": floatToGoString(sample.value), - "timestamp": sample.timestamp, - "exemplar": self.get_exemplar(sample, metric) - } - - def get_metric(self, metric): - return { - "samples": [self.get_sample(sample, metric) for sample in metric.samples], - "help": metric.documentation, - "type": metric.type - } - - def generate_latest_json(self): - """ - Returns the metrics from the registry - in latest JSON format as a string. - """ - output = {} - for metric in self.registry.collect(): - try: - output[metric.name] = self.get_metric(metric) - except Exception as exception: - exception.args = (exception.args or ('',)) + (metric,) - raise - - json_dump = json.dumps(output, cls=MetricsEncoder).encode('utf-8') - return json_dump diff --git a/nucypher/utilities/prometheus.py b/nucypher/utilities/prometheus.py index a54ca4151..6b1a1554c 100644 --- a/nucypher/utilities/prometheus.py +++ b/nucypher/utilities/prometheus.py @@ -19,12 +19,19 @@ try: from prometheus_client import Gauge, Enum, Counter, Info, Histogram, Summary except ImportError: raise ImportError('prometheus_client is not installed - Install it and try again.') + +import json +from typing import List, Union, Tuple + +from prometheus_client.core import Timestamp +from prometheus_client.registry import REGISTRY +from prometheus_client.utils import floatToGoString from twisted.internet import reactor, task +from twisted.web.resource import Resource import nucypher -from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent, WorkLockAgent, PolicyManagerAgent from nucypher.blockchain.eth.actors import NucypherTokenActor -from typing import List, Union, Tuple +from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent, WorkLockAgent, PolicyManagerAgent from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory ContractAgents = Union[StakingEscrowAgent, WorkLockAgent, PolicyManagerAgent] @@ -37,6 +44,75 @@ class PrometheusMetricsConfig: self.listen_address = listen_address +class MetricsEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, Timestamp): + return obj.__float__() + return json.JSONEncoder.default(self, obj) + + +class JSONMetricsResource(Resource): + """ + Twisted ``Resource`` that serves metrics in JSON. + """ + isLeaf = True + + def __init__(self, registry=REGISTRY): + self.registry = registry + + def render_GET(self, request): + request.setHeader(b'Content-Type', "text/json") + return self.generate_latest_json() + + @staticmethod + def get_exemplar(sample, metric): + if not sample.exemplar: + return {} + elif metric.type not in ('histogram', 'gaugehistogram') \ + or not sample.name.endswith('_bucket'): + raise ValueError( + "Metric {} has exemplars, but is not a " + "histogram bucket".format(metric.name) + ) + return { + "labels": sample.exemplar.labels, + "value": floatToGoString(sample.exemplar.value), + "timestamp": sample.exemplar.timestamp + } + + def get_sample(self, sample, metric): + return { + "sample_name": sample.name, + "labels": sample.labels, + "value": floatToGoString(sample.value), + "timestamp": sample.timestamp, + "exemplar": self.get_exemplar(sample, metric) + } + + def get_metric(self, metric): + return { + "samples": [self.get_sample(sample, metric) for sample in metric.samples], + "help": metric.documentation, + "type": metric.type + } + + def generate_latest_json(self): + """ + Returns the metrics from the registry + in latest JSON format as a string. + """ + output = {} + for metric in self.registry.collect(): + try: + output[metric.name] = self.get_metric(metric) + except Exception as exception: + exception.args = (exception.args or ('',)) + (metric,) + raise + + json_dump = json.dumps(output, cls=MetricsEncoder).encode('utf-8') + return json_dump + + class BaseEventMetricsCollector: def __init__(self, staker_address: str, worker_address: str, contract_agent: ContractAgents, event_name: str, @@ -292,7 +368,6 @@ def initialize_prometheus_exporter(ursula, prometheus_config: PrometheusMetricsC from prometheus_client.twisted import MetricsResource from twisted.web.resource import Resource from twisted.web.server import Site - from .json_metrics_export import JSONMetricsResource metrics_prefix = prometheus_config.metrics_prefix diff --git a/tests/prometheus/test_json_exporter.py b/tests/prometheus/test_json_exporter.py deleted file mode 100644 index ced8fdabb..000000000 --- a/tests/prometheus/test_json_exporter.py +++ /dev/null @@ -1,204 +0,0 @@ -""" - This file is part of nucypher. - - nucypher is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - nucypher is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with nucypher. If not, see . -""" - -from __future__ import unicode_literals - -import json -import sys -import time -import unittest - -from prometheus_client import ( - CollectorRegistry, Counter, Enum, - Gauge, Histogram, Info, Metric, Summary, -) -from prometheus_client.core import GaugeHistogramMetricFamily, Timestamp - -from nucypher.utilities.json_metrics_export import JSONMetricsResource - - -class TestGenerateJSON(unittest.TestCase): - def setUp(self): - self.registry = CollectorRegistry() - - self.json_exporter = JSONMetricsResource(self.registry) - - # Mock time so _created values are fixed. - self.old_time = time.time - time.time = lambda: 123.456 - - def tearDown(self): - time.time = self.old_time - - def custom_collector(self, metric_family): - class CustomCollector(object): - def collect(self): - return [metric_family] - - self.registry.register(CustomCollector()) - - def test_counter(self): - c = Counter('cc', 'A counter', registry=self.registry) - c.inc() - self.assertEqual(json.loads("""{"cc": {"samples": [{"sample_name": "cc_total", "labels": {}, "value": "1.0", - "timestamp": null, "exemplar": {}}, {"sample_name": "cc_created", "labels": {}, "value": "123.456", - "timestamp": null, "exemplar": {}}], "help": "A counter", "type": "counter"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_counter_name_unit_append(self): - c = Counter('requests', 'Request counter', unit="total", registry=self.registry) - c.inc() - self.assertEqual(json.loads("""{"requests_total": {"samples": [{"sample_name": "requests_total", "labels": { - }, "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "requests_created", "labels": {}, - "value": "123.456", "timestamp": null, "exemplar": {}}], "help": "Request counter", "type": "counter"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_counter_total(self): - c = Counter('cc_total', 'A counter', registry=self.registry) - c.inc() - self.assertEqual(json.loads("""{"cc": {"samples": [{"sample_name": "cc_total", "labels": {}, "value": "1.0", - "timestamp": null, "exemplar": {}}, {"sample_name": "cc_created", "labels": {}, "value": "123.456", - "timestamp": null, "exemplar": {}}], "help": "A counter", "type": "counter"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_gauge(self): - g = Gauge('gg', 'A gauge', registry=self.registry) - g.set(17) - self.assertEqual(json.loads("""{"gg": {"samples": [{"sample_name": "gg", "labels": {}, "value": "17.0", - "timestamp": null, "exemplar": {}}], "help": "A gauge", "type": "gauge"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_summary(self): - s = Summary('ss', 'A summary', ['a', 'b'], registry=self.registry) - s.labels('c', 'd').observe(17) - self.assertEqual(json.loads("""{"ss": {"samples": [{"sample_name": "ss_count", "labels": {"a": "c", - "b": "d"}, "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "ss_sum", "labels": {"a": "c", - "b": "d"}, "value": "17.0", "timestamp": null, "exemplar": {}}, {"sample_name": "ss_created", "labels": {"a": - "c", "b": "d"}, "value": "123.456", "timestamp": null, "exemplar": {}}], "help": "A summary", - "type": "summary"}}"""), json.loads(self.json_exporter.generate_latest_json())) - - @unittest.skipIf(sys.version_info < (2, 7), "Test requires Python 2.7+.") - def test_histogram(self): - s = Histogram('hh', 'A histogram', registry=self.registry) - s.observe(0.05) - self.assertEqual(json.loads("""{"hh": {"samples": [{"sample_name": "hh_bucket", "labels": {"le": "0.005"}, - "value": "0.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.01"}, - "value": "0.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.025"}, - "value": "0.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.05"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.075"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.1"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.25"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.5"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.75"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "1.0"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "2.5"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "5.0"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "7.5"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "10.0"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "+Inf"}, - "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_count", "labels": {}, "value": "1.0", - "timestamp": null, "exemplar": {}}, {"sample_name": "hh_sum", "labels": {}, "value": "0.05", "timestamp": - null, "exemplar": {}}, {"sample_name": "hh_created", "labels": {}, "value": "123.456", "timestamp": null, - "exemplar": {}}], "help": "A histogram", "type": "histogram"}}"""), json.loads( - self.json_exporter.generate_latest_json())) - - def test_gaugehistogram(self): - self.custom_collector(GaugeHistogramMetricFamily('gh', 'help', buckets=[('1.0', 4), ('+Inf', 5)], gsum_value=7)) - self.assertEqual(json.loads("""{"gh": {"samples": [{"sample_name": "gh_bucket", "labels": {"le": "1.0"}, - "value": "4.0", "timestamp": null, "exemplar": {}}, {"sample_name": "gh_bucket", "labels": {"le": "+Inf"}, - "value": "5.0", "timestamp": null, "exemplar": {}}, {"sample_name": "gh_gcount", "labels": {}, - "value": "5.0", "timestamp": null, "exemplar": {}}, {"sample_name": "gh_gsum", "labels": {}, "value": "7.0", - "timestamp": null, "exemplar": {}}], "help": "help", "type": "gaugehistogram"}}"""), json.loads( - self.json_exporter.generate_latest_json())) - - def test_info(self): - i = Info('ii', 'A info', ['a', 'b'], registry=self.registry) - i.labels('c', 'd').info({'foo': 'bar'}) - self.assertEqual(json.loads("""{"ii": {"samples": [{"sample_name": "ii_info", "labels": {"a": "c", "b": "d", - "foo": "bar"}, "value": "1.0", "timestamp": null, "exemplar": {}}], "help": "A info", "type": "info"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_enum(self): - i = Enum('ee', 'An enum', ['a', 'b'], registry=self.registry, states=['foo', 'bar']) - i.labels('c', 'd').state('bar') - self.assertEqual( - json.loads("""{"ee": {"samples": [{"sample_name": "ee", "labels": {"a": "c", "b": "d", "ee": "foo"}, - "value": "0.0", "timestamp": null, "exemplar": {}}, {"sample_name": "ee", "labels": {"a": - "c", "b": "d", "ee": "bar"}, "value": "1.0", "timestamp": null, "exemplar": {}}], - "help": "An enum","type": "stateset"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_unicode(self): - c = Gauge('cc', '\u4500', ['l'], registry=self.registry) - c.labels('\u4500').inc() - self.assertEqual(json.loads("""{"cc": {"samples": [{"sample_name": "cc", "labels": {"l": "\\u4500"}, "value": - "1.0", "timestamp": null, "exemplar": {}}], "help": "\\u4500", - "type": "gauge"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_escaping(self): - g = Gauge('cc', 'A\ngaug\\e', ['a'], registry=self.registry) - g.labels('\\x\n"').inc(1) - self.assertEqual(json.loads("""{"cc": {"samples": [{"sample_name": "cc", "labels": {"a": "\\\\x\\n\\""}, - "value": "1.0", "timestamp": null, "exemplar": {}}], "help": "A\\ngaug\\\\e", - "type": "gauge"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_nonnumber(self): - class MyNumber(object): - def __repr__(self): - return "MyNumber(123)" - - def __float__(self): - return 123.0 - - class MyCollector(object): - def collect(self): - metric = Metric("nonnumber", "Non number", 'untyped') - metric.add_sample("nonnumber", {}, MyNumber()) - yield metric - - self.registry.register(MyCollector()) - self.assertEqual(json.loads("""{"nonnumber": {"samples": [{"sample_name": "nonnumber", "labels": {}, "value": - "123.0", "timestamp": null, "exemplar": {}}], "help": "Non number", "type": "unknown"}}"""), - json.loads(self.json_exporter.generate_latest_json())) - - def test_timestamp(self): - class MyCollector(object): - def collect(self): - metric = Metric("ts", "help", 'untyped') - metric.add_sample("ts", {"foo": "a"}, 0, 123.456) - metric.add_sample("ts", {"foo": "b"}, 0, -123.456) - metric.add_sample("ts", {"foo": "c"}, 0, 123) - metric.add_sample("ts", {"foo": "d"}, 0, Timestamp(123, 456000000)) - metric.add_sample("ts", {"foo": "e"}, 0, Timestamp(123, 456000)) - metric.add_sample("ts", {"foo": "f"}, 0, Timestamp(123, 456)) - yield metric - - self.registry.register(MyCollector()) - self.assertEqual(json.loads("""{"ts": {"samples": [{"sample_name": "ts", "labels": {"foo": "a"}, "value": - "0.0", "timestamp": 123.456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "b"}, "value": "0.0", - "timestamp": -123.456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "c"}, "value": "0.0", - "timestamp": 123, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "d"}, "value": "0.0", "timestamp": - 123.456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "e"}, "value": "0.0", "timestamp": - 123.000456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "f"}, "value": "0.0", "timestamp": - 123.000000456, "exemplar": {}}], "help": "help", "type": "unknown"}}"""), json.loads( - self.json_exporter.generate_latest_json())) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/test_prometheus.py b/tests/unit/test_prometheus.py index b2a7bbaca..2e04b4ec2 100644 --- a/tests/unit/test_prometheus.py +++ b/tests/unit/test_prometheus.py @@ -15,6 +15,20 @@ along with nucypher. If not, see . """ +from __future__ import unicode_literals + +import json +import sys +import time +import unittest + +from prometheus_client import ( + CollectorRegistry, Counter, Enum, + Gauge, Histogram, Info, Metric, Summary, +) +from prometheus_client.core import GaugeHistogramMetricFamily, Timestamp + +from nucypher.utilities.prometheus import JSONMetricsResource from nucypher.utilities.prometheus import PrometheusMetricsConfig TEST_PREFIX = 'test_prefix' @@ -29,3 +43,172 @@ def test_prometheus_metrics_config(): assert prometheus_config.port == 2020 assert prometheus_config.metrics_prefix == TEST_PREFIX assert listen_address == listen_address + + +class TestGenerateJSON(unittest.TestCase): + def setUp(self): + self.registry = CollectorRegistry() + + self.json_exporter = JSONMetricsResource(self.registry) + + # Mock time so _created values are fixed. + self.old_time = time.time + time.time = lambda: 123.456 + + def tearDown(self): + time.time = self.old_time + + def custom_collector(self, metric_family): + class CustomCollector(object): + def collect(self): + return [metric_family] + + self.registry.register(CustomCollector()) + + def test_counter(self): + c = Counter('cc', 'A counter', registry=self.registry) + c.inc() + self.assertEqual(json.loads("""{"cc": {"samples": [{"sample_name": "cc_total", "labels": {}, "value": "1.0", + "timestamp": null, "exemplar": {}}, {"sample_name": "cc_created", "labels": {}, "value": "123.456", + "timestamp": null, "exemplar": {}}], "help": "A counter", "type": "counter"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_counter_name_unit_append(self): + c = Counter('requests', 'Request counter', unit="total", registry=self.registry) + c.inc() + self.assertEqual(json.loads("""{"requests_total": {"samples": [{"sample_name": "requests_total", "labels": { + }, "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "requests_created", "labels": {}, + "value": "123.456", "timestamp": null, "exemplar": {}}], "help": "Request counter", "type": "counter"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_counter_total(self): + c = Counter('cc_total', 'A counter', registry=self.registry) + c.inc() + self.assertEqual(json.loads("""{"cc": {"samples": [{"sample_name": "cc_total", "labels": {}, "value": "1.0", + "timestamp": null, "exemplar": {}}, {"sample_name": "cc_created", "labels": {}, "value": "123.456", + "timestamp": null, "exemplar": {}}], "help": "A counter", "type": "counter"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_gauge(self): + g = Gauge('gg', 'A gauge', registry=self.registry) + g.set(17) + self.assertEqual(json.loads("""{"gg": {"samples": [{"sample_name": "gg", "labels": {}, "value": "17.0", + "timestamp": null, "exemplar": {}}], "help": "A gauge", "type": "gauge"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_summary(self): + s = Summary('ss', 'A summary', ['a', 'b'], registry=self.registry) + s.labels('c', 'd').observe(17) + self.assertEqual(json.loads("""{"ss": {"samples": [{"sample_name": "ss_count", "labels": {"a": "c", + "b": "d"}, "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "ss_sum", "labels": {"a": "c", + "b": "d"}, "value": "17.0", "timestamp": null, "exemplar": {}}, {"sample_name": "ss_created", "labels": {"a": + "c", "b": "d"}, "value": "123.456", "timestamp": null, "exemplar": {}}], "help": "A summary", + "type": "summary"}}"""), json.loads(self.json_exporter.generate_latest_json())) + + @unittest.skipIf(sys.version_info < (2, 7), "Test requires Python 2.7+.") + def test_histogram(self): + s = Histogram('hh', 'A histogram', registry=self.registry) + s.observe(0.05) + self.assertEqual(json.loads("""{"hh": {"samples": [{"sample_name": "hh_bucket", "labels": {"le": "0.005"}, + "value": "0.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.01"}, + "value": "0.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.025"}, + "value": "0.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.05"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.075"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.1"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.25"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.5"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "0.75"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "1.0"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "2.5"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "5.0"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "7.5"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "10.0"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_bucket", "labels": {"le": "+Inf"}, + "value": "1.0", "timestamp": null, "exemplar": {}}, {"sample_name": "hh_count", "labels": {}, "value": "1.0", + "timestamp": null, "exemplar": {}}, {"sample_name": "hh_sum", "labels": {}, "value": "0.05", "timestamp": + null, "exemplar": {}}, {"sample_name": "hh_created", "labels": {}, "value": "123.456", "timestamp": null, + "exemplar": {}}], "help": "A histogram", "type": "histogram"}}"""), json.loads( + self.json_exporter.generate_latest_json())) + + def test_gaugehistogram(self): + self.custom_collector(GaugeHistogramMetricFamily('gh', 'help', buckets=[('1.0', 4), ('+Inf', 5)], gsum_value=7)) + self.assertEqual(json.loads("""{"gh": {"samples": [{"sample_name": "gh_bucket", "labels": {"le": "1.0"}, + "value": "4.0", "timestamp": null, "exemplar": {}}, {"sample_name": "gh_bucket", "labels": {"le": "+Inf"}, + "value": "5.0", "timestamp": null, "exemplar": {}}, {"sample_name": "gh_gcount", "labels": {}, + "value": "5.0", "timestamp": null, "exemplar": {}}, {"sample_name": "gh_gsum", "labels": {}, "value": "7.0", + "timestamp": null, "exemplar": {}}], "help": "help", "type": "gaugehistogram"}}"""), json.loads( + self.json_exporter.generate_latest_json())) + + def test_info(self): + i = Info('ii', 'A info', ['a', 'b'], registry=self.registry) + i.labels('c', 'd').info({'foo': 'bar'}) + self.assertEqual(json.loads("""{"ii": {"samples": [{"sample_name": "ii_info", "labels": {"a": "c", "b": "d", + "foo": "bar"}, "value": "1.0", "timestamp": null, "exemplar": {}}], "help": "A info", "type": "info"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_enum(self): + i = Enum('ee', 'An enum', ['a', 'b'], registry=self.registry, states=['foo', 'bar']) + i.labels('c', 'd').state('bar') + self.assertEqual( + json.loads("""{"ee": {"samples": [{"sample_name": "ee", "labels": {"a": "c", "b": "d", "ee": "foo"}, + "value": "0.0", "timestamp": null, "exemplar": {}}, {"sample_name": "ee", "labels": {"a": + "c", "b": "d", "ee": "bar"}, "value": "1.0", "timestamp": null, "exemplar": {}}], + "help": "An enum","type": "stateset"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_unicode(self): + c = Gauge('cc', '\u4500', ['l'], registry=self.registry) + c.labels('\u4500').inc() + self.assertEqual(json.loads("""{"cc": {"samples": [{"sample_name": "cc", "labels": {"l": "\\u4500"}, "value": + "1.0", "timestamp": null, "exemplar": {}}], "help": "\\u4500", + "type": "gauge"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_escaping(self): + g = Gauge('cc', 'A\ngaug\\e', ['a'], registry=self.registry) + g.labels('\\x\n"').inc(1) + self.assertEqual(json.loads("""{"cc": {"samples": [{"sample_name": "cc", "labels": {"a": "\\\\x\\n\\""}, + "value": "1.0", "timestamp": null, "exemplar": {}}], "help": "A\\ngaug\\\\e", + "type": "gauge"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_nonnumber(self): + class MyNumber(object): + def __repr__(self): + return "MyNumber(123)" + + def __float__(self): + return 123.0 + + class MyCollector(object): + def collect(self): + metric = Metric("nonnumber", "Non number", 'untyped') + metric.add_sample("nonnumber", {}, MyNumber()) + yield metric + + self.registry.register(MyCollector()) + self.assertEqual(json.loads("""{"nonnumber": {"samples": [{"sample_name": "nonnumber", "labels": {}, "value": + "123.0", "timestamp": null, "exemplar": {}}], "help": "Non number", "type": "unknown"}}"""), + json.loads(self.json_exporter.generate_latest_json())) + + def test_timestamp(self): + class MyCollector(object): + def collect(self): + metric = Metric("ts", "help", 'untyped') + metric.add_sample("ts", {"foo": "a"}, 0, 123.456) + metric.add_sample("ts", {"foo": "b"}, 0, -123.456) + metric.add_sample("ts", {"foo": "c"}, 0, 123) + metric.add_sample("ts", {"foo": "d"}, 0, Timestamp(123, 456000000)) + metric.add_sample("ts", {"foo": "e"}, 0, Timestamp(123, 456000)) + metric.add_sample("ts", {"foo": "f"}, 0, Timestamp(123, 456)) + yield metric + + self.registry.register(MyCollector()) + self.assertEqual(json.loads("""{"ts": {"samples": [{"sample_name": "ts", "labels": {"foo": "a"}, "value": + "0.0", "timestamp": 123.456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "b"}, "value": "0.0", + "timestamp": -123.456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "c"}, "value": "0.0", + "timestamp": 123, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "d"}, "value": "0.0", "timestamp": + 123.456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "e"}, "value": "0.0", "timestamp": + 123.000456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "f"}, "value": "0.0", "timestamp": + 123.000000456, "exemplar": {}}], "help": "help", "type": "unknown"}}"""), json.loads( + self.json_exporter.generate_latest_json()))