#!/bin/bash
# Validate both laptop fans spin and ramp under thermal load.
# Stresses CPU, samples fan RPM + core temp over time, then idles and watches ramp-down.
# Verdict: both fans must climb under load, stay in lockstep, and drop on idle.
# Usage: sudo ./fan_check.sh

set -u

rebind() {
  HWMON=$(echo /sys/devices/platform/dell_smm_hwmon/hwmon/hwmon*)
  PWM1="$HWMON/pwm1"; PWM2="$HWMON/pwm2"
  PWM1E="$HWMON/pwm1_enable"; PWM2E="$HWMON/pwm2_enable"
  FAN1="$HWMON/fan1_input"; FAN2="$HWMON/fan2_input"
}
rebind

if [ "$EUID" -ne 0 ]; then
  echo "needs root: sudo $0"
  exit 1
fi

NCPU=$(nproc)
STRESS_S=30
COOLDOWN_MAX_S=120     # cap on cooldown wait
COOL_TEMP=65           # °C — CPU must sit below this...
COOL_HOLD_S=10         # ...for this long before we trust the steady state
SAMPLE_DT=2

cpu_temp() {
  sensors coretemp-isa-0000 | awk '/Package id 0/{gsub(/[+°C]/,"",$4); print $4; exit}'
}

sample() {
  printf "  t=%2ds  fan1=%4s  fan2=%4s  Δ=%4s  cpu=%s°C  pwm=%s/%s\n" \
    "$1" "$(cat "$FAN1")" "$(cat "$FAN2")" \
    "$(( $(cat "$FAN1") - $(cat "$FAN2") ))" \
    "$(cpu_temp)" "$(cat "$PWM1")" "$(cat "$PWM2")"
}

# Background CPU stress. Prefer stress-ng, fall back to N busy-yes loops.
start_load() {
  if command -v stress-ng >/dev/null 2>&1; then
    stress-ng --cpu "$NCPU" --timeout "${STRESS_S}s" >/dev/null 2>&1 &
    LOAD_PID=$!
    LOAD_KIND=stress-ng
  else
    LOAD_KIND="yes×$NCPU"
    LOAD_PIDS=()
    for _ in $(seq "$NCPU"); do
      yes > /dev/null &
      LOAD_PIDS+=("$!")
    done
  fi
}

stop_load() {
  if [ "${LOAD_KIND:-}" = "stress-ng" ]; then
    wait "$LOAD_PID" 2>/dev/null || true
  else
    for p in "${LOAD_PIDS[@]:-}"; do kill "$p" 2>/dev/null || true; done
    wait 2>/dev/null || true
  fi
}

trap 'stop_load 2>/dev/null || true' EXIT INT TERM

echo "=== baseline (idle, 6s) ==="
for t in 0 2 4 6; do sample "$t"; sleep "$SAMPLE_DT"; done
BASE_F1=$(cat "$FAN1"); BASE_F2=$(cat "$FAN2"); BASE_T=$(cpu_temp)

echo
echo "=== LOAD ${STRESS_S}s ($NCPU cores via $(command -v stress-ng >/dev/null && echo stress-ng || echo 'yes×N')) ==="
start_load
PEAK_F1=$BASE_F1; PEAK_F2=$BASE_F2; PEAK_T=$BASE_T
for t in $(seq 0 "$SAMPLE_DT" "$STRESS_S"); do
  sample "$t"
  f1=$(cat "$FAN1"); f2=$(cat "$FAN2"); tt=$(cpu_temp)
  [ "$f1" -gt "$PEAK_F1" ] && PEAK_F1=$f1
  [ "$f2" -gt "$PEAK_F2" ] && PEAK_F2=$f2
  # tt is float ("68.0") — compare via awk
  PEAK_T=$(awk -v a="$PEAK_T" -v b="$tt" 'BEGIN{print (b>a)?b:a}')
  sleep "$SAMPLE_DT"
done
stop_load

