8000 Improve tests. · jinty/client_python@c9d89ba · GitHub
[go: up one dir, main page]

Skip to content

Commit c9d89ba

Browse files
committed
Improve tests.
Split out test files. Add missing histogram exposition test. Add unittests for metric families.
1 parent 4bf1f5c commit c9d89ba

File tree

4 files changed

+315
-195
lines changed

4 files changed

+315
-195
lines changed

prometheus_client/core.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ class Metric(object):
7878
7979
This is intended only for internal use by the instrumentation client.
8080
81-
8281
Custom collectors should use GaugeMetricFamily, CounterMetricFamily
8382
and SummaryMetricFamily instead.
8483
'''
@@ -97,13 +96,13 @@ def add_sample(self, name, labels, value):
9796
self._samples.append((name, labels, value))
9897

9998

100-
class GaugeMetricFamily(Metric):
101-
'''A single gauge and its samples.
99+
class CounterMetricFamily(Metric):
100+
'''A single counter and its samples.
102101
103102
For use by custom collectors.
104103
'''
105104
def __init__(self, name, documentation, value=None, labels=None):
106-
Metric.__init__(self, name, documentation, 'gauge')
105+
Metric.__init__(self, name, documentation, 'counter')
107106
if labels is not None and value is not None:
108107
raise ValueError('Can only specify at most one of value and labels.')
109108
if labels is None:
@@ -117,18 +116,18 @@ def add_metric(self, labels, value):
117116
118117
Args:
119118
labels: A list of label values
120-
value: A float
119+
value: The value of the metric.
121120
'''
122121
self._samples.append((self._name, dict(zip(self._labelnames, labels)), value))
123122

124123

125-
class CounterMetricFamily(Metric):
126-
'''A single counter and its samples.
124+
class GaugeMetricFamily(Metric):
125+
'''A single gauge and its samples.
127126
128127
For use by custom collectors.
129128
'''
130129
def __init__(self, name, documentation, value=None, labels=None):
131-
Metric.__init__(self, name, documentation, 'counter')
130+
Metric.__init__(self, name, documentation, 'gauge')
132131
if labels is not None and value is not None:
133132
raise ValueError('Can only specify at most one of value and labels.')
134133
if labels is None:
@@ -142,7 +141,7 @@ def add_metric(self, labels, value):
142141
143142
Args:
144143
labels: A list of label values
145-
value: The value of the metric.
144+
value: A float
146145
'''
147146
self._samples.append((self._name, dict(zip(self._labelnames, labels)), value))
148147

@@ -154,14 +153,14 @@ class SummaryMetricFamily(Metric):
154153
'''
155154
def __init__(self, name, documentation, count_value=None, sum_value=None, labels=None):
156155
Metric.__init__(self, name, documentation, 'summary')
157-
if sum_value is not None != count_value is not None:
156+
if (sum_value is None) != (count_value is None):
158157
raise ValueError('count_value and sum_value must be provided together.')
159158
if labels is not None and count_value is not None:
160159
raise ValueError('Can only specify at most one of value and labels.')
161160
if labels is None:
162161
labels = []
163162
self._labelnames = labels
164-
if value is not None:
163+
if count_value is not None:
165164
self.add_metric([], count_value, sum_value)
166165

