Created
August 8, 2026 03:48
-
-
Save ifduyue/576be9eacf9f09eabd10508634240251 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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