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.
This commit is contained in:
Bart Bakker 2023-04-04 23:06:27 +02:00
parent 017b12de5b
commit 8e1ed125fe
No known key found for this signature in database
GPG key ID: 8BC1D8F4C6C7B548
4 changed files with 178 additions and 32 deletions

View file

@ -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";
};
};
}

View file

@ -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;
}

View file

@ -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}
'';
}

View file

@ -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