grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / desktops / system76-scheduler.nix
blobb021ae6bfbfa9e7cc3bdec8ad1287679bb9a260c
1 { config, lib, pkgs, ... }:
3 let
4   cfg = config.services.system76-scheduler;
6   inherit (builtins) concatStringsSep map toString attrNames;
7   inherit (lib) boolToString types mkOption literalExpression optional mkIf mkMerge;
8   inherit (types) nullOr listOf bool int ints float str enum;
10   withDefaults = optionSpecs: defaults:
11     lib.genAttrs (attrNames optionSpecs) (name:
12       mkOption (optionSpecs.${name} // {
13         default = optionSpecs.${name}.default or defaults.${name} or null;
14       }));
16   latencyProfile = withDefaults {
17     latency = {
18       type = int;
19       description = "`sched_latency_ns`.";
20     };
21     nr-latency = {
22       type = int;
23       description = "`sched_nr_latency`.";
24     };
25     wakeup-granularity = {
26       type = float;
27       description = "`sched_wakeup_granularity_ns`.";
28     };
29     bandwidth-size = {
30       type = int;
31       description = "`sched_cfs_bandwidth_slice_us`.";
32     };
33     preempt = {
34       type = enum [ "none" "voluntary" "full" ];
35       description = "Preemption mode.";
36     };
37   };
38   schedulerProfile = withDefaults {
39     nice = {
40       type = nullOr (ints.between (-20) 19);
41       description = "Niceness.";
42     };
43     class = {
44       type = nullOr (enum [ "idle" "batch" "other" "rr" "fifo" ]);
45       example = literalExpression "\"batch\"";
46       description = "CPU scheduler class.";
47     };
48     prio = {
49       type = nullOr (ints.between 1 99);
50       example = literalExpression "49";
51       description = "CPU scheduler priority.";
52     };
53     ioClass = {
54       type = nullOr (enum [ "idle" "best-effort" "realtime" ]);
55       example = literalExpression "\"best-effort\"";
56       description = "IO scheduler class.";
57     };
58     ioPrio = {
59       type = nullOr (ints.between 0 7);
60       example = literalExpression "4";
61       description = "IO scheduler priority.";
62     };
63     matchers = {
64       type = nullOr (listOf str);
65       default = [];
66       example = literalExpression ''
67         [
68           "include cgroup=\"/user.slice/*.service\" parent=\"systemd\""
69           "emacs"
70         ]
71       '';
72       description = "Process matchers.";
73     };
74   };
76   cfsProfileToString = name: let
77     p = cfg.settings.cfsProfiles.${name};
78   in
79     "${name} latency=${toString p.latency} nr-latency=${toString p.nr-latency} wakeup-granularity=${toString p.wakeup-granularity} bandwidth-size=${toString p.bandwidth-size} preempt=\"${p.preempt}\"";
81   prioToString = class: prio: if prio == null then "\"${class}\"" else "(${class})${toString prio}";
83   schedulerProfileToString = name: a: indent:
84     concatStringsSep " "
85       (["${indent}${name}"]
86        ++ (optional (a.nice != null) "nice=${toString a.nice}")
87        ++ (optional (a.class != null) "sched=${prioToString a.class a.prio}")
88        ++ (optional (a.ioClass != null) "io=${prioToString a.ioClass a.ioPrio}")
89        ++ (optional ((builtins.length a.matchers) != 0) ("{\n${concatStringsSep "\n" (map (m: "  ${indent}${m}") a.matchers)}\n${indent}}")));
91 in {
92   options = {
93     services.system76-scheduler = {
94       enable = lib.mkEnableOption "system76-scheduler";
96       package = mkOption {
97         type = types.package;
98         default = config.boot.kernelPackages.system76-scheduler;
99         defaultText = literalExpression "config.boot.kernelPackages.system76-scheduler";
100         description = "Which System76-Scheduler package to use.";
101       };
103       useStockConfig = mkOption {
104         type = bool;
105         default = true;
106         description = ''
107           Use the (reasonable and featureful) stock configuration.
109           When this option is `true`, `services.system76-scheduler.settings`
110           are ignored.
111         '';
112       };
114       settings = {
115         cfsProfiles = {
116           enable = mkOption {
117             type = bool;
118             default = true;
119             description = "Tweak CFS latency parameters when going on/off battery";
120           };
122           default = latencyProfile {
123             latency = 6;
124             nr-latency = 8;
125             wakeup-granularity = 1.0;
126             bandwidth-size = 5;
127             preempt = "voluntary";
128           };
129           responsive = latencyProfile {
130             latency = 4;
131             nr-latency = 10;
132             wakeup-granularity = 0.5;
133             bandwidth-size = 3;
134             preempt = "full";
135           };
136         };
138         processScheduler = {
139           enable = mkOption {
140             type = bool;
141             default = true;
142             description = "Tweak scheduling of individual processes in real time.";
143           };
145           useExecsnoop = mkOption {
146             type = bool;
147             default = true;
148             description = "Use execsnoop (otherwise poll the precess list periodically).";
149           };
151           refreshInterval = mkOption {
152             type = int;
153             default = 60;
154             description = "Process list poll interval, in seconds";
155           };
157           foregroundBoost = {
158             enable = mkOption {
159               type = bool;
160               default = true;
161               description = ''
162                 Boost foreground process priorities.
164                 (And de-boost background ones).  Note that this option needs cooperation
165                 from the desktop environment to work.  On Gnome the client side is
166                 implemented by the "System76 Scheduler" shell extension.
167               '';
168             };
169             foreground = schedulerProfile {
170               nice = 0;
171               ioClass = "best-effort";
172               ioPrio = 0;
173             };
174             background = schedulerProfile {
175               nice = 6;
176               ioClass = "idle";
177             };
178           };
180           pipewireBoost = {
181             enable = mkOption {
182               type = bool;
183               default = true;
184               description = "Boost Pipewire client priorities.";
185             };
186             profile = schedulerProfile {
187               nice = -6;
188               ioClass = "best-effort";
189               ioPrio = 0;
190             };
191           };
192         };
193       };
195       assignments = mkOption {
196         type = types.attrsOf (types.submodule {
197           options = schedulerProfile { };
198         });
199         default = {};
200         example = literalExpression ''
201           {
202             nix-builds = {
203               nice = 15;
204               class = "batch";
205               ioClass = "idle";
206               matchers = [
207                 "nix-daemon"
208               ];
209             };
210           }
211         '';
212         description = "Process profile assignments.";
213       };
215       exceptions = mkOption {
216         type = types.listOf str;
217         default = [];
218         example = literalExpression ''
219           [
220             "include descends=\"schedtool\""
221             "schedtool"
222           ]
223         '';
224         description = "Processes that are left alone.";
225       };
226     };
227   };
229   config = mkIf cfg.enable {
230     environment.systemPackages = [ cfg.package ];
231     services.dbus.packages = [ cfg.package ];
233     systemd.services.system76-scheduler = {
234       description = "Manage process priorities and CFS scheduler latencies for improved responsiveness on the desktop";
235       wantedBy = [ "multi-user.target" ];
236       path = [
237         # execsnoop needs those to extract kernel headers:
238         pkgs.kmod
239         pkgs.gnutar
240         pkgs.xz
241       ];
242       serviceConfig = {
243         Type = "dbus";
244         BusName= "com.system76.Scheduler";
245         ExecStart = "${cfg.package}/bin/system76-scheduler daemon";
246         ExecReload = "${cfg.package}/bin/system76-scheduler daemon reload";
247       };
248     };
250     environment.etc = mkMerge [
251       (mkIf cfg.useStockConfig {
252         # No custom settings: just use stock configuration with a fix for Pipewire
253         "system76-scheduler/config.kdl".source = "${cfg.package}/data/config.kdl";
254         "system76-scheduler/process-scheduler/00-dist.kdl".source = "${cfg.package}/data/pop_os.kdl";
255         "system76-scheduler/process-scheduler/01-fix-pipewire-paths.kdl".source = ../../../../pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl;
256       })
258       (let
259         settings = cfg.settings;
260         cfsp = settings.cfsProfiles;
261         ps = settings.processScheduler;
262       in mkIf (!cfg.useStockConfig) {
263         "system76-scheduler/config.kdl".text = ''
264           version "2.0"
265           autogroup-enabled false
266           cfs-profiles enable=${boolToString cfsp.enable} {
267             ${cfsProfileToString "default"}
268             ${cfsProfileToString "responsive"}
269           }
270           process-scheduler enable=${boolToString ps.enable} {
271             execsnoop ${boolToString ps.useExecsnoop}
272             refresh-rate ${toString ps.refreshInterval}
273             assignments {
274               ${if ps.foregroundBoost.enable then (schedulerProfileToString "foreground" ps.foregroundBoost.foreground "    ") else ""}
275               ${if ps.foregroundBoost.enable then (schedulerProfileToString "background" ps.foregroundBoost.background "    ") else ""}
276               ${if ps.pipewireBoost.enable then (schedulerProfileToString "pipewire" ps.pipewireBoost.profile "    ") else ""}
277             }
278           }
279         '';
280       })
282       {
283         "system76-scheduler/process-scheduler/02-config.kdl".text =
284           "exceptions {\n${concatStringsSep "\n" (map (e: "  ${e}") cfg.exceptions)}\n}\n"
285           + "assignments {\n"
286           + (concatStringsSep "\n" (map (name: schedulerProfileToString name cfg.assignments.${name} "  ")
287             (attrNames cfg.assignments)))
288           + "\n}\n";
289       }
290     ];
291   };
293   meta = {
294     maintainers = [ lib.maintainers.cmm ];
295   };