Skip to content

Commit b339d21

Browse files
committed
Switch to a namedtuple Sample for samples.
This makes things more structured and readable, and allows for extra information OpenMetrics will need. Signed-off-by: Brian Brazil <brian.brazil@robustperception.io>
1 parent 6718eb5 commit b339d21

File tree

8 files changed

+53
-43
lines changed

8 files changed

+53
-43
lines changed

prometheus_client/bridge/graphite.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,16 @@ def push(self, prefix=''):
6060
prefixstr = prefix + '.'
6161

6262
for metric in self._registry.collect():
63-
for name, labels, value in metric.samples:
64-
if labels:
63+
for s in metric.samples:
64+
if s.labels:
6565
labelstr = '.' + '.'.join(
6666
['{0}.{1}'.format(
6767
_sanitize(k), _sanitize(v))
68-
for k, v in sorted(labels.items())])
68+
for k, v in sorted(s.labels.items())])
6969
else:
7070
labelstr = ''
7171
output.append('{0}{1}{2} {3} {4}\n'.format(
72-
prefixstr, _sanitize(name), labelstr, float(value), now))
72+
prefixstr, _sanitize(s.name), labelstr, float(s.value), now))
7373

7474
conn = socket.create_connection(self._address, self._timeout)
7575
conn.sendall(''.join(output).encode('ascii'))

prometheus_client/core.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from threading import Lock
1717
from timeit import default_timer
18+
from collections import namedtuple
1819

1920
from .decorator import decorate
2021

@@ -34,6 +35,9 @@
3435
_unpack_integer = struct.Struct(b'i').unpack_from
3536
_unpack_double = struct.Struct(b'd').unpack_from
3637

38+
Sample = namedtuple('Sample', ['name', 'labels', 'value', 'timestamp', 'exemplar'])
39+
# Timestamp and exemplar are optional.
40+
Sample.__new__.__defaults__ = (None, None)
3741

3842
class CollectorRegistry(object):
3943
'''Metric collector registry.
@@ -142,9 +146,9 @@ def get_sample_value(self, name, labels=None):
142146
if labels is None:
143147
labels = {}
144148
for metric in self.collect():
145-
for n, l, value in metric.samples:
146-
if n == name and l == labels:
147-
return value
149+
for s in metric.samples:
150+
if s.name == name and s.labels == labels:
151+
return s.value
148152
return None
149153

150154

@@ -175,7 +179,7 @@ def add_sample(self, name, labels, value):
175179
'''Add a sample to the metric.
176180
177181
Internal-only, do not use.'''
178-
self.samples.append((name, labels, value))
182+
self.samples.append(Sample(name, labels, value))
179183

180184
def __eq__(self, other):
181185
return (isinstance(other, Metric) and
@@ -208,7 +212,7 @@ def add_metric(self, labels, value):
208212
labels: A list of label values
209213
value: The value of the metric.
210214
'''
211-
self.samples.append((self.name, dict(zip(self._labelnames, labels)), value))
215+
self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value))
212216

213217

214218
class CounterMetricFamily(Metric):
@@ -237,9 +241,9 @@ def add_metric(self, labels, value, created=None):
237241
value: The value of the metric
238242
created: Optional unix timestamp the child was created at.
239243
'''
240-
self.samples.append((self.name + '_total', dict(zip(self._labelnames, labels)), value))
244+
self.samples.append(Sample(self.name + '_total', dict(zip(self._labelnames, labels)), value))
241245
if created is not None:
242-
self.samples.append((self.name + '_created', dict(zip(self._labelnames, labels)), created))
246+
self.samples.append(Sample(self.name + '_created', dict(zip(self._labelnames, labels)), created))
243247

244248

245249
class GaugeMetricFamily(Metric):
@@ -264,7 +268,7 @@ def add_metric(self, labels, value):
264268
labels: A list of label values
265269
value: A float
266270
'''
267-
self.samples.append((self.name, dict(zip(self._labelnames, labels)), value))
271+
self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value))
268272

269273

