zram-swap/zram-swap.sh

164 lines
4.3 KiB
Bash
Executable File

#!/bin/sh
# source: https://github.com/foundObjects/zram-swap
# shellcheck disable=SC2013,SC2039,SC2064
[ "$(id -u)" -eq '0' ] || { echo "This script requires root." && exit 1; }
case "$(readlink /proc/$$/exe)" in */bash) set -euo pipefail ;; *) set -eu ;; esac
# ensure a predictable environment
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
\unalias -a
# parse debug flag early so we can trace user configuration
[ "$#" -gt "0" ] && [ "$1" = "-x" ] && shift && set -x
# set sane defaults, see /etc/default/zram-swap for explanations
_zram_fraction="1/2"
_zram_algorithm="lz4"
_comp_factor=''
_zram_fixedsize=''
_zram_swap_debug=''
# load user config
[ -f /etc/default/zram-swap ] &&
. /etc/default/zram-swap
# support a debugging flag in the config file so people don't have to edit the systemd service
# to enable debugging
[ -n "$_zram_swap_debug" ] && set -x
# set expected compression ratio based on algorithm -- we'll use this to
# calculate how much uncompressed swap data we expect to fit into our
# target ram allocation. skip if already set in user config
if [ -z "$_comp_factor" ]; then
case $_zram_algorithm in
lzo* | zstd) _comp_factor="3" ;;
lz4) _comp_factor="2.5" ;;
*) _comp_factor="2" ;;
esac
fi
# main script:
_main() {
if ! modprobe zram; then
err "main: Failed to load zram module, exiting"
return 1
fi
# make sure `set -u` doesn't cause 'case "$1"' to throw errors below
{ [ "$#" -eq "0" ] && set -- ""; } > /dev/null 2>&1
case "$1" in
"init" | "start")
if grep -q zram /proc/swaps; then
err "main: zram swap already in use, exiting"
return 1
fi
_init
;;
"end" | "stop")
if ! grep -q zram /proc/swaps; then
err "main: no zram swaps to cleanup, exiting"
return 1
fi
_end
;;
"restart")
# TODO: stub for restart support
echo "not supported yet"
_usage
exit 1
;;
*)
_usage
exit 1
;;
esac
}
# initialize swap
_init() {
if [ -n "$_zram_fixedsize" ]; then
if ! _regex_match "$_zram_fixedsize" '^[[:digit:]]+(\.[[:digit:]]+)?(G|M)$'; then
err "init: Invalid size '$_zram_fixedsize'. Format sizes like: 100M 250M 1.5G 2G etc."
exit 1
fi
# Use user supplied zram size
mem="$_zram_fixedsize"
else
# Calculate memory to use for zram
totalmem=$(awk '/MemTotal/{print $2}' /proc/meminfo)
mem=$(calc "$totalmem * $_comp_factor * $_zram_fraction * 1024")
fi
# NOTE: zramctl sometimes fails if we don't wait for the module to settle after loading
# we'll retry a couple of times with slightly increasing delays before giving up
_device=''
for i in $(seq 3); do
# sleep for "0.1 * $i" seconds rounded to 2 digits
sleep "$(calc 2 "0.1 * $i")"
_device=$(zramctl -f -s "$mem" -a "$_zram_algorithm") || true
[ -b "$_device" ] && break
done
if [ -b "$_device" ]; then
# cleanup the device if swap setup fails
trap "_rem_zdev $_device" EXIT
mkswap "$_device"
swapon -d -p 15 "$_device"
trap - EXIT
return 0
else
err "init: Failed to initialize zram device"
return 1
fi
}
# end swapping and cleanup
_end() {
ret="0"
for dev in $(awk '/zram/ {print $1}' /proc/swaps); do
swapoff "$dev"
if ! _rem_zdev "$dev"; then
err "end: Failed to remove zram device $dev"
ret=1
fi
done
return "$ret"
}
# Remove zram device with retry
_rem_zdev() {
if [ ! -b "$1" ]; then
err "rem_zdev: No zram device '$1' to remove"
return 1
fi
for i in $(seq 3); do
# sleep for "0.1 * $i" seconds rounded to 2 digits
sleep "$(calc 2 "0.1 * $i")"
zramctl -r "$1" || true
[ -b "$1" ] || break
done
if [ -b "$1" ]; then
err "rem_zdev: Couldn't remove zram device '$1' after 3 attempts"
return 1
fi
return 0
}
# posix substitute for bash pattern matching [[ $foo =~ bar-pattern ]]
# usage: _regex_match "$foo" "bar-pattern"
_regex_match() { echo "$1" | grep -Eq -- "$2" > /dev/null 2>&1; }
# calculate with variable precision
# usage: calc (int; precision := 0) (str; expr to evaluate)
calc() {
_regex_match "$1" '^[[:digit:]]+$' && { n="$1" && shift; } || n=0
LC_NUMERIC=C awk "BEGIN{printf \"%.${n}f\", $*}"
}
err() { echo "Err $*" >&2; }
_usage() { echo "Usage: $(basename "$0") (start|stop)"; }
_main "$@"