Improving Command display/sort

This commit is contained in:
Narendran Gopalakrishnan 2020-10-17 16:24:45 +05:30 committed by BenBE
parent 42c842c190
commit 09fe94da18
18 changed files with 593 additions and 16 deletions

View File

@ -224,6 +224,11 @@ static Htop_Reaction actionToggleProgramPath(State* st) {
return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
}
static Htop_Reaction actionToggleMergedCommand(State* st) {
st->settings->showMergedCommand = !st->settings->showMergedCommand;
return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
}
static Htop_Reaction actionToggleTreeView(State* st) {
st->settings->treeView = !st->settings->treeView;
if (st->settings->treeView) {
@ -450,6 +455,7 @@ static const struct {
{ .key = " F4 \\: ",.info = "incremental name filtering" },
{ .key = " F5 t: ", .info = "tree view" },
{ .key = " p: ", .info = "toggle program path" },
{ .key = " m: ", .info = "toggle merged command" },
{ .key = " Z: ", .info = "pause/resume process updates" },
{ .key = " u: ", .info = "show processes of a single user" },
{ .key = " H: ", .info = "hide/show user process threads" },
@ -640,6 +646,7 @@ void Action_setBindings(Htop_Action* keys) {
keys['H'] = actionToggleUserlandThreads;
keys['K'] = actionToggleKernelThreads;
keys['p'] = actionToggleProgramPath;
keys['m'] = actionToggleMergedCommand;
keys['t'] = actionToggleTreeView;
keys[KEY_F(5)] = actionToggleTreeView;
keys[KEY_F(4)] = actionIncFilter;

18
CRT.c
View File

@ -105,6 +105,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),
[PANEL_SELECTION_UNFOCUS] = ColorPair(Black, White),
[FAILED_SEARCH] = ColorPair(Red, Cyan),
[FAILED_READ] = A_BOLD | ColorPair(Red, Black),
[PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),
[UPTIME] = A_BOLD | ColorPair(Cyan, Black),
[BATTERY] = A_BOLD | ColorPair(Cyan, Black),
@ -133,6 +134,8 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_TOMB] = ColorPair(Black, Red),
[PROCESS_THREAD] = ColorPair(Green, Black),
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Green, Black),
[PROCESS_COMM] = ColorPair(Magenta, Black),
[PROCESS_THREAD_COMM] = ColorPair(Blue, Black),
[BAR_BORDER] = A_BOLD,
[BAR_SHADOW] = A_BOLD | ColorPairGrayBlack,
[SWAP] = ColorPair(Red, Black),
@ -186,6 +189,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PANEL_SELECTION_FOLLOW] = A_REVERSE,
[PANEL_SELECTION_UNFOCUS] = A_BOLD,
[FAILED_SEARCH] = A_REVERSE | A_BOLD,
[FAILED_READ] = A_BOLD,
[PAUSED] = A_BOLD | A_REVERSE,
[UPTIME] = A_BOLD,
[BATTERY] = A_BOLD,
@ -214,6 +218,8 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_TOMB] = A_DIM,
[PROCESS_THREAD] = A_BOLD,
[PROCESS_THREAD_BASENAME] = A_REVERSE,
[PROCESS_COMM] = A_BOLD,
[PROCESS_THREAD_COMM] = A_REVERSE,
[BAR_BORDER] = A_BOLD,
[BAR_SHADOW] = A_DIM,
[SWAP] = A_BOLD,
@ -267,6 +273,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),
[PANEL_SELECTION_UNFOCUS] = ColorPair(Blue, White),
[FAILED_SEARCH] = ColorPair(Red, Cyan),
[FAILED_READ] = ColorPair(Red, White),
[PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),
[UPTIME] = ColorPair(Yellow, White),
[BATTERY] = ColorPair(Yellow, White),
@ -295,6 +302,8 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_TOMB] = ColorPair(White, Red),
[PROCESS_THREAD] = ColorPair(Blue, White),
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, White),
[PROCESS_COMM] = ColorPair(Magenta, White),
[PROCESS_THREAD_COMM] = ColorPair(Green, White),
[BAR_BORDER] = ColorPair(Blue, White),
[BAR_SHADOW] = ColorPair(Black, White),
[SWAP] = ColorPair(Red, White),
@ -348,6 +357,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),
[PANEL_SELECTION_UNFOCUS] = ColorPair(Blue, Black),
[FAILED_SEARCH] = ColorPair(Red, Cyan),
[FAILED_READ] = ColorPair(Red, Black),
[PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),
[UPTIME] = ColorPair(Yellow, Black),
[BATTERY] = ColorPair(Yellow, Black),
@ -376,6 +386,8 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_TOMB] = ColorPair(Black, Red),
[PROCESS_THREAD] = ColorPair(Blue, Black),
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, Black),
[PROCESS_COMM] = ColorPair(Magenta, Black),
[PROCESS_THREAD_COMM] = ColorPair(Yellow, Black),
[BAR_BORDER] = ColorPair(Blue, Black),
[BAR_SHADOW] = ColorPairGrayBlack,
[SWAP] = ColorPair(Red, Black),
@ -429,6 +441,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),
[PANEL_SELECTION_UNFOCUS] = A_BOLD | ColorPair(Yellow, Blue),
[FAILED_SEARCH] = ColorPair(Red, Cyan),
[FAILED_READ] = A_BOLD | ColorPair(Red, Blue),
[PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),
[UPTIME] = A_BOLD | ColorPair(Yellow, Blue),
[BATTERY] = A_BOLD | ColorPair(Yellow, Blue),
@ -457,6 +470,8 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_TOMB] = ColorPair(Blue, Red),
[PROCESS_THREAD] = ColorPair(Green, Blue),
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Green, Blue),
[PROCESS_COMM] = ColorPair(Magenta, Blue),
[PROCESS_THREAD_COMM] = ColorPair(Black, Blue),
[BAR_BORDER] = A_BOLD | ColorPair(Yellow, Blue),
[BAR_SHADOW] = ColorPair(Cyan, Blue),
[SWAP] = ColorPair(Red, Blue),
@ -510,6 +525,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),
[PANEL_SELECTION_UNFOCUS] = ColorPair(Black, White),
[FAILED_SEARCH] = ColorPair(Red, Green),
[FAILED_READ] = A_BOLD | ColorPair(Red, Black),
[PAUSED] = A_BOLD | ColorPair(Yellow, Green),
[UPTIME] = ColorPair(Green, Black),
[BATTERY] = ColorPair(Green, Black),
@ -532,6 +548,8 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_TREE] = ColorPair(Cyan, Black),
[PROCESS_THREAD] = ColorPair(Green, Black),
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, Black),
[PROCESS_COMM] = ColorPair(Magenta, Black),
[PROCESS_THREAD_COMM] = ColorPair(Yellow, Black),
[PROCESS_R_STATE] = ColorPair(Green, Black),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),

