From 8e1ed125febf0888dba8cf6b735d331bacc6d06c Mon Sep 17 00:00:00 2001 From: Bart Bakker Date: Tue, 4 Apr 2023 23:06:27 +0200 Subject: [PATCH] htop: support screens configuration `htop` has changed the singular fields list to a configuration of multiple screens. Each screen has a name and its own set of fields to show, plus some options to control sorting and view style (tree or flat). Screen names can be shown with the `screen_tabs` setting. Fields in screens are set with their names (as strings) rather than numbers. Extend the htop module to allow configuring screens with a defined set of options. Order the screens with dag topology. Configurations without screens are converted automatically by `htop` on load. This feature was requested in #3616. --- modules/programs/htop.nix | 153 +++++++++++++++++++----- tests/modules/programs/htop/default.nix | 1 + tests/modules/programs/htop/screens.nix | 39 ++++++ tests/modules/programs/htop/screens.txt | 17 +++ 4 files changed, 178 insertions(+), 32 deletions(-) create mode 100644 tests/modules/programs/htop/screens.nix create mode 100644 tests/modules/programs/htop/screens.txt diff --git a/modules/programs/htop.nix b/modules/programs/htop.nix index 1c569c40..c1bee25b 100644 --- a/modules/programs/htop.nix +++ b/modules/programs/htop.nix @@ -76,21 +76,6 @@ let M_PSSWP = 120; }; - defaultFields = with fields; [ - PID - USER - PRIORITY - NICE - M_SIZE - M_RESIDENT - M_SHARE - STATE - PERCENT_CPU - PERCENT_MEM - TIME - COMM - ]; - modes = { Bar = 1; Text = 2; @@ -106,6 +91,48 @@ let led = meter modes.LED; blank = text "Blank"; + screenOptions = { + name = mkOption { type = types.str; }; + + fields = mkOption { + type = types.oneOf [ types.str (types.listOf types.str) ]; + default = "PID USER M_VIRT STATE PERCENT_CPU PERCENT_MEM TIME Command"; + }; + + all_branches_collapsed = mkOption { + type = types.bool; + default = false; + }; + + sort_direction = mkOption { + type = types.enum [ (-1) 1 ]; + default = -1; + }; + sort_key = mkOption { + type = types.str; + example = "PERCENT_MEM"; + }; + + tree_sort_direction = mkOption { + type = types.enum [ (-1) 1 ]; + default = -1; + }; + tree_sort_key = mkOption { + type = types.str; + example = "PERCENT_MEM"; + }; + + tree_view = mkOption { + type = types.bool; + default = false; + }; + + tree_view_always_by_pid = mkOption { + type = types.bool; + default = false; + }; + }; + in { meta.maintainers = [ hm.maintainers.bjpbakker ]; @@ -155,6 +182,38 @@ in { ''; }; + screens = mkOption { + type = hm.types.dagOf (types.submodule ({ dagName, ... }: { + options = screenOptions; + config.name = mkDefault dagName; + })); + default = { }; + example = literalExpression '' + { + "Main" = { + fields = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command"; + sort_key = "PERCENT_MEM"; + tree_sort_key = "PERCENT_MEM"; + tree_view = false; + tree_view_always_by_pid = false; + sort_direction = -1; + tree_sort_direction = -1; + all_branches_collapsed = false; + }; + "I/O" = lib.hm.dag.entryAfter ["Main"] { + fields = "PID STATE STARTTIME M_RESIDENT COMM EXE USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE"; + sort_key = "IO_RATE"; + tree_sort_key = "PID"; + tree_view = false; + tree_view_always_by_pid = false; + sort_direction = -1; + tree_sort_direction = -1; + all_branches_collapsed = false; + }; + }; + ''; + }; + package = mkOption { type = types.package; default = pkgs.htop; @@ -171,25 +230,55 @@ in { home.packages = [ cfg.package ]; xdg.configFile."htop/htoprc" = let - defaults = { - fields = if isDarwin then - remove fields.M_SHARE defaultFields - else - defaultFields; - }; - - before = optionalAttrs (cfg.settings ? header_layout) { - inherit (cfg.settings) header_layout; - }; - - settings = defaults // (removeAttrs cfg.settings (attrNames before)); - formatOptions = mapAttrsToList formatOption; - in mkIf (cfg.settings != { }) { - text = - concatStringsSep "\n" (formatOptions before ++ formatOptions settings) - + "\n"; + hasScreens = cfg.screens != { }; + + settings = let + # old (no screen) configuration support + defaultFields = let + defaults = with fields; [ + PID + USER + PRIORITY + NICE + M_SIZE + M_RESIDENT + M_SHARE + STATE + PERCENT_CPU + PERCENT_MEM + TIME + COMM + ]; + in if isDarwin then remove fields.M_SHARE defaults else defaults; + + oldDefaults = optionalAttrs (!hasScreens) { fields = defaultFields; }; + + leading = optionalAttrs (cfg.settings ? header_layout) { + inherit (cfg.settings) header_layout; + }; + + settings' = oldDefaults + // (removeAttrs cfg.settings (attrNames leading)); + in formatOptions leading ++ formatOptions settings'; + + screens = let + formatOption' = k: formatOption ".${k}"; + formatScreen = { name, fields, ... }@screen: + let + options = removeAttrs screen [ "fields" "name" ]; + newScreen = "screen:${formatOption name fields}"; + in [ newScreen ] ++ mapAttrsToList formatOption' options; + + screens' = let sorted = hm.dag.topoSort cfg.screens; + in sorted.result or (abort + "Dependency cycle in htop screens: ${builtins.toJSON sorted}"); + + in concatMap (x: formatScreen x.data) screens'; + + in mkIf (cfg.settings != { } || hasScreens) { + text = concatStringsSep "\n" (settings ++ screens) + "\n"; }; }; } diff --git a/tests/modules/programs/htop/default.nix b/tests/modules/programs/htop/default.nix index e63d74fd..327d7a14 100644 --- a/tests/modules/programs/htop/default.nix +++ b/tests/modules/programs/htop/default.nix @@ -2,5 +2,6 @@ htop-empty-settings = ./empty-settings.nix; htop-example-settings = ./example-settings.nix; htop-header_layout = ./header_layout.nix; + htop-screens = ./screens.nix; htop-settings-without-fields = ./settings-without-fields.nix; } diff --git a/tests/modules/programs/htop/screens.nix b/tests/modules/programs/htop/screens.nix new file mode 100644 index 00000000..40141c4d --- /dev/null +++ b/tests/modules/programs/htop/screens.nix @@ -0,0 +1,39 @@ +{ lib, ... }: + +{ + programs.htop.enable = true; + programs.htop.settings = { screen_tabs = true; }; + programs.htop.screens = { + "Main" = { + fields = + "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command"; + sort_key = "PERCENT_MEM"; + tree_sort_key = "PERCENT_MEM"; + tree_view = false; + tree_view_always_by_pid = false; + sort_direction = -1; + tree_sort_direction = -1; + all_branches_collapsed = false; + }; + "I/O" = lib.hm.dag.entryAfter [ "Main" ] { + fields = + "PID STATE STARTTIME M_RESIDENT COMM EXE USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE"; + sort_key = "IO_RATE"; + tree_sort_key = "PID"; + tree_view = false; + tree_view_always_by_pid = false; + sort_direction = -1; + tree_sort_direction = -1; + all_branches_collapsed = false; + }; + }; + + test.stubs.htop = { }; + + nmt.script = '' + htoprc=home-files/.config/htop/htoprc + assertFileExists $htoprc + assertFileContent $htoprc ${./screens.txt} + ''; + +} diff --git a/tests/modules/programs/htop/screens.txt b/tests/modules/programs/htop/screens.txt new file mode 100644 index 00000000..10bd884b --- /dev/null +++ b/tests/modules/programs/htop/screens.txt @@ -0,0 +1,17 @@ +screen_tabs=1 +screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command +.all_branches_collapsed=0 +.sort_direction=-1 +.sort_key=PERCENT_MEM +.tree_sort_direction=-1 +.tree_sort_key=PERCENT_MEM +.tree_view=0 +.tree_view_always_by_pid=0 +screen:I/O=PID STATE STARTTIME M_RESIDENT COMM EXE USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE +.all_branches_collapsed=0 +.sort_direction=-1 +.sort_key=IO_RATE +.tree_sort_direction=-1 +.tree_sort_key=PID +.tree_view=0 +.tree_view_always_by_pid=0