Created
June 27, 2018 16:52
-
-
Save Trucido/7c4fd91b240eb1b2659ad816e29ed212 to your computer and use it in GitHub Desktop.
This file contains 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
#!/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