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}