8d8a7210e4
- 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 ```
187 lines
5.8 KiB
Nix
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><= 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;
|
|
|
|
};
|
|
|
|
}
|