297 lines
9.5 KiB
Nix
297 lines
9.5 KiB
Nix
|
{ config, lib, pkgs, ... }:
|
||
|
|
||
|
let
|
||
|
cfg = config.services.system76-scheduler;
|
||
|
|
||
|
inherit (builtins) concatStringsSep map toString attrNames;
|
||
|
inherit (lib) boolToString types mkOption literalExpression mdDoc optional mkIf mkMerge;
|
||
|
inherit (types) nullOr listOf bool int ints float str enum;
|
||
|
|
||
|
withDefaults = optionSpecs: defaults:
|
||
|
lib.genAttrs (attrNames optionSpecs) (name:
|
||
|
mkOption (optionSpecs.${name} // {
|
||
|
default = optionSpecs.${name}.default or defaults.${name} or null;
|
||
|
}));
|
||
|
|
||
|
latencyProfile = withDefaults {
|
||
|
latency = {
|
||
|
type = int;
|
||
|
description = mdDoc "`sched_latency_ns`.";
|
||
|
};
|
||
|
nr-latency = {
|
||
|
type = int;
|
||
|
description = mdDoc "`sched_nr_latency`.";
|
||
|
};
|
||
|
wakeup-granularity = {
|
||
|
type = float;
|
||
|
description = mdDoc "`sched_wakeup_granularity_ns`.";
|
||
|
};
|
||
|
bandwidth-size = {
|
||
|
type = int;
|
||
|
description = mdDoc "`sched_cfs_bandwidth_slice_us`.";
|
||
|
};
|
||
|
preempt = {
|
||
|
type = enum [ "none" "voluntary" "full" ];
|
||
|
description = mdDoc "Preemption mode.";
|
||
|
};
|
||
|
};
|
||
|
schedulerProfile = withDefaults {
|
||
|
nice = {
|
||
|
type = nullOr (ints.between (-20) 19);
|
||
|
description = mdDoc "Niceness.";
|
||
|
};
|
||
|
class = {
|
||
|
type = nullOr (enum [ "idle" "batch" "other" "rr" "fifo" ]);
|
||
|
example = literalExpression "\"batch\"";
|
||
|
description = mdDoc "CPU scheduler class.";
|
||
|
};
|
||
|
prio = {
|
||
|
type = nullOr (ints.between 1 99);
|
||
|
example = literalExpression "49";
|
||
|
description = mdDoc "CPU scheduler priority.";
|
||
|
};
|
||
|
ioClass = {
|
||
|
type = nullOr (enum [ "idle" "best-effort" "realtime" ]);
|
||
|
example = literalExpression "\"best-effort\"";
|
||
|
description = mdDoc "IO scheduler class.";
|
||
|
};
|
||
|
ioPrio = {
|
||
|
type = nullOr (ints.between 0 7);
|
||
|
example = literalExpression "4";
|
||
|
description = mdDoc "IO scheduler priority.";
|
||
|
};
|
||
|
matchers = {
|
||
|
type = nullOr (listOf str);
|
||
|
default = [];
|
||
|
example = literalExpression ''
|
||
|
[
|
||
|
"include cgroup=\"/user.slice/*.service\" parent=\"systemd\""
|
||
|
"emacs"
|
||
|
]
|
||
|
'';
|
||
|
description = mdDoc "Process matchers.";
|
||
|
};
|
||
|
};
|
||
|
|
||
|
cfsProfileToString = name: let
|
||
|
p = cfg.settings.cfsProfiles.${name};
|
||
|
in
|
||
|
"${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}\"";
|
||
|
|
||
|
prioToString = class: prio: if prio == null then "\"${class}\"" else "(${class})${toString prio}";
|
||
|
|
||
|
schedulerProfileToString = name: a: indent:
|
||
|
concatStringsSep " "
|
||
|
(["${indent}${name}"]
|
||
|
++ (optional (a.nice != null) "nice=${toString a.nice}")
|
||
|
++ (optional (a.class != null) "sched=${prioToString a.class a.prio}")
|
||
|
++ (optional (a.ioClass != null) "io=${prioToString a.ioClass a.ioPrio}")
|
||
|
++ (optional ((builtins.length a.matchers) != 0) ("{\n${concatStringsSep "\n" (map (m: " ${indent}${m}") a.matchers)}\n${indent}}")));
|
||
|
|
||
|
in {
|
||
|
options = {
|
||
|
services.system76-scheduler = {
|
||
|
enable = lib.mkEnableOption (lib.mdDoc "system76-scheduler");
|
||
|
|
||
|
package = mkOption {
|
||
|
type = types.package;
|
||
|
default = config.boot.kernelPackages.system76-scheduler;
|
||
|
defaultText = literalExpression "config.boot.kernelPackages.system76-scheduler";
|
||
|
description = mdDoc "Which System76-Scheduler package to use.";
|
||
|
};
|
||
|
|
||
|
useStockConfig = mkOption {
|
||
|
type = bool;
|
||
|
default = true;
|
||
|
description = mdDoc ''
|
||
|
Use the (reasonable and featureful) stock configuration.
|
||
|
|
||
|
When this option is `true`, `services.system76-scheduler.settings`
|
||
|
are ignored.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
settings = {
|
||
|
cfsProfiles = {
|
||
|
enable = mkOption {
|
||
|
type = bool;
|
||
|
default = true;
|
||
|
description = mdDoc "Tweak CFS latency parameters when going on/off battery";
|
||
|
};
|
||
|
|
||
|
default = latencyProfile {
|
||
|
latency = 6;
|
||
|
nr-latency = 8;
|
||
|
wakeup-granularity = 1.0;
|
||
|
bandwidth-size = 5;
|
||
|
preempt = "voluntary";
|
||
|
};
|
||
|
responsive = latencyProfile {
|
||
|
latency = 4;
|
||
|
nr-latency = 10;
|
||
|
wakeup-granularity = 0.5;
|
||
|
bandwidth-size = 3;
|
||
|
preempt = "full";
|
||
|
};
|
||
|
};
|
||
|
|
||
|
processScheduler = {
|
||
|
enable = mkOption {
|
||
|
type = bool;
|
||
|
default = true;
|
||
|
description = mdDoc "Tweak scheduling of individual processes in real time.";
|
||
|
};
|
||
|
|
||
|
useExecsnoop = mkOption {
|
||
|
type = bool;
|
||
|
default = true;
|
||
|
description = mdDoc "Use execsnoop (otherwise poll the precess list periodically).";
|
||
|
};
|
||
|
|
||
|
refreshInterval = mkOption {
|
||
|
type = int;
|
||
|
default = 60;
|
||
|
description = mdDoc "Process list poll interval, in seconds";
|
||
|
};
|
||
|
|
||
|
foregroundBoost = {
|
||
|
enable = mkOption {
|
||
|
type = bool;
|
||
|
default = true;
|
||
|
description = mdDoc ''
|
||
|
Boost foreground process priorities.
|
||
|
|
||
|
(And de-boost background ones). Note that this option needs cooperation
|
||
|
from the desktop environment to work. On Gnome the client side is
|
||
|
implemented by the "System76 Scheduler" shell extension.
|
||
|
'';
|
||
|
};
|
||
|
foreground = schedulerProfile {
|
||
|
nice = 0;
|
||
|
ioClass = "best-effort";
|
||
|
ioPrio = 0;
|
||
|
};
|
||
|
background = schedulerProfile {
|
||
|
nice = 6;
|
||
|
ioClass = "idle";
|
||
|
};
|
||
|
};
|
||
|
|
||
|
pipewireBoost = {
|
||
|
enable = mkOption {
|
||
|
type = bool;
|
||
|
default = true;
|
||
|
description = mdDoc "Boost Pipewire client priorities.";
|
||
|
};
|
||
|
profile = schedulerProfile {
|
||
|
nice = -6;
|
||
|
ioClass = "best-effort";
|
||
|
ioPrio = 0;
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
assignments = mkOption {
|
||
|
type = types.attrsOf (types.submodule {
|
||
|
options = schedulerProfile { };
|
||
|
});
|
||
|
default = {};
|
||
|
example = literalExpression ''
|
||
|
{
|
||
|
nix-builds = {
|
||
|
nice = 15;
|
||
|
class = "batch";
|
||
|
ioClass = "idle";
|
||
|
matchers = [
|
||
|
"nix-daemon"
|
||
|
];
|
||
|
};
|
||
|
}
|
||
|
'';
|
||
|
description = mdDoc "Process profile assignments.";
|
||
|
};
|
||
|
|
||
|
exceptions = mkOption {
|
||
|
type = types.listOf str;
|
||
|
default = [];
|
||
|
example = literalExpression ''
|
||
|
[
|
||
|
"include descends=\"schedtool\""
|
||
|
"schedtool"
|
||
|
]
|
||
|
'';
|
||
|
description = mdDoc "Processes that are left alone.";
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
config = mkIf cfg.enable {
|
||
|
environment.systemPackages = [ cfg.package ];
|
||
|
services.dbus.packages = [ cfg.package ];
|
||
|
|
||
|
systemd.services.system76-scheduler = {
|
||
|
description = "Manage process priorities and CFS scheduler latencies for improved responsiveness on the desktop";
|
||
|
wantedBy = [ "multi-user.target" ];
|
||
|
path = [
|
||
|
# execsnoop needs those to extract kernel headers:
|
||
|
pkgs.kmod
|
||
|
pkgs.gnutar
|
||
|
pkgs.xz
|
||
|
];
|
||
|
serviceConfig = {
|
||
|
Type = "dbus";
|
||
|
BusName= "com.system76.Scheduler";
|
||
|
ExecStart = "${cfg.package}/bin/system76-scheduler daemon";
|
||
|
ExecReload = "${cfg.package}/bin/system76-scheduler daemon reload";
|
||
|
};
|
||
|
};
|
||
|
|
||
|
environment.etc = mkMerge [
|
||
|
(mkIf cfg.useStockConfig {
|
||
|
# No custom settings: just use stock configuration with a fix for Pipewire
|
||
|
"system76-scheduler/config.kdl".source = "${cfg.package}/data/config.kdl";
|
||
|
"system76-scheduler/process-scheduler/00-dist.kdl".source = "${cfg.package}/data/pop_os.kdl";
|
||
|
"system76-scheduler/process-scheduler/01-fix-pipewire-paths.kdl".source = ../../../../pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl;
|
||
|
})
|
||
|
|
||
|
(let
|
||
|
settings = cfg.settings;
|
||
|
cfsp = settings.cfsProfiles;
|
||
|
ps = settings.processScheduler;
|
||
|
in mkIf (!cfg.useStockConfig) {
|
||
|
"system76-scheduler/config.kdl".text = ''
|
||
|
version "2.0"
|
||
|
autogroup-enabled false
|
||
|
cfs-profiles enable=${boolToString cfsp.enable} {
|
||
|
${cfsProfileToString "default"}
|
||
|
${cfsProfileToString "responsive"}
|
||
|
}
|
||
|
process-scheduler enable=${boolToString ps.enable} {
|
||
|
execsnoop ${boolToString ps.useExecsnoop}
|
||
|
refresh-rate ${toString ps.refreshInterval}
|
||
|
assignments {
|
||
|
${if ps.foregroundBoost.enable then (schedulerProfileToString "foreground" ps.foregroundBoost.foreground " ") else ""}
|
||
|
${if ps.foregroundBoost.enable then (schedulerProfileToString "background" ps.foregroundBoost.background " ") else ""}
|
||
|
${if ps.pipewireBoost.enable then (schedulerProfileToString "pipewire" ps.pipewireBoost.profile " ") else ""}
|
||
|
}
|
||
|
}
|
||
|
'';
|
||
|
})
|
||
|
|
||
|
{
|
||
|
"system76-scheduler/process-scheduler/02-config.kdl".text =
|
||
|
"exceptions {\n${concatStringsSep "\n" (map (e: " ${e}") cfg.exceptions)}\n}\n"
|
||
|
+ "assignments {\n"
|
||
|
+ (concatStringsSep "\n" (map (name: schedulerProfileToString name cfg.assignments.${name} " ")
|
||
|
(attrNames cfg.assignments)))
|
||
|
+ "\n}\n";
|
||
|
}
|
||
|
];
|
||
|
};
|
||
|
|
||
|
meta = {
|
||
|
maintainers = [ lib.maintainers.cmm ];
|
||
|
};
|
||
|
}
|