depot/third_party/nixpkgs/nixos/modules/config/zram.nix
Default email 13da32182d Project import generated by Copybara.
GitOrigin-RevId: a7855f2235a1876f97473a76151fec2afa02b287
2022-08-21 15:32:41 +02:00

203 lines
6.4 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 = lib.mdDoc ''
Enable in-memory compressed devices and swap space provided by the zram
kernel module.
See [
https://www.kernel.org/doc/Documentation/blockdev/zram.txt
](https://www.kernel.org/doc/Documentation/blockdev/zram.txt).
'';
};
numDevices = mkOption {
default = 1;
type = types.int;
description = lib.mdDoc ''
Number of zram devices to create. See also
`zramSwap.swapDevices`
'';
};
swapDevices = mkOption {
default = null;
example = 1;
type = with types; nullOr 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.
'';
};
memoryPercent = mkOption {
default = 50;
type = types.int;
description = lib.mdDoc ''
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 `zramctl` to check how good memory is
compressed.
'';
};
memoryMax = mkOption {
default = null;
type = with types; nullOr int;
description = lib.mdDoc ''
Maximum total amount of memory (in bytes) that can be used by the zram
swap devices.
'';
};
priority = mkOption {
default = 5;
type = 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
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 = lib.mdDoc ''
Compression algorithm. `lzo` has good compression,
but is slow. `lz4` has bad compression, but is fast.
`zstd` is both good compression and fast, but requires newer kernel.
You can check what other algorithms are supported by your zram device with
{command}`cat /sys/class/block/zram*/comp_algorithm`
'';
};
};
};
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;
};
}