#!/bin/sh
# busy-check.sh [samples] [interval_s] [raw] -- what each radio's survey
# counters say about channel busy, band by band and window by window.
# Read-only.
#
# The fleet check reports busy/active only. This splits busy into what the
# radio says it was receiving (any network), receiving for its own network,
# and transmitting; whatever is left is energy it counted as busy without
# decoding a frame. Lifetime ratios come from the cumulative counters, so a
# short window can be compared with the radio's whole uptime.
#
# On an AP:   wget -qO- https://pre-alpha.henryhaller.com/busy-check.sh | sh -s 5 2
# From the rXg, site-wide or for chosen APs: busy-check.rb (same place).
# "raw" prints machine-readable lines for busy-check.rb:
#   L band busy rx own tx undecoded noise     lifetime, percent
#   W k band busy rx own tx undecoded noise   window k, percent
#   S stations                                associated stations, all radios

N=${1:-5}
I=${2:-2}
MODE=${3:-text}

# One line per band (2 = 2.4 GHz, 5 = 5 GHz), first in-use survey entry:
# band freq active busy rx own_rx tx noise. Counters a driver does not
# report print as -1.
snap() {
	for i in $(iw dev 2>/dev/null | awk '$1 == "Interface" {print $2}'); do
		iw dev "$i" survey dump 2>/dev/null
	done | awk '
	function keep() {
		if (u && f > 0) {
			b = (f < 3000) ? 2 : 5
			if (!(b in S)) S[b] = b " " f " " a " " bz " " r " " s " " t " " nz
		}
		u = 0; f = 0; a = bz = r = s = t = -1; nz = 0
	}
	BEGIN { a = bz = r = s = t = -1 }
	/^Survey data/ { keep() }
	/frequency:/ { f = $2 + 0; u = ($0 ~ /in use/) }
	/noise:/ { nz = $2 + 0 }
	/channel active time:/ { a = $4 + 0 }
	/channel busy time:/ { bz = $4 + 0 }
	/channel receive time:/ { r = $4 + 0 }
	/channel BSS receive time:/ { s = $5 + 0 }
	/channel transmit time:/ { t = $4 + 0 }
	END { keep(); for (b in S) print S[b] }'
}

# Percentages of active time from two snapshots (or one, against zero).
# Prints "band busy rx own tx undecoded noise" per band; -1 where a counter
# is missing.
ratios() {
	awk '
	function p(x, y) { return (x < 0 || y <= 0) ? -1 : sprintf("%.1f", 100 * x / y) }
	$1 == "@@" { ph = 1; next }
	!ph { A[$1] = $3; B[$1] = $4; R[$1] = $5; S[$1] = $6; T[$1] = $7; next }
	{
		da = $3 - A[$1]; db = $4 - B[$1]; dr = ($5 < 0) ? -1 : $5 - R[$1]
		ds = ($6 < 0) ? -1 : $6 - S[$1]; dt = ($7 < 0) ? -1 : $7 - T[$1]
		du = (dr < 0 || dt < 0) ? -1 : db - dr - dt
		if (da > 0) print $1, p(db, da), p(dr, da), p(ds, da), p(dt, da), p(du, da), $8
	}'
}

prev=$(snap)
if [ -z "$prev" ]; then
	echo "no in-use radio survey data here -- run this on an AP, not the rXg"
	echo "(from the rXg: bin/rails runner /tmp/busy-check.rb)"
	exit 1
fi
zero=$(echo "$prev" | awk '{print $1, $2, 0, 0, ($5 < 0 ? -1 : 0), ($6 < 0 ? -1 : 0), ($7 < 0 ? -1 : 0), $8}')
life=$(printf '%s\n@@\n%s\n' "$zero" "$prev" | ratios)

label() { [ "$1" = 2 ] && echo 2.4GHz || echo 5GHz; }
show() {
	printf '%-7s busy %5s%%  rx %5s%%  own-net rx %5s%%  tx %5s%%  undecoded %5s%%  noise %s dBm\n' \
		"$(label "$1")" "$2" "$3" "$4" "$5" "$6" "$7" | sed 's/ -1%/  -%/g'
}

if [ "$MODE" = raw ]; then
	echo "$life" | sed 's/^/L /'
else
	echo "lifetime (cumulative counters since the radio came up):"
	echo "$life" | sort | while read b x1 x2 x3 x4 x5 nz; do printf '  '; show "$b" "$x1" "$x2" "$x3" "$x4" "$x5" "$nz"; done
	echo
	echo "windows of ${I}s:"
fi
k=1
while [ "$k" -le "$N" ]; do
	sleep "$I"
	cur=$(snap)
	win=$(printf '%s\n@@\n%s\n' "$prev" "$cur" | ratios | sort)
	if [ "$MODE" = raw ]; then
		echo "$win" | sed "s/^/W $k /"
	else
		echo "$win" | while read b x1 x2 x3 x4 x5 nz; do printf '  #%-2d ' "$k"; show "$b" "$x1" "$x2" "$x3" "$x4" "$x5" "$nz"; done
	fi
	prev=$cur
	k=$((k + 1))
done
if [ "$MODE" = raw ]; then
	echo "S $(for i in $(iw dev 2>/dev/null | awk '$1 == "Interface" {print $2}'); do iw dev "$i" station dump 2>/dev/null; done | grep -c '^Station')"
fi