167166
def add_metric(self, labels, count_value, sum_value):
@@ -183,31 +182,32 @@ class HistogramMetricFamily(Metric):
183182
'''
184183
def __init__(self, name, documentation, buckets=None, sum_value=None, labels=None):
185184
Metric.__init__(self, name, documentation, 'histogram')
186-
if sum_value is not None != buckets is not None:
185+
if (sum_value is None) != (buckets is None):
187186
raise ValueError('buckets and sum_value must be provided together.')
188187
if labels is not None and buckets is not None:
189188
raise ValueError('Can only specify at most one of buckets and labels.')
190189
if labels is None:
191190
labels = []
192191
self._labelnames = labels
193-
if value is not None:
192+
if buckets is not None:
194193
self.add_metric([], buckets, sum_value)
195194

196195
def add_metric(self, labels, buckets, sum_value):
197196
'''Add a metric to the metric family.
198197
199198
Args:
200199
labels: A list of label values
201-
buckets: A dict of bucket names to values. The +Inf key must be present.
200+
buckets: A list of pairs of bucket names and values.
201+
The buckets must be sorted, and +Inf present.
202202
sum_value: The sum value of the metric.
203203
'''
204-
for bucket, value in buckets.items:
205-
self._samples.append((self._name + u'_bucket', dict(zip(self._labelnames, labels) + (u'le', bucket)), value))
206-
self._samples.append((self._name + u'_count', dict(zip(self._labelnames, labels)), buckets['+Inf']))
204+
for bucket, value in buckets:
205+
self._samples.append((self._name + u'_bucket', dict(zip(self._labelnames, labels) + [(u'le', bucket)]), value))
206+
# +Inf is last and provides the count value.
207+
self._samples.append((self._name + u'_count', dict(zip(self._labelnames, labels)), buckets[-1][1]))
207208
self._samples.append((self._name + u'_sum', dict(zip(self._labelnames, labels)), sum_value))
208209

209210

210-
211211
class _MutexValue(object):
212212
'''A float protected by a mutex.'''
213213

tests/test_client.py

Lines changed: 68 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,7 @@
44
import time
55
import unittest
66

7-
8-
from prometheus_client import Gauge, Counter, Summary, Histogram, Metric
9-
from prometheus_client import CollectorRegistry, generate_latest, ProcessCollector
10-
from prometheus_client import push_to_gateway, pushadd_to_gateway, delete_from_gateway
11-
from prometheus_client import CONTENT_TYPE_LATEST, instance_ip_grouping_key
12-
13-
try:
14-
from BaseHTTPServer import BaseHTTPRequestHandler
15-
from BaseHTTPServer import HTTPServer
16-
except ImportError:
17-
# Python 3
18-
from http.server import BaseHTTPRequestHandler
19-
from http.server import HTTPServer
20-
21-
7+
from prometheus_client.core import *
228

239
class TestCounter(unittest.TestCase):
2410
def setUp(self):
@@ -304,178 +290,83 @@ def test_invalid_names_raise(self):
304290
self.assertRaises(ValueError, Summary, 'c', '', labelnames=['quantile'])
305291

306292

307-
class TestGenerateText(unittest.TestCase):
293+
class TestMetricFamilies(unittest.TestCase):
308294
def setUp(self):
309295
self.registry = CollectorRegistry()
310296

297+
def custom_collector(self, metric_family):
298+
class CustomCollector(object):
299+
def collect(self):
300+
return [metric_family]
301+
self.registry.register(CustomCollector())
302+
311303
def test_counter(self):
312-
c = Counter('cc', 'A counter', registry=self.registry)
313-
c.inc()
314-
self.assertEqual(b'# HELP cc A counter\n# TYPE cc counter\ncc 1.0\n', generate_latest(self.registry))
304+
self.custom_collector(CounterMetricFamily('c', 'help', value=1))
305+
self.assertEqual(1, self.registry.get_sample_value('c', {}))
306+
307+
def test_counter_labels(self):
308+
cmf = CounterMetricFamily('c', 'help', labels=['a', 'c'])
309+
cmf.add_metric(['b', 'd'], 2)
310+
self.custom_collector(cmf)
311+
self.assertEqual(2, self.registry.get_sample_value('c', {'a': 'b', 'c': 'd'}))
315312

316313
def test_gauge(self):
317-
g = Gauge('gg', 'A gauge', registry=self.registry)
318-
g.set(17)
319-
self.assertEqual(b'# HELP gg A gauge\n# TYPE gg gauge\ngg 17.0\n', generate_latest(self.registry))
314+
self.custom_collector(GaugeMetricFamily('g', 'help', value=1))
315+
self.assertEqual(1, self.registry.get_sample_value('g', {}))
316+
317+
def test_gauge_labels(self):
318+
cmf = GaugeMetricFamily('g', 'help', labels=['a'])
319+
cmf.add_metric(['b'], 2)
320+
self.custom_collector(cmf)
321 179B +
self.assertEqual(2, self.registry.get_sample_value('g', {'a':'b'}))
320322

321323
def test_summary(self):
322-
s = Summary('ss', 'A summary', ['a', 'b'], registry=self.registry)
323-
s.labels('c', 'd').observe(17)
324-
self.assertEqual(b'# HELP ss A summary\n# TYPE ss summary\nss_count{a="c",b="d"} 1.0\nss_sum{a="c",b="d"} 17.0\n', generate_latest(self.registry))
325-
326-
def test_unicode(self):
327-
c = Counter('cc', '\u4500', ['l'], registry=self.registry)
328-
c.labels('\u4500').inc()
329-
self.assertEqual(b'# HELP cc \xe4\x94\x80\n# TYPE cc counter\ncc{l="\xe4\x94\x80"} 1.0\n', generate_latest(self.registry))
330-
331-
def test_escaping(self):
332-
c = Counter('cc', 'A\ncount\\er', ['a'], registry=self.registry)
333-
c.labels('\\x\n"').inc(1)
334-
self.assertEqual(b'# HELP cc A\\ncount\\\\er\n# TYPE cc counter\ncc{a="\\\\x\\n\\""} 1.0\n', generate_latest(self.registry))
335-
336-
def test_nonnumber(self):
337-
class MyNumber():
338-
def __repr__(self):
339-
return "MyNumber(123)"
340-
def __float__(self):
341-
return 123.0
342-
class MyCollector():
343-
def collect(self):
344-
metric = Metric("nonnumber", "Non number", 'untyped')
345-
metric.add_sample("nonnumber", {}, MyNumber())
346-
yield metric
347-
self.registry.register(MyCollector())
348-
self.assertEqual(b'# HELP nonnumber Non number\n# TYPE nonnumber untyped\nnonnumber 123.0\n', generate_latest(self.registry))
324+
self.custom_collector(SummaryMetricFamily('s', 'help', count_value=1, sum_value=2))
325+
self.assertEqual(1, self.registry.get_sample_value('s_count', {}))
326+
self.assertEqual(2, self.registry.get_sample_value('s_sum', {}))
349327

328+
def test_summary_labels(self):
329+
cmf = SummaryMetricFamily('s', 'help', labels=['a'])
330+
cmf.add_metric(['b'], count_value=1, sum_value=2)
331+
self.custom_collector(cmf)
332+
self.assertEqual(1, self.registry.get_sample_value('s_count', {'a': 'b'}))
333+
self.assertEqual(2, self.registry.get_sample_value('s_sum', {'a': 'b'}))
350334

351-
class TestProcessCollector(unittest.TestCase):
352-
def setUp(self):
353-
self.registry = CollectorRegistry()
354-
self.test_proc = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'proc')
355-
356-
def test_working(self):
357-
collector = ProcessCollector(proc=self.test_proc, pid=lambda: 26231, registry=self.registry)
358-
collector._ticks = 100
359-
360-
self.assertEqual(17.21, self.registry.get_sample_value('process_cpu_seconds_total'))
361-
self.assertEqual(56274944.0, self.registry.get_sample_value('process_virtual_memory_bytes'))
362-
self.assertEqual(8114176, self.registry.get_sample_value('process_resident_memory_bytes'))
363-
self.assertEqual(1418184099.75, self.registry.get_sample_value('process_start_time_seconds'))
364-
self.assertEqual(2048.0, self.registry.get_sample_value('process_max_fds'))
365-
self.assertEqual(5.0, self.registry.get_sample_value('process_open_fds'))
366-
self.assertEqual(None, self.registry.get_sample_value('process_fake_namespace'))
367-
368-
def test_namespace(self):
369-
collector = ProcessCollector(proc=self.test_proc, pid=lambda: 26231, registry=self.registry, namespace='n')
370-
collector._ticks = 100
371-
372-
self.assertEqual(17.21, self.registry.get_sample_value('n_process_cpu_seconds_total'))
373-
self.assertEqual(56274944.0, self.registry.get_sample_value('n_process_virtual_memory_bytes'))
374-
self.assertEqual(8114176, self.registry.get_sample_value('n_process_resident_memory_bytes'))
375-
self.assertEqual(1418184099.75, self.registry.get_sample_value('n_process_start_time_seconds'))
376-
self.assertEqual(2048.0, self.registry.get_sample_value('n_process_max_fds'))
377-
self.assertEqual(5.0, self.registry.get_sample_value('n_process_open_fds'))
378-
self.assertEqual(None, self.registry.get_sample_value('process_cpu_seconds_total'))
379-
380-
def test_working_584(self):
381-
collector = ProcessCollector(proc=self.test_proc, pid=lambda: "584\n", registry=self.registry)
382-
collector._ticks = 100
383-
384-
self.assertEqual(0.0, self.registry.get_sample_value('process_cpu_seconds_total'))
385-
self.assertEqual(10395648.0, self.registry.get_sample_value('process_virtual_memory_bytes'))
386-
self.assertEqual(634880, self.registry.get_sample_value('process_resident_memory_bytes'))
387-
self.assertEqual(1418291667.75, self.registry.get_sample_value('process_start_time_seconds'))
388-
self.assertEqual(None, self.registry.get_sample_value('process_max_fds'))
389-
self.assertEqual(None, self.registry.get_sample_value('process_open_fds'))
390-
391-
def test_working_fake_pid(self):
392-
collector = ProcessCollector(proc=self.test_proc, pid=lambda: 123, registry=self.registry)
393-
collector._ticks = 100
394-
395-
self.assertEqual(None, self.registry.get_sample_value('process_cpu_seconds_total'))
396-
self.assertEqual(None, self.registry.get_sample_value('process_virtual_memory_bytes'))
397-
self.assertEqual(None, self.registry.get_sample_value('process_resident_memory_bytes'))
398-
self.assertEqual(None, self.registry.get_sample_value('process_start_time_seconds'))
399-
self.assertEqual(None, self.registry.get_sample_value('process_max_fds'))
400-
self.assertEqual(None, self.registry.get_sample_value('process_open_fds'))
401-
self.assertEqual(None, self.registry.get_sample_value('process_fake_namespace'))
402-
403-
404-
class TestPushGateway(unittest.TestCase):
405-
def setUp(self):
406-
self.registry = CollectorRegistry()
407-
self.counter = Gauge('g', 'help', registry=self.registry)
408-
self.requests = requests = []
409-
class TestHandler(BaseHTTPRequestHandler):
410-
def do_PUT(self):
411-
length = int(self.headers['content-length'])
412-
requests.append((self, self.rfile.read(length)))
413-
self.send_response(201)
414-
self.end_headers()
415-
416-
do_POST = do_PUT
417-
do_DELETE = do_PUT
418-
419-
httpd = HTTPServer(('', 0), TestHandler)
420-
self.address = 'localhost:' + str(httpd.server_address[1])
421-
class TestServer(threading.Thread):
422-
def run(self):
423-
httpd.handle_request()
424-
self.server = TestServer()
425-
self.server.daemon = True
426-
self.server.start()
427-
428-
def test_push(self):
429-
push_to_gateway(self.address, "my_job", self.registry)
430-
self.assertEqual(self.requests[0][0].command, 'PUT')
431-
self.assertEqual(self.requests[0][0].path, '/job/my_job')
432-
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
433-
self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
434-
435-
def test_push_with_groupingkey(self):
436-
push_to_gateway(self.address, "my_job", self.registry, {'a': 9})
437-
self.assertEqual(self.requests[0][0].command, 'PUT')
438-
self.assertEqual(self.requests[0][0].path, '/job/my_job/a/9')
439-
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
440-
self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
441-
442-
def test_push_with_complex_groupingkey(self):
443-
push_to_gateway(self.address, "my_job", self.registry, {'a': 9, 'b': 'a/ z'})
444-
self.assertEqual(self.requests[0][0].command, 'PUT')
445-
self.assertEqual(self.requests[0][0].path, '/job/my_job/a/9/b/a%2F+z')
446-
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
447-
self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
448-
449-
def test_pushadd(self):
450-
pushadd_to_gateway(self.address, "my_job", self.registry)
451-
self.assertEqual(self.requests[0][0].command, 'POST')
452-
self.assertEqual(self.requests[0][0].path, '/job/my_job')
453-
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
454-
self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
455-
456-
def test_pushadd_with_groupingkey(self):
457-
pushadd_to_gateway(self.address, "my_job", self.registry, {'a': 9})
458-
self.assertEqual(self.requests[0][0].command, 'POST')
459-
self.assertEqual(self.requests[0][0].path, '/job/my_job/a/9')
460-
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
461-
self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
462-
463-
def test_delete(self):
464-
delete_from_gateway(self.address, "my_job")
465-
self.assertEqual(self.requests[0][0].command, 'DELETE')
466-
self.assertEqual(self.requests[0][0].path, '/job/my_job')
467-
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
468-
self.assertEqual(self.requests[0][1], b'')
469-
470-
def test_delete_with_groupingkey(self):
471-
delete_from_gateway(self.address, "my_job", {'a': 9})
472-
self.assertEqual(self.requests[0][0].command, 'DELETE')
473-
self.assertEqual(self.requests[0][0].path, '/job/my_job/a/9')
474-
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
475-
self.assertEqual(self.requests[0][1], b'')
476-
477-
def test_instance_ip_grouping_key(self):
478-
self.assertTrue('' != instance_ip_grouping_key()['instance'])
335+
def test_histogram(self):
336+
self.custom_collector(HistogramMetricFamily('h', 'help', buckets=[('0', 1), ('+Inf', 2)], sum_value=3))
337+
self.assertEqual(1, self.registry.get_sample_value('h_bucket', {'le': '0'}))
338+
self.assertEqual(2, self.registry.get_sample_value('h_bucket', {'le': '+Inf'}))
339+
self.assertEqual(2, self.registry.get_sample_value('h_count', {}))
340+
self.assertEqual(3, self.registry.get_sample_value('h_sum', {}))
341+
342+
def test_histogram_labels(self):
343+
cmf = HistogramMetricFamily('h', 'help', labels=['a'])
344+
cmf.add_metric(['b'], buckets=[('0', 1), ('+Inf', 2)], sum_value=3)
345+
self.custom_collector(cmf)
346+
self.assertEqual(1, self.registry.get_sample_value('h_bucket', {'a': 'b', 'le': '0'}))
347+
self.assertEqual(2, self.registry.get_sample_value('h_bucket', {'a': 'b', 'le': '+Inf'}))
348+
self.assertEqual(2, self.registry.get_sample_value('h_count', {'a': 'b'}))
349+
self.assertEqual(3, self.registry.get_sample_value('h_sum', {'a': 'b'}))
350+
351+
def test_bad_constructors(self):
352+
self.assertRaises(ValueError, CounterMetricFamily, 'c', 'help', value=1, labels=[])
353+
self.assertRaises(ValueError, CounterMetricFamily, 'c', 'help', value=1, labels=['a'])
354+
355+
self.assertRaises(ValueError, GaugeMetricFamily, 'g', 'help', value=1, labels=[])
356+
self.assertRaises(ValueError, GaugeMetricFamily, 'g', 'help', value=1, labels=['a'])
357+
358+
self.assertRaises(ValueError, SummaryMetricFamily, 's', 'help', sum_value=1)
359+
self.assertRaises(ValueError, SummaryMetricFamily, 's', 'help', count_value=1)
360+
self.assertRaises(ValueError, SummaryMetricFamily, 's', 'help', count_value=1, labels=['a'])
361+
self.assertRaises(ValueError, SummaryMetricFamily, 's', 'help', sum_value=1, labels=['a'])
362+
self.assertRaises(ValueError, SummaryMetricFamily, 's', 'help', count_value=1, sum_value=1, labels=['a'])
363+
364+
self.assertRaises(ValueError, HistogramMetricFamily, 'h', 'help', sum_value=1)
365+
self.assertRaises(ValueError, HistogramMetricFamily, 'h', 'help', buckets={})
366+
self.assertRaises(ValueError, HistogramMetricFamily, 'h', 'help', sum_value=1, labels=['a'])
367+
self.assertRaises(ValueError, HistogramMetricFamily, 'h', 'help', buckets={}, labels=['a'])
368+
self.assertRaises(ValueError, HistogramMetricFamily, 'h', 'help', buckets={}, sum_value=1, labels=['a'])
369+
self.assertRaises(KeyError, HistogramMetricFamily, 'h', 'help', buckets={}, sum_value=1)
479370

480371

481372
if __name__ == '__main__':

0 commit comments

Comments
 (0)
0