From a60796035bcc31cbf132342642ad689e50dcc5d1 Mon Sep 17 00:00:00 2001 From: Om Singhal Date: Tue, 20 Oct 2026 14:37:23 -0400 Subject: [PATCH] Ignore foreign .db files in the multiprocess directory MultiProcessCollector.collect() read every *.db file in PROMETHEUS_MULTIPROC_DIR and parsed it as a metric file. Another database in the same directory, such as a SQLite file, made collection fail with struct.error. Because the first 4 bytes of a file are taken as its used size, a large foreign file could also be read into memory in full. Only collect files named the way MultiProcessValue writes them: counter, histogram or summary, or gauge plus a known multiprocess mode, followed by the process identifier. The identifier can be any string, so custom process_identifier functions keep working. Signed-off-by: Om Singhal --- prometheus_client/multiprocess.py | 13 ++++++++++++- tests/test_multiprocess.py | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/prometheus_client/multiprocess.py b/prometheus_client/multiprocess.py index 046bb2c2..3b243da3 100644 --- a/prometheus_client/multiprocess.py +++ b/prometheus_client/multiprocess.py @@ -2,6 +2,7 @@ import glob import json import os +import re import warnings from .metrics import Gauge @@ -15,6 +16,11 @@ except NameError: # Python >= 2.5 FileNotFoundError = IOError +# Names of the files written by MultiProcessValue: the metric type, the +# multiprocess mode for gauges, then the process identifier. +_MULTIPROC_FILE_RE = re.compile(r'(?:counter|histogram|summary|gauge_(?:{}))_.+\.db'.format( + '|'.join(sorted(Gauge._MULTIPROC_MODES)))) + class MultiProcessCollector: """Collector for files for multi-process mode.""" @@ -167,7 +173,12 @@ def _accumulate_metrics(metrics, accumulate): return metrics.values() def collect(self): - files = glob.glob(os.path.join(self._path, '*.db')) + # Only read files written by this library, so other .db files in the + # directory (for example a SQLite database) are not parsed as metrics. + files = [ + f for f in glob.glob(os.path.join(self._path, '*.db')) + if _MULTIPROC_FILE_RE.fullmatch(os.path.basename(f)) + ] return self.merge(files, accumulate=True) diff --git a/tests/test_multiprocess.py b/tests/test_multiprocess.py index d7cfe3f2..f5d50f67 100644 --- a/tests/test_multiprocess.py +++ b/tests/test_multiprocess.py @@ -489,6 +489,28 @@ def test_missing_gauge_file_during_merge(self): os.path.join(self.tempdir, 'gauge_livesum_9999999.db'), ])) + def test_collect_ignores_foreign_db_files(self): + pid = 0 + values.ValueClass = MultiProcessValue(lambda: pid) + c = Counter('c', 'help', registry=None) + g = Gauge('g', 'help', registry=None, multiprocess_mode='livesum') + c.inc(1) + g.set(2) + # Process identifiers do not have to be integers. + pid = 'worker_1' + c.inc(1) + g.set(3) + + # Other .db files in the directory, some named like metric files. + # None of them can be parsed as a metric file. + sqlite_header = b'SQLite format 3\x00' + b'\x00' * 4080 + for name in ('app.db', 'cache_0.db', 'counter.db', 'gauge_unknown_0.db'): + with open(os.path.join(self.tempdir, name), 'wb') as f: + f.write(sqlite_header) + + self.assertEqual(2, self.registry.get_sample_value('c_total')) + self.assertEqual(5, self.registry.get_sample_value('g')) + def test_remove_clear_warning(self): os.environ['PROMETHEUS_MULTIPROC_DIR'] = self.tempdir with warnings.catch_warnings(record=True) as w: