8000 Merge pull request #306 from prometheus/openmetrics · kongklw/client_python@80a3944 · GitHub
[go: up one dir, main page]

Skip to content

Commit 80a3944

Browse files
authored
Merge pull request prometheus#306 from prometheus/openmetrics
Add gsum/gcount to GaugeHistogram.
2 parents d1e8b34 + 283ba23 commit 80a3944

File tree

8 files changed

+416
-92
lines changed

8 files changed

+416
-92
lines changed

prometheus_client/core.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class Timestamp(object):
4949
def __init__(self, sec, nsec):
5050
if nsec < 0 or nsec >= 1e9:
5151
raise ValueError("Invalid value for nanoseconds in Timestamp: {}".format(nsec))
52+
if sec < 0:
53+
nsec = -nsec
5254
self.sec = int(sec)
5355
self.nsec = int(nsec)
5456

@@ -64,6 +66,12 @@ def __float__(self):
6466
def __eq__(self, other):
6567
return type(self) == type(other) and self.sec == other.sec and self.nsec == other.nsec
6668

69+
def __ne__(self, other):
70+
return not self == other
71+
72+
def __gt__(self, other):
73+
return self.sec > other.sec or self.nsec > other.nsec
74+
6775

6876
Exemplar = namedtuple('Exemplar', ['labels', 'value', 'timestamp'])
6977
Exemplar.__new__.__defaults__ = (None, )
@@ -122,6 +130,7 @@ def _get_names(self, collector):
122130
'counter': ['_total', '_created'],
123131
'summary': ['', '_sum', '_count', '_created'],
124132
'histogram': ['_bucket', '_sum', '_count', '_created'],
133+
'gaugehistogram': ['_bucket', '_gsum', '_gcount'],
125134
'info': ['_info'],
126135
}
127136
for metric in desc_func():
@@ -391,29 +400,33 @@ class GaugeHistogramMetricFamily(Metric):
391400
392401
For use by custom collectors.
393402
'''
394-
def __init__(self, name, documentation, buckets=None, labels=None, unit=''):
403+
def __init__(self, name, documentation, buckets=None, gsum_value=None, labels=None, unit=''):
395404
Metric.__init__(self, name, documentation, 'gaugehistogram', unit)
396405
if labels is not None and buckets is not None:
397406
raise ValueError('Can only specify at most one of buckets and labels.')
398407
if labels is None:
399408
labels = []
400409
self._labelnames = tuple(labels)
401410
if buckets is not None:
402-
self.add_metric([], buckets)
411+
self.add_metric([], buckets, gsum_value)
403412

404-
def add_metric(self, labels, buckets, timestamp=None):
413+
def add_metric(self, labels, buckets, gsum_value, timestamp=None):
405414
'''Add a metric to the metric family.
406415
407416
Args:
408417
labels: A list of label values
409418
buckets: A list of pairs of bucket names and values.
410419
The buckets must be sorted, and +Inf present.
420+
gsum_value: The sum value of the metric.
411421
'''
412422
for bucket, value in buckets:
413423
self.samples.append(Sample(
414424
self.name + '_bucket',
415425
dict(list(zip(self._labelnames, labels)) + [('le', bucket)]),
416426
value, timestamp))
427+
# +Inf is last and provides the count value.
428+
self.samples.append(Sample(self.name + '_gcount', dict(zip(self._labelnames, labels)), buckets[-1][1], timestamp))
429+
self.samples.append(Sample(self.name + '_gsum', dict(zip(self._labelnames, labels)), gsum_value, timestamp))
417430

418431

419432
class InfoMetricFamily(Metric):
@@ -465,7 +478,7 @@ def add_metric(self, labels, value, timestamp=None):
465478
value: A dict of string state names to booleans
466479
'''
467480
labels = tuple(labels)
468-
for state, enabled in value.items():
481+
for state, enabled in sorted(value.items()):
469482
v = (1 if enabled else 0)
470483
self.samples.append(Sample(self.name,
471484
dict(zip(self._labelnames + (self.name,), labels + (state,))), v, timestamp))

prometheus_client/exposition.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ def start_wsgi_server(port, addr='', registry=core.REGISTRY):
6767