echo
echo "=== COOLDOWN (wait until CPU < ${COOL_TEMP}°C for ${COOL_HOLD_S}s, max ${COOLDOWN_MAX_S}s) ==="
held=0; t=0; END_F1=0; END_F2=0; END_T=0
while [ "$t" -le "$COOLDOWN_MAX_S" ]; do
  sample "$t"
  tt=$(cpu_temp)
  below=$(awk -v a="$tt" -v b="$COOL_TEMP" 'BEGIN{print (a<b)?1:0}')
  if [ "$below" = "1" ]; then
    held=$(( held + SAMPLE_DT ))
  else
    held=0
  fi
  if [ "$held" -ge "$COOL_HOLD_S" ]; then
    echo "  [cooled] CPU sat below ${COOL_TEMP}°C for ${held}s — capturing end state"
    END_F1=$(cat "$FAN1"); END_F2=$(cat "$FAN2"); END_T="$tt"
    break
  fi
  sleep "$SAMPLE_DT"
  t=$(( t + SAMPLE_DT ))
done
if [ "$END_F1" = "0" ]; then
  echo "  [timeout] never settled below ${COOL_TEMP}°C — using last sample"
  END_F1=$(cat "$FAN1"); END_F2=$(cat "$FAN2"); END_T=$(cpu_temp)
  COOL_TIMEOUT=1
else
  COOL_TIMEOUT=0
fi

echo
echo "=== VERDICT ==="
printf "  baseline:  fan1=%s  fan2=%s  cpu=%s°C\n" "$BASE_F1" "$BASE_F2" "$BASE_T"
printf "  peak load: fan1=%s  fan2=%s  cpu=%s°C\n" "$PEAK_F1" "$PEAK_F2" "$PEAK_T"
printf "  post-cool: fan1=%s  fan2=%s  cpu=%s°C\n" "$END_F1" "$END_F2" "$END_T"

DELTA_F1=$(( PEAK_F1 - BASE_F1 ))
DELTA_F2=$(( PEAK_F2 - BASE_F2 ))
PEAK_DIFF=$(( PEAK_F1 - PEAK_F2 ))
[ "$PEAK_DIFF" -lt 0 ] && PEAK_DIFF=$(( -PEAK_DIFF ))

# Climb test: both fans must gain meaningfully (>=300 RPM) under load.
if [ "$DELTA_F1" -ge 300 ]; then echo "  [OK]  fan1 climbed +$DELTA_F1 RPM under load"
else echo "  [!!]  fan1 only +$DELTA_F1 RPM — weak response"; fi
if [ "$DELTA_F2" -ge 300 ]; then echo "  [OK]  fan2 climbed +$DELTA_F2 RPM under load"
else echo "  [!!]  fan2 only +$DELTA_F2 RPM — weak response"; fi

# Lockstep: peak RPMs should match within ~150 RPM.
if [ "$PEAK_DIFF" -le 150 ]; then echo "  [OK]  fans in lockstep (peak Δ=$PEAK_DIFF RPM)"
else echo "  [!!]  fans diverged at peak (Δ=$PEAK_DIFF RPM) — one may be obstructed"; fi

# Ramp-down: only meaningful if we reached the cooled steady state.
if [ "$COOL_TIMEOUT" = "1" ]; then
  echo "  [skip] ramp-down check (CPU never settled below ${COOL_TEMP}°C in ${COOLDOWN_MAX_S}s — try again with no background load)"
else
  [ "$END_F1" -lt "$PEAK_F1" ] && echo "  [OK]  fan1 ramped down ($PEAK_F1 -> $END_F1)" \
                                || echo "  [!!]  fan1 did not ramp down ($PEAK_F1 -> $END_F1)"
  [ "$END_F2" -lt "$PEAK_F2" ] && echo "  [OK]  fan2 ramped down ($PEAK_F2 -> $END_F2)" \
                                || echo "  [!!]  fan2 did not ramp down ($PEAK_F2 -> $END_F2)"
fi

# Thermal recovery: end temp should be lower than peak (using awk for float).
RECOV=$(awk -v p="$PEAK_T" -v e="$END_T" 'BEGIN{print (e<p)?"yes":"no"}')
if [ "$RECOV" = "yes" ]; then echo "  [OK]  CPU cooled $PEAK_T°C -> $END_T°C"
else echo "  [!!]  CPU did not cool ($PEAK_T°C -> $END_T°C)"; fi

echo
echo "done."
