NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

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"