From 88634c306493e9ad1409c300108daf1eba99cab7 Mon Sep 17 00:00:00 2001 From: jiri001meitner Date: Fri, 25 Sep 2026 01:07:43 +0200 Subject: [PATCH] fix: use container-aware load averages Assisted-by: ClaudeCode:claude-sonnet-5-5 Assisted-by: Codex:gpt-5 Signed-off-by: jiri001meitner Signed-off-by: Daniel Kesselberg --- lib/OperatingSystems/Dummy.php | 5 +++++ lib/OperatingSystems/FreeBSD.php | 5 +++++ lib/OperatingSystems/IOperatingSystem.php | 7 +++++++ lib/OperatingSystems/Linux.php | 23 ++++++++++++++++++++++ lib/Os.php | 18 +++++++++++++---- lib/Resources/CPU.php | 12 ------------ lib/SystemStatistics.php | 2 +- tests/lib/LinuxTest.php | 24 +++++++++++++++++++++++ 8 files changed, 79 insertions(+), 17 deletions(-) diff --git a/lib/OperatingSystems/Dummy.php b/lib/OperatingSystems/Dummy.php index fe2327ec1..3c19fc541 100644 --- a/lib/OperatingSystems/Dummy.php +++ b/lib/OperatingSystems/Dummy.php @@ -24,6 +24,11 @@ public function getCPU(): CPU { return new CPU('Unknown Processor', -1); } + #[\Override] + public function getAverageLoad(): array|false { + return false; + } + #[\Override] public function sampleUptime(): int { return -1; diff --git a/lib/OperatingSystems/FreeBSD.php b/lib/OperatingSystems/FreeBSD.php index 3d1764c3c..08e88a49a 100644 --- a/lib/OperatingSystems/FreeBSD.php +++ b/lib/OperatingSystems/FreeBSD.php @@ -36,6 +36,11 @@ public function getCPU(): CPU { return new CPU($name, $cores); } + #[\Override] + public function getAverageLoad(): array|false { + return false; + } + #[\Override] public function sampleUptime(): int { return -1; diff --git a/lib/OperatingSystems/IOperatingSystem.php b/lib/OperatingSystems/IOperatingSystem.php index 7393dba32..2e2aeaa53 100644 --- a/lib/OperatingSystems/IOperatingSystem.php +++ b/lib/OperatingSystems/IOperatingSystem.php @@ -20,6 +20,13 @@ public function supported(): bool; public function getCPU(): CPU; + /** + * Get the 1, 5, and 15 minute load averages, or false when unavailable. + * + * @return array{0: float, 1: float, 2: float}|false + */ + public function getAverageLoad(): array|false; + /** * Uptime obtained by a means that may cost a process fork, used only when * getUptime() cannot answer. diff --git a/lib/OperatingSystems/Linux.php b/lib/OperatingSystems/Linux.php index 017cde0b5..3ff141082 100644 --- a/lib/OperatingSystems/Linux.php +++ b/lib/OperatingSystems/Linux.php @@ -63,6 +63,29 @@ public function getCPU(): CPU { return new CPU($matches[1][0], $threads); } + #[\Override] + public function getAverageLoad(): array|false { + try { + $loadavg = $this->readContent('/proc/loadavg'); + } catch (RuntimeException) { + return false; + } + + $fields = preg_split('/\s+/', $loadavg); + if ($fields === false || count($fields) < 3) { + return false; + } + + $averages = array_slice($fields, 0, 3); + foreach ($averages as $average) { + if (!is_numeric($average) || (float)$average < 0) { + return false; + } + } + + return [(float)$averages[0], (float)$averages[1], (float)$averages[2]]; + } + /** * Read memory from free(1) when /proc/meminfo is out of reach. * diff --git a/lib/Os.php b/lib/Os.php index 766da8451..75d0acc45 100644 --- a/lib/Os.php +++ b/lib/Os.php @@ -33,14 +33,24 @@ public function __construct( } /** - * The load average comes from sys_getloadavg() and needs nothing that - * getCPU() collects, which is why it does not go through it: getCPU() parses - * /proc/cpuinfo, or forks nproc, and this runs on a two-second poll. + * Prefer the operating-system backend because container-aware virtual files + * can differ from sys_getloadavg(), which may expose the host's load. Fall + * back to PHP's portable implementation when no native value is available. * * @return array{0: float, 1: float, 2: float}|false */ + #[\Override] public function getAverageLoad(): array|false { - return sys_getloadavg(); + $load = $this->backend->getAverageLoad(); + if ($load !== false) { + return $load; + } + + if (function_exists('sys_getloadavg')) { + return sys_getloadavg(); + } + + return false; } #[\Override] diff --git a/lib/Resources/CPU.php b/lib/Resources/CPU.php index b89530206..6024a0457 100644 --- a/lib/Resources/CPU.php +++ b/lib/Resources/CPU.php @@ -26,18 +26,6 @@ public function getThreads(): int { return $this->threads; } - /** - * Retrieves the system load averages. - * - * @return array|false Returns an array containing the system load averages for the last 1, 5, and 15 minutes. - */ - public function getAverageLoad(): array|false { - if (function_exists('sys_getloadavg')) { - return sys_getloadavg(); - } - return false; - } - #[\Override] public function jsonSerialize(): array { return [ diff --git a/lib/SystemStatistics.php b/lib/SystemStatistics.php index e48be7cd3..eac070189 100644 --- a/lib/SystemStatistics.php +++ b/lib/SystemStatistics.php @@ -59,7 +59,7 @@ public function getSystemStatistics(bool $skipApps = false, bool $skipUpdate = t 'memcache.locking' => $this->config->getSystemValue('memcache.locking', 'none'), 'debug' => $this->config->getSystemValue('debug', false) ? 'yes' : 'no', 'freespace' => $this->getFreeSpace(), - 'cpuload' => $cpu->getAverageLoad(), + 'cpuload' => $this->os->getAverageLoad(), 'cpunum' => $cpu->getThreads(), 'mem_total' => $this->toKibibytes($memory->getMemTotal()), 'mem_free' => $this->toKibibytes($memory->getMemAvailable()), diff --git a/tests/lib/LinuxTest.php b/tests/lib/LinuxTest.php index 7263e0c90..ea7b54aad 100644 --- a/tests/lib/LinuxTest.php +++ b/tests/lib/LinuxTest.php @@ -189,6 +189,30 @@ public function testGetCpu(): void { $this->assertEquals(4, $cpu->getThreads()); } + public function testGetAverageLoad(): void { + $this->os->method('readContent') + ->with('/proc/loadavg') + ->willReturn('0.22 0.41 0.53 1/123 456'); + + $this->assertSame([0.22, 0.41, 0.53], $this->os->getAverageLoad()); + } + + public function testGetAverageLoadNoData(): void { + $this->os->method('readContent') + ->with('/proc/loadavg') + ->willThrowException(new RuntimeException('Unable to read: "/proc/loadavg"')); + + $this->assertFalse($this->os->getAverageLoad()); + } + + public function testGetAverageLoadInvalidData(): void { + $this->os->method('readContent') + ->with('/proc/loadavg') + ->willReturn('invalid data'); + + $this->assertFalse($this->os->getAverageLoad()); + } + public function testGetCpuOneCore(): void { $this->os->method('readContent') ->with('/proc/cpuinfo')