From 1b805a31720727008b32b1129a167758519fd4db Mon Sep 17 00:00:00 2001 From: Daniel Lange Date: Mon, 2 May 2022 16:04:21 +0200 Subject: New upstream version 3.2.0 --- .github/workflows/ci.yml | 8 +- Action.c | 85 ++++++-- Action.h | 2 + CRT.c | 53 ++++- CRT.h | 13 ++ CategoriesPanel.c | 12 +- ChangeLog | 34 +++ ColumnsPanel.c | 30 ++- ColumnsPanel.h | 7 +- CommandLine.c | 27 +-- DiskIOMeter.c | 44 +++- DisplayOptionsPanel.c | 7 +- FunctionBar.c | 11 +- FunctionBar.h | 4 +- Hashtable.c | 10 +- Header.c | 8 +- HeaderLayout.h | 1 + IncSet.c | 41 ++-- IncSet.h | 3 +- InfoScreen.c | 16 +- ListItem.c | 14 +- ListItem.h | 8 + MainPanel.c | 27 ++- MainPanel.h | 3 +- Makefile.am | 2 + Meter.c | 10 +- Meter.h | 7 + NetworkIOMeter.c | 43 +++- OpenFilesScreen.c | 10 +- Panel.c | 22 +- Panel.h | 10 + Process.c | 98 +++++++-- Process.h | 24 +- ProcessList.c | 388 +++++++++------------------------ ProcessList.h | 12 +- README | 10 + ScreenManager.c | 97 +++++++-- ScreenManager.h | 5 +- ScreensPanel.c | 327 +++++++++++++++++++++++++++ ScreensPanel.h | 53 +++++ Settings.c | 347 ++++++++++++++++++++++------- Settings.h | 49 +++-- SignalsPanel.c | 5 +- SignalsPanel.h | 6 +- TraceScreen.c | 5 +- XUtils.c | 54 ++--- XUtils.h | 4 +- configure.ac | 36 ++- darwin/DarwinProcess.c | 48 +++- darwin/DarwinProcess.h | 2 + darwin/Platform.c | 10 +- darwin/Platform.h | 4 +- darwin/PlatformHelpers.h | 4 +- dragonflybsd/DragonFlyBSDProcess.c | 4 +- dragonflybsd/DragonFlyBSDProcessList.c | 3 +- dragonflybsd/Platform.c | 9 +- dragonflybsd/Platform.h | 4 +- dragonflybsd/ProcessField.h | 4 +- freebsd/FreeBSDProcess.c | 11 +- freebsd/FreeBSDProcess.h | 1 + freebsd/FreeBSDProcessList.c | 8 +- freebsd/Platform.c | 9 +- freebsd/Platform.h | 4 +- freebsd/ProcessField.h | 5 +- generic/uname.c | 2 +- htop.1.in | 25 ++- linux/CGroupUtils.c | 26 ++- linux/LinuxProcess.c | 22 +- linux/LinuxProcessList.c | 83 ++++--- linux/Platform.c | 104 +++++++-- linux/Platform.h | 4 +- netbsd/NetBSDProcess.c | 2 + netbsd/NetBSDProcessList.c | 5 +- netbsd/Platform.c | 10 +- netbsd/Platform.h | 4 +- openbsd/OpenBSDProcess.c | 2 + openbsd/OpenBSDProcessList.c | 5 +- openbsd/Platform.c | 10 +- openbsd/Platform.h | 4 +- pcp-htop.5.in | 2 +- pcp/PCPMetric.c | 2 +- pcp/PCPProcess.c | 4 +- pcp/PCPProcessList.c | 27 +-- pcp/Platform.c | 15 +- pcp/Platform.h | 4 +- solaris/Platform.c | 12 +- solaris/Platform.h | 6 +- solaris/SolarisProcess.c | 4 +- solaris/SolarisProcessList.c | 7 +- unsupported/Platform.c | 12 +- unsupported/Platform.h | 6 +- unsupported/UnsupportedProcess.c | 5 +- unsupported/UnsupportedProcessList.c | 3 +- 93 files changed, 1871 insertions(+), 768 deletions(-) create mode 100644 ScreensPanel.c create mode 100644 ScreensPanel.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 617ed30..af8ed88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,6 +113,10 @@ jobs: build-ubuntu-latest-pcp: # Turns out 'ubuntu-latest' can be older than 20.04, we want PCP v5+ runs-on: ubuntu-20.04 + env: + # Until Ubuntu catches up with pcp-5.2.3+: + # pcp/Platform.c:309:45: warning: passing argument 2 of ‘pmLookupName’ from incompatible pointer type [-Wincompatible-pointer-types] + CFLAGS: -Wno-error=incompatible-pointer-types steps: - uses: actions/checkout@v2 - name: Install Dependencies @@ -120,9 +124,7 @@ jobs: - name: Bootstrap run: ./autogen.sh - name: Configure - # Until Ubuntu catches up with pcp-5.2.3+, cannot use -werror due to: - # passing argument 2 of ‘pmLookupName’ from incompatible pointer type - run: ./configure --enable-pcp --enable-unicode + run: ./configure --enable-werror --enable-pcp --enable-unicode - name: Build run: make -k diff --git a/Action.c b/Action.c index 07e21dc..ce3cd13 100644 --- a/Action.c +++ b/Action.c @@ -58,7 +58,7 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) header->pl->following = pid; unfollow = true; } - ScreenManager_run(scr, &panelFocus, &ch); + ScreenManager_run(scr, &panelFocus, &ch, NULL); if (unfollow) { header->pl->following = -1; } @@ -85,7 +85,7 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) static void Action_runSetup(State* st) { ScreenManager* scr = ScreenManager_new(st->header, st->settings, st, true); CategoriesPanel_new(scr, st->settings, st->header, st->pl); - ScreenManager_run(scr, NULL, NULL); + ScreenManager_run(scr, NULL, NULL, "Setup"); ScreenManager_delete(scr); if (st->settings->changed) { Header_writeBackToSettings(st->header); @@ -154,7 +154,7 @@ static bool collapseIntoParent(Panel* panel) { } Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) { - Settings_setSortKey(settings, sortKey); + ScreenSettings_setSortKey(settings->ss, sortKey); return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_UPDATE_PANELHDR | HTOP_KEEP_FOLLOWING; } @@ -164,8 +164,9 @@ static Htop_Reaction actionSetSortColumn(State* st) { Htop_Reaction reaction = HTOP_OK; Panel* sortPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Sort ", "Cancel ")); Panel_setHeader(sortPanel, "Sort by"); - const ProcessField* fields = st->settings->fields; - Hashtable* dynamicColumns = st->settings->dynamicColumns; + const Settings* settings = st->settings; + const ProcessField* fields = settings->ss->fields; + Hashtable* dynamicColumns = settings->dynamicColumns; for (int i = 0; fields[i]; i++) { char* name = NULL; if (fields[i] >= LAST_PROCESSFIELD) { @@ -177,7 +178,7 @@ static Htop_Reaction actionSetSortColumn(State* st) { name = String_trim(Process_fields[fields[i]].name); } Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i])); - if (fields[i] == Settings_getActiveSortKey(st->settings)) + if (fields[i] == ScreenSettings_getActiveSortKey(settings->ss)) Panel_setSelected(sortPanel, i); free(name); @@ -188,8 +189,7 @@ static Htop_Reaction actionSetSortColumn(State* st) { } Object_delete(sortPanel); - if (st->pauseProcessUpdate) - ProcessList_sort(st->pl); + st->pl->needsSort = true; return reaction | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } @@ -231,16 +231,18 @@ static Htop_Reaction actionToggleMergedCommand(State* st) { } static Htop_Reaction actionToggleTreeView(State* st) { - st->settings->treeView = !st->settings->treeView; + ScreenSettings* ss = st->settings->ss; + ss->treeView = !ss->treeView; - if (!st->settings->allBranchesCollapsed) + if (!ss->allBranchesCollapsed) ProcessList_expandTree(st->pl); return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) { - st->settings->allBranchesCollapsed = !st->settings->allBranchesCollapsed; - if (st->settings->allBranchesCollapsed) + ScreenSettings* ss = st->settings->ss; + ss->allBranchesCollapsed = !ss->allBranchesCollapsed; + if (ss->allBranchesCollapsed) ProcessList_collapseAllBranches(st->pl); else ProcessList_expandTree(st->pl); @@ -277,9 +279,8 @@ static Htop_Reaction actionLowerPriority(State* st) { } static Htop_Reaction actionInvertSortOrder(State* st) { - Settings_invertSortOrder(st->settings); - if (st->pauseProcessUpdate) - ProcessList_sort(st->pl); + ScreenSettings_invertSortOrder(st->settings->ss); + st->pl->needsSort = true; return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING; } @@ -289,7 +290,7 @@ static Htop_Reaction actionExpandOrCollapse(State* st) { } static Htop_Reaction actionCollapseIntoParent(State* st) { - if (!st->settings->treeView) { + if (!st->settings->ss->treeView) { return HTOP_OK; } bool changed = collapseIntoParent((Panel*)st->mainPanel); @@ -297,7 +298,46 @@ static Htop_Reaction actionCollapseIntoParent(State* st) { } static Htop_Reaction actionExpandCollapseOrSortColumn(State* st) { - return st->settings->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st); + return st->settings->ss->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st); +} + +static Htop_Reaction actionNextScreen(State* st) { + Settings* settings = st->settings; + settings->ssIndex++; + if (settings->ssIndex == settings->nScreens) { + settings->ssIndex = 0; + } + settings->ss = settings->screens[settings->ssIndex]; + return HTOP_REFRESH; +} + +static Htop_Reaction actionPrevScreen(State* st) { + Settings* settings = st->settings; + if (settings->ssIndex == 0) { + settings->ssIndex = settings->nScreens - 1; + } else { + settings->ssIndex--; + } + settings->ss = settings->screens[settings->ssIndex]; + return HTOP_REFRESH; +} + +Htop_Reaction Action_setScreenTab(Settings* settings, int x) { + int s = 2; + for (unsigned int i = 0; i < settings->nScreens; i++) { + if (x < s) { + return 0; + } + const char* name = settings->screens[i]->name; + int len = strlen(name); + if (x <= s + len + 1) { + settings->ssIndex = i; + settings->ss = settings->screens[i]; + return HTOP_REFRESH; + } + s += len + 3; + } + return 0; } static Htop_Reaction actionQuit(ATTR_UNUSED State* st) { @@ -344,9 +384,12 @@ static Htop_Reaction actionKill(State* st) { if (Settings_isReadonly()) return HTOP_OK; - Panel* signalsPanel = SignalsPanel_new(); + static int preSelectedSignal = SIGNALSPANEL_INITSELECTEDSIGNAL; + + Panel* signalsPanel = SignalsPanel_new(preSelectedSignal); const ListItem* sgn = (ListItem*) Action_pickFromVector(st, signalsPanel, 14, true); if (sgn && sgn->key != 0) { + preSelectedSignal = sgn->key; Panel_setHeader((Panel*)st->mainPanel, "Sending..."); Panel_draw((Panel*)st->mainPanel, false, true, true, State_hideFunctionBar(st)); refresh(); @@ -459,6 +502,7 @@ static const struct { bool roInactive; const char* info; } helpLeft[] = { + { .key = " Tab: ", .roInactive = false, .info = "switch to next screen tab" }, { .key = " Arrows: ", .roInactive = false, .info = "scroll process list" }, { .key = " Digits: ", .roInactive = false, .info = "incremental PID search" }, { .key = " F3 /: ", .roInactive = false, .info = "incremental name search" }, @@ -483,6 +527,7 @@ static const struct { bool roInactive; const char* info; } helpRight[] = { + { .key = " S-Tab: ", .roInactive = false, .info = "switch to previous screen tab" }, { .key = " Space: ", .roInactive = false, .info = "tag process" }, { .key = " c: ", .roInactive = false, .info = "tag process and its children" }, { .key = " U: ", .roInactive = false, .info = "untag all processes" }, @@ -558,7 +603,7 @@ static Htop_Reaction actionHelp(State* st) { addattrstr(CRT_colors[BAR_BORDER], "["); addattrstr(CRT_colors[SWAP], "used"); #ifdef HTOP_LINUX - addattrstr(CRT_colors[BAR_SHADOW], "/"); + addstr("/"); addattrstr(CRT_colors[SWAP_CACHE], "cache"); addattrstr(CRT_colors[BAR_SHADOW], " used/total"); #else @@ -711,4 +756,6 @@ void Action_setBindings(Htop_Action* keys) { keys[KEY_F(10)] = actionQuit; keys[KEY_F(18)] = actionExpandCollapseOrSortColumn; keys[KEY_RECLICK] = actionExpandOrCollapse; + keys[KEY_SHIFT_TAB] = actionPrevScreen; + keys['\t'] = actionNextScreen; } diff --git a/Action.h b/Action.h index e1d14e4..06af188 100644 --- a/Action.h +++ b/Action.h @@ -57,6 +57,8 @@ bool Action_setUserOnly(const char* userName, uid_t* userId); Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey); +Htop_Reaction Action_setScreenTab(Settings* settings, int x); + Htop_Reaction Action_follow(State* st); void Action_setBindings(Htop_Action* keys); diff --git a/CRT.c b/CRT.c index ce59994..583b626 100644 --- a/CRT.c +++ b/CRT.c @@ -13,6 +13,7 @@ in the source distribution for its full text. #include #include #include +#include #include #include #include @@ -193,6 +194,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Magenta, Black), [CPU_STEAL] = ColorPair(Cyan, Black), [CPU_GUEST] = ColorPair(Cyan, Black), + [PANEL_EDIT] = ColorPair(White, Blue), + [SCREENS_OTH_BORDER] = ColorPair(Blue, Blue), + [SCREENS_OTH_TEXT] = ColorPair(Black, Blue), + [SCREENS_CUR_BORDER] = ColorPair(Green, Green), + [SCREENS_CUR_TEXT] = ColorPair(Black, Green), [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Cyan, Black), [PRESSURE_STALL_SIXTY] = A_BOLD | ColorPair(Cyan, Black), [PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Black), @@ -295,6 +301,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = A_BOLD, [CPU_STEAL] = A_DIM, [CPU_GUEST] = A_DIM, + [PANEL_EDIT] = A_BOLD, + [SCREENS_OTH_BORDER] = A_DIM, + [SCREENS_OTH_TEXT] = A_DIM, + [SCREENS_CUR_BORDER] = A_REVERSE, + [SCREENS_CUR_TEXT] = A_REVERSE, [PRESSURE_STALL_THREEHUNDRED] = A_DIM, [PRESSURE_STALL_SIXTY] = A_NORMAL, [PRESSURE_STALL_TEN] = A_BOLD, @@ -397,6 +408,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Blue, White), [CPU_STEAL] = ColorPair(Cyan, White), [CPU_GUEST] = ColorPair(Cyan, White), + [PANEL_EDIT] = ColorPair(White,Blue), + [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Black,White), + [SCREENS_OTH_TEXT] = A_BOLD | ColorPair(Black,White), + [SCREENS_CUR_BORDER] = ColorPair(Green,Green), + [SCREENS_CUR_TEXT] = ColorPair(Black,Green), [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, White), [PRESSURE_STALL_SIXTY] = ColorPair(Black, White), [PRESSURE_STALL_TEN] = ColorPair(Black, White), @@ -499,6 +515,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Blue, Black), [CPU_STEAL] = ColorPair(Black, Black), [CPU_GUEST] = ColorPair(Black, Black), + [PANEL_EDIT] = ColorPair(White,Blue), + [SCREENS_OTH_BORDER] = ColorPair(Blue,Black), + [SCREENS_OTH_TEXT] = ColorPair(Blue,Black), + [SCREENS_CUR_BORDER] = ColorPair(Green,Green), + [SCREENS_CUR_TEXT] = ColorPair(Black,Green), [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, Black), [PRESSURE_STALL_SIXTY] = ColorPair(Black, Black), [PRESSURE_STALL_TEN] = ColorPair(Black, Black), @@ -601,6 +622,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Black, Blue), [CPU_STEAL] = ColorPair(White, Blue), [CPU_GUEST] = ColorPair(White, Blue), + [PANEL_EDIT] = ColorPair(White,Blue), + [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Yellow,Blue), + [SCREENS_OTH_TEXT] = ColorPair(Cyan,Blue), + [SCREENS_CUR_BORDER] = ColorPair(Cyan,Cyan), + [SCREENS_CUR_TEXT] = ColorPair(Black,Cyan), [PRESSURE_STALL_THREEHUNDRED] = A_BOLD | ColorPair(Black, Blue), [PRESSURE_STALL_SIXTY] = A_NORMAL | ColorPair(White, Blue), [PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Blue), @@ -701,6 +727,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Blue, Black), [CPU_STEAL] = ColorPair(Cyan, Black), [CPU_GUEST] = ColorPair(Cyan, Black), + [PANEL_EDIT] = ColorPair(White,Cyan), + [SCREENS_OTH_BORDER] = ColorPair(White,Black), + [SCREENS_OTH_TEXT] = ColorPair(Cyan,Black), + [SCREENS_CUR_BORDER] = A_BOLD | ColorPair(White,Black), + [SCREENS_CUR_TEXT] = A_BOLD | ColorPair(Green,Black), [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Green, Black), [PRESSURE_STALL_SIXTY] = ColorPair(Green, Black), [PRESSURE_STALL_TEN] = A_BOLD | ColorPair(Green, Black), @@ -725,8 +756,6 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [COLORSCHEME_BROKENGRAY] = { 0 } // dynamically generated. }; -int CRT_cursorX = 0; - int CRT_scrollHAmount = 5; int CRT_scrollWheelVAmount = 10; @@ -816,6 +845,16 @@ static void dumpStderr(void) { stderrRedirectNewFd = -1; } +void CRT_debug_impl(const char* file, size_t lineno, const char* func, const char* fmt, ...) { + va_list args; + + fprintf(stderr, "[%s:%zu (%s)]: ", file, lineno, func); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + #else /* !NDEBUG */ static void redirectStderr(void) { @@ -841,6 +880,7 @@ static void CRT_installSignalHandlers(void) { sigaction (SIGSYS, &act, &old_sig_handler[SIGSYS]); sigaction (SIGABRT, &act, &old_sig_handler[SIGABRT]); + signal(SIGCHLD, SIG_DFL); signal(SIGINT, CRT_handleSIGTERM); signal(SIGTERM, CRT_handleSIGTERM); signal(SIGQUIT, CRT_handleSIGTERM); @@ -915,6 +955,7 @@ IGNORE_WCASTQUAL_BEGIN define_key("\033[14~", KEY_F(4)); define_key("\033[14;2~", KEY_F(15)); define_key("\033[17;2~", KEY_F(18)); + define_key("\033[Z", KEY_SHIFT_TAB); char sequence[3] = "\033a"; for (char c = 'a'; c <= 'z'; c++) { sequence[1] = c; @@ -925,6 +966,9 @@ IGNORE_WCASTQUAL_END #undef define_key #endif } + if (termType && (String_startsWith(termType, "rxvt"))) { + define_key("\033[Z", KEY_SHIFT_TAB); + } CRT_installSignalHandlers(); @@ -961,6 +1005,11 @@ IGNORE_WCASTQUAL_END } void CRT_done() { + attron(CRT_colors[RESET_COLOR]); + mvhline(LINES - 1, 0, ' ', COLS); + attroff(CRT_colors[RESET_COLOR]); + refresh(); + curs_set(1); endwin(); diff --git a/CRT.h b/CRT.h index c437e65..eae4722 100644 --- a/CRT.h +++ b/CRT.h @@ -120,6 +120,11 @@ typedef enum ColorElements_ { CPU_SOFTIRQ, CPU_STEAL, CPU_GUEST, + PANEL_EDIT, + SCREENS_OTH_BORDER, + SCREENS_OTH_TEXT, + SCREENS_CUR_BORDER, + SCREENS_CUR_TEXT, PRESSURE_STALL_TEN, PRESSURE_STALL_SIXTY, PRESSURE_STALL_THREEHUNDRED, @@ -145,11 +150,19 @@ typedef enum ColorElements_ { void CRT_fatalError(const char* note) ATTR_NORETURN; +#ifdef NDEBUG +# define CRT_debug(...) +#else +void CRT_debug_impl(const char* file, size_t lineno, const char* func, const char* fmt, ...) ATTR_FORMAT(printf, 4, 5); +# define CRT_debug(...) CRT_debug_impl(__FILE__, __LINE__, __func__, __VA_ARGS__) +#endif + void CRT_handleSIGSEGV(int signal) ATTR_NORETURN; #define KEY_WHEELUP KEY_F(30) #define KEY_WHEELDOWN KEY_F(31) #define KEY_RECLICK KEY_F(32) +#define KEY_SHIFT_TAB KEY_F(33) #define KEY_ALT(x) (KEY_F(64 - 26) + ((x) - 'A')) extern const char* CRT_degreeSign; diff --git a/CategoriesPanel.c b/CategoriesPanel.c index 30867ee..6e905ce 100644 --- a/CategoriesPanel.c +++ b/CategoriesPanel.c @@ -14,7 +14,6 @@ in the source distribution for its full text. #include "AvailableColumnsPanel.h" #include "AvailableMetersPanel.h" #include "ColorsPanel.h" -#include "ColumnsPanel.h" #include "DisplayOptionsPanel.h" #include "FunctionBar.h" #include "Header.h" @@ -25,6 +24,7 @@ in the source distribution for its full text. #include "MetersPanel.h" #include "Object.h" #include "ProvideCurses.h" +#include "ScreensPanel.h" #include "Vector.h" #include "XUtils.h" @@ -69,9 +69,11 @@ static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) { ScreenManager_add(this->scr, colors, -1); } -static void CategoriesPanel_makeColumnsPage(CategoriesPanel* this) { - Panel* columns = (Panel*) ColumnsPanel_new(this->settings); +static void CategoriesPanel_makeScreensPage(CategoriesPanel* this) { + Panel* screens = (Panel*) ScreensPanel_new(this->settings); + Panel* columns = (Panel*) ((ScreensPanel*)screens)->columns; Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, this->settings->dynamicColumns); + ScreenManager_add(this->scr, screens, 20); ScreenManager_add(this->scr, columns, 20); ScreenManager_add(this->scr, availableColumns, -1); } @@ -91,7 +93,7 @@ static const CategoriesPanelPage categoriesPanelPages[] = { { .name = "Display options", .ctor = CategoriesPanel_makeDisplayOptionsPage }, { .name = "Header layout", .ctor = CategoriesPanel_makeHeaderOptionsPage }, { .name = "Meters", .ctor = CategoriesPanel_makeMetersPage }, - { .name = "Columns", .ctor = CategoriesPanel_makeColumnsPage }, + { .name = "Screens", .ctor = CategoriesPanel_makeScreensPage }, { .name = "Colors", .ctor = CategoriesPanel_makeColorsPage }, }; @@ -157,7 +159,7 @@ CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Settings* settings, Hea this->settings = settings; this->header = header; this->pl = pl; - Panel_setHeader(super, "Setup"); + Panel_setHeader(super, "Categories"); for (size_t i = 0; i < ARRAYSIZE(categoriesPanelPages); i++) Panel_add(super, (Object*) ListItem_new(categoriesPanelPages[i].name, 0)); diff --git a/ChangeLog b/ChangeLog index a7f92bb..a7f4526 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,37 @@ +What's new in version 3.2.0 + +* Support for displaying multiple tabs in the user interface +* Allow multiple filter and search terms (logical OR, separate by "|") +* Set correct default sorting direction (defaultSortDesc) +* Improve performance for process lookup and update +* Rework the IOMeters initial display +* Removed duplicate sections on COMM and EXE +* Highlight process UNINTERRUPTIBLE_WAIT state (D) +* Show only integer value when CPU% more than 99.9% +* Handle rounding ambiguity between 99.9 and 100.0% +* No longer leaves empty the last column in header +* Fix header layout and meters reset if a header column is empty +* Fix PID and UID column widths off-by-one error +* On Linux, read generic sysfs batteries +* On Linux, do not collect LRS per thread (it is process-wide) +* On Linux, dynamically adjust the SECATTR and CGROUP column widths +* On Linux, fix a crash in LXD +* On FreeBSD, add support for showing process emulation +* On Darwin, lazily set process TTY name +* Always set SIGCHLD to default handling +* Avoid zombie processes on signal races +* Ensure last line is cleared when SIGINT is received +* Instead of SIGTERM, pre-select the last sent signal +* Internal Hashtable performance and sizing improvements +* Add heuristics for guessing LXC or Docker from /proc/1/mounts +* Force elapsed time display to zero if process started in the future +* Avoid extremely large year values when printing time +* Fix division by zero when calculating IO rates +* Fix out of boundary writes in XUtils +* Fix custom thread name display issue +* Use AC_CANONICAL_HOST, not AC_CANONICAL_TARGET in configure.ac +* Support libunwind of LLVM + What's new in version 3.1.2 * Bugfix for crash when storing modified settings at exit diff --git a/ColumnsPanel.c b/ColumnsPanel.c index a1450bb..2482693 100644 --- a/ColumnsPanel.c +++ b/ColumnsPanel.c @@ -138,20 +138,26 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) Panel_add(super, (Object*) ListItem_new(name, key)); } -ColumnsPanel* ColumnsPanel_new(Settings* settings) { +void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns) { + Panel* super = (Panel*) this; + Panel_prune(super); + for (const ProcessField* fields = ss->fields; *fields; fields++) + ColumnsPanel_add(super, *fields, columns); + this->ss = ss; +} + +ColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed) { ColumnsPanel* this = AllocThis(ColumnsPanel); Panel* super = (Panel*) this; FunctionBar* fuBar = FunctionBar_new(ColumnsFunctions, NULL, NULL); Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); - this->settings = settings; + this->ss = ss; + this->changed = changed; this->moving = false; Panel_setHeader(super, "Active Columns"); - Hashtable* dynamicColumns = settings->dynamicColumns; - const ProcessField* fields = settings->fields; - for (; *fields; fields++) - ColumnsPanel_add(super, *fields, dynamicColumns); + ColumnsPanel_fill(this, ss, columns); return this; } @@ -159,14 +165,14 @@ ColumnsPanel* ColumnsPanel_new(Settings* settings) { void ColumnsPanel_update(Panel* super) { ColumnsPanel* this = (ColumnsPanel*) super; int size = Panel_size(super); - this->settings->changed = true; - this->settings->fields = xRealloc(this->settings->fields, sizeof(ProcessField) * (size + 1)); - this->settings->flags = 0; + *(this->changed) = true; + this->ss->fields = xRealloc(this->ss->fields, sizeof(ProcessField) * (size + 1)); + this->ss->flags = 0; for (int i = 0; i < size; i++) { int key = ((ListItem*) Panel_get(super, i))->key; - this->settings->fields[i] = key; + this->ss->fields[i] = key; if (key < LAST_PROCESSFIELD) - this->settings->flags |= Process_fields[key].flags; + this->ss->flags |= Process_fields[key].flags; } - this->settings->fields[size] = 0; + this->ss->fields[size] = 0; } diff --git a/ColumnsPanel.h b/ColumnsPanel.h index d9360f4..63f6f92 100644 --- a/ColumnsPanel.h +++ b/ColumnsPanel.h @@ -15,14 +15,17 @@ in the source distribution for its full text. typedef struct ColumnsPanel_ { Panel super; + ScreenSettings* ss; + bool* changed; - Settings* settings; bool moving; } ColumnsPanel; extern const PanelClass ColumnsPanel_class; -ColumnsPanel* ColumnsPanel_new(Settings* settings); +ColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed); + +void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns); void ColumnsPanel_update(Panel* super); diff --git a/CommandLine.c b/CommandLine.c index b2ad06e..d21f482 100644 --- a/CommandLine.c +++ b/CommandLine.c @@ -53,15 +53,17 @@ static void printHelpFlag(const char* name) { "-d --delay=DELAY Set the delay between updates, in tenths of seconds\n" "-F --filter=FILTER Show only the commands matching the given filter\n" "-h --help Print this help screen\n" - "-H --highlight-changes[=DELAY] Highlight new and old processes\n" - "-M --no-mouse Disable the mouse\n" - "-p --pid=PID[,PID,PID...] Show only the given PIDs\n" + "-H --highlight-changes[=DELAY] Highlight new and old processes\n", name); +#ifdef HAVE_GETMOUSE + printf("-M --no-mouse Disable the mouse\n"); +#endif + printf("-p --pid=PID[,PID,PID...] Show only the given PIDs\n" " --readonly Disable all system and process changing features\n" "-s --sort-key=COLUMN Sort by COLUMN in list view (try --sort-key=help for a list)\n" "-t --tree Show the tree view (can be combined with -s)\n" "-u --user[=USERNAME] Show only processes for a given user (or $USER)\n" "-U --no-unicode Do not use unicode but plain ASCII\n" - "-V --version Print version info\n", name); + "-V --version Print version info\n"); Platform_longOptionsUsage(name); printf("\n" "Long options may be passed with a single dash.\n\n" @@ -328,7 +330,7 @@ int CommandLine_run(const char* name, int argc, char** argv) { settings->enableMouse = false; #endif if (flags.treeView) - settings->treeView = true; + settings->ss->treeView = true; if (flags.highlightChanges) settings->highlightChanges = true; if (flags.highlightDelaySecs != -1) @@ -337,9 +339,9 @@ int CommandLine_run(const char* name, int argc, char** argv) { // -t -s means "tree sorted by key" // -s means "list sorted by key" (previous existing behavior) if (!flags.treeView) { - settings->treeView = false; + settings->ss->treeView = false; } - Settings_setSortKey(settings, flags.sortKey); + ScreenSettings_setSortKey(settings->ss, flags.sortKey); } CRT_init(settings, flags.allowUnicode); @@ -347,7 +349,7 @@ int CommandLine_run(const char* name, int argc, char** argv) { MainPanel* panel = MainPanel_new(); ProcessList_setPanel(pl, (Panel*) panel); - MainPanel_updateTreeFunctions(panel, settings->treeView); + MainPanel_updateLabels(panel, settings->ss->treeView, flags.commFilter); State state = { .settings = settings, @@ -370,15 +372,10 @@ int CommandLine_run(const char* name, int argc, char** argv) { CommandLine_delay(pl, 75); ProcessList_scan(pl, false); - if (settings->allBranchesCollapsed) + if (settings->ss->allBranchesCollapsed) ProcessList_collapseAllBranches(pl); - ScreenManager_run(scr, NULL, NULL); - - attron(CRT_colors[RESET_COLOR]); - mvhline(LINES - 1, 0, ' ', COLS); - attroff(CRT_colors[RESET_COLOR]); - refresh(); + ScreenManager_run(scr, NULL, NULL, NULL); Platform_done(); diff --git a/DiskIOMeter.c b/DiskIOMeter.c index 11fb791..12c87de 100644 --- a/DiskIOMeter.c +++ b/DiskIOMeter.c @@ -12,6 +12,7 @@ in the source distribution for its full text. #include "CRT.h" #include "Macros.h" +#include "Meter.h" #include "Object.h" #include "Platform.h" #include "ProcessList.h" @@ -25,7 +26,7 @@ static const int DiskIOMeter_attributes[] = { METER_VALUE_IOWRITE, }; -static bool hasData = false; +static MeterRateStatus status = RATESTATUS_INIT; static uint32_t cached_read_diff; static uint32_t cached_write_diff; static double cached_utilisation_diff; @@ -36,20 +37,27 @@ static void DiskIOMeter_updateValues(Meter* this) { static uint64_t cached_last_update; uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update; - /* update only every 500ms */ + /* update only every 500ms to have a sane span for rate calculation */ if (passedTimeInMs > 500) { static uint64_t cached_read_total; static uint64_t cached_write_total; static uint64_t cached_msTimeSpend_total; uint64_t diff; - cached_last_update = pl->realtimeMs; - DiskIOData data; + if (!Platform_getDiskIO(&data)) { + status = RATESTATUS_NODATA; + } else if (cached_last_update == 0) { + status = RATESTATUS_INIT; + } else if (passedTimeInMs > 30000) { + status = RATESTATUS_STALE; + } else { + status = RATESTATUS_DATA; + } + + cached_last_update = pl->realtimeMs; - hasData = Platform_getDiskIO(&data); - if (!hasData) { - this->values[0] = 0; + if (status == RATESTATUS_NODATA) { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); return; } @@ -81,10 +89,13 @@ static void DiskIOMeter_updateValues(Meter* this) { cached_msTimeSpend_total = data.totalMsTimeSpend; } - if (passedTimeInMs > 30000) { - // Triggers for the first initialization and - // when there was a long time we did not collect updates - hasData = false; + if (status == RATESTATUS_INIT) { + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init"); + return; + } + if (status == RATESTATUS_STALE) { + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale"); + return; } this->values[0] = cached_utilisation_diff; @@ -97,9 +108,18 @@ static void DiskIOMeter_updateValues(Meter* this) { } static void DiskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { - if (!hasData) { + switch (status) { + case RATESTATUS_NODATA: RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); return; + case RATESTATUS_INIT: + RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing..."); + return; + case RATESTATUS_STALE: + RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data"); + return; + case RATESTATUS_DATA: + break; } char buffer[16]; diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index 8212120..f5e64b1 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -97,9 +97,10 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* this->scr = scr; Panel_setHeader(super, "Display options"); - Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->treeView))); - Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->treeViewAlwaysByPID))); - Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->allBranchesCollapsed))); + Panel_add(super, (Object*) CheckItem_newByRef("Tree view (for the current Screen tab)", &(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*) 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))); Panel_add(super, (Object*) CheckItem_newByRef("Hide userland process threads", &(settings->hideUserlandThreads))); diff --git a/FunctionBar.c b/FunctionBar.c index fc3304a..0850037 100644 --- a/FunctionBar.c +++ b/FunctionBar.c @@ -88,11 +88,12 @@ void FunctionBar_setLabel(FunctionBar* this, int event, const char* text) { } } -void FunctionBar_draw(const FunctionBar* this) { - FunctionBar_drawExtra(this, NULL, -1, false); +int FunctionBar_draw(const FunctionBar* this) { + return FunctionBar_drawExtra(this, NULL, -1, false); } -void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor) { +int FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor) { + int cursorX = 0; attrset(CRT_colors[FUNCTION_BAR]); mvhline(LINES - 1, 0, ' ', COLS); int x = 0; @@ -113,18 +114,20 @@ void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr } mvaddstr(LINES - 1, x, buffer); x += strlen(buffer); + cursorX = x; } attrset(CRT_colors[RESET_COLOR]); if (setCursor) { - CRT_cursorX = x; curs_set(1); } else { curs_set(0); } currentLen = x; + + return cursorX; } void FunctionBar_append(const char* buffer, int attr) { diff --git a/FunctionBar.h b/FunctionBar.h index ebe405f..f01a5ef 100644 --- a/FunctionBar.h +++ b/FunctionBar.h @@ -29,9 +29,9 @@ void FunctionBar_delete(FunctionBar* this); void FunctionBar_setLabel(FunctionBar* this, int event, const char* text); -void FunctionBar_draw(const FunctionBar* this); +int FunctionBar_draw(const FunctionBar* this); -void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor); +int FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor); void FunctionBar_append(const char* buffer, int attr); diff --git a/Hashtable.c b/Hashtable.c index c880cf7..a0cfc9e 100644 --- a/Hashtable.c +++ b/Hashtable.c @@ -90,7 +90,7 @@ size_t Hashtable_count(const Hashtable* this) { /* https://oeis.org/A014234 */ static const uint64_t OEISprimes[] = { - 2, 3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, + 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, 134217689, 268435399, 536870909, 1073741789, @@ -191,10 +191,14 @@ void Hashtable_setSize(Hashtable* this, size_t size) { if (size <= this->items) return; + size_t newSize = nextPrime(size); + if (newSize == this->size) + return; + HashtableItem* oldBuckets = this->buckets; size_t oldSize = this->size; - this->size = nextPrime(size); + this->size = newSize; this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem)); this->items = 0; @@ -282,7 +286,7 @@ void* Hashtable_remove(Hashtable* this, ht_key_t key) { /* shrink on load-factor < 0.125 */ if (8 * this->items < this->size) - Hashtable_setSize(this, this->size / 2); + Hashtable_setSize(this, this->size / 3); /* account for nextPrime rounding up */ return res; } diff --git a/Header.c b/Header.c index c557a45..8dff89b 100644 --- a/Header.c +++ b/Header.c @@ -194,7 +194,8 @@ void Header_draw(const Header* this) { for (int y = 0; y < height; y++) { mvhline(y, 0, ' ', COLS); } - const int width = COLS - pad; + const int numCols = HeaderLayout_getColumns(this->headerLayout); + const int width = COLS - 2 * pad - (numCols - 1); int x = pad; float roundingLoss = 0.0F; @@ -217,6 +218,7 @@ void Header_draw(const Header* this) { except for multi column meters. */ if (meter->mode == TEXT_METERMODE && !Meter_isMultiColumn(meter)) { for (int j = 1; j < meter->columnWidthCount; j++) { + actualWidth++; /* separator column */ actualWidth += (float)width * HeaderLayout_layouts[this->headerLayout].widths[col + j] / 100.0F; } } @@ -227,6 +229,7 @@ void Header_draw(const Header* this) { } x += floorf(colWidth); + x++; /* separator column */ } } @@ -283,6 +286,9 @@ int Header_calculateHeight(Header* this) { } maxHeight = MAXIMUM(maxHeight, height); } + if (this->settings->screenTabs) { + maxHeight++; + } this->height = maxHeight; this->pad = pad; return maxHeight; diff --git a/HeaderLayout.h b/HeaderLayout.h index 46cca14..1cf7bf7 100644 --- a/HeaderLayout.h +++ b/HeaderLayout.h @@ -18,6 +18,7 @@ in the source distribution for its full text. typedef enum HeaderLayout_ { + HF_INVALID = -1, HF_TWO_50_50, HF_TWO_33_67, HF_TWO_67_33, diff --git a/IncSet.c b/IncSet.c index 56f9c32..71edf1c 100644 --- a/IncSet.c +++ b/IncSet.c @@ -85,7 +85,7 @@ static void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) { const char* incFilter = this->modes[INC_FILTER].buffer; for (int i = 0; i < Vector_size(lines); i++) { ListItem* line = (ListItem*)Vector_get(lines, i); - if (String_contains_i(line->value, incFilter)) { + if (String_contains_i(line->value, incFilter, true)) { Panel_add(panel, (Object*)line); if (selected == (Object*)line) { Panel_setSelected(panel, n); @@ -105,10 +105,10 @@ static void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) { } } -static bool search(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue) { +static bool search(const IncSet* this, Panel* panel, IncMode_GetPanelValue getPanelValue) { int size = Panel_size(panel); for (int i = 0; i < size; i++) { - if (String_contains_i(getPanelValue(panel, i), mode->buffer)) { + if (String_contains_i(getPanelValue(panel, i), this->active->buffer, true)) { Panel_setSelected(panel, i); return true; } @@ -117,6 +117,21 @@ static bool search(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getP return false; } +void IncSet_activate(IncSet* this, IncType type, Panel* panel) { + this->active = &(this->modes[type]); + panel->currentBar = this->active->bar; + panel->cursorOn = true; + this->panel = panel; + IncSet_drawBar(this, CRT_colors[FUNCTION_BAR]); +} + +static void IncSet_deactivate(IncSet* this, Panel* panel) { + this->active = NULL; + Panel_setDefaultBar(panel); + panel->cursorOn = false; + FunctionBar_draw(this->defaultBar); +} + static bool IncMode_find(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue, int step) { int size = Panel_size(panel); int here = Panel_getSelectedIndex(panel); @@ -133,7 +148,7 @@ static bool IncMode_find(const IncMode* mode, Panel* panel, IncMode_GetPanelValu return false; } - if (String_contains_i(getPanelValue(panel, i), mode->buffer)) { + if (String_contains_i(getPanelValue(panel, i), mode->buffer, true)) { Panel_setSelected(panel, i); return true; } @@ -194,12 +209,11 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue IncMode_reset(mode); } } - this->active = NULL; - Panel_setDefaultBar(panel); + IncSet_deactivate(this, panel); doSearch = false; } if (doSearch) { - this->found = search(mode, panel, getPanelValue); + this->found = search(this, panel, getPanelValue); } if (filterChanged && lines) { updateWeakPanel(this, panel, lines); @@ -212,14 +226,13 @@ const char* IncSet_getListItemValue(Panel* panel, int i) { return l ? l->value : ""; } -void IncSet_activate(IncSet* this, IncType type, Panel* panel) { - this->active = &(this->modes[type]); - panel->currentBar = this->active->bar; -} - -void IncSet_drawBar(const IncSet* this) { +void IncSet_drawBar(const IncSet* this, int attr) { if (this->active) { - FunctionBar_drawExtra(this->active->bar, this->active->buffer, (this->active->isFilter || this->found) ? -1 : CRT_colors[FAILED_SEARCH], true); + if (!this->active->isFilter && !this->found) + attr = CRT_colors[FAILED_SEARCH]; + int cursorX = FunctionBar_drawExtra(this->active->bar, this->active->buffer, attr, true); + this->panel->cursorY = LINES - 1; + this->panel->cursorX = cursorX; } else { FunctionBar_draw(this->defaultBar); } diff --git a/IncSet.h b/IncSet.h index a98e7f8..15b5d5d 100644 --- a/IncSet.h +++ b/IncSet.h @@ -32,6 +32,7 @@ typedef struct IncMode_ { typedef struct IncSet_ { IncMode modes[2]; IncMode* active; + Panel* panel; FunctionBar* defaultBar; bool filtering; bool found; @@ -57,7 +58,7 @@ const char* IncSet_getListItemValue(Panel* panel, int i); void IncSet_activate(IncSet* this, IncType type, Panel* panel); -void IncSet_drawBar(const IncSet* this); +void IncSet_drawBar(const IncSet* this, int attr); int IncSet_synthesizeEvent(IncSet* this, int x); diff --git a/InfoScreen.c b/InfoScreen.c index f431f79..105d9c3 100644 --- a/InfoScreen.c +++ b/InfoScreen.c @@ -57,13 +57,13 @@ void InfoScreen_drawTitled(InfoScreen* this, const char* fmt, ...) { attrset(CRT_colors[DEFAULT_COLOR]); Panel_draw(this->display, true, true, true, false); - IncSet_drawBar(this->inc); + IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]); } void InfoScreen_addLine(InfoScreen* this, const char* line) { Vector_add(this->lines, (Object*) ListItem_new(line, 0)); const char* incFilter = IncSet_filter(this->inc); - if (!incFilter || String_contains_i(line, incFilter)) { + if (!incFilter || String_contains_i(line, incFilter, true)) { Panel_add(this->display, Vector_get(this->lines, Vector_size(this->lines) - 1)); } } @@ -72,7 +72,7 @@ void InfoScreen_appendLine(InfoScreen* this, const char* line) { ListItem* last = (ListItem*)Vector_get(this->lines, Vector_size(this->lines) - 1); ListItem_append(last, line); const char* incFilter = IncSet_filter(this->inc); - if (incFilter && Panel_get(this->display, Panel_size(this->display) - 1) != (Object*)last && String_contains_i(line, incFilter)) { + if (incFilter && Panel_get(this->display, Panel_size(this->display) - 1) != (Object*)last && String_contains_i(line, incFilter, true)) { Panel_add(this->display, (Object*)last); } } @@ -89,15 +89,9 @@ void InfoScreen_run(InfoScreen* this) { while (looping) { Panel_draw(panel, false, true, true, false); - IncSet_drawBar(this->inc); + IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]); - if (this->inc->active) { - (void) move(LINES - 1, CRT_cursorX); - } -#ifdef HAVE_SET_ESCDELAY - set_escdelay(25); -#endif - int ch = getch(); + int ch = Panel_getCh(panel); if (ch == ERR) { if (As_InfoScreen(this)->onErr) { diff --git a/ListItem.c b/ListItem.c index 9161a1c..246bc5d 100644 --- a/ListItem.c +++ b/ListItem.c @@ -18,13 +18,13 @@ in the source distribution for its full text. #include "XUtils.h" -static void ListItem_delete(Object* cast) { +void ListItem_delete(Object* cast) { ListItem* this = (ListItem*)cast; free(this->value); free(this); } -static void ListItem_display(const Object* cast, RichString* out) { +void ListItem_display(const Object* cast, RichString* out) { const ListItem* const this = (const ListItem*)cast; assert (this != NULL); @@ -38,11 +38,15 @@ static void ListItem_display(const Object* cast, RichString* out) { RichString_appendWide(out, CRT_colors[DEFAULT_COLOR], this->value); } -ListItem* ListItem_new(const char* value, int key) { - ListItem* this = AllocThis(ListItem); +void ListItem_init(ListItem* this, const char* value, int key) { this->value = xStrdup(value); this->key = key; this->moving = false; +} + +ListItem* ListItem_new(const char* value, int key) { + ListItem* this = AllocThis(ListItem); + ListItem_init(this, value, key); return this; } @@ -55,7 +59,7 @@ void ListItem_append(ListItem* this, const char* text) { this->value[newLen] = '\0'; } -static int ListItem_compare(const void* cast1, const void* cast2) { +int ListItem_compare(const void* cast1, const void* cast2) { const ListItem* obj1 = (const ListItem*) cast1; const ListItem* obj2 = (const ListItem*) cast2; return strcmp(obj1->value, obj2->value); diff --git a/ListItem.h b/ListItem.h index 2c7ae4e..b2c3e06 100644 --- a/ListItem.h +++ b/ListItem.h @@ -21,10 +21,18 @@ typedef struct ListItem_ { extern const ObjectClass ListItem_class; +void ListItem_delete(Object* cast); + +void ListItem_display(const Object* cast, RichString* out); + +void ListItem_init(ListItem* this, const char* value, int key); + ListItem* ListItem_new(const char* value, int key); void ListItem_append(ListItem* this, const char* text); +int ListItem_compare(const void* cast1, const void* cast2); + static inline const char* ListItem_getRef(const ListItem* this) { return this->value; } diff --git a/MainPanel.c b/MainPanel.c index 07dc631..44915df 100644 --- a/MainPanel.c +++ b/MainPanel.c @@ -24,9 +24,10 @@ in the source distribution for its full text. static const char* const MainFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL}; static const char* const MainFunctions_ro[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", " ", " ", " ", "Quit ", NULL}; -void MainPanel_updateTreeFunctions(MainPanel* this, bool mode) { +void MainPanel_updateLabels(MainPanel* this, bool list, bool filter) { FunctionBar* bar = MainPanel_getFunctionBar(this); - FunctionBar_setLabel(bar, KEY_F(5), mode ? "List " : "Tree "); + FunctionBar_setLabel(bar, KEY_F(5), list ? "List " : "Tree "); + FunctionBar_setLabel(bar, KEY_F(4), filter ? "FILTER" : "Filter"); } static void MainPanel_pidSearch(MainPanel* this, int ch) { @@ -71,23 +72,29 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { if (needReset) this->state->hideProcessSelection = false; + Settings* settings = this->state->settings; + ScreenSettings* ss = settings->ss; + if (EVENT_IS_HEADER_CLICK(ch)) { int x = EVENT_HEADER_CLICK_GET_X(ch); const ProcessList* pl = this->state->pl; - Settings* settings = this->state->settings; int hx = super->scrollH + x + 1; ProcessField field = ProcessList_keyAt(pl, hx); - if (settings->treeView && settings->treeViewAlwaysByPID) { - settings->treeView = false; - settings->direction = 1; + if (ss->treeView && ss->treeViewAlwaysByPID) { + ss->treeView = false; + ss->direction = 1; reaction |= Action_setSortKey(settings, field); - } else if (field == Settings_getActiveSortKey(settings)) { - Settings_invertSortOrder(settings); + } else if (field == ScreenSettings_getActiveSortKey(ss)) { + ScreenSettings_invertSortOrder(ss); } else { reaction |= Action_setSortKey(settings, field); } reaction |= HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_SAVE_SETTINGS; result = HANDLED; + } else if (EVENT_IS_SCREEN_TAB_CLICK(ch)) { + int x = EVENT_SCREEN_TAB_GET_X(ch); + reaction |= Action_setScreenTab(settings, x); + result = HANDLED; } else if (ch != ERR && this->inc->active) { bool filterChanged = IncSet_handleKey(this->inc, ch, super, MainPanel_getValue, NULL); if (filterChanged) { @@ -116,7 +123,7 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { } if (reaction & HTOP_REDRAW_BAR) { - MainPanel_updateTreeFunctions(this, this->state->settings->treeView); + MainPanel_updateLabels(this, settings->ss->treeView, this->state->pl->incFilter); } if (reaction & HTOP_RESIZE) { result |= RESIZE; @@ -182,7 +189,7 @@ static void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) { if (hideFunctionBar && !this->inc->active) return; - IncSet_drawBar(this->inc); + IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]); if (this->state->pauseProcessUpdate) { FunctionBar_append("PAUSED", CRT_colors[PAUSED]); } diff --git a/MainPanel.h b/MainPanel.h index 04f4c0a..bd22acd 100644 --- a/MainPanel.h +++ b/MainPanel.h @@ -32,7 +32,8 @@ typedef bool(*MainPanel_ForeachProcessFn)(Process*, Arg); #define MainPanel_getFunctionBar(this_) (((Panel*)(this_))->defaultBar) -void MainPanel_updateTreeFunctions(MainPanel* this, bool mode); +// update the Label Keys in the MainPanel bar, list: list / tree mode, filter: filter (inc) active / inactive +void MainPanel_updateLabels(MainPanel* this, bool list, bool filter); int MainPanel_selectedPid(MainPanel* this); diff --git a/Makefile.am b/Makefile.am index 2a9cc29..8af1864 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,6 +74,7 @@ myhtopsources = \ ProcessLocksScreen.c \ RichString.c \ ScreenManager.c \ + ScreensPanel.c \ Settings.c \ SignalsPanel.c \ SwapMeter.c \ @@ -135,6 +136,7 @@ myhtopheaders = \ ProvideCurses.h \ RichString.h \ ScreenManager.h \ + ScreensPanel.h \ Settings.h \ SignalsPanel.h \ SwapMeter.h \ diff --git a/Meter.c b/Meter.c index 8df8517..164c4d3 100644 --- a/Meter.c +++ b/Meter.c @@ -155,7 +155,7 @@ ListItem* Meter_toListItem(const Meter* this, bool moving) { static void TextMeterMode_draw(Meter* this, int x, int y, int w) { const char* caption = Meter_getCaption(this); attrset(CRT_colors[METER_TEXT]); - mvaddnstr(y, x, caption, w - 1); + mvaddnstr(y, x, caption, w); attrset(CRT_colors[RESET_COLOR]); int captionLen = strlen(caption); @@ -166,7 +166,7 @@ static void TextMeterMode_draw(Meter* this, int x, int y, int w) { RichString_begin(out); Meter_displayBuffer(this, &out); - RichString_printoffnVal(out, y, x, 0, w - 1); + RichString_printoffnVal(out, y, x, 0, w); RichString_delete(&out); } @@ -176,7 +176,6 @@ static const char BarMeterMode_characters[] = "|#*@$%&."; static void BarMeterMode_draw(Meter* this, int x, int y, int w) { const char* caption = Meter_getCaption(this); - w -= 2; attrset(CRT_colors[METER_TEXT]); int captionLen = 3; mvaddnstr(y, x, caption, captionLen); @@ -184,10 +183,11 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) { w -= captionLen; attrset(CRT_colors[BAR_BORDER]); mvaddch(y, x, '['); + w--; mvaddch(y, x + MAXIMUM(w, 0), ']'); + w--; attrset(CRT_colors[RESET_COLOR]); - w--; x++; if (w < 1) @@ -329,7 +329,7 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) { data->values[nValues - 1] = value; } - int i = nValues - (w * 2) + 2, k = 0; + int i = nValues - (w * 2), k = 0; if (i < 0) { k = -i / 2; i = 0; diff --git a/Meter.h b/Meter.h index be8207c..bd7604a 100644 --- a/Meter.h +++ b/Meter.h @@ -134,6 +134,13 @@ typedef enum { LAST_METERMODE } MeterModeId; +typedef enum { + RATESTATUS_DATA, + RATESTATUS_INIT, + RATESTATUS_NODATA, + RATESTATUS_STALE +} MeterRateStatus; + extern const MeterClass Meter_class; Meter* Meter_new(const ProcessList* pl, unsigned int param, const MeterClass* type); diff --git a/NetworkIOMeter.c b/NetworkIOMeter.c index 6c429c3..dd91b75 100644 --- a/NetworkIOMeter.c +++ b/NetworkIOMeter.c @@ -5,6 +5,7 @@ #include "CRT.h" #include "Macros.h" +#include "Meter.h" #include "Object.h" #include "Platform.h" #include "Process.h" @@ -18,8 +19,7 @@ static const int NetworkIOMeter_attributes[] = { METER_VALUE_IOWRITE, }; -static bool hasData = false; - +static MeterRateStatus status = RATESTATUS_INIT; static uint32_t cached_rxb_diff; static uint32_t cached_rxp_diff; static uint32_t cached_txb_diff; @@ -31,7 +31,7 @@ static void NetworkIOMeter_updateValues(Meter* this) { uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update; - /* update only every 500ms */ + /* update only every 500ms to have a sane span for rate calculation */ if (passedTimeInMs > 500) { static uint64_t cached_rxb_total; static uint64_t cached_rxp_total; @@ -39,11 +39,20 @@ static void NetworkIOMeter_updateValues(Meter* this) { static uint64_t cached_txp_total; uint64_t diff; + NetworkIOData data; + if (!Platform_getNetworkIO(&data)) { + status = RATESTATUS_NODATA; + } else if (cached_last_update == 0) { + status = RATESTATUS_INIT; + } else if (passedTimeInMs > 30000) { + status = RATESTATUS_STALE; + } else { + status = RATESTATUS_DATA; + } + cached_last_update = pl->realtimeMs; - NetworkIOData data; - hasData = Platform_getNetworkIO(&data); - if (!hasData) { + if (status == RATESTATUS_NODATA) { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); return; } @@ -85,10 +94,13 @@ static void NetworkIOMeter_updateValues(Meter* this) { cached_txp_total = data.packetsTransmitted; } - if (passedTimeInMs > 30000) { - // Triggers for the first initialization and - // when there was a long time we did not collect updates - hasData = false; + if (status == RATESTATUS_INIT) { + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init"); + return; + } + if (status == RATESTATUS_STALE) { + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale"); + return; } this->values[0] = cached_rxb_diff; @@ -104,9 +116,18 @@ static void NetworkIOMeter_updateValues(Meter* this) { } static void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { - if (!hasData) { + switch (status) { + case RATESTATUS_NODATA: RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); return; + case RATESTATUS_INIT: + RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing..."); + return; + case RATESTATUS_STALE: + RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data"); + return; + case RATESTATUS_DATA: + break; } char buffer[64]; diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index 34367eb..2d19169 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -9,6 +9,7 @@ in the source distribution for its full text. #include "OpenFilesScreen.h" +#include #include #include #include @@ -197,10 +198,11 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { fclose(fd); int wstatus; - if (waitpid(child, &wstatus, 0) == -1) { - pdata->error = 1; - return pdata; - } + while (waitpid(child, &wstatus, 0) == -1) + if (errno != EINTR) { + pdata->error = 1; + return pdata; + } if (!WIFEXITED(wstatus)) { pdata->error = 1; diff --git a/Panel.c b/Panel.c index a5773d5..4ea03f6 100644 --- a/Panel.c +++ b/Panel.c @@ -49,6 +49,8 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type this->y = y; this->w = w; this->h = h; + this->cursorX = 0; + this->cursorY = 0; this->eventHandlerState = NULL; this->items = Vector_new(type, owner, DEFAULT_SIZE); this->scrollV = 0; @@ -57,6 +59,7 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type this->oldSelected = 0; this->selectedLen = 0; this->needsRedraw = true; + this->cursorOn = false; this->wasFocus = false; RichString_beginAllocated(this->header); this->defaultBar = fuBar; @@ -72,6 +75,11 @@ void Panel_done(Panel* this) { RichString_delete(&this->header); } +void Panel_setCursorToSelection(Panel* this) { + this->cursorY = this->y + this->selected - this->scrollV + 1; + this->cursorX = this->x + this->selectedLen - this->scrollH; +} + void Panel_setSelectionColor(Panel* this, ColorElements colorId) { this->selectionColorId = colorId; } @@ -330,7 +338,6 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect this->oldSelected = this->selected; this->wasFocus = focus; this->needsRedraw = false; - move(0, 0); } static int Panel_headerHeight(const Panel* this) { @@ -485,3 +492,16 @@ HandlerResult Panel_selectByTyping(Panel* this, int ch) { return IGNORED; } + +int Panel_getCh(Panel* this) { + if (this->cursorOn) { + move(this->cursorY, this->cursorX); + curs_set(1); + } else { + curs_set(0); + } +#ifdef HAVE_SET_ESCDELAY + set_escdelay(25); +#endif + return getch(); +} diff --git a/Panel.h b/Panel.h index 9bb4c77..33d532e 100644 --- a/Panel.h +++ b/Panel.h @@ -39,6 +39,10 @@ typedef enum HandlerResult_ { #define EVENT_IS_HEADER_CLICK(ev_) ((ev_) >= -10000 && (ev_) <= -9000) #define EVENT_HEADER_CLICK_GET_X(ev_) ((ev_) + 10000) +#define EVENT_SCREEN_TAB_CLICK(x_) (-20000 + (x_)) +#define EVENT_IS_SCREEN_TAB_CLICK(ev_) ((ev_) >= -20000 && (ev_) < -10000) +#define EVENT_SCREEN_TAB_GET_X(ev_) ((ev_) + 20000) + typedef HandlerResult (*Panel_EventHandler)(Panel*, int); typedef void (*Panel_DrawFunctionBar)(Panel*, bool); typedef void (*Panel_PrintHeader)(Panel*); @@ -61,6 +65,7 @@ typedef struct PanelClass_ { struct Panel_ { Object super; int x, y, w, h; + int cursorX, cursorY; Vector* items; int selected; int oldSelected; @@ -69,6 +74,7 @@ struct Panel_ { int scrollV; int scrollH; bool needsRedraw; + bool cursorOn; bool wasFocus; FunctionBar* currentBar; FunctionBar* defaultBar; @@ -90,6 +96,8 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type void Panel_done(Panel* this); +void Panel_setCursorToSelection(Panel* this); + void Panel_setSelectionColor(Panel* this, ColorElements colorId); void Panel_setHeader(Panel* this, const char* header); @@ -130,4 +138,6 @@ bool Panel_onKey(Panel* this, int key); HandlerResult Panel_selectByTyping(Panel* this, int ch); +int Panel_getCh(Panel* this); + #endif diff --git a/Process.c b/Process.c index 6be36b7..6c7b326 100644 --- a/Process.c +++ b/Process.c @@ -54,7 +54,7 @@ void Process_setupColumnWidths() { return; } - Process_pidDigits = ceil(log10(maxPid)); + Process_pidDigits = (int)log10(maxPid) + 1; assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS); } @@ -64,7 +64,7 @@ void Process_setUidColumnWidth(uid_t maxUid) { return; } - Process_uidDigits = ceil(log10(maxUid)); + Process_uidDigits = (int)log10(maxUid) + 1; assert(Process_uidDigits <= PROCESS_MAX_UID_DIGITS); } @@ -211,7 +211,12 @@ void Process_printTime(RichString* str, unsigned long long totalHundredths, bool int years = days / 365; int daysLeft = days - 365 * years; - if (daysLeft >= 100) { + if (years >= 10000000) { + RichString_appendnAscii(str, yearColor, "eternity ", 9); + } else if (years >= 1000) { + len = xSnprintf(buffer, sizeof(buffer), "%7dy ", years); + RichString_appendnAscii(str, yearColor, buffer, len); + } else if (daysLeft >= 100) { len = xSnprintf(buffer, sizeof(buffer), "%3dy", years); RichString_appendnAscii(str, yearColor, buffer, len); len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft); @@ -397,7 +402,7 @@ static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr * 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 * Process_writeCommand() for coloring. The merged Command string is also - * returned by Process_getCommandStr() for searching, sorting and filtering. + * returned by Process_getCommand() for searching, sorting and filtering. */ void Process_makeCommandStr(Process* this) { ProcessMergedCommand* mc = &this->mergedCommand; @@ -417,7 +422,7 @@ void Process_makeCommandStr(Process* this) { return; if (this->state == ZOMBIE && !this->mergedCommand.str) return; - if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames)) + 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. @@ -516,11 +521,14 @@ void Process_makeCommandStr(Process* this) { assert(cmdlineBasenameStart <= (int)strlen(cmdline)); if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */ - if (showMergedCommand && (!Process_isUserlandThread(this) || showThreadNames) && !procExe && procComm && strlen(procComm)) { /* Prefix column with comm */ + if ((showMergedCommand || (Process_isUserlandThread(this) && showThreadNames)) && procComm && strlen(procComm)) { /* set column to or prefix it with comm */ if (strncmp(cmdline + cmdlineBasenameStart, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) { WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM); str = stpcpy(str, procComm); + if(!showMergedCommand) + return; + WRITE_SEPARATOR; } } @@ -729,23 +737,22 @@ void Process_printLeftAlignedField(RichString* str, int attr, const char* conten RichString_appendChr(str, attr, ' ', width + 1 - columns); } -void Process_printPercentage(float val, char* buffer, int n, int* attr) { +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, "%4.1f ", val); - } else if (val < 999) { - *attr = CRT_colors[PROCESS_MEGABYTES]; - xSnprintf(buffer, n, "%3d. ", (int)val); + xSnprintf(buffer, n, "%*.1f ", width, val); } else { *attr = CRT_colors[PROCESS_MEGABYTES]; - xSnprintf(buffer, n, "%4d ", (int)val); + if (val < 100.0F) + val = 100.0F; // Don't round down and display "val" as "99". + xSnprintf(buffer, n, "%*.0f ", width, val); } } else { *attr = CRT_colors[PROCESS_SHADOW]; - xSnprintf(buffer, n, " N/A "); + xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); } } @@ -784,7 +791,8 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field attr = CRT_colors[PROCESS_THREAD]; baseattr = CRT_colors[PROCESS_THREAD_BASENAME]; } - if (!this->settings->treeView || this->indent == 0) { + const ScreenSettings* ss = this->settings->ss; + if (!ss->treeView || this->indent == 0) { Process_writeCommand(this, attr, baseattr, str); return; } @@ -868,7 +876,15 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field Process_printLeftAlignedField(str, attr, cwd, 25); return; } - case ELAPSED: Process_printTime(str, /* convert to hundreds of a second */ this->processList->realtimeMs / 10 - 100 * this->starttime_ctime, coloring); return; + case ELAPSED: { + const uint64_t rt = this->processList->realtimeMs; + const uint64_t st = this->starttime_ctime * 1000; + const uint64_t dt = + rt < st ? 0 : + rt - st; + Process_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring); + return; + } case MAJFLT: Process_printCount(str, this->majflt, coloring); return; case MINFLT: Process_printCount(str, this->minflt, coloring); return; case M_RESIDENT: Process_printKBytes(str, this->m_resident, coloring); return; @@ -885,13 +901,13 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field xSnprintf(buffer, n, "%4ld ", this->nlwp); break; - case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, &attr); break; + case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break; case PERCENT_NORM_CPU: { float cpuPercentage = this->percent_cpu / this->processList->activeCPUs; - Process_printPercentage(cpuPercentage, buffer, n, &attr); + Process_printPercentage(cpuPercentage, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break; } - case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, &attr); break; + case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, 4, &attr); break; case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break; case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pid); break; case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->ppid); break; @@ -916,13 +932,13 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field case BLOCKED: case DEFUNCT: case STOPPED: + case UNINTERRUPTIBLE_WAIT: case ZOMBIE: attr = CRT_colors[PROCESS_D_STATE]; break; case QUEUED: case WAITING: - case UNINTERRUPTIBLE_WAIT: case IDLE: case SLEEPING: attr = CRT_colors[PROCESS_SHADOW]; @@ -974,7 +990,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field void Process_display(const Object* cast, RichString* out) { const Process* this = (const Process*) cast; - const ProcessField* fields = this->settings->fields; + const ProcessField* fields = this->settings->ss->fields; for (int i = 0; fields[i]; i++) As_Process(this)->writeField(this, out, fields[i]); @@ -1010,7 +1026,7 @@ void Process_done(Process* this) { /* 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 Process_writeField(COMM) and Process_writeCommand */ -const char* Process_getCommandStr(const Process* this) { +const char* Process_getCommand(const Process* this) { if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !this->mergedCommand.str) { return this->cmdline; } @@ -1026,7 +1042,6 @@ const ProcessClass Process_class = { .compare = Process_compare }, .writeField = Process_writeField, - .getCommandStr = Process_getCommandStr, }; void Process_init(Process* this, const Settings* settings) { @@ -1092,8 +1107,9 @@ int Process_compare(const void* v1, const void* v2) { const Process* p2 = (const Process*)v2; const Settings* settings = p1->settings; + const ScreenSettings* ss = settings->ss; - ProcessField key = Settings_getActiveSortKey(settings); + ProcessField key = ScreenSettings_getActiveSortKey(ss); int result = Process_compareByKey(p1, p2, key); @@ -1101,7 +1117,7 @@ int Process_compare(const void* v1, const void* v2) { if (!result) return SPACESHIP_NUMBER(p1->pid, p2->pid); - return (Settings_getActiveDirection(settings) == 1) ? result : -result; + return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result; } int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) { @@ -1173,6 +1189,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField case USER: return SPACESHIP_NULLSTR(p1->user, p2->user); default: + CRT_debug("Process_compareByKey_Base() called with key %d", key); assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */ return SPACESHIP_NUMBER(p1->pid, p2->pid); } @@ -1248,3 +1265,36 @@ void Process_updateExe(Process* this, const char* exe) { } this->mergedCommand.exeChanged = true; } + +uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 }; + +void Process_resetFieldWidths() { + for (size_t i = 0; i < LAST_PROCESSFIELD; i++) { + if (!Process_fields[i].autoWidth) + continue; + + size_t len = strlen(Process_fields[i].title); + assert(len <= UINT8_MAX); + Process_fieldWidths[i] = (uint8_t)len; + } +} + +void Process_updateFieldWidth(ProcessField key, size_t width) { + if (width > UINT8_MAX) + Process_fieldWidths[key] = UINT8_MAX; + else if (width > Process_fieldWidths[key]) + Process_fieldWidths[key] = (uint8_t)width; +} + +void Process_updateCPUFieldWidths(float percentage) { + if (percentage < 99.9) { + Process_updateFieldWidth(PERCENT_CPU, 4); + Process_updateFieldWidth(PERCENT_NORM_CPU, 4); + return; + } + + uint8_t width = ceil(log10(percentage + .2)); + + Process_updateFieldWidth(PERCENT_CPU, width); + Process_updateFieldWidth(PERCENT_NORM_CPU, width); +} diff --git a/Process.h b/Process.h index 26f6434..e1408c2 100644 --- a/Process.h +++ b/Process.h @@ -250,10 +250,10 @@ typedef struct Process_ { * Internal state for tree-mode. */ int indent; - unsigned int tree_left; - unsigned int tree_right; unsigned int tree_depth; - unsigned int tree_index; + + /* Has no known parent process */ + bool isRoot; /* * Internal state for merged Command display @@ -279,6 +279,9 @@ typedef struct ProcessFieldData_ { /* Whether the column should be sorted in descending order by default */ bool defaultSortDesc; + + /* Whether the column width is dynamically adjusted (the minimum width is determined by the title length) */ + bool autoWidth; } ProcessFieldData; // Implemented in platform-specific code: @@ -286,6 +289,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field int Process_compare(const void* v1, const void* v2); void Process_delete(Object* cast); extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; +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 @@ -296,18 +300,15 @@ extern int Process_uidDigits; typedef Process* (*Process_New)(const struct Settings_*); typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField); typedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField); -typedef const char* (*Process_GetCommandStr)(const Process*); typedef struct ProcessClass_ { const ObjectClass super; const Process_WriteField writeField; const Process_CompareByKey compareByKey; - const Process_GetCommandStr getCommandStr; } ProcessClass; #define As_Process(this_) ((const ProcessClass*)((this_)->super.klass)) -#define Process_getCommand(this_) (As_Process(this_)->getCommandStr ? As_Process(this_)->getCommandStr((const Process*)(this_)) : Process_getCommandStr((const Process*)(this_))) #define Process_compareByKey(p1_, p2_, key_) (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_)) static inline pid_t Process_getParentPid(const Process* this) { @@ -371,7 +372,7 @@ void Process_fillStarttimeBuffer(Process* this); void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width); -void Process_printPercentage(float val, char* buffer, int n, int* attr); +void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr); void Process_display(const Object* cast, RichString* out); @@ -397,17 +398,20 @@ int Process_pidCompare(const void* v1, const void* v2); int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key); -// Avoid direct calls, use Process_getCommand instead -const char* Process_getCommandStr(const Process* this); +const char* Process_getCommand(const Process* this); void Process_updateComm(Process* this, const char* comm); void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd); void Process_updateExe(Process* this, const char* exe); /* This function constructs the string that is displayed by - * Process_writeCommand and also returned by Process_getCommandStr */ + * Process_writeCommand and also returned by Process_getCommand */ void Process_makeCommandStr(Process* this); void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str); +void Process_resetFieldWidths(void); +void Process_updateFieldWidth(ProcessField key, size_t width); +void Process_updateCPUFieldWidths(float percentage); + #endif diff --git a/ProcessList.c b/ProcessList.c index c4c759d..2e5ad7c 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -22,11 +22,10 @@ in the source distribution for its full text. ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { this->processes = Vector_new(klass, true, DEFAULT_SIZE); - this->processes2 = Vector_new(klass, true, DEFAULT_SIZE); // tree-view auxiliary buffer + this->displayList = Vector_new(klass, false, DEFAULT_SIZE); this->processTable = Hashtable_new(200, false); - this->displayTreeSet = Hashtable_new(200, false); - this->draftingTreeSet = Hashtable_new(200, false); + this->needsSort = true; this->usersTable = usersTable; this->pidMatchList = pidMatchList; @@ -73,11 +72,9 @@ void ProcessList_done(ProcessList* this) { } #endif - Hashtable_delete(this->draftingTreeSet); - Hashtable_delete(this->displayTreeSet); Hashtable_delete(this->processTable); - Vector_delete(this->processes2); + Vector_delete(this->displayList); Vector_delete(this->processes); } @@ -117,6 +114,12 @@ static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessFiel 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); + return titleBuffer; + } + return title; } @@ -124,13 +127,14 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header) { RichString_rewind(header, RichString_size(header)); const Settings* settings = this->settings; - const ProcessField* fields = settings->fields; + const ScreenSettings* ss = settings->ss; + const ProcessField* fields = ss->fields; - ProcessField key = Settings_getActiveSortKey(settings); + ProcessField key = ScreenSettings_getActiveSortKey(ss); for (int i = 0; fields[i]; i++) { int color; - if (settings->treeView && settings->treeViewAlwaysByPID) { + if (ss->treeView && ss->treeViewAlwaysByPID) { color = CRT_colors[PANEL_HEADER_FOCUS]; } else if (key == fields[i]) { color = CRT_colors[PANEL_SELECTION_FOCUS]; @@ -140,10 +144,11 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header) { RichString_appendWide(header, color, alignedProcessFieldTitle(this, fields[i])); if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') { + bool ascending = ScreenSettings_getActiveDirection(ss) == 1; RichString_rewind(header, 1); // rewind to override space RichString_appendnWide(header, CRT_colors[PANEL_SELECTION_FOCUS], - CRT_treeStr[Settings_getActiveDirection(this->settings) == 1 ? TREE_STR_ASC : TREE_STR_DESC], + CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC], 1); } if (COMM == fields[i] && settings->showMergedCommand) { @@ -192,313 +197,145 @@ void ProcessList_remove(ProcessList* this, const Process* p) { assert(Hashtable_count(this->processTable) == Vector_count(this->processes)); } -// ProcessList_updateTreeSetLayer sorts this->displayTreeSet, -// relying only on itself. -// -// Algorithm -// -// The algorithm is based on `depth-first search`, -// even though `breadth-first search` approach may be more efficient on first glance, -// after comparison it may be not, as it's not safe to go deeper without first updating the tree structure. -// If it would be safe that approach would likely bring an advantage in performance. -// -// Each call of the function looks for a 'layer'. A 'layer' is a list of processes with the same depth. -// First it sorts a list. Then it runs the function recursively for each element of the sorted list. -// After that it updates the settings of processes. -// -// It relies on `leftBound` and `rightBound` as an optimization to cut the list size at the time it builds a 'layer'. -// -// It uses a temporary Hashtable `draftingTreeSet` because it's not safe to traverse a tree -// and at the same time make changes in it. -// -static void ProcessList_updateTreeSetLayer(ProcessList* this, unsigned int leftBound, unsigned int rightBound, unsigned int deep, unsigned int left, unsigned int right, unsigned int* index, unsigned int* treeIndex, int indent) { - - // It's guaranteed that layer_size is enough space - // but most likely it needs less. Specifically on first iteration. - int layerSize = (right - left) / 2; - - // check if we reach `children` of `leaves` - if (layerSize == 0) - return; - - Vector* layer = Vector_new(Vector_type(this->processes), false, layerSize); - - // Find all processes on the same layer (process with the same `deep` value - // and included in a range from `leftBound` to `rightBound`). - // - // This loop also keeps track of left_bound and right_bound of these processes - // in order not to lose this information once the list is sorted. - // - // The variables left_bound and right_bound are different from what the values lhs and rhs represent. - // While left_bound and right_bound define a range of processes to look at, the values given by lhs and rhs are indices into an array - // - // In the below example note how filtering a range of indices i is different from filtering for processes in the bounds left_bound < x < right_bound … - // - // The nested tree set is sorted by left value, which is guaranteed upon entry/exit of this function. - // - // i | l | r - // 1 | 1 | 9 - // 2 | 2 | 8 - // 3 | 4 | 5 - // 4 | 6 | 7 - for (unsigned int i = leftBound; i < rightBound; i++) { - Process* proc = (Process*)Hashtable_get(this->displayTreeSet, i); - assert(proc); - if (proc && proc->tree_depth == deep && proc->tree_left > left && proc->tree_right < right) { - if (Vector_size(layer) > 0) { - Process* previous_process = (Process*)Vector_get(layer, Vector_size(layer) - 1); - - // Make a 'right_bound' of previous_process in a layer the current process's index. - // - // Use 'tree_depth' as a temporal variable. - // It's safe to do as later 'tree_depth' will be renovated. - previous_process->tree_depth = proc->tree_index; - } - - Vector_add(layer, proc); - } - } - - // The loop above changes just up to process-1. - // So the last process of the layer isn't updated by the above code. - // - // Thus, if present, set the `rightBound` to the last process on the layer - if (Vector_size(layer) > 0) { - Process* previous_process = (Process*)Vector_get(layer, Vector_size(layer) - 1); - previous_process->tree_depth = rightBound; - } - - Vector_quickSort(layer); - - int size = Vector_size(layer); - for (int i = 0; i < size; i++) { - Process* proc = (Process*)Vector_get(layer, i); - - unsigned int idx = (*index)++; - int newLeft = (*treeIndex)++; - - int level = deep == 0 ? 0 : (int)deep - 1; - int currentIndent = indent == -1 ? 0 : indent | (1 << level); - int nextIndent = indent == -1 ? 0 : ((i < size - 1) ? currentIndent : indent); - - unsigned int newLeftBound = proc->tree_index; - unsigned int newRightBound = proc->tree_depth; - ProcessList_updateTreeSetLayer(this, newLeftBound, newRightBound, deep + 1, proc->tree_left, proc->tree_right, index, treeIndex, nextIndent); - - int newRight = (*treeIndex)++; - - proc->tree_left = newLeft; - proc->tree_right = newRight; - proc->tree_index = idx; - proc->tree_depth = deep; - - if (indent == -1) { - proc->indent = 0; - } else if (i == size - 1) { - proc->indent = -currentIndent; - } else { - proc->indent = currentIndent; - } - - Hashtable_put(this->draftingTreeSet, proc->tree_index, proc); - - // It's not strictly necessary to do this, but doing so anyways - // allows for checking the correctness of the inner workings. - Hashtable_remove(this->displayTreeSet, newLeftBound); - } - - Vector_delete(layer); -} - -static void ProcessList_updateTreeSet(ProcessList* this) { - unsigned int index = 0; - unsigned int tree_index = 1; - - const int vsize = Vector_size(this->processes); - - assert(Hashtable_count(this->draftingTreeSet) == 0); - assert((int)Hashtable_count(this->displayTreeSet) == vsize); - - ProcessList_updateTreeSetLayer(this, 0, vsize, 0, 0, vsize * 2 + 1, &index, &tree_index, -1); - - Hashtable* tmp = this->draftingTreeSet; - this->draftingTreeSet = this->displayTreeSet; - this->displayTreeSet = tmp; - - assert(Hashtable_count(this->draftingTreeSet) == 0); - assert((int)Hashtable_count(this->displayTreeSet) == vsize); -} - -static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, int direction, bool show, int* node_counter, int* node_index) { +static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, bool show) { // On OpenBSD the kernel thread 'swapper' has pid 0. // Do not treat it as root of any tree. if (pid == 0) return; - Vector* children = Vector_new(Class(Process), false, DEFAULT_SIZE); - - for (int i = Vector_size(this->processes) - 1; i >= 0; i--) { - Process* process = (Process*)Vector_get(this->processes, i); - if (process->show && Process_isChildOf(process, pid)) { - process = (Process*)Vector_take(this->processes, i); - Vector_add(children, process); + // The vector is sorted by parent PID, find the start of the range by bisection + int vsize = Vector_size(this->processes); + int l = 0; + int r = vsize; + while (l < r) { + int c = (l + r) / 2; + Process* process = (Process*)Vector_get(this->processes, c); + pid_t ppid = process->isRoot ? 0 : Process_getParentPid(process); + if (ppid < pid) { + l = c + 1; + } else { + r = c; } } + // Find the end to know the last line for indent handling purposes + int lastShown = r; + while (r < vsize) { + Process* process = (Process*)Vector_get(this->processes, r); + if (!Process_isChildOf(process, pid)) + break; + if (process->show) + lastShown = r; + r++; + } - int size = Vector_size(children); - for (int i = 0; i < size; i++) { - int index = (*node_index)++; - Process* process = (Process*)Vector_get(children, i); - - int lft = (*node_counter)++; + for (int i = l; i < r; i++) { + Process* process = (Process*)Vector_get(this->processes, i); if (!show) { process->show = false; } - int s = Vector_size(this->processes2); - if (direction == 1) { - Vector_add(this->processes2, process); - } else { - Vector_insert(this->processes2, 0, process); - } - - assert(Vector_size(this->processes2) == s + 1); (void)s; + Vector_add(this->displayList, process); int nextIndent = indent | (1 << level); - ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < size - 1) ? nextIndent : indent, direction, show ? process->showChildren : false, node_counter, node_index); - if (i == size - 1) { + ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < lastShown) ? nextIndent : indent, process->show && process->showChildren); + if (i == lastShown) { process->indent = -nextIndent; } else { process->indent = nextIndent; } - int rht = (*node_counter)++; - - process->tree_left = lft; - process->tree_right = rht; process->tree_depth = level + 1; - process->tree_index = index; - Hashtable_put(this->displayTreeSet, index, process); } - Vector_delete(children); } -static int ProcessList_treeProcessCompare(const void* v1, const void* v2) { +static int compareProcessByKnownParentThenNatural(const void* v1, const void* v2) { const Process* p1 = (const Process*)v1; const Process* p2 = (const Process*)v2; - return SPACESHIP_NUMBER(p1->tree_left, p2->tree_left); -} + int result = SPACESHIP_NUMBER( + p1->isRoot ? 0 : Process_getParentPid(p1), + p2->isRoot ? 0 : Process_getParentPid(p2) + ); -static int ProcessList_treeProcessCompareByPID(const void* v1, const void* v2) { - const Process* p1 = (const Process*)v1; - const Process* p2 = (const Process*)v2; + if (result != 0) + return result; - return SPACESHIP_NUMBER(p1->pid, p2->pid); + return Process_compare(v1, v2); } // Builds a sorted tree from scratch, without relying on previously gathered information static void ProcessList_buildTree(ProcessList* this) { - int node_counter = 1; - int node_index = 0; - int direction = Settings_getActiveDirection(this->settings); + Vector_prune(this->displayList); - // Sort by PID - Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompareByPID); + // Mark root processes int vsize = Vector_size(this->processes); + for (int i = 0; i < vsize; i++) { + Process* process = (Process*)Vector_get(this->processes, i); + pid_t ppid = Process_getParentPid(process); + process->isRoot = false; - // Find all processes whose parent is not visible - int size; - while ((size = Vector_size(this->processes))) { - int i; - for (i = 0; i < size; i++) { - Process* process = (Process*)Vector_get(this->processes, i); - - // Immediately consume processes hidden from view - if (!process->show) { - process = (Process*)Vector_take(this->processes, i); - process->indent = 0; - process->tree_depth = 0; - process->tree_left = node_counter++; - process->tree_index = node_index++; - Vector_add(this->processes2, process); - ProcessList_buildTreeBranch(this, process->pid, 0, 0, direction, false, &node_counter, &node_index); - process->tree_right = node_counter++; - Hashtable_put(this->displayTreeSet, process->tree_index, process); - break; - } - - pid_t ppid = Process_getParentPid(process); - - // Bisect the process vector to find parent - int l = 0; - int r = size; - - // If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0) - // on Mac OS X 10.11.6) cancel bisecting and regard this process as - // root. - if (process->pid == ppid) - r = 0; - - // On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2) - // use a ppid of 0. As that PID can't exist, we can skip searching for it. - if (!ppid) - r = 0; - - while (l < r) { - int c = (l + r) / 2; - pid_t pid = ((Process*)Vector_get(this->processes, c))->pid; - if (ppid == pid) { - break; - } else if (ppid < pid) { - r = c; - } else { - l = c + 1; - } - } + // If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0) + // on Mac OS X 10.11.6) regard this process as root. + if (process->pid == ppid) { + process->isRoot = true; + continue; + } - // If parent not found, then construct the tree with this node as root - if (l >= r) { - process = (Process*)Vector_take(this->processes, i); - process->indent = 0; - process->tree_depth = 0; - process->tree_left = node_counter++; - process->tree_index = node_index++; - Vector_add(this->processes2, process); - Hashtable_put(this->displayTreeSet, process->tree_index, process); - ProcessList_buildTreeBranch(this, process->pid, 0, 0, direction, process->showChildren, &node_counter, &node_index); - process->tree_right = node_counter++; - break; - } + // On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2) + // use a ppid of 0. As that PID can't exist, we can skip searching for it. + if (!ppid) { + process->isRoot = true; + continue; } - // There should be no loop in the process tree - assert(i < size); + // We don't know about its parent for whatever reason + if (ProcessList_findProcess(this, ppid) == NULL) + process->isRoot = true; + } + + // Sort by known parent PID (roots first), then PID + Vector_quickSortCustomCompare(this->processes, compareProcessByKnownParentThenNatural); + + // Find all processes whose parent is not visible + for (int i = 0; i < vsize; i++) { + Process* process = (Process*)Vector_get(this->processes, i); + + // If parent not found, then construct the tree with this node as root + if (process->isRoot) { + process = (Process*)Vector_get(this->processes, i); + process->indent = 0; + process->tree_depth = 0; + Vector_add(this->displayList, process); + ProcessList_buildTreeBranch(this, process->pid, 0, 0, process->showChildren); + continue; + } } - // Swap listings around - Vector* t = this->processes; - this->processes = this->processes2; - this->processes2 = t; + this->needsSort = false; // Check consistency of the built structures - assert(Vector_size(this->processes) == vsize); (void)vsize; - assert(Vector_size(this->processes2) == 0); + assert(Vector_size(this->displayList) == vsize); (void)vsize; } -void ProcessList_sort(ProcessList* this) { - if (this->settings->treeView) { - ProcessList_updateTreeSet(this); - Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompare); +void ProcessList_updateDisplayList(ProcessList* this) { + if (this->settings->ss->treeView) { + if (this->needsSort) + ProcessList_buildTree(this); } else { - Vector_insertionSort(this->processes); + if (this->needsSort) + Vector_insertionSort(this->processes); + Vector_prune(this->displayList); + int size = Vector_size(this->processes); + for (int i = 0; i < size; i++) + Vector_add(this->displayList, Vector_get(this->processes, i)); } + this->needsSort = false; } ProcessField ProcessList_keyAt(const ProcessList* this, int at) { int x = 0; - const ProcessField* fields = this->settings->fields; + const ProcessField* fields = this->settings->ss->fields; ProcessField field; for (int i = 0; (field = fields[i]); i++) { int len = strlen(alignedProcessFieldTitle(this, field)); @@ -529,6 +366,8 @@ void ProcessList_collapseAllBranches(ProcessList* this) { } void ProcessList_rebuildPanel(ProcessList* this) { + ProcessList_updateDisplayList(this); + const char* incFilter = this->incFilter; const int currPos = Panel_getSelectedIndex(this->panel); @@ -546,16 +385,16 @@ void ProcessList_rebuildPanel(ProcessList* this) { } } - const int processCount = Vector_size(this->processes); + const int processCount = Vector_size(this->displayList); int idx = 0; bool foundFollowed = false; for (int i = 0; i < processCount; i++) { - Process* p = (Process*) Vector_get(this->processes, i); + Process* p = (Process*) Vector_get(this->displayList, i); if ( (!p->show) || (this->userId != (uid_t) -1 && (p->st_uid != this->userId)) - || (incFilter && !(String_contains_i(Process_getCommand(p), incFilter))) + || (incFilter && !(String_contains_i(Process_getCommand(p), incFilter, true))) || (this->pidMatchList && !Hashtable_get(this->pidMatchList, p->tgid)) ) continue; @@ -620,6 +459,7 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { this->kernelThreads = 0; this->runningTasks = 0; + Process_resetFieldWidths(); // set scan timestamp static bool firstScanDone = false; @@ -660,14 +500,4 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { // Set UID column width based on max UID. Process_setUidColumnWidth(maxUid); - - if (this->settings->treeView) { - // Clear out the hashtable to avoid any left-over processes from previous build - // - // The sorting algorithm relies on the fact that - // len(this->displayTreeSet) == len(this->processes) - Hashtable_clear(this->displayTreeSet); - - ProcessList_buildTree(this); - } } diff --git a/ProcessList.h b/ProcessList.h index a12ffa1..c420038 100644 --- a/ProcessList.h +++ b/ProcessList.h @@ -43,13 +43,13 @@ typedef unsigned long long int memory_t; typedef struct ProcessList_ { const Settings* settings; - Vector* processes; - Vector* processes2; - Hashtable* processTable; + Vector* processes; /* all known processes; sort order can vary and differ from display order */ + Vector* displayList; /* process tree flattened in display order (borrowed); + updated in ProcessList_updateDisplayList when rebuilding panel */ + Hashtable* processTable; /* fast known process lookup by PID */ UsersTable* usersTable; - Hashtable* displayTreeSet; - Hashtable* draftingTreeSet; + bool needsSort; Hashtable* dynamicMeters; /* runtime-discovered meters */ Hashtable* dynamicColumns; /* runtime-discovered Columns */ @@ -108,7 +108,7 @@ void ProcessList_add(ProcessList* this, Process* p); void ProcessList_remove(ProcessList* this, const Process* p); -void ProcessList_sort(ProcessList* this); +void ProcessList_updateDisplayList(ProcessList* this); ProcessField ProcessList_keyAt(const ProcessList* this, int at); diff --git a/README b/README index e4831f8..55372b4 100644 --- a/README +++ b/README @@ -62,6 +62,16 @@ sudo apt install libncursesw5-dev autotools-dev autoconf build-essential sudo dnf install ncurses-devel automake autoconf gcc ~~~ +**Archlinux/Manjaro** +~~~ shell +sudo pacman -S ncurses automake autoconf gcc +~~~ + +**macOS** +~~~ shell +brew install ncurses automake autoconf gcc +~~~ + ### Compile from source: To compile from source, download from the Git repository (`git clone` or downloads from [GitHub releases](https://github.com/htop-dev/htop/releases/)), then run: ~~~ shell diff --git a/ScreenManager.c b/ScreenManager.c index 96e9c47..e4b04bd 100644 --- a/ScreenManager.c +++ b/ScreenManager.c @@ -16,6 +16,7 @@ in the source distribution for its full text. #include "CRT.h" #include "FunctionBar.h" +#include "Macros.h" #include "Object.h" #include "Platform.h" #include "ProcessList.h" @@ -49,27 +50,43 @@ inline int ScreenManager_size(const ScreenManager* this) { } void ScreenManager_add(ScreenManager* this, Panel* item, int size) { + ScreenManager_insert(this, item, size, Vector_size(this->panels)); +} + +void ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx) { int lastX = 0; - if (this->panelCount > 0) { - const Panel* last = (const Panel*) Vector_get(this->panels, this->panelCount - 1); + if (idx > 0) { + const Panel* last = (const Panel*) Vector_get(this->panels, idx - 1); lastX = last->x + last->w + 1; } int height = LINES - this->y1 - (this->header ? this->header->height : 0) + this->y2; - if (size > 0) { - Panel_resize(item, size, height); - } else { - Panel_resize(item, COLS - this->x1 + this->x2 - lastX, height); + if (size <= 0) { + size = COLS - this->x1 + this->x2 - lastX; } + Panel_resize(item, size, height); Panel_move(item, lastX, this->y1 + (this->header ? this->header->height : 0)); - Vector_add(this->panels, item); + if (idx < this->panelCount) { + for (int i = idx + 1; i <= this->panelCount; i++) { + Panel* p = (Panel*) Vector_get(this->panels, i); + Panel_move(p, p->x + size, p->y); + } + } + Vector_insert(this->panels, idx, item); item->needsRedraw = true; this->panelCount++; } Panel* ScreenManager_remove(ScreenManager* this, int idx) { assert(this->panelCount > idx); + int w = ((Panel*) Vector_get(this->panels, idx))->w; Panel* panel = (Panel*) Vector_remove(this->panels, idx); this->panelCount--; + if (idx < this->panelCount) { + for (int i = idx; i < this->panelCount; i++) { + Panel* p = (Panel*) Vector_get(this->panels, i); + Panel_move(p, p->x - w, p->y); + } + } return panel; } @@ -104,14 +121,14 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi if (*rescan) { *oldTime = newTime; int oldUidDigits = Process_uidDigits; + if (!this->state->pauseProcessUpdate && (*sortTimeout == 0 || this->settings->ss->treeView)) { + pl->needsSort = true; + *sortTimeout = 1; + } // scan processes first - some header values are calculated there ProcessList_scan(pl, this->state->pauseProcessUpdate); // always update header, especially to avoid gaps in graph meters Header_updateData(this->header); - if (!this->state->pauseProcessUpdate && (*sortTimeout == 0 || this->settings->treeView)) { - ProcessList_sort(pl); - *sortTimeout = 1; - } // force redraw if the number of UID digits was changed if (Process_uidDigits != oldUidDigits) { *force_redraw = true; @@ -125,7 +142,53 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi *rescan = false; } +static inline bool drawTab(int* y, int* x, int l, const char* name, bool cur) { + attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]); + mvaddch(*y, *x, '['); + (*x)++; + if (*x >= l) + return false; + int nameLen = strlen(name); + int n = MINIMUM(l - *x, nameLen); + attrset(CRT_colors[cur ? SCREENS_CUR_TEXT : SCREENS_OTH_TEXT]); + mvaddnstr(*y, *x, name, n); + *x += n; + if (*x >= l) + return false; + attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]); + mvaddch(*y, *x, ']'); + *x += 2; + if (*x >= l) + return false; + return true; +} + +static void ScreenManager_drawScreenTabs(ScreenManager* this) { + ScreenSettings** screens = this->settings->screens; + int cur = this->settings->ssIndex; + int l = COLS; + Panel* panel = (Panel*) Vector_get(this->panels, 0); + int y = panel->y - 1; + int x = 2; + + if (this->name) { + drawTab(&y, &x, l, this->name, true); + return; + } + + for (int s = 0; screens[s]; s++) { + bool ok = drawTab(&y, &x, l, screens[s]->name, s == cur); + if (!ok) { + break; + } + } + attrset(CRT_colors[RESET_COLOR]); +} + static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_redraw) { + if (this->settings->screenTabs) { + ScreenManager_drawScreenTabs(this); + } const int nPanels = this->panelCount; for (int i = 0; i < nPanels; i++) { Panel* panel = (Panel*) Vector_get(this->panels, i); @@ -138,7 +201,7 @@ static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_ } } -void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) { +void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name) { bool quit = false; int focus = 0; @@ -156,6 +219,8 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) { int sortTimeout = 0; int resetSortTimeout = 5; + this->name = name; + while (!quit) { if (this->header) { checkRecalculation(this, &oldTime, &sortTimeout, &redraw, &rescan, &timedOut, &force_redraw); @@ -167,10 +232,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) { } int prevCh = ch; -#ifdef HAVE_SET_ESCDELAY - set_escdelay(25); -#endif - ch = getch(); + ch = Panel_getCh(panelFocus); HandlerResult result = IGNORED; #ifdef HAVE_GETMOUSE @@ -189,6 +251,9 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) { if (mevent.y == panel->y) { ch = EVENT_HEADER_CLICK(mevent.x - panel->x); break; + } else if (this->settings->screenTabs && mevent.y == panel->y - 1) { + ch = EVENT_SCREEN_TAB_CLICK(mevent.x); + break; } else if (mevent.y > panel->y && mevent.y <= panel->y + panel->h) { ch = KEY_MOUSE; if (panel == panelFocus || this->allowFocusChange) { diff --git a/ScreenManager.h b/ScreenManager.h index 455be70..978b524 100644 --- a/ScreenManager.h +++ b/ScreenManager.h @@ -22,6 +22,7 @@ typedef struct ScreenManager_ { int x2; int y2; Vector* panels; + const char* name; int panelCount; Header* header; const Settings* settings; @@ -37,10 +38,12 @@ int ScreenManager_size(const ScreenManager* this); void ScreenManager_add(ScreenManager* this, Panel* item, int size); +void ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx); + Panel* ScreenManager_remove(ScreenManager* this, int idx); void ScreenManager_resize(ScreenManager* this); -void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey); +void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name); #endif diff --git a/ScreensPanel.c b/ScreensPanel.c new file mode 100644 index 0000000..1427fef --- /dev/null +++ b/ScreensPanel.c @@ -0,0 +1,327 @@ +/* +htop - ScreensPanel.c +(C) 2004-2011 Hisham H. Muhammad +(C) 2020-2022 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "ScreensPanel.h" + +#include +#include +#include + +#include "CRT.h" +#include "FunctionBar.h" +#include "Hashtable.h" +#include "ProvideCurses.h" +#include "Settings.h" +#include "XUtils.h" + + +static void ScreenListItem_delete(Object* cast) { + ScreenListItem* this = (ScreenListItem*)cast; + if (this->ss) { + ScreenSettings_delete(this->ss); + } + ListItem_delete(cast); +} + +ObjectClass ScreenListItem_class = { + .extends = Class(ListItem), + .display = ListItem_display, + .delete = ScreenListItem_delete, + .compare = ListItem_compare +}; + +ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) { + ScreenListItem* this = AllocThis(ScreenListItem); + ListItem_init((ListItem*)this, value, 0); + this->ss = ss; + return this; +} + +static const char* const ScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL}; + +static void ScreensPanel_delete(Object* object) { + Panel* super = (Panel*) object; + ScreensPanel* this = (ScreensPanel*) object; + + /* do not delete screen settings still in use */ + int n = Panel_size(super); + for (int i = 0; i < n; i++) { + ScreenListItem* item = (ScreenListItem*) Panel_get(super, i); + item->ss = NULL; + } + + Panel_done(super); + free(this); +} + +static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) { + ScreensPanel* const this = (ScreensPanel*) super; + + if (ch >= 32 && ch < 127 && ch != '=') { + if (this->cursor < SCREEN_NAME_LEN - 1) { + this->buffer[this->cursor] = (char)ch; + this->cursor++; + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); + } + } else { + switch(ch) { + case 127: + case KEY_BACKSPACE: + { + if (this->cursor > 0) { + this->cursor--; + this->buffer[this->cursor] = '\0'; + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); + } + break; + } + case '\n': + case '\r': + case KEY_ENTER: + { + ListItem* item = (ListItem*) Panel_getSelected(super); + if (!item) + break; + free(this->saved); + item->value = xStrdup(this->buffer); + this->renaming = false; + super->cursorOn = false; + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + ScreensPanel_update(super); + break; + } + case 27: // Esc + { + ListItem* item = (ListItem*) Panel_getSelected(super); + if (!item) + break; + item->value = this->saved; + this->renaming = false; + super->cursorOn = false; + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + break; + } + } + } + return HANDLED; +} + +static void startRenaming(Panel* super) { + ScreensPanel* const this = (ScreensPanel*) super; + + ListItem* item = (ListItem*) Panel_getSelected(super); + if (item == NULL) + return; + this->renaming = true; + super->cursorOn = true; + char* name = item->value; + this->saved = name; + strncpy(this->buffer, name, SCREEN_NAME_LEN); + this->buffer[SCREEN_NAME_LEN] = '\0'; + this->cursor = strlen(this->buffer); + item->value = this->buffer; + Panel_setSelectionColor(super, PANEL_EDIT); + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); +} + +static void rebuildSettingsArray(Panel* super) { + ScreensPanel* const this = (ScreensPanel*) super; + + int n = Panel_size(super); + free(this->settings->screens); + this->settings->screens = xMallocArray(n + 1, sizeof(ScreenSettings*)); + this->settings->screens[n] = NULL; + for (int i = 0; i < n; i++) { + ScreenListItem* item = (ScreenListItem*) Panel_get(super, i); + this->settings->screens[i] = item->ss; + } + this->settings->nScreens = n; +} + +static void addNewScreen(Panel* super) { + ScreensPanel* const this = (ScreensPanel*) super; + + const char* name = "New"; + ScreenSettings* ss = Settings_newScreen(this->settings, &(const ScreenDefaults){ .name = name, .columns = "PID Command", .sortKey = "PID" }); + ScreenListItem* item = ScreenListItem_new(name, ss); + int idx = Panel_getSelectedIndex(super); + Panel_insert(super, idx + 1, (Object*) item); + Panel_setSelected(super, idx + 1); +} + +static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) { + ScreensPanel* const this = (ScreensPanel*) super; + + int selected = Panel_getSelectedIndex(super); + ScreenListItem* oldFocus = (ScreenListItem*) Panel_getSelected(super); + bool shouldRebuildArray = false; + HandlerResult result = IGNORED; + switch(ch) { + case '\n': + case '\r': + case KEY_ENTER: + case KEY_MOUSE: + case KEY_RECLICK: + { + this->moving = !(this->moving); + Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS); + ListItem* item = (ListItem*) Panel_getSelected(super); + if (item) + item->moving = this->moving; + result = HANDLED; + break; + } + case EVENT_SET_SELECTED: + result = HANDLED; + break; + case KEY_NPAGE: + case KEY_PPAGE: + case KEY_HOME: + case KEY_END: { + Panel_onKey(super, ch); + break; + } + case KEY_F(2): + case KEY_CTRL('R'): + { + startRenaming(super); + result = HANDLED; + break; + } + case KEY_F(5): + case KEY_CTRL('N'): + { + addNewScreen(super); + startRenaming(super); + shouldRebuildArray = true; + result = HANDLED; + break; + } + case KEY_UP: + { + if (!this->moving) { + Panel_onKey(super, ch); + break; + } + /* else fallthrough */ + } /* FALLTHRU */ + case KEY_F(7): + case '[': + case '-': + { + Panel_moveSelectedUp(super); + shouldRebuildArray = true; + result = HANDLED; + break; + } + case KEY_DOWN: + { + if (!this->moving) { + Panel_onKey(super, ch); + break; + } + /* else fallthrough */ + } /* FALLTHRU */ + case KEY_F(8): + case ']': + case '+': + { + Panel_moveSelectedDown(super); + shouldRebuildArray = true; + result = HANDLED; + break; + } + case KEY_F(9): + //case KEY_DC: + { + if (Panel_size(super) > 1) { + Panel_remove(super, selected); + } + shouldRebuildArray = true; + result = HANDLED; + break; + } + default: + { + if (ch < 255 && isalpha(ch)) + result = Panel_selectByTyping(super, ch); + if (result == BREAK_LOOP) + result = IGNORED; + break; + } + } + ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super); + if (newFocus && oldFocus != newFocus) { + ColumnsPanel_fill(this->columns, newFocus->ss, this->settings->dynamicColumns); + result = HANDLED; + } + if (shouldRebuildArray) + rebuildSettingsArray(super); + if (result == HANDLED) + ScreensPanel_update(super); + return result; +} + +static HandlerResult ScreensPanel_eventHandler(Panel* super, int ch) { + ScreensPanel* const this = (ScreensPanel*) super; + + if (this->renaming) { + return ScreensPanel_eventHandlerRenaming(super, ch); + } else { + return ScreensPanel_eventHandlerNormal(super, ch); + } +} + +PanelClass ScreensPanel_class = { + .super = { + .extends = Class(Panel), + .delete = ScreensPanel_delete + }, + .eventHandler = ScreensPanel_eventHandler +}; + +ScreensPanel* ScreensPanel_new(Settings* settings) { + ScreensPanel* this = AllocThis(ScreensPanel); + Panel* super = (Panel*) this; + Hashtable* columns = settings->dynamicColumns; + FunctionBar* fuBar = FunctionBar_new(ScreensFunctions, NULL, NULL); + Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); + + this->settings = settings; + this->columns = ColumnsPanel_new(settings->screens[0], columns, &(settings->changed)); + this->moving = false; + this->renaming = false; + super->cursorOn = false; + this->cursor = 0; + Panel_setHeader(super, "Screens"); + + for (unsigned int i = 0; i < settings->nScreens; i++) { + ScreenSettings* ss = settings->screens[i]; + char* name = ss->name; + Panel_add(super, (Object*) ScreenListItem_new(name, ss)); + } + return this; +} + +void ScreensPanel_update(Panel* super) { + ScreensPanel* this = (ScreensPanel*) super; + int size = Panel_size(super); + this->settings->changed = true; + 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); + ScreenSettings* ss = item->ss; + free(ss->name); + this->settings->screens[i] = ss; + ss->name = xStrdup(((ListItem*) item)->value); + } + this->settings->screens[size] = NULL; +} diff --git a/ScreensPanel.h b/ScreensPanel.h new file mode 100644 index 0000000..1f82395 --- /dev/null +++ b/ScreensPanel.h @@ -0,0 +1,53 @@ +#ifndef HEADER_ScreensPanel +#define HEADER_ScreensPanel +/* +htop - ScreensPanel.h +(C) 2004-2011 Hisham H. Muhammad +(C) 2020-2022 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include + +#include "ColumnsPanel.h" +#include "ListItem.h" +#include "Object.h" +#include "Panel.h" +#include "ScreenManager.h" +#include "Settings.h" + +#ifndef SCREEN_NAME_LEN +#define SCREEN_NAME_LEN 20 +#endif + +typedef struct ScreensPanel_ { + Panel super; + + ScreenManager* scr; + Settings* settings; + ColumnsPanel* columns; + char buffer[SCREEN_NAME_LEN + 1]; + char* saved; + int cursor; + bool moving; + bool renaming; +} ScreensPanel; + +typedef struct ScreenListItem_ { + ListItem super; + ScreenSettings* ss; +} ScreenListItem; + + +extern ObjectClass ScreenListItem_class; + +ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss); + +extern PanelClass ScreensPanel_class; + +ScreensPanel* ScreensPanel_new(Settings* settings); + +void ScreensPanel_update(Panel* super); + +#endif diff --git a/Settings.c b/Settings.c index 1f13b5a..a630374 100644 --- a/Settings.c +++ b/Settings.c @@ -24,14 +24,61 @@ in the source distribution for its full text. #include "XUtils.h" +/* + +static char** readQuotedList(char* line) { + int n = 0; + char** list = xCalloc(sizeof(char*), 1); + int start = 0; + for (;;) { + while (line[start] && line[start] == ' ') { + start++; + } + if (line[start] != '"') { + break; + } + start++; + int close = start; + while (line[close] && line[close] != '"') { + close++; + } + int len = close - start; + char* item = xMalloc(len + 1); + strncpy(item, line + start, len); + item[len] = '\0'; + list[n] = item; + n++; + list = xRealloc(list, sizeof(char*) * (n + 1)); + start = close + 1; + } + list[n] = NULL; + return list; +} + +static void writeQuotedList(FILE* fd, char** list) { + const char* sep = ""; + for (int i = 0; list[i]; i++) { + fprintf(fd, "%s\"%s\"", sep, list[i]); + sep = " "; + } + fprintf(fd, "\n"); +} + +*/ + void Settings_delete(Settings* this) { free(this->filename); - free(this->fields); for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) { String_freeArray(this->hColumns[i].names); free(this->hColumns[i].modes); } free(this->hColumns); + if (this->screens) { + for (unsigned int i = 0; this->screens[i]; i++) { + ScreenSettings_delete(this->screens[i]); + } + free(this->screens); + } free(this); } @@ -64,14 +111,21 @@ static void Settings_readMeterModes(Settings* this, const char* line, unsigned i static bool Settings_validateMeters(Settings* this) { const size_t colCount = HeaderLayout_getColumns(this->hLayout); + bool anyMeter = false; + for (size_t column = 0; column < colCount; column++) { char** names = this->hColumns[column].names; const int* modes = this->hColumns[column].modes; const size_t len = this->hColumns[column].len; - if (!names || !modes || !len) + if (!len) + continue; + + if (!names || !modes) return false; + anyMeter |= !!len; + // Check for each mode there is an entry with a non-NULL name for (size_t meterIdx = 0; meterIdx < len; meterIdx++) if (!names[meterIdx]) @@ -81,7 +135,7 @@ static bool Settings_validateMeters(Settings* this) { return false; } - return true; + return anyMeter; } static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) { @@ -148,51 +202,121 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) this->hColumns[1].modes[r++] = TEXT_METERMODE; } -static void Settings_readFields(Settings* settings, const char* line) { +static const char* toFieldName(Hashtable* columns, int id) { + if (id < 0) + return NULL; + if (id >= LAST_PROCESSFIELD) { + const DynamicColumn* column = DynamicColumn_lookup(columns, id); + return column->name; + } + return Process_fields[id].name; +} + +static int toFieldIndex(Hashtable* columns, const char* str) { + if (isdigit(str[0])) { + // This "+1" is for compatibility with the older enum format. + int id = atoi(str) + 1; + if (toFieldName(columns, id)) { + return id; + } + } else { + // Dynamically-defined columns are always stored by-name. + char dynamic[32] = {0}; + if (sscanf(str, "Dynamic(%30s)", dynamic)) { + char* end; + if ((end = strrchr(dynamic, ')')) != NULL) { + bool success; + unsigned int key; + *end = '\0'; + success = DynamicColumn_search(columns, dynamic, &key) != NULL; + *end = ')'; + if (success) + return key; + } + } + // Fallback to iterative scan of table of fields by-name. + for (int p = 1; p < LAST_PROCESSFIELD; p++) { + const char* pName = toFieldName(columns, p); + if (pName && strcmp(pName, str) == 0) + return p; + } + } + return -1; +} + +static void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, const char* line) { char* trim = String_trim(line); char** ids = String_split(trim, ' ', NULL); free(trim); - settings->flags = 0; + /* reset default fields */ + memset(ss->fields, '\0', LAST_PROCESSFIELD * sizeof(ProcessField)); - unsigned int i, j; - for (j = 0, i = 0; ids[i]; i++) { + for (size_t j = 0, i = 0; ids[i]; i++) { if (j >= UINT_MAX / sizeof(ProcessField)) continue; if (j >= LAST_PROCESSFIELD) { - settings->fields = xRealloc(settings->fields, j * sizeof(ProcessField)); - memset(&settings->fields[j], 0, sizeof(ProcessField)); - } - - // Dynamically-defined columns are always stored by-name. - char dynamic[32] = {0}; - if (sscanf(ids[i], "Dynamic(%30s)", dynamic)) { - char* end; - if ((end = strrchr(dynamic, ')')) == NULL) - continue; - *end = '\0'; - unsigned int key; - if (!DynamicColumn_search(settings->dynamicColumns, dynamic, &key)) - continue; - settings->fields[j++] = key; - continue; - } - // This "+1" is for compatibility with the older enum format. - int id = atoi(ids[i]) + 1; - if (id > 0 && id < LAST_PROCESSFIELD && Process_fields[id].name) { - settings->flags |= Process_fields[id].flags; - settings->fields[j++] = id; + ss->fields = xRealloc(ss->fields, (j + 1) * sizeof(ProcessField)); + memset(&ss->fields[j], 0, sizeof(ProcessField)); } + int id = toFieldIndex(columns, ids[i]); + if (id >= 0) + ss->fields[j] = id; + if (id > 0 && id < LAST_PROCESSFIELD) + ss->flags |= Process_fields[id].flags; + j++; } - settings->fields[j] = NULL_PROCESSFIELD; String_freeArray(ids); } +ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults) { + int sortKey = defaults->sortKey ? toFieldIndex(this->dynamicColumns, defaults->sortKey) : PID; + int sortDesc = (sortKey >= 0 && sortKey < LAST_PROCESSFIELD) ? Process_fields[sortKey].defaultSortDesc : 1; + + ScreenSettings* ss = xMalloc(sizeof(ScreenSettings)); + *ss = (ScreenSettings) { + .name = xStrdup(defaults->name), + .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)), + .flags = 0, + .direction = sortDesc ? -1 : 1, + .treeDirection = 1, + .sortKey = sortKey, + .treeSortKey = PID, + .treeView = false, + .treeViewAlwaysByPID = false, + .allBranchesCollapsed = false, + }; + + ScreenSettings_readFields(ss, this->dynamicColumns, defaults->columns); + this->screens[this->nScreens] = ss; + this->nScreens++; + this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1)); + this->screens[this->nScreens] = NULL; + return ss; +} + +void ScreenSettings_delete(ScreenSettings* this) { + free(this->name); + free(this->fields); + free(this); +} + +static ScreenSettings* Settings_defaultScreens(Settings* this) { + if (this->nScreens) + return this->screens[0]; + for (unsigned int i = 0; i < Platform_numberOfDefaultScreens; i++) { + const ScreenDefaults* defaults = &Platform_defaultScreens[i]; + Settings_newScreen(this, defaults); + } + return this->screens[0]; +} + static bool Settings_read(Settings* this, const char* fileName, unsigned int initialCpuCount) { FILE* fd = fopen(fileName, "r"); if (!fd) return false; + ScreenSettings* screen = NULL; bool didReadMeters = false; bool didReadAny = false; for (;;) { @@ -219,24 +343,40 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini fclose(fd); return false; } - } else if (String_eq(option[0], "fields")) { - Settings_readFields(this, option[1]); - } else if (String_eq(option[0], "sort_key")) { + } else if (String_eq(option[0], "fields") && this->config_version <= 2) { + // old (no screen) naming also supported for backwards compatibility + screen = Settings_defaultScreens(this); + ScreenSettings_readFields(screen, this->dynamicColumns, option[1]); + } else if (String_eq(option[0], "sort_key") && this->config_version <= 2) { + // old (no screen) naming also supported for backwards compatibility // This "+1" is for compatibility with the older enum format. - this->sortKey = atoi(option[1]) + 1; - } else if (String_eq(option[0], "tree_sort_key")) { + screen = Settings_defaultScreens(this); + screen->sortKey = atoi(option[1]) + 1; + } else if (String_eq(option[0], "tree_sort_key") && this->config_version <= 2) { + // old (no screen) naming also supported for backwards compatibility // This "+1" is for compatibility with the older enum format. - this->treeSortKey = atoi(option[1]) + 1; - } else if (String_eq(option[0], "sort_direction")) { - this->direction = atoi(option[1]); - } else if (String_eq(option[0], "tree_sort_direction")) { - this->treeDirection = atoi(option[1]); - } else if (String_eq(option[0], "tree_view")) { - this->treeView = atoi(option[1]); - } else if (String_eq(option[0], "tree_view_always_by_pid")) { - this->treeViewAlwaysByPID = atoi(option[1]); - } else if (String_eq(option[0], "all_branches_collapsed")) { - this->allBranchesCollapsed = atoi(option[1]); + screen = Settings_defaultScreens(this); + screen->treeSortKey = atoi(option[1]) + 1; + } else if (String_eq(option[0], "sort_direction") && this->config_version <= 2) { + // old (no screen) naming also supported for backwards compatibility + screen = Settings_defaultScreens(this); + screen->direction = atoi(option[1]); + } else if (String_eq(option[0], "tree_sort_direction") && this->config_version <= 2) { + // old (no screen) naming also supported for backwards compatibility + screen = Settings_defaultScreens(this); + screen->treeDirection = atoi(option[1]); + } else if (String_eq(option[0], "tree_view") && this->config_version <= 2) { + // old (no screen) naming also supported for backwards compatibility + screen = Settings_defaultScreens(this); + screen->treeView = atoi(option[1]); + } else if (String_eq(option[0], "tree_view_always_by_pid") && this->config_version <= 2) { + // old (no screen) naming also supported for backwards compatibility + screen = Settings_defaultScreens(this); + screen->treeViewAlwaysByPID = atoi(option[1]); + } else if (String_eq(option[0], "all_branches_collapsed") && this->config_version <= 2) { + // old (no screen) naming also supported for backwards compatibility + screen = Settings_defaultScreens(this); + screen->allBranchesCollapsed = atoi(option[1]); } else if (String_eq(option[0], "hide_kernel_threads")) { this->hideKernelThreads = atoi(option[1]); } else if (String_eq(option[0], "hide_userland_threads")) { @@ -267,6 +407,8 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini this->showMergedCommand = atoi(option[1]); } else if (String_eq(option[0], "header_margin")) { this->headerMargin = atoi(option[1]); + } else if (String_eq(option[0], "screen_tabs")) { + this->screenTabs = atoi(option[1]); } else if (String_eq(option[0], "expand_system_time")) { // Compatibility option. this->detailedCPUTime = atoi(option[1]); @@ -332,23 +474,49 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini } else if (String_eq(option[0], "topology_affinity")) { this->topologyAffinity = !!atoi(option[1]); #endif + } else if (strncmp(option[0], "screen:", 7) == 0) { + screen = Settings_newScreen(this, &(const ScreenDefaults){ .name = option[0] + 7, .columns = option[1] }); + } else if (String_eq(option[0], ".sort_key")) { + if (screen) + screen->sortKey = toFieldIndex(this->dynamicColumns, option[1]); + } else if (String_eq(option[0], ".tree_sort_key")) { + if (screen) + screen->treeSortKey = toFieldIndex(this->dynamicColumns, option[1]); + } else if (String_eq(option[0], ".sort_direction")) { + if (screen) + screen->direction = atoi(option[1]); + } else if (String_eq(option[0], ".tree_sort_direction")) { + if (screen) + screen->treeDirection = atoi(option[1]); + } else if (String_eq(option[0], ".tree_view")) { + if (screen) + screen->treeView = atoi(option[1]); + } else if (String_eq(option[0], ".tree_view_always_by_pid")) { + if (screen) + screen->treeViewAlwaysByPID = atoi(option[1]); + } else if (String_eq(option[0], ".all_branches_collapsed")) { + if (screen) + screen->allBranchesCollapsed = atoi(option[1]); } String_freeArray(option); } fclose(fd); - if (!didReadMeters || !Settings_validateMeters(this)) { + if (!didReadMeters || !Settings_validateMeters(this)) Settings_defaultMeters(this, initialCpuCount); - } + if (!this->nScreens) + Settings_defaultScreens(this); return didReadAny; } -static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, const char* name, char separator) { - fprintf(fd, "%s=", name); +static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, bool byName, char separator) { const char* sep = ""; for (unsigned int i = 0; fields[i]; i++) { - if (fields[i] >= LAST_PROCESSFIELD) { - const DynamicColumn* column = DynamicColumn_lookup(columns, fields[i]); - fprintf(fd, "%sDynamic(%s)", sep, column->name); + if (fields[i] < LAST_PROCESSFIELD && byName) { + const char* pName = toFieldName(columns, fields[i]); + fprintf(fd, "%s%s", sep, pName); + } else if (fields[i] >= LAST_PROCESSFIELD && byName) { + const char* pName = toFieldName(columns, fields[i]); + fprintf(fd, " Dynamic(%s)", pName); } else { // This "-1" is for compatibility with the older enum format. fprintf(fd, "%s%d", sep, (int) fields[i] - 1); @@ -358,15 +526,19 @@ static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns fputc(separator, fd); } -static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) { +static void writeList(FILE* fd, char** list, int len, char separator) { const char* sep = ""; - for (size_t i = 0; i < this->hColumns[column].len; i++) { - fprintf(fd, "%s%s", sep, this->hColumns[column].names[i]); + for (int i = 0; i < len; i++) { + fprintf(fd, "%s%s", sep, list[i]); sep = " "; } fputc(separator, fd); } +static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) { + writeList(fd, this->hColumns[column].names, this->hColumns[column].len, separator); +} + static void writeMeterModes(const Settings* this, FILE* fd, char separator, unsigned int column) { const char* sep = ""; for (size_t i = 0; i < this->hColumns[column].len; i++) { @@ -400,12 +572,7 @@ int Settings_write(const Settings* this, bool onCrash) { } printSettingString("htop_version", VERSION); printSettingInteger("config_reader_min_version", CONFIG_READER_MIN_VERSION); - writeFields(fd, this->fields, this->dynamicColumns, "fields", separator); - // This "-1" is for compatibility with the older enum format. - printSettingInteger("sort_key", this->sortKey - 1); - printSettingInteger("sort_direction", this->direction); - printSettingInteger("tree_sort_key", this->treeSortKey - 1); - printSettingInteger("tree_sort_direction", this->treeDirection); + fprintf(fd, "fields="); writeFields(fd, this->screens[0]->fields, this->dynamicColumns, false, separator); printSettingInteger("hide_kernel_threads", this->hideKernelThreads); printSettingInteger("hide_userland_threads", this->hideUserlandThreads); printSettingInteger("shadow_other_users", this->shadowOtherUsers); @@ -420,10 +587,8 @@ int Settings_write(const Settings* this, bool onCrash) { printSettingInteger("find_comm_in_cmdline", this->findCommInCmdline); printSettingInteger("strip_exe_from_cmdline", this->stripExeFromCmdline); printSettingInteger("show_merged_command", this->showMergedCommand); - printSettingInteger("tree_view", this->treeView); - printSettingInteger("tree_view_always_by_pid", this->treeViewAlwaysByPID); - printSettingInteger("all_branches_collapsed", this->allBranchesCollapsed); printSettingInteger("header_margin", this->headerMargin); + printSettingInteger("screen_tabs", this->screenTabs); printSettingInteger("detailed_cpu_time", this->detailedCPUTime); printSettingInteger("cpu_count_from_one", this->countCPUsFromOne); printSettingInteger("show_cpu_usage", this->showCPUUsage); @@ -452,6 +617,29 @@ int Settings_write(const Settings* this, bool onCrash) { writeMeterModes(this, fd, separator, i); } + // Legacy compatibility with older versions of htop + printSettingInteger("tree_view", this->screens[0]->treeView); + // This "-1" is for compatibility with the older enum format. + printSettingInteger("sort_key", this->screens[0]->sortKey - 1); + printSettingInteger("tree_sort_key", this->screens[0]->treeSortKey - 1); + printSettingInteger("sort_direction", this->screens[0]->direction); + printSettingInteger("tree_sort_direction", this->screens[0]->treeDirection); + printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByPID); + printSettingInteger("all_branches_collapsed", this->screens[0]->allBranchesCollapsed); + + for (unsigned int i = 0; i < this->nScreens; i++) { + ScreenSettings* ss = this->screens[i]; + fprintf(fd, "screen:%s=", ss->name); + writeFields(fd, ss->fields, this->dynamicColumns, true, separator); + printSettingString(".sort_key", toFieldName(this->dynamicColumns, ss->sortKey)); + printSettingString(".tree_sort_key", toFieldName(this->dynamicColumns, ss->treeSortKey)); + printSettingInteger(".tree_view", ss->treeView); + printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID); + printSettingInteger(".sort_direction", ss->direction); + printSettingInteger(".tree_sort_direction", ss->treeDirection); + printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed); + } + #undef printSettingString #undef printSettingInteger @@ -475,16 +663,11 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns) this->dynamicColumns = dynamicColumns; this->hLayout = HF_TWO_50_50; this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting)); - this->sortKey = PERCENT_CPU; - this->treeSortKey = PID; - this->direction = -1; - this->treeDirection = 1; + this->shadowOtherUsers = false; this->showThreadNames = false; this->hideKernelThreads = true; this->hideUserlandThreads = false; - this->treeView = false; - this->allBranchesCollapsed = false; this->highlightBaseName = false; this->highlightDeletedExe = true; this->highlightMegabytes = true; @@ -509,15 +692,9 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns) #ifdef HAVE_LIBHWLOC this->topologyAffinity = false; #endif - this->fields = xCalloc(LAST_PROCESSFIELD + 1, sizeof(ProcessField)); - // TODO: turn 'fields' into a Vector, - // (and ProcessFields into proper objects). - this->flags = 0; - const ProcessField* defaults = Platform_defaultFields; - for (int i = 0; defaults[i]; i++) { - this->fields[i] = defaults[i]; - this->flags |= Process_fields[defaults[i]].flags; - } + + this->screens = xCalloc(Platform_numberOfDefaultScreens * sizeof(ScreenSettings*), 1); + this->nScreens = 0; char* legacyDotfile = NULL; const char* rcfile = getenv("HTOPRC"); @@ -573,21 +750,27 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns) ok = Settings_read(this, this->filename, initialCpuCount); } if (!ok) { + this->screenTabs = true; this->changed = true; ok = Settings_read(this, SYSCONFDIR "/htoprc", initialCpuCount); } if (!ok) { Settings_defaultMeters(this, initialCpuCount); + Settings_defaultScreens(this); } + + this->ssIndex = 0; + this->ss = this->screens[this->ssIndex]; + return this; } -void Settings_invertSortOrder(Settings* this) { +void ScreenSettings_invertSortOrder(ScreenSettings* this) { int* attr = (this->treeView) ? &(this->treeDirection) : &(this->direction); *attr = (*attr == 1) ? -1 : 1; } -void Settings_setSortKey(Settings* this, ProcessField sortKey) { +void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) { if (this->treeViewAlwaysByPID || !this->treeView) { this->sortKey = sortKey; this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1; diff --git a/Settings.h b/Settings.h index a1f7040..345a535 100644 --- a/Settings.h +++ b/Settings.h @@ -19,7 +19,13 @@ in the source distribution for its full text. #define DEFAULT_DELAY 15 -#define CONFIG_READER_MIN_VERSION 2 +#define CONFIG_READER_MIN_VERSION 3 + +typedef struct { + const char* name; + const char* columns; + const char* sortKey; +} ScreenDefaults; typedef struct { size_t len; @@ -27,6 +33,19 @@ typedef struct { int* modes; } MeterColumnSetting; +typedef struct { + char* name; + ProcessField* fields; + uint32_t flags; + int direction; + int treeDirection; + ProcessField sortKey; + ProcessField treeSortKey; + bool treeView; + bool treeViewAlwaysByPID; + bool allBranchesCollapsed; +} ScreenSettings; + typedef struct Settings_ { char* filename; int config_version; @@ -34,16 +53,14 @@ typedef struct Settings_ { MeterColumnSetting* hColumns; Hashtable* dynamicColumns; - ProcessField* fields; - uint32_t flags; + ScreenSettings** screens; + unsigned int nScreens; + unsigned int ssIndex; + ScreenSettings* ss; + int colorScheme; int delay; - int direction; - int treeDirection; - ProcessField sortKey; - ProcessField treeSortKey; - bool countCPUsFromOne; bool detailedCPUTime; bool showCPUUsage; @@ -52,9 +69,6 @@ typedef struct Settings_ { bool showCPUTemperature; bool degreeFahrenheit; #endif - bool treeView; - bool treeViewAlwaysByPID; - bool allBranchesCollapsed; bool showProgramPath; bool shadowOtherUsers; bool showThreadNames; @@ -72,6 +86,7 @@ typedef struct Settings_ { bool updateProcessNames; bool accountGuestInCPUMeter; bool headerMargin; + bool screenTabs; #ifdef HAVE_GETMOUSE bool enableMouse; #endif @@ -85,13 +100,13 @@ typedef struct Settings_ { #define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu)) -static inline ProcessField Settings_getActiveSortKey(const Settings* this) { +static inline ProcessField ScreenSettings_getActiveSortKey(const ScreenSettings* this) { return (this->treeView) ? (this->treeViewAlwaysByPID ? PID : this->treeSortKey) : this->sortKey; } -static inline int Settings_getActiveDirection(const Settings* this) { +static inline int ScreenSettings_getActiveDirection(const ScreenSettings* this) { return this->treeView ? this->treeDirection : this->direction; } @@ -101,9 +116,13 @@ int Settings_write(const Settings* this, bool onCrash); Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns); -void Settings_invertSortOrder(Settings* this); +ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults); + +void ScreenSettings_delete(ScreenSettings* this); + +void ScreenSettings_invertSortOrder(ScreenSettings* this); -void Settings_setSortKey(Settings* this, ProcessField sortKey); +void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey); void Settings_enableReadonly(void); diff --git a/SignalsPanel.c b/SignalsPanel.c index f1c5379..cb71130 100644 --- a/SignalsPanel.c +++ b/SignalsPanel.c @@ -18,15 +18,14 @@ in the source distribution for its full text. #include "XUtils.h" -Panel* SignalsPanel_new() { +Panel* SignalsPanel_new(int preSelectedSignal) { Panel* this = Panel_new(1, 1, 1, 1, Class(ListItem), true, FunctionBar_newEnterEsc("Send ", "Cancel ")); - const int defaultSignal = SIGTERM; int defaultPosition = 15; unsigned int i; for (i = 0; i < Platform_numberOfSignals; i++) { Panel_set(this, i, (Object*) ListItem_new(Platform_signals[i].name, Platform_signals[i].number)); // signal 15 is not always the 15th signal in the table - if (Platform_signals[i].number == defaultSignal) { + if (Platform_signals[i].number == preSelectedSignal) { defaultPosition = i; } } diff --git a/SignalsPanel.h b/SignalsPanel.h index da9711a..7b9303f 100644 --- a/SignalsPanel.h +++ b/SignalsPanel.h @@ -7,6 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include + #include "Panel.h" @@ -15,6 +17,8 @@ typedef struct SignalItem_ { int number; } SignalItem; -Panel* SignalsPanel_new(void); +#define SIGNALSPANEL_INITSELECTEDSIGNAL SIGTERM + +Panel* SignalsPanel_new(int preSelectedSignal); #endif diff --git a/TraceScreen.c b/TraceScreen.c index c726394..c3a9449 100644 --- a/TraceScreen.c +++ b/TraceScreen.c @@ -10,6 +10,7 @@ in the source distribution for its full text. #include "TraceScreen.h" #include +#include #include #include #include @@ -47,7 +48,9 @@ void TraceScreen_delete(Object* cast) { TraceScreen* this = (TraceScreen*) cast; if (this->child > 0) { kill(this->child, SIGTERM); - waitpid(this->child, NULL, 0); + while (waitpid(this->child, NULL, 0) == -1) + if (errno != EINTR) + break; } if (this->strace) { diff --git a/XUtils.c b/XUtils.c index b6999f9..b34a8a7 100644 --- a/XUtils.c +++ b/XUtils.c @@ -94,13 +94,28 @@ void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size) return ret; } -inline bool String_contains_i(const char* s1, const char* s2) { - return strcasestr(s1, s2) != NULL; +inline bool String_contains_i(const char* s1, const char* s2, bool multi) { + // we have a multi-string search term, handle as special case for performance reasons + if (multi && strstr(s2, "|")) { + size_t nNeedles; + char** needles = String_split(s2, '|', &nNeedles); + for (size_t i = 0; i < nNeedles; i++) { + if (strcasestr(s1, needles[i]) != NULL) { + String_freeArray(needles); + return true; + } + } + String_freeArray(needles); + return false; + } else { + return strcasestr(s1, s2) != NULL; + } } 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); char* out = xMalloc(l1 + l2 + 1); memcpy(out, s1, l1); memcpy(out + l1, s2, l2); @@ -122,10 +137,10 @@ char* String_trim(const char* in) { } char** String_split(const char* s, char sep, size_t* n) { - const unsigned int rate = 10; + const size_t rate = 10; char** out = xCalloc(rate, sizeof(char*)); size_t ctr = 0; - unsigned int blocks = rate; + size_t blocks = rate; const char* where; while ((where = strchr(s, sep)) != NULL) { size_t size = (size_t)(where - s); @@ -160,36 +175,9 @@ void String_freeArray(char** s) { free(s); } -char* String_getToken(const char* line, const unsigned short int numMatch) { - const size_t len = strlen(line); - char inWord = 0; - unsigned short int count = 0; - char match[50]; - - size_t foundCount = 0; - - for (size_t i = 0; i < len; i++) { - char lastState = inWord; - inWord = line[i] == ' ' ? 0 : 1; - - if (lastState == 0 && inWord == 1) - count++; - - if (inWord == 1) { - if (count == numMatch && line[i] != ' ' && line[i] != '\0' && line[i] != '\n' && line[i] != (char)EOF) { - match[foundCount] = line[i]; - foundCount++; - } - } - } - - match[foundCount] = '\0'; - return xStrdup(match); -} - char* String_readLine(FILE* fd) { - const unsigned int step = 1024; - unsigned int bufSize = step; + const size_t step = 1024; + size_t bufSize = step; char* buffer = xMalloc(step + 1); char* at = buffer; for (;;) { diff --git a/XUtils.h b/XUtils.h index eeb214d..2522a71 100644 --- a/XUtils.h +++ b/XUtils.h @@ -40,7 +40,7 @@ static inline bool String_startsWith(const char* s, const char* match) { return strncmp(s, match, strlen(match)) == 0; } -bool String_contains_i(const char* s1, const char* s2); +bool String_contains_i(const char* s1, const char* s2, bool multi); static inline bool String_eq(const char* s1, const char* s2) { return strcmp(s1, s2) == 0; @@ -54,8 +54,6 @@ char** String_split(const char* s, char sep, size_t* n); void String_freeArray(char** s); -char* String_getToken(const char* line, unsigned short int numMatch) ATTR_MALLOC; - char* String_readLine(FILE* fd) ATTR_MALLOC; /* Always null-terminates dest. Caller must pass a strictly positive size. */ diff --git a/configure.ac b/configure.ac index 0e69096..e7e47e3 100644 --- a/configure.ac +++ b/configure.ac @@ -6,13 +6,13 @@ # ---------------------------------------------------------------------- AC_PREREQ([2.69]) -AC_INIT([htop], [3.1.2], [htop@groups.io], [], [https://htop.dev/]) +AC_INIT([htop], [3.2.0], [htop@groups.io], [], [https://htop.dev/]) AC_CONFIG_SRCDIR([htop.c]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_HEADERS([config.h]) -AC_CANONICAL_TARGET +AC_CANONICAL_HOST AM_INIT_AUTOMAKE([-Wall std-options subdir-objects]) # ---------------------------------------------------------------------- @@ -22,7 +22,7 @@ AM_INIT_AUTOMAKE([-Wall std-options subdir-objects]) # Checks for platform. # ---------------------------------------------------------------------- -case "$target_os" in +case "$host_os" in linux*|gnu*) my_htop_platform=linux AC_DEFINE([HTOP_LINUX], [], [Building for Linux.]) @@ -419,13 +419,23 @@ case "$enable_unwind" in AC_CHECK_LIB([lzma], [lzma_index_buffer_decode]) fi AC_CHECK_LIB([unwind], [backtrace], [], [enable_unwind=no]) - AC_CHECK_HEADERS([libunwind.h], [], [enable_unwind=no]) + AC_CHECK_HEADERS([libunwind.h], [], [ + old_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -I/usr/include/libunwind" + AC_CHECK_HEADERS([libunwind/libunwind.h], [], [ + enable_unwind=no + CFLAGS="$old_CFLAGS" + ]) + ]) ;; no) ;; yes) AC_CHECK_LIB([unwind], [backtrace], [], [AC_MSG_ERROR([can not find required library libunwind])]) - AC_CHECK_HEADERS([libunwind.h], [], [AC_MSG_ERROR([can not find require header file libunwind.h])]) + AC_CHECK_HEADERS([libunwind.h], [], [ + CFLAGS="$CFLAGS -I/usr/include/libunwind" + AC_CHECK_HEADERS([libunwind/libunwind.h], [], [AC_MSG_ERROR([can not find required header file libunwind.h])]) + ]) ;; *) AC_MSG_ERROR([bad value '$enable_unwind' for --enable-unwind]) @@ -446,8 +456,18 @@ case "$enable_hwloc" in no) ;; yes) - AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])]) - AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])]) + m4_ifdef([PKG_PROG_PKG_CONFIG], [ + PKG_PROG_PKG_CONFIG() + PKG_CHECK_MODULES(HWLOC, hwloc, [ + CFLAGS="$CFLAGS $HWLOC_CFLAGS" LIBS="$LIBS $HWLOC_LIBS" + ], [ + AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])]) + AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])]) + ]) + ], [ + AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])]) + AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])]) + ]) ;; *) AC_MSG_ERROR([bad value '$enable_hwloc' for --enable-hwloc]) @@ -703,7 +723,7 @@ AC_SUBST([AM_CPPFLAGS]) # We're done, let's go! # ---------------------------------------------------------------------- -AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2021 htop dev team."], [Copyright message.]) +AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2022 htop dev team."], [Copyright message.]) AM_CONDITIONAL([HTOP_LINUX], [test "$my_htop_platform" = linux]) AM_CONDITIONAL([HTOP_FREEBSD], [test "$my_htop_platform" = freebsd]) diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index 95ae960..71b9add 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include #include +#include #include "CRT.h" #include "Process.h" @@ -26,7 +27,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, .pidColumn = true, }, [PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, .pidColumn = true, }, [SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .flags = 0, .pidColumn = true, }, - [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, }, + [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = PROCESS_FLAG_TTY, }, [TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, .pidColumn = true, }, [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, }, [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, }, @@ -38,8 +39,8 @@ 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, }, - [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, }, + [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, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, @@ -276,6 +277,18 @@ static long long int nanosecondsToCentiseconds(uint64_t nanoseconds) { return nanoseconds / nanoseconds_per_second * centiseconds_per_second; } +static char* DarwinProcess_getDevname(dev_t dev) { + if (dev == NODEV) { + return NULL; + } + char buf[sizeof("/dev/") + MAXNAMLEN]; + char *name = devname_r(dev, S_IFCHR, buf, MAXNAMLEN); + if (name) { + return xStrdup(name); + } + return NULL; +} + void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) { DarwinProcess* dp = (DarwinProcess*)proc; @@ -306,15 +319,8 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, proc->isKernelThread = false; proc->isUserlandThread = false; dp->translated = ps->kp_proc.p_flag & P_TRANSLATED; - proc->tty_nr = ps->kp_eproc.e_tdev; - const char* name = (ps->kp_eproc.e_tdev != NODEV) ? devname(ps->kp_eproc.e_tdev, S_IFCHR) : NULL; - if (!name) { - free(proc->tty_name); - proc->tty_name = NULL; - } else { - free_and_xStrdup(&proc->tty_name, name); - } + proc->tty_name = NULL; proc->starttime_ctime = ep->p_starttime.tv_sec; Process_fillStarttimeBuffer(proc); @@ -322,11 +328,28 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, DarwinProcess_updateExe(ep->p_pid, proc); DarwinProcess_updateCmdLine(ps, proc); - if (proc->settings->flags & PROCESS_FLAG_CWD) { + if (proc->settings->ss->flags & PROCESS_FLAG_CWD) { DarwinProcess_updateCwd(ep->p_pid, proc); } } + if (proc->tty_name == NULL && (dev_t)proc->tty_nr != NODEV) { + /* The call to devname() is extremely expensive (due to lstat) + * and represents ~95% of htop's CPU usage when there is high + * process turnover. + * + * To mitigate this we only fetch TTY information if the TTY + * field is enabled in the settings. + */ + if (proc->settings->ss->flags & PROCESS_FLAG_TTY) { + proc->tty_name = DarwinProcess_getDevname(proc->tty_nr); + if (!proc->tty_name) { + /* devname failed: prevent us from calling it again */ + proc->tty_nr = NODEV; + } + } + } + /* Mutable information */ proc->nice = ep->p_nice; proc->priority = ep->p_priority; @@ -354,6 +377,7 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* } else { proc->super.percent_cpu = 0.0; } + Process_updateCPUFieldWidths(proc->super.percent_cpu); proc->super.time = nanosecondsToCentiseconds(total_current_time_ns); proc->super.nlwp = pti.pti_threadnum; diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h index b4b86fd..bd17974 100644 --- a/darwin/DarwinProcess.h +++ b/darwin/DarwinProcess.h @@ -13,6 +13,8 @@ in the source distribution for its full text. #include "darwin/DarwinProcessList.h" +#define PROCESS_FLAG_TTY 0x00000100 + typedef struct DarwinProcess_ { Process super; diff --git a/darwin/Platform.c b/darwin/Platform.c index 152f617..4b34d88 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -49,7 +49,15 @@ in the source distribution for its full text. #endif -const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); const SignalItem Platform_signals[] = { { .name = " 0 Cancel", .number = 0 }, diff --git a/darwin/Platform.h b/darwin/Platform.h index fe75db0..4f8e7c9 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -26,7 +26,9 @@ in the source distribution for its full text. #include "generic/uname.h" -extern const ProcessField Platform_defaultFields[]; +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; extern const SignalItem Platform_signals[]; diff --git a/darwin/PlatformHelpers.h b/darwin/PlatformHelpers.h index f1af1c0..07f3fe2 100644 --- a/darwin/PlatformHelpers.h +++ b/darwin/PlatformHelpers.h @@ -2,8 +2,8 @@ #define HEADER_PlatformHelpers /* htop - darwin/PlatformHelpers.h -(C) 2018 Pierre Malhaire, 2020-2021 htop dev team, 2021 Alexander Momchilov -Released under the GNU GPLv2, see the COPYING file +(C) 2018 Pierre Malhaire, 2020-2022 htop dev team, 2021 Alexander Momchilov +Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c index ceb346c..e11348d 100644 --- a/dragonflybsd/DragonFlyBSDProcess.c +++ b/dragonflybsd/DragonFlyBSDProcess.c @@ -38,8 +38,8 @@ 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, }, - [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, }, + [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, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, diff --git a/dragonflybsd/DragonFlyBSDProcessList.c b/dragonflybsd/DragonFlyBSDProcessList.c index 86586a8..0d0e1a4 100644 --- a/dragonflybsd/DragonFlyBSDProcessList.c +++ b/dragonflybsd/DragonFlyBSDProcessList.c @@ -481,7 +481,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { DragonFlyBSDProcessList_updateExe(kproc, proc); DragonFlyBSDProcessList_updateProcessName(dfpl->kd, kproc, proc); - if (settings->flags & PROCESS_FLAG_CWD) { + if (settings->ss->flags & PROCESS_FLAG_CWD) { DragonFlyBSDProcessList_updateCwd(kproc, proc); } @@ -513,6 +513,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { proc->percent_cpu = 100.0 * ((double)kproc->kp_lwp.kl_pctcpu / (double)kernelFScale); proc->percent_mem = 100.0 * proc->m_resident / (double)(super->totalMem); + Process_updateCPUFieldWidths(proc->percent_cpu); if (proc->percent_cpu > 0.1) { // system idle process should own all CPU time left regardless of CPU count diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index 4941445..ea5f4fa 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -32,8 +32,15 @@ in the source distribution for its full text. #include "dragonflybsd/DragonFlyBSDProcess.h" #include "dragonflybsd/DragonFlyBSDProcessList.h" +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; -const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); const SignalItem Platform_signals[] = { { .name = " 0 Cancel", .number = 0 }, diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h index 281a7ee..ec140ad 100644 --- a/dragonflybsd/Platform.h +++ b/dragonflybsd/Platform.h @@ -29,7 +29,9 @@ in the source distribution for its full text. #include "generic/uname.h" -extern const ProcessField Platform_defaultFields[]; +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; extern const SignalItem Platform_signals[]; diff --git a/dragonflybsd/ProcessField.h b/dragonflybsd/ProcessField.h index 02b5568..1409675 100644 --- a/dragonflybsd/ProcessField.h +++ b/dragonflybsd/ProcessField.h @@ -9,8 +9,8 @@ in the source distribution for its full text. #define PLATFORM_PROCESS_FIELDS \ - JID = 100, \ - JAIL = 101, \ + JID = 100, \ + JAIL = 101, \ \ DUMMY_BUMP_FIELD = CWD, \ // End of list diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c index 345edff..2746540 100644 --- a/freebsd/FreeBSDProcess.c +++ b/freebsd/FreeBSDProcess.c @@ -37,8 +37,8 @@ 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, }, - [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, }, + [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, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, @@ -49,6 +49,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, [JID] = { .name = "JID", .title = "JID", .description = "Jail prison ID", .flags = 0, .pidColumn = true, }, [JAIL] = { .name = "JAIL", .title = "JAIL ", .description = "Jail prison name", .flags = 0, }, + [EMULATION] = { .name = "EMULATION", .title = "EMULATION ", .description = "System call emulation environment (ABI)", .flags = 0, }, }; Process* FreeBSDProcess_new(const Settings* settings) { @@ -61,6 +62,7 @@ Process* FreeBSDProcess_new(const Settings* settings) { void Process_delete(Object* cast) { FreeBSDProcess* this = (FreeBSDProcess*) cast; Process_done((Process*)cast); + free(this->emul); free(this->jname); free(this); } @@ -77,6 +79,9 @@ static void FreeBSDProcess_writeField(const Process* this, RichString* str, Proc case JAIL: Process_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11); return; + case EMULATION: + Process_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16); + return; default: Process_writeField(this, str, field); return; @@ -94,6 +99,8 @@ static int FreeBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro return SPACESHIP_NUMBER(p1->jid, p2->jid); case JAIL: return SPACESHIP_NULLSTR(p1->jname, p2->jname); + case EMULATION: + return SPACESHIP_NULLSTR(p1->emul, p2->emul); default: return Process_compareByKey_Base(v1, v2, key); } diff --git a/freebsd/FreeBSDProcess.h b/freebsd/FreeBSDProcess.h index 2443f89..654f9cf 100644 --- a/freebsd/FreeBSDProcess.h +++ b/freebsd/FreeBSDProcess.h @@ -18,6 +18,7 @@ typedef struct FreeBSDProcess_ { Process super; int jid; char* jname; + char* emul; } FreeBSDProcess; extern const ProcessClass FreeBSDProcess_class; diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessList.c index 507f480..f58f338 100644 --- a/freebsd/FreeBSDProcessList.c +++ b/freebsd/FreeBSDProcessList.c @@ -509,6 +509,9 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { proc->pgrp = kproc->ki_pgid; proc->st_uid = kproc->ki_uid; proc->starttime_ctime = kproc->ki_start.tv_sec; + if (proc->starttime_ctime < 0) { + proc->starttime_ctime = super->realtimeMs / 1000; + } Process_fillStarttimeBuffer(proc); proc->user = UsersTable_getRef(super->usersTable, proc->st_uid); ProcessList_add(super, proc); @@ -516,7 +519,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { FreeBSDProcessList_updateExe(kproc, proc); FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc); - if (settings->flags & PROCESS_FLAG_CWD) { + if (settings->ss->flags & PROCESS_FLAG_CWD) { FreeBSDProcessList_updateCwd(kproc, proc); } @@ -549,6 +552,8 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { } } + free_and_xStrdup(&fp->emul, kproc->ki_emul); + // from FreeBSD source /src/usr.bin/top/machine.c proc->m_virt = kproc->ki_size / ONE_K; proc->m_resident = kproc->ki_rssize * pageSizeKb; @@ -557,6 +562,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { proc->percent_cpu = 100.0 * ((double)kproc->ki_pctcpu / (double)kernelFScale); proc->percent_mem = 100.0 * proc->m_resident / (double)(super->totalMem); + Process_updateCPUFieldWidths(proc->percent_cpu); if (kproc->ki_stat == SRUN && kproc->ki_oncpu != NOCPU) { proc->processor = kproc->ki_oncpu; diff --git a/freebsd/Platform.c b/freebsd/Platform.c index 6c82bc2..d21fc80 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -50,8 +50,15 @@ in the source distribution for its full text. #include "zfs/ZfsArcMeter.h" #include "zfs/ZfsCompressedArcMeter.h" +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; -const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); const SignalItem Platform_signals[] = { { .name = " 0 Cancel", .number = 0 }, diff --git a/freebsd/Platform.h b/freebsd/Platform.h index a1aa31b..c0292d9 100644 --- a/freebsd/Platform.h +++ b/freebsd/Platform.h @@ -25,7 +25,9 @@ in the source distribution for its full text. #include "generic/uname.h" -extern const ProcessField Platform_defaultFields[]; +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; extern const SignalItem Platform_signals[]; diff --git a/freebsd/ProcessField.h b/freebsd/ProcessField.h index 8ed2746..d20900d 100644 --- a/freebsd/ProcessField.h +++ b/freebsd/ProcessField.h @@ -9,8 +9,9 @@ in the source distribution for its full text. #define PLATFORM_PROCESS_FIELDS \ - JID = 100, \ - JAIL = 101, \ + JID = 100, \ + JAIL = 101, \ + EMULATION = 102, \ \ DUMMY_BUMP_FIELD = CWD, \ // End of list diff --git a/generic/uname.c b/generic/uname.c index 21f9a3c..2a734dc 100644 --- a/generic/uname.c +++ b/generic/uname.c @@ -85,7 +85,7 @@ char* Generic_uname(void) { if (uname_result == 0) { size_t written = xSnprintf(savedString, sizeof(savedString), "%s %s [%s]", uname_info.sysname, uname_info.release, uname_info.machine); - if (!String_contains_i(savedString, distro) && sizeof(savedString) > written) + if (!String_contains_i(savedString, distro, false) && sizeof(savedString) > written) snprintf(savedString + written, sizeof(savedString) - written, " @ %s", distro); } else { snprintf(savedString, sizeof(savedString), "%s", distro); diff --git a/htop.1.in b/htop.1.in index 49c4a52..c81c819 100644 --- a/htop.1.in +++ b/htop.1.in @@ -1,4 +1,4 @@ -.TH "HTOP" "1" "2021" "@PACKAGE_STRING@" "User Commands" +.TH "HTOP" "1" "2022" "@PACKAGE_STRING@" "User Commands" .SH "NAME" htop, pcp-htop \- interactive process viewer .SH "SYNOPSIS" @@ -50,7 +50,8 @@ Start in monochrome mode .TP \fB\-F \-\-filter=FILTER -Filter processes by command +Filter processes by terms matching the commands. The terms are matched +case-insensitive and as fixed strings (not regexs). You can separate multiple terms with "|". .TP \fB\-h \-\-help Display a help message and exit @@ -95,6 +96,10 @@ held. The following commands are supported while in .BR htop : .TP 5 +.B Tab, Shift-Tab +Select the next / the previous screen tab to display. +You can enable showing the screen tab names in the Setup screen (F2). +.TP .B Up, Alt-k Select (highlight) the previous process in the process list. Scroll the list if necessary. @@ -175,6 +180,8 @@ bindings take precedence. Incremental process filtering: type in part of a process command line and only processes whose names match will be shown. To cancel filtering, enter the Filter option again and press Esc. +The matching is done case-insensitive. Terms are fixed strings (no regex). +You can separate multiple terms with "|". .TP .B F5, t Tree view: organize processes by parenthood, and layout the relations @@ -289,6 +296,8 @@ highlighting can be configured for stale executables (cf. EXE column below). .TP .B COMM The command name of the process obtained from /proc/[pid]/comm, if readable. + +Requires Linux kernel 2.6.33 or newer. .TP .B EXE The abbreviated basename of the executable of the process, obtained from @@ -310,6 +319,8 @@ been replaced or deleted. This additional color markup can be configured in the "Display Options" section of the setup screen. + +Displaying EXE requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED. .TP .B PID The process ID. @@ -496,7 +507,7 @@ This performs some pattern-based replacements to shorten the displayed string an \fB/*.slice\fR is shortened to \fB/[*]\fR (exceptions below) \fB/system.slice\fR is shortened to \fB/[S]\fR \fB/user.slice\fR is shortened to \fB/[U]\fR - \fB/user-*.slice\fR is shortened to \fB/[U:*]\fR (directly preceeding \fB/[U]\fR before dropped) + \fB/user-*.slice\fR is shortened to \fB/[U:*]\fR (directly preceding \fB/[U]\fR before dropped) \fB/machine.slice\fR is shortened to \fB/[M]\fR \fB/machine-*.scope\fR is shortened to \fB/[SNC:*]\fR (SNC: systemd nspawn container), uppercase for the monitor \fB/lxc.monitor.*\fR is shortened to \fB/[LXC:*]\fR @@ -527,12 +538,6 @@ The percentage of time spent waiting for the completion of synchronous block I/O .B PERCENT_SWAP_DELAY (SWAPD%) The percentage of time spent swapping in pages. Requires CAP_NET_ADMIN. .TP -.B COMM -The command name for the process. Requires Linux kernel 2.6.33 or newer. -.TP -.B EXE -The executable file of the process as reported by the kernel. Requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED. -.TP .B AGRP The autogroup identifier for the process. Requires Linux CFS to be enabled. .TP @@ -663,7 +668,7 @@ communities, and forms part of the Performance Co-Pilot suite of tools. .SH "COPYRIGHT" Copyright \(co 2004-2019 Hisham Muhammad. .br -Copyright \(co 2020-2021 htop dev team. +Copyright \(co 2020-2022 htop dev team. .LP License GPLv2+: GNU General Public License version 2 or, at your option, any later version. .LP diff --git a/linux/CGroupUtils.c b/linux/CGroupUtils.c index 6f3b6fe..22cce91 100644 --- a/linux/CGroupUtils.c +++ b/linux/CGroupUtils.c @@ -33,7 +33,7 @@ static bool StrBuf_putc_write(StrBuf_state* p, char c) { } static bool StrBuf_putsn(StrBuf_state* p, StrBuf_putc_t w, const char* s, size_t count) { - while (count--) + for (; count; count--) if (!w(p, *s++)) return false; @@ -66,6 +66,7 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB const char* str_user_slice = "user.slice"; const char* str_machine_slice = "machine.slice"; const char* str_user_slice_prefix = "/user-"; + const char* str_system_slice_prefix = "/system-"; const char* str_lxc_monitor_legacy = "lxc.monitor"; const char* str_lxc_payload_legacy = "lxc.payload"; @@ -76,6 +77,8 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB const char* str_nspawn_monitor_label = "/supervisor"; const char* str_nspawn_payload_label = "/payload"; + const char* str_snap_scope_prefix = "snap."; + const char* str_service_suffix = ".service"; const char* str_scope_suffix = ".scope"; @@ -100,6 +103,11 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB if (!StrBuf_putsz(s, w, "[S]")) return false; + if (String_startsWith(cgroup, str_system_slice_prefix)) { + cgroup = strchrnul(cgroup + 1, '/'); + continue; + } + continue; } @@ -266,6 +274,22 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB else if (String_startsWith(nextSlash, str_nspawn_payload_label)) cgroup += strlen(str_nspawn_payload_label); + continue; + } else if(Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) { + const char* nextDot = strchrnul(labelStart + strlen(str_snap_scope_prefix), '.'); + + if (!StrBuf_putsz(s, w, "!snap:")) + return false; + + if (nextDot >= labelStart + scopeNameLen) { + nextDot = labelStart + scopeNameLen; + } + + if (!StrBuf_putsn(s, w, labelStart + strlen(str_snap_scope_prefix), nextDot - (labelStart + strlen(str_snap_scope_prefix)))) + return false; + + cgroup = nextSlash; + continue; } diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index ba2dbd4..299b167 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -57,8 +57,8 @@ 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, }, - [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, }, + [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, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, @@ -81,8 +81,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, - [CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw) ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, }, - [CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed) ", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, }, + [CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw)", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, }, + [CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed)", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, }, [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 @@ -94,7 +94,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, [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, .defaultSortDesc = true, }, [CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, .defaultSortDesc = true, }, - [SECATTR] = { .name = "SECATTR", .title = "Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, }, + [SECATTR] = { .name = "SECATTR", .title = "Security Attribute", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, .autoWidth = true, }, [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, }, [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, @@ -248,8 +248,8 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces #ifdef HAVE_VSERVER case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break; #endif - case CGROUP: xSnprintf(buffer, n, "%-35.35s ", lp->cgroup ? lp->cgroup : "N/A"); break; - case CCGROUP: xSnprintf(buffer, n, "%-35.35s ", lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break; + case CGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CGROUP], Process_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break; + case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CCGROUP], Process_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break; case OOM: xSnprintf(buffer, n, "%4u ", lp->oom); break; case IO_PRIORITY: { int klass = IOPriority_class(lp->ioPriority); @@ -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, &attr); break; - case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, &attr); break; - case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, &attr); break; + 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; #endif case CTXT: if (lp->ctxt_diff > 1000) { @@ -280,7 +280,7 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces } xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff); break; - case SECATTR: snprintf(buffer, n, "%-30.30s ", lp->secattr ? lp->secattr : "?"); break; + case SECATTR: snprintf(buffer, n, "%-*.*s ", Process_fieldWidths[SECATTR], Process_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break; case AUTOGROUP_ID: if (lp->autogroup_id != -1) { xSnprintf(buffer, n, "%4ld ", lp->autogroup_id); diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index 3bfe7db..5e18f6d 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -174,21 +174,24 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) { LinuxProcessList* this = (LinuxProcessList*) super; unsigned int existing = 0, active = 0; - DIR* dir = opendir("/sys/devices/system/cpu"); - if (!dir) { - this->cpuData = xReallocArrayZero(this->cpuData, super->existingCPUs ? (super->existingCPUs + 1) : 0, 2, sizeof(CPUData)); + // Initialize the cpuData array before anything else. + if (!this->cpuData) { + this->cpuData = xCalloc(2, sizeof(CPUData)); this->cpuData[0].online = true; /* average is always "online" */ this->cpuData[1].online = true; super->activeCPUs = 1; super->existingCPUs = 1; - return; } + DIR* dir = opendir("/sys/devices/system/cpu"); + if (!dir) + return; + unsigned int currExisting = super->existingCPUs; const struct dirent* entry; while ((entry = readdir(dir)) != NULL) { - if (entry->d_type != DT_DIR) + if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) continue; if (!String_startsWith(entry->d_name, "cpu")) @@ -233,6 +236,10 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) { closedir(dir); + // return if no CPU is found + if (existing < 1) + return; + #ifdef HAVE_SENSORS_SENSORS_H /* When started with offline CPUs, libsensors does not monitor those, * even when they become online. */ @@ -493,6 +500,8 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc unsigned long long last_read = process->io_read_bytes; unsigned long long last_write = process->io_write_bytes; + unsigned long long time_delta = realtimeMs > process->io_last_scan_time_ms ? realtimeMs - process->io_last_scan_time_ms : 0; + char* buf = buffer; const char* line; while ((line = strsep(&buf, "\n")) != NULL) { @@ -502,7 +511,7 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc process->io_rchar = strtoull(line + 7, NULL, 10); } else if (String_startsWith(line + 1, "ead_bytes: ")) { process->io_read_bytes = strtoull(line + 12, NULL, 10); - process->io_rate_read_bps = (process->io_read_bytes - last_read) * /*ms to s*/1000 / (realtimeMs - process->io_last_scan_time_ms); + process->io_rate_read_bps = time_delta ? (process->io_read_bytes - last_read) * /*ms to s*/1000. / time_delta : NAN; } break; case 'w': @@ -510,7 +519,7 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc process->io_wchar = strtoull(line + 7, NULL, 10); } else if (String_startsWith(line + 1, "rite_bytes: ")) { process->io_write_bytes = strtoull(line + 13, NULL, 10); - process->io_rate_write_bps = (process->io_write_bytes - last_write) * /*ms to s*/1000 / (realtimeMs - process->io_last_scan_time_ms); + process->io_rate_write_bps = time_delta ? (process->io_write_bytes - last_write) * /*ms to s*/1000. / time_delta : NAN; } break; case 's': @@ -900,16 +909,27 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t bool changed = !process->cgroup || !String_eq(process->cgroup, output); + Process_updateFieldWidth(CGROUP, strlen(output)); free_and_xStrdup(&process->cgroup, output); - if (!changed) + if (!changed) { + if(process->cgroup_short) { + Process_updateFieldWidth(CCGROUP, strlen(process->cgroup_short)); + } else { + //CCGROUP is alias to normal CGROUP if shortening fails + Process_updateFieldWidth(CCGROUP, strlen(process->cgroup)); + } return; + } char* cgroup_short = CGroup_filterName(process->cgroup); if (cgroup_short) { + Process_updateFieldWidth(CCGROUP, strlen(cgroup_short)); free_and_xStrdup(&process->cgroup_short, cgroup_short); free(cgroup_short); } else { + //CCGROUP is alias to normal CGROUP if shortening fails + Process_updateFieldWidth(CCGROUP, strlen(process->cgroup)); free(process->cgroup_short); process->cgroup_short = NULL; } @@ -1027,6 +1047,9 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t if (newline) { *newline = '\0'; } + + Process_updateFieldWidth(SECATTR, strlen(buffer)); + if (process->secattr && String_eq(process->secattr, buffer)) { return; } @@ -1370,6 +1393,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ ProcessList* pl = (ProcessList*) this; const struct dirent* entry; const Settings* settings = pl->settings; + const ScreenSettings* ss = settings->ss; #ifdef HAVE_OPENAT int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); @@ -1463,7 +1487,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ continue; } - if (settings->flags & PROCESS_FLAG_IO) + if (ss->flags & PROCESS_FLAG_IO) LinuxProcessList_readIoFile(lp, procFd, pl->realtimeMs); if (!LinuxProcessList_readStatmFile(lp, procFd)) @@ -1472,8 +1496,9 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ { bool prev = proc->usesDeletedLib; - if ((settings->flags & PROCESS_FLAG_LINUX_LRS_FIX) || - (settings->highlightDeletedExe && !proc->procExeDeleted && !proc->isKernelThread && !proc->isUserlandThread)) { + if (!proc->isKernelThread && !proc->isUserlandThread && + ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted))) { + // Check if we really should recalculate the M_LRS value for this process uint64_t passedTimeInMs = pl->realtimeMs - lp->last_mlrs_calctime; @@ -1481,17 +1506,18 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ if (passedTimeInMs > recheck) { lp->last_mlrs_calctime = pl->realtimeMs; - LinuxProcessList_readMaps(lp, procFd, settings->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe); + LinuxProcessList_readMaps(lp, procFd, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe); } } else { /* Copy from process structure in threads and reset if setting got disabled */ proc->usesDeletedLib = (proc->isUserlandThread && parent) ? parent->usesDeletedLib : false; + lp->m_lrs = (proc->isUserlandThread && parent) ? ((const LinuxProcess*)parent)->m_lrs : 0; } proc->mergedCommand.exeChanged |= prev ^ proc->usesDeletedLib; } - if ((settings->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) { + if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) { if (!parent) { // Read smaps file of each process only every second pass to improve performance static int smaps_flag = 0; @@ -1521,7 +1547,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ proc->tty_name = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr); } - if (settings->flags & PROCESS_FLAG_LINUX_IOPRIO) { + if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) { LinuxProcess_updateIOPriority(lp); } @@ -1529,6 +1555,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ float percent_cpu = (period < 1E-6) ? 0.0F : ((lp->utime + lp->stime - lasttimes) / period * 100.0); proc->percent_cpu = CLAMP(percent_cpu, 0.0F, activeCPUs * 100.0F); proc->percent_mem = proc->m_resident / (double)(pl->totalMem) * 100.0; + Process_updateCPUFieldWidths(proc->percent_cpu); if (! LinuxProcessList_updateUser(pl, proc, procFd)) goto errorReadingProcess; @@ -1536,13 +1563,13 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ if (!preExisting) { #ifdef HAVE_OPENVZ - if (settings->flags & PROCESS_FLAG_LINUX_OPENVZ) { + if (ss->flags & PROCESS_FLAG_LINUX_OPENVZ) { LinuxProcessList_readOpenVZData(lp, procFd); } #endif #ifdef HAVE_VSERVER - if (settings->flags & PROCESS_FLAG_LINUX_VSERVER) { + if (ss->flags & PROCESS_FLAG_LINUX_VSERVER) { LinuxProcessList_readVServerData(lp, procFd); } #endif @@ -1567,32 +1594,32 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } #ifdef HAVE_DELAYACCT - if (settings->flags & PROCESS_FLAG_LINUX_DELAYACCT) { + if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) { LinuxProcessList_readDelayAcctData(this, lp); } #endif - if (settings->flags & PROCESS_FLAG_LINUX_CGROUP) { + if (ss->flags & PROCESS_FLAG_LINUX_CGROUP) { LinuxProcessList_readCGroupFile(lp, procFd); } - if (settings->flags & PROCESS_FLAG_LINUX_OOM) { + if (ss->flags & PROCESS_FLAG_LINUX_OOM) { LinuxProcessList_readOomData(lp, procFd); } - if (settings->flags & PROCESS_FLAG_LINUX_CTXT) { + if (ss->flags & PROCESS_FLAG_LINUX_CTXT) { LinuxProcessList_readCtxtData(lp, procFd); } - if (settings->flags & PROCESS_FLAG_LINUX_SECATTR) { + if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) { LinuxProcessList_readSecattrData(lp, procFd); } - if (settings->flags & PROCESS_FLAG_CWD) { + if (ss->flags & PROCESS_FLAG_CWD) { LinuxProcessList_readCwd(lp, procFd); } - if ((settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) { + if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) { LinuxProcessList_readAutogroup(lp, procFd); } @@ -1994,7 +2021,7 @@ static inline double LinuxProcessList_scanCPUTime(ProcessList* super) { return period; } -static int scanCPUFreqencyFromSysCPUFreq(LinuxProcessList* this) { +static int scanCPUFrequencyFromSysCPUFreq(LinuxProcessList* this) { unsigned int existingCPUs = this->super.existingCPUs; int numCPUsWithFrequency = 0; unsigned long totalFrequency = 0; @@ -2057,7 +2084,7 @@ static int scanCPUFreqencyFromSysCPUFreq(LinuxProcessList* this) { return 0; } -static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) { +static void scanCPUFrequencyFromCPUinfo(LinuxProcessList* this) { FILE* file = fopen(PROCCPUINFOFILE, "r"); if (file == NULL) return; @@ -2114,11 +2141,11 @@ static void LinuxProcessList_scanCPUFrequency(LinuxProcessList* this) { this->cpuData[i].frequency = NAN; } - if (scanCPUFreqencyFromSysCPUFreq(this) == 0) { + if (scanCPUFrequencyFromSysCPUFreq(this) == 0) { return; } - scanCPUFreqencyFromCPUinfo(this); + scanCPUFrequencyFromCPUinfo(this); } void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { @@ -2146,7 +2173,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { return; } - if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) { + if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) { // Refer to sched(7) 'autogroup feature' section // The kernel feature can be enabled/disabled through procfs at // any time, so check for it at the start of each sample - only diff --git a/linux/Platform.c b/linux/Platform.c index 93953e8..775f9ae 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -70,6 +70,10 @@ in the source distribution for its full text. #include "LibSensors.h" #endif +#ifndef O_PATH +#define O_PATH 010000000 // declare for ancient glibc versions +#endif + #ifdef HAVE_LIBCAP enum CapMode { @@ -79,7 +83,22 @@ enum CapMode { }; #endif -const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; +bool Running_containerized = false; + +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, + { + .name = "I/O", + .columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command", + .sortKey = "IO_RATE", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); const SignalItem Platform_signals[] = { { .name = " 0 Cancel", .number = 0 }, @@ -338,7 +357,7 @@ void Platform_setMemoryValues(Meter* this) { this->values[3] = pl->cachedMem; this->values[4] = pl->availableMem; - if (lpl->zfs.enabled != 0) { + if (lpl->zfs.enabled != 0 && !Running_containerized) { this->values[0] -= lpl->zfs.size; this->values[3] += lpl->zfs.size; } @@ -712,18 +731,47 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { uint64_t totalFull = 0; uint64_t totalRemain = 0; - struct dirent* dirEntry = NULL; + const struct dirent* dirEntry; while ((dirEntry = readdir(dir))) { const char* entryName = dirEntry->d_name; +#ifdef HAVE_OPENAT + int entryFd = openat(dirfd(dir), entryName, O_DIRECTORY | O_PATH); + if (entryFd < 0) + continue; +#else + char entryFd[4096]; + xSnprintf(entryFd, sizeof(entryFd), SYS_POWERSUPPLY_DIR "/%s", entryName); +#endif + + enum { AC, BAT } type; if (String_startsWith(entryName, "BAT")) { - char buffer[1024] = {0}; - char filePath[256]; - xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/uevent", entryName); + type = BAT; + } else if (String_startsWith(entryName, "AC")) { + type = AC; + } else { + char buffer[32]; + ssize_t ret = xReadfileat(entryFd, "type", buffer, sizeof(buffer)); + if (ret <= 0) + goto next; + + /* drop optional trailing newlines */ + for (char* buf = &buffer[(size_t)ret - 1]; *buf == '\n'; buf--) + *buf = '\0'; + + if (String_eq(buffer, "Battery")) + type = BAT; + else if (String_eq(buffer, "Mains")) + type = AC; + else + goto next; + } - ssize_t r = xReadfile(filePath, buffer, sizeof(buffer)); + if (type == BAT) { + char buffer[1024]; + ssize_t r = xReadfileat(entryFd, "uevent", buffer, sizeof(buffer)); if (r < 0) - continue; + goto next; bool full = false; bool now = false; @@ -765,18 +813,15 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { if (!now && full && !isnan(capacityLevel)) totalRemain += capacityLevel * fullCharge; - } else if (String_startsWith(entryName, "AC")) { - char buffer[2] = {0}; + } else if (type == AC) { if (*isOnAC != AC_ERROR) - continue; - - char filePath[256]; - xSnprintf(filePath, sizeof(filePath), SYS_POWERSUPPLY_DIR "/%s/online", entryName); + goto next; - ssize_t r = xReadfile(filePath, buffer, sizeof(buffer)); + char buffer[2]; + ssize_t r = xReadfileat(entryFd, "online", buffer, sizeof(buffer)); if (r < 1) { *isOnAC = AC_ERROR; - continue; + goto next; } if (buffer[0] == '0') @@ -784,6 +829,9 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { else if (buffer[0] == '1') *isOnAC = AC_PRESENT; } + +next: + Compat_openatArgClose(entryFd); } closedir(dir); @@ -971,6 +1019,30 @@ bool Platform_init(void) { LibSensors_init(); #endif + char target[PATH_MAX]; + ssize_t ret = readlink(PROCDIR "/self/ns/pid", target, sizeof(target) - 1); + if (ret > 0) { + target[ret] = '\0'; + + if (!String_eq("pid:[4026531836]", target)) { // magic constant PROC_PID_INIT_INO from include/linux/proc_ns.h#L46 + Running_containerized = true; + return true; // early return + } + } + + FILE* fd = fopen(PROCDIR "/1/mounts", "r"); + if (fd) { + 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 ")) { + Running_containerized = true; + break; + } + } + fclose(fd); + } // if (fd) + return true; } diff --git a/linux/Platform.h b/linux/Platform.h index 2e2fb3e..f2c314f 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -38,7 +38,9 @@ in the source distribution for its full text. #endif -extern const ProcessField Platform_defaultFields[]; +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; extern const SignalItem Platform_signals[]; diff --git a/netbsd/NetBSDProcess.c b/netbsd/NetBSDProcess.c index 8d2b4c4..a7d59be 100644 --- a/netbsd/NetBSDProcess.c +++ b/netbsd/NetBSDProcess.c @@ -148,6 +148,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { .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", @@ -155,6 +156,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { .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", diff --git a/netbsd/NetBSDProcessList.c b/netbsd/NetBSDProcessList.c index ab0f0b7..197a150 100644 --- a/netbsd/NetBSDProcessList.c +++ b/netbsd/NetBSDProcessList.c @@ -307,7 +307,7 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { } } - if (settings->flags & PROCESS_FLAG_CWD) { + if (settings->ss->flags & PROCESS_FLAG_CWD) { NetBSDProcessList_updateCwd(kproc, proc); } @@ -318,8 +318,11 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { proc->m_virt = kproc->p_vm_vsize; proc->m_resident = kproc->p_vm_rssize; + proc->percent_mem = (proc->m_resident * pageSizeKB) / (double)(this->super.totalMem) * 100.0; proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0, this->super.activeCPUs * 100.0); + Process_updateCPUFieldWidths(proc->percent_cpu); + proc->nlwp = kproc->p_nlwps; proc->nice = kproc->p_nice - 20; proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000)); diff --git a/netbsd/Platform.c b/netbsd/Platform.c index 3b27548..1812ddd 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -66,7 +66,15 @@ in the source distribution for its full text. #define prop_number_signed_value prop_number_integer_value #endif -const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); /* * See /usr/include/sys/signal.h diff --git a/netbsd/Platform.h b/netbsd/Platform.h index e650bcd..3ad51e2 100644 --- a/netbsd/Platform.h +++ b/netbsd/Platform.h @@ -34,7 +34,9 @@ in the source distribution for its full text. #define PLATFORM_LONG_OPTIONS \ // End of list -extern const ProcessField Platform_defaultFields[]; +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; /* see /usr/include/sys/signal.h */ extern const SignalItem Platform_signals[]; diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c index ac3def3..feef7c4 100644 --- a/openbsd/OpenBSDProcess.c +++ b/openbsd/OpenBSDProcess.c @@ -146,6 +146,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { .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", @@ -153,6 +154,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { .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", diff --git a/openbsd/OpenBSDProcessList.c b/openbsd/OpenBSDProcessList.c index af7879e..d070e5e 100644 --- a/openbsd/OpenBSDProcessList.c +++ b/openbsd/OpenBSDProcessList.c @@ -309,7 +309,7 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { OpenBSDProcessList_updateProcessName(this->kd, kproc, proc); - if (settings->flags & PROCESS_FLAG_CWD) { + if (settings->ss->flags & PROCESS_FLAG_CWD) { OpenBSDProcessList_updateCwd(kproc, proc); } @@ -330,8 +330,11 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { fp->addr = kproc->p_addr; proc->m_virt = kproc->p_vm_dsize * pageSizeKB; proc->m_resident = kproc->p_vm_rssize * pageSizeKB; + proc->percent_mem = proc->m_resident / (float)this->super.totalMem * 100.0F; proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0F, this->super.activeCPUs * 100.0F); + Process_updateCPUFieldWidths(proc->percent_cpu); + proc->nice = kproc->p_nice - 20; proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000)); proc->priority = kproc->p_priority - PZERO; diff --git a/openbsd/Platform.c b/openbsd/Platform.c index 15467e1..db9780c 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -46,7 +46,15 @@ in the source distribution for its full text. #include "openbsd/OpenBSDProcessList.h" -const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); /* * See /usr/include/sys/signal.h diff --git a/openbsd/Platform.h b/openbsd/Platform.h index fd6a657..e3d6116 100644 --- a/openbsd/Platform.h +++ b/openbsd/Platform.h @@ -26,7 +26,9 @@ in the source distribution for its full text. #include "generic/uname.h" -extern const ProcessField Platform_defaultFields[]; +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; /* see /usr/include/sys/signal.h */ extern const SignalItem Platform_signals[]; diff --git a/pcp-htop.5.in b/pcp-htop.5.in index de62bfd..0f5cb14 100644 --- a/pcp-htop.5.in +++ b/pcp-htop.5.in @@ -1,4 +1,4 @@ -.TH "PCP-HTOP" "5" "2021" "@PACKAGE_STRING@" "File Formats" +.TH "PCP-HTOP" "5" "2022" "@PACKAGE_STRING@" "File Formats" .SH "NAME" \f3pcp-htop\f1 \- pcp-htop configuration file .SH "DESCRIPTION" diff --git a/pcp/PCPMetric.c b/pcp/PCPMetric.c index f6d4c97..606a5df 100644 --- a/pcp/PCPMetric.c +++ b/pcp/PCPMetric.c @@ -166,7 +166,7 @@ bool PCPMetric_fetch(struct timeval* timestamp) { } int sts, count = 0; do { - sts = pmFetch(pcp->totalMetrics, pcp->fetch, &pcp->result); + sts = pmFetch(pcp->totalMetrics, pcp->fetch, &pcp->result); } while (sts == PM_ERR_IPC && ++count < 3); if (sts < 0) { if (pmDebugOptions.appl0) diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c index e686d51..d0bcfbb 100644 --- a/pcp/PCPProcess.c +++ b/pcp/PCPProcess.c @@ -54,8 +54,8 @@ 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, }, - [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, }, + [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, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c index cae097f..ca82575 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessList.c @@ -384,12 +384,12 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, continue; } - if (settings->flags & PROCESS_FLAG_IO) + if (settings->ss->flags & PROCESS_FLAG_IO) PCPProcessList_updateIO(pp, pid, offset, now); PCPProcessList_updateMemory(pp, pid, offset); - if ((settings->flags & PROCESS_FLAG_LINUX_SMAPS) && + if ((settings->ss->flags & PROCESS_FLAG_LINUX_SMAPS) && (Process_isKernelThread(proc) == false)) { if (PCPMetric_enabled(PCP_PROC_SMAPS_PSS)) PCPProcessList_updateSmaps(pp, pid, offset); @@ -408,6 +408,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, proc->percent_cpu = isnan(percent_cpu) ? 0.0 : CLAMP(percent_cpu, 0.0, pl->activeCPUs * 100.0); proc->percent_mem = proc->m_resident / (double)pl->totalMem * 100.0; + Process_updateCPUFieldWidths(proc->percent_cpu); PCPProcessList_updateUsername(proc, pid, offset, pl->usersTable); @@ -419,22 +420,22 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, PCPProcessList_updateCmdline(proc, pid, offset, command); } - if (settings->flags & PROCESS_FLAG_LINUX_CGROUP) + if (settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP) PCPProcessList_readCGroups(pp, pid, offset); - if (settings->flags & PROCESS_FLAG_LINUX_OOM) + if (settings->ss->flags & PROCESS_FLAG_LINUX_OOM) PCPProcessList_readOomData(pp, pid, offset); - if (settings->flags & PROCESS_FLAG_LINUX_CTXT) + if (settings->ss->flags & PROCESS_FLAG_LINUX_CTXT) PCPProcessList_readCtxtData(pp, pid, offset); - if (settings->flags & PROCESS_FLAG_LINUX_SECATTR) + if (settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR) PCPProcessList_readSecattrData(pp, pid, offset); - if (settings->flags & PROCESS_FLAG_CWD) + if (settings->ss->flags & PROCESS_FLAG_CWD) PCPProcessList_readCwd(pp, pid, offset); - if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) + if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) PCPProcessList_readAutogroup(pp, pid, offset); if (proc->state == ZOMBIE && !proc->cmdline && command[0]) { @@ -676,16 +677,16 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++) PCPMetric_enable(metric, enabled); - flagged = settings->flags & PROCESS_FLAG_LINUX_CGROUP; + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP; PCPMetric_enable(PCP_PROC_CGROUPS, flagged && enabled); - flagged = settings->flags & PROCESS_FLAG_LINUX_OOM; + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_OOM; PCPMetric_enable(PCP_PROC_OOMSCORE, flagged && enabled); - flagged = settings->flags & PROCESS_FLAG_LINUX_CTXT; + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CTXT; PCPMetric_enable(PCP_PROC_VCTXSW, flagged && enabled); PCPMetric_enable(PCP_PROC_NVCTXSW, flagged && enabled); - flagged = settings->flags & PROCESS_FLAG_LINUX_SECATTR; + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR; PCPMetric_enable(PCP_PROC_LABELS, flagged && enabled); - flagged = settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP; + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP; PCPMetric_enable(PCP_PROC_AUTOGROUP_ID, flagged && enabled); PCPMetric_enable(PCP_PROC_AUTOGROUP_NICE, flagged && enabled); diff --git a/pcp/Platform.c b/pcp/Platform.c index 150660a..b3d6ed4 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -54,9 +54,20 @@ in the source distribution for its full text. Platform* pcp; -ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, (int)M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, + { + .name = "I/O", + .columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command", + .sortKey = "IO_RATE", + }, +}; -int Platform_numberOfFields = LAST_PROCESSFIELD; +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); const SignalItem Platform_signals[] = { { .name = " 0 Cancel", .number = 0 }, diff --git a/pcp/Platform.h b/pcp/Platform.h index ad38cbb..dc51c73 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -58,9 +58,9 @@ typedef struct Platform_ { unsigned int ncpu; /* maximum processor count configured */ } Platform; -extern ProcessField Platform_defaultFields[]; +extern const ScreenDefaults Platform_defaultScreens[]; -extern int Platform_numberOfFields; +extern const unsigned int Platform_numberOfDefaultScreens; extern const SignalItem Platform_signals[]; diff --git a/solaris/Platform.c b/solaris/Platform.c index cdc79ae..20b4d13 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -40,6 +40,16 @@ in the source distribution for its full text. #include "SolarisProcessList.h" +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Default", + .columns = "PID LWPID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); + const SignalItem Platform_signals[] = { { .name = " 0 Cancel", .number = 0 }, { .name = " 1 SIGHUP", .number = 1 }, @@ -87,8 +97,6 @@ const SignalItem Platform_signals[] = { const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals); -const ProcessField Platform_defaultFields[] = { PID, LWPID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; - const MeterClass* const Platform_meterTypes[] = { &CPUMeter_class, &ClockMeter_class, diff --git a/solaris/Platform.h b/solaris/Platform.h index b140788..1b3dc9f 100644 --- a/solaris/Platform.h +++ b/solaris/Platform.h @@ -52,12 +52,14 @@ typedef struct envAccum_ { char* env; } envAccum; +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; + extern const SignalItem Platform_signals[]; extern const unsigned int Platform_numberOfSignals; -extern const ProcessField Platform_defaultFields[]; - extern const MeterClass* const Platform_meterTypes[]; bool Platform_init(void); diff --git a/solaris/SolarisProcess.c b/solaris/SolarisProcess.c index b0f6d94..ae8bd70 100644 --- a/solaris/SolarisProcess.c +++ b/solaris/SolarisProcess.c @@ -40,8 +40,8 @@ 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, }, - [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, }, + [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, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, diff --git a/solaris/SolarisProcessList.c b/solaris/SolarisProcessList.c index 71e85fc..0c619ae 100644 --- a/solaris/SolarisProcessList.c +++ b/solaris/SolarisProcessList.c @@ -451,7 +451,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, Process_updateComm(proc, _psinfo->pr_fname); Process_updateCmdline(proc, _psinfo->pr_psargs, 0, 0); - if (proc->settings->flags & PROCESS_FLAG_CWD) { + if (proc->settings->ss->flags & PROCESS_FLAG_CWD) { SolarisProcessList_updateCwd(_psinfo->pr_pid, proc); } } @@ -463,8 +463,11 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, proc->tgid = (_psinfo->pr_ppid * 1024); sproc->realppid = _psinfo->pr_ppid; sproc->realtgid = _psinfo->pr_ppid; + // See note above (in common section) about this BINARY FRACTION 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; if (!preExisting) { // Tasks done only for NEW processes proc->isUserlandThread = false; @@ -492,6 +495,8 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, proc->show = !(pl->settings->hideKernelThreads && proc->isKernelThread); } else { // We are not in the master LWP, so jump to the LWP handling code 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; if (!preExisting) { // Tasks done only for NEW LWPs proc->isUserlandThread = true; diff --git a/unsupported/Platform.c b/unsupported/Platform.c index e55de4a..33d7c41 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -27,14 +27,22 @@ in the source distribution for its full text. #include "UptimeMeter.h" +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); + const SignalItem Platform_signals[] = { { .name = " 0 Cancel", .number = 0 }, }; const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals); -const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; - const MeterClass* const Platform_meterTypes[] = { &CPUMeter_class, &ClockMeter_class, diff --git a/unsupported/Platform.h b/unsupported/Platform.h index 7f4ad9a..5c874d4 100644 --- a/unsupported/Platform.h +++ b/unsupported/Platform.h @@ -20,12 +20,14 @@ in the source distribution for its full text. #include "unsupported/UnsupportedProcess.h" +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; + extern const SignalItem Platform_signals[]; extern const unsigned int Platform_numberOfSignals; -extern const ProcessField Platform_defaultFields[]; - extern const MeterClass* const Platform_meterTypes[]; bool Platform_init(void); diff --git a/unsupported/UnsupportedProcess.c b/unsupported/UnsupportedProcess.c index b1f63c6..5e27fe9 100644 --- a/unsupported/UnsupportedProcess.c +++ b/unsupported/UnsupportedProcess.c @@ -35,8 +35,8 @@ 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, }, - [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, }, + [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, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, @@ -100,6 +100,5 @@ const ProcessClass UnsupportedProcess_class = { .compare = Process_compare }, .writeField = UnsupportedProcess_writeField, - .getCommandStr = NULL, .compareByKey = UnsupportedProcess_compareByKey }; diff --git a/unsupported/UnsupportedProcessList.c b/unsupported/UnsupportedProcessList.c index b64de41..5291797 100644 --- a/unsupported/UnsupportedProcessList.c +++ b/unsupported/UnsupportedProcessList.c @@ -51,7 +51,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { Process_updateCmdline(proc, "", 0, 0); Process_updateExe(proc, "/path/to/executable"); - if (proc->settings->flags & PROCESS_FLAG_CWD) { + if (proc->settings->ss->flags & PROCESS_FLAG_CWD) { free_and_xStrdup(&proc->procCwd, "/current/working/directory"); } @@ -70,6 +70,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { proc->percent_cpu = 2.5; proc->percent_mem = 2.5; + Process_updateCPUFieldWidths(proc->percent_cpu); proc->st_uid = 0; proc->user = "nobody"; /* Update whenever proc->st_uid is changed */ -- cgit v1.2.3