Skip to content

Instantly share code, notes, and snippets.

@ifduyue
Created August 8, 2026 03:48
Show Gist options
  • Select an option

  • Save ifduyue/576be9eacf9f09eabd10508634240251 to your computer and use it in GitHub Desktop.

Select an option

Save ifduyue/576be9eacf9f09eabd10508634240251 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# Scan per-process swap usage (VmSwap from /proc).
# Usage: swap-scan.sh [-n N] [-a]
set -euo pipefail
TOP_N=20
SHOW_ALL=0
while getopts "n:ah" opt; do
case "$opt" in
n) TOP_N="$OPTARG" ;;
a) SHOW_ALL=1; TOP_N=0 ;;
h)
cat <<'USAGE'
Usage: swap-scan.sh [-n N] [-a]
-n N show top N processes (default 20)
-a show all processes with non-zero swap
-h help
USAGE
exit 0
;;
*) exit 2 ;;
esac
done
human() {
local kb=$1
if (( kb >= 1048576 )); then
awk -v k="$kb" 'BEGIN { printf "%.1f GiB", k/1048576 }'
elif (( kb >= 1024 )); then
awk -v k="$kb" 'BEGIN { printf "%.1f MiB", k/1024 }'
else
printf "%d kB" "$kb"
fi
}
tmpdir=
cleanup() { [[ -n "${tmpdir:-}" && -d "$tmpdir" ]] && rm -rf "$tmpdir"; }
trap cleanup EXIT
tmpdir=$(mktemp -d)
rows="$tmpdir/rows"
: >"$rows"
for status in /proc/[0-9]*/status; do
[[ -r "$status" ]] || continue
pid=${status#/proc/}
pid=${pid%/status}
# awk splits on whitespace, so leading spaces before numbers are fine
read -r name rss swap < <(awk '
/^Name:/ { name=$2 }
/^VmRSS:/ { rss=$2+0 }
/^VmSwap:/ { swap=$2+0 }
END { printf "%s %d %d\n", (name==""?"?":name), rss+0, swap+0 }
' "$status" 2>/dev/null) || continue
(( swap > 0 )) || continue
cmd=
if [[ -r "/proc/$pid/cmdline" ]]; then
cmd=$(tr '\0' ' ' <"/proc/$pid/cmdline" 2>/dev/null || true)
fi
# trim and collapse spaces; fallback to [comm]
cmd=$(printf '%s' "$cmd" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/[[:space:]]\{1,\}/ /g' | cut -c1-100)
[[ -z "$cmd" ]] && cmd="[$name]"
printf '%s\1%s\1%s\1%s\1%s\n' "$swap" "$rss" "$pid" "$name" "$cmd" >>"$rows"
done
total_swap_kb=0
count=0
if [[ -s "$rows" ]]; then
while IFS=$'\1' read -r swap _r _p _n _c; do
total_swap_kb=$((total_swap_kb + swap))
count=$((count + 1))
done <"$rows"
fi
echo "=== Swap summary ==="
if command -v free >/dev/null 2>&1; then
free -h | grep -iE 'Mem|Swap' || free -h
fi
if command -v swapon >/dev/null 2>&1; then
swapon --show 2>/dev/null || true
fi
echo "Processes with swap > 0: $count"
echo "Sum of process VmSwap: $(human "$total_swap_kb") (${total_swap_kb} kB)"
echo
echo "=== Top by process (PID) ==="
printf '%-8s %-10s %-10s %s\n' "PID" "SWAP" "RSS" "COMMAND"
if [[ ! -s "$rows" ]]; then
echo "(none)"
else
sort -t$'\1' -k1,1nr "$rows" | {
if (( SHOW_ALL )); then cat; else head -n "$TOP_N"; fi
} | while IFS=$'\1' read -r swap rss pid name cmd; do
printf '%-8s %-10s %-10s %s\n' "$pid" "$(human "$swap")" "$(human "$rss")" "$cmd"
done
fi
echo
echo "=== Aggregated by process name ==="
printf '%-10s %-6s %s\n' "SWAP" "COUNT" "NAME"
if [[ ! -s "$rows" ]]; then
echo "(none)"
else
awk -F'\1' '{ a[$4]+=$1; c[$4]++ }
END { for (n in a) printf "%d\1%d\1%s\n", a[n], c[n], n }
' "$rows" | sort -t$'\1' -k1,1nr | {
if (( SHOW_ALL )); then cat; else head -n "$TOP_N"; fi
} | while IFS=$'\1' read -r swap cnt name; do
printf '%-10s x%-5s %s\n' "$(human "$swap")" "$cnt" "$name"
done
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment