Report container CPU as a share of the machine, matching Unraid's Docker page
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.
This commit is contained in:
@@ -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], '%'),
|
||||
|
||||
@@ -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) {
|
||||
<div class="vv-arr-stat"><div class="vv-arr-stat-n${s.users ? '' : ' dim'}">${_n(s.users)}</div><div class="vv-arr-stat-l">Users</div></div>
|
||||
<div class="vv-arr-stat"><div class="vv-arr-stat-n${s.transcodes ? '' : ' dim'}">${_n(s.transcodes)}</div><div class="vv-arr-stat-l">Transcode</div></div>
|
||||
</div>
|
||||
${row('CPU', `<span style="color:${cpuCol}">${cpu == null ? '—' : cpu.toFixed(1) + '%'}</span>`)}
|
||||
${row('CPU', `<span style="color:${cpuCol}" title="${vvEscAttr(s.cpu_raw != null ? s.cpu_raw.toFixed(1) + '% as docker stats reports it — summed across all threads' : '')}">${cpu == null ? '—' : cpu.toFixed(1) + '%'}</span>`
|
||||
+ (s.cpu_cores ? ` <span style="color:#3a3a3a">${s.cpu_cores.toFixed(1)} cores</span>` : ''))}
|
||||
${row('Memory', vvEscHtml(s.mem_used || '—') + (s.mem_pct != null ? ` <span style="color:#3a3a3a">${s.mem_pct.toFixed(1)}%</span>` : ''))}
|
||||
${row('Uptime', s.uptime != null ? _uptime(s.uptime) : '—')}
|
||||
${s.os ? row('Host OS', vvEscHtml(s.os)) : ''}
|
||||
|
||||
Reference in New Issue
Block a user