270274
class SummaryMetricFamily(Metric):
@@ -292,8 +296,8 @@ def add_metric(self, labels, count_value, sum_value):
292296
count_value: The count value of the metric.
293297
sum_value: The sum value of the metric.
294298
'''
295-
self.samples.append((self.name + '_count', dict(zip(self._labelnames, labels)), count_value))
296-
self.samples.append((self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))
299+
self.samples.append(Sample(self.name + '_count', dict(zip(self._labelnames, labels)), count_value))
300+
self.samples.append(Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))
297301

298302

299303
class HistogramMetricFamily(Metric):
@@ -323,10 +327,10 @@ def add_metric(self, labels, buckets, sum_value):
323327
sum_value: The sum value of the metric.
324328
'''
325329
for bucket, value in buckets:
326-
self.samples.append((self.name + '_bucket', dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), value))
330+
self.samples.append(Sample(self.name + '_bucket', dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), value))
327331
# +Inf is last and provides the count value.
328-
self.samples.append((self.name + '_count', dict(zip(self._labelnames, labels)), buckets[-1][1]))
329-
self.samples.append((self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))
332+
self.samples.append(Sample(self.name + '_count', dict(zip(self._labelnames, labels)), buckets[-1][1]))
333+
self.samples.append(Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))
330334

331335

