Thanks to visit codestin.com
Credit goes to github.com

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