nixpkgs/nixos/modules/services/desktops/system76-scheduler.nix
Silvan Mosberger 4f0dadbf38 treewide: format all inactive Nix files
After final improvements to the official formatter implementation,
this commit now performs the first treewide reformat of Nix files using it.
This is part of the implementation of RFC 166.

Only "inactive" files are reformatted, meaning only files that
aren't being touched by any PR with activity in the past 2 months.
This is to avoid conflicts for PRs that might soon be merged.
Later we can do a full treewide reformat to get the rest,
which should not cause as many conflicts.

A CI check has already been running for some time to ensure that new and
already-formatted files are formatted, so the files being reformatted here
should also stay formatted.

This commit was automatically created and can be verified using

    nix-build a08b3a4d19.tar.gz \
      --argstr baseRev b32a0943687d2a5094a6d92f25a4b6e16a76b5b7
    result/bin/apply-formatting $NIXPKGS_PATH
2024-12-10 20:26:33 +01:00

373 lines
10 KiB
Nix

{
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 = {
type = int;
description = "`sched_nr_latency`.";
};
wakeup-granularity = {
type = float;
description = "`sched_wakeup_granularity_ns`.";
};
bandwidth-size = {
type = int;
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 = {
type = nullOr (enum [
"idle"
"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 =
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 "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 {
type = bool;
default = true;
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 = {
enable = mkOption {
type = bool;
default = true;
description = "Tweak scheduling of individual processes in real time.";
};
useExecsnoop = mkOption {
type = bool;
default = true;
description = "Use execsnoop (otherwise poll the precess list periodically).";
};
refreshInterval = mkOption {
type = int;
default = 60;
description = "Process list poll interval, in seconds";
};
foregroundBoost = {
enable = mkOption {
type = bool;
default = true;
description = ''
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 = "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 = "Process profile assignments.";
};
exceptions = mkOption {
type = types.listOf str;
default = [ ];
example = literalExpression ''
[
"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;
})
(
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 ];
};
}