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><= 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}