Repository: foundObjects/zram-swap Branch: master Commit: 205ea1ec5b16 Files: 7 Total size: 13.8 KB Directory structure: gitextract_qcf21njh/ ├── .gitignore ├── LICENSE ├── README.md ├── install.sh ├── service/ │ ├── zram-swap.config │ └── zram-swap.service └── zram-swap.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ zram-swap/ src/ pkg/ *.pkg.tar.* *.log.* *.log ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Scott B Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # zram-swap A simple zram swap script for modern systemd Linux https://github.com/foundObjects/zram-swap ### Why? I wrote zram-swap because I couldn't find a simple modern replacement for the Ubuntu `zram-config` package that included basic error handling, didn't make device sizing mistakes and kept user-facing configuration straightforward and easy to understand. ### Installation and Usage *Arch Linux:* Install from the AUR: `paru -S zram-swap-git` or directly from GitHub: ```sh mkdir zram-swap-git && cd zram-swap-git wget https://raw.githubusercontent.com/foundObjects/zram-swap/arch-packaging/PKGBUILD makepkg -Cci ``` You'll need to start and enable `zram-swap.service` after installation on Arch. Make any configuration changes to `/etc/default/zram-swap` first if desired, see below for details. *Others:* ```sh git clone https://github.com/foundObjects/zram-swap.git cd zram-swap && sudo ./install.sh ``` The install script starts the zram-swap.service automatically after installation and enables the systemd service during boot. The default allocation creates an lz4 zram device that should use around half of physical memory when completely full. I chose lz4 as the default to give low spec machines (systems that often see the greatest benefit from swap on zram) every performance edge I could. While lzo-rle is quite fast on modern high-performance hardware a machine like a Raspberry Pi or a low spec laptop appreciates every speed advantage I can give it. ### Configuration Edit `/etc/default/zram-swap` if you'd like to change the compression algorithm or swap allocation and then restart zram-swap with `systemctl restart zram-swap.service`. The configuration file is heavily commented and self-documenting. A very simple configuration that's expected to use roughly 2GB RAM might look something like: ```sh # override fractional calculations and specify a fixed swap size _zram_fixedsize="6G" # compression algorithm to employ (lzo, lz4, zstd, lzo-rle) _zram_algorithm="lzo-rle" ``` Remember that the ZRAM device size references uncompressed data, real memory utilization should be ~2-3x smaller than the zram device size due to compression. #### A quick note RE: compression algorithms: The default configuration using lz4 should work well for most people. lzo may provide slightly better RAM utilization at a cost of slightly more expensive decompression. zstd should provide better compression than lz\* and still be moderately fast on most machines. On very modern kernels and reasonably fast hardware the most balanced choice is probably lzo-rle. On low spec machines (ARM SBCs, ARM laptops, thin clients, etc) you'll probably want to stick with lz4. ### Debugging To view a script debug trace either start zram-swap.sh with `zram-swap.sh -x (start|stop)` or uncomment the debug flag in `/etc/default/zram-swap`: ```sh # setting _zram_swap_debugging to any non-zero value enables debugging # default: undefined _zram_swap_debugging="beep boop" ``` ### Compatibility Tested on Linux 4.4 through Linux 5.14. Requirements are minimal; Underneath the systemd service wrapper the swap setup script needs only a posix shell, `modprobe`, `zramctl` and very basic `awk` and `grep` support to function. It should work in pretty much any modern Linux environment. ================================================ FILE: install.sh ================================================ #!/bin/sh # source: https://github.com/foundObjects/zram-swap # shellcheck disable=SC2039,SC2162 #[ "$(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 # installer main body: _main() { # ensure $1 exists so 'set -u' doesn't error out { [ "$#" -eq "0" ] && set -- ""; } > /dev/null 2>&1 case "$1" in "--uninstall") # uninstall, requires root assert_root _uninstall ;; "--install" | "") # install dpkg hooks, requires root assert_root _install "$@" ;; *) # unknown flags, print usage and exit _usage ;; esac exit 0 } _install() { configdiff='' newconfig='' if systemctl -q is-active zram-swap.service; then echo "Stopping zram-swap service" systemctl stop zram-swap.service fi echo "Installing script and service ..." install -o root zram-swap.sh /usr/local/sbin/zram-swap.sh install -o root -m 0644 service/zram-swap.service /etc/systemd/system/zram-swap.service # rename & cleanup old version config file if [ -f /etc/default/zram-swap-service ]; then mv -f /etc/default/zram-swap-service /etc/default/zram-swap chown root:root /etc/default/zram-swap chmod 0644 /etc/default/zram-swap fi if [ -f /etc/default/zram-swap ]; then { set +e configdiff=$(diff -y /etc/default/zram-swap service/zram-swap.config) set -e } > /dev/null 2>&1 if [ -n "$configdiff" ]; then yn='' echo "Local configuration differs from packaged version" echo echo "Install package default configuration? Local config will be saved as /etc/default/zram-swap.oldconfig" while true; do echo "(I)nstall package default / (K)eep local configuration / View (D)iff" printf "[i/k/d]: " read yn case "$yn" in [Ii]*) echo "Installing package default ..." install -o root -m 0644 --backup --suffix=".oldconfig" service/zram-swap.config /etc/default/zram-swap newconfig='y' break ;; [Kk]*) break ;; [Dd]*) printf "%s\n\n" "$configdiff" ;; esac done fi else install -o root -m 0644 -b service/zram-swap.config /etc/default/zram-swap fi echo "Reloading systemd unit files and enabling boot-time service ..." systemctl daemon-reload systemctl enable zram-swap.service if [ -n "$newconfig" ]; then cat <<- HEREDOC Configuration file updated; old config saved as /etc/default/zram-swap.oldconfig Please review changes between configurations and then start the service with systemctl start zram-swap.service HEREDOC else echo "Starting zram-swap service ..." systemctl start zram-swap.service fi echo echo "zram-swap service installed successfully!" echo } _uninstall() { if systemctl -q is-active zram-swap.service; then echo "Stopping zram-swap service" systemctl stop zram-swap.service fi echo "Uninstalling script and systemd service." if [ -f /etc/systemd/system/zram-swap.service ]; then systemctl disable zram-swap.service || true rm -f /etc/systemd/system/zram-swap.service fi if [ -f /usr/local/sbin/zram-swap.sh ]; then rm -f /usr/local/sbin/zram-swap.sh fi echo "Reloading systemd unit files" systemctl daemon-reload echo "zram-swap service uninstalled; remove configuration /etc/default/zram-swap if desired" } assert_root() { [ "$(id -u)" -eq '0' ] || { echo "This action requires root." && exit 1; }; } _usage() { echo "Usage: $(basename "$0") (--install|--uninstall)"; } _main "$@" ================================================ FILE: service/zram-swap.config ================================================ # compression algorithm to employ (lzo, lz4, zstd, lzo-rle) # default: lz4 _zram_algorithm="lz4" # portion of system ram to use as zram swap (expression: "1/2", "2/3", "0.5", etc) # default: "1/2" _zram_fraction="1/2" # setting _zram_swap_debugging to any non-zero value enables debugging # default: undefined #_zram_swap_debugging="beep boop" # expected compression factor; set this by hand if your compression results are # drastically different from the estimates below # # Note: These are the defaults coded into /usr/local/sbin/zram-swap.sh; don't alter # these values, use the override variable '_comp_factor' below. # # defaults if otherwise unset: # lzo*|zstd) _comp_factor="3" ;; # expect 3:1 compression from lzo*, zstd # lz4) _comp_factor="2.5" ;; # expect 2.5:1 compression from lz4 # *) _comp_factor="2" ;; # default to 2:1 for everything else # #_comp_factor="2.5" # if set skip device size calculation and create a fixed-size swap device # (size, in MiB/GiB, eg: "250M" "500M" "1.5G" "2G" "6G" etc.) # # Note: this is the swap device size before compression, real memory use will # depend on compression results, a 2-3x reduction is typical # #_zram_fixedsize="2G" # vim:ft=sh:ts=2:sts=2:sw=2:et: ================================================ FILE: service/zram-swap.service ================================================ [Unit] Description=zram swap service Requires=systemd-modules-load.service Before=shutdown.target #After= [Service] Type=oneshot ExecStart=/usr/local/sbin/zram-swap.sh start ExecStop=/usr/local/sbin/zram-swap.sh stop RemainAfterExit=true [Install] WantedBy=sysinit.target #RequiredBy= # vim:ft=systemd ================================================ FILE: zram-swap.sh ================================================ #!/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 "$@"