Loading nixos/modules/config/zram.nix +39 −123 Original line number Diff line number Diff line { 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. Loading @@ -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 Loading @@ -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. Loading @@ -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 Loading @@ -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. Loading @@ -116,9 +87,7 @@ in }; config = mkIf cfg.enable { inherit warnings; config = lib.mkIf cfg.enable { system.requiredKernelConfig = with config.lib.kernelConfig; [ (isModule "ZRAM") Loading @@ -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} ''; 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; })]); systemd.packages = [ pkgs.zram-generator ]; systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap swapDevices = environment.etc."systemd/zram-generator.conf".source = (pkgs.formats.ini { }).generate "zram-generator.conf" (lib.listToAttrs (builtins.map (dev: { name = dev; value = let useZramSwap = dev: size = "${toString cfg.memoryPercent} / 100 * ram"; in { device = "/dev/${dev}"; priority = cfg.priority; zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size; compression-algorithm = cfg.algorithm; swap-priority = cfg.priority; }; in map useZramSwap devices; }) devices)); }; Loading nixos/tests/zram-generator.nix +15 −9 Original line number Diff line number Diff line 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 ''; } Loading
nixos/modules/config/zram.nix +39 −123 Original line number Diff line number Diff line { 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. Loading @@ -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 Loading @@ -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. Loading @@ -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 Loading @@ -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. Loading @@ -116,9 +87,7 @@ in }; config = mkIf cfg.enable { inherit warnings; config = lib.mkIf cfg.enable { system.requiredKernelConfig = with config.lib.kernelConfig; [ (isModule "ZRAM") Loading @@ -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} ''; 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; })]); systemd.packages = [ pkgs.zram-generator ]; systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap swapDevices = environment.etc."systemd/zram-generator.conf".source = (pkgs.formats.ini { }).generate "zram-generator.conf" (lib.listToAttrs (builtins.map (dev: { name = dev; value = let useZramSwap = dev: size = "${toString cfg.memoryPercent} / 100 * ram"; in { device = "/dev/${dev}"; priority = cfg.priority; zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size; compression-algorithm = cfg.algorithm; swap-priority = cfg.priority; }; in map useZramSwap devices; }) devices)); }; Loading
nixos/tests/zram-generator.nix +15 −9 Original line number Diff line number Diff line 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 ''; }