Skip to content

Commit 494a27d

Browse files
committed
Add the GaugeHistogram
Signed-off-by: Brian Brazil <brian.brazil@robustperception.io>
1 parent 1aa8405 commit 494a27d

File tree

4 files changed

+56
-1
lines changed

4 files changed

+56
-1
lines changed

prometheus_client/core.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def get_sample_value(self, name, labels=None):
152152
'''The default registry.'''
153153

154154
_METRIC_TYPES = ('counter', 'gauge', 'summary', 'histogram',
155-
'untyped', 'info', 'stateset')
155+
'gaugehistogram', 'untyped', 'info', 'stateset')
156156

157157

158158
class Metric(object):
@@ -329,6 +329,33 @@ def add_metric(self, labels, buckets, sum_value):
329329
self.samples.append((self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))
330330

331331

332+
class GaugeHistogramMetricFamily(Metric):
333+
'''A single gauge histogram and its samples.
334+
335+
For use by custom collectors.
336+
'''
337+
def __init__(self, name, documentation, buckets=None, labels=None):
338+
Metric.__init__(self, name, documentation, 'gaugehistogram')
339+
if labels is not None and buckets is not None:
340+
raise ValueError('Can only specify at most one of buckets and labels.')
341+
if labels is None:
342+
labels = []
343+
self._labelnames = tuple(labels)
344+
if buckets is not None:
345+
self.add_metric([], buckets)
346+
347+
def add_metric(self, labels, buckets):
348+
'''Add a metric to the metric family.
349+
350+
Args:
351+
labels: A list of label values
352+
buckets: A list of pairs of bucket names and values.
353+
The buckets must be sorted, and +Inf present.
354+
'''
355+
for bucket, value in buckets:
356+
self.samples.append((self.name + '_bucket', dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), value))
357+
358+
332359
class InfoMetricFamily(Metric):
333360
'''A single info and its samples.
334361

prometheus_client/exposition.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ def generate_latest(registry=core.REGISTRY):
7878
mtype = 'gauge'
7979
elif mtype == 'stateset':
8080
mtype = 'gauge'
81+
elif mtype == 'gaugehistogram':
82+
# A gauge histogram is really a gauge,
83+
# but this captures the strucutre better.
84+
mtype = 'histogram'
8185
output.append('# HELP {0} {1}'.format(
8286
mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
8387
output.append('\n# TYPE {0} {1}\n'.format(mname, mtype))

tests/test_core.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
GaugeMetricFamily,
1919
Histogram,
2020
HistogramMetricFamily,
21+
GaugeHistogramMetricFamily,
2122
Info,
2223
InfoMetricFamily,
2324
Enum,
@@ -545,6 +546,18 @@ def test_histogram_labels(self):
545546
self.assertEqual(2, self.registry.get_sample_value('h_count', {'a': 'b'}))
546547
self.assertEqual(3, self.registry.get_sample_value('h_sum', {'a': 'b'}))
547548

549+
def test_gaugehistogram(self):
550+
self.custom_collector(GaugeHistogramMetricFamily('h', 'help', buckets=[('0', 1), ('+Inf', 2)]))
551+
self.assertEqual(1, self.registry.get_sample_value('h_bucket', {'le': '0'}))
552+
self.assertEqual(2, self.registry.get_sample_value('h_bucket', {'le': '+Inf'}))
553+
554+
def test_gaugehistogram_labels(self):
555+
cmf = GaugeHistogramMetricFamily('h', 'help', labels=['a'])
556+
cmf.add_metric(['b'], buckets=[('0', 1), ('+Inf', 2)])
557+
self.custom_collector(cmf)
558+
self.assertEqual(1, self.registry.get_sample_value('h_bucket', {'a': 'b', 'le': '0'}))
559+
self.assertEqual(2, self.registry.get_sample_value('h_bucket', {'a': 'b', 'le': '+Inf'}))
560+
548561
def test_info(self):
549562
self.custom_collector(InfoMetricFamily('i', 'help', value={'a': 'b'}))
550563
self.assertEqual(1, self.registry.get_sample_value('i_info', {'a': 'b'}))

tests/test_exposition.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from prometheus_client import CollectorRegistry, generate_latest
1414
from prometheus_client import push_to_gateway, pushadd_to_gateway, delete_from_gateway
1515
from prometheus_client import CONTENT_TYPE_LATEST, instance_ip_grouping_key
16+
from prometheus_client.core import GaugeHistogramMetricFamily
1617
from prometheus_client.exposition import default_handler, basic_auth_handler, MetricsHandler
1718

1819
try:
@@ -28,6 +29,12 @@ class TestGenerateText(unittest.TestCase):
2829
def setUp(self):
2930
self.registry = CollectorRegistry()
3031

32+
def custom_collector(self, metric_family):
33+
class CustomCollector(object):
34+
def collect(self):
35+
return [metric_family]
36+
self.registry.register(CustomCollector())
37+
3138
def test_counter(self):
3239
c = Counter('cc', 'A counter', registry=self.registry)
3340
c.inc()
@@ -72,6 +79,10 @@ def test_histogram(self):
7279
hh_sum 0.05
7380
''', generate_latest(self.registry))
7481

82+
def test_gaugehistogram(self):
83+
self.custom_collector(GaugeHistogramMetricFamily('gh', 'help', buckets=[('1.0', 4), ('+Inf', (5))]))
84+
self.assertEqual(b'''# HELP gh help\n# TYPE gh histogram\ngh_bucket{le="1.0"} 4.0\ngh_bucket{le="+Inf"} 5.0\n''', generate_latest(self.registry))
85+
7586
def test_info(self):
7687
i = Info('ii', 'A info', ['a', 'b'], registry=self.registry)
7788
i.labels('c', 'd').info({'foo': 'bar'})

0 commit comments

Comments
 (0)