#!/bin/bash

# Get current swap usage for all running processes
# Adapted from Erik Ljungstrom's script:
# http://northernmost.org/blog/find-out-what-is-using-your-swap/
#
# Send comments to wonko@wonkology.org
#
# Version 0.1

# parse command line options
 key="-k 2"
long=
 min=0
while getopts ":hlm:nps" opt
do
	case $opt in
	h )
		cat <<- FNORD
		Show current swap usage of processes
		
		Usage: ${0##*/} [-hlnps] [-m min] [name...]
		
		Optional arguments are process names to filter.
		
		Options:
		  -h     show this help
		  -l     long format with whole command line
		  -m min show only swap usage greater than min (in K)
		  -n     sort by process name
		  -p     sort by PID (default)
		  -s     sort by size
		FNORD
		exit 0
		;;
	l )
		long=true
		;;
	m )
		min=$OPTARG
		;;
	n )
		key="-k 4"
		;;
	p )
		key="-k 2"
		;;
	s )
		key="-k 3"
		;;
	* )
		printf "Error: No such option '-%s'!\n" $OPTARG
		exit 1
		;;
	esac
done
shift $(( OPTIND-1 ))

# store arguments
args=( "$@" )

# wrapper routine to get data and hide total size in comment
# (original routine, but slower than getswap2)
getswap()
{
	total=0
	for dir in $( ls -d /proc/*/smaps | cut -f 3 -d / | grep -E '^[0-9]+$' )
	do
		 pid=${dir##*/}
		name=$( ps -p $pid -o comm --no-headers )
		 sum=0
		for swap in $( grep Swap /proc/$dir/smaps 2> /dev/null |
		               awk '{ print $2 }' )
		do
			(( sum += swap ))
		done
		if (( ${#args[@]} ))
		then
			found=
			for arg in "${args[@]}"
			do
				if [[ $name =~ $arg ]]
				then
					found=true
					break
				fi
			done
		else
			found=true
		fi
		if [[ $found ]] && (( sum > min ))
		then
			(( total += sum ))
			printf "PID: %5d  swap used: %6dk  process: %s\n" \
			       $pid $sum "$name"
		fi
	done
	echo "#$total"
}

# wrapper routine to get data and hide total size in comment
getswap2()
{
	total=0
	cd /proc
	IFS=$'\n'
	for pid in [0-9]*
	do
		cmdLine=( $( cat /proc/$pid/cmdline 2>/dev/null |
		             while read -d $'\0' str; do echo "$str"; done ) )
		[[ $long ]] &&
			cmd=${cmdLine[*]} ||
			cmd=${cmdLine[0]}
		if (( ${#args[@]} ))
		then
			found=
			for arg in "${args[@]}"
			do
				if [[ $cmd =~ $arg ]]
				then
					found=true
					break
				fi
			done
			[[ $found ]] || continue
		fi
		
		swap=$( awk 'BEGIN {total=0} /Swap/ {total+=$2} END {print total}' \
		        /proc/$pid/smaps 2> /dev/null )
		if (( swap > min ))
		then
			(( total += swap ))
			printf "PID: %5d  swap used: %6dk  process: %s\n" \
			       $pid $swap "$cmd"
		fi
	done
	echo "#$total"
}

# calculate and output data
printf "Calculating... "
str=$( getswap2 )
printf "\r"
echo "$str" | grep -v ^\# | sort -t : "$key"

# extract and output total swap size
total=$( echo "$str" )
total=${total##*\#}
printf "Overall swap used: %dM\n" $(( total / 1024 ))
