Skip to content

Commit

Permalink
Switch to a namedtuple Sample for samples.
Browse files Browse the repository at this point in the history
This makes things more structured and readable,
and allows for extra information OpenMetrics will
need.

Signed-off-by: Brian Brazil <[email protected]>
  • Loading branch information
brian-brazil committed Sep 7, 2018
1 parent 6718eb5 commit b339d21
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 43 deletions.
8 changes: 4 additions & 4 deletions prometheus_client/bridge/graphite.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,16 @@ def push(self, prefix=''):
prefixstr = prefix + '.'

for metric in self._registry.collect():
for name, labels, value in metric.samples:
if labels:
for s in metric.samples:
if s.labels:
labelstr = '.' + '.'.join(
['{0}.{1}'.format(
_sanitize(k), _sanitize(v))
for k, v in sorted(labels.items())])
for k, v in sorted(s.labels.items())])
else:
labelstr = ''
output.append('{0}{1}{2} {3} {4}\n'.format(
prefixstr, _sanitize(name), labelstr, float(value), now))
prefixstr, _sanitize(s.name), labelstr, float(s.value), now))

conn = socket.create_connection(self._address, self._timeout)
conn.sendall(''.join(output).encode('ascii'))
Expand Down
39 changes: 23 additions & 16 deletions prometheus_client/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from threading import Lock
from timeit import default_timer
from collections import namedtuple

from .decorator import decorate

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

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

class CollectorRegistry(object):
'''Metric collector registry.
Expand Down Expand Up @@ -142,9 +146,9 @@ def get_sample_value(self, name, labels=None):
if labels is None:
labels = {}
for metric in self.collect():
for n, l, value in metric.samples:
if n == name and l == labels:
return value
for s in metric.samples:
if s.name == name and s.labels == labels:
return s.value
return None


Expand Down Expand Up @@ -175,7 +179,7 @@ def add_sample(self, name, labels, value):
'''Add a sample to the metric.
Internal-only, do not use.'''
self.samples.append((name, labels, value))
self.samples.append(Sample(name, labels, value))

def __eq__(self, other):
return (isinstance(other, Metric) and
Expand Down Expand Up @@ -208,7 +212,7 @@ def add_metric(self, labels, value):
labels: A list of label values
value: The value of the metric.
'''
self.samples.append((self.name, dict(zip(self._labelnames, labels)), value))
self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value))


class CounterMetricFamily(Metric):
Expand Down Expand Up @@ -237,9 +241,9 @@ def add_metric(self, labels, value, created=None):
value: The value of the metric
created: Optional unix timestamp the child was created at.
'''
self.samples.append((self.name + '_total', dict(zip(self._labelnames, labels)), value))
self.samples.append(Sample(self.name + '_total', dict(zip(self._labelnames, labels)), value))
if created is not None:
self.samples.append((self.name + '_created', dict(zip(self._labelnames, labels)), created))
self.samples.append(Sample(self.name + '_created', dict(zip(self._labelnames, labels)), created))


class GaugeMetricFamily(Metric):
Expand All @@ -264,7 +268,7 @@ def add_metric(self, labels, value):
labels: A list of label values
value: A float
'''
self.samples.append((self.name, dict(zip(self._labelnames, labels)), value))
self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value))


class SummaryMetricFamily(Metric):
Expand Down Expand Up @@ -292,8 +296,8 @@ def add_metric(self, labels, count_value, sum_value):
count_value: The count value of the metric.
sum_value: The sum value of the metric.
'''
self.samples.append((self.name + '_count', dict(zip(self._labelnames, labels)), count_value))
self.samples.append((self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))
self.samples.append(Sample(self.name + '_count', dict(zip(self._labelnames, labels)), count_value))
self.samples.append(Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))


class HistogramMetricFamily(Metric):
Expand Down Expand Up @@ -323,10 +327,10 @@ def add_metric(self, labels, buckets, sum_value):
sum_value: The sum value of the metric.
'''
for bucket, value in buckets:
self.samples.append((self.name + '_bucket', dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), value))
self.samples.append(Sample(self.name + '_bucket', dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), value))
# +Inf is last and provides the count value.
self.samples.append((self.name + '_count', dict(zip(self._labelnames, labels)), buckets[-1][1]))
self.samples.append((self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))
self.samples.append(Sample(self.name + '_count', dict(zip(self._labelnames, labels)), buckets[-1][1]))
self.samples.append(Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))


