diff --git a/Action.c b/Action.c index 81432f5de..5d33f6624 100644 --- a/Action.c +++ b/Action.c @@ -29,6 +29,7 @@ in the source distribution for its full text. #include "Process.h" #include "ProcessLocksScreen.h" #include "ProvideCurses.h" +#include "Scheduling.h" #include "ScreenManager.h" #include "SignalsPanel.h" #include "TraceScreen.h" @@ -407,6 +408,51 @@ static Htop_Reaction actionSetAffinity(State* st) { } +#ifdef SCHEDULER_SUPPORT +static Htop_Reaction actionSetSchedPolicy(State* st) { + if (Settings_isReadonly()) + return HTOP_KEEP_FOLLOWING; + + static int preSelectedPolicy = SCHEDULINGPANEL_INITSELECTEDPOLICY; + static int preSelectedPriority = SCHEDULINGPANEL_INITSELECTEDPRIORITY; + + Panel* schedPanel = Scheduling_newPolicyPanel(preSelectedPolicy); + + const ListItem* policy; + for(;;) { + policy = (const ListItem*) Action_pickFromVector(st, schedPanel, 18, true); + + if (!policy || policy->key != -1) + break; + + Scheduling_togglePolicyPanelResetOnFork(schedPanel); + } + + if (policy) { + preSelectedPolicy = policy->key; + + Panel* prioPanel = Scheduling_newPriorityPanel(policy->key, preSelectedPriority); + if (prioPanel) { + const ListItem* prio = (const ListItem*) Action_pickFromVector(st, prioPanel, 14, true); + if (prio) + preSelectedPriority = prio->key; + + Panel_delete((Object*) prioPanel); + } + + SchedulingArg v = { .policy = preSelectedPolicy, .priority = preSelectedPriority }; + + bool ok = MainPanel_foreachProcess(st->mainPanel, Scheduling_setPolicy, (Arg) { .v = &v }, NULL); + if (!ok) + beep(); + } + + Panel_delete((Object*)schedPanel); + + return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING; +} +#endif /* SCHEDULER_SUPPORT */ + static Htop_Reaction actionKill(State* st) { if (Settings_isReadonly()) return HTOP_OK; @@ -571,6 +617,9 @@ static const struct { { .key = " x: ", .roInactive = false, .info = "list file locks of process" }, { .key = " s: ", .roInactive = true, .info = "trace syscalls with strace" }, { .key = " w: ", .roInactive = false, .info = "wrap process command in multiple lines" }, +#ifdef SCHEDULER_SUPPORT + { .key = " Y: ", .roInactive = true, .info = "set scheduling policy" }, +#endif { .key = " F2 C S: ", .roInactive = false, .info = "setup" }, { .key = " F1 h ?: ", .roInactive = false, .info = "show this help screen" }, { .key = " F10 q: ", .roInactive = false, .info = "quit" }, @@ -779,6 +828,9 @@ void Action_setBindings(Htop_Action* keys) { keys['S'] = actionSetup; keys['T'] = actionSortByTime; keys['U'] = actionUntagAll; +#ifdef SCHEDULER_SUPPORT + keys['Y'] = actionSetSchedPolicy; +#endif keys['Z'] = actionTogglePauseProcessUpdate; keys['['] = actionLowerPriority; keys['\014'] = actionRedraw; // Ctrl+L diff --git a/Makefile.am b/Makefile.am index 8af1864ac..1c685e4d3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -73,6 +73,7 @@ myhtopsources = \ ProcessList.c \ ProcessLocksScreen.c \ RichString.c \ + Scheduling.c \ ScreenManager.c \ ScreensPanel.c \ Settings.c \ @@ -135,6 +136,7 @@ myhtopheaders = \ ProcessLocksScreen.h \ ProvideCurses.h \ RichString.h \ + Scheduling.h \ ScreenManager.h \ ScreensPanel.h \ Settings.h \ diff --git a/Process.c b/Process.c index fcaa3d540..614369b40 100644 --- a/Process.c +++ b/Process.c @@ -28,6 +28,7 @@ in the source distribution for its full text. #include "ProcessList.h" #include "DynamicColumn.h" #include "RichString.h" +#include "Scheduling.h" #include "Settings.h" #include "XUtils.h" @@ -960,6 +961,15 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field xSnprintf(buffer, n, "%3ld ", this->priority); break; case PROCESSOR: xSnprintf(buffer, n, "%3d ", Settings_cpuId(this->settings, this->processor)); break; + case SCHEDULERPOLICY: { + const char* schedPolStr = "N/A"; +#ifdef SCHEDULER_SUPPORT + if (this->scheduling_policy >= 0) + schedPolStr = Scheduling_formatPolicy(this->scheduling_policy); +#endif + xSnprintf(buffer, n, "%-5s ", schedPolStr); + break; + } case SESSION: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->session); break; case STARTTIME: xSnprintf(buffer, n, "%s", this->starttime_show); break; case STATE: @@ -1203,6 +1213,8 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField return SPACESHIP_NUMBER(p1->priority, p2->priority); case PROCESSOR: return SPACESHIP_NUMBER(p1->processor, p2->processor); + case SCHEDULERPOLICY: + return SPACESHIP_NUMBER(p1->scheduling_policy, p2->scheduling_policy); case SESSION: return SPACESHIP_NUMBER(p1->session, p2->session); case STARTTIME: diff --git a/Process.h b/Process.h index eb79470d3..0fdc392be 100644 --- a/Process.h +++ b/Process.h @@ -19,6 +19,7 @@ in the source distribution for its full text. #define PROCESS_FLAG_IO 0x00000001 #define PROCESS_FLAG_CWD 0x00000002 +#define PROCESS_FLAG_SCHEDPOL 0x00000004 #define DEFAULT_HIGHLIGHT_SECS 5 @@ -49,6 +50,7 @@ typedef enum ProcessField_ { TGID = 52, PERCENT_NORM_CPU = 53, ELAPSED = 54, + SCHEDULERPOLICY = 55, PROC_COMM = 124, PROC_EXE = 125, CWD = 126, @@ -221,6 +223,9 @@ typedef struct Process_ { /* Process state enum field (platform dependent) */ ProcessState state; + /* Current scheduling policy */ + int scheduling_policy; + /* Whether the process was updated during the current scan */ bool updated; diff --git a/Scheduling.c b/Scheduling.c new file mode 100644 index 000000000..5ca49ae2d --- /dev/null +++ b/Scheduling.c @@ -0,0 +1,154 @@ +/* +htop - Scheduling.c +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Scheduling.h" +#include "EnvScreen.h" + +#ifdef SCHEDULER_SUPPORT + +#include + +#include "FunctionBar.h" +#include "ListItem.h" +#include "Macros.h" +#include "Object.h" +#include "Panel.h" +#include "XUtils.h" + + +static const SchedulingPolicy policies[] = { + [SCHED_OTHER] = { "Other", SCHED_OTHER, false }, +#ifdef SCHED_BATCH + [SCHED_BATCH] = { "Batch", SCHED_BATCH, false }, +#endif +#ifdef SCHED_IDLE + [SCHED_IDLE] = { "Idle", SCHED_IDLE, false }, +#endif + [SCHED_FIFO] = { "FiFo", SCHED_FIFO, true }, + [SCHED_RR] = { "RoundRobin", SCHED_RR, true }, +}; + +#ifdef SCHED_RESET_ON_FORK +static bool reset_on_fork = false; +#endif + + +Panel* Scheduling_newPolicyPanel(int preSelectedPolicy) { + Panel* this = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Select ", "Cancel ")); + Panel_setHeader(this, "New policy:"); + +#ifdef SCHED_RESET_ON_FORK + Panel_add(this, (Object*) ListItem_new(reset_on_fork ? "Reset on fork: on" : "Reset on fork: off", -1)); +#endif + + for (unsigned i = 0; i < ARRAYSIZE(policies); i++) { + if (!policies[i].name) + continue; + + Panel_add(this, (Object*) ListItem_new(policies[i].name, policies[i].id)); + if (policies[i].id == preSelectedPolicy) + Panel_setSelected(this, i); + } + + return this; +} + +void Scheduling_togglePolicyPanelResetOnFork(Panel* schedPanel) { +#ifdef SCHED_RESET_ON_FORK + reset_on_fork = !reset_on_fork; + + ListItem* item = (ListItem*) Panel_get(schedPanel, 0); + + free_and_xStrdup(&item->value, reset_on_fork ? "Reset on fork: on" : "Reset on fork: off"); +#else + (void)schedPanel; +#endif +} + +Panel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority) { + if (policy < 0 || (unsigned)policy >= ARRAYSIZE(policies) || policies[policy].name == NULL) + return NULL; + + if (!policies[policy].prioritySupport) + return NULL; + + int min = sched_get_priority_min(policy); + if (min < 0) + return NULL; + int max = sched_get_priority_max(policy); + if (max < 0 ) + return NULL; + + Panel* this = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Select ", "Cancel ")); + Panel_setHeader(this, "Priority:"); + + for (int i = min; i <= max; i++) { + char buf[16]; + xSnprintf(buf, sizeof(buf), "%d", i); + Panel_add(this, (Object*) ListItem_new(buf, i)); + if (i == preSelectedPriority) + Panel_setSelected(this, i); + } + + return this; +} + +bool Scheduling_setPolicy(Process* proc, Arg arg) { + const SchedulingArg* sarg = arg.v; + int policy = sarg->policy; + + assert(policy >= 0); + assert((unsigned)policy < ARRAYSIZE(policies)); + assert(policies[policy].name); + + const struct sched_param param = { .sched_priority = policies[policy].prioritySupport ? sarg->priority : 0 }; + + #ifdef SCHED_RESET_ON_FORK + if (reset_on_fork) + policy &= SCHED_RESET_ON_FORK; + #endif + + int r = sched_setscheduler(proc->pid, policy, ¶m); + + /* POSIX says on success the previous scheduling policy should be returned, + * but Linux always returns 0. */ + return r != -1; +} + +const char* Scheduling_formatPolicy(int policy) { +#ifdef SCHED_RESET_ON_FORK + policy = policy & ~SCHED_RESET_ON_FORK; +#endif + + switch (policy) { + case SCHED_OTHER: + return "OTHER"; + case SCHED_FIFO: + return "FIFO"; + case SCHED_RR: + return "RR"; +#ifdef SCHED_BATCH + case SCHED_BATCH: + return "BATCH"; +#endif +#ifdef SCHED_IDLE + case SCHED_IDLE: + return "IDLE"; +#endif +#ifdef SCHED_DEADLINE + case SCHED_DEADLINE: + return "EDF"; +#endif + default: + return "???"; + } +} + +void Scheduling_readProcessPolicy(Process* proc) { + proc->scheduling_policy = sched_getscheduler(proc->pid); +} +#endif /* SCHEDULER_SUPPORT */ diff --git a/Scheduling.h b/Scheduling.h new file mode 100644 index 000000000..d91855ae4 --- /dev/null +++ b/Scheduling.h @@ -0,0 +1,49 @@ +#ifndef HEADER_Scheduling +#define HEADER_Scheduling +/* +htop - Scheduling.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include +#include + +#include "Panel.h" + + +#if defined(HAVE_SCHED_SETSCHEDULER) && defined(HAVE_SCHED_GETSCHEDULER) +#define SCHEDULER_SUPPORT + +typedef struct { + const char* name; + int id; + bool prioritySupport; +} SchedulingPolicy; + +#define SCHEDULINGPANEL_INITSELECTEDPOLICY SCHED_OTHER +#define SCHEDULINGPANEL_INITSELECTEDPRIORITY 50 + +Panel* Scheduling_newPolicyPanel(int preSelectedPolicy); +void Scheduling_togglePolicyPanelResetOnFork(Panel* schedPanel); + +Panel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority); + + +typedef struct { + int policy; + int priority; +} SchedulingArg; + +bool Scheduling_setPolicy(Process* proc, Arg arg); + +const char* Scheduling_formatPolicy(int policy); + +void Scheduling_readProcessPolicy(Process* proc); + +#endif + +#endif /* HEADER_Scheduling */ diff --git a/configure.ac b/configure.ac index de0718e14..73a8e91c8 100644 --- a/configure.ac +++ b/configure.ac @@ -260,6 +260,8 @@ AC_CHECK_FUNCS([ \ memfd_create\ openat \ readlinkat \ + sched_getscheduler \ + sched_setscheduler \ ]) if test "$my_htop_platform" = darwin; then diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c index 4eccfe7fd..90bc1f2c3 100644 --- a/freebsd/FreeBSDProcess.c +++ b/freebsd/FreeBSDProcess.c @@ -13,6 +13,7 @@ in the source distribution for its full text. #include "Macros.h" #include "Process.h" #include "RichString.h" +#include "Scheduling.h" #include "XUtils.h" @@ -47,6 +48,9 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [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, }, +#ifdef SCHEDULER_SUPPORT + [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, }, +#endif [JID] = { .name = "JID", .title = "JID", .description = "Jail prison ID", .flags = 0, .pidColumn = true, }, [JAIL] = { .name = "JAIL", .title = "JAIL ", .description = "Jail prison name", .flags = 0, }, [EMULATION] = { .name = "EMULATION", .title = "EMULATION ", .description = "System call emulation environment (ABI)", .flags = 0, }, diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessList.c index 9ee5ea402..98447df9a 100644 --- a/freebsd/FreeBSDProcessList.c +++ b/freebsd/FreeBSDProcessList.c @@ -34,6 +34,7 @@ in the source distribution for its full text. #include "Object.h" #include "Process.h" #include "ProcessList.h" +#include "Scheduling.h" #include "Settings.h" #include "XUtils.h" #include "generic/openzfs_sysctl.h" @@ -606,6 +607,11 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { if (Process_isKernelThread(proc)) super->kernelThreads++; +#ifdef SCHEDULER_SUPPORT + if (settings->ss->flags & PROCESS_FLAG_SCHEDPOL) + Scheduling_readProcessPolicy(proc); +#endif + proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); super->totalTasks++; diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 381b7cf5f..8f9c34620 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -19,6 +19,7 @@ in the source distribution for its full text. #include "Process.h" #include "ProvideCurses.h" #include "RichString.h" +#include "Scheduling.h" #include "XUtils.h" #include "linux/IOPriority.h" @@ -100,6 +101,9 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [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, }, +#ifdef SCHEDULER_SUPPORT + [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, }, +#endif }; Process* LinuxProcess_new(const Settings* settings) { diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index eca9459b5..8490d82c1 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -44,6 +44,7 @@ in the source distribution for its full text. #include "Macros.h" #include "Object.h" #include "Process.h" +#include "Scheduling.h" #include "Settings.h" #include "XUtils.h" #include "linux/CGroupUtils.h" @@ -1721,6 +1722,12 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ LinuxProcessList_readAutogroup(lp, procFd); } + #ifdef SCHEDULER_SUPPORT + if (ss->flags & PROCESS_FLAG_SCHEDPOL) { + Scheduling_readProcessPolicy(proc); + } + #endif + if (!proc->cmdline && statCommand[0] && (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) { Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));