aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Lange <DLange@git.local>2022-05-02 16:04:21 +0200
committerDaniel Lange <DLange@git.local>2022-05-02 16:04:21 +0200
commit1b805a31720727008b32b1129a167758519fd4db (patch)
tree900c84e10a25f2c8eaeec3ae54f1365688ce02a4
parenta6822e98434cf7da6fab033898094976d881ee0f (diff)
downloaddebian_htop-1b805a31720727008b32b1129a167758519fd4db.tar.gz
debian_htop-1b805a31720727008b32b1129a167758519fd4db.tar.bz2
debian_htop-1b805a31720727008b32b1129a167758519fd4db.zip
New upstream version 3.2.0upstream/3.2.0
-rw-r--r--.github/workflows/ci.yml8
-rw-r--r--Action.c85
-rw-r--r--Action.h2
-rw-r--r--CRT.c53
-rw-r--r--CRT.h13
-rw-r--r--CategoriesPanel.c12
-rw-r--r--ChangeLog34
-rw-r--r--ColumnsPanel.c30
-rw-r--r--ColumnsPanel.h7
-rw-r--r--CommandLine.c27
-rw-r--r--DiskIOMeter.c44
-rw-r--r--DisplayOptionsPanel.c7
-rw-r--r--FunctionBar.c11
-rw-r--r--FunctionBar.h4
-rw-r--r--Hashtable.c10
-rw-r--r--Header.c8
-rw-r--r--HeaderLayout.h1
-rw-r--r--IncSet.c41
-rw-r--r--IncSet.h3
-rw-r--r--InfoScreen.c16
-rw-r--r--ListItem.c14
-rw-r--r--ListItem.h8
-rw-r--r--MainPanel.c27
-rw-r--r--MainPanel.h3
-rw-r--r--Makefile.am2
-rw-r--r--Meter.c10
-rw-r--r--Meter.h7
-rw-r--r--NetworkIOMeter.c43
-rw-r--r--OpenFilesScreen.c10
-rw-r--r--Panel.c22
-rw-r--r--Panel.h10
-rw-r--r--Process.c98
-rw-r--r--Process.h24
-rw-r--r--ProcessList.c388
-rw-r--r--ProcessList.h12
-rw-r--r--README10
-rw-r--r--ScreenManager.c97
-rw-r--r--ScreenManager.h5
-rw-r--r--ScreensPanel.c327
-rw-r--r--ScreensPanel.h53
-rw-r--r--Settings.c347
-rw-r--r--Settings.h49
-rw-r--r--SignalsPanel.c5
-rw-r--r--SignalsPanel.h6
-rw-r--r--TraceScreen.c5
-rw-r--r--XUtils.c54
-rw-r--r--XUtils.h4
-rw-r--r--configure.ac36
-rw-r--r--darwin/DarwinProcess.c48
-rw-r--r--darwin/DarwinProcess.h2
-rw-r--r--darwin/Platform.c10
-rw-r--r--darwin/Platform.h4
-rw-r--r--darwin/PlatformHelpers.h4
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.c4
-rw-r--r--dragonflybsd/DragonFlyBSDProcessList.c3
-rw-r--r--dragonflybsd/Platform.c9
-rw-r--r--dragonflybsd/Platform.h4
-rw-r--r--dragonflybsd/ProcessField.h4
-rw-r--r--freebsd/FreeBSDProcess.c11
-rw-r--r--freebsd/FreeBSDProcess.h1
-rw-r--r--freebsd/FreeBSDProcessList.c8
-rw-r--r--freebsd/Platform.c9
-rw-r--r--freebsd/Platform.h4
-rw-r--r--freebsd/ProcessField.h5
-rw-r--r--generic/uname.c2
-rw-r--r--htop.1.in25
-rw-r--r--linux/CGroupUtils.c26
-rw-r--r--linux/LinuxProcess.c22
-rw-r--r--linux/LinuxProcessList.c83
-rw-r--r--linux/Platform.c104
-rw-r--r--linux/Platform.h4
-rw-r--r--netbsd/NetBSDProcess.c2
-rw-r--r--netbsd/NetBSDProcessList.c5
-rw-r--r--netbsd/Platform.c10
-rw-r--r--netbsd/Platform.h4
-rw-r--r--openbsd/OpenBSDProcess.c2
-rw-r--r--openbsd/OpenBSDProcessList.c5
-rw-r--r--openbsd/Platform.c10
-rw-r--r--openbsd/Platform.h4
-rw-r--r--pcp-htop.5.in2
-rw-r--r--pcp/PCPMetric.c2
-rw-r--r--pcp/PCPProcess.c4
-rw-r--r--pcp/PCPProcessList.c27
-rw-r--r--pcp/Platform.c15
-rw-r--r--pcp/Platform.h4
-rw-r--r--solaris/Platform.c12
-rw-r--r--solaris/Platform.h6
-rw-r--r--solaris/SolarisProcess.c4
-rw-r--r--solaris/SolarisProcessList.c7
-rw-r--r--unsupported/Platform.c12
-rw-r--r--unsupported/Platform.h6
-rw-r--r--unsupported/UnsupportedProcess.c5
-rw-r--r--unsupported/UnsupportedProcessList.c3
93 files changed, 1871 insertions, 768 deletions
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 <fcntl.h>
#include <langinfo.h>
#include <signal.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -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 <key> means "tree sorted by key"
// -s <key> 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 <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
@@ -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 <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+#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 <stdbool.h>
+
+#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 <signal.h>
+
#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 <assert.h>
+#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
@@ -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 <stdlib.h>
#include <string.h>
#include <mach/mach.h>
+#include <sys/dirent.h>
#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;
}
@@ -267,6 +275,22 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
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;
}
if (!w(s, '!'))
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, "<unsupported architecture>", 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 */

© 2014-2024 Faster IT GmbH | imprint | privacy policy