From 5eed986ee3e018c0bcf98d0c0159b5309563a11a Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Thu, 9 Jul 2026 18:13:29 -0700 Subject: [PATCH] Report never-set gauges in mostrecent multiprocess mode In the mostrecent/livemostrecent accumulation branch, sample_timestamps is a defaultdict(float), so the stored timestamp of a series defaults to 0.0. A gauge child that was created but never set() also has a stored timestamp of 0, so the strict 'current_timestamp < timestamp' check was never satisfied and the series was dropped from collection entirely. Every other gauge mode (sum, min, max, all) reports such a child as 0, so mostrecent silently omitting it was inconsistent. Register the sample when it is first seen, regardless of timestamp, and still let a strictly newer timestamp win afterwards. Add test_gauge_mostrecent_never_set, asserting a mostrecent gauge that is created but never set is reported as 0 rather than missing. Signed-off-by: Sean Kim --- prometheus_client/multiprocess.py | 2 +- tests/test_multiprocess.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/prometheus_client/multiprocess.py b/prometheus_client/multiprocess.py index db55874e..420d5977 100644 --- a/prometheus_client/multiprocess.py +++ b/prometheus_client/multiprocess.py @@ -119,7 +119,7 @@ def _accumulate_metrics(metrics, accumulate): elif metric._multiprocess_mode in ('mostrecent', 'livemostrecent'): current_timestamp = sample_timestamps[labels][name] timestamp = float(timestamp or 0) - if current_timestamp < timestamp: + if (name, labels) not in samples[labels] or current_timestamp < timestamp: samples[labels][(name, labels)] = value sample_timestamps[labels][name] = timestamp else: # all/liveall diff --git a/tests/test_multiprocess.py b/tests/test_multiprocess.py index ee0c7423..b21bc36e 100644 --- a/tests/test_multiprocess.py +++ b/tests/test_multiprocess.py @@ -205,6 +205,16 @@ def test_gauge_livemostrecent(self): mark_process_dead(123, os.environ['PROMETHEUS_MULTIPROC_DIR']) self.assertEqual(2, self.registry.get_sample_value('g')) + def test_gauge_mostrecent_never_set(self): + # A child created but never set() has a stored timestamp of 0. It must + # still be reported as 0, like every other gauge mode, rather than + # dropped from collection because 0 is not strictly greater than the + # default timestamp of 0. + Gauge('g', 'help', registry=None, multiprocess_mode='mostrecent') + values.ValueClass = MultiProcessValue(lambda: 456) + Gauge('g', 'help', registry=None, multiprocess_mode='mostrecent') + self.assertEqual(0, self.registry.get_sample_value('g')) + def test_namespace_subsystem(self): c1 = Counter('c', 'help', registry=None, namespace='ns', subsystem='ss') c1.inc(1)