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

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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