From c61bb637193a8dc3e31f33ce889a8562bcb2b12f Mon Sep 17 00:00:00 2001 From: PerryLink <255665900+PerryLink@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:34:20 +0000 Subject: [PATCH] fix: bind max_fds before using it in the process collector Signed-off-by: PerryLink <255665900+PerryLink@users.noreply.github.com> --- prometheus_client/process_collector.py | 5 ++++- tests/test_process_collector.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/prometheus_client/process_collector.py b/prometheus_client/process_collector.py index 2894e874..268634d2 100644 --- a/prometheus_client/process_collector.py +++ b/prometheus_client/process_collector.py @@ -79,6 +79,7 @@ def collect(self) -> Iterable[Metric]: except OSError: pass + max_fds: Optional[GaugeMetricFamily] = None try: with open(os.path.join(pid, 'limits'), 'rb') as limits: for line in limits: @@ -90,7 +91,9 @@ def collect(self) -> Iterable[Metric]: open_fds = GaugeMetricFamily(self._prefix + 'open_fds', 'Number of open file descriptors.', len(os.listdir(os.path.join(pid, 'fd')))) - result.extend([open_fds, max_fds]) + result.append(open_fds) + if max_fds is not None: + result.append(max_fds) except OSError: pass diff --git a/tests/test_process_collector.py b/tests/test_process_collector.py index 81be6a55..4f716a04 100644 --- a/tests/test_process_collector.py +++ b/tests/test_process_collector.py @@ -1,4 +1,5 @@ import os +import tempfile import unittest from prometheus_client import CollectorRegistry, ProcessCollector @@ -47,6 +48,27 @@ def test_working_584(self): self.assertEqual(None, self.registry.get_sample_value('process_max_fds')) self.assertEqual(None, self.registry.get_sample_value('process_open_fds')) + def test_limits_without_max_open_files(self): + # A readable limits file is not guaranteed to contain the + # "Max open files" line, see + # https://github.com/home-assistant/core/issues/140045. The collector + # used to raise UnboundLocalError (and with it break the import of + # prometheus_client), instead of just omitting process_max_fds. + with tempfile.TemporaryDirectory() as tmpdir: + proc = os.path.join(tmpdir, 'proc') + os.makedirs(os.path.join(proc, '123', 'fd')) + with open(os.path.join(proc, 'stat'), 'wb') as stat: + stat.write(b'btime 1234567890\n') + with open(os.path.join(proc, '123', 'limits'), 'wb') as limits: + limits.write(b'Limit Soft Limit Hard Limit Units\n' + b'Max cpu time unlimited unlimited seconds\n' + b'Max open fides 2048 4096 files\n') + registry = CollectorRegistry(auto_describe=True) + ProcessCollector(proc=proc, pid=lambda: 123, registry=registry) + + self.assertEqual(0.0, registry.get_sample_value('process_open_fds')) + self.assertEqual(None, registry.get_sample_value('process_max_fds')) + def test_working_fake_pid(self): collector = ProcessCollector(proc=self.test_proc, pid=lambda: 123, registry=self.registry) collector._ticks = 100