class GaugeHistogramMetricFamily(Metric):
Expand All @@ -353,7 +357,10 @@ def add_metric(self, labels, buckets):
The buckets must be sorted, and +Inf present.
'''
for bucket, value in buckets:
self.samples.append((self.name + '_bucket', dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), value))
self.samples.append(Sample(
self.name + '_bucket',
dict(list(zip(self._labelnames, labels)) + [('le', bucket)]),
value))


class InfoMetricFamily(Metric):
Expand All @@ -378,7 +385,7 @@ def add_metric(self, labels, value):
labels: A list of label values
value: A dict of labels
'''
self.samples.append((self.name + '_info',
self.samples.append(Sample(self.name + '_info',
dict(dict(zip(self._labelnames, labels)), **value), 1))


Expand Down Expand Up @@ -407,7 +414,7 @@ def add_metric(self, labels, value):
labels = tuple(labels)
for state, enabled in value.items():
v = (1 if enabled else 0)
self.samples.append((self.name,
self.samples.append(Sample(self.name,
dict(zip(self._labelnames + (self.name,), labels + (state,))), v))


Expand Down
10 changes: 5 additions & 5 deletions prometheus_client/exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,17 @@ def generate_latest(registry=core.REGISTRY):
output.append('# HELP {0} {1}'.format(
mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
output.append('\n# TYPE {0} {1}\n'.format(mname, mtype))
for name, labels, value in metric.samples:
if name == metric.name + '_created':
for s in metric.samples:
if s.name == metric.name + '_created':
continue # Ignore OpenMetrics specific sample.
if labels:
if s.labels:
labelstr = '{{{0}}}'.format(','.join(
['{0}="{1}"'.format(
k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
for k, v in sorted(labels.items())]))
for k, v in sorted(s.labels.items())]))
else:
labelstr = ''
output.append('{0}{1} {2}\n'.format(name, labelstr, core._floatToGoString(value)))
output.append('{0}{1} {2}\n'.format(s.name, labelstr, core._floatToGoString(s.value)))
return ''.join(output).encode('utf-8')


Expand Down
13 changes: 7 additions & 6 deletions prometheus_client/multiprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,18 @@ def collect(self):
for metric in metrics.values():
samples = defaultdict(float)
buckets = {}
for name, labels, value in metric.samples:
for s in metric.samples:
name, labels, value = s.name, s.labels, s.value
if metric.type == 'gauge':
without_pid = tuple(l for l in labels if l[0] != 'pid')
if metric._multiprocess_mode == 'min':
current = samples.setdefault((name, without_pid), value)
if value < current:
samples[(name, without_pid)] = value
samples[(s.name, without_pid)] = value
elif metric._multiprocess_mode == 'max':
current = samples.setdefault((name, without_pid), value)
if value > current:
samples[(name, without_pid)] = value
samples[(s.name, without_pid)] = value
elif metric._multiprocess_mode == 'livesum':
samples[(name, without_pid)] += value
else: # all/liveall
Expand All @@ -74,11 +75,11 @@ def collect(self):
buckets[without_le][bucket[0]] += value
else:
# _sum/_count
samples[(name, labels)] += value
samples[(s.name, labels)] += value

else:
# Counter and Summary.
samples[(name, labels)] += value
samples[(s.name, labels)] += value

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

# Convert to correct sample format.
metric.samples = [(name, dict(labels), value) for (name, labels), value in samples.items()]
metric.samples = [core.Sample(name, dict(labels), value) for (name, labels), value in samples.items()]
return metrics.values()


Expand Down
6 changes: 3 additions & 3 deletions prometheus_client/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def _parse_sample(text):
label = text[label_start + 1:label_end]
# The value is after the label end (ignoring curly brace and space)
value = float(_parse_value(text[label_end + 2:]))
return name, _parse_labels(label), value
return core.Sample(name, _parse_labels(label), value)

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


def text_fd_to_metric_families(fd):
Expand Down Expand Up @@ -214,7 +214,7 @@ def build_metric(name, documentation, typ, samples):
pass
else:
sample = _parse_sample(line)
if sample[0] not in allowed_names:
if sample.name not in allowed_names:
if name != '':
yield build_metric(name, documentation, typ, samples)
# New metric, yield immediately as untyped singleton
Expand Down
9 changes: 5 additions & 4 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@
CollectorRegistry,
Counter,
CounterMetricFamily,
Enum,
Gauge,
GaugeHistogramMetricFamily,
GaugeMetricFamily,
Histogram,
HistogramMetricFamily,
GaugeHistogramMetricFamily,
Info,
InfoMetricFamily,
Enum,
StateSetMetricFamily,
Metric,
StateSetMetricFamily,
Sample,
Summary,
SummaryMetricFamily,
UntypedMetricFamily,
Expand Down Expand Up @@ -682,7 +683,7 @@ def test_restricted_registry(self):
Summary('s', 'help', registry=registry).observe(7)

m = Metric('s', 'help', 'summary')
m.samples = [('s_sum', {}, 7)]
m.samples = [Sample('s_sum', {}, 7)]
self.assertEquals([m], registry.restricted_registry(['s_sum']).collect())


Expand Down
5 changes: 3 additions & 2 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
GaugeMetricFamily,
HistogramMetricFamily,
Metric,
Sample,
SummaryMetricFamily,
)
from prometheus_client.exposition import (
Expand Down Expand Up @@ -89,8 +90,8 @@ def test_untyped(self):
""")
m = Metric("redis_connected_clients", "Redis connected clients", "untyped")
m.samples = [
("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6380"}, 10),
("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6381"}, 12),
Sample("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6380"}, 10),
Sample("redis_connected_clients", {"instance": "rough-snowflake-web", "port": "6381"}, 12),
]
self.assertEqual([m], list(families))

Expand Down
6 changes: 3 additions & 3 deletions tests/test_platform_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ def test_system_info_java(self):

def assertLabels(self, name, labels):
for metric in self.registry.collect():
for n, l, value in metric.samples:
if n == name:
assert l == labels
for s in metric.samples:
if s.name == name:
assert s.labels == labels
return
assert False

Expand Down

0 comments on commit b339d21

Please sign in to comment.