Skip to content

Commit 7e19da9

Browse files
committed
Add OM timestamp support, and unittests
Signed-off-by: Brian Brazil <brian.brazil@robustperception.io>
1 parent 8f5ea53 commit 7e19da9

File tree

2 files changed

+186
-1
lines changed

2 files changed

+186
-1
lines changed

prometheus_client/openmetrics/exposition.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ def generate_latest(registry):
2525
for k, v in sorted(s.labels.items())]))
2626
else:
2727
labelstr = ''
28-
output.append('{0}{1} {2}\n'.format(s.name, labelstr, core._floatToGoString(s.value)))
28+
timestamp = ''
29+
if s.timestamp is not None:
30+
# Convert to milliseconds.
31+
timestamp = ' {0}'.format(s.timestamp)
32+
output.append('{0}{1} {2}{3}\n'.format(s.name, labelstr, core._floatToGoString(s.value), timestamp))
2933
output.append('# EOF\n')
3034
return ''.join(output).encode('utf-8')
3135

tests/openmetrics/test_exposition.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
from __future__ import unicode_literals
2+
3+
import sys
4+
import time
5+
6+
if sys.version_info < (2, 7):
7+
# We need the skip decorators from unittest2 on Python 2.6.
8+
import unittest2 as unittest
9+
else:
10+
import unittest
11+
12+
from prometheus_client import Gauge, Counter, Summary, Histogram, Info, Enum, Metric
13+
from prometheus_client import CollectorRegistry
14+
from prometheus_client.core import GaugeHistogramMetricFamily, Timestamp
15+
from prometheus_client.openmetrics.exposition import (
16+
generate_latest,
17+
)
18+
19+
class TestGenerateText(unittest.TestCase):
20+
def setUp(self):
21+
self.registry = CollectorRegistry()
22+
23+
# Mock time so _created values are fixed.
24+
self.old_time = time.time
25+
time.time = lambda: 123.456
26+
27+
def tearDown(self):
28+
time.time = self.old_time
29+
30+
def custom_collector(self, metric_family):
31+
class CustomCollector(object):
32+
def collect(self):
33+
return [metric_family]
34+
self.registry.register(CustomCollector())
35+
36+
def test_counter(self):
37+
c = Counter('cc', 'A counter', registry=self.registry)
38+
c.inc()
39+
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))
40+
41+
def test_counter_total(self):
42+
c = Counter('cc_total', 'A counter', registry=self.registry)
43+
c.inc()
44+
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))
45+
46+
def test_gauge(self):
47+
g = Gauge('gg', 'A gauge', registry=self.registry)
48+
g.set(17)
49+
self.assertEqual(b'# HELP gg A gauge\n# TYPE gg gauge\ngg 17.0\n# EOF\n', generate_latest(self.registry))
50+
51+
def test_summary(self):
52+
s = Summary('ss', 'A summary', ['a', 'b'], registry=self.registry)
53+
s.labels('c', 'd').observe(17)
54+
self.assertEqual(b'''# HELP ss A summary
55+
# TYPE ss summary
56+
ss_count{a="c",b="d"} 1.0
57+
ss_sum{a="c",b="d"} 17.0
58+
ss_created{a="c",b="d"} 123.456
59+
# EOF
60+
''', generate_latest(self.registry))
61+
62+
@unittest.skipIf(sys.version_info < (2, 7), "Test requires Python 2.7+.")
63+
def test_histogram(self):
64+
s = Histogram('hh', 'A histogram', registry=self.registry)
65+
s.observe(0.05)
66+
self.assertEqual(b'''# HELP hh A histogram
67+
# TYPE hh histogram
68+
hh_bucket{le="0.005"} 0.0
69+
hh_bucket{le="0.01"} 0.0
70+
hh_bucket{le="0.025"} 0.0
71+
hh_bucket{le="0.05"} 1.0
72+
hh_bucket{le="0.075"} 1.0
73+
hh_bucket{le="0.1"} 1.0
74+
hh_bucket{le="0.25"} 1.0
75+
hh_bucket{le="0.5"} 1.0
76+
hh_bucket{le="0.75"} 1.0
77+
hh_bucket{le="1.0"} 1.0
78+
hh_bucket{le="2.5"} 1.0
79+
hh_bucket{le="5.0"} 1.0
80+
hh_bucket{le="7.5"} 1.0
81+
hh_bucket{le="10.0"} 1.0
82+
hh_bucket{le="+Inf"} 1.0
83+
hh_count 1.0
84+
hh_sum 0.05
85+
hh_created 123.456
86+
# EOF
87+
''', generate_latest(self.registry))
88+
89+
def test_gaugehistogram(self):
90+
self.custom_collector(GaugeHistogramMetricFamily('gh', 'help', buckets=[('1.0', 4), ('+Inf', (5))]))
91+
self.assertEqual(b'''# HELP gh help
92+
# TYPE gh gaugehistogram
93+
gh_bucket{le="1.0"} 4.0
94+
gh_bucket{le="+Inf"} 5.0
95+
# EOF
96+
''', generate_latest(self.registry))
97+
98+
def test_info(self):
99+
i = Info('ii', 'A info', ['a', 'b'], registry=self.registry)
100+
i.labels('c', 'd').info({'foo': 'bar'})
101+
self.assertEqual(b'''# HELP ii A info
102+
# TYPE ii info
103+
ii_info{a="c",b="d",foo="bar"} 1.0
104+
# EOF
105+
''', generate_latest(self.registry))
106+
107+
def test_enum(self):
108+
i = Enum('ee', 'An enum', ['a', 'b'], registry=self.registry, states=['foo', 'bar'])
109+
i.labels('c', 'd').state('bar')
110+
self.assertEqual(b'''# HELP ee An enum
111+
# TYPE ee stateset
112+
ee{a="c",b="d",ee="foo"} 0.0
113+
ee{a="c",b="d",ee="bar"} 1.0
114+
# EOF
115+
''', generate_latest(self.registry))
116+
117+
def test_unicode(self):
118+
c = Counter('cc', '\u4500', ['l'], registry=self.registry)
119+
c.labels('\u4500').inc()
120+
self.assertEqual(b'''# HELP cc \xe4\x94\x80
121+
# TYPE cc counter
122+
cc_total{l="\xe4\x94\x80"} 1.0
123+
cc_created{l="\xe4\x94\x80"} 123.456
124+
# EOF
125+
''', generate_latest(self.registry))
126+
127+
def test_escaping(self):
128+
c = Counter('cc', 'A\ncount\\er\"', ['a'], registry=self.registry)
129+
c.labels('\\x\n"').inc(1)
130+
self.assertEqual(b'''# HELP cc A\\ncount\\\\er\\"
131+
# TYPE cc counter
132+
cc_total{a="\\\\x\\n\\""} 1.0
133+
cc_created{a="\\\\x\\n\\""} 123.456
134+
# EOF
135+
''', generate_latest(self.registry))
136+
137+
def test_nonnumber(self):
138+
139+
class MyNumber(object):
140+
def __repr__(self):
141+
return "MyNumber(123)"
142+
143+
def __float__(self):
144+
return 123.0
145+
146+
class MyCollector(object):
147+
def collect(self):
148+
metric = Metric("nonnumber", "Non number", 'untyped')
149+
metric.add_sample("nonnumber", {}, MyNumber())
150+
yield metric
151+
152+
self.registry.register(MyCollector())
153+
self.assertEqual(b'# HELP nonnumber Non number\n# TYPE nonnumber untyped\nnonnumber 123.0\n# EOF\n', generate_latest(self.registry))
154+
155+
def test_timestamp(self):
156+
class MyCollector(object):
157+
def collect(self):
158+
metric = Metric("ts", "help", 'untyped')
159+
metric.add_sample("ts", {"foo": "a"}, 0, 123.456)
160+
metric.add_sample("ts", {"foo": "b"}, 0, -123.456)
161+
metric.add_sample("ts", {"foo": "c"}, 0, 123)
162+
metric.add_sample("ts", {"foo": "d"}, 0, Timestamp(123, 456000000))
163+
metric.add_sample("ts", {"foo": "e"}, 0, Timestamp(123, 456000))
164+
metric.add_sample("ts", {"foo": "f"}, 0, Timestamp(123, 456))
165+
yield metric
166+
167+
self.registry.register(MyCollector())
168+
self.assertEqual(b'''# HELP ts help
169+
# TYPE ts untyped
170+
ts{foo="a"} 0.0 123.456
171+
ts{foo="b"} 0.0 -123.456
172+
ts{foo="c"} 0.0 123
173+
ts{foo="d"} 0.0 123.456000000
174+
ts{foo="e"} 0.0 123.000456000
175+
ts{foo="f"} 0.0 123.000000456
176+
# EOF
177+
''', generate_latest(self.registry))
178+
179+
180+
if __name__ == '__main__':
181+
unittest.main()

0 commit comments

Comments
 (0)