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, ... }: { config, lib, pkgs, ... }:
with lib;
let let
cfg = config.zramSwap; cfg = config.zramSwap;
devices = map (nr: "zram${toString nr}") (lib.range 0 (cfg.swapDevices - 1));
# 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 in
{ {
imports = [
(lib.mkRemovedOptionModule [ "zramSwap" "numDevices" ] "Using ZRAM devices as general purpose ephemeral block devices is no longer supported")
];
###### interface ###### interface
options = { options = {
zramSwap = { zramSwap = {
enable = mkOption { enable = lib.mkOption {
default = false; default = false;
type = types.bool; type = lib.types.bool;
description = lib.mdDoc '' description = lib.mdDoc ''
Enable in-memory compressed devices and swap space provided by the zram Enable in-memory compressed devices and swap space provided by the zram
kernel module. kernel module.
@ -49,29 +31,18 @@ in
''; '';
}; };
numDevices = mkOption { swapDevices = lib.mkOption {
default = 1; default = 0;
type = types.int;
description = lib.mdDoc ''
Number of zram devices to create. See also
`zramSwap.swapDevices`
'';
};
swapDevices = mkOption {
default = null;
example = 1; example = 1;
type = with types; nullOr int; type = lib.types.int;
description = lib.mdDoc '' description = lib.mdDoc ''
Number of zram devices to be used as swap. Must be Number of zram devices to be used as swap, recommended is 1.
`<= zramSwap.numDevices`.
Default is same as `zramSwap.numDevices`, recommended is 1.
''; '';
}; };
memoryPercent = mkOption { memoryPercent = lib.mkOption {
default = 50; default = 50;
type = types.int; type = lib.types.int;
description = lib.mdDoc '' description = lib.mdDoc ''
Maximum total amount of memory that can be stored in the zram swap devices 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 (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; default = null;
type = with types; nullOr int; type = with lib.types; nullOr int;
description = lib.mdDoc '' description = lib.mdDoc ''
Maximum total amount of memory (in bytes) that can be stored in the zram Maximum total amount of memory (in bytes) that can be stored in the zram
swap devices. swap devices.
@ -90,9 +61,9 @@ in
''; '';
}; };
priority = mkOption { priority = lib.mkOption {
default = 5; default = 5;
type = types.int; type = lib.types.int;
description = lib.mdDoc '' description = lib.mdDoc ''
Priority of the zram swap devices. It should be a number higher than 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 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"; default = "zstd";
example = "lz4"; example = "lz4";
type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str; type = with lib.types; either (enum [ "lzo" "lz4" "zstd" ]) str;
description = lib.mdDoc '' description = lib.mdDoc ''
Compression algorithm. `lzo` has good compression, Compression algorithm. `lzo` has good compression,
but is slow. `lz4` has bad compression, but is fast. but is slow. `lz4` has bad compression, but is fast.
@ -116,9 +87,7 @@ in
}; };
config = mkIf cfg.enable { config = lib.mkIf cfg.enable {
inherit warnings;
system.requiredKernelConfig = with config.lib.kernelConfig; [ system.requiredKernelConfig = with config.lib.kernelConfig; [
(isModule "ZRAM") (isModule "ZRAM")
@ -128,78 +97,25 @@ in
# once in stage 2 boot, and again when the zram-reloader service starts. # once in stage 2 boot, and again when the zram-reloader service starts.
# boot.kernelModules = [ "zram" ]; # boot.kernelModules = [ "zram" ];
boot.extraModprobeConfig = '' systemd.packages = [ pkgs.zram-generator ];
options zram num_devices=${toString cfg.numDevices} systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap
'';
boot.kernelParams = ["zram.num_devices=${toString cfg.numDevices}"]; environment.etc."systemd/zram-generator.conf".source =
(pkgs.formats.ini { }).generate "zram-generator.conf" (lib.listToAttrs
services.udev.extraRules = '' (builtins.map
KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd" (dev: {
''; name = dev;
value =
systemd.services = let
let size = "${toString cfg.memoryPercent} / 100 * ram";
createZramInitService = dev: in
nameValuePair "zram-init-${dev}" { {
description = "Init swap on zram-based device ${dev}"; zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size;
after = [ "dev-${dev}.device" "zram-reloader.service" ]; compression-algorithm = cfg.algorithm;
requires = [ "dev-${dev}.device" "zram-reloader.service" ]; swap-priority = cfg.priority;
before = [ "dev-${dev}.swap" ]; };
requiredBy = [ "dev-${dev}.swap" ]; })
unitConfig.DefaultDependencies = false; # needed to prevent a cycle devices));
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;
}; };

View file

@ -1,18 +1,24 @@
import ./make-test-python.nix { import ./make-test-python.nix {
name = "zram-generator"; name = "zram-generator";
nodes.machine = { pkgs, ... }: { nodes.machine = { ... }: {
environment.etc."systemd/zram-generator.conf".text = '' zramSwap = {
[zram0] enable = true;
zram-size = ram / 2 priority = 10;
''; algorithm = "lz4";
systemd.packages = [ pkgs.zram-generator ]; swapDevices = 2;
systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap memoryPercent = 30;
memoryMax = 10 * 1024 * 1024;
};
}; };
testScript = '' testScript = ''
machine.wait_for_unit("systemd-zram-setup@zram0.service") machine.wait_for_unit("systemd-zram-setup@zram0.service")
assert "zram0" in machine.succeed("zramctl -n") machine.wait_for_unit("systemd-zram-setup@zram1.service")
assert "zram0" in machine.succeed("swapon --show --noheadings") 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
''; '';
} }