Skip to content

Instantly share code, notes, and snippets.

@combatwombat
Last active August 19, 2026 22:46
Show Gist options
  • Select an option

  • Save combatwombat/141db9cf13cdded0e63377edfd62b1aa to your computer and use it in GitHub Desktop.

Select an option

Save combatwombat/141db9cf13cdded0e63377edfd62b1aa to your computer and use it in GitHub Desktop.
rtop - top for dummies
#!/usr/bin/env bash
# rtop — quick iStat-style CPU + memory overview
#
# Works on Linux (reads /proc) and macOS/BSD (sysctl/top/vm_stat).
# On macOS, stock bash is 3.2; watch-mode's single-key quit needs bash 4+
# (install via Homebrew for it), otherwise it falls back to Ctrl-C to quit.
#
# Usage:
# ./rtop.sh One-shot glance
# ./rtop.sh -w [secs] Watch mode, repaint roughly every [secs], default 2.
# On macOS add ~2s/frame, top needs two CPU samples.
# ./rtop.sh -h This help
#
# Put an alias in .bashrc/.bash_profile for easier usage:
# alias rtop="/path/to/rtop.sh"
case $1 in
-h|--help)
# print the header comment block (everything after the shebang, up to the code)
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "$0"
exit 0 ;;
esac
# coloured bar: $1=percent(0-100), $2=width
_bar() {
local pct=$1 width=${2:-20} filled i out='' color reset=$'\e[0m'
(( pct < 0 )) && pct=0; (( pct > 100 )) && pct=100
(( filled = (pct * width + 50) / 100 ))
if (( pct >= 90 )); then color=$'\e[31m' # red
elif (( pct >= 70 )); then color=$'\e[33m' # yellow
else color=$'\e[32m' # green
fi
for ((i = 0; i < width; i++)); do
(( i < filled )) && out+='█' || out+='░'
done
printf '%s%s%s' "$color" "$out" "$reset"
}
# GiB with one decimal, no bc (arg in kB)
_gib() { printf '%d.%d' $(( $1 / 1048576 )) $(( ($1 % 1048576) * 10 / 1048576 )); }
# online CPU count, portable
_ncpu() {
local n
n=$(nproc 2>/dev/null) \
|| n=$(sysctl -n hw.ncpu 2>/dev/null) \
|| n=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
[[ $n =~ ^[0-9]+$ ]] && echo "$n" || echo 1
}
# macOS "1234.50M" / "2.00G" -> kB (integer, keeps 2 decimals of the unit)
_unit_to_kb() {
local s=$1 int frac v
local unit=${s: -1} n=${s%?} # note: separate line; local expands RHS before assigning
[[ $n == *.* ]] && { int=${n%.*}; frac=${n#*.}; } || { int=$n; frac=0; }
frac=${frac}00; frac=${frac:0:2}
v=$(( 10#${int:-0} * 100 + 10#${frac:-0} ))
case $unit in
G) echo $(( v * 1048576 / 100 )) ;;
M) echo $(( v * 1024 / 100 )) ;;
K|k) echo $(( v / 100 )) ;;
*) echo 0 ;;
esac
}
# wait $1 seconds; watch mode overrides this to also listen for a quit key.
# returns 9 to signal "quit requested".
_wait() { sleep "$1"; }
# render one frame; $1 = CPU sample window in seconds (default 0.3)
_render() {
local window=${1:-0.3}
local cpu=0 load='?' cores total=0 avail memfree=0 buffers=0 cached=0
local memused=0 mempct=0 swtotal=0 swfree=0 swused=0 swpct=0
# ---- CPU + load ----
if [[ -r /proc/stat ]]; then
# Linux: sample /proc/stat twice over the window
read -r _ u1 n1 s1 i1 w1 irq1 sirq1 st1 _ < /proc/stat
local idle1=$(( i1 + w1 )) tot1=$(( u1 + n1 + s1 + i1 + w1 + irq1 + sirq1 + st1 ))
_wait "$window" || return 9 # 9 = quit key pressed during the sample window
read -r _ u2 n2 s2 i2 w2 irq2 sirq2 st2 _ < /proc/stat
local idle2=$(( i2 + w2 )) tot2=$(( u2 + n2 + s2 + i2 + w2 + irq2 + sirq2 + st2 ))
local dtot=$(( tot2 - tot1 )) didle=$(( idle2 - idle1 ))
(( dtot > 0 )) && cpu=$(( ( (dtot - didle) * 100 + dtot / 2 ) / dtot ))
read -r load _ < /proc/loadavg
else
# macOS/BSD: top -l 2 gives a real delta on its 2nd sample; parse idle%
local secs=${window%.*} line idle
[[ $secs =~ ^[0-9]+$ ]] && (( secs >= 1 )) || secs=1
line=$(top -l 2 -n 0 -s "$secs" 2>/dev/null | grep 'CPU usage' | tail -1)
idle=${line##*, }; idle=${idle%%[.% ]*} # "…, 90.62% idle" -> "90"
[[ $idle =~ ^[0-9]+$ ]] && cpu=$(( 100 - idle ))
load=$(sysctl -n vm.loadavg 2>/dev/null); load=${load#* }; load=${load%% *}
[[ -n $load ]] || load='?'
fi
cores=$(_ncpu)
# ---- memory + swap (kB throughout) ----
if [[ -r /proc/meminfo ]]; then
# "used" = total - MemAvailable (cache counts as free; right for a server)
while read -r key val _; do
case $key in
MemTotal:) total=$val ;;
MemAvailable:) avail=$val ;;
MemFree:) memfree=$val ;;
Buffers:) buffers=$val ;;
Cached:) cached=$val ;;
SwapTotal:) swtotal=$val ;;
SwapFree:) swfree=$val ;;
esac
done < /proc/meminfo
: "${avail:=$(( memfree + buffers + cached ))}" # pre-3.14 kernels
memused=$(( total - avail ))
(( swtotal > 0 )) && swused=$(( swtotal - swfree ))
else
# macOS: "used" = active + wired + compressed (Activity Monitor's number)
local pagesize active=0 wired=0 comp=0 line sw swt swu
pagesize=$(sysctl -n hw.pagesize 2>/dev/null); : "${pagesize:=4096}"
total=$(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1024 ))
while IFS= read -r line; do
case $line in
"Pages active:"*) active=${line##*:} ;;
"Pages wired down:"*) wired=${line##*:} ;;
"Pages occupied by compressor:"*) comp=${line##*:} ;;
esac
done < <(vm_stat 2>/dev/null)
active=${active//[!0-9]/}; wired=${wired//[!0-9]/}; comp=${comp//[!0-9]/}
memused=$(( (${active:-0} + ${wired:-0} + ${comp:-0}) * pagesize / 1024 ))
sw=$(sysctl -n vm.swapusage 2>/dev/null)
if [[ $sw == *total*used* ]]; then
swt=${sw#*total = }; swt=${swt%% *}
swu=${sw#*used = }; swu=${swu%% *}
swtotal=$(_unit_to_kb "$swt"); swused=$(_unit_to_kb "$swu")
fi
fi
(( total > 0 )) && mempct=$(( memused * 100 / total ))
printf 'CPU %s %3d%% load %s (%d cores)\n' "$(_bar "$cpu")" "$cpu" "$load" "$cores"
printf 'MEM %s %3d%% %s / %s GiB\n' \
"$(_bar "$mempct")" "$mempct" "$(_gib "$memused")" "$(_gib "$total")"
if (( swtotal > 0 )); then
swpct=$(( swused * 100 / swtotal ))
printf 'SWAP %s %3d%% %s / %s GiB\n' \
"$(_bar "$swpct")" "$swpct" "$(_gib "$swused")" "$(_gib "$swtotal")"
fi
}
# ---- watch mode ------------------------------------------------------------
if [[ $1 == -w || $1 == --watch ]]; then
interval=${2:-2}
hint='(Ctrl-C quits)'
stty_saved=$(stty -g 2>/dev/null)
cleanup() { [[ -n $stty_saved ]] && stty "$stty_saved" 2>/dev/null; printf '\e[?25h\n'; exit 0; }
trap cleanup INT TERM
# single-key 'q' quit needs bash 4+ (read -N / fractional -t); else Ctrl-C
if (( ${BASH_VERSINFO[0]:-0} >= 4 )); then
stty -echo -icanon min 0 time 0 2>/dev/null # unbuffered single-key reads
_wait() {
local key
read -rt "$1" -N 1 key 2>/dev/null && [[ $key == q || $key == Q ]] && return 9
return 0
}
hint='(q quits)'
fi
_paint() {
printf '\e[H\e[1m rtop \e[0m %s \e[2m%s\e[0m\n\n%s\n' \
"$(date '+%H:%M:%S')" "$hint" "$frame"
}
printf '\e[?25l\e[2J' # hide cursor, clear once
if [[ -r /proc/stat ]]; then
# Linux: the sample window spans the whole interval (q caught during it)
while true; do
frame=$(_render "$interval")
(( $? == 9 )) && break
_paint
done
else
# macOS/BSD: top does its own ~1s sample, then pace/listen for the interval
while true; do
frame=$(_render 1)
_paint
_wait "$interval" || break
done
fi
cleanup
else
_render
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment