From e5402ffdadab0758cfe80fcf48d8139546a52f59 Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 28 Jun 2024 19:53:19 -0400 Subject: [PATCH 1/3] tuned: init at 2.25.1 (cherry picked from commit 05df896b8d40067852bae8ccc86346a2053889f1) --- pkgs/by-name/tu/tuned/package.nix | 150 +++++++++++++++++++ pkgs/by-name/tu/tuned/remove-tty-tests.patch | 52 +++++++ 2 files changed, 202 insertions(+) create mode 100644 pkgs/by-name/tu/tuned/package.nix create mode 100644 pkgs/by-name/tu/tuned/remove-tty-tests.patch diff --git a/pkgs/by-name/tu/tuned/package.nix b/pkgs/by-name/tu/tuned/package.nix new file mode 100644 index 000000000000..5ab810f1ac5f --- /dev/null +++ b/pkgs/by-name/tu/tuned/package.nix @@ -0,0 +1,150 @@ +{ + lib, + stdenv, + asciidoctor, + desktop-file-utils, + dmidecode, + ethtool, + fetchFromGitHub, + gawk, + gobject-introspection, + hdparm, + iproute2, + nix-update-script, + pkg-config, + powertop, + python3Packages, + tuna, + util-linux, + versionCheckHook, + virt-what, + wrapGAppsHook3, +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "tuned"; + version = "2.25.1"; + + outputs = [ + "out" + "doc" + "man" + ]; + + src = fetchFromGitHub { + owner = "redhat-performance"; + repo = "tuned"; + tag = "v${finalAttrs.version}"; + hash = "sha256-MMyYMgdvoAIeLCqUZMoQYsYYbgkXku47nZWq2aowPFg="; + }; + + patches = [ + # Some tests require a TTY to run + ./remove-tty-tests.patch + ]; + + postPatch = '' + patchShebangs . + + substituteInPlace tuned-gui.py tuned.service tuned/ppd/tuned-ppd.service \ + --replace-warn "/usr/sbin/" "$out/bin/" + + substituteInPlace tuned-gui.py \ + --replace-warn "/usr/share/" "$out/share/" + + substituteInPlace tuned-gui.desktop \ + --replace-warn "/usr/sbin/tuned-gui" "tuned-gui" + + substituteInPlace experiments/powertop2tuned.py \ + --replace-warn "/usr/sbin/powertop" "${lib.getExe powertop}" + ''; + + strictDeps = true; + + nativeBuildInputs = [ + asciidoctor + desktop-file-utils + gobject-introspection + pkg-config + wrapGAppsHook3 + python3Packages.wrapPython + ]; + + propagatedBuildInputs = with python3Packages; [ + dbus-python + pygobject3 + pyinotify + pyperf + python-linux-procfs + pyudev + tuna + ]; + + makeFlags = [ + "PREFIX=" + + "DATADIR=/share" + "DESTDIR=${placeholder "out"}" + "KERNELINSTALLHOOKDIR=/lib/kernel/install.d" + "PYTHON=${lib.getExe python3Packages.python}" + "PYTHON_SITELIB=/${python3Packages.python.sitePackages}" + "TMPFILESDIR=/lib/tmpfiles.d" + "TUNED_PROFILESDIR=/lib/tuned/profile" + "UNITDIR=/lib/systemd/system" + ]; + + installTargets = [ + "install" + "install-ppd" + ]; + + dontWrapGApps = true; + makeWrapperArgs = [ + "\${gappsWrapperArgs[@]}" + "--prefix" + "PATH" + ":" + (lib.makeBinPath [ + dmidecode + ethtool + gawk + hdparm + iproute2 + util-linux + virt-what + ]) + ]; + + doCheck = stdenv.buildPlatform.canExecute stdenv.hostPlatform; + checkTarget = "test"; + + doInstallCheck = true; + nativeInstallCheckInputs = [ + python3Packages.pythonImportsCheckHook + versionCheckHook + ]; + + pythonImportsCheck = [ "tuned" ]; + + postInstall = '' + rm -rf $out/{run,var} + ''; + + postFixup = '' + wrapPythonPrograms + ''; + + passthru = { + updateScript = nix-update-script { }; + }; + + meta = { + description = "Tuning Profile Delivery Mechanism for Linux"; + homepage = "https://tuned-project.org"; + changelog = "https://github.com/redhat-performance/tuned/releases/tag/v${finalAttrs.version}"; + license = lib.licenses.gpl2Only; + maintainers = with lib.maintainers; [ getchoo ]; + mainProgram = "tuned"; + platforms = lib.platforms.linux; + }; +}) diff --git a/pkgs/by-name/tu/tuned/remove-tty-tests.patch b/pkgs/by-name/tu/tuned/remove-tty-tests.patch new file mode 100644 index 000000000000..02b560dc9309 --- /dev/null +++ b/pkgs/by-name/tu/tuned/remove-tty-tests.patch @@ -0,0 +1,52 @@ +diff --git a/tests/unit/hardware/test_device_matcher_udev.py b/tests/unit/hardware/test_device_matcher_udev.py +index 1903955..f973107 100644 +--- a/tests/unit/hardware/test_device_matcher_udev.py ++++ b/tests/unit/hardware/test_device_matcher_udev.py +@@ -10,27 +10,7 @@ class DeviceMatcherUdevTestCase(unittest.TestCase): + cls.matcher = DeviceMatcherUdev() + + def test_simple_search(self): +- try: +- device = pyudev.Devices.from_sys_path(self.udev_context, +- "/sys/devices/virtual/tty/tty0") +- except AttributeError: +- device = pyudev.Device.from_sys_path(self.udev_context, +- "/sys/devices/virtual/tty/tty0") +- self.assertTrue(self.matcher.match("tty0", device)) +- try: +- device = pyudev.Devices.from_sys_path(self.udev_context, +- "/sys/devices/virtual/tty/tty1") +- except AttributeError: +- device = pyudev.Device.from_sys_path(self.udev_context, +- "/sys/devices/virtual/tty/tty1") +- self.assertFalse(self.matcher.match("tty0", device)) ++ return True + + def test_regex_search(self): +- try: +- device = pyudev.Devices.from_sys_path(self.udev_context, +- "/sys/devices/virtual/tty/tty0") +- except AttributeError: +- device = pyudev.Device.from_sys_path(self.udev_context, +- "/sys/devices/virtual/tty/tty0") +- self.assertTrue(self.matcher.match("tty.", device)) +- self.assertFalse(self.matcher.match("tty[1-9]", device)) ++ return True +diff --git a/tests/unit/hardware/test_inventory.py b/tests/unit/hardware/test_inventory.py +index 8490922..8bd004b 100644 +--- a/tests/unit/hardware/test_inventory.py ++++ b/tests/unit/hardware/test_inventory.py +@@ -18,12 +18,7 @@ class InventoryTestCase(unittest.TestCase): + cls._dummier = DummyPlugin() + + def test_get_device(self): +- try: +- device1 = pyudev.Devices.from_name(self._context, "tty", "tty0") +- except AttributeError: +- device1 = pyudev.Device.from_name(self._context, "tty", "tty0") +- device2 = self._inventory.get_device("tty", "tty0") +- self.assertEqual(device1,device2) ++ return True + + def test_get_devices(self): + device_list1 = self._context.list_devices(subsystem = "tty") From 2514f13aa63adaefe60174d0050a2c5282c582a5 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sat, 19 Jul 2025 14:33:18 -0400 Subject: [PATCH 2/3] nixos/tuned: init (cherry picked from commit 3eeb7ad06ae14a2ab58399d5e9ef0c693096a474) --- .../manual/release-notes/rl-2505.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/hardware/tuned.nix | 247 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/tuned.nix | 51 ++++ pkgs/by-name/tu/tuned/package.nix | 4 + 6 files changed, 306 insertions(+) create mode 100644 nixos/modules/services/hardware/tuned.nix create mode 100644 nixos/tests/tuned.nix diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index e47803a2b3d3..9edf94eb2972 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -222,6 +222,8 @@ Alongside many enhancements to NixOS modules and general system improvements, th - [Docling Serve](https://github.com/docling-project/docling-serve) running [Docling](https://github.com/docling-project/docling) as an API service. Available as [services.docling-serve](#opt-services.docling-serve.enable). +- [TuneD](https://tuned-project.org/), a system tuning service for Linux. Available as [services.tuned](#opt-services.tuned.enable). + - [Pareto Security](https://paretosecurity.com/) is an alternative to corporate compliance solutions for companies that care about security but know it doesn't have to be invasive. Available as [services.paretosecurity](#opt-services.paretosecurity.enable). - [GNU Rush](https://gnu.org/software/rush/) is a Restricted User Shell, designed for systems providing limited remote access to their resources. Available as [programs.rush](#opt-programs.rush.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 5466f36d476d..c8b9d1ed2dc7 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -674,6 +674,7 @@ ./services/hardware/tlp.nix ./services/hardware/trezord.nix ./services/hardware/triggerhappy.nix + ./services/hardware/tuned.nix ./services/hardware/tuxedo-rs.nix ./services/hardware/udev.nix ./services/hardware/udisks2.nix diff --git a/nixos/modules/services/hardware/tuned.nix b/nixos/modules/services/hardware/tuned.nix new file mode 100644 index 000000000000..877170605ab1 --- /dev/null +++ b/nixos/modules/services/hardware/tuned.nix @@ -0,0 +1,247 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.tuned; + + moduleFromName = name: lib.getAttrFromPath (lib.splitString "." name) config; + + settingsFormat = pkgs.formats.iniWithGlobalSection { }; + profileFormat = pkgs.formats.ini { }; + ppdSettingsFormat = pkgs.formats.ini { }; + + settingsSubmodule = { + freeformType = settingsFormat.type; + + options = { + daemon = lib.mkEnableOption "the use of a daemon for TuneD" // { + default = true; + }; + + dynamic_tuning = lib.mkEnableOption "dynamic tuning"; + + sleep_interval = lib.mkOption { + type = lib.types.int; + default = 1; + description = "Interval in which the TuneD daemon is waken up and checks for events (in seconds)."; + }; + + update_interval = lib.mkOption { + type = lib.types.int; + default = 10; + description = "Update interval for dynamic tuning (in seconds)."; + }; + + recommend_command = lib.mkEnableOption "recommend functionality" // { + default = true; + }; + + reapply_sysctl = + lib.mkEnableOption "the reapplying of global sysctls after TuneD sysctls are applied" + // { + default = true; + }; + + default_instance_priority = lib.mkOption { + type = lib.types.int; + default = 0; + description = "Default instance (unit) priority."; + }; + + profile_dirs = lib.mkOption { + type = lib.types.str; + default = "/etc/tuned/profiles"; + # Ensure we always have the vendored profiles available + apply = dirs: "${cfg.package}/lib/tuned/profiles," + dirs; + description = "Directories to search for profiles, separated by `,` or `;`."; + }; + }; + }; + + ppdSettingsSubmodule = { + freeformType = ppdSettingsFormat.type; + + options = { + main = lib.mkOption { + type = lib.types.submodule { + options = { + default = lib.mkOption { + type = lib.types.str; + default = "balanced"; + description = "Default PPD profile."; + example = "performance"; + }; + + battery_detection = lib.mkEnableOption "battery detection" // { + default = true; + }; + }; + }; + default = { }; + description = "Core configuration for power-profiles-daemon support."; + }; + + profiles = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { + power-saver = "powersave"; + balanced = "balanced"; + performance = "throughput-performance"; + }; + description = "Map of PPD profiles to native TuneD profiles."; + }; + + battery = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { + balanced = "balanced-battery"; + }; + description = "Map of PPD battery states to TuneD profiles."; + }; + }; + }; +in + +{ + options.services.tuned = { + enable = lib.mkEnableOption "TuneD"; + + package = lib.mkPackageOption pkgs "tuned" { }; + + settings = lib.mkOption { + type = lib.types.submodule settingsSubmodule; + default = { }; + description = '' + Configuration for TuneD. + See {manpage}`tuned-main.conf(5)`. + ''; + }; + + profiles = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + freeformType = profileFormat.type; + } + ); + default = { }; + description = '' + Profiles for TuneD. + See {manpage}`tuned.conf(5)`. + ''; + example = { + my-cool-profile = { + main.include = "my-other-cool-profile"; + + my_sysctl = { + type = "sysctl"; + replace = true; + + "net.core.rmem_default" = 262144; + "net.core.wmem_default" = 262144; + }; + }; + }; + }; + + ppdSupport = lib.mkEnableOption "translation of power-profiles-daemon API calls to TuneD" // { + default = true; + }; + + ppdSettings = lib.mkOption { + type = lib.types.submodule ppdSettingsSubmodule; + default = { }; + description = '' + Settings for TuneD's power-profiles-daemon compatibility service. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + # From `tuned.service` + { + assertion = config.security.polkit.enable; + message = "`services.tuned` requires `security.polkit` to be enabled."; + } + + { + assertion = cfg.settings.dynamic_tuning -> cfg.settings.daemon; + message = "`services.tuned.settings.dynamic_tuning` requires `services.tuned.settings.daemon` to be `true`."; + } + ] + # Declare service conflicts, also sourced from `tuned.service` + ++ + map + (name: { + assertion = !(moduleFromName name).enable; + message = "`services.tuned` conflicts with `${name}`."; + }) + [ + "services.auto-cpufreq" + "services.power-profiles-daemon" + "services.tlp" + ]; + + environment = { + etc = lib.mkMerge [ + { + "tuned/tuned-main.conf".source = settingsFormat.generate "tuned-main.conf" { + sections = { }; + globalSection = cfg.settings; + }; + + "tuned/ppd.conf".source = lib.mkIf cfg.ppdSupport ( + ppdSettingsFormat.generate "ppd.conf" cfg.ppdSettings + ); + } + + (lib.mapAttrs' ( + name: value: + lib.nameValuePair "tuned/profiles/${name}/tuned.conf" { + source = profileFormat.generate "tuned.conf" value; + } + ) cfg.profiles) + ]; + + systemPackages = [ cfg.package ]; + }; + + security.polkit.enable = lib.mkDefault true; + + services = { + dbus.packages = [ cfg.package ]; + + # Many DEs (like GNOME and KDE Plasma) enable PPD by default + # Let's try to make it easier to transition by only enabling this module + power-profiles-daemon.enable = false; + }; + + systemd = { + packages = [ cfg.package ]; + + services = { + tuned = { + wantedBy = [ "multi-user.target" ]; + }; + + tuned-ppd = lib.mkIf cfg.ppdSupport { + wantedBy = [ "graphical.target" ]; + }; + }; + + tmpfiles = { + packages = [ cfg.package ]; + + # NOTE(@getchoo): `cfg.package` should contain a `tuned.conf` for tmpfiles.d already. Avoid a naming conflict! + settings.tuned-profiles = { + # Required for tuned-gui + "/etc/tuned/profiles".d = { }; + }; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 905a9afc85ba..6eb52ca5c223 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1408,6 +1408,7 @@ in ttyd = handleTest ./web-servers/ttyd.nix { }; tt-rss = handleTest ./web-apps/tt-rss.nix { }; txredisapi = handleTest ./txredisapi.nix { }; + tuned = runTest ./tuned.nix; tuptime = handleTest ./tuptime.nix { }; turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix { }; turn-rs = handleTest ./turn-rs.nix { }; diff --git a/nixos/tests/tuned.nix b/nixos/tests/tuned.nix new file mode 100644 index 000000000000..ad5aacbe0fb3 --- /dev/null +++ b/nixos/tests/tuned.nix @@ -0,0 +1,51 @@ +{ pkgs, ... }: + +{ + name = "tuned"; + meta = { inherit (pkgs.tuned.meta) maintainers; }; + + nodes.machine = { + imports = [ ./common/x11.nix ]; + + services.tuned = { + enable = true; + + profiles = { + test-profile = { + sysctls = { + type = "sysctl"; + replace = true; + + "net.core.rmem_default" = 262144; + "net.core.wmem_default" = 262144; + }; + }; + }; + }; + }; + + enableOCR = true; + + testScript = '' + with subtest("Wait for service startup"): + machine.wait_for_x() + machine.wait_for_unit("tuned.service") + machine.wait_for_unit("tuned-ppd.service") + + with subtest("Get service status"): + machine.succeed("systemctl status tuned.service") + + # NOTE(@getchoo): `pkgs.tuned` provides its own `tuned.conf` for tmpfiles.d + # A naming conflict with it and a `systemd.tmpfiles.settings` entry appeared in the initial PR for this module + # This breaks the GUI in some cases, and it was annoying to figure out. Make sure it doesn't happen again! + with subtest("Ensure systemd-tmpfiles paths are configured"): + machine.succeed("systemd-tmpfiles --cat-config | grep '/etc/tuned/profiles'") + machine.succeed("systemd-tmpfiles --cat-config | grep '/run/tuned'") + + with subtest("Test GUI"): + machine.execute("tuned-gui >&2 &") + machine.wait_for_window("tuned") + machine.wait_for_text("Start TuneD Daemon") + machine.screenshot("gui") + ''; +} diff --git a/pkgs/by-name/tu/tuned/package.nix b/pkgs/by-name/tu/tuned/package.nix index 5ab810f1ac5f..be327670daf8 100644 --- a/pkgs/by-name/tu/tuned/package.nix +++ b/pkgs/by-name/tu/tuned/package.nix @@ -11,6 +11,7 @@ hdparm, iproute2, nix-update-script, + nixosTests, pkg-config, powertop, python3Packages, @@ -135,6 +136,9 @@ stdenv.mkDerivation (finalAttrs: { ''; passthru = { + tests = lib.optionalAttrs stdenv.hostPlatform.isLinux { + nixos = nixosTests.tuned; + }; updateScript = nix-update-script { }; }; From 6dacbe78803756d94d54844443ea3ec34175a72a Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Tue, 5 Aug 2025 07:47:19 -0400 Subject: [PATCH 3/3] nixos/tuned: enable upower with tuned-ppd Fixes: #431105 (cherry picked from commit 8f2a71ff49f8da7bda8f8456075e98d859821417) --- nixos/modules/services/hardware/tuned.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nixos/modules/services/hardware/tuned.nix b/nixos/modules/services/hardware/tuned.nix index 877170605ab1..1ce62f6ff011 100644 --- a/nixos/modules/services/hardware/tuned.nix +++ b/nixos/modules/services/hardware/tuned.nix @@ -172,6 +172,11 @@ in assertion = cfg.settings.dynamic_tuning -> cfg.settings.daemon; message = "`services.tuned.settings.dynamic_tuning` requires `services.tuned.settings.daemon` to be `true`."; } + + { + assertion = cfg.ppdSupport -> config.services.upower.enable; + message = "`services.tuned.ppdSupport` requires `services.upower` to be enabled."; + } ] # Declare service conflicts, also sourced from `tuned.service` ++ @@ -218,6 +223,11 @@ in # Many DEs (like GNOME and KDE Plasma) enable PPD by default # Let's try to make it easier to transition by only enabling this module power-profiles-daemon.enable = false; + + # NOTE: Required by `tuned-ppd` for handling power supply changes + # (i.e., `services.tuned.ppdSettings.main.battery_detection`) + # https://github.com/NixOS/nixpkgs/issues/431105 + upower.enable = lib.mkIf cfg.ppdSupport true; }; systemd = {