1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.zramSwap;
8
9 devices = map (nr: "zram${toString nr}") (range 0 (cfg.numDevices - 1));
10
11 modprobe = "${pkgs.kmod}/bin/modprobe";
12
13in
14
15{
16
17 ###### interface
18
19 options = {
20
21 zramSwap = {
22
23 enable = mkOption {
24 default = false;
25 type = types.bool;
26 description = ''
27 Enable in-memory compressed swap space provided by the zram kernel
28 module. It is recommended to enable only for kernel 3.14 or higher.
29 '';
30 };
31
32 numDevices = mkOption {
33 default = 4;
34 type = types.int;
35 description = ''
36 Number of zram swap devices to create. It should be equal to the
37 number of CPU cores your system has.
38 '';
39 };
40
41 memoryPercent = mkOption {
42 default = 50;
43 type = types.int;
44 description = ''
45 Maximum amount of memory that can be used by the zram swap devices
46 (as a percentage of your total memory). Defaults to 1/2 of your total
47 RAM.
48 '';
49 };
50
51 priority = mkOption {
52 default = 5;
53 type = types.int;
54 description = ''
55 Priority of the zram swap devices. It should be a number higher than
56 the priority of your disk-based swap devices (so that the system will
57 fill the zram swap devices before falling back to disk swap).
58 '';
59 };
60
61 };
62
63 };
64
65 config = mkIf cfg.enable {
66
67 system.requiredKernelConfig = with config.lib.kernelConfig; [
68 (isModule "ZRAM")
69 ];
70
71 # Disabling this for the moment, as it would create and mkswap devices twice,
72 # once in stage 2 boot, and again when the zram-reloader service starts.
73 # boot.kernelModules = [ "zram" ];
74
75 boot.extraModprobeConfig = ''
76 options zram num_devices=${toString cfg.numDevices}
77 '';
78
79 services.udev.extraRules = ''
80 KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd"
81 '';
82
83 systemd.services =
84 let
85 createZramInitService = dev:
86 nameValuePair "zram-init-${dev}" {
87 description = "Init swap on zram-based device ${dev}";
88 bindsTo = [ "dev-${dev}.swap" ];
89 after = [ "dev-${dev}.device" "zram-reloader.service" ];
90 requires = [ "dev-${dev}.device" "zram-reloader.service" ];
91 before = [ "dev-${dev}.swap" ];
92 requiredBy = [ "dev-${dev}.swap" ];
93 serviceConfig = {
94 Type = "oneshot";
95 RemainAfterExit = true;
96 ExecStop = "${pkgs.stdenv.shell} -c 'echo 1 > /sys/class/block/${dev}/reset'";
97 };
98 script = ''
99 set -u
100 set -o pipefail
101
102 # Calculate memory to use for zram
103 totalmem=$(${pkgs.gnugrep}/bin/grep 'MemTotal: ' /proc/meminfo | ${pkgs.gawk}/bin/awk '{print $2}')
104 mem=$(((totalmem * ${toString cfg.memoryPercent} / 100 / ${toString cfg.numDevices}) * 1024))
105
106 echo $mem > /sys/class/block/${dev}/disksize
107 ${pkgs.utillinux}/sbin/mkswap /dev/${dev}
108 '';
109 restartIfChanged = false;
110 };
111 in listToAttrs ((map createZramInitService devices) ++ [(nameValuePair "zram-reloader"
112 {
113 description = "Reload zram kernel module when number of devices changes";
114 serviceConfig = {
115 Type = "oneshot";
116 RemainAfterExit = true;
117 ExecStartPre = "${modprobe} -r zram";
118 ExecStart = "${modprobe} zram";
119 ExecStop = "${modprobe} -r zram";
120 };
121 restartTriggers = [ cfg.numDevices ];
122 restartIfChanged = true;
123 })]);
124
125 swapDevices =
126 let
127 useZramSwap = dev:
128 {
129 device = "/dev/${dev}";
130 priority = cfg.priority;
131 };
132 in map useZramSwap devices;
133
134 };
135
136}