nixos/zram: use zram-generator

This commit is contained in:
Nick Cao 2023-02-02 11:28:12 +08:00
parent 73b9fb8afa
commit 3d26221082
No known key found for this signature in database
2 changed files with 53 additions and 131 deletions

View file

@ -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));
};

View file

@ -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
'';
}