at 23.05-pre 6.8 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.zramSwap; 8 9 # don't set swapDevices as mkDefault, so we can detect user had read our warning 10 # (see below) and made an action (or not) 11 devicesCount = if cfg.swapDevices != null then cfg.swapDevices else cfg.numDevices; 12 13 devices = map (nr: "zram${toString nr}") (range 0 (devicesCount - 1)); 14 15 modprobe = "${pkgs.kmod}/bin/modprobe"; 16 17 warnings = 18 assert cfg.swapDevices != null -> cfg.numDevices >= cfg.swapDevices; 19 flatten [ 20 (optional (cfg.numDevices > 1 && cfg.swapDevices == null) '' 21 Using several small zram devices as swap is no better than using one large. 22 Set either zramSwap.numDevices = 1 or explicitly set zramSwap.swapDevices. 23 24 Previously multiple zram devices were used to enable multithreaded 25 compression. Linux supports multithreaded compression for 1 device 26 since 3.15. See https://lkml.org/lkml/2014/2/28/404 for details. 27 '') 28 ]; 29 30in 31 32{ 33 34 ###### interface 35 36 options = { 37 38 zramSwap = { 39 40 enable = mkOption { 41 default = false; 42 type = types.bool; 43 description = lib.mdDoc '' 44 Enable in-memory compressed devices and swap space provided by the zram 45 kernel module. 46 See [ 47 https://www.kernel.org/doc/Documentation/blockdev/zram.txt 48 ](https://www.kernel.org/doc/Documentation/blockdev/zram.txt). 49 ''; 50 }; 51 52 numDevices = mkOption { 53 default = 1; 54 type = types.int; 55 description = lib.mdDoc '' 56 Number of zram devices to create. See also 57 `zramSwap.swapDevices` 58 ''; 59 }; 60 61 swapDevices = mkOption { 62 default = null; 63 example = 1; 64 type = with types; nullOr int; 65 description = lib.mdDoc '' 66 Number of zram devices to be used as swap. Must be 67 `<= zramSwap.numDevices`. 68 Default is same as `zramSwap.numDevices`, recommended is 1. 69 ''; 70 }; 71 72 memoryPercent = mkOption { 73 default = 50; 74 type = types.int; 75 description = lib.mdDoc '' 76 Maximum total amount of memory that can be stored in the zram swap devices 77 (as a percentage of your total memory). Defaults to 1/2 of your total 78 RAM. Run `zramctl` to check how good memory is compressed. 79 This doesn't define how much memory will be used by the zram swap devices. 80 ''; 81 }; 82 83 memoryMax = mkOption { 84 default = null; 85 type = with types; nullOr int; 86 description = lib.mdDoc '' 87 Maximum total amount of memory (in bytes) that can be stored in the zram 88 swap devices. 89 This doesn't define how much memory will be used by the zram swap devices. 90 ''; 91 }; 92 93 priority = mkOption { 94 default = 5; 95 type = types.int; 96 description = lib.mdDoc '' 97 Priority of the zram swap devices. It should be a number higher than 98 the priority of your disk-based swap devices (so that the system will 99 fill the zram swap devices before falling back to disk swap). 100 ''; 101 }; 102 103 algorithm = mkOption { 104 default = "zstd"; 105 example = "lz4"; 106 type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str; 107 description = lib.mdDoc '' 108 Compression algorithm. `lzo` has good compression, 109 but is slow. `lz4` has bad compression, but is fast. 110 `zstd` is both good compression and fast, but requires newer kernel. 111 You can check what other algorithms are supported by your zram device with 112 {command}`cat /sys/class/block/zram*/comp_algorithm` 113 ''; 114 }; 115 }; 116 117 }; 118 119 config = mkIf cfg.enable { 120 121 inherit warnings; 122 123 system.requiredKernelConfig = with config.lib.kernelConfig; [ 124 (isModule "ZRAM") 125 ]; 126 127 # Disabling this for the moment, as it would create and mkswap devices twice, 128 # once in stage 2 boot, and again when the zram-reloader service starts. 129 # boot.kernelModules = [ "zram" ]; 130 131 boot.extraModprobeConfig = '' 132 options zram num_devices=${toString cfg.numDevices} 133 ''; 134 135 services.udev.extraRules = '' 136 KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd" 137 ''; 138 139 systemd.services = 140 let 141 createZramInitService = dev: 142 nameValuePair "zram-init-${dev}" { 143 description = "Init swap on zram-based device ${dev}"; 144 after = [ "dev-${dev}.device" "zram-reloader.service" ]; 145 requires = [ "dev-${dev}.device" "zram-reloader.service" ]; 146 before = [ "dev-${dev}.swap" ]; 147 requiredBy = [ "dev-${dev}.swap" ]; 148 unitConfig.DefaultDependencies = false; # needed to prevent a cycle 149 serviceConfig = { 150 Type = "oneshot"; 151 RemainAfterExit = true; 152 ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/class/block/${dev}/reset'"; 153 }; 154 script = '' 155 set -euo pipefail 156 157 # Calculate memory to use for zram 158 mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / { 159 value=int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024); 160 ${lib.optionalString (cfg.memoryMax != null) '' 161 memory_max=int(${toString cfg.memoryMax}/${toString devicesCount}); 162 if (value > memory_max) { value = memory_max } 163 ''} 164 print value 165 }' /proc/meminfo) 166 167 ${pkgs.util-linux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev} 168 ${pkgs.util-linux}/sbin/mkswap /dev/${dev} 169 ''; 170 restartIfChanged = false; 171 }; 172 in listToAttrs ((map createZramInitService devices) ++ [(nameValuePair "zram-reloader" 173 { 174 description = "Reload zram kernel module when number of devices changes"; 175 wants = [ "systemd-udevd.service" ]; 176 after = [ "systemd-udevd.service" ]; 177 unitConfig.DefaultDependencies = false; # needed to prevent a cycle 178 serviceConfig = { 179 Type = "oneshot"; 180 RemainAfterExit = true; 181 ExecStartPre = "${modprobe} -r zram"; 182 ExecStart = "${modprobe} zram"; 183 ExecStop = "${modprobe} -r zram"; 184 }; 185 restartTriggers = [ 186 cfg.numDevices 187 cfg.algorithm 188 cfg.memoryPercent 189 ]; 190 restartIfChanged = true; 191 })]); 192 193 swapDevices = 194 let 195 useZramSwap = dev: 196 { 197 device = "/dev/${dev}"; 198 priority = cfg.priority; 199 }; 200 in map useZramSwap devices; 201 202 }; 203 204}