diff --git a/Plugin/unraid/adapter.sh b/Plugin/unraid/adapter.sh index 17e1fd0..340f842 100755 --- a/Plugin/unraid/adapter.sh +++ b/Plugin/unraid/adapter.sh @@ -203,6 +203,46 @@ platform_get_temp_thresholds() { echo "${hdd_hot:-45} ${hdd_max:-55} ${ssd_hot:-60} ${ssd_max:-70}" } +# ────────────────────────────────────────────────────────────────────────────────────────────── +# platform_get_cpu_temp +# Writes the CPU temperature in °C to stdout as a decimal, or nothing if no sensor answers. +# Returns 1 when nothing could be read, so a caller can tell "no sensor" from "cold". +# +# Two things make this harder than it looks, and both produced wrong numbers for months: +# +# 1. lm-sensors prints the alarm thresholds on the same line as the reading: +# Core 0: +63.0 C (high = +80.0 C, crit = +100.0 C) +# Anything that scrapes numbers off the whole line and takes the largest reports 100 — +# the critical threshold — as the current temperature, on every Intel box, forever. The +# parenthetical is stripped before a single digit is read. +# +# 2. On AMD, Tctl is not the die temperature. It is a control value carrying a fixed offset +# (+27°C on Threadripper), which is why HOST1 read 70 while the die was at 43. Tdie is the +# real measurement and is preferred wherever both are published. +# +# Preference order: Tdie, then Intel's package sensor, then the board's own CPU Temp, then the +# hottest individual core. The first label that answers wins; within a label the maximum is +# taken, because a multi-die part publishes one line per die and the hottest is the one that +# matters. +# ────────────────────────────────────────────────────────────────────────────────────────────── +platform_get_cpu_temp() { + command -v sensors >/dev/null 2>&1 || return 1 + + local out label value + out=$(sensors 2>/dev/null | sed 's/(.*//') # drop "(high = ..., crit = ...)" + [[ -n "$out" ]] || return 1 + + for label in 'Tdie' 'Package id 0' 'CPU Temp' 'Core '; do + value=$(echo "$out" | grep -i "^[[:space:]]*${label}" \ + | grep -oE '[+-]?[0-9]+\.[0-9]+' | sort -n | tail -1) + if [[ -n "$value" ]]; then + echo "${value#+}" # bash printf tolerates a leading +, PHP casts fine, awk does not + return 0 + fi + done + return 1 +} + # ────────────────────────────────────────────────────────────────────────────────────────────── # platform_is_maintenance_running # Returns 0 if a parity check or sync is currently in progress. diff --git a/Plugin/unraid/include/common.php b/Plugin/unraid/include/common.php index 819b376..dc794f2 100644 --- a/Plugin/unraid/include/common.php +++ b/Plugin/unraid/include/common.php @@ -507,6 +507,41 @@ function vv_ups_stats(): array { ]; } +// CPU temperature in °C, or null if no sensor answers. +// +// Mirror of platform_get_cpu_temp() in Plugin/unraid/adapter.sh — the two must agree, because the +// Monitor card and the stability watchdog display and act on the same number, and for months they +// did not. The PHP side scraped every decimal off the sensor line and took the largest, which on +// any board printing "(high = +80.0 C, crit = +100.0 C)" is the critical threshold: HOST2 reported +// a flat 100°C while sitting at 63. The shell side took the line's last field, which on that same +// board is the literal ")", so its check silently never fired. +// +// Both defects came from parsing a line that carries three temperatures when only one of them is a +// reading. The parenthetical is stripped before any digit is read. +// +// Tdie before Tctl for the AMD reason: Tctl carries a fixed offset (+27°C on Threadripper) and is +// a control value, not a measurement — it is why HOST1 read 70 while the die was at 43. +function vv_cpu_temp(): ?int { + $out = shell_exec('sensors 2>/dev/null') ?: ''; + if (trim($out) === '') return null; + + $out = preg_replace('/\(.*$/m', '', $out); // drop "(high = ..., crit = ...)" + + foreach (['Tdie', 'Package id 0', 'CPU Temp', 'Core '] as $label) { + $best = null; + foreach (explode("\n", $out) as $line) { + if (stripos(ltrim($line), $label) !== 0) continue; + if (!preg_match('/([+-]?\d+\.\d+)/', $line, $m)) continue; + $v = (float)$m[1]; + // Max within a label: a multi-die part publishes one line per die, and the hottest is + // the one worth acting on. + if ($best === null || $v > $best) $best = $v; + } + if ($best !== null) return (int)round($best); + } + return null; +} + function vv_parity_status(): array { $var = []; foreach (@file('/var/local/emhttp/var.ini') ?: [] as $line) { diff --git a/Plugin/unraid/include/monitor.php b/Plugin/unraid/include/monitor.php index e18f8ea..5577dcb 100644 --- a/Plugin/unraid/include/monitor.php +++ b/Plugin/unraid/include/monitor.php @@ -275,9 +275,7 @@ function vv_watchdog_summary(): array { $loadRaw = @file_get_contents('/proc/loadavg') ?: '0'; $load1 = (float)explode(' ', trim($loadRaw))[0]; - $cpuTemp = null; - $sensorsOut = shell_exec("sensors 2>/dev/null | grep -E 'Core 0|Package id 0|Tdie|Tctl|CPU Temp' | grep -oE '[0-9]+\\.[0-9]+' | sort -n | tail -1") ?: ''; - if ($sensorsOut && is_numeric(trim($sensorsOut))) $cpuTemp = (int)round((float)trim($sensorsOut)); + $cpuTemp = vv_cpu_temp(); $zombies = (int)trim(shell_exec("ps -eo stat 2>/dev/null | grep -c '^Z'") ?: '0'); diff --git a/Watchdogs/stability_watchdog.sh b/Watchdogs/stability_watchdog.sh index 321e162..bf986ec 100755 --- a/Watchdogs/stability_watchdog.sh +++ b/Watchdogs/stability_watchdog.sh @@ -737,12 +737,11 @@ echo "━━━ $ICON_REBOOT Stability Watchdog — $(date '+%Y-%m-%d %H:%M:%S') # ── CPU temperature ─────────────────────────────────────────────────────────────────────── if [[ "$SYS_WATCHDOG_CHECK_CPU_TEMP" == true ]]; then - CPU_TEMP="" - if command -v sensors >/dev/null 2>&1; then - CPU_TEMP=$(sensors 2>/dev/null | \ - grep -i "Package id 0\|Tctl\|CPU Temp" | \ - awk '{print $NF}' | tr -d '+°C' | head -1) - fi + # Was a local sensors scrape taking the line's last field, which on a board that prints + # "(high = +80.0 C, crit = +100.0 C)" is the literal ")" — so this check has been reading + # nothing and never firing on HOST2. The adapter owns the parse now; both halves of + # Varaverk read the same number by construction. See platform_get_cpu_temp(). + CPU_TEMP=$(platform_get_cpu_temp 2>/dev/null || true) if [[ -n "$CPU_TEMP" ]]; then CPU_TEMP_INT=$(printf "%.0f" "$CPU_TEMP") TRIGGERED=false