nixpkgs-suyu/nixos/modules/config/zram.nix
danbst 8d8a7210e4 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
```
2019-01-17 15:58:53 +02:00

187 lines
5.8 KiB
Nix

{ 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>&lt;= 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.
'';
};
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 = "lzo";
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.
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" ];
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: / {
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;
};
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;
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;
};
}