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 = "${config.system.sbin.modprobe}/sbin/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 PATH=${pkgs.procps}/bin:${pkgs.gnugrep}/bin:${pkgs.gnused}/bin
103
104 # Calculate memory to use for zram
105 totalmem=$(free | grep -e "^Mem:" | sed -e 's/^Mem: *//' -e 's/ *.*//')
106 mem=$(((totalmem * ${toString cfg.memoryPercent} / 100 / ${toString cfg.numDevices}) * 1024))
107
108 echo $mem > /sys/class/block/${dev}/disksize
109 ${pkgs.utillinux}/sbin/mkswap /dev/${dev}
110 '';
111 restartIfChanged = false;
112 };
113 in listToAttrs ((map createZramInitService devices) ++ [(nameValuePair "zram-reloader"
114 {
115 description = "Reload zram kernel module when number of devices changes";
116 serviceConfig = {
117 Type = "oneshot";
118 RemainAfterExit = true;
119 ExecStartPre = "${modprobe} -r zram";
120 ExecStart = "${modprobe} zram";
121 ExecStop = "${modprobe} -r zram";
122 };
123 restartTriggers = [ cfg.numDevices ];
124 restartIfChanged = true;
125 })]);
126
127 swapDevices =
128 let
129 useZramSwap = dev:
130 {
131 device = "/dev/${dev}";
132 priority = cfg.priority;
133 };
134 in map useZramSwap devices;
135
136 };
137
138}