From 3d2622108264a9d4f96b9bc2bf7013e6fbacc5b4 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Thu, 2 Feb 2023 11:28:12 +0800 Subject: [PATCH] nixos/zram: use zram-generator --- nixos/modules/config/zram.nix | 160 ++++++++------------------------- nixos/tests/zram-generator.nix | 24 +++-- 2 files changed, 53 insertions(+), 131 deletions(-) diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix index 87ac53a60b7..7b4639d8b1c 100644 --- a/nixos/modules/config/zram.nix +++ b/nixos/modules/config/zram.nix @@ -1,45 +1,27 @@ { config, lib, pkgs, ... }: -with lib; - let cfg = config.zramSwap; - - # 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. - '') - ]; + devices = map (nr: "zram${toString nr}") (lib.range 0 (cfg.swapDevices - 1)); in { + imports = [ + (lib.mkRemovedOptionModule [ "zramSwap" "numDevices" ] "Using ZRAM devices as general purpose ephemeral block devices is no longer supported") + ]; + ###### interface options = { zramSwap = { - enable = mkOption { + enable = lib.mkOption { default = false; - type = types.bool; + type = lib.types.bool; description = lib.mdDoc '' Enable in-memory compressed devices and swap space provided by the zram kernel module. @@ -49,29 +31,18 @@ in ''; }; - numDevices = mkOption { - default = 1; - type = types.int; - description = lib.mdDoc '' - Number of zram devices to create. See also - `zramSwap.swapDevices` - ''; - }; - - swapDevices = mkOption { - default = null; + swapDevices = lib.mkOption { + default = 0; example = 1; - type = with types; nullOr int; + type = lib.types.int; description = lib.mdDoc '' - Number of zram devices to be used as swap. Must be - `<= zramSwap.numDevices`. - Default is same as `zramSwap.numDevices`, recommended is 1. + Number of zram devices to be used as swap, recommended is 1. ''; }; - memoryPercent = mkOption { + memoryPercent = lib.mkOption { default = 50; - type = types.int; + type = lib.types.int; description = lib.mdDoc '' Maximum total amount of memory that can be stored in the zram swap devices (as a percentage of your total memory). Defaults to 1/2 of your total @@ -80,9 +51,9 @@ in ''; }; - memoryMax = mkOption { + memoryMax = lib.mkOption { default = null; - type = with types; nullOr int; + type = with lib.types; nullOr int; description = lib.mdDoc '' Maximum total amount of memory (in bytes) that can be stored in the zram swap devices. @@ -90,9 +61,9 @@ in ''; }; - priority = mkOption { + priority = lib.mkOption { default = 5; - type = types.int; + type = lib.types.int; description = lib.mdDoc '' Priority of the zram swap devices. It should be a number higher than the priority of your disk-based swap devices (so that the system will @@ -100,10 +71,10 @@ in ''; }; - algorithm = mkOption { + algorithm = lib.mkOption { default = "zstd"; example = "lz4"; - type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str; + type = with lib.types; either (enum [ "lzo" "lz4" "zstd" ]) str; description = lib.mdDoc '' Compression algorithm. `lzo` has good compression, but is slow. `lz4` has bad compression, but is fast. @@ -116,9 +87,7 @@ in }; - config = mkIf cfg.enable { - - inherit warnings; + config = lib.mkIf cfg.enable { system.requiredKernelConfig = with config.lib.kernelConfig; [ (isModule "ZRAM") @@ -128,78 +97,25 @@ in # once in stage 2 boot, and again when the zram-reloader service starts. # boot.kernelModules = [ "zram" ]; - boot.extraModprobeConfig = '' - options zram num_devices=${toString cfg.numDevices} - ''; + systemd.packages = [ pkgs.zram-generator ]; + systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap - boot.kernelParams = ["zram.num_devices=${toString cfg.numDevices}"]; - - services.udev.extraRules = '' - KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd" - ''; - - systemd.services = - let - createZramInitService = dev: - nameValuePair "zram-init-${dev}" { - description = "Init swap on zram-based device ${dev}"; - after = [ "dev-${dev}.device" "zram-reloader.service" ]; - 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; - ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/class/block/${dev}/reset'"; - }; - script = '' - set -euo pipefail - - # Calculate memory to use for zram - mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / { - value=int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024); - ${lib.optionalString (cfg.memoryMax != null) '' - memory_max=int(${toString cfg.memoryMax}/${toString devicesCount}); - if (value > memory_max) { value = memory_max } - ''} - print value - }' /proc/meminfo) - - ${pkgs.util-linux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev} - ${pkgs.util-linux}/sbin/mkswap /dev/${dev} - ''; - restartIfChanged = false; - }; - 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" ]; - unitConfig.DefaultDependencies = false; # needed to prevent a cycle - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStartPre = "-${modprobe} -r zram"; - ExecStart = "-${modprobe} zram"; - ExecStop = "-${modprobe} -r zram"; - }; - restartTriggers = [ - cfg.numDevices - cfg.algorithm - cfg.memoryPercent - ]; - restartIfChanged = true; - })]); - - swapDevices = - let - useZramSwap = dev: - { - device = "/dev/${dev}"; - priority = cfg.priority; - }; - in map useZramSwap devices; + environment.etc."systemd/zram-generator.conf".source = + (pkgs.formats.ini { }).generate "zram-generator.conf" (lib.listToAttrs + (builtins.map + (dev: { + name = dev; + value = + let + size = "${toString cfg.memoryPercent} / 100 * ram"; + in + { + zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size; + compression-algorithm = cfg.algorithm; + swap-priority = cfg.priority; + }; + }) + devices)); }; diff --git a/nixos/tests/zram-generator.nix b/nixos/tests/zram-generator.nix index affa081bcc3..3407361d282 100644 --- a/nixos/tests/zram-generator.nix +++ b/nixos/tests/zram-generator.nix @@ -1,18 +1,24 @@ import ./make-test-python.nix { name = "zram-generator"; - nodes.machine = { pkgs, ... }: { - environment.etc."systemd/zram-generator.conf".text = '' - [zram0] - zram-size = ram / 2 - ''; - systemd.packages = [ pkgs.zram-generator ]; - systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap + nodes.machine = { ... }: { + zramSwap = { + enable = true; + priority = 10; + algorithm = "lz4"; + swapDevices = 2; + memoryPercent = 30; + memoryMax = 10 * 1024 * 1024; + }; }; testScript = '' machine.wait_for_unit("systemd-zram-setup@zram0.service") - assert "zram0" in machine.succeed("zramctl -n") - assert "zram0" in machine.succeed("swapon --show --noheadings") + machine.wait_for_unit("systemd-zram-setup@zram1.service") + zram = machine.succeed("zramctl --noheadings --raw") + swap = machine.succeed("swapon --show --noheadings") + for i in range(2): + assert f"/dev/zram{i} lz4 10M" in zram + assert f"/dev/zram{i} partition 10M" in swap ''; }