diff options
Diffstat (limited to 'nixos/modules/config/zram.nix')
-rw-r--r-- | nixos/modules/config/zram.nix | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix new file mode 100644 index 00000000000..1f513b7e4da --- /dev/null +++ b/nixos/modules/config/zram.nix @@ -0,0 +1,203 @@ +{ 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. + '') + ]; + +in + +{ + + ###### interface + + options = { + + zramSwap = { + + enable = mkOption { + default = false; + type = types.bool; + description = '' + Enable in-memory compressed devices and swap space provided by the zram + kernel module. + See <link xlink:href="https://www.kernel.org/doc/Documentation/blockdev/zram.txt"> + https://www.kernel.org/doc/Documentation/blockdev/zram.txt + </link>. + ''; + }; + + numDevices = mkOption { + default = 1; + type = types.int; + description = '' + Number of zram devices to create. See also + <literal>zramSwap.swapDevices</literal> + ''; + }; + + swapDevices = mkOption { + default = null; + example = 1; + type = with types; nullOr int; + description = '' + Number of zram devices to be used as swap. Must be + <literal><= zramSwap.numDevices</literal>. + Default is same as <literal>zramSwap.numDevices</literal>, recommended is 1. + ''; + }; + + memoryPercent = mkOption { + default = 50; + type = types.int; + 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. Run <literal>zramctl</literal> to check how good memory is + compressed. + ''; + }; + + memoryMax = mkOption { + default = null; + type = with types; nullOr int; + description = '' + Maximum total amount of memory (in bytes) that can be used by the zram + swap devices. + ''; + }; + + priority = mkOption { + default = 5; + type = types.int; + description = '' + 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 + fill the zram swap devices before falling back to disk swap). + ''; + }; + + algorithm = mkOption { + default = "zstd"; + example = "lz4"; + type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str; + description = '' + Compression algorithm. <literal>lzo</literal> has good compression, + but is slow. <literal>lz4</literal> has bad compression, but is fast. + <literal>zstd</literal> is both good compression and fast, but requires newer kernel. + You can check what other algorithms are supported by your zram device with + <programlisting>cat /sys/class/block/zram*/comp_algorithm</programlisting> + ''; + }; + }; + + }; + + config = mkIf cfg.enable { + + inherit warnings; + + system.requiredKernelConfig = with config.lib.kernelConfig; [ + (isModule "ZRAM") + ]; + + # Disabling this for the moment, as it would create and mkswap devices twice, + # 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} + ''; + + 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; + + }; + +} |