From 937052b231259a47d881d539ad5748245ef55b99 Mon Sep 17 00:00:00 2001 From: Daniel Lange Date: Fri, 3 Jun 2022 08:55:20 +0200 Subject: New upstream version 3.2.1 --- Action.c | 83 ++++++++++++++++++++---------- AvailableMetersPanel.c | 1 + CRT.c | 22 +++++--- CRT.h | 2 + ChangeLog | 20 ++++++++ ColorsPanel.c | 1 + DisplayOptionsPanel.c | 14 ++++- Hashtable.c | 2 +- HeaderOptionsPanel.c | 1 + MetersPanel.c | 1 + OpenFilesScreen.c | 4 +- OptionItem.c | 24 +++++++++ OptionItem.h | 10 ++++ Process.c | 101 +++++++++++++++---------------------- Process.h | 17 +++---- ProcessList.c | 62 +++++++++++++---------- ProcessList.h | 2 - ScreensPanel.c | 1 + Settings.c | 2 + Settings.h | 1 + SignalsPanel.c | 2 +- SignalsPanel.h | 4 ++ TraceScreen.c | 4 +- Vector.c | 81 ++++++++++++++++++++++++++--- Vector.h | 20 +++++++- XUtils.c | 4 +- configure.ac | 2 +- darwin/DarwinProcess.c | 2 +- docs/styleguide.md | 2 +- dragonflybsd/DragonFlyBSDProcess.c | 2 +- freebsd/FreeBSDProcess.c | 2 +- freebsd/Platform.c | 11 ++++ generic/openzfs_sysctl.c | 6 +++ linux/LinuxProcess.c | 14 ++--- linux/LinuxProcessList.c | 87 ++++++++++++++++++++++++-------- linux/Platform.c | 11 ++-- linux/Platform.h | 2 + linux/SystemdMeter.c | 21 ++++---- netbsd/NetBSDProcess.c | 2 +- netbsd/Platform.c | 9 +++- openbsd/OpenBSDProcess.c | 2 +- openbsd/Platform.c | 9 +++- pcp/PCPMetric.h | 1 + pcp/PCPProcess.c | 4 +- pcp/PCPProcessList.c | 2 + pcp/Platform.c | 10 +++- solaris/Platform.c | 22 +++++--- solaris/SolarisProcess.c | 2 +- solaris/SolarisProcessList.c | 4 +- unsupported/UnsupportedProcess.c | 2 +- zfs/ZfsArcStats.h | 1 + 51 files changed, 507 insertions(+), 211 deletions(-) diff --git a/Action.c b/Action.c index ce3cd13..b765248 100644 --- a/Action.c +++ b/Action.c @@ -88,6 +88,7 @@ static void Action_runSetup(State* st) { ScreenManager_run(scr, NULL, NULL, "Setup"); ScreenManager_delete(scr); if (st->settings->changed) { + CRT_setMouse(st->settings->enableMouse); Header_writeBackToSettings(st->header); } } @@ -241,6 +242,9 @@ static Htop_Reaction actionToggleTreeView(State* st) { static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) { ScreenSettings* ss = st->settings->ss; + if (!ss->treeView) { + return HTOP_OK; + } ss->allBranchesCollapsed = !ss->allBranchesCollapsed; if (ss->allBranchesCollapsed) ProcessList_collapseAllBranches(st->pl); @@ -533,7 +537,7 @@ static const struct { { .key = " U: ", .roInactive = false, .info = "untag all processes" }, { .key = " F9 k: ", .roInactive = true, .info = "kill process/tagged processes" }, { .key = " F7 ]: ", .roInactive = true, .info = "higher priority (root only)" }, - { .key = " F8 [: ", .roInactive = false, .info = "lower priority (+ nice)" }, + { .key = " F8 [: ", .roInactive = true, .info = "lower priority (+ nice)" }, #if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)) { .key = " a: ", .roInactive = true, .info = "set CPU affinity" }, #endif @@ -570,46 +574,57 @@ static Htop_Reaction actionHelp(State* st) { line++; mvaddstr(line++, 0, "CPU usage bar: "); +#define addbartext(attr, prefix, text) \ + do { \ + addattrstr(CRT_colors[DEFAULT_COLOR], prefix); \ + addattrstr(attr, text); \ + } while(0) + addattrstr(CRT_colors[BAR_BORDER], "["); + addbartext(CRT_colors[CPU_NICE_TEXT], "", "low"); + addbartext(CRT_colors[CPU_NORMAL], "/", "normal"); + addbartext(CRT_colors[CPU_SYSTEM], "/", "kernel"); if (st->settings->detailedCPUTime) { - addattrstr(CRT_colors[CPU_NICE_TEXT], "low"); addstr("/"); - addattrstr(CRT_colors[CPU_NORMAL], "normal"); addstr("/"); - addattrstr(CRT_colors[CPU_SYSTEM], "kernel"); addstr("/"); - addattrstr(CRT_colors[CPU_IRQ], "irq"); addstr("/"); - addattrstr(CRT_colors[CPU_SOFTIRQ], "soft-irq"); addstr("/"); - addattrstr(CRT_colors[CPU_STEAL], "steal"); addstr("/"); - addattrstr(CRT_colors[CPU_GUEST], "guest"); addstr("/"); - addattrstr(CRT_colors[CPU_IOWAIT], "io-wait"); - addattrstr(CRT_colors[BAR_SHADOW], " used%"); + addbartext(CRT_colors[CPU_IRQ], "/", "irq"); + addbartext(CRT_colors[CPU_SOFTIRQ], "/", "soft-irq"); + addbartext(CRT_colors[CPU_STEAL], "/", "steal"); + addbartext(CRT_colors[CPU_GUEST], "/", "guest"); + addbartext(CRT_colors[CPU_IOWAIT], "/", "io-wait"); + addbartext(CRT_colors[BAR_SHADOW], " ", "used%"); } else { - addattrstr(CRT_colors[CPU_NICE_TEXT], "low-priority"); addstr("/"); - addattrstr(CRT_colors[CPU_NORMAL], "normal"); addstr("/"); - addattrstr(CRT_colors[CPU_SYSTEM], "kernel"); addstr("/"); - addattrstr(CRT_colors[CPU_GUEST], "virtualized"); - addattrstr(CRT_colors[BAR_SHADOW], " used%"); + addbartext(CRT_colors[CPU_GUEST], "/", "guest"); + addbartext(CRT_colors[BAR_SHADOW], " ", "used%"); } addattrstr(CRT_colors[BAR_BORDER], "]"); + attrset(CRT_colors[DEFAULT_COLOR]); mvaddstr(line++, 0, "Memory bar: "); addattrstr(CRT_colors[BAR_BORDER], "["); - addattrstr(CRT_colors[MEMORY_USED], "used"); addstr("/"); - addattrstr(CRT_colors[MEMORY_BUFFERS_TEXT], "buffers"); addstr("/"); - addattrstr(CRT_colors[MEMORY_SHARED], "shared"); addstr("/"); - addattrstr(CRT_colors[MEMORY_CACHE], "cache"); - addattrstr(CRT_colors[BAR_SHADOW], " used/total"); + addbartext(CRT_colors[MEMORY_USED], "", "used"); + addbartext(CRT_colors[MEMORY_BUFFERS_TEXT], "/", "buffers"); + addbartext(CRT_colors[MEMORY_SHARED], "/", "shared"); + addbartext(CRT_colors[MEMORY_CACHE], "/", "cache"); + addbartext(CRT_colors[BAR_SHADOW], " ", "used"); + addbartext(CRT_colors[BAR_SHADOW], "/", "total"); addattrstr(CRT_colors[BAR_BORDER], "]"); + attrset(CRT_colors[DEFAULT_COLOR]); mvaddstr(line++, 0, "Swap bar: "); addattrstr(CRT_colors[BAR_BORDER], "["); - addattrstr(CRT_colors[SWAP], "used"); + addbartext(CRT_colors[SWAP], "", "used"); #ifdef HTOP_LINUX - addstr("/"); - addattrstr(CRT_colors[SWAP_CACHE], "cache"); - addattrstr(CRT_colors[BAR_SHADOW], " used/total"); + addbartext(CRT_colors[SWAP_CACHE], "/", "cache"); #else - addattrstr(CRT_colors[BAR_SHADOW], " used/total"); + addbartext(CRT_colors[SWAP_CACHE], " ", ""); #endif + addbartext(CRT_colors[BAR_SHADOW], " ", "used"); + addbartext(CRT_colors[BAR_SHADOW], "/", "total"); addattrstr(CRT_colors[BAR_BORDER], "]"); + + line++; + +#undef addbartext + attrset(CRT_colors[DEFAULT_COLOR]); mvaddstr(line++, 0, "Type and layout of header meters are configurable in the setup screen."); if (CRT_colorScheme == COLORSCHEME_MONOCHROME) { @@ -617,9 +632,23 @@ static Htop_Reaction actionHelp(State* st) { } line++; - mvaddstr(line++, 0, "Process state: R: running; S: sleeping; T: traced/stopped; Z: zombie; D: disk sleep"); +#define addattrstatestr(attr, state, desc) \ + do { \ + addattrstr(attr, state); \ + addattrstr(CRT_colors[DEFAULT_COLOR], ": " desc); \ + } while(0) + + mvaddstr(line, 0, "Process state: "); + addattrstatestr(CRT_colors[PROCESS_RUN_STATE], "R", "running; "); + addattrstatestr(CRT_colors[PROCESS_SHADOW], "S", "sleeping; "); + addattrstatestr(CRT_colors[PROCESS_RUN_STATE], "t", "traced/stopped; "); + addattrstatestr(CRT_colors[PROCESS_D_STATE], "Z", "zombie; "); + addattrstatestr(CRT_colors[PROCESS_D_STATE], "D", "disk sleep"); + attrset(CRT_colors[DEFAULT_COLOR]); - line++; +#undef addattrstatestr + + line += 2; const bool readonly = Settings_isReadonly(); diff --git a/AvailableMetersPanel.c b/AvailableMetersPanel.c index ae289ec..368f494 100644 --- a/AvailableMetersPanel.c +++ b/AvailableMetersPanel.c @@ -79,6 +79,7 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) { } if (update) { this->settings->changed = true; + this->settings->lastUpdate++; Header_calculateHeight(header); Header_updateData(header); Header_draw(header); diff --git a/CRT.c b/CRT.c index 583b626..868f237 100644 --- a/CRT.c +++ b/CRT.c @@ -900,6 +900,20 @@ void CRT_resetSignalHandlers(void) { signal(SIGQUIT, SIG_DFL); } +void CRT_setMouse(bool enabled) { +#ifdef HAVE_GETMOUSE + if (enabled) { +#if NCURSES_MOUSE_VERSION > 1 + mousemask(BUTTON1_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL); +#else + mousemask(BUTTON1_RELEASED, NULL); +#endif + } else { + mousemask(0, NULL); + } +#endif +} + void CRT_init(const Settings* settings, bool allowUnicode) { redirectStderr(); @@ -993,13 +1007,7 @@ IGNORE_WCASTQUAL_END #endif CRT_treeStrAscii; -#ifdef HAVE_GETMOUSE -#if NCURSES_MOUSE_VERSION > 1 - mousemask(BUTTON1_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL); -#else - mousemask(BUTTON1_RELEASED, NULL); -#endif -#endif + CRT_setMouse(settings->enableMouse); CRT_degreeSign = initDegreeSign(); } diff --git a/CRT.h b/CRT.h index eae4722..297e896 100644 --- a/CRT.h +++ b/CRT.h @@ -185,6 +185,8 @@ extern int CRT_scrollWheelVAmount; extern ColorScheme CRT_colorScheme; +void CRT_setMouse(bool enabled); + void CRT_init(const Settings* settings, bool allowUnicode); void CRT_done(void); diff --git a/ChangeLog b/ChangeLog index a7f4526..8f5266c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +What's new in version 3.2.1 + +* Fix setting to show all branches collapsed by default +* Restore functionality of stripExeFromCmdline setting +* Fix some command line display settings not being honored without restart +* Display single digit precision for CPU% greater than 99.9% +* On Linux, FreeBSD and PCP consider only shrinkable ZFS ARC as cache +* On Linux, increase field width of CPUD% and SWAPD% columns +* Colorize process state characters in help screen +* Use mousemask(3X) to enable and disable mouse control +* Fix heap buffer overflow in Vector_compact +* On Solaris, fix a process time scaling error +* On Solaris, fix the build +* On NetBSD, OpenBSD and Solaris ensure env buffer size is sufficient +* On Linux, resolve processes exiting interfering with sampling +* Fix ProcessList quadratic removal when scanning processes +* Under LXC, limit CPU count to that given by /proc/cpuinfo +* Improve container detection for LXC +* Some minor documentation fixes + What's new in version 3.2.0 * Support for displaying multiple tabs in the user interface diff --git a/ColorsPanel.c b/ColorsPanel.c index 50188f6..5900884 100644 --- a/ColorsPanel.c +++ b/ColorsPanel.c @@ -68,6 +68,7 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) { this->settings->colorScheme = mark; this->settings->changed = true; + this->settings->lastUpdate++; CRT_setColors(mark); clear(); diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index f5e64b1..fc23cb6 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -18,6 +18,7 @@ in the source distribution for its full text. #include "Object.h" #include "OptionItem.h" #include "ProvideCurses.h" +#include "ScreensPanel.h" static const char* const DisplayOptionsFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL}; @@ -43,6 +44,8 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) { case KEY_RECLICK: case ' ': switch (OptionItem_kind(selected)) { + case OPTION_ITEM_TEXT: + break; case OPTION_ITEM_CHECK: CheckItem_toggle((CheckItem*)selected); result = HANDLED; @@ -69,6 +72,7 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) { if (result == HANDLED) { this->settings->changed = true; + this->settings->lastUpdate++; Header* header = this->scr->header; Header_calculateHeight(header); Header_reinit(header); @@ -97,9 +101,17 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* this->scr = scr; Panel_setHeader(super, "Display options"); - Panel_add(super, (Object*) CheckItem_newByRef("Tree view (for the current Screen tab)", &(settings->ss->treeView))); + + #define TABMSG "For current screen tab: \0" + char tabheader[sizeof(TABMSG) + SCREEN_NAME_LEN + 1] = TABMSG; + strncat(tabheader, settings->ss->name, SCREEN_NAME_LEN); + Panel_add(super, (Object*) TextItem_new(tabheader)); + #undef TABMSG + + Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->ss->treeView))); Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->ss->treeViewAlwaysByPID))); Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->ss->allBranchesCollapsed))); + Panel_add(super, (Object*) TextItem_new("Global options:")); Panel_add(super, (Object*) CheckItem_newByRef("Show tabs for screens", &(settings->screenTabs))); Panel_add(super, (Object*) CheckItem_newByRef("Shadow other users' processes", &(settings->shadowOtherUsers))); Panel_add(super, (Object*) CheckItem_newByRef("Hide kernel threads", &(settings->hideKernelThreads))); diff --git a/Hashtable.c b/Hashtable.c index a0cfc9e..2756b23 100644 --- a/Hashtable.c +++ b/Hashtable.c @@ -52,7 +52,7 @@ static void Hashtable_dump(const Hashtable* this) { i, this->buckets[i].key, this->buckets[i].probe, - this->buckets[i].value ? (const void*)this->buckets[i].value : "(nil)"); + this->buckets[i].value); if (this->buckets[i].value) items++; diff --git a/HeaderOptionsPanel.c b/HeaderOptionsPanel.c index 22bcd09..25d1ddb 100644 --- a/HeaderOptionsPanel.c +++ b/HeaderOptionsPanel.c @@ -52,6 +52,7 @@ static HandlerResult HeaderOptionsPanel_eventHandler(Panel* super, int ch) { Header_setLayout(this->scr->header, mark); this->settings->changed = true; + this->settings->lastUpdate++; ScreenManager_resize(this->scr); diff --git a/MetersPanel.c b/MetersPanel.c index 1bdefdb..97da630 100644 --- a/MetersPanel.c +++ b/MetersPanel.c @@ -184,6 +184,7 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { if (result == HANDLED || sideMove) { Header* header = this->scr->header; this->settings->changed = true; + this->settings->lastUpdate++; Header_calculateHeight(header); ScreenManager_resize(this->scr); } diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index 2d19169..787e17b 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -120,7 +120,9 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { close(fdnull); char buffer[32] = {0}; xSnprintf(buffer, sizeof(buffer), "%d", pid); - execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", NULL); + // Use of NULL in variadic functions must have a pointer cast. + // The NULL constant is not required by standard to have a pointer type. + execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char *)NULL); exit(127); } close(fdpair[1]); diff --git a/OptionItem.c b/OptionItem.c index 7ab6900..962c0a9 100644 --- a/OptionItem.c +++ b/OptionItem.c @@ -25,6 +25,13 @@ static void OptionItem_delete(Object* cast) { free(this); } +static void TextItem_display(const Object* cast, RichString* out) { + const TextItem* this = (const TextItem*)cast; + assert (this != NULL); + + RichString_appendWide(out, CRT_colors[HELP_BOLD], this->super.text); +} + static void CheckItem_display(const Object* cast, RichString* out) { const CheckItem* this = (const CheckItem*)cast; assert (this != NULL); @@ -68,6 +75,16 @@ const OptionItemClass OptionItem_class = { } }; +const OptionItemClass TextItem_class = { + .super = { + .extends = Class(OptionItem), + .delete = OptionItem_delete, + .display = TextItem_display + }, + .kind = OPTION_ITEM_TEXT +}; + + const OptionItemClass CheckItem_class = { .super = { .extends = Class(OptionItem), @@ -77,6 +94,7 @@ const OptionItemClass CheckItem_class = { .kind = OPTION_ITEM_CHECK }; + const OptionItemClass NumberItem_class = { .super = { .extends = Class(OptionItem), @@ -86,6 +104,12 @@ const OptionItemClass NumberItem_class = { .kind = OPTION_ITEM_NUMBER }; +TextItem* TextItem_new(const char* text) { + TextItem* this = AllocThis(TextItem); + this->super.text = xStrdup(text); + return this; +} + CheckItem* CheckItem_newByRef(const char* text, bool* ref) { CheckItem* this = AllocThis(CheckItem); this->super.text = xStrdup(text); diff --git a/OptionItem.h b/OptionItem.h index 35c8506..ba28775 100644 --- a/OptionItem.h +++ b/OptionItem.h @@ -13,6 +13,7 @@ in the source distribution for its full text. enum OptionItemType { + OPTION_ITEM_TEXT, OPTION_ITEM_CHECK, OPTION_ITEM_NUMBER, }; @@ -32,6 +33,12 @@ typedef struct OptionItem_ { char* text; } OptionItem; +typedef struct TextItem_ { + OptionItem super; + + char* text; +} TextItem; + typedef struct CheckItem_ { OptionItem super; @@ -51,9 +58,12 @@ typedef struct NumberItem_ { } NumberItem; extern const OptionItemClass OptionItem_class; +extern const OptionItemClass TextItem_class; extern const OptionItemClass CheckItem_class; extern const OptionItemClass NumberItem_class; +TextItem* TextItem_new(const char* text); + CheckItem* CheckItem_newByRef(const char* text, bool* ref); CheckItem* CheckItem_newByVal(const char* text, bool value); bool CheckItem_get(const CheckItem* this); diff --git a/Process.c b/Process.c index 6c7b326..2a4c809 100644 --- a/Process.c +++ b/Process.c @@ -414,6 +414,8 @@ void Process_makeCommandStr(Process* this) { bool stripExeFromCmdline = settings->stripExeFromCmdline; bool showThreadNames = settings->showThreadNames; + uint64_t settingsStamp = settings->lastUpdate; + /* Nothing to do to (Re)Generate the Command string, if the process is: * - a kernel thread, or * - a zombie from before being under htop's watch, or @@ -422,52 +424,27 @@ void Process_makeCommandStr(Process* this) { return; if (this->state == ZOMBIE && !this->mergedCommand.str) return; - if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames) && (mc->prevMergeSet == showMergedCommand)) - return; /* this->mergedCommand.str needs updating only if its state or contents changed. * Its content is based on the fields cmdline, comm, and exe. */ - if ( - mc->prevMergeSet == showMergedCommand && - mc->prevPathSet == showProgramPath && - mc->prevCommSet == searchCommInCmdline && - mc->prevCmdlineSet == stripExeFromCmdline && - mc->prevShowThreadNames == showThreadNames && - !mc->cmdlineChanged && - !mc->commChanged && - !mc->exeChanged - ) { + if (mc->lastUpdate >= settingsStamp) return; - } + + mc->lastUpdate = settingsStamp; /* The field separtor "│" has been chosen such that it will not match any * valid string used for searching or filtering */ const char* SEPARATOR = CRT_treeStr[TREE_STR_VERT]; const int SEPARATOR_LEN = strlen(SEPARATOR); - /* Check for any changed fields since we last built this string */ - if (mc->cmdlineChanged || mc->commChanged || mc->exeChanged) { - free(mc->str); - /* Accommodate the column text, two field separators and terminating NUL */ - size_t maxLen = 2 * SEPARATOR_LEN + 1; - maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)"); - maxLen += this->procComm ? strlen(this->procComm) : 0; - maxLen += this->procExe ? strlen(this->procExe) : 0; - - mc->str = xCalloc(1, maxLen); - } + /* Accommodate the column text, two field separators and terminating NUL */ + size_t maxLen = 2 * SEPARATOR_LEN + 1; + maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)"); + maxLen += this->procComm ? strlen(this->procComm) : 0; + maxLen += this->procExe ? strlen(this->procExe) : 0; - /* Preserve the settings used in this run */ - mc->prevMergeSet = showMergedCommand; - mc->prevPathSet = showProgramPath; - mc->prevCommSet = searchCommInCmdline; - mc->prevCmdlineSet = stripExeFromCmdline; - mc->prevShowThreadNames = showThreadNames; - - /* Mark everything as unchanged */ - mc->cmdlineChanged = false; - mc->commChanged = false; - mc->exeChanged = false; + free(mc->str); + mc->str = xCalloc(1, maxLen); /* Reset all locations that need extra handling when actually displaying */ mc->highlightCount = 0; @@ -601,11 +578,15 @@ void Process_makeCommandStr(Process* this) { } if (matchLen) { - /* strip the matched exe prefix */ - cmdline += matchLen; + if (stripExeFromCmdline) { + /* strip the matched exe prefix */ + cmdline += matchLen; - commStart -= matchLen; - commEnd -= matchLen; + commStart -= matchLen; + commEnd -= matchLen; + } else { + matchLen = 0; + } } if (!matchLen || (haveCommField && *cmdline)) { @@ -739,17 +720,20 @@ void Process_printLeftAlignedField(RichString* str, int attr, const char* conten void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr) { if (val >= 0) { - if (val < 99.9F) { - if (val < 0.05F) { - *attr = CRT_colors[PROCESS_SHADOW]; - } - xSnprintf(buffer, n, "%*.1f ", width, val); - } else { + if (val < 0.05F) + *attr = CRT_colors[PROCESS_SHADOW]; + else if (val >= 99.9F) *attr = CRT_colors[PROCESS_MEGABYTES]; - if (val < 100.0F) - val = 100.0F; // Don't round down and display "val" as "99". - xSnprintf(buffer, n, "%*.0f ", width, val); + + int precision = 1; + + // Display "val" as "100" for columns like "MEM%". + if (width == 4 && val > 99.9F) { + precision = 0; + val = 100.0F; } + + xSnprintf(buffer, n, "%*.*f ", width, precision, val); } else { *attr = CRT_colors[PROCESS_SHADOW]; xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); @@ -1095,13 +1079,6 @@ bool Process_sendSignal(Process* this, Arg sgn) { return kill(this->pid, sgn.i) == 0; } -int Process_pidCompare(const void* v1, const void* v2) { - const Process* p1 = (const Process*)v1; - const Process* p2 = (const Process*)v2; - - return SPACESHIP_NUMBER(p1->pid, p2->pid); -} - int Process_compare(const void* v1, const void* v2) { const Process* p1 = (const Process*)v1; const Process* p2 = (const Process*)v2; @@ -1204,7 +1181,8 @@ void Process_updateComm(Process* this, const char* comm) { free(this->procComm); this->procComm = comm ? xStrdup(comm) : NULL; - this->mergedCommand.commChanged = true; + + this->mergedCommand.lastUpdate = 0; } static int skipPotentialPath(const char* cmdline, int end) { @@ -1244,7 +1222,8 @@ void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart this->cmdline = cmdline ? xStrdup(cmdline) : NULL; this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd); this->cmdlineBasenameEnd = basenameEnd; - this->mergedCommand.cmdlineChanged = true; + + this->mergedCommand.lastUpdate = 0; } void Process_updateExe(Process* this, const char* exe) { @@ -1263,7 +1242,8 @@ void Process_updateExe(Process* this, const char* exe) { this->procExe = NULL; this->procExeBasenameOffset = 0; } - this->mergedCommand.exeChanged = true; + + this->mergedCommand.lastUpdate = 0; } uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 }; @@ -1287,13 +1267,14 @@ void Process_updateFieldWidth(ProcessField key, size_t width) { } void Process_updateCPUFieldWidths(float percentage) { - if (percentage < 99.9) { + if (percentage < 99.9F) { Process_updateFieldWidth(PERCENT_CPU, 4); Process_updateFieldWidth(PERCENT_NORM_CPU, 4); return; } - uint8_t width = ceil(log10(percentage + .2)); + // Add additional two characters, one for "." and another for precision. + uint8_t width = ceil(log10(percentage + 0.1)) + 2; Process_updateFieldWidth(PERCENT_CPU, width); Process_updateFieldWidth(PERCENT_NORM_CPU, width); diff --git a/Process.h b/Process.h index e1408c2..a1ca50f 100644 --- a/Process.h +++ b/Process.h @@ -96,17 +96,10 @@ typedef struct ProcessCmdlineHighlight_ { * Process_writeCommand to color the string. str will be NULL for kernel * threads and zombies */ typedef struct ProcessMergedCommand_ { + uint64_t lastUpdate; /* Marker based on settings->lastUpdate to track when the rendering needs refreshing */ char* str; /* merged Command string */ size_t highlightCount; /* how many portions of cmdline to highlight */ ProcessCmdlineHighlight highlights[8]; /* which portions of cmdline to highlight */ - bool cmdlineChanged : 1; /* whether cmdline changed */ - bool exeChanged : 1; /* whether exe changed */ - bool commChanged : 1; /* whether comm changed */ - bool prevMergeSet : 1; /* whether showMergedCommand was set */ - bool prevPathSet : 1; /* whether showProgramPath was set */ - bool prevCommSet : 1; /* whether findCommInCmdline was set */ - bool prevCmdlineSet : 1; /* whether stripExeFromCmdline was set */ - bool prevShowThreadNames : 1; /* whether showThreadNames was set */ } ProcessMergedCommand; typedef struct Process_ { @@ -293,7 +286,7 @@ extern uint8_t Process_fieldWidths[LAST_PROCESSFIELD]; #define PROCESS_MIN_PID_DIGITS 5 #define PROCESS_MAX_PID_DIGITS 19 #define PROCESS_MIN_UID_DIGITS 5 -#define PROCESS_MAX_UID_DIGITS 19 +#define PROCESS_MAX_UID_DIGITS 20 extern int Process_pidDigits; extern int Process_uidDigits; @@ -394,7 +387,11 @@ bool Process_changePriorityBy(Process* this, Arg delta); bool Process_sendSignal(Process* this, Arg sgn); -int Process_pidCompare(const void* v1, const void* v2); +static inline int Process_pidEqualCompare(const void* v1, const void* v2) { + const pid_t p1 = ((const Process*)v1)->pid; + const pid_t p2 = ((const Process*)v2)->pid; + return p1 != p2; /* return zero when equal */ +} int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key); diff --git a/ProcessList.c b/ProcessList.c index 2e5ad7c..bbaddd8 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -82,41 +82,45 @@ void ProcessList_setPanel(ProcessList* this, Panel* panel) { this->panel = panel; } -static const char* alignedDynamicColumnTitle(const ProcessList* this, int key) { +static const char* alignedDynamicColumnTitle(const ProcessList* this, int key, char* titleBuffer, size_t titleBufferSize) { const DynamicColumn* column = Hashtable_get(this->dynamicColumns, key); if (column == NULL) return "- "; - static char titleBuffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1]; int width = column->width; if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) width = DYNAMIC_DEFAULT_COLUMN_WIDTH; - xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s", width, column->heading); + xSnprintf(titleBuffer, titleBufferSize, "%*s", width, column->heading); return titleBuffer; } static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessField field) { + static char titleBuffer[UINT8_MAX + sizeof(" ")]; + assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" ")); + assert(sizeof(titleBuffer) >= PROCESS_MAX_PID_DIGITS + sizeof(" ")); + assert(sizeof(titleBuffer) >= PROCESS_MAX_UID_DIGITS + sizeof(" ")); + if (field >= LAST_PROCESSFIELD) - return alignedDynamicColumnTitle(this, field); + return alignedDynamicColumnTitle(this, field, titleBuffer, sizeof(titleBuffer)); const char* title = Process_fields[field].title; if (!title) return "- "; if (Process_fields[field].pidColumn) { - static char titleBuffer[PROCESS_MAX_PID_DIGITS + sizeof(" ")]; xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title); return titleBuffer; } if (field == ST_UID) { - static char titleBuffer[PROCESS_MAX_UID_DIGITS + sizeof(" ")]; xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_uidDigits, title); return titleBuffer; } if (Process_fields[field].autoWidth) { - static char titleBuffer[UINT8_MAX + 1]; - xSnprintf(titleBuffer, sizeof(titleBuffer), "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title); + if (field == PERCENT_CPU) + xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_fieldWidths[field], title); + else + xSnprintf(titleBuffer, sizeof(titleBuffer), "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title); return titleBuffer; } @@ -158,7 +162,7 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header) { } void ProcessList_add(ProcessList* this, Process* p) { - assert(Vector_indexOf(this->processes, p, Process_pidCompare) == -1); + assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) == -1); assert(Hashtable_get(this->processTable, p->pid) == NULL); p->processList = this; @@ -168,25 +172,23 @@ void ProcessList_add(ProcessList* this, Process* p) { Vector_add(this->processes, p); Hashtable_put(this->processTable, p->pid, p); - assert(Vector_indexOf(this->processes, p, Process_pidCompare) != -1); + assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) != -1); assert(Hashtable_get(this->processTable, p->pid) != NULL); - assert(Hashtable_count(this->processTable) == Vector_count(this->processes)); + assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable))); } -void ProcessList_remove(ProcessList* this, const Process* p) { - assert(Vector_indexOf(this->processes, p, Process_pidCompare) != -1); - assert(Hashtable_get(this->processTable, p->pid) != NULL); - - const Process* pp = Hashtable_remove(this->processTable, p->pid); - assert(pp == p); (void)pp; - +// ProcessList_removeIndex removes Process p from the list's map and soft deletes +// it from its vector. Vector_compact *must* be called once the caller is done +// removing items. +// Should only be called from ProcessList_scan to avoid breaking dying process highlighting. +static void ProcessList_removeIndex(ProcessList* this, const Process* p, int idx) { pid_t pid = p->pid; - int idx = Vector_indexOf(this->processes, p, Process_pidCompare); - assert(idx != -1); - if (idx >= 0) { - Vector_remove(this->processes, idx); - } + assert(p == (Process*)Vector_get(this->processes, idx)); + assert(Hashtable_get(this->processTable, pid) != NULL); + + Hashtable_remove(this->processTable, pid); + Vector_softRemove(this->processes, idx); if (this->following != -1 && this->following == pid) { this->following = -1; @@ -194,7 +196,7 @@ void ProcessList_remove(ProcessList* this, const Process* p) { } assert(Hashtable_get(this->processTable, pid) == NULL); - assert(Hashtable_count(this->processTable) == Vector_count(this->processes)); + assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable))); } static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, bool show) { @@ -355,7 +357,10 @@ void ProcessList_expandTree(ProcessList* this) { } } +// Called on collapse-all toggle and on startup, possibly in non-tree mode void ProcessList_collapseAllBranches(ProcessList* this) { + ProcessList_buildTree(this); // Update `tree_depth` fields of the processes + this->needsSort = true; // ProcessList is sorted by parent now, force new sort int size = Vector_size(this->processes); for (int i = 0; i < size; i++) { Process* process = (Process*) Vector_get(this->processes, i); @@ -429,7 +434,7 @@ Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process* proc = (Process*) Hashtable_get(this->processTable, pid); *preExisting = proc != NULL; if (proc) { - assert(Vector_indexOf(this->processes, proc, Process_pidCompare) != -1); + assert(Vector_indexOf(this->processes, proc, Process_pidEqualCompare) != -1); assert(proc->pid == pid); } else { proc = constructor(this->settings); @@ -484,7 +489,7 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { if (p->tombStampMs > 0) { // remove tombed process if (this->monotonicMs >= p->tombStampMs) { - ProcessList_remove(this, p); + ProcessList_removeIndex(this, p, i); } } else if (p->updated == false) { // process no longer exists @@ -493,11 +498,14 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { p->tombStampMs = this->monotonicMs + 1000 * this->settings->highlightDelaySecs; } else { // immediately remove - ProcessList_remove(this, p); + ProcessList_removeIndex(this, p, i); } } } + // Compact the processes vector in case of any deletions + Vector_compact(this->processes); + // Set UID column width based on max UID. Process_setUidColumnWidth(maxUid); } diff --git a/ProcessList.h b/ProcessList.h index c420038..419dea8 100644 --- a/ProcessList.h +++ b/ProcessList.h @@ -106,8 +106,6 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header); void ProcessList_add(ProcessList* this, Process* p); -void ProcessList_remove(ProcessList* this, const Process* p); - void ProcessList_updateDisplayList(ProcessList* this); ProcessField ProcessList_keyAt(const ProcessList* this, int at); diff --git a/ScreensPanel.c b/ScreensPanel.c index 1427fef..785c387 100644 --- a/ScreensPanel.c +++ b/ScreensPanel.c @@ -315,6 +315,7 @@ void ScreensPanel_update(Panel* super) { ScreensPanel* this = (ScreensPanel*) super; int size = Panel_size(super); this->settings->changed = true; + this->settings->lastUpdate++; this->settings->screens = xReallocArray(this->settings->screens, size + 1, sizeof(ScreenSettings*)); for (int i = 0; i < size; i++) { ScreenListItem* item = (ScreenListItem*) Panel_get(super, i); diff --git a/Settings.c b/Settings.c index a630374..7d6fca4 100644 --- a/Settings.c +++ b/Settings.c @@ -762,6 +762,8 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns) this->ssIndex = 0; this->ss = this->screens[this->ssIndex]; + this->lastUpdate = 1; + return this; } diff --git a/Settings.h b/Settings.h index 345a535..facd3f2 100644 --- a/Settings.h +++ b/Settings.h @@ -96,6 +96,7 @@ typedef struct Settings_ { #endif bool changed; + uint64_t lastUpdate; } Settings; #define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu)) diff --git a/SignalsPanel.c b/SignalsPanel.c index cb71130..7e21ce9 100644 --- a/SignalsPanel.c +++ b/SignalsPanel.c @@ -6,8 +6,8 @@ in the source distribution for its full text. */ #include "SignalsPanel.h" +// the above contains #include so do not add that here again (breaks Solaris build) -#include #include #include "FunctionBar.h" diff --git a/SignalsPanel.h b/SignalsPanel.h index 7b9303f..529043a 100644 --- a/SignalsPanel.h +++ b/SignalsPanel.h @@ -7,7 +7,11 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + +#ifndef HTOP_SOLARIS #include +#endif #include "Panel.h" diff --git a/TraceScreen.c b/TraceScreen.c index c3a9449..90400b4 100644 --- a/TraceScreen.c +++ b/TraceScreen.c @@ -90,7 +90,9 @@ bool TraceScreen_forkTracer(TraceScreen* this) { char buffer[32] = {0}; xSnprintf(buffer, sizeof(buffer), "%d", this->super.process->pid); - execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, NULL); + // Use of NULL in variadic functions must have a pointer cast. + // The NULL constant is not required by standard to have a pointer type. + execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (char *)NULL); // Should never reach here, unless execlp fails ... const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH."; diff --git a/Vector.c b/Vector.c index 52ed44a..eddbc9a 100644 --- a/Vector.c +++ b/Vector.c @@ -29,6 +29,8 @@ Vector* Vector_new(const ObjectClass* type, bool owner, int size) { this->items = 0; this->type = type; this->owner = owner; + this->dirty_index = -1; + this->dirty_count = 0; return this; } @@ -44,10 +46,21 @@ void Vector_delete(Vector* this) { free(this); } +static inline bool Vector_isDirty(const Vector* this) { + if (this->dirty_count > 0) { + assert(0 <= this->dirty_index && this->dirty_index < this->items); + assert(this->dirty_count <= this->items); + return true; + } + assert(this->dirty_index == -1); + return false; +} + #ifndef NDEBUG static bool Vector_isConsistent(const Vector* this) { assert(this->items <= this->arraySize); + assert(!Vector_isDirty(this)); if (this->owner) { for (int i = 0; i < this->items; i++) { @@ -60,15 +73,14 @@ static bool Vector_isConsistent(const Vector* this) { return true; } -unsigned int Vector_count(const Vector* this) { - unsigned int items = 0; +bool Vector_countEquals(const Vector* this, unsigned int expectedCount) { + unsigned int n = 0; for (int i = 0; i < this->items; i++) { if (this->array[i]) { - items++; + n++; } } - assert(items == (unsigned int)this->items); - return items; + return n == expectedCount; } Object* Vector_get(const Vector* this, int idx) { @@ -88,13 +100,16 @@ int Vector_size(const Vector* this) { void Vector_prune(Vector* this) { assert(Vector_isConsistent(this)); if (this->owner) { - for (int i = 0; i < this->items; i++) + for (int i = 0; i < this->items; i++) { if (this->array[i]) { Object_delete(this->array[i]); - //this->array[i] = NULL; + this->array[i] = NULL; } + } } this->items = 0; + this->dirty_index = -1; + this->dirty_count = 0; } //static int comparisons = 0; @@ -242,6 +257,58 @@ Object* Vector_remove(Vector* this, int idx) { } } +Object* Vector_softRemove(Vector* this, int idx) { + assert(idx >= 0 && idx < this->items); + + Object* removed = this->array[idx]; + assert(removed); + if (removed) { + this->array[idx] = NULL; + + this->dirty_count++; + if (this->dirty_index < 0 || idx < this->dirty_index) { + this->dirty_index = idx; + } + + if (this->owner) { + Object_delete(removed); + return NULL; + } + } + + return removed; +} + +void Vector_compact(Vector* this) { + if (!Vector_isDirty(this)) { + return; + } + + const int size = this->items; + assert(0 <= this->dirty_index && this->dirty_index < size); + assert(this->array[this->dirty_index] == NULL); + + int idx = this->dirty_index; + + /* one deletion: use memmove, which should be faster */ + if (this->dirty_count == 1) { + memmove(&this->array[idx], &this->array[idx + 1], (this->items - idx - 1) * sizeof(this->array[0])); + } else { + /* multiple deletions */ + for (int i = idx + 1; i < size; i++) { + if (this->array[i]) { + this->array[idx++] = this->array[i]; + } + } + } + + this->items -= this->dirty_count; + this->dirty_index = -1; + this->dirty_count = 0; + + assert(Vector_isConsistent(this)); +} + void Vector_moveUp(Vector* this, int idx) { assert(idx >= 0 && idx < this->items); assert(Vector_isConsistent(this)); diff --git a/Vector.h b/Vector.h index 0fdb706..b7b3903 100644 --- a/Vector.h +++ b/Vector.h @@ -22,6 +22,11 @@ typedef struct Vector_ { int arraySize; int growthRate; int items; + /* lowest index of a pending soft remove/delete operation, + used to speed up compaction */ + int dirty_index; + /* count of soft deletes, required for Vector_count to work in debug mode */ + int dirty_count; bool owner; } Vector; @@ -44,6 +49,15 @@ Object* Vector_take(Vector* this, int idx); Object* Vector_remove(Vector* this, int idx); +/* Vector_softRemove marks the item at index idx for deletion without + reclaiming any space. If owned, the item is immediately freed. + + Vector_compact must be called to reclaim space.*/ +Object* Vector_softRemove(Vector* this, int idx); + +/* Vector_compact reclaims space free'd up by Vector_softRemove, if any. */ +void Vector_compact(Vector* this); + void Vector_moveUp(Vector* this, int idx); void Vector_moveDown(Vector* this, int idx); @@ -54,7 +68,11 @@ void Vector_set(Vector* this, int idx, void* data_); Object* Vector_get(const Vector* this, int idx); int Vector_size(const Vector* this); -unsigned int Vector_count(const Vector* this); + +/* Vector_countEquals returns true if the number of non-NULL items + in the Vector is equal to expectedCount. This is only for debugging + and consistency checks. */ +bool Vector_countEquals(const Vector* this, unsigned int expectedCount); #else /* NDEBUG */ diff --git a/XUtils.c b/XUtils.c index b34a8a7..2012b6c 100644 --- a/XUtils.c +++ b/XUtils.c @@ -115,7 +115,9 @@ inline bool String_contains_i(const char* s1, const char* s2, bool multi) { char* String_cat(const char* s1, const char* s2) { const size_t l1 = strlen(s1); const size_t l2 = strlen(s2); - assert(SIZE_MAX - l1 > l2); + if (SIZE_MAX - l1 <= l2) { + fail(); + } char* out = xMalloc(l1 + l2 + 1); memcpy(out, s1, l1); memcpy(out + l1, s2, l2); diff --git a/configure.ac b/configure.ac index e7e47e3..7a52ab5 100644 --- a/configure.ac +++ b/configure.ac @@ -6,7 +6,7 @@ # ---------------------------------------------------------------------- AC_PREREQ([2.69]) -AC_INIT([htop], [3.2.0], [htop@groups.io], [], [https://htop.dev/]) +AC_INIT([htop], [3.2.1], [htop@groups.io], [], [https://htop.dev/]) AC_CONFIG_SRCDIR([htop.c]) AC_CONFIG_AUX_DIR([build-aux]) diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index 71b9add..d0204dd 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -39,7 +39,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, - [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, diff --git a/docs/styleguide.md b/docs/styleguide.md index 18c53af..977ee38 100644 --- a/docs/styleguide.md +++ b/docs/styleguide.md @@ -219,7 +219,7 @@ The primary user documentation should be the man file which you can find in `hto Additional documentation, like this file, should be written in gh-style markdown. Make each sentence one line. -Markdown will combined these in output formats. +Markdown will combine these in output formats. It does only insert a paragraph if you insert a blank line into the source file. This way git can better diff and present the changes when documentation is altered. diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c index e11348d..7ff9245 100644 --- a/dragonflybsd/DragonFlyBSDProcess.c +++ b/dragonflybsd/DragonFlyBSDProcess.c @@ -38,7 +38,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, - [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c index 2746540..4eccfe7 100644 --- a/freebsd/FreeBSDProcess.c +++ b/freebsd/FreeBSDProcess.c @@ -37,7 +37,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, - [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, diff --git a/freebsd/Platform.c b/freebsd/Platform.c index d21fc80..1a731b7 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -227,6 +227,7 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) { void Platform_setMemoryValues(Meter* this) { const ProcessList* pl = this->pl; + const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) pl; this->total = pl->totalMem; this->values[0] = pl->usedMem; @@ -234,6 +235,16 @@ void Platform_setMemoryValues(Meter* this) { // this->values[2] = "shared memory, like tmpfs and shm" this->values[3] = pl->cachedMem; // this->values[4] = "available memory" + + if (fpl->zfs.enabled) { + // ZFS does not shrink below the value of zfs_arc_min. + unsigned long long int shrinkableSize = 0; + if (fpl->zfs.size > fpl->zfs.min) + shrinkableSize = fpl->zfs.size - fpl->zfs.min; + this->values[0] -= shrinkableSize; + this->values[3] += shrinkableSize; + // this->values[4] += shrinkableSize; + } } void Platform_setSwapValues(Meter* this) { diff --git a/generic/openzfs_sysctl.c b/generic/openzfs_sysctl.c index d088da7..bcd37dc 100644 --- a/generic/openzfs_sysctl.c +++ b/generic/openzfs_sysctl.c @@ -15,6 +15,7 @@ in the source distribution for its full text. static int MIB_kstat_zfs_misc_arcstats_size[5]; +static int MIB_kstat_zfs_misc_arcstats_c_min[5]; static int MIB_kstat_zfs_misc_arcstats_c_max[5]; static int MIB_kstat_zfs_misc_arcstats_mfu_size[5]; static int MIB_kstat_zfs_misc_arcstats_mru_size[5]; @@ -35,6 +36,7 @@ void openzfs_sysctl_init(ZfsArcStats* stats) { len = 5; sysctlnametomib("kstat.zfs.misc.arcstats.size", MIB_kstat_zfs_misc_arcstats_size, &len); + sysctlnametomib("kstat.zfs.misc.arcstats.c_min", MIB_kstat_zfs_misc_arcstats_c_min, &len); sysctlnametomib("kstat.zfs.misc.arcstats.c_max", MIB_kstat_zfs_misc_arcstats_c_max, &len); sysctlnametomib("kstat.zfs.misc.arcstats.mfu_size", MIB_kstat_zfs_misc_arcstats_mfu_size, &len); sysctlnametomib("kstat.zfs.misc.arcstats.mru_size", MIB_kstat_zfs_misc_arcstats_mru_size, &len); @@ -61,6 +63,10 @@ void openzfs_sysctl_updateArcStats(ZfsArcStats* stats) { sysctl(MIB_kstat_zfs_misc_arcstats_size, 5, &(stats->size), &len, NULL, 0); stats->size /= 1024; + len = sizeof(stats->min); + sysctl(MIB_kstat_zfs_misc_arcstats_c_min, 5, &(stats->min), &len, NULL, 0); + stats->min /= 1024; + len = sizeof(stats->max); sysctl(MIB_kstat_zfs_misc_arcstats_c_max, 5, &(stats->max), &len, NULL, 0); stats->max /= 1024; diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 299b167..92be326 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -57,7 +57,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, }, [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, .defaultSortDesc = true, }, [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, - [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, @@ -86,9 +86,9 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, }, [IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, }, #ifdef HAVE_DELAYACCT - [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD%", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, - [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, - [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWPD%", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, + [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, + [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, + [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWPD% ", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, #endif [M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, [M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, @@ -270,9 +270,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces break; } #ifdef HAVE_DELAYACCT - case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 4, &attr); break; - case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 4, &attr); break; - case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 4, &attr); break; + case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break; + case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break; + case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break; #endif case CTXT: if (lp->ctxt_diff > 1000) { diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index 5e18f6d..45b045c 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -166,6 +166,28 @@ static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) { #endif +static unsigned int scanAvailableCPUsFromCPUinfo(LinuxProcessList* this) { + FILE* file = fopen(PROCCPUINFOFILE, "r"); + if (file == NULL) + return this->super.existingCPUs; + + unsigned int availableCPUs = 0; + + while (!feof(file)) { + char buffer[PROC_LINE_LENGTH]; + + if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL) + break; + + if (String_startsWith(buffer, "processor")) + availableCPUs++; + } + + fclose(file); + + return availableCPUs ? availableCPUs : 1; +} + static void LinuxProcessList_updateCPUcount(ProcessList* super) { /* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD @@ -240,6 +262,12 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) { if (existing < 1) return; + if (Running_containerized) { + /* LXC munges /proc/cpuinfo but not the /sys/devices/system/cpu/ files, + * so limit the visible CPUs to what the guest has been configured to see: */ + currExisting = active = scanAvailableCPUsFromCPUinfo(this); + } + #ifdef HAVE_SENSORS_SENSORS_H /* When started with offline CPUs, libsensors does not monitor those, * even when they become online. */ @@ -248,7 +276,7 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) { #endif super->activeCPUs = active; - assert(existing == currExisting); + assert(Running_containerized || (existing == currExisting)); super->existingCPUs = currExisting; } @@ -1323,7 +1351,8 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc if (process->procExeDeleted) filename[filenameLen - markerLen] = '\0'; - process->mergedCommand.exeChanged |= oldExeDeleted ^ process->procExeDeleted; + if (oldExeDeleted != process->procExeDeleted) + process->mergedCommand.lastUpdate = 0; } Process_updateExe(process, filename); @@ -1389,6 +1418,21 @@ static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned lo return out; } +static bool isOlderThan(const ProcessList* pl, const Process* proc, unsigned int seconds) { + assert(pl->realtimeMs > 0); + + /* Starttime might not yet be parsed */ + if (proc->starttime_ctime <= 0) + return false; + + uint64_t realtime = pl->realtimeMs / 1000; + + if (realtime < (uint64_t)proc->starttime_ctime) + return false; + + return realtime - proc->starttime_ctime > seconds; +} + static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const char* dirname, const Process* parent, double period) { ProcessList* pl = (ProcessList*) this; const struct dirent* entry; @@ -1446,22 +1490,22 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ if (parent && pid == parent->pid) continue; - bool preExisting; - Process* proc = ProcessList_getProcess(pl, pid, &preExisting, LinuxProcess_new); - LinuxProcess* lp = (LinuxProcess*) proc; - - proc->tgid = parent ? parent->pid : pid; - proc->isUserlandThread = proc->pid != proc->tgid; - #ifdef HAVE_OPENAT int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); if (procFd < 0) - goto errorReadingProcess; + continue; #else char procFd[4096]; xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name); #endif + bool preExisting; + Process* proc = ProcessList_getProcess(pl, pid, &preExisting, LinuxProcess_new); + LinuxProcess* lp = (LinuxProcess*) proc; + + proc->tgid = parent ? parent->pid : pid; + proc->isUserlandThread = proc->pid != proc->tgid; + LinuxProcessList_recurseProcTree(this, procFd, "task", proc, period); /* @@ -1497,7 +1541,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ bool prev = proc->usesDeletedLib; if (!proc->isKernelThread && !proc->isUserlandThread && - ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted))) { + ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(pl, proc, 10)))) { // Check if we really should recalculate the M_LRS value for this process uint64_t passedTimeInMs = pl->realtimeMs - lp->last_mlrs_calctime; @@ -1514,7 +1558,8 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ lp->m_lrs = (proc->isUserlandThread && parent) ? ((const LinuxProcess*)parent)->m_lrs : 0; } - proc->mergedCommand.exeChanged |= prev ^ proc->usesDeletedLib; + if (prev != proc->usesDeletedLib) + proc->mergedCommand.lastUpdate = 0; } if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) { @@ -1653,8 +1698,13 @@ errorReadingProcess: #endif if (preExisting) { - ProcessList_remove(pl, proc); + /* + * The only real reason for coming here (apart from Linux violating the /proc API) + * would be the process going away with its /proc files disappearing (!HAVE_OPENAT). + * However, we want to keep in the process list for now for the "highlight dying" mode. + */ } else { + /* A really short-lived process that we don't have full info about */ Process_delete((Object*)proc); } } @@ -1874,6 +1924,7 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) { switch (buffer[0]) { case 'c': + tryRead("c_min", &lpl->zfs.min); tryRead("c_max", &lpl->zfs.max); tryReadFlag("compressed_size", &lpl->zfs.compressed, lpl->zfs.isCompressed); break; @@ -1908,6 +1959,7 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) { lpl->zfs.enabled = (lpl->zfs.size > 0 ? 1 : 0); lpl->zfs.size /= 1024; + lpl->zfs.min /= 1024; lpl->zfs.max /= 1024; lpl->zfs.MFU /= 1024; lpl->zfs.MRU /= 1024; @@ -2101,16 +2153,11 @@ static void scanCPUFrequencyFromCPUinfo(LinuxProcessList* this) { if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL) break; - if ( - (sscanf(buffer, "processor : %d", &cpuid) == 1) || - (sscanf(buffer, "processor: %d", &cpuid) == 1) - ) { + if (sscanf(buffer, "processor : %d", &cpuid) == 1) { continue; } else if ( (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) || - (sscanf(buffer, "cpu MHz: %lf", &frequency) == 1) || - (sscanf(buffer, "clock : %lfMHz", &frequency) == 1) || - (sscanf(buffer, "clock: %lfMHz", &frequency) == 1) + (sscanf(buffer, "clock : %lfMHz", &frequency) == 1) ) { if (cpuid < 0 || (unsigned int)cpuid > (existingCPUs - 1)) { continue; diff --git a/linux/Platform.c b/linux/Platform.c index 775f9ae..38b66e8 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -358,8 +358,13 @@ void Platform_setMemoryValues(Meter* this) { this->values[4] = pl->availableMem; if (lpl->zfs.enabled != 0 && !Running_containerized) { - this->values[0] -= lpl->zfs.size; - this->values[3] += lpl->zfs.size; + // ZFS does not shrink below the value of zfs_arc_min. + unsigned long long int shrinkableSize = 0; + if (lpl->zfs.size > lpl->zfs.min) + shrinkableSize = lpl->zfs.size - lpl->zfs.min; + this->values[0] -= shrinkableSize; + this->values[3] += shrinkableSize; + this->values[4] += shrinkableSize; } } @@ -1035,7 +1040,7 @@ bool Platform_init(void) { char lineBuffer[256]; while (fgets(lineBuffer, sizeof(lineBuffer), fd)) { // detect lxc or overlayfs and guess that this means we are running containerized - if (String_startsWith(lineBuffer, "lxcfs ") || String_startsWith(lineBuffer, "overlay ")) { + if (String_startsWith(lineBuffer, "lxcfs /proc") || String_startsWith(lineBuffer, "overlay ")) { Running_containerized = true; break; } diff --git a/linux/Platform.h b/linux/Platform.h index f2c314f..e6fa161 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -51,6 +51,8 @@ extern const MeterClass* const Platform_meterTypes[]; bool Platform_init(void); void Platform_done(void); +extern bool Running_containerized; + void Platform_setBindings(Htop_Action* keys); int Platform_getUptime(void); diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c index 567cfc7..53ae2d2 100644 --- a/linux/SystemdMeter.c +++ b/linux/SystemdMeter.c @@ -219,15 +219,18 @@ static void updateViaExec(void) { exit(1); dup2(fdnull, STDERR_FILENO); close(fdnull); - execlp("systemctl", - "systemctl", - "show", - "--property=SystemState", - "--property=NFailedUnits", - "--property=NNames", - "--property=NJobs", - "--property=NInstalledJobs", - NULL); + // Use of NULL in variadic functions must have a pointer cast. + // The NULL constant is not required by standard to have a pointer type. + execlp( + "systemctl", + "systemctl", + "show", + "--property=SystemState", + "--property=NFailedUnits", + "--property=NNames", + "--property=NJobs", + "--property=NInstalledJobs", + (char *)NULL); exit(127); } close(fdpair[1]); diff --git a/netbsd/NetBSDProcess.c b/netbsd/NetBSDProcess.c index a7d59be..4d4ac4e 100644 --- a/netbsd/NetBSDProcess.c +++ b/netbsd/NetBSDProcess.c @@ -144,7 +144,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { }, [PERCENT_CPU] = { .name = "PERCENT_CPU", - .title = "CPU% ", + .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, diff --git a/netbsd/Platform.c b/netbsd/Platform.c index 1812ddd..cf6079d 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -311,7 +311,13 @@ char* Platform_getProcessEnv(pid_t pid) { for (char** p = ptr; *p; p++) { size_t len = strlen(*p) + 1; - if (size + len > capacity) { + while (size + len > capacity) { + if (capacity > (SIZE_MAX / 2)) { + free(env); + env = NULL; + goto end; + } + capacity *= 2; env = xRealloc(env, capacity); } @@ -327,6 +333,7 @@ char* Platform_getProcessEnv(pid_t pid) { env[size + 1] = 0; } +end: (void) kvm_close(kt); return env; } diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c index feef7c4..c2f2ed4 100644 --- a/openbsd/OpenBSDProcess.c +++ b/openbsd/OpenBSDProcess.c @@ -142,7 +142,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { }, [PERCENT_CPU] = { .name = "PERCENT_CPU", - .title = "CPU% ", + .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, diff --git a/openbsd/Platform.c b/openbsd/Platform.c index db9780c..b222bee 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -269,7 +269,13 @@ char* Platform_getProcessEnv(pid_t pid) { for (char** p = ptr; *p; p++) { size_t len = strlen(*p) + 1; - if (size + len > capacity) { + while (size + len > capacity) { + if (capacity > (SIZE_MAX / 2)) { + free(env); + env = NULL; + goto end; + } + capacity *= 2; env = xRealloc(env, capacity); } @@ -285,6 +291,7 @@ char* Platform_getProcessEnv(pid_t pid) { env[size + 1] = 0; } +end: (void) kvm_close(kt); return env; } diff --git a/pcp/PCPMetric.h b/pcp/PCPMetric.h index aa0e2a7..84ccbb9 100644 --- a/pcp/PCPMetric.h +++ b/pcp/PCPMetric.h @@ -81,6 +81,7 @@ typedef enum PCPMetric_ { PCP_ZFS_ARC_BONUS_SIZE, /* zfs.arc.bonus_size */ PCP_ZFS_ARC_COMPRESSED_SIZE, /* zfs.arc.compressed_size */ PCP_ZFS_ARC_UNCOMPRESSED_SIZE, /* zfs.arc.uncompressed_size */ + PCP_ZFS_ARC_C_MIN, /* zfs.arc.c_min */ PCP_ZFS_ARC_C_MAX, /* zfs.arc.c_max */ PCP_ZFS_ARC_DBUF_SIZE, /* zfs.arc.dbuf_size */ PCP_ZFS_ARC_DNODE_SIZE, /* zfs.arc.dnode_size */ diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c index d0bcfbb..b8b87ca 100644 --- a/pcp/PCPProcess.c +++ b/pcp/PCPProcess.c @@ -54,7 +54,7 @@ const ProcessFieldData Process_fields[] = { [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, }, [M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, }, [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, - [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, @@ -74,7 +74,7 @@ const ProcessFieldData Process_fields[] = { [CGROUP] = { .name = "CGROUP", .title = " CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, }, [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, }, [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = 0, .defaultSortDesc = true, }, - [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, }, + [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, }, [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWAPD% ", .description = "Swapin delay %", .flags = 0, .defaultSortDesc = true, }, [M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it.", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, [M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c index ca82575..045f7ae 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessList.c @@ -601,6 +601,8 @@ static inline void PCPProcessList_scanZfsArcstats(PCPProcessList* this) { memset(&this->zfs, 0, sizeof(ZfsArcStats)); if (PCPMetric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.anon = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64)) + this->zfs.min = value.ull / ONE_K; if (PCPMetric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64)) this->zfs.max = value.ull / ONE_K; if (PCPMetric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64)) diff --git a/pcp/Platform.c b/pcp/Platform.c index b3d6ed4..342bf43 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -178,6 +178,7 @@ static const char* Platform_metricNames[] = { [PCP_ZFS_ARC_BONUS_SIZE] = "zfs.arc.bonus_size", [PCP_ZFS_ARC_COMPRESSED_SIZE] = "zfs.arc.compressed_size", [PCP_ZFS_ARC_UNCOMPRESSED_SIZE] = "zfs.arc.uncompressed_size", + [PCP_ZFS_ARC_C_MIN] = "zfs.arc.c_min", [PCP_ZFS_ARC_C_MAX] = "zfs.arc.c_max", [PCP_ZFS_ARC_DBUF_SIZE] = "zfs.arc.dbuf_size", [PCP_ZFS_ARC_DNODE_SIZE] = "zfs.arc.dnode_size", @@ -510,8 +511,13 @@ void Platform_setMemoryValues(Meter* this) { this->values[4] = pl->availableMem; if (ppl->zfs.enabled != 0) { - this->values[0] -= ppl->zfs.size; - this->values[3] += ppl->zfs.size; + // ZFS does not shrink below the value of zfs_arc_min. + unsigned long long int shrinkableSize = 0; + if (ppl->zfs.size > ppl->zfs.min) + shrinkableSize = ppl->zfs.size - ppl->zfs.min; + this->values[0] -= shrinkableSize; + this->values[3] += shrinkableSize; + this->values[4] += shrinkableSize; } } diff --git a/solaris/Platform.c b/solaris/Platform.c index 20b4d13..9c5acb5 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -267,16 +267,21 @@ static int Platform_buildenv(void* accum, struct ps_prochandle* Phandle, uintptr envAccum* accump = accum; (void) Phandle; (void) addr; + size_t thissz = strlen(str); - if ((thissz + 2) > (accump->capacity - accump->size)) { - accump->env = xRealloc(accump->env, accump->capacity *= 2); - } - if ((thissz + 2) > (accump->capacity - accump->size)) { - return 1; + + while ((thissz + 2) > (accump->capacity - accump->size)) { + if (accump->capacity > (SIZE_MAX / 2)) + return 1; + + accump->capacity *= 2; + accump->env = xRealloc(accump->env, accump->capacity); } - strlcpy( accump->env + accump->size, str, (accump->capacity - accump->size)); + + strlcpy( accump->env + accump->size, str, accump->capacity - accump->size); strncpy( accump->env + accump->size + thissz + 1, "\n", 2); - accump->size = accump->size + thissz + 1; + + accump->size += thissz + 1; return 0; } @@ -299,7 +304,8 @@ char* Platform_getProcessEnv(pid_t pid) { Prelease(Phandle, 0); strncpy( envBuilder.env + envBuilder.size, "\0", 1); - return envBuilder.env; + + return xRealloc(envBuilder.env, envBuilder.size + 1); } char* Platform_getInodeFilename(pid_t pid, ino_t inode) { diff --git a/solaris/SolarisProcess.c b/solaris/SolarisProcess.c index ae8bd70..840757e 100644 --- a/solaris/SolarisProcess.c +++ b/solaris/SolarisProcess.c @@ -40,7 +40,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, - [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, diff --git a/solaris/SolarisProcessList.c b/solaris/SolarisProcessList.c index 0c619ae..905cfbd 100644 --- a/solaris/SolarisProcessList.c +++ b/solaris/SolarisProcessList.c @@ -468,7 +468,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, proc->percent_cpu = ((uint16_t)_psinfo->pr_pctcpu / (double)32768) * (double)100.0; Process_updateCPUFieldWidths(proc->percent_cpu); - proc->time = _psinfo->pr_time.tv_sec; + proc->time = _psinfo->pr_time.tv_sec * 100 + _psinfo->pr_time.tv_nsec / 10000000; if (!preExisting) { // Tasks done only for NEW processes proc->isUserlandThread = false; proc->starttime_ctime = _psinfo->pr_start.tv_sec; @@ -497,7 +497,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, proc->percent_cpu = ((uint16_t)_lwpsinfo->pr_pctcpu / (double)32768) * (double)100.0; Process_updateCPUFieldWidths(proc->percent_cpu); - proc->time = _lwpsinfo->pr_time.tv_sec; + proc->time = _lwpsinfo->pr_time.tv_sec * 100 + _lwpsinfo->pr_time.tv_nsec / 10000000; if (!preExisting) { // Tasks done only for NEW LWPs proc->isUserlandThread = true; proc->ppid = _psinfo->pr_pid * 1024; diff --git a/unsupported/UnsupportedProcess.c b/unsupported/UnsupportedProcess.c index 5e27fe9..2aca048 100644 --- a/unsupported/UnsupportedProcess.c +++ b/unsupported/UnsupportedProcess.c @@ -35,7 +35,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, - [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, diff --git a/zfs/ZfsArcStats.h b/zfs/ZfsArcStats.h index e2cb411..1fe7236 100644 --- a/zfs/ZfsArcStats.h +++ b/zfs/ZfsArcStats.h @@ -10,6 +10,7 @@ in the source distribution for its full text. typedef struct ZfsArcStats_ { int enabled; int isCompressed; + unsigned long long int min; unsigned long long int max; unsigned long long int size; unsigned long long int MFU; -- cgit v1.2.3