fan-state (3528B)
1 #!/usr/bin/env bash 2 # fan-state — one tab-separated line describing fans/thermals/throttle-guard, 3 # for the daemon-sec.fans Omarchy bar widget + panel. Everything here is 4 # readable without root; polled every few seconds so it must stay cheap. 5 # 6 # TCTL \t GPU_C \t FAN1_RPM \t FAN1_PCT \t FAN2_RPM \t FAN2_PCT \t DRIVER \t 7 # CAP_MHZ \t HWMAX_MHZ \t PERSIST \t BOOST \t PROFILE \t AVG_MHZ \t MAX_MHZ \t 8 # BUSY_PCT \t THROTTLE \t CURVE \t PKG_W 9 # 10 # TCTL = CPU Tctl °C (k10temp) 11 # GPU_C = dGPU °C from the uniwill EC driver (empty if not loaded) 12 # FAN1/2 = CPU/GPU fan rpm + commanded duty % (uniwill; GPU fan is dead) 13 # DRIVER = 1 when the uniwill hwmon is present (fanfix fans loads it) 14 # CAP/HWMAX = cpu0 scaling_max_freq / cpuinfo_max_freq in MHz 15 # PERSIST = 1 when the fanfix tmpfiles cap survives reboots 16 # BOOST = global cpufreq boost flag (must stay 1 for power-profiles-daemon) 17 # AVG/MAX = instantaneous core clocks from /proc/cpuinfo 18 # BUSY_PCT = CPU utilisation since the PREVIOUS poll (stat snapshot in 19 # $XDG_RUNTIME_DIR — no sleep in the poll path) 20 # THROTTLE = 1 when the EC clamp is active right now (fanfix's rule: 21 # busy ≥ 25 % yet no core above 600 MHz) 22 # CURVE = 1 when the fanfix-fan.service curve daemon is running 23 # PKG_W = SoC package power in W (amdgpu) 24 set -o pipefail 25 export LC_ALL=C 26 27 TRIP_MHZ=600 28 BUSY_MIN=25 29 CONF=/etc/tmpfiles.d/99-cpu-freq-cap.conf 30 STATE_FILE=${XDG_RUNTIME_DIR:-/tmp}/daemon-sec.fans.stat 31 32 hwmon_by_name() { local h; for h in /sys/class/hwmon/hwmon*; do [ "$(cat "$h/name" 2>/dev/null)" = "$1" ] && { echo "$h"; return 0; }; done; return 1; } 33 rd() { cat "$1" 2>/dev/null || echo 0; } 34 35 tctl=0 36 h=$(hwmon_by_name k10temp) && tctl=$(( $(rd "$h/temp1_input") / 1000 )) 37 38 driver=0 gpu="" f1r="" f1p="" f2r="" f2p="" 39 if h=$(hwmon_by_name uniwill); then 40 driver=1 41 gpu=$(( $(rd "$h/temp2_input") / 1000 )) 42 f1r=$(rd "$h/fan1_input"); f1p=$(( $(rd "$h/pwm1") * 100 / 255 )) 43 f2r=$(rd "$h/fan2_input"); f2p=$(( $(rd "$h/pwm2") * 100 / 255 )) 44 fi 45 46 pkg="" 47 h=$(hwmon_by_name amdgpu) && pkg=$(awk "BEGIN{printf \"%.1f\", $(rd "$h/power1_input")/1e6}") 48 49 cap=$(( $(rd /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) / 1000 )) 50 hwmax=$(( $(rd /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq) / 1000 )) 51 persist=0; [ -e "$CONF" ] && persist=1 52 boost=$(rd /sys/devices/system/cpu/cpufreq/boost) 53 profile=$(powerprofilesctl get 2>/dev/null || echo "") 54 55 read -r avg mx <<<"$(grep MHz /proc/cpuinfo \ 56 | awk '{s+=$4; if($4>m)m=$4} END{if(NR)printf "%d %d", s/NR, m; else print "0 0"}')" 57 58 busy=0 59 cur=$(head -1 /proc/stat) 60 if [ -r "$STATE_FILE" ]; then 61 busy=$(awk -v a="$(cat "$STATE_FILE")" -v b="$cur" 'BEGIN { 62 na = split(a, pa, " "); nb = split(b, pb, " ") 63 for (i = 2; i <= 9 && i <= na && i <= nb; i++) { ta += pa[i]; tb += pb[i] } 64 ia = pa[5] + pa[6]; ib = pb[5] + pb[6] # idle + iowait 65 dt = tb - ta 66 printf "%d", (dt > 0) ? 100 - (ib - ia) * 100 / dt : 0 67 }') 68 fi 69 printf '%s\n' "$cur" > "$STATE_FILE" 70 71 throttle=0 72 [ "$busy" -ge "$BUSY_MIN" ] && [ "$mx" -gt 0 ] && [ "$mx" -lt "$TRIP_MHZ" ] && throttle=1 73 74 curve=0 75 systemctl is-active --quiet fanfix-fan.service 2>/dev/null && curve=1 76 77 printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ 78 "$tctl" "$gpu" "$f1r" "$f1p" "$f2r" "$f2p" "$driver" \ 79 "$cap" "$hwmax" "$persist" "$boost" "$profile" "$avg" "$mx" \ 80 "$busy" "$throttle" "$curve" "$pkg"