From 1bd95983b2703cb313ea301367ff3199fabd1f9d Mon Sep 17 00:00:00 2001 From: Nathan Scott Date: Fri, 6 Aug 2021 16:45:30 +1000 Subject: [PATCH] Add columns for process autogroup identifier and nice value Adds AGRP (autogroup) and ANI (autogroup nice) columns that report the information from /proc/PID/autogroup, as well as handlers for '{' and '}' to change the autogroup nice value. This is guarded by /proc/sys/kernel/sched_autogroup_enabled such that sampling and/or changing values wont be attempted unless the kernel feature is enabled. Fixes: #720 --- htop.1.in | 13 ++++++++++ linux/LinuxProcess.c | 56 ++++++++++++++++++++++++++++++++++++++++ linux/LinuxProcess.h | 9 +++++++ linux/LinuxProcessList.c | 31 ++++++++++++++++++++++ linux/LinuxProcessList.h | 1 + linux/Platform.c | 30 +++++++++++++++++++++ linux/ProcessField.h | 4 +-- pcp/PCPProcess.c | 25 ++++++++++++++++++ pcp/PCPProcess.h | 13 ++++++---- pcp/PCPProcessList.c | 20 +++++++++++++- pcp/Platform.c | 2 ++ pcp/Platform.h | 3 +++ pcp/ProcessField.h | 4 +-- 13 files changed, 201 insertions(+), 10 deletions(-) diff --git a/htop.1.in b/htop.1.in index 9b81a210..5d00c7cb 100644 --- a/htop.1.in +++ b/htop.1.in @@ -176,6 +176,13 @@ This can only be done by the superuser. .B F8, [ Decrease the selected process's priority (add to 'nice' value) .TP +.B } +Increase the selected process's autogroup priority (subtract from autogroup 'nice' value). +This can only be done by the superuser. +.TP +.B { +Decrease the selected process's autogroup priority (add to autogroup 'nice' value) +.TP .B F9, k "Kill" process: sends a signal which is selected in a menu, to one or a group of processes. If processes were tagged, sends the signal to all tagged processes. @@ -484,6 +491,12 @@ The command name for the process. Requires Linux kernel 2.6.33 or newer. .B EXE The executable file of the process as reported by the kernel. Requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED. .TP +.B AGRP +The autogroup identifier for the process. Requires Linux CFS to be enabled. +.TP +.B ANI +The autogroup nice value for the process autogroup. Requires Linux CFS to be enabled. +.TP .B All other flags Currently unsupported (always displays '-'). .SH "EXTERNAL LIBRARIES" diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index fe8492df..c353f8a7 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -100,6 +100,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process from /proc/[pid]/comm", .flags = 0, }, [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, }, [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, + [AUTOGROUP_ID] = { .name = "AUTOGROUP_ID", .title = "AGRP", .description = "The autogroup identifier of the process", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, + [AUTOGROUP_NICE] = { .name = "AUTOGROUP_NICE", .title = " ANI", .description = "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, }; Process* LinuxProcess_new(const Settings* settings) { @@ -159,6 +161,37 @@ bool LinuxProcess_setIOPriority(Process* this, Arg ioprio) { return (LinuxProcess_updateIOPriority((LinuxProcess*)this) == ioprio.i); } +bool LinuxProcess_isAutogroupEnabled(void) { + char buf[16]; + if (xReadfile(PROCDIR "/sys/kernel/sched_autogroup_enabled", buf, sizeof(buf)) < 0) + return false; + return buf[0] == '1'; +} + +bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) { + char buffer[256]; + xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", this->pid); + + FILE* file = fopen(buffer, "r+"); + if (!file) + return false; + + long int identity; + int nice; + int ok = fscanf(file, "/autogroup-%ld nice %d", &identity, &nice); + bool success; + if (ok == 2) { + rewind(file); + xSnprintf(buffer, sizeof(buffer), "%d", nice + delta.i); + success = fputs(buffer, file) > 0; + } else { + success = false; + } + + fclose(file); + return success; +} + #ifdef HAVE_DELAYACCT static void LinuxProcess_printDelay(float delay_percent, char* buffer, int n) { if (isnan(delay_percent)) { @@ -259,6 +292,25 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff); break; case SECATTR: snprintf(buffer, n, "%-30s ", lp->secattr ? lp->secattr : "?"); break; + case AUTOGROUP_ID: + if (lp->autogroup_id != -1) { + xSnprintf(buffer, n, "%4ld ", lp->autogroup_id); + } else { + attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, " N/A "); + } + break; + case AUTOGROUP_NICE: + if (lp->autogroup_id != -1) { + xSnprintf(buffer, n, "%3d ", lp->autogroup_nice); + attr = lp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY] + : lp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] + : CRT_colors[PROCESS_SHADOW]; + } else { + attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, "N/A "); + } + break; default: Process_writeField(this, str, field); return; @@ -350,6 +402,10 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff); case SECATTR: return SPACESHIP_NULLSTR(p1->secattr, p2->secattr); + case AUTOGROUP_ID: + return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id); + case AUTOGROUP_NICE: + return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice); default: return Process_compareByKey_Base(v1, v2, key); } diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index 5f77db2e..bca5247a 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -28,6 +28,7 @@ in the source distribution for its full text. #define PROCESS_FLAG_LINUX_SECATTR 0x00008000 #define PROCESS_FLAG_LINUX_LRS_FIX 0x00010000 #define PROCESS_FLAG_LINUX_DELAYACCT 0x00040000 +#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000 typedef struct LinuxProcess_ { Process super; @@ -99,6 +100,10 @@ typedef struct LinuxProcess_ { unsigned long ctxt_diff; char* secattr; unsigned long long int last_mlrs_calctime; + + /* Autogroup scheduling (CFS) information */ + long int autogroup_id; + int autogroup_nice; } LinuxProcess; extern int pageSize; @@ -117,6 +122,10 @@ IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this); bool LinuxProcess_setIOPriority(Process* this, Arg ioprio); +bool LinuxProcess_isAutogroupEnabled(void); + +bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta); + bool Process_isThread(const Process* this); #endif diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index e1b923d6..d3463b51 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -909,6 +909,23 @@ static void LinuxProcessList_readOomData(LinuxProcess* process, openat_arg_t pro fclose(file); } +static void LinuxProcessList_readAutogroup(LinuxProcess* process, openat_arg_t procFd) { + process->autogroup_id = -1; + + char autogroup[64]; // space for two numeric values and fixed length strings + ssize_t amtRead = xReadfileat(procFd, "autogroup", autogroup, sizeof(autogroup)); + if (amtRead < 0) + return; + + long int identity; + int nice; + int ok = sscanf(autogroup, "/autogroup-%ld nice %d", &identity, &nice); + if (ok == 2) { + process->autogroup_id = identity; + process->autogroup_nice = nice; + } +} + static void LinuxProcessList_readCtxtData(LinuxProcess* process, openat_arg_t procFd) { FILE* file = fopenat(procFd, "status", "r"); if (!file) @@ -1521,6 +1538,10 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ LinuxProcessList_readCwd(lp, procFd); } + if ((settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) { + LinuxProcessList_readAutogroup(lp, procFd); + } + if (proc->state == 'Z' && !proc->cmdline && statCommand[0]) { Process_updateCmdline(proc, statCommand, 0, strlen(statCommand)); } else if (Process_isThread(proc)) { @@ -2071,6 +2092,16 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { return; } + if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) { + // Refer to sched(7) 'autogroup feature' section + // The kernel feature can be enabled/disabled through procfs at + // any time, so check for it at the start of each sample - only + // read from per-process procfs files if it's globally enabled. + this->haveAutogroup = LinuxProcess_isAutogroupEnabled(); + } else { + this->haveAutogroup = false; + } + /* PROCDIR is an absolute path */ assert(PROCDIR[0] == '/'); #ifdef HAVE_OPENAT diff --git a/linux/LinuxProcessList.h b/linux/LinuxProcessList.h index 2f296eca..0d1ad48c 100644 --- a/linux/LinuxProcessList.h +++ b/linux/LinuxProcessList.h @@ -71,6 +71,7 @@ typedef struct LinuxProcessList_ { TtyDriver* ttyDrivers; bool haveSmapsRollup; + bool haveAutogroup; #ifdef HAVE_DELAYACCT struct nl_sock* netlink_socket; diff --git a/linux/Platform.c b/linux/Platform.c index d23bf57d..64be93c7 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -150,8 +150,38 @@ static Htop_Reaction Platform_actionSetIOPriority(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } +static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) { + if (LinuxProcess_isAutogroupEnabled() == false) { + beep(); + return false; + } + bool anyTagged; + bool ok = MainPanel_foreachProcess(panel, LinuxProcess_changeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged); + if (!ok) + beep(); + return anyTagged; +} + +static Htop_Reaction Platform_actionHigherAutogroupPriority(State* st) { + if (Settings_isReadonly()) + return HTOP_OK; + + bool changed = Platform_changeAutogroupPriority(st->mainPanel, -1); + return changed ? HTOP_REFRESH : HTOP_OK; +} + +static Htop_Reaction Platform_actionLowerAutogroupPriority(State* st) { + if (Settings_isReadonly()) + return HTOP_OK; + + bool changed = Platform_changeAutogroupPriority(st->mainPanel, 1); + return changed ? HTOP_REFRESH : HTOP_OK; +} + void Platform_setBindings(Htop_Action* keys) { keys['i'] = Platform_actionSetIOPriority; + keys['{'] = Platform_actionLowerAutogroupPriority; + keys['}'] = Platform_actionHigherAutogroupPriority; } const MeterClass* const Platform_meterTypes[] = { diff --git a/linux/ProcessField.h b/linux/ProcessField.h index c8d24c0b..d109ff9d 100644 --- a/linux/ProcessField.h +++ b/linux/ProcessField.h @@ -44,8 +44,8 @@ in the source distribution for its full text. M_PSSWP = 121, \ CTXT = 122, \ SECATTR = 123, \ - \ - DUMMY_BUMP_FIELD = CWD, \ + AUTOGROUP_ID = 127, \ + AUTOGROUP_NICE = 128, \ // End of list diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c index 993a746f..de709110 100644 --- a/pcp/PCPProcess.c +++ b/pcp/PCPProcess.c @@ -82,6 +82,8 @@ const ProcessFieldData Process_fields[] = { [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process", .flags = 0, }, [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, }, [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, + [AUTOGROUP_ID] = { .name = "AUTOGROUP_ID", .title = "AGRP", .description = "The autogroup identifier of the process", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, + [AUTOGROUP_NICE] = { .name = "AUTOGROUP_NICE", .title = " ANI", .description = "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, }; Process* PCPProcess_new(const Settings* settings) { @@ -168,6 +170,25 @@ static void PCPProcess_writeField(const Process* this, RichString* str, ProcessF xSnprintf(buffer, n, "%5lu ", pp->ctxt_diff); break; case SECATTR: snprintf(buffer, n, "%-30s ", pp->secattr ? pp->secattr : "?"); break; + case AUTOGROUP_ID: + if (pp->autogroup_id != -1) { + xSnprintf(buffer, n, "%4ld ", pp->autogroup_id); + } else { + attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, " N/A "); + } + break; + case AUTOGROUP_NICE: + if (pp->autogroup_id != -1) { + xSnprintf(buffer, n, "%3d ", pp->autogroup_nice); + attr = pp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY] + : pp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] + : CRT_colors[PROCESS_SHADOW]; + } else { + attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, "N/A "); + } + break; default: Process_writeField(this, str, field); return; @@ -245,6 +266,10 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff); case SECATTR: return SPACESHIP_NULLSTR(p1->secattr, p2->secattr); + case AUTOGROUP_ID: + return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id); + case AUTOGROUP_NICE: + return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice); default: return Process_compareByKey_Base(v1, v2, key); } diff --git a/pcp/PCPProcess.h b/pcp/PCPProcess.h index 25707f2b..2d1a8b6c 100644 --- a/pcp/PCPProcess.h +++ b/pcp/PCPProcess.h @@ -20,11 +20,12 @@ in the source distribution for its full text. #include "Settings.h" -#define PROCESS_FLAG_LINUX_CGROUP 0x0800 -#define PROCESS_FLAG_LINUX_OOM 0x1000 -#define PROCESS_FLAG_LINUX_SMAPS 0x2000 -#define PROCESS_FLAG_LINUX_CTXT 0x4000 -#define PROCESS_FLAG_LINUX_SECATTR 0x8000 +#define PROCESS_FLAG_LINUX_CGROUP 0x00000800 +#define PROCESS_FLAG_LINUX_OOM 0x00001000 +#define PROCESS_FLAG_LINUX_SMAPS 0x00002000 +#define PROCESS_FLAG_LINUX_CTXT 0x00004000 +#define PROCESS_FLAG_LINUX_SECATTR 0x00008000 +#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000 typedef struct PCPProcess_ { Process super; @@ -70,6 +71,8 @@ typedef struct PCPProcess_ { double io_rate_read_bps; double io_rate_write_bps; char* cgroup; + long int autogroup_id; + int autogroup_nice; unsigned int oom; unsigned long long int delay_read_time; unsigned long long cpu_delay_total; diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c index ae974435..b0fe7666 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessList.c @@ -89,13 +89,20 @@ void ProcessList_delete(ProcessList* pl) { free(this); } -static inline unsigned long Metric_instance_s32(int metric, int pid, int offset, unsigned long fallback) { +static inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) { pmAtomValue value; if (Metric_instance(metric, pid, offset, &value, PM_TYPE_32)) return value.l; return fallback; } +static inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) { + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_64)) + return value.l; + return fallback; +} + static inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) { pmAtomValue value; if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U32)) @@ -222,6 +229,11 @@ static void PCPProcessList_readOomData(PCPProcess* pp, int pid, int offset) { pp->oom = Metric_instance_u32(PCP_PROC_OOMSCORE, pid, offset, 0); } +static void PCPProcessList_readAutogroup(PCPProcess* pp, int pid, int offset) { + pp->autogroup_id = Metric_instance_s64(PCP_PROC_AUTOGROUP_ID, pid, offset, -1); + pp->autogroup_nice = Metric_instance_s32(PCP_PROC_AUTOGROUP_NICE, pid, offset, 0); +} + static void PCPProcessList_readCtxtData(PCPProcess* pp, int pid, int offset) { pmAtomValue value; unsigned long ctxt = 0; @@ -403,6 +415,9 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, if (settings->flags & PROCESS_FLAG_CWD) PCPProcessList_readCwd(pp, pid, offset); + if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) + PCPProcessList_readAutogroup(pp, pid, offset); + if (proc->state == 'Z' && !proc->cmdline && command[0]) { Process_updateCmdline(proc, command, 0, strlen(command)); } else if (Process_isThread(proc)) { @@ -651,6 +666,9 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { Metric_enable(PCP_PROC_NVCTXSW, flagged && enabled); flagged = settings->flags & PROCESS_FLAG_LINUX_SECATTR; Metric_enable(PCP_PROC_LABELS, flagged && enabled); + flagged = settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP; + Metric_enable(PCP_PROC_AUTOGROUP_ID, flagged && enabled); + Metric_enable(PCP_PROC_AUTOGROUP_NICE, flagged && enabled); /* Sample smaps metrics on every second pass to improve performance */ static int smaps_flag; diff --git a/pcp/Platform.c b/pcp/Platform.c index 4ddf7e68..63ff50a7 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -224,6 +224,8 @@ static const char* Platform_metricNames[] = { [PCP_PROC_TTYNAME] = "proc.psinfo.ttyname", [PCP_PROC_EXE] = "proc.psinfo.exe", [PCP_PROC_CWD] = "proc.psinfo.cwd", + [PCP_PROC_AUTOGROUP_ID] = "proc.autogroup.id", + [PCP_PROC_AUTOGROUP_NICE] = "proc.autogroup.nice", [PCP_PROC_ID_UID] = "proc.id.uid", [PCP_PROC_ID_USER] = "proc.id.uid_nm", [PCP_PROC_IO_RCHAR] = "proc.io.rchar", diff --git a/pcp/Platform.h b/pcp/Platform.h index 6b0215c8..527bef29 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -210,6 +210,9 @@ typedef enum Metric_ { PCP_PROC_EXE, /* proc.psinfo.exe */ PCP_PROC_CWD, /* proc.psinfo.cwd */ + PCP_PROC_AUTOGROUP_ID, /* proc.autogroup.id */ + PCP_PROC_AUTOGROUP_NICE, /* proc.autogroup.nice */ + PCP_PROC_ID_UID, /* proc.id.uid */ PCP_PROC_ID_USER, /* proc.id.uid_nm */ diff --git a/pcp/ProcessField.h b/pcp/ProcessField.h index 3d5cb743..979addc0 100644 --- a/pcp/ProcessField.h +++ b/pcp/ProcessField.h @@ -43,8 +43,8 @@ in the source distribution for its full text. M_PSSWP = 121, \ CTXT = 122, \ SECATTR = 123, \ - \ - DUMMY_BUMP_FIELD = CWD, \ + AUTOGROUP_ID = 127, \ + AUTOGROUP_NICE = 128, \ // End of list