diff --git a/Action.c b/Action.c index ef7caaf4..f6e8fab5 100644 --- a/Action.c +++ b/Action.c @@ -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; diff --git a/CRT.c b/CRT.c index 17a3e12c..31c94cbd 100644 --- a/CRT.c +++ b/CRT.c @@ -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), diff --git a/CRT.h b/CRT.h index df77f1e8..c95b0fb7 100644 --- a/CRT.h +++ b/CRT.h @@ -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, diff --git a/CommandScreen.c b/CommandScreen.c index 578b3ae1..d3428290 100644 --- a/CommandScreen.c +++ b/CommandScreen.c @@ -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 = { diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index 4f4a81b0..74c04298 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -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))); diff --git a/EnvScreen.c b/EnvScreen.c index 30faaaba..ae63d3e8 100644 --- a/EnvScreen.c +++ b/EnvScreen.c @@ -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) { diff --git a/MainPanel.c b/MainPanel.c index 5a1fe8f5..6a9d6303 100644 --- a/MainPanel.c +++ b/MainPanel.c @@ -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) { diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index cd309648..b1137c7b 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -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) { diff --git a/Process.c b/Process.c index ec4d853e..c47d95df 100644 --- a/Process.c +++ b/Process.c @@ -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: diff --git a/Process.h b/Process.h index 595e2bec..fba782b8 100644 --- a/Process.h +++ b/Process.h @@ -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; } diff --git a/ProcessList.c b/ProcessList.c index 227e90f4..bf67dab8 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -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; diff --git a/Settings.c b/Settings.c index 90671e65..9564c8ee 100644 --- a/Settings.c +++ b/Settings.c @@ -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; diff --git a/Settings.h b/Settings.h index 98531855..3c829ee1 100644 --- a/Settings.h +++ b/Settings.h @@ -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; diff --git a/TraceScreen.c b/TraceScreen.c index 1280b1e4..47cf0ab1 100644 --- a/TraceScreen.c +++ b/TraceScreen.c @@ -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); } diff --git a/htop.1.in b/htop.1.in index f597ea90..547f4e35 100644 --- a/htop.1.in +++ b/htop.1.in @@ -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. diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 6bed3322..1d1f9004 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -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 }; diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index 6116808e..88da78f1 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -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 diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index 1cc0a274..498ee0ef 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -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'; - process->basenameOffset = tokenEnd; - setCommand(process, command, lastChar + 1); + 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);