6868
def generate_latest(registry=core.REGISTRY):
6969
'''Returns the metrics from the registry in latest text format as a string.'''
70+
71+
def sample_line(s):
72+
if s.labels:
73+
labelstr = '{{{0}}}'.format(','.join(
74+
['{0}="{1}"'.format(
75+
k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
76+
for k, v in sorted(s.labels.items())]))
77+
else:
78+
labelstr = ''
79+
timestamp = ''
80+
if s.timestamp is not None:
81+
# Convert to milliseconds.
82+
timestamp = ' {0:d}'.format(int(float(s.timestamp) * 1000))
83+
return '{0}{1} {2}{3}\n'.format(
84+
s.name, labelstr, core._floatToGoString(s.value), timestamp)
85+
7086
output = []
7187
for metric in registry.collect():
7288
mname = metric.name
@@ -86,32 +102,29 @@ def generate_latest(registry=core.REGISTRY):
86102
elif mtype == 'unknown':
87103
mtype = 'untyped'
88104

89-
output.append('# HELP {0} {1}'.format(
105+
output.append('# HELP {0} {1}\n'.format(
90106
mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
91-
output.append('\n# TYPE {0} {1}\n'.format(mname, mtype))
107+
output.append('# TYPE {0} {1}\n'.format(mname, mtype))
108+
109+
om_samples = {}
92110
for s in metric.samples:
93-
if s.name == metric.name + '_created':
94-
continue # Ignore OpenMetrics specific sample. TODO: Make these into a gauge.
95-
if s.labels:
96-
labelstr = '{{{0}}}'.format(','.join(
97-
['{0}="{1}"'.format(
98-
k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
99-
for k, v in sorted(s.labels.items())]))
111+
for suffix in ['_created', '_gsum', '_gcount']:
112+
if s.name == metric.name + suffix:
113+
# OpenMetrics specific sample, put in a gauge at the end.
114+
om_samples.setdefault(suffix, []).append(sample_line(s))
115+
break
100116
else:
101-
labelstr = ''
102-
timestamp = ''
103-
if s.timestamp is not None:
104-
# Convert to milliseconds.
105-
timestamp = ' {0:d}'.format(int(float(s.timestamp) * 1000))
106-
output.append('{0}{1} {2}{3}\n'.format(
107-
s.name, labelstr, core._floatToGoString(s.value), timestamp))
117+
output.append(sample_line(s))
118+
for suffix, lines in sorted(om_samples.items()):
119+
output.append('# TYPE {0}{1} gauge\n'.format(metric.name, suffix))
120+
output.extend(lines)
108121
return ''.join(output).encode('utf-8')
109122

110123

111124
def choose_encoder(accept_header):
112125
accept_header = accept_header or ''
113126
for accepted in accept_header.split(','):
114-
if accepted == 'text/openmetrics; version=0.0.1':
127+
if accepted == 'application/openmetrics-text; version=0.0.1':
115128
return (openmetrics.exposition.generate_latest,
116129
openmetrics.exposition.CONTENT_TYPE_LATEST)
117130
return (generate_latest, CONTENT_TYPE_LATEST)

prometheus_client/openmetrics/exposition.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from .. import core
66

7-
CONTENT_TYPE_LATEST = str('text/openmetrics; version=0.0.1; charset=utf-8')
7+
CONTENT_TYPE_LATEST = str('application/openmetrics-text; version=0.0.1; charset=utf-8')
88
'''Content type of the latest OpenMetrics text format'''
99

1010
def generate_latest(registry):
@@ -26,7 +26,7 @@ def generate_latest(registry):
2626
else:
2727
labelstr = ''
2828
if s.exemplar:
29-
if metric.type != 'histogram' or not s.name.endswith('_bucket'):
29+
if metric.type not in ('histogram', 'gaugehistogram') or not s.name.endswith('_bucket'):
3030
raise ValueError("Metric {0} has exemplars, but is not a histogram bucket".format(metric.name))
3131
labels = '{{{0}}}'.format(','.join(
3232
['{0}="{1}"'.format(
@@ -42,7 +42,6 @@ def generate_latest(registry):
4242
exemplarstr = ''
4343
timestamp = ''
4444
if s.timestamp is not None:
45-
# Convert to milliseconds.
4645
timestamp = ' {0}'.format(s.timestamp)
4746
output.append('{0}{1} {2}{3}{4}\n'.format(s.name, labelstr,
4847
core._floatToGoString(s.value), timestamp, exemplarstr))

0 commit comments

Comments
 (0)
0