Skip to content

Instantly share code, notes, and snippets.

@Trucido
Created June 27, 2018 16:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Trucido/5076b792fa7a09b4a43e45cdadcb2304 to your computer and use it in GitHub Desktop.
Save Trucido/5076b792fa7a09b4a43e45cdadcb2304 to your computer and use it in GitHub Desktop.
#!/bin/sh
# -*- mode:shell-script sh-basic-offset:4 indent-tabs-mode:nil -*-
########################################################################################################################
# zswap.sh - Activate and tune Zswap compressed cache for swap pages.
#
# Enables/Renables/tunes zswap without the need for initramfs/dracut/modprobe/modules-load hackery.
#
# Author : <REDACTED>
# Copyright: 2018 <REDACTED> <########@####.com>
#
########################################################################################################################
# Basic support for the Linux Standard Base Specification 3.1.0
### BEGIN INIT INFO
# Provides: zswap
# Required-Start:
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Zswap
# Description: Activate and tune Zswap compressed cache for swap pages
### END INIT INFO
########################################################################################################################
# TODO: a lot...
# BUGS: probably a lot of typos.
# "FEATURES": Does not run under /bin/sh -> /bin/dash (shebang change to /bin/bash required and removal of set +o posix)
########################################################################################################################
set +o posix
if [ -z "$SHELLOPTS" ] || [[ "$SHELLOPTS" = *posix* ]]; then exit; fi
##
# Tunables
##
ZSWAP_COMPRESSOR="lz4hc" # Default: 20% if unset or invalid.
ZSWAP_SIZE="40" # Default: lz4 if unset or invalid.
# Set upstart $JOB for compatibility, and also for logger -t "${JOB}"
JOB="ZSWAP"
# shellcheck disable=SC2039
zswap__load_depmods() {
__load_depmods__()
{ ## Probably unnecessary, but modprobe compressor/zpool before setting sysfs params. #;
__load_depmods__load_compressor__()
{ ## Load optimal compressor and zpool modules if available and not already loaded #;
__load_compressor__()
{ ## $ZSWAP_COMPRESSOR || lz4 || lzo #;
local \
req_zswap_compressor \
kver 2>/dev/null || :
req_zswap_compressor="${ZSWAP_COMPRESSOR}" 2>/dev/null || :
kver="$(uname -r 2>/dev/null)" || :
__load_compressor__lz4hc()
{
echo lz4hc >/sys/module/zswap/parameters/compressor >/dev/null 2>&1 || true
if [[ "$(</sys/module/zswap/parameters/compressor)" = lz4hc &&
-e '/sys/module/lz4hc'
]] ||
[[ "$(</proc/modules)" = *'lz4hc '* ||
"$(</boot/config-"$kver")" = *'CONFIG_LZ4HC_COMPRESS=y'* ||
"$(zcat /proc/config.gz)" = *'CONFIG_LZ4HC_COMPRESS=y'*
]] 2>/dev/null;
then
return
else
if modprobe -q lz4hc; then
return
else
logger -t "${JOB}" "WARNING: lz4hc compressor unavailable."
return 1
fi
fi
}
__load_compressor__lz4()
{
echo lz4 >/sys/module/zswap/parameters/compressor >/dev/null 2>&1 || true
if [[ "$(</sys/module/zswap/parameters/compressor)" = lz4 &&
-e '/sys/module/lz4'
]] ||
[[ "$(</proc/modules)" = *'lz4 '* ||
"$(</boot/config-"$kver")" = *'CONFIG_LZ4_COMPRESS=y'* ||
"$(zcat /proc/config.gz)" = *'CONFIG_LZ4_COMPRESS=y'*
]] 2>/dev/null;
then
return
else
if modprobe -q lz4; then
return
else
logger -t "${JOB}" "WARNING: lz4 compressor unavailable."
return 1
fi
fi
}
__load_compressor__lzo()
{
echo lzo >/sys/module/zswap/parameters/compressor >/dev/null 2>&1 || true
if [[ "$(</sys/module/zswap/parameters/compressor)" = lzo &&
-e '/sys/module/lzo'
]] ||
[[ "$(</proc/modules)" = *'lzo '* ||
"$(</boot/config-"$kver")" = *'CONFIG_LZO_COMPRESS=y'* ||
"$(zcat /proc/config.gz)" = *'CONFIG_LZO_COMPRESS=y'*
]] 2>/dev/null;
then
return
else
if modprobe -q lzo; then
return
else ## Highly unlikely... #;
logger -t "${JOB}" "ERROR: lzo appears missing!"
return 1
fi
fi
}
case "$req_zswap_compressor" in
lz4hc)
__load_compressor__lz4hc ||
__load_compressor__lz4 ||
__load_compressor__lzo || return 1
;;
lz4)
__load_compressor__lz4 ||
__load_compressor__lzo || return 1
;;
lzo)
__load_compressor__lzo || return 1
;;
esac
return
}
__load_compressor__ || return 1
return
}
__load_depmods__load_compressor__ || return 1
return
}
__load_depmods__load_zpool__()
{ # shellcheck disable=SC2039 #;
__load_zpool__()
{
__load_zpool__z3fold__()
{
echo z3fold >/sys/module/zswap/parameters/zpool >/dev/null 2>&1 || true
if [[ "$(</sys/module/zswap/parameters/zpool)" = z3fold &&
-e '/sys/module/z3fold'
]] ||
[[ "$(</proc/modules)" = *'z3fold '* ||
"$(</boot/config-"$kver")" = *'CONFIG_Z3FOLD=y'* ||
"$(zcat /proc/config.gz)" = *'CONFIG_Z3FOLD=y'*
]] 2>/dev/null;
then
return
else
if modprobe -q z3fold; then
logger -t "${JOB}" "z3fold loaded."
return
else ## Not fatal (yet). #;
logger -t "${JOB}" "z3fold not available, falling back to zbud."
modprobe -q zbud >/dev/null 2>&1 || :
return 1
fi
fi
}
__load_zpool__zbud()
{
echo z3fold >/sys/module/zswap/parameters/zpool >/dev/null 2>&1 || true
if [[ "$(</sys/module/zswap/parameters/zpool)" = zbud &&
-e '/sys/module/zbud'
]] ||
[[ "$(</proc/modules)" = *'z3fold '* ||
"$(</boot/config-"$kver")" = *'CONFIG_ZBUD=y'* ||
"$(zcat /proc/config.gz)" = *'CONFIG_ZBUD=y'*
]] 2>/dev/null;
then
return
fi
modprobe -q zbud ## Else fallback to zbud, but be extra sure before we errror+exit. #;
echo zbud >/sys/module/zswap/parameters/zpool >/dev/null 2>&1 || true
if [[ "$(</sys/module/zswap/parameters/zpool)" = zbud &&
-e '/sys/module/zbud'
]] ||
[[ "$(</proc/modules)" = *'zbud '* ||
"$(zcat /proc/config.gz)" = *'CONFIG_ZBUD=y'* ||
"$(zcat /proc/config.gz)" = *'CONFIG_ZBUD=y'*
]] 2>/dev/null;
then
logger -t "${JOB}" "zbud loaded."
return
else
logger -t "${JOB}" "Error: zbud not available,"
return 1
fi
}
__load_zpool__z3fold || __load_zpool__zbud || return 1
return
}
__load_depmods__load_compressor || return 1
__load_depmods__load_zpool || return 1
return
}
__load_depmods__ || return 1
return
}
# shellcheck disable=SC2039
zswap__load_module() {
__load_module__()
{ ## Load zswap module with optimal zpool/compressor. #;
## Prefer z3fold over zbud while respecting value of $ZSWAP_COMPRESSOR #;
local req_zswap_compressor 2>/dev/null || :
req_zswap_compressor="${ZSWAP_COMPRESSOR}"
__load_module__z3fold_lz4hc() {
modprobe -q zswap zpool=z3fold compressor=lz4hc enabled=Y || return 1
return
}
__load_module__z3fold_lz4() {
modprobe -q zswap zpool=z3fold compressor=lz4 enabled=Y || return 1
return
}
__load_module__z3fold_lzo() {
modprobe -q zswap zpool=z3fold compressor=lzo enabled=Y || return 1
return
}
__load_module__zbud_lz4hc() {
modprobe -q zswap zpool=zbud compressor=lz4hc enabled=Y || return 1
return
}
__load_module__zbud_lz4() {
modprobe -q zswap zpool=zbud compressor=lz4 enabled=Y || return 1
return
}
__load_module__zbud_lzo() {
modprobe -q zswap zpool=zbud compressor=lzo enabled=Y || return 1
return
}
case "$req_zswap_compressor" in
lz4hc)
__load_module__z3fold_lz4hc && return
__load_module__zbud_lz4hc && return
__load_module__z3fold_lz4 && return
__load_module__zbud_lz4 && return
__load_module__z3fold_lzo && return
__load_module__zbud_lzo || return 1
;;
lz4)
__load_module__z3fold_lz4 && return
__load_module__zbud_lz4 && return
__load_module__z3fold_lzo && return
__load_module__zbud_lzo || return 1
;;
lzo)
__load_module__z3fold_lzo && return
__load_module__zbud_lzo || return 1
;;
esac
return
}
__load_module__ || return 1
return
}
# shellcheck disable=SC2039
zswap__set_param() {
__set_param__()
{ ## set sysfs parameters #;
# Abort early if sysfs interface is not available.
if [ ! -e "/sys/module/zswap/parameters" ]; then
logger -t "${JOB}" "Error: /sys/module/zswap/parameters doesn't exist!";
return 1
fi
local \
req_zswap_compressor \
max_pool_size 2>/dev/null || :
req_zswap_compressor="${ZSWAP_COMPRESSOR}"
max_pool_size="${ZSWAP_SIZE}"
#
# Note: These probably need a test at the end to make sure it was set correctly,
# but return codes from sysfs writes to zswap are usually fairly accurate.
# Note: A write to sysfs *should* autoload the module(s) even if not loaded,
# but if the preferred module fails, we attempt to load the fallback modules anyway.
# Note: `modprobe -q <module>` returns 0 if builtin or already loaded.
#
__set_param__set_comp__() # Compressors #;
{ ## Priority: $ZSWAP_COMPRESSOR={lz4hc?} || lz4 || lzo || fail #;
__set_comp__()
{
__set_comp__lz4hc()
{
if echo lz4hc >/sys/module/zswap/parameters/compressor
then
logger -t "${JOB}" "Compressor set to lz4hc"
return
else ## Failing here isn't fatal (yet) #;
logger -t "${JOB}" "Failed to set lz4hc compressor. Trying lz4 instead."
modprobe -q lz4 >/dev/null 2>&1
return 1
fi
}
__set_comp__lz4()
{
if echo lz4 >/sys/module/zswap/parameters/compressor
then
logger -t "${JOB}" "Compressor set to lz4"
return
else
logger -t "${JOB}" "Failed to set lz4 compressor. Trying lzo instead."
return 1
fi
}
__set_comp__lzo()
{
if echo lzo >/sys/module/zswap/parameters/compressor
then
logger -t "${JOB}" "Compressor set to lzo"
return
else # Fatal if all 3 return non-zero. #;
logger -t "${JOB}" "Failed to set lzo compressor, aborting."
return 1
fi
}
case "$req_zswap_compressor" in
lz4hc)
__set_comp__lz4hc ||
__set_comp__lz4 ||
__set_comp__lzo || return 1
;;
lz4)
__set_comp__lz4hc ||
__set_comp__lz4 || return 1
;;
lzo)
__set_comp__lzo || return 1
;;
esac
return
}
__set_comp__ || return 1
return
}
__set_param__set_zpool__()
{ ## Priority: z3fold || zbud || fail #;
__set_zpool__()
{
__set_zpool__z3fold()
{ ## Failing here isn't fatal (yet). #;
if echo z3fold >/sys/module/zswap/parameters/zpool
then
logger -t "${JOB}" "Zpool set to z3fold."
return
else
logger -t "${JOB}" "Failed to set Zpool to z3fold, trying zbud."
modprobe -q zbud >/dev/null 2>&1 || :
return 1
fi
}
__set_zpool__zbud()
{ ## Fatal if neither load. #;
if echo zbud >/sys/module/zswap/parameters/zpool
then
logger -t "${JOB}" "Zpool set to zbud."
return
else
logger -t "${JOB}" "Error: Failed to Zpool to zbud, aborting."
return 1
fi
}
__set_zpool__z3fold ||
__set_zpool__zbud || return 1
return
}
__set_zpool__ || return 1
return
}
__set_param__set_max_pool_size__()
{
__set_max_pool_size__()
{ ## $ZSWAP_SIZE || 20% #;
__set_max_pool_size__req_size()
{
if [ -n "$max_pool_size" ]
then
if echo "$max_pool_size" >/sys/module/zswap/parameters/max_pool_percent
then
if [[ "$(</sys/module/zswap/parameters/max_pool_percent)" = "$max_pool_size" ]]
then
logger -t "${JOB}" "max_pool_size set to ${max_pool_size}."
return
else
logger -t "${JOB}" "Failed to set max_pool_size to ${max_pool_size}, trying default 20%"
return 1
fi
fi
fi
}
__set_max_pool_size__default()
{ ## Else use fallback value of 20% #;
if echo 20 >/sys/module/zswap/parameters/max_pool_percent
then
if [[ "$(</sys/module/zswap/parameters/max_pool_percent)" = "20" ]]
then
logger -t "${JOB}" "max_pool_size set to 20%."
return
else ## Fatal if neither are successfully set #;
"${JOB}" "Failed to set max_pool_size, aborting."
return 1
fi
fi
}
__set_max_pool_size__req_size ||
__set_max_pool_size__default || return 1
return 0
}
__set_max_pool_size__ || return 1
return 0
}
__set_param__set_comp__ || return 1
__set_param__set_zpool__ || return 1
__set_param__set_max_pool_size__ || return 1
logger -t "${JOB}" "Zswap parameters successfully set.";
return
}
__set_param__ || return 1
return
}
# shellcheck disable=SC2039 #;
zswap__start() {
if [ "$EUID" != 0 ] || [ "$UID" != 0 ]; then
echo 'Error: Must be root to start/stop.' >&2
return 1
fi
__start__()
{
__start__reset_reload()
{
if [[ "$(</sys/module/zswap/parameters/enabled)" = "Y" ]]; then
echo N >/sys/module/zswap/parameters/enabled
fi
# If the cache is large, it may take time to decompress back to main memory,
# so try several times. It isn't fatal if this fails, however.
local tries=0
while [ ${tries} -le 6 ]; do ## wait up to 3 seconds
modprobe -rq zswap 2>/dev/null && break
: $(( tries += 1 ))
sleep 0.5
done
# (Re)load module with best available options (or requested options).
zswap__load_depmods || return 1
zswap__load_module || return 1
return
}
__start__reset_setparam()
{ ## Note: `gzip -cd /proc/config.gz | /bin/grep -sq 'CONFIG_ZSWAP=y'` is a tiny bit faster, #;
## but makes the code more unreadable. (zgrep is slower however).
if [[ -e '/sys/module/zswap' ||
"$(</proc/modules)" = *'zswap '* ||
"$(</boot/config-"$kver")" = *'CONFIG_ZSWAP=y'* ||
"$(zcat /proc/config.gz)" = *'CONFIG_ZSWAP=y'*
]] 2>/dev/null;
then ## just load compressor and zpool if builtin #;
zswap__load_depmods || return 1
if [[ "$(</sys/module/zswap/parameters/enabled)" = "Y" ]]; then
echo N >/sys/module/zswap/parameters/enabled
fi
# Set our sysfs parameters and enable zswap again.
zswap__set_param || return 1
echo Y >/sys/module/zswap/parameters/enabled || return 1
return
fi
}
if [[ "$(</proc/modules)" = *'zswap '* ]]; then
__start__reset_reload || return 1
fi
__start__reset_setparam || return 1
return
}
logger -t "${JOB}" "Starting Zswap compressed swap cache..."
__start__ || { logger -t "${JOB}" "Failed to start Zswap compressed swap cache"; return 1; }
logger -t "${JOB}" "Started Zswap compressed swap cache."
return
}
# shellcheck disable=SC2039
zswap__stop() {
if [ "$EUID" != 0 ] || [ "$UID" != 0 ]; then
echo 'Error: Must be root to start/stop' >&2
return 1
fi
logger -t "${JOB}" "Stopping Zswap compressed swap cache..."
if [[ "$(</sys/module/zswap/parameters/enabled)" = "Y" ]]; then
echo N >/sys/module/zswap/parameters/enabled
fi
if [[ "$(</proc/modules)" = *'zswap '* ]]; then
local tries=0
while [ ${tries} -le 10 ]; do ## wait up to 5 seconds to unload.
modprobe -rq zswap 2>/dev/null && break
: $(( tries += 1 ))
sleep 0.5
done
fi
if [[ ! -e /sys/module/zswap || "$(</proc/modules)" != *'zswap '* ]] ||
[[ "$(</sys/module/zswap/parameters/enabled)" = "N" ]] 2>/dev/null;
then
logger -t "${JOB}" "Stopped Zswap compressed swap cache."
return
else
logger -t "${JOB}" "Failed to stop Zswap compressed swap cache."
return 1
fi
}
# shellcheck disable=SC2035,SC2039
zswap__status() { ## STATUS: Spam various zswap settings. #;
# Show general swap info first.
printf '\n==== Swap block devs ==== \n\n'
if [ -x /sbin/swapon ]; then
/sbin/swapon 2>/dev/null
elif [ -x /usr/sbin/swapon ]; then
/usr/sbin/swapon 2>/dev/null
fi
local zdir fdir mdir 2>/dev/null || :
zdir="/sys/kernel/debug/zswap"
fdir="/sys/kernel/debug/frontswap"
mdir="/sys/module/zswap/parameters"
printf '\n==== Zswap Stats ==== \n\n'
# Spam Zswap module parameters
if [ -r "${mdir}" ]; then
printf '%s:\n' "${mdir}"
cd "${mdir}" || return 1
grep -s --color=auto '^' * || :
else
printf '\nZswap module not loaded.\n'
return
fi
# Spam Zswap/Frontswap debug stats
if [ -r "${zdir}" ] && [ -r "${fdir}" ]; then
printf '%s:\n' "${fdir}"
cd "${fdir}" || return 1
grep -s --color=auto '^' * || :
printf '%s:\n' "${zdir}"
cd "${zdir}" || return 1
grep -s --color=auto '^' * || :
else
if [ "$UID" = 0 ] || [ "$EUID" = 0 ]; then
printf '\nDebugfs not mounted. Zswap/frontswap stats unavailable.\n\n'
else
printf '\ncannot access '/sys/kernel/debug/zswap/': Permission denied\n'
printf 'cannot access '/sys/kernel/debug/frontswap/': Permission denied\n\n'
fi
fi
}
zswap__usage() { ## Usage & exit #;
cat <<EOF
Usage: $0 <start|stop|status>
Start, stop, or show the status of compressed swap cache.
EOF
exit "$1"
}
# shellcheck disable=SC2039
zswap__main()
{ # MAIN #;
set -e
[ -z "$ZSWAP_SIZE" ] && ZSWAP_SIZE="25"
[ -z "$ZSWAP_COMPRESSOR" ] && ZSWAP_COMPRESSOR="lz4"
if [ $# -lt 1 ]; then ## If we're called without any argument, just start it by default. #;
zswap__start || return 1
elif [ $# -gt 1 ]; then ## If we're called with more than one argument, return usage and exit. #;
zswap__usage 1
else
local cmd 2>/dev/null || :
cmd="$1"
shift
case "${cmd}" in ## If given argument, ensure it's one we can handle. #;
start) cmd="zswap__start" ;;
stop) cmd="zswap__stop" ;;
status) cmd="zswap__status" ;;
*) usage 1
;;
esac
${cmd} "$@" || return 1 ## Call the func directly if valid. #;
fi
return
}
zswap__main "$@" || exit 1
########################################################################################################################
#
# ex: ft=sh sw=4 ts=4 sts=4 ai et
# End of zswap
#
########################################################################################################################
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment