From 8d8a7210e480ac122d55a7d36879b8f41ea6fc80 Mon Sep 17 00:00:00 2001 From: danbst Date: Fri, 28 Dec 2018 00:00:48 +0200 Subject: [PATCH 1/2] zramSwap: allow configure compression algorithm + cleanups - add `zramSwap.algorithm` option, which allows to change compressor declaratively. zstd as default - add `zramSwap.swapDevices` option, which allows to define how many zram devices will be used as swap. Rest devices can be managed freely - simpler floating calculations - fix udev race condition - some documentation changes - replaced `/sys/block/zram*` handling with `zramctl`, because I had occasional "Device is busy" error (looks like zram has to be configured in predefined order) - added `memoryPercent` and `algorithm` as restart triggers. I think, it was a bug that changing `memoryPercent` in configuration wasn't applied immediately. - removed a bind to .swap device. While it looks natural (when swap device goes off, so should zram device), it wasn't implemented properly. This caused problems with swapon/swapoff: ``` $ cat /proc/swaps Filename Type Size Used Priority /dev/zram0 partition 8166024 0 -2 /var/swapfile file 5119996 5120 1 $ sudo swapoff -a $ sudo swapon -a swapon: /dev/zram0: read swap header failed $ cat /proc/swaps Filename Type Size Used Priority /var/swapfile file 5119996 0 1 ``` --- nixos/doc/manual/release-notes/rl-1903.xml | 15 ++++ nixos/modules/config/zram.nix | 81 ++++++++++++++++++---- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-1903.xml b/nixos/doc/manual/release-notes/rl-1903.xml index 89d9f48aedd3..da3b75cf6144 100644 --- a/nixos/doc/manual/release-notes/rl-1903.xml +++ b/nixos/doc/manual/release-notes/rl-1903.xml @@ -408,6 +408,21 @@ from nixpkgs due to the lack of maintainers. + + + It is possible now to uze ZRAM devices as general purpose ephemeral block devices, + not only as swap. Using more than 1 device as ZRAM swap is no longer recommended, + but is still possible by setting zramSwap.swapDevices explicitly. + + + Default algorithm for ZRAM swap was changed to zstd. + + + Changes to ZRAM algorithm are applied during nixos-rebuild switch, + so make sure you have enough swap space on disk to survive ZRAM device rebuild. Alternatively, + use nixos-rebuild boot; reboot. + + diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix index c1748812821e..f8ff926cd56d 100644 --- a/nixos/modules/config/zram.nix +++ b/nixos/modules/config/zram.nix @@ -6,10 +6,27 @@ let cfg = config.zramSwap; - devices = map (nr: "zram${toString nr}") (range 0 (cfg.numDevices - 1)); + # don't set swapDevices as mkDefault, so we can detect user had read our warning + # (see below) and made an action (or not) + devicesCount = if cfg.swapDevices != null then cfg.swapDevices else cfg.numDevices; + + devices = map (nr: "zram${toString nr}") (range 0 (devicesCount - 1)); modprobe = "${pkgs.kmod}/bin/modprobe"; + warnings = + assert cfg.swapDevices != null -> cfg.numDevices >= cfg.swapDevices; + flatten [ + (optional (cfg.numDevices > 1 && cfg.swapDevices == null) '' + Using several small zram devices as swap is no better than using one large. + Set either zramSwap.numDevices = 1 or explicitly set zramSwap.swapDevices. + + Previously multiple zram devices were used to enable multithreaded + compression. Linux supports multithreaded compression for 1 device + since 3.15. See https://lkml.org/lkml/2014/2/28/404 for details. + '') + ]; + in { @@ -24,9 +41,11 @@ in default = false; type = types.bool; description = '' - Enable in-memory compressed swap space provided by the zram kernel - module. - See https://www.kernel.org/doc/Documentation/blockdev/zram.txt + Enable in-memory compressed devices and swap space provided by the zram + kernel module. + See + https://www.kernel.org/doc/Documentation/blockdev/zram.txt + . ''; }; @@ -34,7 +53,19 @@ in default = 1; type = types.int; description = '' - Number of zram swap devices to create. + Number of zram devices to create. See also + zramSwap.swapDevices + ''; + }; + + swapDevices = mkOption { + default = null; + example = 1; + type = with types; nullOr int; + description = '' + Number of zram devices to be used as swap. Must be + <= zramSwap.numDevices. + Default is same as zramSwap.numDevices, recommended is 1. ''; }; @@ -44,7 +75,8 @@ in description = '' Maximum amount of memory that can be used by the zram swap devices (as a percentage of your total memory). Defaults to 1/2 of your total - RAM. + RAM. Run zramctl to check how good memory is + compressed. ''; }; @@ -58,12 +90,26 @@ in ''; }; + algorithm = mkOption { + default = "zstd"; + example = "lzo"; + type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str; + description = '' + Compression algorithm. lzo has good compression, + but is slow. lz4 has bad compression, but is fast. + zstd is both good compression and fast. + You can check what other algorithms are supported by your zram device with + cat /sys/class/block/zram*/comp_algorithm + ''; + }; }; }; config = mkIf cfg.enable { + inherit warnings; + system.requiredKernelConfig = with config.lib.kernelConfig; [ (isModule "ZRAM") ]; @@ -85,7 +131,6 @@ in createZramInitService = dev: nameValuePair "zram-init-${dev}" { description = "Init swap on zram-based device ${dev}"; - bindsTo = [ "dev-${dev}.swap" ]; after = [ "dev-${dev}.device" "zram-reloader.service" ]; requires = [ "dev-${dev}.device" "zram-reloader.service" ]; before = [ "dev-${dev}.swap" ]; @@ -96,14 +141,14 @@ in ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/class/block/${dev}/reset'"; }; script = '' - set -u - set -o pipefail - - # Calculate memory to use for zram - totalmem=$(${pkgs.gnugrep}/bin/grep 'MemTotal: ' /proc/meminfo | ${pkgs.gawk}/bin/awk '{print $2}') - mem=$(((totalmem * ${toString cfg.memoryPercent} / 100 / ${toString cfg.numDevices}) * 1024)) + set -euo pipefail - echo $mem > /sys/class/block/${dev}/disksize + # Calculate memory to use for zram + mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / { + print int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024) + }' /proc/meminfo) + + ${pkgs.utillinux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev} ${pkgs.utillinux}/sbin/mkswap /dev/${dev} ''; restartIfChanged = false; @@ -111,6 +156,8 @@ in in listToAttrs ((map createZramInitService devices) ++ [(nameValuePair "zram-reloader" { description = "Reload zram kernel module when number of devices changes"; + wants = [ "systemd-udevd.service" ]; + after = [ "systemd-udevd.service" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; @@ -118,7 +165,11 @@ in ExecStart = "${modprobe} zram"; ExecStop = "${modprobe} -r zram"; }; - restartTriggers = [ cfg.numDevices ]; + restartTriggers = [ + cfg.numDevices + cfg.algorithm + cfg.memoryPercent + ]; restartIfChanged = true; })]); From 34a764ce87340d0008cf5a06978e73ec7a794334 Mon Sep 17 00:00:00 2001 From: danbst Date: Thu, 17 Jan 2019 21:18:45 +0200 Subject: [PATCH 2/2] zramSwap: remove basic.target for zram devices This creates a dependency cycle when used with boot.tmpOnTmpfs: basic.target <- tmp.mount <- swap.target <- zram-init-dev0 <- basic.target This same fix is done already for tmp.mount Fixes https://github.com/NixOS/nixpkgs/issues/47474 --- nixos/modules/config/zram.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix index f8ff926cd56d..925d945c081e 100644 --- a/nixos/modules/config/zram.nix +++ b/nixos/modules/config/zram.nix @@ -135,6 +135,7 @@ in requires = [ "dev-${dev}.device" "zram-reloader.service" ]; before = [ "dev-${dev}.swap" ]; requiredBy = [ "dev-${dev}.swap" ]; + unitConfig.DefaultDependencies = false; # needed to prevent a cycle serviceConfig = { Type = "oneshot"; RemainAfterExit = true; @@ -158,6 +159,7 @@ in description = "Reload zram kernel module when number of devices changes"; wants = [ "systemd-udevd.service" ]; after = [ "systemd-udevd.service" ]; + unitConfig.DefaultDependencies = false; # needed to prevent a cycle serviceConfig = { Type = "oneshot"; RemainAfterExit = true;