Skip to content

Commit

Permalink
Add support for scheduling policies
Browse files Browse the repository at this point in the history
Add a process column for scheduling policy to show the current
scheduling policy of the process.

Add a the ability to change the scheduling policy of a process via the
key 'Y'.

Currently implemented on Linux and FreeBSD only but should be portable,
since sched_getscheduler(2) is part of POSIX.1-2001.

Closes: #1161
  • Loading branch information
cgzones authored and BenBE committed Feb 4, 2023
1 parent f1da8cf commit da49489
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 0 deletions.
52 changes: 52 additions & 0 deletions Action.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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" },
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ myhtopsources = \
ProcessList.c \
ProcessLocksScreen.c \
RichString.c \
Scheduling.c \
ScreenManager.c \
ScreensPanel.c \
Settings.c \
Expand Down Expand Up @@ -135,6 +136,7 @@ myhtopheaders = \
ProcessLocksScreen.h \
ProvideCurses.h \
RichString.h \
Scheduling.h \
ScreenManager.h \
ScreensPanel.h \
Settings.h \
Expand Down
12 changes: 12 additions & 0 deletions Process.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down
154 changes: 154 additions & 0 deletions Scheduling.c
Original file line number Diff line number Diff line change
@@ -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 <errno.h>

#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, &param);

/* 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 */
49 changes: 49 additions & 0 deletions Scheduling.h
Original file line number Diff line number Diff line change
@@ -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 <sched.h>
#include <stdbool.h>

#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 */
2 changes: 2 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ AC_CHECK_FUNCS([ \
memfd_create\
openat \
readlinkat \
sched_getscheduler \
sched_setscheduler \
])

if test "$my_htop_platform" = darwin; then
Expand Down
4 changes: 4 additions & 0 deletions freebsd/FreeBSDProcess.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"


Expand Down Expand Up @@ -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, },
Expand Down

0 comments on commit da49489

Please sign in to comment.