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