From f6a2b4a7faf9374f66c72665cc3171186571d7bd Mon Sep 17 00:00:00 2001 From: Cian Butler Date: Tue, 3 May 2022 16:13:37 +0100 Subject: [PATCH 1/2] Support metric help text in multiprocess mode Issue #211 Add support for storing the metrics help text in the multiprocess map. The help will come from the first process read, but it should be the same for all metrics. Signed-off-by: Cian Butler --- prometheus_client/metrics.py | 13 +++++++------ prometheus_client/mmap_dict.py | 5 +++-- prometheus_client/multiprocess.py | 10 ++++------ prometheus_client/values.py | 10 +++++----- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 8878fb86..45125b42 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -275,7 +275,7 @@ def f(): def _metric_init(self) -> None: self._value = values.ValueClass(self._type, self._name, self._name + '_total', self._labelnames, - self._labelvalues) + self._labelvalues, self._documentation) self._created = time.time() def inc(self, amount: float = 1, exemplar: Optional[Dict[str, str]] = None) -> None: @@ -377,7 +377,7 @@ def __init__(self, def _metric_init(self) -> None: self._value = values.ValueClass( self._type, self._name, self._name, self._labelnames, self._labelvalues, - multiprocess_mode=self._multiprocess_mode + self._documentation, multiprocess_mode=self._multiprocess_mode ) def inc(self, amount: float = 1) -> None: @@ -469,8 +469,8 @@ def create_response(request): def _metric_init(self) -> None: self._count = values.ValueClass(self._type, self._name, self._name + '_count', self._labelnames, - self._labelvalues) - self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues) + self._labelvalues, self._documentation) + self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues, self._documentation) self._created = time.time() def observe(self, amount: float) -> None: @@ -583,14 +583,15 @@ def _metric_init(self) -> None: self._buckets: List[values.ValueClass] = [] self._created = time.time() bucket_labelnames = self._labelnames + ('le',) - self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues) + self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues, self._documentation) for b in self._upper_bounds: self._buckets.append(values.ValueClass( self._type, self._name, self._name + '_bucket', bucket_labelnames, - self._labelvalues + (floatToGoString(b),)) + self._labelvalues + (floatToGoString(b),), + self._documentation) ) def observe(self, amount: float, exemplar: Optional[Dict[str, str]] = None) -> None: diff --git a/prometheus_client/mmap_dict.py b/prometheus_client/mmap_dict.py index e8b2df9e..2bdd6efa 100644 --- a/prometheus_client/mmap_dict.py +++ b/prometheus_client/mmap_dict.py @@ -2,6 +2,7 @@ import mmap import os import struct +from typing import List _INITIAL_MMAP_SIZE = 1 << 20 _pack_integer_func = struct.Struct(b'i').pack @@ -137,8 +138,8 @@ def close(self): self._f = None -def mmap_key(metric_name, name, labelnames, labelvalues): +def mmap_key(metric_name: str, name: str, labelnames: List[str], labelvalues: List[str], help_text: str) -> str: """Format a key for use in the mmap file.""" # ensure labels are in consistent order for identity labels = dict(zip(labelnames, labelvalues)) - return json.dumps([metric_name, name, labels], sort_keys=True) + return json.dumps([metric_name, name, labels, help_text], sort_keys=True) diff --git a/prometheus_client/multiprocess.py b/prometheus_client/multiprocess.py index 5a23c482..dd343913 100644 --- a/prometheus_client/multiprocess.py +++ b/prometheus_client/multiprocess.py @@ -15,8 +15,6 @@ except NameError: # Python >= 2.5 FileNotFoundError = IOError -MP_METRIC_HELP = 'Multiprocess metric' - class MultiProcessCollector: """Collector for files for multi-process mode.""" @@ -53,9 +51,9 @@ def _read_metrics(files): def _parse_key(key): val = key_cache.get(key) if not val: - metric_name, name, labels = json.loads(key) + metric_name, name, labels, help_text = json.loads(key) labels_key = tuple(sorted(labels.items())) - val = key_cache[key] = (metric_name, name, labels, labels_key) + val = key_cache[key] = (metric_name, name, labels, labels_key, help_text) return val for f in files: @@ -71,11 +69,11 @@ def _parse_key(key): continue raise for key, value, _ in file_values: - metric_name, name, labels, labels_key = _parse_key(key) + metric_name, name, labels, labels_key, help_text = _parse_key(key) metric = metrics.get(metric_name) if metric is None: - metric = Metric(metric_name, MP_METRIC_HELP, typ) + metric = Metric(metric_name, help_text, typ) metrics[metric_name] = metric if typ == 'gauge': diff --git a/prometheus_client/values.py b/prometheus_client/values.py index 03b203be..3373379b 100644 --- a/prometheus_client/values.py +++ b/prometheus_client/values.py @@ -10,7 +10,7 @@ class MutexValue: _multiprocess = False - def __init__(self, typ, metric_name, name, labelnames, labelvalues, **kwargs): + def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, **kwargs): self._value = 0.0 self._exemplar = None self._lock = Lock() @@ -57,8 +57,8 @@ class MmapedValue: _multiprocess = True - def __init__(self, typ, metric_name, name, labelnames, labelvalues, multiprocess_mode='', **kwargs): - self._params = typ, metric_name, name, labelnames, labelvalues, multiprocess_mode + def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode='', **kwargs): + self._params = typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode # This deprecation warning can go away in a few releases when removing the compatibility if 'prometheus_multiproc_dir' in os.environ and 'PROMETHEUS_MULTIPROC_DIR' not in os.environ: os.environ['PROMETHEUS_MULTIPROC_DIR'] = os.environ['prometheus_multiproc_dir'] @@ -69,7 +69,7 @@ def __init__(self, typ, metric_name, name, labelnames, labelvalues, multiprocess values.append(self) def __reset(self): - typ, metric_name, name, labelnames, labelvalues, multiprocess_mode = self._params + typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode = self._params if typ == 'gauge': file_prefix = typ + '_' + multiprocess_mode else: @@ -81,7 +81,7 @@ def __reset(self): files[file_prefix] = MmapedDict(filename) self._file = files[file_prefix] - self._key = mmap_key(metric_name, name, labelnames, labelvalues) + self._key = mmap_key(metric_name, name, labelnames, labelvalues, help_text) self._value = self._file.read_value(self._key) def __check_for_pid_change(self): From 9a2c50f6a8b75cff06757521b4cd7e8a2492958d Mon Sep 17 00:00:00 2001 From: Evgeny Markov Date: Sun, 4 Dec 2022 21:07:58 +0500 Subject: [PATCH 2/2] Add test for help text Signed-off-by: Evgeny Markov --- tests/test_multiprocess.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_multiprocess.py b/tests/test_multiprocess.py index a41903a1..10990ad3 100644 --- a/tests/test_multiprocess.py +++ b/tests/test_multiprocess.py @@ -281,6 +281,31 @@ def add_label(key, value): self.assertEqual(metrics['h'].samples, expected_histogram) + def test_collect_preserves_help(self): + pid = 0 + values.ValueClass = MultiProcessValue(lambda: pid) + labels = {i: i for i in 'abcd'} + + c = Counter('c', 'c help', labelnames=labels.keys(), registry=None) + g = Gauge('g', 'g help', labelnames=labels.keys(), registry=None) + h = Histogram('h', 'h help', labelnames=labels.keys(), registry=None) + + c.labels(**labels).inc(1) + g.labels(**labels).set(1) + h.labels(**labels).observe(1) + + pid = 1 + + c.labels(**labels).inc(1) + g.labels(**labels).set(1) + h.labels(**labels).observe(5) + + metrics = {m.name: m for m in self.collector.collect()} + + self.assertEqual(metrics['c'].documentation, 'c help') + self.assertEqual(metrics['g'].documentation, 'g help') + self.assertEqual(metrics['h'].documentation, 'h help') + def test_merge_no_accumulate(self): pid = 0 values.ValueClass = MultiProcessValue(lambda: pid)