1 { config, lib, pkgs, ... }:
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;
16 latencyProfile = withDefaults {
19 description = "`sched_latency_ns`.";
23 description = "`sched_nr_latency`.";
25 wakeup-granularity = {
27 description = "`sched_wakeup_granularity_ns`.";
31 description = "`sched_cfs_bandwidth_slice_us`.";
34 type = enum [ "none" "voluntary" "full" ];
35 description = "Preemption mode.";
38 schedulerProfile = withDefaults {
40 type = nullOr (ints.between (-20) 19);
41 description = "Niceness.";
44 type = nullOr (enum [ "idle" "batch" "other" "rr" "fifo" ]);
45 example = literalExpression "\"batch\"";
46 description = "CPU scheduler class.";
49 type = nullOr (ints.between 1 99);
50 example = literalExpression "49";
51 description = "CPU scheduler priority.";
54 type = nullOr (enum [ "idle" "best-effort" "realtime" ]);
55 example = literalExpression "\"best-effort\"";
56 description = "IO scheduler class.";
59 type = nullOr (ints.between 0 7);
60 example = literalExpression "4";
61 description = "IO scheduler priority.";
64 type = nullOr (listOf str);
66 example = literalExpression ''
68 "include cgroup=\"/user.slice/*.service\" parent=\"systemd\""
72 description = "Process matchers.";
76 cfsProfileToString = name: let
77 p = cfg.settings.cfsProfiles.${name};
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:
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}}")));
93 services.system76-scheduler = {
94 enable = lib.mkEnableOption "system76-scheduler";
98 default = config.boot.kernelPackages.system76-scheduler;
99 defaultText = literalExpression "config.boot.kernelPackages.system76-scheduler";
100 description = "Which System76-Scheduler package to use.";
103 useStockConfig = mkOption {
107 Use the (reasonable and featureful) stock configuration.
109 When this option is `true`, `services.system76-scheduler.settings`
119 description = "Tweak CFS latency parameters when going on/off battery";
122 default = latencyProfile {
125 wakeup-granularity = 1.0;
127 preempt = "voluntary";
129 responsive = latencyProfile {
132 wakeup-granularity = 0.5;
142 description = "Tweak scheduling of individual processes in real time.";
145 useExecsnoop = mkOption {
148 description = "Use execsnoop (otherwise poll the precess list periodically).";
151 refreshInterval = mkOption {
154 description = "Process list poll interval, in seconds";
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.
169 foreground = schedulerProfile {
171 ioClass = "best-effort";
174 background = schedulerProfile {
184 description = "Boost Pipewire client priorities.";
186 profile = schedulerProfile {
188 ioClass = "best-effort";
195 assignments = mkOption {
196 type = types.attrsOf (types.submodule {
197 options = schedulerProfile { };
200 example = literalExpression ''
212 description = "Process profile assignments.";
215 exceptions = mkOption {
216 type = types.listOf str;
218 example = literalExpression ''
220 "include descends=\"schedtool\""
224 description = "Processes that are left alone.";
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" ];
237 # execsnoop needs those to extract kernel headers:
244 BusName= "com.system76.Scheduler";
245 ExecStart = "${cfg.package}/bin/system76-scheduler daemon";
246 ExecReload = "${cfg.package}/bin/system76-scheduler daemon reload";
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;
259 settings = cfg.settings;
260 cfsp = settings.cfsProfiles;
261 ps = settings.processScheduler;
262 in mkIf (!cfg.useStockConfig) {
263 "system76-scheduler/config.kdl".text = ''
265 autogroup-enabled false
266 cfs-profiles enable=${boolToString cfsp.enable} {
267 ${cfsProfileToString "default"}
268 ${cfsProfileToString "responsive"}
270 process-scheduler enable=${boolToString ps.enable} {
271 execsnoop ${boolToString ps.useExecsnoop}
272 refresh-rate ${toString ps.refreshInterval}
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 ""}
283 "system76-scheduler/process-scheduler/02-config.kdl".text =
284 "exceptions {\n${concatStringsSep "\n" (map (e: " ${e}") cfg.exceptions)}\n}\n"
286 + (concatStringsSep "\n" (map (name: schedulerProfileToString name cfg.assignments.${name} " ")
287 (attrNames cfg.assignments)))
294 maintainers = [ lib.maintainers.cmm ];