3
CRT.h
View File

@ -43,6 +43,7 @@ typedef enum ColorElements_ {
FUNCTION_BAR,
FUNCTION_KEY,
FAILED_SEARCH,
FAILED_READ,
PAUSED,
PANEL_HEADER_FOCUS,
PANEL_HEADER_UNFOCUS,
@ -77,6 +78,8 @@ typedef enum ColorElements_ {
PROCESS_TOMB,
PROCESS_THREAD,
PROCESS_THREAD_BASENAME,
PROCESS_COMM,
PROCESS_THREAD_COMM,
BAR_BORDER,
BAR_SHADOW,
GRAPH_1,

View File

@ -16,7 +16,7 @@ static void CommandScreen_scan(InfoScreen* this) {
int idx = MAXIMUM(Panel_getSelectedIndex(panel), 0);
Panel_prune(panel);
const char* p = this->process->comm;
const char* p = Process_getCommand(this->process);
char* line = xMalloc(COLS + 1);
int line_offset = 0, last_spc = -1, len;
for (; *p != '\0'; p++, line_offset++) {
@ -46,7 +46,7 @@ static void CommandScreen_scan(InfoScreen* this) {
}
static void CommandScreen_draw(InfoScreen* this) {
InfoScreen_drawTitled(this, "Command of process %d - %s", this->process->pid, this->process->comm);
InfoScreen_drawTitled(this, "Command of process %d - %s", this->process->pid, Process_getCommand(this->process));
}
const InfoScreenClass CommandScreen_class = {

View File

@ -84,6 +84,9 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Show custom thread names"), &(settings->showThreadNames)));
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Show program path"), &(settings->showProgramPath)));
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Highlight program \"basename\""), &(settings->highlightBaseName)));
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Try to find comm in cmdline, in merged Command"), &(settings->findCommInCmdline)));
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Try to strip exe from cmdline, in merged Command"), &(settings->stripExeFromCmdline)));
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Merge exe, comm and cmdline in Command"), &(settings->showMergedCommand)));
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Highlight large numbers in memory counters"), &(settings->highlightMegabytes)));
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Leave a margin around header"), &(settings->headerMargin)));
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Detailed CPU time (System/IO-Wait/Hard-IRQ/Soft-IRQ/Steal/Guest)"), &(settings->detailedCPUTime)));

View File

@ -34,7 +34,7 @@ void EnvScreen_delete(Object* this) {
}
void EnvScreen_draw(InfoScreen* this) {
InfoScreen_drawTitled(this, "Environment of process %d - %s", this->process->pid, this->process->comm);
InfoScreen_drawTitled(this, "Environment of process %d - %s", this->process->pid, Process_getCommand(this->process));
}
void EnvScreen_scan(InfoScreen* this) {

View File

@ -136,7 +136,7 @@ int MainPanel_selectedPid(MainPanel* this) {
const char* MainPanel_getValue(MainPanel* this, int i) {
Process* p = (Process*) Panel_get((Panel*)this, i);
return p ? p->comm : "";
return Process_getCommand(p);
}
bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged) {

View File

@ -82,7 +82,7 @@ void OpenFilesScreen_delete(Object* this) {
}
static void OpenFilesScreen_draw(InfoScreen* this) {
InfoScreen_drawTitled(this, "Snapshot of files open in process %d - %s", ((OpenFilesScreen*)this)->pid, this->process->comm);
InfoScreen_drawTitled(this, "Snapshot of files open in process %d - %s", ((OpenFilesScreen*)this)->pid, Process_getCommand(this->process));
}
static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {

View File

@ -416,6 +416,10 @@ void Process_done(Process* this) {
free(this->comm);
}
static const char* Process_getCommandStr(const Process* p) {
return p->comm ? p->comm : "";
}
const ProcessClass Process_class = {
.super = {
.extends = Class(Object),
@ -424,6 +428,7 @@ const ProcessClass Process_class = {
.compare = Process_compare
},
.writeField = Process_writeField,
.getCommandStr = Process_getCommandStr,
};
void Process_init(Process* this, const struct Settings_* settings) {
@ -503,7 +508,7 @@ long Process_compare(const void* v1, const void* v2) {
case PERCENT_MEM:
return SPACESHIP_NUMBER(p2->m_resident, p1->m_resident);
case COMM:
return SPACESHIP_NULLSTR(p1->comm, p2->comm);
return SPACESHIP_NULLSTR(Process_getCommand(p1), Process_getCommand(p2));
case MAJFLT:
return SPACESHIP_NUMBER(p2->majflt, p1->majflt);
case MINFLT:

View File

@ -67,7 +67,7 @@ typedef struct Process_ {
pid_t pid;
pid_t ppid;
pid_t tgid;
char* comm;
char* comm; /* use Process_getCommand() for a decorated command line */
int commLen;
int indent;
@ -127,14 +127,18 @@ extern char Process_pidFormat[20];
typedef Process*(*Process_New)(const struct Settings_*);
typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField);
typedef const char* (*Process_GetCommandStr)(const Process*);
typedef struct ProcessClass_ {
const ObjectClass super;
const Process_WriteField writeField;
const Process_GetCommandStr getCommandStr;
} ProcessClass;
#define As_Process(this_) ((const ProcessClass*)((this_)->super.klass))
#define Process_getCommand(this_) As_Process(this_)->getCommandStr((const Process*)(this_))
static inline pid_t Process_getParentPid(const Process* this) {
return this->tgid == this->pid ? this->ppid : this->tgid;
}

View File

@ -281,7 +281,7 @@ void ProcessList_rebuildPanel(ProcessList* this) {
if ( (!p->show)
|| (this->userId != (uid_t) -1 && (p->st_uid != this->userId))
|| (incFilter && !(String_contains_i(p->comm, incFilter)))
|| (incFilter && !(String_contains_i(Process_getCommand(p), incFilter)))
|| (this->pidMatchList && !Hashtable_get(this->pidMatchList, p->tgid)) )
hidden = true;

View File

@ -161,6 +161,12 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
this->highlightChanges = atoi(option[1]);
} else if (String_eq(option[0], "highlight_changes_delay_secs")) {
this->highlightDelaySecs = atoi(option[1]);
} else if (String_eq(option[0], "find_comm_in_cmdline")) {
this->findCommInCmdline = atoi(option[1]);
} else if (String_eq(option[0], "strip_exe_from_cmdline")) {
this->stripExeFromCmdline = atoi(option[1]);
} else if (String_eq(option[0], "show_merged_command")) {
this->showMergedCommand = atoi(option[1]);
} else if (String_eq(option[0], "header_margin")) {
this->headerMargin = atoi(option[1]);
} else if (String_eq(option[0], "expand_system_time")) {
@ -277,6 +283,9 @@ bool Settings_write(Settings* this) {
fprintf(fd, "highlight_threads=%d\n", (int) this->highlightThreads);
fprintf(fd, "highlight_changes=%d\n", (int) this->highlightChanges);
fprintf(fd, "highlight_changes_delay_secs=%d\n", (int) this->highlightDelaySecs);
fprintf(fd, "find_comm_in_cmdline=%d\n", (int) this->findCommInCmdline);
fprintf(fd, "strip_exe_from_cmdline=%d\n", (int) this->stripExeFromCmdline);
fprintf(fd, "show_merged_command=%d\n", (int) this->showMergedCommand);
fprintf(fd, "tree_view=%d\n", (int) this->treeView);
fprintf(fd, "header_margin=%d\n", (int) this->headerMargin);
fprintf(fd, "detailed_cpu_time=%d\n", (int) this->detailedCPUTime);
@ -328,6 +337,9 @@ Settings* Settings_new(int initialCpuCount) {
this->highlightThreads = true;
this->highlightChanges = false;
this->highlightDelaySecs = DEFAULT_HIGHLIGHT_SECS;
this->findCommInCmdline = true;
this->stripExeFromCmdline = true;
this->showMergedCommand = false;
#ifdef HAVE_LIBHWLOC
this->topologyAffinity = false;
#endif
@ -406,6 +418,9 @@ Settings* Settings_new(int initialCpuCount) {
this->hideKernelThreads = true;
this->highlightMegabytes = true;
this->highlightThreads = true;
this->findCommInCmdline = true;
this->stripExeFromCmdline = true;
this->showMergedCommand = false;
this->headerMargin = true;
}
return this;

View File

@ -53,6 +53,9 @@ typedef struct Settings_ {
bool highlightThreads;
bool highlightChanges;
int highlightDelaySecs;
bool findCommInCmdline;
bool stripExeFromCmdline;
bool showMergedCommand;
bool updateProcessNames;
bool accountGuestInCPUMeter;
bool headerMargin;

View File

@ -73,7 +73,7 @@ void TraceScreen_delete(Object* cast) {
void TraceScreen_draw(InfoScreen* this) {
attrset(CRT_colors[PANEL_HEADER_FOCUS]);
mvhline(0, 0, ' ', COLS);
mvprintw(0, 0, "Trace of process %d - %s", this->process->pid, this->process->comm);
mvprintw(0, 0, "Trace of process %d - %s", this->process->pid, Process_getCommand(this->process));
attrset(CRT_colors[DEFAULT_COLOR]);
IncSet_drawBar(this->inc);
}

View File

@ -218,6 +218,9 @@ Show full paths to running programs, where applicable. (This is a toggle key.)
.B Z
Pause/resume process updates.
.TP
.B m
Merge exe, comm and cmdline, where applicable. (This is a toggle key.)
.TP
.B Ctrl-L
Refresh: redraw screen and recalculate values.
.TP
@ -238,7 +241,18 @@ main screen, it is shown below in parenthesis.
.LP
.TP 5
.B Command
The full command line of the process (i.e. program name and arguments).
The full command line of the process (i.e. program name and arguments). If the
option 'Merge exe, comm and cmdline in Command' (toggled by the 'm' key) is set,
and if readable, the executable path (/proc/[pid]/exe) and the command name
(/proc/[pid]/comm) are also shown merged with the command line.
.TP
.B Comm
The command name of the process obtained from /proc/[pid]/comm, if readable.
.TP
.B Exe
The abbreviated basename of the executable of the process, obtained from
/proc/[pid]/exe, if readable. htop is able to read this file on linux for ALL
the processes only if it has the capability CAP_SYS_PTRACE or root privileges.
.TP
.B PID
The process ID.

View File

@ -25,6 +25,9 @@ in the source distribution for its full text.
/* semi-global */
long long btime;
/* Used to identify kernel threads in Comm and Exe columns */
static const char *const kthreadID = "KTHREAD";
ProcessFieldData Process_fields[] = {
[0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, },
[PID] = { .name = "PID", .title = " PID ", .description = "Process/thread ID", .flags = 0, },
@ -114,6 +117,8 @@ ProcessFieldData Process_fields[] = {
[M_PSSWP] = { .name = "M_PSSWP", .title = " PSSWP ", .description = "shows proportional swap share of this mapping, Unlike \"Swap\", this does not take into account swapped out page of underlying shmem objects.", .flags = PROCESS_FLAG_LINUX_SMAPS, },
[CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, },
[SECATTR] = { .name = "SECATTR", .title = " Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, },
[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, },
[LAST_PROCESSFIELD] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, },
};
@ -130,6 +135,17 @@ ProcessPidColumn Process_pidColumns[] = {
{ .id = 0, .label = NULL },
};
/* This function returns the string displayed in Command column, so that sorting
* happens on what is displayed - whether comm, full path, basename, etc.. So
* this follows LinuxProcess_writeField(COMM) and LinuxProcess_writeCommand */
static const char* LinuxProcess_getCommandStr(const Process *this) {
const LinuxProcess *lp = (const LinuxProcess *)this;
if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !lp->mergedCommand.str) {
return this->comm;
}
return lp->mergedCommand.str;
}
Process* LinuxProcess_new(const Settings* settings) {
LinuxProcess* this = xCalloc(1, sizeof(LinuxProcess));
Object_setClass(this, Class(LinuxProcess));
@ -148,6 +164,9 @@ void Process_delete(Object* cast) {
#endif
free(this->secattr);
free(this->ttyDevice);
free(this->procExe);
free(this->procComm);
free(this->mergedCommand.str);
free(this);
}
@ -195,6 +214,348 @@ static void LinuxProcess_printDelay(float delay_percent, char* buffer, int n) {
}
#endif
/*
TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is
not available in an userspace header - so define it. Note: when colorizing a
basename with the comm prefix, the entire basename (not just the comm prefix) is
colorized for better readability, and it is implicit that only upto
(TASK_COMM_LEN - 1) could be comm
*/
#define TASK_COMM_LEN 16
static bool findCommInCmdline(const char *comm, const char *cmdline, int cmdlineBasenameOffset, int *pCommStart, int *pCommEnd) {
/* Try to find procComm in tokenized cmdline - this might in rare cases
* mis-identify a string or fail, if comm or cmdline had been unsuitably
* modified by the process */
const char *token;
const char *tokenBase;
size_t tokenLen;
const size_t commLen = strlen(comm);
for (token = cmdline + cmdlineBasenameOffset; *token; ) {
for (tokenBase = token; *token && *token != '\n'; ++token) {
if (*token == '/') {
tokenBase = token + 1;
}
}
tokenLen = token - tokenBase;
if (((commLen < (TASK_COMM_LEN - 1) && tokenLen == commLen) ||
(commLen == (TASK_COMM_LEN - 1) && tokenLen >= commLen)) &&
strncmp(tokenBase, comm, commLen) == 0) {
*pCommStart = tokenBase - cmdline;
*pCommEnd = token - cmdline;
return true;
}
if (*token) {
do {
++token;
} while ('\n' == *token);
}
}
return false;
}
static int matchCmdlinePrefixWithExeSuffix(const char *cmdline, int cmdlineBaseOffset, const char *exe, int exeBaseOffset, int exeBaseLen) {
int matchLen; /* matching length to be returned */
char delim; /* delimiter following basename */
/* cmdline prefix is an absolute path: it must match whole exe. */
if (cmdline[0] == '/') {
matchLen = exeBaseLen + exeBaseOffset;
if (strncmp(cmdline, exe, matchLen) == 0) {
delim = cmdline[matchLen];
if (delim == 0 || delim == '\n' || delim == ' ') {
return matchLen;
}
}
return 0;
}
/* cmdline prefix is a relative path: We need to first match the basename at
* cmdlineBaseOffset and then reverse match the cmdline prefix with the exe
* suffix. But there is a catch: Some processes modify their cmdline in ways
* that make htop's identification of the basename in cmdline unreliable.
* For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to
* "gdm-session-worker [pam/gdm-autologin]" and htop ends up with
* procCmdlineBasenameOffset at "gdm-autologin]". This issue could arise with
* chrome as well as it stores in cmdline its concatenated argument vector,
* without NUL delimiter between the arguments (which may contain a '/')
*
* So if needed, we adjust cmdlineBaseOffset to the previous (if any)
* component of the cmdline relative path, and retry the procedure. */
bool delimFound; /* if valid basename delimiter found */
do {
/* match basename */
matchLen = exeBaseLen + cmdlineBaseOffset;
if (cmdlineBaseOffset < exeBaseOffset &&
strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) {
delim = cmdline[matchLen];
if (delim == 0 || delim == '\n' || delim == ' ') {
int i, j;
/* reverse match the cmdline prefix and exe suffix */
for (i = cmdlineBaseOffset - 1, j = exeBaseOffset - 1;
i >= 0 && cmdline[i] == exe[j]; --i, --j)
;
/* full match, with exe suffix being a valid relative path */
if (i < 0 && exe[j] == '/') {
return matchLen;
}
}
}
/* Try to find the previous potential cmdlineBaseOffset - it would be
* preceded by '/' or nothing, and delimited by ' ' or '\n' */
for (delimFound = false, cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) {
if (delimFound) {
if (cmdline[cmdlineBaseOffset - 1] == '/') {
break;
}
} else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\n') {
delimFound = true;
}
}
} while (delimFound);
return 0;
}
/* stpcpy, but also converts newlines to spaces */
static inline char *stpcpyWithNewlineConversion(char *dstStr, const char *srcStr) {
for (; *srcStr; ++srcStr) {
*dstStr++ = (*srcStr == '\n') ? ' ' : *srcStr;
}
*dstStr = 0;
return dstStr;
}
/*
This function makes the merged Command string. It also stores the offsets of the
basename, comm w.r.t the merged Command string - these offsets will be used by
LinuxProcess_writeCommand() for coloring. The merged Command string is also
returned by LinuxProcess_getCommandStr() for searching, sorting and filtering.
*/
void LinuxProcess_makeCommandStr(Process* this) {
LinuxProcess *lp = (LinuxProcess *)this;
bool showMergedCommand = this->settings->showMergedCommand;
bool showProgramPath = this->settings->showProgramPath;
bool searchCommInCmdline = this->settings->findCommInCmdline;
bool stripExeFromCmdline = this->settings->stripExeFromCmdline;
/* lp->mergedCommand.str needs to be remade only if there is a change in its
* state consisting of the relevant settings and the three fields cmdline,
* comm and exe */
if (showMergedCommand == lp->mergedCommand.prevMergeSet && showProgramPath == lp->mergedCommand.prevPathSet &&
searchCommInCmdline == lp->mergedCommand.prevCommSet && stripExeFromCmdline == lp->mergedCommand.prevCmdlineSet &&
!lp->mergedCommand.cmdlineChanged && !lp->mergedCommand.commChanged && !lp->mergedCommand.exeChanged) {
return;
}
/* The field separtor "│" has been chosen such that it will not match any
* valid search string used for sorting or filtering */
const char *SEPARATOR = CRT_treeStr[TREE_STR_VERT];
const int SEPARATOR_LEN = strlen(SEPARATOR);
if (lp->mergedCommand.cmdlineChanged || lp->mergedCommand.commChanged || lp->mergedCommand.exeChanged) {
free(lp->mergedCommand.str);
/* Also accomodate two field separators and a NUL */
lp->mergedCommand.str = xMalloc(lp->mergedCommand.maxLen + 2*SEPARATOR_LEN + 1);
}
lp->mergedCommand.prevMergeSet = showMergedCommand;
lp->mergedCommand.prevPathSet = showProgramPath;
lp->mergedCommand.prevCommSet = searchCommInCmdline;
lp->mergedCommand.prevCmdlineSet = stripExeFromCmdline;
lp->mergedCommand.cmdlineChanged = false;
lp->mergedCommand.commChanged = false;
lp->mergedCommand.exeChanged = false;
char *str;
char *strStart = lp->mergedCommand.str;
const char *cmdline = this->comm;
const char *procExe = lp->procExe;
const char *procComm = lp->procComm;
int cmdlineBasenameOffset = lp->procCmdlineBasenameOffset;
if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */
if (showProgramPath) {
(void) stpcpyWithNewlineConversion(strStart, cmdline);
lp->mergedCommand.baseStart = cmdlineBasenameOffset;
lp->mergedCommand.baseEnd = lp->procCmdlineBasenameEnd;
} else {
(void) stpcpyWithNewlineConversion(strStart, cmdline + cmdlineBasenameOffset);
lp->mergedCommand.baseStart = 0;
lp->mergedCommand.baseEnd = lp->procCmdlineBasenameEnd - cmdlineBasenameOffset;
}
lp->mergedCommand.commEnd = 0;
return;
}
int commStart = 0;
int commEnd = 0;
int exeBasenameOffset = lp->procExeBasenameOffset;
int exeLen = lp->procExeLen;
int exeBaseLen = exeLen - exeBasenameOffset;
bool commInCmdline = false;
/* Start with copying exe */
if (showProgramPath) {
str = stpcpy(strStart, procExe);
lp->mergedCommand.baseStart = exeBasenameOffset;
lp->mergedCommand.baseEnd = exeLen;
} else {
str = stpcpy(strStart, procExe + exeBasenameOffset);
lp->mergedCommand.baseStart = 0;
lp->mergedCommand.baseEnd = exeBaseLen;
}
/* Try to match procComm with procExe's basename: This is reliable (predictable) */
if (strncmp(procExe + exeBasenameOffset, procComm, TASK_COMM_LEN - 1) == 0) {
commStart = lp->mergedCommand.baseStart;
commEnd = lp->mergedCommand.baseEnd;
} else if (searchCommInCmdline) {
/* commStart/commEnd will be adjusted later along with cmdline */
commInCmdline = findCommInCmdline(procComm, cmdline, cmdlineBasenameOffset, &commStart, &commEnd);
}
int matchLen = matchCmdlinePrefixWithExeSuffix(cmdline, cmdlineBasenameOffset,
procExe, exeBasenameOffset, exeBaseLen);
/* Note: commStart, commEnd are offsets into RichString. But the multibyte
* separator (with size SEPARATOR_LEN) has size 1 in RichString. The offset
* adjustments below reflect this. */
if (commEnd) {
if (matchLen) { /* strip the matched exe prefix */
lp->mergedCommand.unmatchedExe = false;
cmdline += matchLen;
if (commInCmdline) {
commStart += str - strStart - matchLen;
commEnd += str - strStart - matchLen;
}
} else { /* cmdline will be a separate field */
lp->mergedCommand.unmatchedExe = true;
str = stpcpy(str, SEPARATOR);
if (commInCmdline) {
commStart += str - strStart - SEPARATOR_LEN + 1;
commEnd += str - strStart - SEPARATOR_LEN + 1;
}
}
lp->mergedCommand.separateComm = false; /* procComm merged */
} else {
str = stpcpy(str, SEPARATOR);
commStart = str - strStart - SEPARATOR_LEN + 1;
str = stpcpy(str, procComm);
commEnd = str - strStart - SEPARATOR_LEN + 1; /* or commStart + strlen(procComm) */
if (matchLen) {
lp->mergedCommand.unmatchedExe = false;
if (stripExeFromCmdline) {
cmdline += matchLen;
}
} else {
lp->mergedCommand.unmatchedExe = true;
}
if (*cmdline) {
str = stpcpy(str, SEPARATOR);
}
lp->mergedCommand.separateComm = true; /* procComm a separate field */
}
/* Display cmdline if it hasn't been consumed by procExe */
if (*cmdline) {
(void) stpcpyWithNewlineConversion(str, cmdline);
}
lp->mergedCommand.commStart = commStart;
lp->mergedCommand.commEnd = commEnd;
return;
}
static void LinuxProcess_writeCommand(const Process* this, int attr, int baseattr, RichString* str) {
const LinuxProcess *lp = (const LinuxProcess *)this;
int strStart = RichString_size(str);
int baseStart = strStart + lp->mergedCommand.baseStart;
int baseEnd = strStart + lp->mergedCommand.baseEnd;
bool highlightBaseName = this->settings->highlightBaseName;
RichString_append(str, attr, lp->mergedCommand.str);
if (lp->mergedCommand.commEnd) {
int commStart = strStart + lp->mergedCommand.commStart;
int commEnd = strStart + lp->mergedCommand.commEnd;
int commAttr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
if (lp->mergedCommand.separateComm) {
RichString_setAttrn(str, commAttr, commStart, commEnd - 1);
if (lp->mergedCommand.unmatchedExe) {
RichString_setAttrn(str, CRT_colors[FAILED_READ], commEnd, commEnd);
}
} else {
/* If it was matched with procExe's basename, make it bold if needed */
if (commStart == baseStart && highlightBaseName) {
if (commEnd > baseEnd) {
RichString_setAttrn(str, A_BOLD | commAttr, commStart, baseEnd - 1);
baseStart = baseEnd;
RichString_setAttrn(str, commAttr, baseStart, commEnd - 1);
} else {
RichString_setAttrn(str, A_BOLD | commAttr, commStart, commEnd - 1);
baseStart = commEnd;
}
} else {
RichString_setAttrn(str, commAttr, commStart, commEnd - 1);
}
if (lp->mergedCommand.unmatchedExe) {
RichString_setAttrn(str, CRT_colors[FAILED_READ], baseEnd, baseEnd);
}
}
}
if (baseStart < baseEnd && highlightBaseName) {
RichString_setAttrn(str, baseattr, baseStart, baseEnd - 1);
}
}
static void LinuxProcess_writeCommandField(const Process *this, RichString *str, char *buffer, int n, int attr) {
/* This code is from Process_writeField for COMM, but we invoke
* LinuxProcess_writeCommand to display
* /proc/pid/exe (or its basename)/proc/pid/comm/proc/pid/cmdline */
int baseattr = CRT_colors[PROCESS_BASENAME];
if (this->settings->highlightThreads && Process_isThread(this)) {
attr = CRT_colors[PROCESS_THREAD];
baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
}
if (!this->settings->treeView || this->indent == 0) {
LinuxProcess_writeCommand(this, attr, baseattr, str);
} else {
char* buf = buffer;
int maxIndent = 0;
bool lastItem = (this->indent < 0);
int indent = (this->indent < 0 ? -this->indent : this->indent);
int vertLen = strlen(CRT_treeStr[TREE_STR_VERT]);
for (int i = 0; i < 32; i++) {
if (indent & (1U << i)) {
maxIndent = i+1;
}
}
for (int i = 0; i < maxIndent - 1; i++) {
if (indent & (1 << i)) {
if (buf - buffer + (vertLen + 3) > n) {
break;
}
buf = stpcpy(buf, CRT_treeStr[TREE_STR_VERT]);
buf = stpcpy(buf, " ");
} else {
if (buf - buffer + 4 > n) {
break;
}
buf = stpcpy(buf, " ");
}
}
n -= (buf - buffer);
const char* draw = CRT_treeStr[lastItem ? (this->settings->direction == 1 ? TREE_STR_BEND : TREE_STR_TEND) : TREE_STR_RTEE];
xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
RichString_append(str, CRT_colors[PROCESS_TREE], buffer);
LinuxProcess_writeCommand(this, attr, baseattr, str);
}
return;
}
static void LinuxProcess_writeField(const Process* this, RichString* str, ProcessField field) {
const LinuxProcess* lp = (const LinuxProcess*) this;
bool coloring = this->settings->highlightMegabytes;
@ -289,6 +650,35 @@ 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 COMM: {
if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !lp->mergedCommand.str) {
Process_writeField(this, str, field);
} else {
LinuxProcess_writeCommandField(this, str, buffer, n, attr);
}
return;
}
case PROC_COMM: {
if (lp->procComm) {
attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
/* 15 being (TASK_COMM_LEN - 1) */
xSnprintf(buffer, n, "%-15.15s ", lp->procComm);
} else {
attr = CRT_colors[FAILED_READ];
xSnprintf(buffer, n, "%-15.15s ", Process_isKernelThread(lp) ? kthreadID : "?");
}
break;
}
case PROC_EXE: {
if (lp->procExe) {
attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME];
xSnprintf(buffer, n, "%-15.15s ", lp->procExe + lp->procExeBasenameOffset);
} else {
attr = CRT_colors[FAILED_READ];
xSnprintf(buffer, n, "%-15.15s ", Process_isKernelThread(lp) ? kthreadID : "?");
}
break;
}
default:
Process_writeField(this, str, field);
return;
@ -385,6 +775,16 @@ static long LinuxProcess_compare(const void* v1, const void* v2) {
return SPACESHIP_NUMBER(p2->ctxt_diff, p1->ctxt_diff);
case SECATTR:
return SPACESHIP_NULLSTR(p1->secattr, p2->secattr);
case PROC_COMM: {
const char *comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : "");
const char *comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : "");
return strcmp(comm1, comm2);
}
case PROC_EXE: {
const char *exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : "");
const char *exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : "");
return strcmp(exe1, exe2);
}
default:
return Process_compare(v1, v2);
}
@ -401,5 +801,6 @@ const ProcessClass LinuxProcess_class = {
.delete = Process_delete,
.compare = LinuxProcess_compare
},
.writeField = LinuxProcess_writeField
.writeField = LinuxProcess_writeField,
.getCommandStr = LinuxProcess_getCommandStr
};

View File

@ -95,11 +95,42 @@ typedef enum LinuxProcessFields {
M_PSSWP = 121,
CTXT = 122,
SECATTR = 123,
LAST_PROCESSFIELD = 124,
PROC_COMM = 124,
PROC_EXE = 125,
LAST_PROCESSFIELD = 126,
} LinuxProcessField;
/* LinuxProcessMergedCommand is populated by LinuxProcess_makeCommandStr: It
* contains the merged Command string, and the information needed by
* LinuxProcess_writeCommand to color the string. str will be NULL for kernel
* threads and zombies */
typedef struct LinuxProcessMergedCommand_ {
char *str; /* merged Command string */
int maxLen; /* maximum expected length of Command string */
int baseStart; /* basename's start offset */
int baseEnd; /* basename's end offset */
int commStart; /* comm's start offset */
int commEnd; /* comm's end offset */
bool separateComm; /* whether comm is a separate field */
bool unmatchedExe; /* whether exe matched with cmdline */
bool cmdlineChanged; /* whether cmdline changed */
bool exeChanged; /* whether exe changed */
bool commChanged; /* whether comm changed */
bool prevMergeSet; /* whether showMergedCommand was set */
bool prevPathSet; /* whether showProgramPath was set */
bool prevCommSet; /* whether findCommInCmdline was set */
bool prevCmdlineSet; /* whether findCommInCmdline was set */
} LinuxProcessMergedCommand;
typedef struct LinuxProcess_ {
Process super;
char *procComm;
char *procExe;
int procExeLen;
int procExeBasenameOffset;
int procCmdlineBasenameOffset;
int procCmdlineBasenameEnd;
LinuxProcessMergedCommand mergedCommand;
bool isKernelThread;
IOPriority ioPriority;
unsigned long int cminflt;
@ -177,6 +208,10 @@ IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this);
bool LinuxProcess_setIOPriority(Process* this, Arg ioprio);
/* This function constructs the string that is displayed by
* LinuxProcess_writeCommand and also returned by LinuxProcess_getCommandStr */
void LinuxProcess_makeCommandStr(Process *this);
bool Process_isThread(const Process* this);
#endif

View File

@ -875,6 +875,7 @@ static void setCommand(Process* process, const char* command, int len) {
}
static bool LinuxProcessList_readCmdlineFile(Process* process, const char* dirname, const char* name) {
LinuxProcess *lp = (LinuxProcess *)process;
char filename[MAX_NAME + 1];
xSnprintf(filename, MAX_NAME, "%s/%s/cmdline", dirname, name);
int fd = open(filename, O_RDONLY);
@ -885,6 +886,7 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, const char* dirna
int amtRead = xread(fd, command, sizeof(command) - 1);
close(fd);
int tokenEnd = 0;
int tokenStart = 0;
int lastChar = 0;
if (amtRead == 0) {
if (process->state == 'Z') {
@ -897,12 +899,23 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, const char* dirna
return false;
}
for (int i = 0; i < amtRead; i++) {
if (command[i] == '\0' || command[i] == '\n') {
/* newline used as delimiter - when forming the mergedCommand, newline is
* converted to space by LinuxProcess_makeCommandStr */
if (command[i] == '\0') {
command[i] = '\n';
}
if (command[i] == '\n') {
if (tokenEnd == 0) {
tokenEnd = i;
}
command[i] = ' ';
} else {
/* htop considers the next character after the last / that is before
* basenameOffset, as the start of the basename in cmdline - see
* Process_writeCommand */
if (!tokenEnd && command[i] == '/') {
tokenStart = i + 1;
}
lastChar = i;
}
}
@ -910,8 +923,55 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, const char* dirna
tokenEnd = amtRead;
}
command[lastChar + 1] = '\0';
lp->mergedCommand.maxLen = lastChar + 1; /* accomodate cmdline */
if (!process->comm || strcmp(command, process->comm)) {
process->basenameOffset = tokenEnd;
setCommand(process, command, lastChar + 1);
lp->procCmdlineBasenameOffset = tokenStart;
lp->procCmdlineBasenameEnd = tokenEnd;
lp->mergedCommand.cmdlineChanged = true;
}
/* /proc/[pid]/comm could change, so should be udpated */
xSnprintf(filename, MAX_NAME, "%s/%s/comm", dirname, name);
if ((fd = open(filename, O_RDONLY)) != -1 &&
(amtRead = xread(fd, command, sizeof(command) - 1)) > 0) {
close(fd);
command[amtRead - 1] = 0;
lp->mergedCommand.maxLen += amtRead - 1; /* accomodate comm */
if (!lp->procComm || strcmp(command, lp->procComm)) {
free(lp->procComm);
lp->procComm = xStrdup(command);
lp->mergedCommand.commChanged = true;
}
} else if (lp->procComm) {
free(lp->procComm);
lp->procComm = NULL;
lp->mergedCommand.commChanged = true;
}
/* execve could change /proc/[pid]/exe, so procExe should be udpated */
xSnprintf(command, sizeof(command), "%s/%s/exe", dirname, name);
if ((amtRead = readlink(command, filename, sizeof(filename) - 1)) > 0) {
filename[amtRead] = 0;
lp->mergedCommand.maxLen += amtRead; /* accomodate exe */
if (!lp->procExe || strcmp(filename, lp->procExe)) {
free(lp->procExe);
lp->procExe = xStrdup(filename);
lp->procExeLen = amtRead;
/* exe is guaranteed to contain at least one /, but validate anyway */
while (amtRead && filename[--amtRead] != '/')
;
lp->procExeBasenameOffset = amtRead + 1;
lp->mergedCommand.exeChanged = true;
}
} else if (lp->procExe) {
free(lp->procExe);
lp->procExe = NULL;
lp->procExeLen = 0;
lp->procExeBasenameOffset = 0;
lp->mergedCommand.exeChanged = true;
}
return true;
}
@ -1121,6 +1181,15 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, const char*
}
}
}
/* (Re)Generate the Command string, but only if the process is:
* - not a kernel thread, and
* - not a zombie or it became zombie under htop's watch, and
* - not a user thread or if showThreadNames is not set */
if (!Process_isKernelThread(proc) &&
(proc->state != 'Z' || lp->mergedCommand.str) &&
(!Process_isUserlandThread(proc) || !settings->showThreadNames)) {
LinuxProcess_makeCommandStr(proc);
}
#ifdef HAVE_DELAYACCT
LinuxProcessList_readDelayAcctData(this, lp);