#!/bin/bash # purgeSwap v0.01 - run swapoff if enough available memory and monitor swap usage. # (C) 2023 Felix Hauri - felix@f-hauri.ch # Licensed under terms of GPL v3. www.gnu.org # # This script permit to monitor ``swapoff'' process. # Without argument this script print amount of swapped and available memory, # then offer a small timeout of 8 seconds before running "swapoff -a". # Once timeout done or if `-y` switch on command line, the show a progress bar # for displaying amount of swap freed. # If `swapoff` is alwready running, just monitor progress until no swap used. # # This script don't use getopts (yet), so only 1 parameter could be used! # # Usage $0 [-q|-y|-n] # -q quiet! Implie -y: Don't ask, and don't print anything # -y Yes, don't ask, run monitor and doit. # -n Don't do anything, just print if we CAN swap or NOT. # # Default variables, could be set by prendening script at run: # accurateTopSwapped=1 ./purgeSwap -y : ${stopTimeout:=8} \ ${refreshBarRate:=.02} \ ${refreshSwapedListRate:=1} \ ${accurateTopSwapped:=0} msgs=("NOT " "") declare -i avail swaped while read field val _; do case $field in MemAvailable:) avail=$val;; SwapTotal:) swaped=$val;; SwapFree:) swaped+=-$val;break;; esac done < /proc/meminfo if (( swaped == 0 )); then echo There is no swap used. exit 0 fi if [[ $1 == -q ]]; then # Be quiet, implie `-y` "Yes, doit". if (( avail > swaped )); then /usr/sbin/swapoff -a /usr/sbin/swapon -a fi exit $? fi txtsize() { # Convert integer into readable string, store result in $2 varname local i=$(($1>=1<<50?5:$1>=1<<40?4:$1>=1<<30?3:$1>=1<<20?2:$1>1023?1:0)) local a=(K M G T P) ((i>4?i+=-2:0)) && a=(${a[@]:2}) && set -- $(($1>>20)) $2 local r=00$((1000*$1/(1024**i))) printf -v $2 %.2f%s ${r::-3}.${r: -3} ${a[i]} } percentBar() { # Compute percent+bar from ( $1 * 100 / $2 ) store is $3 local _pB_percent _pB_lastChar _pB_blankString _pB_barString local -i _pB_totLen _pB_barLen _pB_blankLen _pB_prop _pB_strLen=${4:-80} _pB_prop=" $1 * 100000 / $2 " _pB_totLen=' 8 * _pB_strLen ' _pB_percent=00$_pB_prop printf -v _pB_percent %.2f "${_pB_percent::-3}.${_pB_percent: -3}" _pB_prop=" (10#${_pB_percent/.}>10000?10000:10#${_pB_percent/.})*_pB_totLen/10000" _pB_barLen=' _pB_prop%8 ' (( _pB_barLen )) && _pB_strLen+=-1 && printf -v _pB_lastChar '\\U258%X' $(( 16 - _pB_barLen )) || _pB_lastChar='' _pB_barLen=' _pB_prop / 8 ' _pB_blankLen=' _pB_strLen - _pB_barLen ' printf -v _pB_barString '%*s' "$_pB_barLen" '' printf -v _pB_barString '%b' "${_pB_barString// /\\U2588}$_pB_lastChar" printf -v _pB_blankString '%*s' "$_pB_blankLen" '' printf -v "${3:-renderedBar}" '%s %s%s' \ "$_pB_percent" "$_pB_barString" "$_pB_blankString" } displaySleep() { local -i refrBySeconds=50 _start=${EPOCHREALTIME/.} reqslp target \ crtslp crtp cols cpos dlen local strng percent prctbar tleft ostty="$(stty -g)" [[ $COLUMNS ]] && cols=${COLUMNS} || read -r cols < <(tput cols) refrBySeconds=' 1000000 / refrBySeconds ' printf -v strng %.6f $1 stty -icanon -echo printf '\E[6n' && IFS=\; read -sdR _ cpos stty "$ostty" dlen=${#strng}-1 cols=' cols - dlen - cpos -1 ' reqslp=10#${strng/.} target=reqslp+_start printf \\e7 for ((;${EPOCHREALTIME/.}0 )); do read -t $refreshBarRate -u $dummy _ # pseudo builtin sleep used=0 while read field val _; do case $field in SwapTotal:) used=$val;; SwapFree:) used+=-$val; break;; esac; done < /proc/meminfo crtp=${EPOCHREALTIME/.} etime=" $crtp - startTime" crtfrd=" tot - used > 0 ? tot - used : 1 " endat=" etime * tot / crtfrd + startTime " printf -v endat %.0f ${endat::-6}.${endat: -6} txtsize $crtfrd freed percentBar $crtfrd $tot bar $((cols-35-${#htot})) if (( crtp-lrftpids > refreshSwapedListRate )); then lrftpids=crtp topSwapped swappedStr printf '\e8\e[42;100;32;227m%s\e[0m %6.2f%% %8s/%s End:%(%a %T)T\n%s' \ "${bar#* }" ${bar// *} $freed $htot $endat "${swappedStr}" else printf \ '\e8\e[42;100;32;227m%s\e[0m %6.2f%% %8s/%s End:%(%a %T)T\e[%dB\r\e[32C' \ "${bar#* }" ${bar// *} $freed $htot $endat 12 fi done } topSwapped() { local -i total=0 cnt=${2:-10} local -a bypid=() bysize=() sizes=() local pid swaped cmd output="" local -n result=${1:-topSwapped} if ((accurateTopSwapped)); then while read -r pid swaped cmd; do rSwaped=$(($(sed -ne /dev/null ) 2>/dev/null else while read -r pid swaped cmd; do bysize[$swaped]+="${pid} " bypid[$pid]=${cmd} done< <( sed -ne' /^Name:/h; /^Pid:/H; /^VmSwap:/{ s/.* \([0-9]\+\) \+kB/\1/; /^0/!{H; x; s/^.*:\o11\(.*\)\n.*:\o11\(.*\)\n\(.*\)/\2 \3 \1/; p; }}'\ /proc/[1-9]*/status 2>/dev/null ) fi sizes=(${!bysize[@]}) for ((i=${#sizes[@]};i--;)); do read -ra pids <<<${bysize[sizes[i]]} for pid in ${pids[@]}; do total+=sizes[i] if ((cnt-->0)); then txtsize ${sizes[i]} swaped printf -v swaped ' - %-20s %8s\e[K\n' "${bypid[pid]}" "$swaped" output+=${swaped} fi done done txtsize $total swaped cnt=${#bypid[@]} printf -v swaped 'Total: %d pids %*s\e[K' "$cnt" $((19-${#cnt})) "$swaped" result="${output}${swaped}" } winSize() { cols=$(( $(tput cols) ));((cols))||cols=80 ;} trap winSize WINCH winSize txtsize $avail havail txtsize $swaped hswaped printf 'Avail: %s (%d) swaped: (%s) %d Can %sunswap!\n' \ "$havail" $avail "$hswaped" $swaped "${msgs[ avail > swaped ]}" [[ $1 == -n ]] && exit 0 if ((UID)); then printf 'Run this as \47root\47 to be able to run \e[3mswapoff\e[0m!\n' exit 0 fi if (( avail > swaped )); then if [[ $1 != -y ]] && printf 'Hit any key to stop now or "Y" to continue. Proceed in ' && displaySleep $stopTimeout chr && [[ ${chr//[yY]} ]]; then echo 'User abort.' exit 0 fi printf '\n\n\n\n\n\n\n\n\n\n\n\e[11A\e7' exec {dummy}<> <(:) # pseudo builtin sleep using read -t export startTime=${EPOCHREALTIME/.} tput civis printf -v refreshSwapedListRate '%.6f' $refreshSwapedListRate refreshSwapedListRate=$((10#${refreshSwapedListRate/.})) showProgress $swaped & monPid=$! trap 'tput cnorm;[[ -d /proc/$monPid ]] && kill $monPid;echo;exit' 0 1 2 3 9 15 /usr/sbin/swapoff -a || if (($?==32)) && sofpid=$(ps -C swapoff ho pid); then while [[ -d /proc/$sofpid ]]; do read -t .1 -u $dummy _ done [[ -d /proc/$monPid ]] && kill $monPid exit 0 else exit $? fi elapsedTime=00000$(( ${EPOCHREALTIME/.} - startTime )) [[ -d /proc/$monPid ]] && kill $monPid echo tput cnorm /usr/sbin/swapon -a elapsedMin=$(( 10#$elapsedTime / 60000000 )) elapsedSec=00000$(( 10#$elapsedTime % 60000000 )) printf 'Swap: %s purged in %d minutes and %.3f secs (%.4f").\n' \ "$hswaped" "$elapsedMin" ${elapsedSec::-6}.${elapsedSec: -6} \ ${elapsedTime::-6}.${elapsedTime: -6} fi