diff --git a/prometheus_client/bridge/graphite.py b/prometheus_client/bridge/graphite.py index 6b8f2417..c73d4931 100755 --- a/prometheus_client/bridge/graphite.py +++ b/prometheus_client/bridge/graphite.py @@ -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')) diff --git a/prometheus_client/core.py b/prometheus_client/core.py index 1f66d30e..94a2a16a 100644 --- a/prometheus_client/core.py +++ b/prometheus_client/core.py @@ -15,6 +15,7 @@ from threading import Lock from timeit import default_timer +from collections import namedtuple from .decorator import decorate @@ -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. @@ -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 @@ -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 @@ -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): @@ -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): @@ -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): @@ -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): @@ -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): @@ -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): @@ -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)) @@ -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)) diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index 1ba9c73a..bfd3cd74 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -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') diff --git a/prometheus_client/multiprocess.py b/prometheus_client/multiprocess.py index b605a8f3..7dd74b2b 100644 --- a/prometheus_client/multiprocess.py +++ b/prometheus_client/multiprocess.py @@ -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 @@ -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': @@ -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() diff --git a/prometheus_client/parser.py b/prometheus_client/parser.py index 1afd90f7..06b81eb8 100755 --- a/prometheus_client/parser.py +++ b/prometheus_client/parser.py @@ -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: @@ -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): @@ -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 diff --git a/tests/test_core.py b/tests/test_core.py index 78696615..d1ec031e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -14,16 +14,17 @@ CollectorRegistry, Counter, CounterMetricFamily, + Enum, Gauge, + GaugeHistogramMetricFamily, GaugeMetricFamily, Histogram, HistogramMetricFamily, - GaugeHistogramMetricFamily, Info, InfoMetricFamily, - Enum, - StateSetMetricFamily, Metric, + StateSetMetricFamily, + Sample, Summary, SummaryMetricFamily, UntypedMetricFamily, @@ -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()) diff --git a/tests/test_parser.py b/tests/test_parser.py index 3218a2bc..a421678d 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -15,6 +15,7 @@ GaugeMetricFamily, HistogramMetricFamily, Metric, + Sample, SummaryMetricFamily, ) from prometheus_client.exposition import ( @@ -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)) diff --git a/tests/test_platform_collector.py b/tests/test_platform_collector.py index 9eda5bb5..529d397c 100644 --- a/tests/test_platform_collector.py +++ b/tests/test_platform_collector.py @@ -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