332336
class GaugeHistogramMetricFamily(Metric):
@@ -353,7 +357,10 @@ def add_metric(self, labels, buckets):
353357
The buckets must be sorted, and +Inf present.
354358
'''
355359
for bucket, value in buckets:
356-
self.samples.append((self.name + '_bucket', dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), value))
360+
self.samples.append(Sample(
361+
self.name + '_bucket',
362+
dict(list(zip(self._labelnames, labels)) + [('le', bucket)]),
363+
value))
357364

358365

359366
class InfoMetricFamily(Metric):
@@ -378,7 +385,7 @@ def add_metric(self, labels, value):
378385
labels: A list of label values
379386
value: A dict of labels
380387
'''
381-
self.samples.append((self.name + '_info',
388+
self.samples.append(Sample(self.name + '_info',
382389
dict(dict(zip(self._labelnames, labels)), **value), 1))
383390

384391

@@ -407,7 +414,7 @@ def add_metric(self, labels, value):
407414
labels = tuple(labels)
408415
for state, enabled in value.items():
409416
v = (1 if enabled else 0)
410-
self.samples.append((self.name,
417+
self.samples.append(Sample(self.name,
411418
dict(zip(self._labelnames + (self.name,), labels + (state,))), v))
412419

413420

prometheus_client/exposition.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,17 @@ def generate_latest(registry=core.REGISTRY):
8585
output.append('# HELP {0} {1}'.format(
8686
mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
8787
output.append('\n# TYPE {0} {1}\n'.format(mname, mtype))
88-
for name, labels, value in metric.samples:
89-
if name == metric.name + '_created':
88+
for s in metric.samples:
89+
if s.name == metric.name + '_created':
9090
continue # Ignore OpenMetrics specific sample.
91-
if labels:
91+
if s.labels:
9292
labelstr = '{{{0}}}'.format(','.join(
9393
['{0}="{1}"'.format(
9494
k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
95-
for k, v in sorted(labels.items())]))
95+
for k, v in sorted(s.labels.items())]))
9696
else:
9797
labelstr = ''
98-
output.append('{0}{1} {2}\n'.format(name, labelstr, core._floatToGoString(value)))
98+
output.append('{0}{1} {2}\n'.format(s.name, labelstr, core._floatToGoString(s.value)))
9999
return ''.join(output).encode('utf-8')
100100

101101

prometheus_client/multiprocess.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,18 @@ def collect(self):
4848
for metric in metrics.values():
4949
samples = defaultdict(float)
5050
buckets = {}
51-
for name, labels, value in metric.samples:
51+
for s in metric.samples:
52+
name, labels, value = s.name, s.labels, s.value
5253
if metric.type == 'gauge':
5354
without_pid = tuple(l for l in labels if l[0] != 'pid')
5455
if metric._multiprocess_mode == 'min':
5556
current = samples.setdefault((name, without_pid), value)
5657
if value < current:
57-
samples[(name, without_pid)] = value
58+
samples[(s.name, without_pid)] = value
5859
elif metric._multiprocess_mode == 'max':
5960
current = samples.setdefault((name, without_pid), value)
6061
if value > current:
61-
samples[(name, without_pid)] = value
62+
samples[(s.name, without_pid)] = value
6263
elif metric._multiprocess_mode == 'livesum':
6364
samples[(name, without_pid)] += value
6465
else: # all/liveall
@@ -74,11 +75,11 @@ def collect(self):
7475
buckets[without_le][bucket[0]] += value
7576
else:
7677
# _sum/_count
77-
samples[(name, labels)] += value
78+
samples[(s.name, labels)] += value
7879

7980
else:
8081
# Counter and Summary.
81-
samples[(name, labels)] += value
82+
samples[(s.name, labels)] += value
8283

8384
# Accumulate bucket values.
8485
if metric.type == 'histogram':
@@ -90,7 +91,7 @@ def collect(self):
9091
samples[(metric.name + '_count', labels)] = acc
9192

9293
# Convert to correct sample format.
93-
metric.samples = [(name, dict(labels), value) for (name, labels), value in samples.items()]
94+
metric.samples = [core.Sample(name, dict(labels), value) for (name, labels), value in samples.items()]
9495
return metrics.values()
9596

9697

prometheus_client/parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def _parse_sample(text):
125125
label = text[label_start + 1:label_end]
126126
# The value is after the label end (ignoring curly brace and space)
127127
value = float(_parse_value(text[label_end + 2:]))
128-
return name, _parse_labels(label), value
128+
return core.Sample(name, _parse_labels(label), value)
129129

130130
# We don't have labels
131131
except ValueError:
@@ -137,7 +137,7 @@ def _parse_sample(text):
137137
name = text[:name_end]
138138
# The value is after the name
139139
value = float(_parse_value(text[name_end:]))
140-
return name, {}, value
140+
return core.Sample(name, {}, value)
141141

142142

143143
def text_fd_to_metric_families(fd):
@@ -214,7 +214,7 @@ def build_metric(name, documentation, typ, samples):
214214
pass
215215
else:
216216
sample = _parse_sample(line)
217-
if sample[0] not in allowed_names:
217+
if sample.name not in allowed_names:
218218
if name != '':
219219
yield build_metric(name, documentation, typ, samples)
220220
# New metric, yield immediately as untyped singleton

tests/test_core.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414
CollectorRegistry,
1515
Counter,
1616
CounterMetricFamily,
17+
Enum,
1718
Gauge,
19+
GaugeHistogramMetricFamily,
1820
GaugeMetricFamily,
1921
Histogram,
2022
HistogramMetricFamily,
21-
GaugeHistogramMetricFamily,
2223
Info,
2324
InfoMetricFamily,
24-
Enum,
25-
StateSetMetricFamily,
2625
Metric,
26+
StateSetMetricFamily,
27+
Sample,
2728
Summary,
2829
SummaryMetricFamily,
2930
UntypedMetricFamily,
@@ -682,7 +683,7 @@ def test_restricted_registry(self):
682683
Summary('s', 'help', registry=registry).observe(7)
683684

684685
m = Metric('s', 'help', 'summary')
685-
m.samples = [('s_sum', {}, 7)]
686+
m.samples = [Sample('s_sum', {}, 7)]
686687
self.assertEquals([m], registry.restricted_registry(['s_sum']).collect())
687688

688689

tests/test_parser.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
GaugeMetricFamily,
1616
HistogramMetricFamily,
1717
Metric,
18+
Sample,
1819
SummaryMetricFamily,
1920
)
2021
from prometheus_client.exposition import (
@@ -89,8 +90,8 @@ def test_untyped(self):
8990
""")
9091
m = Metric("redis_connected_clients", "Redis connected clients", "untyped")
9192
m.samples = [
92-
("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6380"}, 10),
93-
("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6381"}, 12),
93+
Sample("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6380"}, 10),
94+
Sample("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6381"}, 12),
9495
]
9596
self.assertEqual([m], list(families))
9697

tests/test_platform_collector.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ def test_system_info_java(self):
3737

3838
def assertLabels(self, name, labels):
3939
for metric in self.registry.collect():
40-
for n, l, value in metric.samples:
41-
if n == name:
42-
assert l == labels
40+
for s in metric.samples:
41+
if s.name == name:
42+
assert s.labels == labels
4343
return
4444
assert False
4545

0 commit comments

Comments
 (0)