Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 36 additions & 29 deletions prometheus_client/metrics_core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, List, Optional, Sequence, Tuple, Union
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Union

from .samples import Exemplar, NativeHistogram, Sample, Timestamp
from .validation import _validate_metric_name
Expand Down Expand Up @@ -86,13 +86,14 @@ def __init__(self,
if value is not None:
self.add_metric([], value)

def add_metric(self, labels: Sequence[str], value: float, timestamp: Optional[Union[Timestamp, float]] = None) -> None:
def add_metric(self, labels: Sequence[object], value: float, timestamp: Optional[Union[Timestamp, float]] = None) -> None:
"""Add a metric to the metric family.
Args:
labels: A list of label values
value: The value of the metric.
"""
self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value, timestamp))
label_values = tuple(str(l) for l in labels)
self.samples.append(Sample(self.name, dict(zip(self._labelnames, label_values)), value, timestamp))


# For backward compatibility.
Expand Down Expand Up @@ -127,7 +128,7 @@ def __init__(self,
self.add_metric([], value, created, exemplar=exemplar)

def add_metric(self,
labels: Sequence[str],
labels: Sequence[object],
value: float,
created: Optional[float] = None,
timestamp: Optional[Union[Timestamp, float]] = None,
Expand All @@ -140,9 +141,10 @@ def add_metric(self,
value: The value of the metric
created: Optional unix timestamp the child was created at.
"""
self.samples.append(Sample(self.name + '_total', dict(zip(self._labelnames, labels)), value, timestamp, exemplar))
label_values = tuple(str(l) for l in labels)
self.samples.append(Sample(self.name + '_total', dict(zip(self._labelnames, label_values)), value, timestamp, exemplar))
if created is not None:
self.samples.append(Sample(self.name + '_created', dict(zip(self._labelnames, labels)), created, timestamp))
self.samples.append(Sample(self.name + '_created', dict(zip(self._labelnames, label_values)), created, timestamp))


class GaugeMetricFamily(Metric):
Expand All @@ -167,14 +169,15 @@ def __init__(self,
if value is not None:
self.add_metric([], value)

def add_metric(self, labels: Sequence[str], value: float, timestamp: Optional[Union[Timestamp, float]] = None) -> None:
def add_metric(self, labels: Sequence[object], value: float, timestamp: Optional[Union[Timestamp, float]] = None) -> None:
"""Add a metric to the metric family.

Args:
labels: A list of label values
value: A float
"""
self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value, timestamp))
label_values = tuple(str(l) for l in labels)
self.samples.append(Sample(self.name, dict(zip(self._labelnames, label_values)), value, timestamp))


class SummaryMetricFamily(Metric):
Expand Down Expand Up @@ -204,7 +207,7 @@ def __init__(self,
self.add_metric([], count_value, sum_value)

def add_metric(self,
labels: Sequence[str],
labels: Sequence[object],
count_value: int,
sum_value: float,
timestamp:
Expand All @@ -217,8 +220,9 @@ def add_metric(self,
count_value: The count value of the metric.
sum_value: The sum value of the metric.
"""
self.samples.append(Sample(self.name + '_count', dict(zip(self._labelnames, labels)), count_value, timestamp))
self.samples.append(Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value, timestamp))
label_values = tuple(str(l) for l in labels)
self.samples.append(Sample(self.name + '_count', dict(zip(self._labelnames, label_values)), count_value, timestamp))
self.samples.append(Sample(self.name + '_sum', dict(zip(self._labelnames, label_values)), sum_value, timestamp))


class HistogramMetricFamily(Metric):
Expand Down Expand Up @@ -247,7 +251,7 @@ def __init__(self,
self.add_metric([], buckets, sum_value)

def add_metric(self,
labels: Sequence[str],
labels: Sequence[object],
buckets: Sequence[Union[Tuple[str, float], Tuple[str, float, Exemplar]]],
sum_value: Optional[float],
timestamp: Optional[Union[Timestamp, float]] = None) -> None:
Expand All @@ -261,14 +265,15 @@ def add_metric(self,
The buckets must be sorted, and +Inf present.
sum_value: The sum value of the metric.
"""
label_values = tuple(str(l) for l in labels)
for b in buckets:
bucket, value = b[:2]
exemplar = None
if len(b) == 3:
exemplar = b[2] # type: ignore
self.samples.append(Sample(
self.name + '_bucket',
dict(list(zip(self._labelnames, labels)) + [('le', bucket)]),
dict(list(zip(self._labelnames, label_values)) + [('le', bucket)]),
value,
timestamp,
exemplar,
Expand All @@ -277,9 +282,9 @@ def add_metric(self,
if float(buckets[0][0]) >= 0 and sum_value is not None:
# +Inf is last and provides the count value.
self.samples.append(
Sample(self.name + '_count', dict(zip(self._labelnames, labels)), buckets[-1][1], timestamp))
Sample(self.name + '_count', dict(zip(self._labelnames, label_values)), buckets[-1][1], timestamp))
self.samples.append(
Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value, timestamp))
Sample(self.name + '_sum', dict(zip(self._labelnames, label_values)), sum_value, timestamp))


class GaugeHistogramMetricFamily(Metric):
Expand All @@ -306,7 +311,7 @@ def __init__(self,
self.add_metric([], buckets, gsum_value)

def add_metric(self,
labels: Sequence[str],
labels: Sequence[object],
buckets: Sequence[Tuple[str, float]],
gsum_value: Optional[float],
timestamp: Optional[Union[float, Timestamp]] = None,
Expand All @@ -319,16 +324,17 @@ def add_metric(self,
The buckets must be sorted, and +Inf present.
gsum_value: The sum value of the metric.
"""
label_values = tuple(str(l) for l in labels)
for bucket, value in buckets:
self.samples.append(Sample(
self.name + '_bucket',
dict(list(zip(self._labelnames, labels)) + [('le', bucket)]),
dict(list(zip(self._labelnames, label_values)) + [('le', bucket)]),
value, timestamp))
# +Inf is last and provides the count value.
self.samples.extend([
Sample(self.name + '_gcount', dict(zip(self._labelnames, labels)), buckets[-1][1], timestamp),
Sample(self.name + '_gcount', dict(zip(self._labelnames, label_values)), buckets[-1][1], timestamp),
# TODO: Handle None gsum_value correctly. Currently a None will fail exposition but is allowed here.
Sample(self.name + '_gsum', dict(zip(self._labelnames, labels)), gsum_value, timestamp), # type: ignore
Sample(self.name + '_gsum', dict(zip(self._labelnames, label_values)), gsum_value, timestamp), # type: ignore
])


Expand All @@ -341,7 +347,7 @@ class InfoMetricFamily(Metric):
def __init__(self,
name: str,
documentation: str,
value: Optional[Dict[str, str]] = None,
value: Optional[Mapping[str, object]] = None,
labels: Optional[Sequence[str]] = None,
):
Metric.__init__(self, name, documentation, 'info')
Expand All @@ -354,8 +360,8 @@ def __init__(self,
self.add_metric([], value)

def add_metric(self,
labels: Sequence[str],
value: Dict[str, str],
labels: Sequence[object],
value: Mapping[str, object],
timestamp: Optional[Union[Timestamp, float]] = None,
) -> None:
"""Add a metric to the metric family.
Expand All @@ -364,9 +370,10 @@ def add_metric(self,
labels: A list of label values
value: A dict of labels
"""
label_values = tuple(str(l) for l in labels)
self.samples.append(Sample(
self.name + '_info',
dict(dict(zip(self._labelnames, labels)), **value),
dict(dict(zip(self._labelnames, label_values)), **{k: str(v) for k, v in value.items()}),
1,
timestamp,
))
Expand All @@ -381,7 +388,7 @@ class StateSetMetricFamily(Metric):
def __init__(self,
name: str,
documentation: str,
value: Optional[Dict[str, bool]] = None,
value: Optional[Mapping[Any, bool]] = None,
labels: Optional[Sequence[str]] = None,
):
Metric.__init__(self, name, documentation, 'stateset')
Expand All @@ -394,8 +401,8 @@ def __init__(self,
self.add_metric([], value)

def add_metric(self,
labels: Sequence[str],
value: Dict[str, bool],
labels: Sequence[object],
value: Mapping[Any, bool],
timestamp: Optional[Union[Timestamp, float]] = None,
) -> None:
"""Add a metric to the metric family.
Expand All @@ -404,12 +411,12 @@ def add_metric(self,
labels: A list of label values
value: A dict of string state names to booleans
"""
labels = tuple(labels)
for state, enabled in sorted(value.items()):
label_values = tuple(str(l) for l in labels)
for state, enabled in sorted((str(s), e) for s, e in value.items()):
v = (1 if enabled else 0)
self.samples.append(Sample(
self.name,
dict(zip(self._labelnames + (self.name,), labels + (state,))),
dict(zip(self._labelnames + (self.name,), label_values + (state,))),
v,
timestamp,
))
50 changes: 50 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
StateSetMetricFamily, Summary, SummaryMetricFamily, UntypedMetricFamily,
)
from prometheus_client.decorator import getargspec
from prometheus_client.exposition import generate_latest
from prometheus_client.metrics import _get_use_created
from prometheus_client.validation import (
disable_legacy_validation, enable_legacy_validation,
Expand Down Expand Up @@ -889,6 +890,55 @@ def test_stateset_labels(self):
self.assertEqual(0, self.registry.get_sample_value('s', {'foo': 'bar', 's': 'a'}))
self.assertEqual(0, self.registry.get_sample_value('s', {'foo': 'bar', 's': 'b'}))

def test_labels_coerced_to_string(self):
umf = UntypedMetricFamily('u', 'help', labels=['a'])
umf.add_metric([1], 2)
self.custom_collector(umf)
self.assertEqual(2, self.registry.get_sample_value('u', {'a': '1'}))

cmf = CounterMetricFamily('c', 'help', labels=['a'])
cmf.add_metric([1], 2, created=3)
self.custom_collector(cmf)
self.assertEqual(2, self.registry.get_sample_value('c_total', {'a': '1'}))
self.assertEqual(3, self.registry.get_sample_value('c_created', {'a': '1'}))

gmf = GaugeMetricFamily('g', 'help', labels=['a'])
gmf.add_metric([1], 2)
self.custom_collector(gmf)
self.assertEqual(2, self.registry.get_sample_value('g', {'a': '1'}))

smf = SummaryMetricFamily('s', 'help', labels=['a'])
smf.add_metric([1], count_value=1, sum_value=2)
self.custom_collector(smf)
self.assertEqual(1, self.registry.get_sample_value('s_count', {'a': '1'}))
self.assertEqual(2, self.registry.get_sample_value('s_sum', {'a': '1'}))

hmf = HistogramMetricFamily('h', 'help', labels=['a'])
hmf.add_metric([1], buckets=[('0', 1), ('+Inf', 2)], sum_value=3)
self.custom_collector(hmf)
self.assertEqual(1, self.registry.get_sample_value('h_bucket', {'a': '1', 'le': '0'}))
self.assertEqual(2, self.registry.get_sample_value('h_count', {'a': '1'}))
self.assertEqual(3, self.registry.get_sample_value('h_sum', {'a': '1'}))

ghmf = GaugeHistogramMetricFamily('gh', 'help', labels=['a'])
ghmf.add_metric([1], buckets=[('0', 1), ('+Inf', 2)], gsum_value=3)
self.custom_collector(ghmf)
self.assertEqual(1, self.registry.get_sample_value('gh_bucket', {'a': '1', 'le': '0'}))
self.assertEqual(2, self.registry.get_sample_value('gh_gcount', {'a': '1'}))
self.assertEqual(3, self.registry.get_sample_value('gh_gsum', {'a': '1'}))

imf = InfoMetricFamily('i', 'help', labels=['a'])
imf.add_metric([1], {'c': 2})
self.custom_collector(imf)
self.assertEqual(1, self.registry.get_sample_value('i_info', {'a': '1', 'c': '2'}))

ssf = StateSetMetricFamily('ss', 'help', labels=['a'])
ssf.add_metric([1], {3: True})
self.custom_collector(ssf)
self.assertEqual(1, self.registry.get_sample_value('ss', {'a': '1', 'ss': '3'}))

self.assertIn(b'g{a="1"} 2.0', generate_latest(self.registry))

def test_bad_constructors(self):
self.assertRaises(ValueError, UntypedMetricFamily, 'u', 'help', value=1, labels=[])
self.assertRaises(ValueError, UntypedMetricFamily, 'u', 'help', value=1, labels=['a'])
Expand Down