at 22.05-pre 6.7 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 = '' 44 Enable in-memory compressed devices and swap space provided by the zram 45 kernel module. 46 See <link xlink:href="https://www.kernel.org/doc/Documentation/blockdev/zram.txt"> 47 https://www.kernel.org/doc/Documentation/blockdev/zram.txt 48 </link>. 49 ''; 50 }; 51 52 numDevices = mkOption { 53 default = 1; 54 type = types.int; 55 description = '' 56 Number of zram devices to create. See also 57 <literal>zramSwap.swapDevices</literal> 58 ''; 59 }; 60 61 swapDevices = mkOption { 62 default = null; 63 example = 1; 64 type = with types; nullOr int; 65 description = '' 66 Number of zram devices to be used as swap. Must be 67 <literal>&lt;= zramSwap.numDevices</literal>. 68 Default is same as <literal>zramSwap.numDevices</literal>, recommended is 1. 69 ''; 70 }; 71 72 memoryPercent = mkOption { 73 default = 50; 74 type = types.int; 75 description = '' 76 Maximum amount of memory that can be used by the zram swap devices 77 (as a percentage of your total memory). Defaults to 1/2 of your total 78 RAM. Run <literal>zramctl</literal> to check how good memory is 79 compressed. 80 ''; 81 }; 82 83 memoryMax = mkOption { 84 default = null; 85 type = with types; nullOr int; 86 description = '' 87 Maximum total amount of memory (in bytes) that can be used by the zram 88 swap devices. 89 ''; 90 }; 91 92 priority = mkOption { 93 default = 5; 94 type = types.int; 95 description = '' 96 Priority of the zram swap devices. It should be a number higher than 97 the priority of your disk-based swap devices (so that the system will 98 fill the zram swap devices before falling back to disk swap). 99 ''; 100 }; 101 102 algorithm = mkOption { 103 default = "zstd"; 104 example = "lz4"; 105 type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str; 106 description = '' 107 Compression algorithm. <literal>lzo</literal> has good compression, 108 but is slow. <literal>lz4</literal> has bad compression, but is fast. 109 <literal>zstd</literal> is both good compression and fast, but requires newer kernel. 110 You can check what other algorithms are supported by your zram device with 111 <programlisting>cat /sys/class/block/zram*/comp_algorithm</programlisting> 112 ''; 113 }; 114 }; 115 116 }; 117 118 config = mkIf cfg.enable { 119 120 inherit warnings; 121 122 system.requiredKernelConfig = with config.lib.kernelConfig; [ 123 (isModule "ZRAM") 124 ]; 125 126 # Disabling this for the moment, as it would create and mkswap devices twice, 127 # once in stage 2 boot, and again when the zram-reloader service starts. 128 # boot.kernelModules = [ "zram" ]; 129 130 boot.extraModprobeConfig = '' 131 options zram num_devices=${toString cfg.numDevices} 132 ''; 133 134 services.udev.extraRules = '' 135 KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd" 136 ''; 137 138 systemd.services = 139 let 140 createZramInitService = dev: 141 nameValuePair "zram-init-${dev}" { 142 description = "Init swap on zram-based device ${dev}"; 143 after = [ "dev-${dev}.device" "zram-reloader.service" ]; 144 requires = [ "dev-${dev}.device" "zram-reloader.service" ]; 145 before = [ "dev-${dev}.swap" ]; 146 requiredBy = [ "dev-${dev}.swap" ]; 147 unitConfig.DefaultDependencies = false; # needed to prevent a cycle 148 serviceConfig = { 149 Type = "oneshot"; 150 RemainAfterExit = true; 151 ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/class/block/${dev}/reset'"; 152 }; 153 script = '' 154 set -euo pipefail 155 156 # Calculate memory to use for zram 157 mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / { 158 value=int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024); 159 ${lib.optionalString (cfg.memoryMax != null) '' 160 memory_max=int(${toString cfg.memoryMax}/${toString devicesCount}); 161 if (value > memory_max) { value = memory_max } 162 ''} 163 print value 164 }' /proc/meminfo) 165 166 ${pkgs.util-linux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev} 167 ${pkgs.util-linux}/sbin/mkswap /dev/${dev} 168 ''; 169 restartIfChanged = false; 170 }; 171 in listToAttrs ((map createZramInitService devices) ++ [(nameValuePair "zram-reloader" 172 { 173 description = "Reload zram kernel module when number of devices changes"; 174 wants = [ "systemd-udevd.service" ]; 175 after = [ "systemd-udevd.service" ]; 176 unitConfig.DefaultDependencies = false; # needed to prevent a cycle 177 serviceConfig = { 178 Type = "oneshot"; 179 RemainAfterExit = true; 180 ExecStartPre = "${modprobe} -r zram"; 181 ExecStart = "${modprobe} zram"; 182 ExecStop = "${modprobe} -r zram"; 183 }; 184 restartTriggers = [ 185 cfg.numDevices 186 cfg.algorithm 187 cfg.memoryPercent 188 ]; 189 restartIfChanged = true; 190 })]); 191 192 swapDevices = 193 let 194 useZramSwap = dev: 195 { 196 device = "/dev/${dev}"; 197 priority = cfg.priority; 198 }; 199 in map useZramSwap devices; 200 201 }; 202 203}