#!/usr/bin/env bash
# airmon - passive whole-chip activity monitor for the Apple M1 on Asahi.
# CPU: per-core utilisation (/proc/stat) + per-cluster frequency (cpufreq).
# GPU: utilisation (rail duty-cycle) + clock (pstate) + region temp (SMC).
# MEM: used/cache/swap. Nothing here dispatches GPU work — fully passive.
#   sudo ./airmon.sh            (~1s refresh, Ctrl-C to quit)
set -u
RAIL=/sys/kernel/debug/gpu_rail_mv
GPUMON=/sys/kernel/debug/gpumon
CLIENTS=/sys/kernel/debug/dri/206400000.gpu/clients
ONCE=${ONCE:-0}
GN=${GN:-25}; GAP=${GAP:-0.03}     # GPU rail samples / gap (also sets the refresh window)

mv2mhz(){ awk -v v="$1" 'BEGIN{n=split("400 0 596 396 631 528 681 720 771 924 862 1128 934 1278",a," ");
  if(v<450){print 0;exit} b=1e9;for(i=1;i<=n;i+=2){d=a[i]-v;if(d<0)d=-d;if(d<b){b=d;f=a[i+1]}}print f}'; }

# util bar of width $2 (default 8): solid blocks for the filled part, blank for the rest
bar(){ local p=$1 w=${2:-8} n i s=""; [ $w -lt 1 ]&&w=1; n=$(( p*w/100 )); [ $n -gt $w ]&&n=$w
  for((i=0;i<w;i++)); do [ $i -lt $n ]&&s+="█"||s+=" "; done; printf "%s" "$s"; }

snap_stat(){ awk '/^cpu[0-9]/{idle=$5+$6;tot=0;for(i=2;i<=NF;i++)tot+=$i;print $1,tot,idle}' /proc/stat; }
cl_mhz(){ printf "%d" $(( $(cat /sys/devices/system/cpu/cpufreq/policy$1/scaling_cur_freq 2>/dev/null)/1000 )); }
core_mhz(){ printf "%d" $(( $(cat /sys/devices/system/cpu/cpu$1/cpufreq/scaling_cur_freq 2>/dev/null)/1000 )); }

frame(){
  # bar width = full terminal width minus the "  cN [" ... "]  NNN%  NNNN MHz" furniture
  local COLS=${COLUMNS:-$(tput cols 2>/dev/null || echo 80)}; local W=$(( COLS - 24 )); [ $W -lt 8 ]&&W=8
  # full-width rule, same span as the meter rows
  local SEP; printf -v SEP '%*s' $(( COLS - 2 )) ''; SEP=${SEP// /─}
  # --- CPU baseline ---
  declare -A pT pI; while read -r c t i; do pT[$c]=$t; pI[$c]=$i; done < <(snap_stat)
  # --- GPU rail duty-cycle sampling (this loop also IS the refresh window) ---
  local busy=0 mx=0 g
  for((g=0;g<GN;g++)); do local mv; mv=$(cat "$RAIL" 2>/dev/null); mv=${mv:-0}
    if [ "$mv" -gt 450 ]; then busy=$((busy+1)); [ "$mv" -gt "$mx" ]&&mx=$mv; fi; sleep "$GAP"; done
  # --- CPU after ---
  declare -A util; while read -r c t i; do
    local dt=$(( t-${pT[$c]:-t} )) di=$(( i-${pI[$c]:-i} )) u=0
    [ $dt -gt 0 ] && u=$(( (dt-di)*100/dt )); util[$c]=$u; done < <(snap_stat)
  local gutil=$(( busy*100/GN )) gmhz; gmhz=$(mv2mhz "$mx")
  # --- temps from gpumon (best-effort SMC) ---
  local gtemp; gtemp=$(sudo cat "$GPUMON" 2>/dev/null | awk '/TEMP Ts1z/{printf "%.0f",$3}')
  local ctx; ctx=$(sudo cat "$CLIENTS" 2>/dev/null | awk 'NR>1&&$1!=""{c[$1]++}END{for(k in c)printf "%s(%d) ",k,c[k]}')
  # --- mem ---  (pct used total cache swap)
  local mpct mused mtot mcache mswap
  read mpct mused mtot mcache mswap < <(awk '/^MemTotal/{t=$2}/^MemAvailable/{a=$2}/^Cached/{c=$2}/^SwapTotal/{st=$2}/^SwapFree/{sf=$2}
    END{u=t-a; printf "%d %.1f %.1f %.1f %.0f", u*100/t, u/1048576, t/1048576, c/1048576, (st-sf)/1024}' /proc/meminfo)

  # temp bar scaled to a 100°C ceiling (M1 throttles ~100°C); value may be "?" if SMC unread
  local tnum=${gtemp%%.*}; [[ "$tnum" =~ ^[0-9]+$ ]] || tnum=0
  [ -t 1 ] && printf "\033[H\033[2J"
  printf "  M1 · Asahi · airmon          %s     (passive · no GPU work)\n" "$(date +%H:%M:%S)"
  printf "  %s\n" "$SEP"
  printf "  P-cluster Firestorm  %4d MHz\n" "$(cl_mhz 4)"
  for c in 4 5 6 7; do printf "   c%d [%s] %3d%% %4d MHz\n" "$c" "$(bar ${util[cpu$c]:-0} $W)" "${util[cpu$c]:-0}" "$(core_mhz $c)"; done
  printf "  E-cluster Icestorm   %4d MHz\n" "$(cl_mhz 0)"
  for c in 0 1 2 3; do printf "   c%d [%s] %3d%% %4d MHz\n" "$c" "$(bar ${util[cpu$c]:-0} $W)" "${util[cpu$c]:-0}" "$(core_mhz $c)"; done
  printf "  %s\n" "$SEP"
  local rpct=$(( mx*100/933 )); [ $rpct -gt 100 ]&&rpct=100   # rail vs max pstate (0.93 V = 1278 MHz)
  printf "  GPU  [%s] %3d%% %4d MHz\n" "$(bar "$gutil" $W)" "$gutil" "$gmhz"
  printf "  Vdd  [%s] %4d mV\n" "$(bar "$rpct" $W)" "$mx"
  printf "  T°   [%s] %3s°C\n" "$(bar "$tnum" $W)" "${gtemp:-?}"
  printf "  %-6s  contexts: %s\n" "$([ $gutil -gt 0 ]&&echo ACTIVE||echo idle)" "${ctx:-none}"
  printf "  %s\n" "$SEP"
  printf "  MEM  [%s] %3d%% %s GiB\n" "$(bar "$mpct" $W)" "$mpct" "$mused"
  printf "        cache %s GiB   swap %s MiB\n" "$mcache" "$mswap"
}

if [ "$ONCE" = 1 ]; then frame; else while true; do frame; done; fi
