├── .gitignore ├── LICENSE ├── README.md ├── install.sh ├── service ├── zram-swap.config └── zram-swap.service └── zram-swap.sh /.gitignore: -------------------------------------------------------------------------------- 1 | zram-swap/ 2 | src/ 3 | pkg/ 4 | *.pkg.tar.* 5 | *.log.* 6 | *.log 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Scott B 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zram-swap 2 | A simple zram swap script for modern systemd Linux 3 | 4 | https://github.com/foundObjects/zram-swap 5 | 6 | ### Why? 7 | 8 | I wrote zram-swap because I couldn't find a simple modern replacement for the Ubuntu 9 | `zram-config` package that included basic error handling, didn't make device sizing 10 | mistakes and kept user-facing configuration straightforward and easy to understand. 11 | 12 | ### Installation and Usage 13 | 14 | *Arch Linux:* 15 | 16 | Install from the AUR: `paru -S zram-swap-git` 17 | 18 | or directly from GitHub: 19 | 20 | ```sh 21 | mkdir zram-swap-git && cd zram-swap-git 22 | wget https://raw.githubusercontent.com/foundObjects/zram-swap/arch-packaging/PKGBUILD 23 | makepkg -Cci 24 | ``` 25 | 26 | You'll need to start and enable `zram-swap.service` after installation on Arch. 27 | Make any configuration changes to `/etc/default/zram-swap` first if desired, see below for details. 28 | 29 | *Others:* 30 | 31 | ```sh 32 | git clone https://github.com/foundObjects/zram-swap.git 33 | cd zram-swap && sudo ./install.sh 34 | ``` 35 | 36 | The install script starts the zram-swap.service automatically after installation 37 | and enables the systemd service during boot. The default allocation creates an lz4 38 | zram device that should use around half of physical memory when completely full. 39 | 40 | I chose lz4 as the default to give low spec machines (systems that often see 41 | the greatest benefit from swap on zram) every performance edge I could. 42 | While lzo-rle is quite fast on modern high-performance hardware a machine like a 43 | Raspberry Pi or a low spec laptop appreciates every speed advantage I can give it. 44 | 45 | ### Configuration 46 | 47 | Edit `/etc/default/zram-swap` if you'd like to change the compression algorithm or 48 | swap allocation and then restart zram-swap with `systemctl restart zram-swap.service`. 49 | The configuration file is heavily commented and self-documenting. 50 | 51 | A very simple configuration that's expected to use roughly 2GB RAM might look 52 | something like: 53 | 54 | ```sh 55 | # override fractional calculations and specify a fixed swap size 56 | _zram_fixedsize="6G" 57 | 58 | # compression algorithm to employ (lzo, lz4, zstd, lzo-rle) 59 | _zram_algorithm="lzo-rle" 60 | ``` 61 | 62 | Remember that the ZRAM device size references uncompressed data, real memory 63 | utilization should be ~2-3x smaller than the zram device size due to compression. 64 | 65 | #### A quick note RE: compression algorithms: 66 | 67 | The default configuration using lz4 should work well for most people. lzo may 68 | provide slightly better RAM utilization at a cost of slightly more expensive 69 | decompression. zstd should provide better compression than lz\* and still be 70 | moderately fast on most machines. On very modern kernels and reasonably fast 71 | hardware the most balanced choice is probably lzo-rle. On low spec machines 72 | (ARM SBCs, ARM laptops, thin clients, etc) you'll probably want to stick with 73 | lz4. 74 | 75 | ### Debugging 76 | 77 | To view a script debug trace either start zram-swap.sh with `zram-swap.sh -x (start|stop)` 78 | or uncomment the debug flag in `/etc/default/zram-swap`: 79 | 80 | ```sh 81 | # setting _zram_swap_debugging to any non-zero value enables debugging 82 | # default: undefined 83 | _zram_swap_debugging="beep boop" 84 | ``` 85 | 86 | ### Compatibility 87 | 88 | Tested on Linux 4.4 through Linux 5.14. 89 | 90 | Requirements are minimal; Underneath the systemd service wrapper the swap setup 91 | script needs only a posix shell, `modprobe`, `zramctl` and very basic `awk` and 92 | `grep` support to function. It should work in pretty much any modern Linux 93 | environment. 94 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # source: https://github.com/foundObjects/zram-swap 3 | # shellcheck disable=SC2039,SC2162 4 | 5 | #[ "$(id -u)" -eq '0' ] || { echo "This script requires root." && exit 1; } 6 | case "$(readlink /proc/$$/exe)" in */bash) set -euo pipefail ;; *) set -eu ;; esac 7 | 8 | # ensure a predictable environment 9 | export PATH=/usr/sbin:/usr/bin:/sbin:/bin 10 | \unalias -a 11 | 12 | # installer main body: 13 | _main() { 14 | # ensure $1 exists so 'set -u' doesn't error out 15 | { [ "$#" -eq "0" ] && set -- ""; } > /dev/null 2>&1 16 | 17 | case "$1" in 18 | "--uninstall") 19 | # uninstall, requires root 20 | assert_root 21 | _uninstall 22 | ;; 23 | "--install" | "") 24 | # install dpkg hooks, requires root 25 | assert_root 26 | _install "$@" 27 | ;; 28 | *) 29 | # unknown flags, print usage and exit 30 | _usage 31 | ;; 32 | esac 33 | exit 0 34 | } 35 | 36 | _install() { 37 | configdiff='' 38 | newconfig='' 39 | if systemctl -q is-active zram-swap.service; then 40 | echo "Stopping zram-swap service" 41 | systemctl stop zram-swap.service 42 | fi 43 | 44 | echo "Installing script and service ..." 45 | install -o root zram-swap.sh /usr/local/sbin/zram-swap.sh 46 | install -o root -m 0644 service/zram-swap.service /etc/systemd/system/zram-swap.service 47 | 48 | # rename & cleanup old version config file 49 | if [ -f /etc/default/zram-swap-service ]; then 50 | mv -f /etc/default/zram-swap-service /etc/default/zram-swap 51 | chown root:root /etc/default/zram-swap 52 | chmod 0644 /etc/default/zram-swap 53 | fi 54 | 55 | if [ -f /etc/default/zram-swap ]; then 56 | { 57 | set +e 58 | configdiff=$(diff -y /etc/default/zram-swap service/zram-swap.config) 59 | set -e 60 | } > /dev/null 2>&1 61 | if [ -n "$configdiff" ]; then 62 | yn='' 63 | echo "Local configuration differs from packaged version" 64 | echo 65 | echo "Install package default configuration? Local config will be saved as /etc/default/zram-swap.oldconfig" 66 | while true; do 67 | echo "(I)nstall package default / (K)eep local configuration / View (D)iff" 68 | printf "[i/k/d]: " 69 | read yn 70 | case "$yn" in 71 | [Ii]*) 72 | echo "Installing package default ..." 73 | install -o root -m 0644 --backup --suffix=".oldconfig" service/zram-swap.config /etc/default/zram-swap 74 | newconfig='y' 75 | break 76 | ;; 77 | [Kk]*) break ;; 78 | [Dd]*) printf "%s\n\n" "$configdiff" ;; 79 | esac 80 | done 81 | fi 82 | else 83 | install -o root -m 0644 -b service/zram-swap.config /etc/default/zram-swap 84 | fi 85 | 86 | echo "Reloading systemd unit files and enabling boot-time service ..." 87 | systemctl daemon-reload 88 | systemctl enable zram-swap.service 89 | 90 | if [ -n "$newconfig" ]; then 91 | cat <<- HEREDOC 92 | Configuration file updated; old config saved as /etc/default/zram-swap.oldconfig 93 | 94 | Please review changes between configurations and then start the service with 95 | systemctl start zram-swap.service 96 | HEREDOC 97 | else 98 | echo "Starting zram-swap service ..." 99 | systemctl start zram-swap.service 100 | fi 101 | 102 | echo 103 | echo "zram-swap service installed successfully!" 104 | echo 105 | } 106 | 107 | _uninstall() { 108 | if systemctl -q is-active zram-swap.service; then 109 | echo "Stopping zram-swap service" 110 | systemctl stop zram-swap.service 111 | fi 112 | 113 | echo "Uninstalling script and systemd service." 114 | if [ -f /etc/systemd/system/zram-swap.service ]; then 115 | systemctl disable zram-swap.service || true 116 | rm -f /etc/systemd/system/zram-swap.service 117 | fi 118 | if [ -f /usr/local/sbin/zram-swap.sh ]; then 119 | rm -f /usr/local/sbin/zram-swap.sh 120 | fi 121 | echo "Reloading systemd unit files" 122 | systemctl daemon-reload 123 | 124 | echo "zram-swap service uninstalled; remove configuration /etc/default/zram-swap if desired" 125 | } 126 | 127 | assert_root() { [ "$(id -u)" -eq '0' ] || { echo "This action requires root." && exit 1; }; } 128 | _usage() { echo "Usage: $(basename "$0") (--install|--uninstall)"; } 129 | 130 | _main "$@" 131 | -------------------------------------------------------------------------------- /service/zram-swap.config: -------------------------------------------------------------------------------- 1 | # compression algorithm to employ (lzo, lz4, zstd, lzo-rle) 2 | # default: lz4 3 | _zram_algorithm="lz4" 4 | 5 | # portion of system ram to use as zram swap (expression: "1/2", "2/3", "0.5", etc) 6 | # default: "1/2" 7 | _zram_fraction="1/2" 8 | 9 | # setting _zram_swap_debugging to any non-zero value enables debugging 10 | # default: undefined 11 | #_zram_swap_debugging="beep boop" 12 | 13 | # expected compression factor; set this by hand if your compression results are 14 | # drastically different from the estimates below 15 | # 16 | # Note: These are the defaults coded into /usr/local/sbin/zram-swap.sh; don't alter 17 | # these values, use the override variable '_comp_factor' below. 18 | # 19 | # defaults if otherwise unset: 20 | # lzo*|zstd) _comp_factor="3" ;; # expect 3:1 compression from lzo*, zstd 21 | # lz4) _comp_factor="2.5" ;; # expect 2.5:1 compression from lz4 22 | # *) _comp_factor="2" ;; # default to 2:1 for everything else 23 | # 24 | #_comp_factor="2.5" 25 | 26 | # if set skip device size calculation and create a fixed-size swap device 27 | # (size, in MiB/GiB, eg: "250M" "500M" "1.5G" "2G" "6G" etc.) 28 | # 29 | # Note: this is the swap device size before compression, real memory use will 30 | # depend on compression results, a 2-3x reduction is typical 31 | # 32 | #_zram_fixedsize="2G" 33 | 34 | # vim:ft=sh:ts=2:sts=2:sw=2:et: 35 | -------------------------------------------------------------------------------- /service/zram-swap.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=zram swap service 3 | Requires=systemd-modules-load.service 4 | Before=shutdown.target 5 | #After= 6 | 7 | [Service] 8 | Type=oneshot 9 | ExecStart=/usr/local/sbin/zram-swap.sh start 10 | ExecStop=/usr/local/sbin/zram-swap.sh stop 11 | RemainAfterExit=true 12 | 13 | [Install] 14 | WantedBy=sysinit.target 15 | #RequiredBy= 16 | 17 | # vim:ft=systemd 18 | -------------------------------------------------------------------------------- /zram-swap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # source: https://github.com/foundObjects/zram-swap 3 | # shellcheck disable=SC2013,SC2039,SC2064 4 | 5 | [ "$(id -u)" -eq '0' ] || { echo "This script requires root." && exit 1; } 6 | case "$(readlink /proc/$$/exe)" in */bash) set -euo pipefail ;; *) set -eu ;; esac 7 | 8 | # ensure a predictable environment 9 | export PATH=/usr/sbin:/usr/bin:/sbin:/bin 10 | \unalias -a 11 | 12 | # parse debug flag early so we can trace user configuration 13 | [ "$#" -gt "0" ] && [ "$1" = "-x" ] && shift && set -x 14 | 15 | # set sane defaults, see /etc/default/zram-swap for explanations 16 | _zram_fraction="1/2" 17 | _zram_algorithm="lz4" 18 | _comp_factor='' 19 | _zram_fixedsize='' 20 | _zram_swap_debug='' 21 | 22 | # load user config 23 | [ -f /etc/default/zram-swap ] && 24 | . /etc/default/zram-swap 25 | 26 | # support a debugging flag in the config file so people don't have to edit the systemd service 27 | # to enable debugging 28 | [ -n "$_zram_swap_debug" ] && set -x 29 | 30 | # set expected compression ratio based on algorithm -- we'll use this to 31 | # calculate how much uncompressed swap data we expect to fit into our 32 | # target ram allocation. skip if already set in user config 33 | if [ -z "$_comp_factor" ]; then 34 | case $_zram_algorithm in 35 | lzo* | zstd) _comp_factor="3" ;; 36 | lz4) _comp_factor="2.5" ;; 37 | *) _comp_factor="2" ;; 38 | esac 39 | fi 40 | 41 | # main script: 42 | _main() { 43 | if ! modprobe zram; then 44 | err "main: Failed to load zram module, exiting" 45 | return 1 46 | fi 47 | 48 | # make sure `set -u` doesn't cause 'case "$1"' to throw errors below 49 | { [ "$#" -eq "0" ] && set -- ""; } > /dev/null 2>&1 50 | 51 | case "$1" in 52 | "init" | "start") 53 | if grep -q zram /proc/swaps; then 54 | err "main: zram swap already in use, exiting" 55 | return 1 56 | fi 57 | _init 58 | ;; 59 | "end" | "stop") 60 | if ! grep -q zram /proc/swaps; then 61 | err "main: no zram swaps to cleanup, exiting" 62 | return 1 63 | fi 64 | _end 65 | ;; 66 | "restart") 67 | # TODO: stub for restart support 68 | echo "not supported yet" 69 | _usage 70 | exit 1 71 | ;; 72 | *) 73 | _usage 74 | exit 1 75 | ;; 76 | esac 77 | } 78 | 79 | # initialize swap 80 | _init() { 81 | if [ -n "$_zram_fixedsize" ]; then 82 | if ! _regex_match "$_zram_fixedsize" '^[[:digit:]]+(\.[[:digit:]]+)?(G|M)$'; then 83 | err "init: Invalid size '$_zram_fixedsize'. Format sizes like: 100M 250M 1.5G 2G etc." 84 | exit 1 85 | fi 86 | # Use user supplied zram size 87 | mem="$_zram_fixedsize" 88 | else 89 | # Calculate memory to use for zram 90 | totalmem=$(awk '/MemTotal/{print $2}' /proc/meminfo) 91 | mem=$(calc "$totalmem * $_comp_factor * $_zram_fraction * 1024") 92 | fi 93 | 94 | # NOTE: zramctl sometimes fails if we don't wait for the module to settle after loading 95 | # we'll retry a couple of times with slightly increasing delays before giving up 96 | _device='' 97 | for i in $(seq 3); do 98 | # sleep for "0.1 * $i" seconds rounded to 2 digits 99 | sleep "$(calc 2 "0.1 * $i")" 100 | _device=$(zramctl -f -s "$mem" -a "$_zram_algorithm") || true 101 | [ -b "$_device" ] && break 102 | done 103 | 104 | if [ -b "$_device" ]; then 105 | # cleanup the device if swap setup fails 106 | trap "_rem_zdev $_device" EXIT 107 | mkswap "$_device" 108 | swapon -d -p 15 "$_device" 109 | trap - EXIT 110 | return 0 111 | else 112 | err "init: Failed to initialize zram device" 113 | return 1 114 | fi 115 | } 116 | 117 | # end swapping and cleanup 118 | _end() { 119 | ret="0" 120 | for dev in $(awk '/zram/ {print $1}' /proc/swaps); do 121 | swapoff "$dev" 122 | if ! _rem_zdev "$dev"; then 123 | err "end: Failed to remove zram device $dev" 124 | ret=1 125 | fi 126 | done 127 | return "$ret" 128 | } 129 | 130 | # Remove zram device with retry 131 | _rem_zdev() { 132 | if [ ! -b "$1" ]; then 133 | err "rem_zdev: No zram device '$1' to remove" 134 | return 1 135 | fi 136 | for i in $(seq 3); do 137 | # sleep for "0.1 * $i" seconds rounded to 2 digits 138 | sleep "$(calc 2 "0.1 * $i")" 139 | zramctl -r "$1" || true 140 | [ -b "$1" ] || break 141 | done 142 | if [ -b "$1" ]; then 143 | err "rem_zdev: Couldn't remove zram device '$1' after 3 attempts" 144 | return 1 145 | fi 146 | return 0 147 | } 148 | 149 | # posix substitute for bash pattern matching [[ $foo =~ bar-pattern ]] 150 | # usage: _regex_match "$foo" "bar-pattern" 151 | _regex_match() { echo "$1" | grep -Eq -- "$2" > /dev/null 2>&1; } 152 | 153 | # calculate with variable precision 154 | # usage: calc (int; precision := 0) (str; expr to evaluate) 155 | calc() { 156 | _regex_match "$1" '^[[:digit:]]+$' && { n="$1" && shift; } || n=0 157 | LC_NUMERIC=C awk "BEGIN{printf \"%.${n}f\", $*}" 158 | } 159 | 160 | err() { echo "Err $*" >&2; } 161 | _usage() { echo "Usage: $(basename "$0") (start|stop)"; } 162 | 163 | _main "$@" 164 | --------------------------------------------------------------------------------