From dbc0c8c6d2f04314af4b3df1c70e61203b3f99da Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Fri, 14 Aug 2026 19:39:14 -0400 Subject: [PATCH] Report container CPU as a share of the machine, matching Unraid's Docker page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker stats sums CPU across every logical processor, so Jellyfin's "227%" is 2.3 threads busy — 7% of a 32-thread host, which is what Unraid shows and what the operator was reading. The card was printing the raw figure and colouring it against thresholds meant for a percentage, so ordinary background work looked like an emergency; I twice flagged Jellyfin as pegged on that basis and was wrong both times. Divides by nproc, not the watchdog's `cores` — that field is the physical 16 because it compares load averages, and borrowing it here would have looked reasonable and doubled every reading. --- Plugin/unraid/include/media.php | 26 +++++++++++++++++++++++++- Plugin/unraid/pages/arrs.php | 11 +++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Plugin/unraid/include/media.php b/Plugin/unraid/include/media.php index 3475e92..c73986a 100644 --- a/Plugin/unraid/include/media.php +++ b/Plugin/unraid/include/media.php @@ -269,10 +269,26 @@ function vv_media_sessions(): array { // paid per page load — the background writer refreshes it every minute, so the CPU figure is at // most a minute stale, which is honest for a card rather than a live graph. Monitor is where live // belongs. +// Logical CPUs, which is what `docker stats` divides its CPU figure by — and therefore what has to +// be divided back out to get a share of the machine. +// +// Logical, not physical. This host reports 16 physical cores and 32 threads; docker's percentage is +// summed across all 32, so dividing by 16 would double every reading. The watchdog's own `cores` +// field is the physical 16 because it is comparing load averages, which is a different question — +// borrowing it here would have looked reasonable and been wrong by exactly a factor of two. +function vv_media_cpu_count(): int { + static $n = null; + if ($n !== null) return $n; + $n = (int)trim((string)@shell_exec('nproc 2>/dev/null')); + if ($n < 1) $n = max(1, (int)@substr_count((string)@file_get_contents('/proc/cpuinfo'), 'processor')); + return $n; +} + function vv_media_container_stats(array $names): array { $names = array_values(array_filter($names)); if (!$names) return []; + $cpus = vv_media_cpu_count(); $out = []; $args = implode(' ', array_map('escapeshellarg', $names)); $fmt = '{{.Name}}|{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}'; @@ -281,8 +297,16 @@ function vv_media_container_stats(array $names): array { $p = explode('|', $line); if (count($p) < 4) continue; [$mUsed, $mLimit] = array_pad(array_map('trim', explode('/', $p[2])), 2, ''); + $raw = (float)rtrim($p[1], '%'); $out[$p[0]] = [ - 'cpu_pct' => (float)rtrim($p[1], '%'), + // Share of the whole machine, so this card and Unraid's own Docker page report the + // same container as the same number. docker's raw figure is per-core-summed: 227% is + // 2.3 cores busy, which reads as an emergency on a card and is 7% of a 32-thread host. + // Kept alongside rather than discarded — "2.3 cores" is the useful form once you know + // it is not a percentage of anything. + 'cpu_pct' => round($raw / max(1, $cpus), 2), + 'cpu_raw' => $raw, + 'cpu_cores' => round($raw / 100, 2), 'mem_used' => $mUsed, 'mem_limit' => $mLimit, 'mem_pct' => (float)rtrim($p[3], '%'), diff --git a/Plugin/unraid/pages/arrs.php b/Plugin/unraid/pages/arrs.php index 6fdd29d..a7467c5 100644 --- a/Plugin/unraid/pages/arrs.php +++ b/Plugin/unraid/pages/arrs.php @@ -450,10 +450,12 @@ function _mediaServersSection(nodes) { const cards = shown.map(s => { const up = !!s.online; const dot = !up ? '#c62828' : s.pending_restart || s.update_available ? '#ffb74d' : '#4caf50'; - // Colour the CPU against cores, not against 100%: these are containers on a 16-core host, so - // 145% is busy rather than broken, and 1500% would be the number that matters. + // cpu_pct is a share of the whole machine, matching Unraid's Docker page. Thresholds are set + // against that: a single container taking a quarter of the host is worth noticing, half is + // worth alarming about. The earlier version coloured docker's raw per-core-summed figure and + // called 145% alarming when it was 4.5% of a 32-thread machine. const cpu = s.cpu_pct; - const cpuCol = cpu == null ? '#555' : cpu > 400 ? '#ef5350' : cpu > 150 ? '#ffb74d' : '#888'; + const cpuCol = cpu == null ? '#555' : cpu > 50 ? '#ef5350' : cpu > 25 ? '#ffb74d' : '#888'; const flags = []; if (s.update_available) flags.push(['update available', '#ffb74d']); @@ -477,7 +479,8 @@ function _mediaServersSection(nodes) {
${_n(s.users)}
Users
${_n(s.transcodes)}
Transcode
- ${row('CPU', `${cpu == null ? '—' : cpu.toFixed(1) + '%'}`)} + ${row('CPU', `${cpu == null ? '—' : cpu.toFixed(1) + '%'}` + + (s.cpu_cores ? ` ${s.cpu_cores.toFixed(1)} cores` : ''))} ${row('Memory', vvEscHtml(s.mem_used || '—') + (s.mem_pct != null ? ` ${s.mem_pct.toFixed(1)}%` : ''))} ${row('Uptime', s.uptime != null ? _uptime(s.uptime) : '—')} ${s.os ? row('Host OS', vvEscHtml(s.os)) : ''}