{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.system76-scheduler;
inherit (builtins)
concatStringsSep
map
toString
attrNames
;
inherit (lib)
boolToString
types
mkOption
literalExpression
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 = "`sched_latency_ns`.";
};
nr-latency = {
description = "`sched_nr_latency`.";
wakeup-granularity = {
type = float;
description = "`sched_wakeup_granularity_ns`.";
bandwidth-size = {
description = "`sched_cfs_bandwidth_slice_us`.";
preempt = {
type = enum [
"none"
"voluntary"
"full"
];
description = "Preemption mode.";
schedulerProfile = withDefaults {
nice = {
type = nullOr (ints.between (-20) 19);
description = "Niceness.";
class = {
type = nullOr (enum [
"idle"
"batch"
"other"
"rr"
"fifo"
]);
example = literalExpression "\"batch\"";
description = "CPU scheduler class.";
prio = {
type = nullOr (ints.between 1 99);
example = literalExpression "49";
description = "CPU scheduler priority.";
ioClass = {
"best-effort"
"realtime"
example = literalExpression "\"best-effort\"";
description = "IO scheduler class.";
ioPrio = {
type = nullOr (ints.between 0 7);
example = literalExpression "4";
description = "IO scheduler priority.";
matchers = {
type = nullOr (listOf str);
default = [ ];
example = literalExpression ''
[
"include cgroup=\"/user.slice/*.service\" parent=\"systemd\""
"emacs"
]
'';
description = "Process matchers.";
cfsProfileToString =
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}}"
))
options = {
services.system76-scheduler = {
enable = lib.mkEnableOption "system76-scheduler";
package = mkOption {
type = types.package;
default = pkgs.system76-scheduler;
defaultText = literalExpression "pkgs.system76-scheduler";
description = "Which System76-Scheduler package to use.";
useStockConfig = mkOption {
type = bool;
default = true;
description = ''
Use the (reasonable and featureful) stock configuration.
When this option is `true`, `services.system76-scheduler.settings`
are ignored.
settings = {
cfsProfiles = {
enable = mkOption {
description = "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 = {
description = "Tweak scheduling of individual processes in real time.";
useExecsnoop = mkOption {
description = "Use execsnoop (otherwise poll the precess list periodically).";
refreshInterval = mkOption {
default = 60;
description = "Process list poll interval, in seconds";
foregroundBoost = {
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 = {
description = "Boost Pipewire client priorities.";
profile = schedulerProfile {
nice = -6;
assignments = mkOption {
type = types.attrsOf (
types.submodule {
options = schedulerProfile { };
default = { };
nix-builds = {
nice = 15;
class = "batch";
matchers = [
"nix-daemon"
description = "Process profile assignments.";
exceptions = mkOption {
type = types.listOf str;
"include descends=\"schedtool\""
"schedtool"
description = "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/by-name/sy/system76-scheduler/01-fix-pipewire-paths.kdl;
})
(
settings = cfg.settings;
cfsp = settings.cfsProfiles;
ps = settings.processScheduler;
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
""
(schedulerProfileToString "background" ps.foregroundBoost.background " ")
if ps.pipewireBoost.enable then
(schedulerProfileToString "pipewire" ps.pipewireBoost.profile " ")
"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 ];