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()))