From e12ac412791391793d7c69fcb3575efbfce8da4d Mon Sep 17 00:00:00 2001 From: Michael Livshin Date: Sat, 29 Apr 2023 13:32:09 +0300 Subject: [PATCH 1/2] system76-scheduler: init at 2.0.1 --- .../01-fix-pipewire-paths.kdl | 8 ++++ .../linux/system76-scheduler/default.nix | 47 +++++++++++++++++++ pkgs/top-level/linux-kernels.nix | 2 + 3 files changed, 57 insertions(+) create mode 100644 pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl create mode 100644 pkgs/os-specific/linux/system76-scheduler/default.nix diff --git a/pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl b/pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl new file mode 100644 index 00000000000..1ce08e2d343 --- /dev/null +++ b/pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl @@ -0,0 +1,8 @@ +assignments { + sound-server { + // original config matches on /usr/bin/..., but this is NixOS + pipewire + pipewire-pulse + jackd + } +} diff --git a/pkgs/os-specific/linux/system76-scheduler/default.nix b/pkgs/os-specific/linux/system76-scheduler/default.nix new file mode 100644 index 00000000000..1ca4fa27610 --- /dev/null +++ b/pkgs/os-specific/linux/system76-scheduler/default.nix @@ -0,0 +1,47 @@ +{ lib +, fetchFromGitHub +, rustPlatform +, llvm +, clang +, libclang +, pipewire +, pkg-config +, bcc +, dbus }: + +let + version = "2.0.1"; +in rustPlatform.buildRustPackage { + pname = "system76-scheduler"; + inherit version; + src = fetchFromGitHub { + owner = "pop-os"; + repo = "system76-scheduler"; + rev = version; + hash = "sha256-o4noaLBXHDe7pMBHfQ85uzKJzwbBE5mkWq8h9l6iIZs="; + }; + cargoSha256 = "sha256-hpFDAhOzm4v3lBWwAl/10pS5xvKCScdKsp5wpCeQ+FE="; + + nativeBuildInputs = [ pkg-config llvm clang ]; + buildInputs = [ dbus pipewire ]; + + LIBCLANG_PATH = "${libclang.lib}/lib"; + EXECSNOOP_PATH = "${bcc}/bin/execsnoop"; + + # tests don't build + doCheck = false; + + postInstall = '' + mkdir -p $out/data + install -D -m 0644 data/com.system76.Scheduler.conf $out/etc/dbus-1/system.d/com.system76.Scheduler.conf + install -D -m 0644 data/*.kdl $out/data/ + ''; + + meta = with lib; { + description = "System76 Scheduler"; + homepage = "https://github.com/pop-os/system76-scheduler"; + license = licenses.mpl20; + platforms = [ "x86_64-linux" "x86-linux" "aarch64-linux" ]; + maintainers = [ maintainers.cmm ]; + }; +} diff --git a/pkgs/top-level/linux-kernels.nix b/pkgs/top-level/linux-kernels.nix index 8daa52a73df..52245dc38e8 100644 --- a/pkgs/top-level/linux-kernels.nix +++ b/pkgs/top-level/linux-kernels.nix @@ -502,6 +502,8 @@ in { system76-io = callPackage ../os-specific/linux/system76-io { }; + system76-scheduler = callPackage ../os-specific/linux/system76-scheduler { }; + tmon = callPackage ../os-specific/linux/tmon { }; tp_smapi = callPackage ../os-specific/linux/tp_smapi { }; From 549fd53520351d2e4b07d42cdbd1a63843e22113 Mon Sep 17 00:00:00 2001 From: Michael Livshin Date: Sat, 29 Apr 2023 13:32:44 +0300 Subject: [PATCH 2/2] system76-scheduler: add config module --- nixos/modules/module-list.nix | 1 + .../services/desktops/system76-scheduler.nix | 296 ++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 nixos/modules/services/desktops/system76-scheduler.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index bbbe8682fd0..7dbc7ba590b 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -439,6 +439,7 @@ ./services/desktops/pipewire/wireplumber.nix ./services/desktops/profile-sync-daemon.nix ./services/desktops/system-config-printer.nix + ./services/desktops/system76-scheduler.nix ./services/desktops/telepathy.nix ./services/desktops/tumbler.nix ./services/desktops/zeitgeist.nix diff --git a/nixos/modules/services/desktops/system76-scheduler.nix b/nixos/modules/services/desktops/system76-scheduler.nix new file mode 100644 index 00000000000..93bd503eae5 --- /dev/null +++ b/nixos/modules/services/desktops/system76-scheduler.nix @@ -0,0 +1,296 @@ +{ 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 = { + 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 ]; + }; +}