44 Commits
3.2.0 ... 3.2.1

Author SHA1 Message Date
4e6ec4a087 Update changelog in preparation for htop-3.2.1 release 2022-06-03 10:54:39 +10:00
abaec509e6 Merge branch 'cmdline-render-cache-rework' of BenBE/htop 2022-06-01 08:00:55 +02:00
f156dfecd5 Fix typo 2022-05-31 22:21:52 +02:00
2999fff88e Refactor code for rendering command line cache
Fixes #1008
2022-05-31 13:55:43 +02:00
2613db4b0d Merge branch 'fix-strip-exe-from-cmdline' of benbe/htop 2022-05-31 09:29:11 +02:00
9eed30949b Restore functionality of stripExeFromCmdline setting
This was accidentally lost in fbec3e4005
2022-05-30 22:38:59 +02:00
ce50095323 Merge branch 'fix-allBranchesCollapsed' of tanriol/htop 2022-05-30 10:37:19 +02:00
17e28d5264 actionExpandOrCollapseAllBranches: NOP in flat mode
This shortcut does not have any visible effect in flat mode, so disable
it completely to avoid possible confusion.
2022-05-30 10:52:12 +03:00
da97d2625a ProcessList_collapseAllBranches: actually build tree
As the loop checks `tree_depth`, a tree build is needed to ensure
they're filled in correctly. Note that this breaks the display list sort
order in case it's non-tree-based (either startup in flat mode, or `*`
hotkey in flat mode), so the display list will need to be sorted again.
2022-05-30 10:52:12 +03:00
7694dbc821 Implement PCP support for minimum ZFS ARC size 2022-05-30 07:50:57 +02:00
c0a9e92eea Implement FreeBSD support for minimum ZFS ARC size 2022-05-30 07:50:57 +02:00
491c6f1044 consider only shrinkable ZFS ARC as cache on Linux 2022-05-30 07:50:57 +02:00
98cbdc6dca Correct PROCESS_MAX_UID_DIGITS constant
The PROCESS_MAX_UID_DIGITS=19 introduced in
696f79fe50 doesn't make sense.
The `uid_t` type does not require to be signed in POSIX. If we are to
support 64 bits as the maximum size of `uid_t`, then
PROCESS_MAX_UID_DIGITS should be 20. (= floor(log10(UINT64_MAX)) + 1).

Signed-off-by: Kang-Che Sung <explorer09@gmail.com>
2022-05-30 07:50:32 +02:00
9ed9d73ab5 Correct titleBuffer size and share it in alignedProcessFieldTitle()
* The size of titleBuffer should be 257 bytes, not 256.

* Remove redundant `static char titleBuffer[]` delarations within
  `alignedProcessFieldTitle()` and let the subroutine use one shared
  buffer for printing field title. This reduces code size.

Signed-off-by: Kang-Che Sung <explorer09@gmail.com>
2022-05-30 07:50:32 +02:00
999801464a Add some headers in the Setup -> Display options panel 2022-05-27 19:47:06 +02:00
0e29174211 Do not scan new processes for deleted libs 2022-05-26 15:38:24 +02:00
WHR
efe09a5e39 Fix process time scaling error on Solaris 2022-05-26 15:31:30 +02:00
038f2ae777 Linux: Increase field width of CPUD% and SWAPD% to 5
Title width of "CPUD%" and "SWAPD%" is 5 and there value cannot go
beyond "100.0%", so increase their field width to 5.

"IOD%" is similar to "MEM%" column, title width is 4 and maximum value
cannot go beyond "100.0%". So in case of "IOD%" column, there is no need
to increase title width to "5". "Process_printPercentage()" function
will handle the maximum value case, it will display value beyond "99.9%"
as "100" instead of "100.0".
2022-05-26 15:03:39 +02:00
0af08bcfc9 Process: Display single digit precision for CPU% greater than 99.9%
Since commit edf319e[1], we're dynamically adjusting column width of
"CPU%", showing single digit precision also for values greater than
"99.9%" makes "CPU%" column consistent with all other values.

[1]: edf319e53d

Change "Process_printPercentage()" function's logic to always display
value (i.e. "val") with single precision. Except when value is greater
than "99.9%" for columns like "MEM%", whose width is fixed to "4" and
value cannot go beyond "100%".

Credits: @Explorer09, thanks for the patch[2] to fix title alignment
         issue.

[2]: https://github.com/htop-dev/htop/pull/959#issuecomment-1092480951

Closes: #957
2022-05-26 15:03:39 +02:00
e053446cbd Fix typo, thx Explorer09 2022-05-21 10:24:42 +02:00
WHR
3d8fa0b926 Mark item separator in default color on help screen
Closes: #1014
2022-05-20 21:54:19 +02:00
d73cc70566 fix typo (dist -> disk)
This was changed in commit 37e01cbe33,
probably unintentional.
2022-05-20 12:58:12 +02:00
37e01cbe33 Colorize process state characters in help screen
Thanks to @Low-power for the idea
Closes #1010
2022-05-20 12:30:37 +02:00
WHR
d22667725a Call mousemask(3X) to truly enable or disable mouse control 2022-05-19 20:23:22 +02:00
ef4cbae5ea Minor code style update 2022-05-19 18:13:46 +02:00
44091705db Use of NULL in execlp() must have a pointer cast.
Signed-off-by: Kang-Che Sung <explorer09@gmail.com>
2022-05-19 18:42:44 +08:00
87793b8555 Merge branch 'lxc-cpu-count-fix' of fasterit/htop 2022-05-17 19:10:40 +02:00
fe7f238e2c Increasing niceness is also disabled by --readonly mode 2022-05-13 09:43:56 +02:00
c24681a078 Fix heap buffer overflow in Vector_compact
Fixes: #1006
2022-05-10 12:12:46 +02:00
2da8f71209 Merge branch 'fix_running_containerized_for_lxc' of ilyam8/htop 2022-05-09 12:21:15 +02:00
51228b6239 fix container detection for LXC 2022-05-08 21:46:53 +03:00
33973f7e40 Limit active CPU count under LXC as well
Fixes the initial htoprc not being configured to amount for the host CPU count
2022-05-07 16:05:11 +02:00
79db69c48d Fix Solaris / OmniOS build 2022-05-06 15:22:08 +02:00
9fc72c1e9c Ensure buffer for environment is large enough on Solaris 2022-05-06 14:35:50 +02:00
db93268968 Ensure buffer for environment is large enough on OpenBSD 2022-05-06 14:35:50 +02:00
4f1269cc9f Ensure buffer for environment is large enough on NetBSD 2022-05-06 14:35:50 +02:00
0388b30077 Hashtable: fix handling of NULL pointer in Hashtable_dump
This fixes an issus in Hashtable_dump where `"(nil"` is passed as an
argument to `%p` in fprintf. This prints the static address of `"(nil)"`
not "(nil)". This commit changes the code to just pass the NULL pointer
to fprintf, which will consistently print "0x0".
2022-05-06 06:34:17 +02:00
4b8b61fe18 ProcessList.h: remove ProcessList_remove
As the "highlight dying processes" option has to keep processes in the
list when they disappear, no code except the cleanup loop in
`ProcessList_scan` should remove processes from the list directly.
Remove the export to prevent random process removals from being
reintroduced accidentally.
2022-05-05 10:00:34 +02:00
fae7ff6f03 LinuxProcessList_recurseProcTree: keep on read error
If a process goes away while reading its fields, but we already have
that process in the list, we should keep it in case the "highlight dying
processes" mode is active. Not only is that expected in this mode, but
this should also ensure parents are in the list when their children are
(wanted for tree mode consistency).
2022-05-05 10:00:34 +02:00
e07fce7014 LinuxProcessList_recurseProcTree: open dirfd first
A process can die between reading the directory listing and opening the
directory FD (if HAVE_OPENAT) or /proc files (otherwise) for reading the
process data. This race would cause LinuxProcessList_recurseProcTree to
remove it from the list immediately, which is unexpected in the
"highlight dying processes" mode and can break the tree structure.
This patch closes this race in the HAVE_OPENAT case by only accessing
the process entry after the directory FD has been opened.
2022-05-05 10:00:34 +02:00
e08eec813c Remove redundant sscanf calls (in (s)scanf a blank validates _zero_ or more whitespace)
man sscanf(3):
A sequence of white-space characters (space, tab, newline, etc.; see isspace(3)).
This directive matches any amount of white space, including none, in the input.
2022-05-05 09:34:25 +02:00
549fcb6bb8 Always abort on overflow in String_cat
Not only in debug mode.
2022-05-05 09:19:14 +02:00
08166b27b1 ProcessList: fix quadratic process removal when scanning
This commit changes ProcessList_scan to lazily remove Processes by
index, which is known, instead of performing a brute-force search by
pid and immediately reclaiming the lost vector space via compaction.

Searching by pid is potentially quadratic in ProcessList_scan because
the process we are searching for is always at the back of the vector
(the scan starts from the back of the vector). Additionally, removal
via Vector_remove immediately reclaims space (by sliding elements
down).

With these changes process removal in ProcessList_scan is now linear.

Changes:
  * ProcessList: add new ProcessList_removeIndex function to remove
    by index
  * Vector: add Vector_softRemove and Vector_compact functions to
    support lazy removal/deletion of entries Vector_softRemove
    Vector_compact
  * Vector: replace Vector_count with Vector_countEquals since it only
    used for consistency assertions.
2022-05-05 09:17:51 +02:00
0d53245cf9 LXC: Limit CPU count to what is given in /proc/cpuinfo despite the container seeing the real host CPUs 2022-05-04 18:21:41 +02:00
51 changed files with 508 additions and 212 deletions

View File

@ -88,6 +88,7 @@ static void Action_runSetup(State* st) {
ScreenManager_run(scr, NULL, NULL, "Setup");
ScreenManager_delete(scr);
if (st->settings->changed) {
CRT_setMouse(st->settings->enableMouse);
Header_writeBackToSettings(st->header);
}
}
@ -241,6 +242,9 @@ static Htop_Reaction actionToggleTreeView(State* st) {
static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) {
ScreenSettings* ss = st->settings->ss;
if (!ss->treeView) {
return HTOP_OK;
}
ss->allBranchesCollapsed = !ss->allBranchesCollapsed;
if (ss->allBranchesCollapsed)
ProcessList_collapseAllBranches(st->pl);
@ -533,7 +537,7 @@ static const struct {
{ .key = " U: ", .roInactive = false, .info = "untag all processes" },
{ .key = " F9 k: ", .roInactive = true, .info = "kill process/tagged processes" },
{ .key = " F7 ]: ", .roInactive = true, .info = "higher priority (root only)" },
{ .key = " F8 [: ", .roInactive = false, .info = "lower priority (+ nice)" },
{ .key = " F8 [: ", .roInactive = true, .info = "lower priority (+ nice)" },
#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
{ .key = " a: ", .roInactive = true, .info = "set CPU affinity" },
#endif
@ -570,46 +574,57 @@ static Htop_Reaction actionHelp(State* st) {
line++;
mvaddstr(line++, 0, "CPU usage bar: ");
#define addbartext(attr, prefix, text) \
do { \
addattrstr(CRT_colors[DEFAULT_COLOR], prefix); \
addattrstr(attr, text); \
} while(0)
addattrstr(CRT_colors[BAR_BORDER], "[");
addbartext(CRT_colors[CPU_NICE_TEXT], "", "low");
addbartext(CRT_colors[CPU_NORMAL], "/", "normal");
addbartext(CRT_colors[CPU_SYSTEM], "/", "kernel");
if (st->settings->detailedCPUTime) {
addattrstr(CRT_colors[CPU_NICE_TEXT], "low"); addstr("/");
addattrstr(CRT_colors[CPU_NORMAL], "normal"); addstr("/");
addattrstr(CRT_colors[CPU_SYSTEM], "kernel"); addstr("/");
addattrstr(CRT_colors[CPU_IRQ], "irq"); addstr("/");
addattrstr(CRT_colors[CPU_SOFTIRQ], "soft-irq"); addstr("/");
addattrstr(CRT_colors[CPU_STEAL], "steal"); addstr("/");
addattrstr(CRT_colors[CPU_GUEST], "guest"); addstr("/");
addattrstr(CRT_colors[CPU_IOWAIT], "io-wait");
addattrstr(CRT_colors[BAR_SHADOW], " used%");
addbartext(CRT_colors[CPU_IRQ], "/", "irq");
addbartext(CRT_colors[CPU_SOFTIRQ], "/", "soft-irq");
addbartext(CRT_colors[CPU_STEAL], "/", "steal");
addbartext(CRT_colors[CPU_GUEST], "/", "guest");
addbartext(CRT_colors[CPU_IOWAIT], "/", "io-wait");
addbartext(CRT_colors[BAR_SHADOW], " ", "used%");
} else {
addattrstr(CRT_colors[CPU_NICE_TEXT], "low-priority"); addstr("/");
addattrstr(CRT_colors[CPU_NORMAL], "normal"); addstr("/");
addattrstr(CRT_colors[CPU_SYSTEM], "kernel"); addstr("/");
addattrstr(CRT_colors[CPU_GUEST], "virtualized");
addattrstr(CRT_colors[BAR_SHADOW], " used%");
addbartext(CRT_colors[CPU_GUEST], "/", "guest");
addbartext(CRT_colors[BAR_SHADOW], " ", "used%");
}
addattrstr(CRT_colors[BAR_BORDER], "]");
attrset(CRT_colors[DEFAULT_COLOR]);
mvaddstr(line++, 0, "Memory bar: ");
addattrstr(CRT_colors[BAR_BORDER], "[");
addattrstr(CRT_colors[MEMORY_USED], "used"); addstr("/");
addattrstr(CRT_colors[MEMORY_BUFFERS_TEXT], "buffers"); addstr("/");
addattrstr(CRT_colors[MEMORY_SHARED], "shared"); addstr("/");
addattrstr(CRT_colors[MEMORY_CACHE], "cache");
addattrstr(CRT_colors[BAR_SHADOW], " used/total");
addbartext(CRT_colors[MEMORY_USED], "", "used");
addbartext(CRT_colors[MEMORY_BUFFERS_TEXT], "/", "buffers");
addbartext(CRT_colors[MEMORY_SHARED], "/", "shared");
addbartext(CRT_colors[MEMORY_CACHE], "/", "cache");
addbartext(CRT_colors[BAR_SHADOW], " ", "used");
addbartext(CRT_colors[BAR_SHADOW], "/", "total");
addattrstr(CRT_colors[BAR_BORDER], "]");
attrset(CRT_colors[DEFAULT_COLOR]);
mvaddstr(line++, 0, "Swap bar: ");
addattrstr(CRT_colors[BAR_BORDER], "[");
addattrstr(CRT_colors[SWAP], "used");
addbartext(CRT_colors[SWAP], "", "used");
#ifdef HTOP_LINUX
addstr("/");
addattrstr(CRT_colors[SWAP_CACHE], "cache");
addattrstr(CRT_colors[BAR_SHADOW], " used/total");
addbartext(CRT_colors[SWAP_CACHE], "/", "cache");
#else
addattrstr(CRT_colors[BAR_SHADOW], " used/total");
addbartext(CRT_colors[SWAP_CACHE], " ", "");
#endif
addbartext(CRT_colors[BAR_SHADOW], " ", "used");
addbartext(CRT_colors[BAR_SHADOW], "/", "total");
addattrstr(CRT_colors[BAR_BORDER], "]");
line++;
#undef addbartext
attrset(CRT_colors[DEFAULT_COLOR]);
mvaddstr(line++, 0, "Type and layout of header meters are configurable in the setup screen.");
if (CRT_colorScheme == COLORSCHEME_MONOCHROME) {
@ -617,9 +632,23 @@ static Htop_Reaction actionHelp(State* st) {
}
line++;
mvaddstr(line++, 0, "Process state: R: running; S: sleeping; T: traced/stopped; Z: zombie; D: disk sleep");
#define addattrstatestr(attr, state, desc) \
do { \
addattrstr(attr, state); \
addattrstr(CRT_colors[DEFAULT_COLOR], ": " desc); \
} while(0)
line++;
mvaddstr(line, 0, "Process state: ");
addattrstatestr(CRT_colors[PROCESS_RUN_STATE], "R", "running; ");
addattrstatestr(CRT_colors[PROCESS_SHADOW], "S", "sleeping; ");
addattrstatestr(CRT_colors[PROCESS_RUN_STATE], "t", "traced/stopped; ");
addattrstatestr(CRT_colors[PROCESS_D_STATE], "Z", "zombie; ");
addattrstatestr(CRT_colors[PROCESS_D_STATE], "D", "disk sleep");
attrset(CRT_colors[DEFAULT_COLOR]);
#undef addattrstatestr
line += 2;
const bool readonly = Settings_isReadonly();

View File

@ -79,6 +79,7 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
}
if (update) {
this->settings->changed = true;
this->settings->lastUpdate++;
Header_calculateHeight(header);
Header_updateData(header);
Header_draw(header);

22
CRT.c
View File

@ -900,6 +900,20 @@ void CRT_resetSignalHandlers(void) {
signal(SIGQUIT, SIG_DFL);
}
void CRT_setMouse(bool enabled) {
#ifdef HAVE_GETMOUSE
if (enabled) {
#if NCURSES_MOUSE_VERSION > 1
mousemask(BUTTON1_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);
#else
mousemask(BUTTON1_RELEASED, NULL);
#endif
} else {
mousemask(0, NULL);
}
#endif
}
void CRT_init(const Settings* settings, bool allowUnicode) {
redirectStderr();
@ -993,13 +1007,7 @@ IGNORE_WCASTQUAL_END
#endif
CRT_treeStrAscii;
#ifdef HAVE_GETMOUSE
#if NCURSES_MOUSE_VERSION > 1
mousemask(BUTTON1_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);
#else
mousemask(BUTTON1_RELEASED, NULL);
#endif
#endif
CRT_setMouse(settings->enableMouse);
CRT_degreeSign = initDegreeSign();
}

2
CRT.h
View File

@ -185,6 +185,8 @@ extern int CRT_scrollWheelVAmount;
extern ColorScheme CRT_colorScheme;
void CRT_setMouse(bool enabled);
void CRT_init(const Settings* settings, bool allowUnicode);
void CRT_done(void);

View File

@ -1,3 +1,23 @@
What's new in version 3.2.1
* Fix setting to show all branches collapsed by default
* Restore functionality of stripExeFromCmdline setting
* Fix some command line display settings not being honored without restart
* Display single digit precision for CPU% greater than 99.9%
* On Linux, FreeBSD and PCP consider only shrinkable ZFS ARC as cache
* On Linux, increase field width of CPUD% and SWAPD% columns
* Colorize process state characters in help screen
* Use mousemask(3X) to enable and disable mouse control
* Fix heap buffer overflow in Vector_compact
* On Solaris, fix a process time scaling error
* On Solaris, fix the build
* On NetBSD, OpenBSD and Solaris ensure env buffer size is sufficient
* On Linux, resolve processes exiting interfering with sampling
* Fix ProcessList quadratic removal when scanning processes
* Under LXC, limit CPU count to that given by /proc/cpuinfo
* Improve container detection for LXC
* Some minor documentation fixes
What's new in version 3.2.0
* Support for displaying multiple tabs in the user interface

View File

@ -68,6 +68,7 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) {
this->settings->colorScheme = mark;
this->settings->changed = true;
this->settings->lastUpdate++;
CRT_setColors(mark);
clear();

View File

@ -18,6 +18,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "OptionItem.h"
#include "ProvideCurses.h"
#include "ScreensPanel.h"
static const char* const DisplayOptionsFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL};
@ -43,6 +44,8 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {
case KEY_RECLICK:
case ' ':
switch (OptionItem_kind(selected)) {
case OPTION_ITEM_TEXT:
break;
case OPTION_ITEM_CHECK:
CheckItem_toggle((CheckItem*)selected);
result = HANDLED;
@ -69,6 +72,7 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {
if (result == HANDLED) {
this->settings->changed = true;
this->settings->lastUpdate++;
Header* header = this->scr->header;
Header_calculateHeight(header);
Header_reinit(header);
@ -97,9 +101,17 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
this->scr = scr;
Panel_setHeader(super, "Display options");
Panel_add(super, (Object*) CheckItem_newByRef("Tree view (for the current Screen tab)", &(settings->ss->treeView)));
#define TABMSG "For current screen tab: \0"
char tabheader[sizeof(TABMSG) + SCREEN_NAME_LEN + 1] = TABMSG;
strncat(tabheader, settings->ss->name, SCREEN_NAME_LEN);
Panel_add(super, (Object*) TextItem_new(tabheader));
#undef TABMSG
Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->ss->treeView)));
Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->ss->treeViewAlwaysByPID)));
Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->ss->allBranchesCollapsed)));
Panel_add(super, (Object*) TextItem_new("Global options:"));
Panel_add(super, (Object*) CheckItem_newByRef("Show tabs for screens", &(settings->screenTabs)));
Panel_add(super, (Object*) CheckItem_newByRef("Shadow other users' processes", &(settings->shadowOtherUsers)));
Panel_add(super, (Object*) CheckItem_newByRef("Hide kernel threads", &(settings->hideKernelThreads)));

View File

@ -52,7 +52,7 @@ static void Hashtable_dump(const Hashtable* this) {
i,
this->buckets[i].key,
this->buckets[i].probe,
this->buckets[i].value ? (const void*)this->buckets[i].value : "(nil)");
this->buckets[i].value);
if (this->buckets[i].value)
items++;

View File

@ -52,6 +52,7 @@ static HandlerResult HeaderOptionsPanel_eventHandler(Panel* super, int ch) {
Header_setLayout(this->scr->header, mark);
this->settings->changed = true;
this->settings->lastUpdate++;
ScreenManager_resize(this->scr);

View File

@ -184,6 +184,7 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {
if (result == HANDLED || sideMove) {
Header* header = this->scr->header;
this->settings->changed = true;
this->settings->lastUpdate++;
Header_calculateHeight(header);
ScreenManager_resize(this->scr);
}

View File

@ -120,7 +120,9 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
close(fdnull);
char buffer[32] = {0};
xSnprintf(buffer, sizeof(buffer), "%d", pid);
execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", NULL);
// Use of NULL in variadic functions must have a pointer cast.
// The NULL constant is not required by standard to have a pointer type.
execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char *)NULL);
exit(127);
}
close(fdpair[1]);

View File

@ -25,6 +25,13 @@ static void OptionItem_delete(Object* cast) {
free(this);
}
static void TextItem_display(const Object* cast, RichString* out) {
const TextItem* this = (const TextItem*)cast;
assert (this != NULL);
RichString_appendWide(out, CRT_colors[HELP_BOLD], this->super.text);
}
static void CheckItem_display(const Object* cast, RichString* out) {
const CheckItem* this = (const CheckItem*)cast;
assert (this != NULL);
@ -68,6 +75,16 @@ const OptionItemClass OptionItem_class = {
}
};
const OptionItemClass TextItem_class = {
.super = {
.extends = Class(OptionItem),
.delete = OptionItem_delete,
.display = TextItem_display
},
.kind = OPTION_ITEM_TEXT
};
const OptionItemClass CheckItem_class = {
.super = {
.extends = Class(OptionItem),
@ -77,6 +94,7 @@ const OptionItemClass CheckItem_class = {
.kind = OPTION_ITEM_CHECK
};
const OptionItemClass NumberItem_class = {
.super = {
.extends = Class(OptionItem),
@ -86,6 +104,12 @@ const OptionItemClass NumberItem_class = {
.kind = OPTION_ITEM_NUMBER
};
TextItem* TextItem_new(const char* text) {
TextItem* this = AllocThis(TextItem);
this->super.text = xStrdup(text);
return this;
}
CheckItem* CheckItem_newByRef(const char* text, bool* ref) {
CheckItem* this = AllocThis(CheckItem);
this->super.text = xStrdup(text);

View File

@ -13,6 +13,7 @@ in the source distribution for its full text.
enum OptionItemType {
OPTION_ITEM_TEXT,
OPTION_ITEM_CHECK,
OPTION_ITEM_NUMBER,
};
@ -32,6 +33,12 @@ typedef struct OptionItem_ {
char* text;
} OptionItem;
typedef struct TextItem_ {
OptionItem super;
char* text;
} TextItem;
typedef struct CheckItem_ {
OptionItem super;
@ -51,9 +58,12 @@ typedef struct NumberItem_ {
} NumberItem;
extern const OptionItemClass OptionItem_class;
extern const OptionItemClass TextItem_class;
extern const OptionItemClass CheckItem_class;
extern const OptionItemClass NumberItem_class;
TextItem* TextItem_new(const char* text);
CheckItem* CheckItem_newByRef(const char* text, bool* ref);
CheckItem* CheckItem_newByVal(const char* text, bool value);
bool CheckItem_get(const CheckItem* this);

101
Process.c
View File

@ -414,6 +414,8 @@ void Process_makeCommandStr(Process* this) {
bool stripExeFromCmdline = settings->stripExeFromCmdline;
bool showThreadNames = settings->showThreadNames;
uint64_t settingsStamp = settings->lastUpdate;
/* Nothing to do to (Re)Generate the Command string, if the process is:
* - a kernel thread, or
* - a zombie from before being under htop's watch, or
@ -422,52 +424,27 @@ void Process_makeCommandStr(Process* this) {
return;
if (this->state == ZOMBIE && !this->mergedCommand.str)
return;
if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames) && (mc->prevMergeSet == showMergedCommand))
return;
/* this->mergedCommand.str needs updating only if its state or contents changed.
* Its content is based on the fields cmdline, comm, and exe. */
if (
mc->prevMergeSet == showMergedCommand &&
mc->prevPathSet == showProgramPath &&
mc->prevCommSet == searchCommInCmdline &&
mc->prevCmdlineSet == stripExeFromCmdline &&
mc->prevShowThreadNames == showThreadNames &&
!mc->cmdlineChanged &&
!mc->commChanged &&
!mc->exeChanged
) {
if (mc->lastUpdate >= settingsStamp)
return;
}
mc->lastUpdate = settingsStamp;
/* The field separtor "│" has been chosen such that it will not match any
* valid string used for searching or filtering */
const char* SEPARATOR = CRT_treeStr[TREE_STR_VERT];
const int SEPARATOR_LEN = strlen(SEPARATOR);
/* Check for any changed fields since we last built this string */
if (mc->cmdlineChanged || mc->commChanged || mc->exeChanged) {
free(mc->str);
/* Accommodate the column text, two field separators and terminating NUL */
size_t maxLen = 2 * SEPARATOR_LEN + 1;
maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)");
maxLen += this->procComm ? strlen(this->procComm) : 0;
maxLen += this->procExe ? strlen(this->procExe) : 0;
/* Accommodate the column text, two field separators and terminating NUL */
size_t maxLen = 2 * SEPARATOR_LEN + 1;
maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)");
maxLen += this->procComm ? strlen(this->procComm) : 0;
maxLen += this->procExe ? strlen(this->procExe) : 0;
mc->str = xCalloc(1, maxLen);
}
/* Preserve the settings used in this run */
mc->prevMergeSet = showMergedCommand;
mc->prevPathSet = showProgramPath;
mc->prevCommSet = searchCommInCmdline;
mc->prevCmdlineSet = stripExeFromCmdline;
mc->prevShowThreadNames = showThreadNames;
/* Mark everything as unchanged */
mc->cmdlineChanged = false;
mc->commChanged = false;
mc->exeChanged = false;
free(mc->str);
mc->str = xCalloc(1, maxLen);
/* Reset all locations that need extra handling when actually displaying */
mc->highlightCount = 0;
@ -601,11 +578,15 @@ void Process_makeCommandStr(Process* this) {
}
if (matchLen) {
/* strip the matched exe prefix */
cmdline += matchLen;
if (stripExeFromCmdline) {
/* strip the matched exe prefix */
cmdline += matchLen;
commStart -= matchLen;
commEnd -= matchLen;
commStart -= matchLen;
commEnd -= matchLen;
} else {
matchLen = 0;
}
}
if (!matchLen || (haveCommField && *cmdline)) {
@ -739,17 +720,20 @@ void Process_printLeftAlignedField(RichString* str, int attr, const char* conten
void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr) {
if (val >= 0) {
if (val < 99.9F) {
if (val < 0.05F) {
*attr = CRT_colors[PROCESS_SHADOW];
}
xSnprintf(buffer, n, "%*.1f ", width, val);
} else {
if (val < 0.05F)
*attr = CRT_colors[PROCESS_SHADOW];
else if (val >= 99.9F)
*attr = CRT_colors[PROCESS_MEGABYTES];
if (val < 100.0F)
val = 100.0F; // Don't round down and display "val" as "99".
xSnprintf(buffer, n, "%*.0f ", width, val);
int precision = 1;
// Display "val" as "100" for columns like "MEM%".
if (width == 4 && val > 99.9F) {
precision = 0;
val = 100.0F;
}
xSnprintf(buffer, n, "%*.*f ", width, precision, val);
} else {
*attr = CRT_colors[PROCESS_SHADOW];
xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
@ -1095,13 +1079,6 @@ bool Process_sendSignal(Process* this, Arg sgn) {
return kill(this->pid, sgn.i) == 0;
}
int Process_pidCompare(const void* v1, const void* v2) {
const Process* p1 = (const Process*)v1;
const Process* p2 = (const Process*)v2;
return SPACESHIP_NUMBER(p1->pid, p2->pid);
}
int Process_compare(const void* v1, const void* v2) {
const Process* p1 = (const Process*)v1;
const Process* p2 = (const Process*)v2;
@ -1204,7 +1181,8 @@ void Process_updateComm(Process* this, const char* comm) {
free(this->procComm);
this->procComm = comm ? xStrdup(comm) : NULL;
this->mergedCommand.commChanged = true;
this->mergedCommand.lastUpdate = 0;
}
static int skipPotentialPath(const char* cmdline, int end) {
@ -1244,7 +1222,8 @@ void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart
this->cmdline = cmdline ? xStrdup(cmdline) : NULL;
this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);
this->cmdlineBasenameEnd = basenameEnd;
this->mergedCommand.cmdlineChanged = true;
this->mergedCommand.lastUpdate = 0;
}
void Process_updateExe(Process* this, const char* exe) {
@ -1263,7 +1242,8 @@ void Process_updateExe(Process* this, const char* exe) {
this->procExe = NULL;
this->procExeBasenameOffset = 0;
}
this->mergedCommand.exeChanged = true;
this->mergedCommand.lastUpdate = 0;
}
uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 };
@ -1287,13 +1267,14 @@ void Process_updateFieldWidth(ProcessField key, size_t width) {
}
void Process_updateCPUFieldWidths(float percentage) {
if (percentage < 99.9) {
if (percentage < 99.9F) {
Process_updateFieldWidth(PERCENT_CPU, 4);
Process_updateFieldWidth(PERCENT_NORM_CPU, 4);
return;
}
uint8_t width = ceil(log10(percentage + .2));
// Add additional two characters, one for "." and another for precision.
uint8_t width = ceil(log10(percentage + 0.1)) + 2;
Process_updateFieldWidth(PERCENT_CPU, width);
Process_updateFieldWidth(PERCENT_NORM_CPU, width);

View File

@ -96,17 +96,10 @@ typedef struct ProcessCmdlineHighlight_ {
* Process_writeCommand to color the string. str will be NULL for kernel
* threads and zombies */
typedef struct ProcessMergedCommand_ {
uint64_t lastUpdate; /* Marker based on settings->lastUpdate to track when the rendering needs refreshing */
char* str; /* merged Command string */
size_t highlightCount; /* how many portions of cmdline to highlight */
ProcessCmdlineHighlight highlights[8]; /* which portions of cmdline to highlight */
bool cmdlineChanged : 1; /* whether cmdline changed */
bool exeChanged : 1; /* whether exe changed */
bool commChanged : 1; /* whether comm changed */
bool prevMergeSet : 1; /* whether showMergedCommand was set */
bool prevPathSet : 1; /* whether showProgramPath was set */
bool prevCommSet : 1; /* whether findCommInCmdline was set */
bool prevCmdlineSet : 1; /* whether stripExeFromCmdline was set */
bool prevShowThreadNames : 1; /* whether showThreadNames was set */
} ProcessMergedCommand;
typedef struct Process_ {
@ -293,7 +286,7 @@ extern uint8_t Process_fieldWidths[LAST_PROCESSFIELD];
#define PROCESS_MIN_PID_DIGITS 5
#define PROCESS_MAX_PID_DIGITS 19
#define PROCESS_MIN_UID_DIGITS 5
#define PROCESS_MAX_UID_DIGITS 19
#define PROCESS_MAX_UID_DIGITS 20
extern int Process_pidDigits;
extern int Process_uidDigits;
@ -394,7 +387,11 @@ bool Process_changePriorityBy(Process* this, Arg delta);
bool Process_sendSignal(Process* this, Arg sgn);
int Process_pidCompare(const void* v1, const void* v2);
static inline int Process_pidEqualCompare(const void* v1, const void* v2) {
const pid_t p1 = ((const Process*)v1)->pid;
const pid_t p2 = ((const Process*)v2)->pid;
return p1 != p2; /* return zero when equal */
}
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key);

View File

@ -82,41 +82,45 @@ void ProcessList_setPanel(ProcessList* this, Panel* panel) {
this->panel = panel;
}
static const char* alignedDynamicColumnTitle(const ProcessList* this, int key) {
static const char* alignedDynamicColumnTitle(const ProcessList* this, int key, char* titleBuffer, size_t titleBufferSize) {
const DynamicColumn* column = Hashtable_get(this->dynamicColumns, key);
if (column == NULL)
return "- ";
static char titleBuffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1];
int width = column->width;
if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s", width, column->heading);
xSnprintf(titleBuffer, titleBufferSize, "%*s", width, column->heading);
return titleBuffer;
}
static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessField field) {
static char titleBuffer[UINT8_MAX + sizeof(" ")];
assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" "));
assert(sizeof(titleBuffer) >= PROCESS_MAX_PID_DIGITS + sizeof(" "));
assert(sizeof(titleBuffer) >= PROCESS_MAX_UID_DIGITS + sizeof(" "));
if (field >= LAST_PROCESSFIELD)
return alignedDynamicColumnTitle(this, field);
return alignedDynamicColumnTitle(this, field, titleBuffer, sizeof(titleBuffer));
const char* title = Process_fields[field].title;
if (!title)
return "- ";
if (Process_fields[field].pidColumn) {
static char titleBuffer[PROCESS_MAX_PID_DIGITS + sizeof(" ")];
xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title);
return titleBuffer;
}
if (field == ST_UID) {
static char titleBuffer[PROCESS_MAX_UID_DIGITS + sizeof(" ")];
xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_uidDigits, title);
return titleBuffer;
}
if (Process_fields[field].autoWidth) {
static char titleBuffer[UINT8_MAX + 1];
xSnprintf(titleBuffer, sizeof(titleBuffer), "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title);
if (field == PERCENT_CPU)
xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_fieldWidths[field], title);
else
xSnprintf(titleBuffer, sizeof(titleBuffer), "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title);
return titleBuffer;
}
@ -158,7 +162,7 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header) {
}
void ProcessList_add(ProcessList* this, Process* p) {
assert(Vector_indexOf(this->processes, p, Process_pidCompare) == -1);
assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) == -1);
assert(Hashtable_get(this->processTable, p->pid) == NULL);
p->processList = this;
@ -168,25 +172,23 @@ void ProcessList_add(ProcessList* this, Process* p) {
Vector_add(this->processes, p);
Hashtable_put(this->processTable, p->pid, p);
assert(Vector_indexOf(this->processes, p, Process_pidCompare) != -1);
assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) != -1);
assert(Hashtable_get(this->processTable, p->pid) != NULL);
assert(Hashtable_count(this->processTable) == Vector_count(this->processes));
assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable)));
}
void ProcessList_remove(ProcessList* this, const Process* p) {
assert(Vector_indexOf(this->processes, p, Process_pidCompare) != -1);
assert(Hashtable_get(this->processTable, p->pid) != NULL);
const Process* pp = Hashtable_remove(this->processTable, p->pid);
assert(pp == p); (void)pp;
// ProcessList_removeIndex removes Process p from the list's map and soft deletes
// it from its vector. Vector_compact *must* be called once the caller is done
// removing items.
// Should only be called from ProcessList_scan to avoid breaking dying process highlighting.
static void ProcessList_removeIndex(ProcessList* this, const Process* p, int idx) {
pid_t pid = p->pid;
int idx = Vector_indexOf(this->processes, p, Process_pidCompare);
assert(idx != -1);
if (idx >= 0) {
Vector_remove(this->processes, idx);
}
assert(p == (Process*)Vector_get(this->processes, idx));
assert(Hashtable_get(this->processTable, pid) != NULL);
Hashtable_remove(this->processTable, pid);
Vector_softRemove(this->processes, idx);
if (this->following != -1 && this->following == pid) {
this->following = -1;
@ -194,7 +196,7 @@ void ProcessList_remove(ProcessList* this, const Process* p) {
}
assert(Hashtable_get(this->processTable, pid) == NULL);
assert(Hashtable_count(this->processTable) == Vector_count(this->processes));
assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable)));
}
static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, bool show) {
@ -355,7 +357,10 @@ void ProcessList_expandTree(ProcessList* this) {
}
}
// Called on collapse-all toggle and on startup, possibly in non-tree mode
void ProcessList_collapseAllBranches(ProcessList* this) {
ProcessList_buildTree(this); // Update `tree_depth` fields of the processes
this->needsSort = true; // ProcessList is sorted by parent now, force new sort
int size = Vector_size(this->processes);
for (int i = 0; i < size; i++) {
Process* process = (Process*) Vector_get(this->processes, i);
@ -429,7 +434,7 @@ Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting,
Process* proc = (Process*) Hashtable_get(this->processTable, pid);
*preExisting = proc != NULL;
if (proc) {
assert(Vector_indexOf(this->processes, proc, Process_pidCompare) != -1);
assert(Vector_indexOf(this->processes, proc, Process_pidEqualCompare) != -1);
assert(proc->pid == pid);
} else {
proc = constructor(this->settings);
@ -484,7 +489,7 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
if (p->tombStampMs > 0) {
// remove tombed process
if (this->monotonicMs >= p->tombStampMs) {
ProcessList_remove(this, p);
ProcessList_removeIndex(this, p, i);
}
} else if (p->updated == false) {
// process no longer exists
@ -493,11 +498,14 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
p->tombStampMs = this->monotonicMs + 1000 * this->settings->highlightDelaySecs;
} else {
// immediately remove
ProcessList_remove(this, p);
ProcessList_removeIndex(this, p, i);
}
}
}
// Compact the processes vector in case of any deletions
Vector_compact(this->processes);
// Set UID column width based on max UID.
Process_setUidColumnWidth(maxUid);
}

View File

@ -106,8 +106,6 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header);
void ProcessList_add(ProcessList* this, Process* p);
void ProcessList_remove(ProcessList* this, const Process* p);
void ProcessList_updateDisplayList(ProcessList* this);
ProcessField ProcessList_keyAt(const ProcessList* this, int at);

View File

@ -315,6 +315,7 @@ void ScreensPanel_update(Panel* super) {
ScreensPanel* this = (ScreensPanel*) super;
int size = Panel_size(super);
this->settings->changed = true;
this->settings->lastUpdate++;
this->settings->screens = xReallocArray(this->settings->screens, size + 1, sizeof(ScreenSettings*));
for (int i = 0; i < size; i++) {
ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);

View File

@ -762,6 +762,8 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
this->ssIndex = 0;
this->ss = this->screens[this->ssIndex];
this->lastUpdate = 1;
return this;
}

View File

@ -96,6 +96,7 @@ typedef struct Settings_ {
#endif
bool changed;
uint64_t lastUpdate;
} Settings;
#define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu))

View File

@ -6,8 +6,8 @@ in the source distribution for its full text.
*/
#include "SignalsPanel.h"
// the above contains #include <signal.h> so do not add that here again (breaks Solaris build)
#include <signal.h>
#include <stdbool.h>
#include "FunctionBar.h"

View File

@ -7,7 +7,11 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#include "config.h" // IWYU pragma: keep
#ifndef HTOP_SOLARIS
#include <signal.h>
#endif
#include "Panel.h"

View File

@ -90,7 +90,9 @@ bool TraceScreen_forkTracer(TraceScreen* this) {
char buffer[32] = {0};
xSnprintf(buffer, sizeof(buffer), "%d", this->super.process->pid);
execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, NULL);
// Use of NULL in variadic functions must have a pointer cast.
// The NULL constant is not required by standard to have a pointer type.
execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (char *)NULL);
// Should never reach here, unless execlp fails ...
const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH.";

View File

@ -29,6 +29,8 @@ Vector* Vector_new(const ObjectClass* type, bool owner, int size) {
this->items = 0;
this->type = type;
this->owner = owner;
this->dirty_index = -1;
this->dirty_count = 0;
return this;
}
@ -44,10 +46,21 @@ void Vector_delete(Vector* this) {
free(this);
}
static inline bool Vector_isDirty(const Vector* this) {
if (this->dirty_count > 0) {
assert(0 <= this->dirty_index && this->dirty_index < this->items);
assert(this->dirty_count <= this->items);
return true;
}
assert(this->dirty_index == -1);
return false;
}
#ifndef NDEBUG
static bool Vector_isConsistent(const Vector* this) {
assert(this->items <= this->arraySize);
assert(!Vector_isDirty(this));
if (this->owner) {
for (int i = 0; i < this->items; i++) {
@ -60,15 +73,14 @@ static bool Vector_isConsistent(const Vector* this) {
return true;
}
unsigned int Vector_count(const Vector* this) {
unsigned int items = 0;
bool Vector_countEquals(const Vector* this, unsigned int expectedCount) {
unsigned int n = 0;
for (int i = 0; i < this->items; i++) {
if (this->array[i]) {
items++;
n++;
}
}
assert(items == (unsigned int)this->items);
return items;
return n == expectedCount;
}
Object* Vector_get(const Vector* this, int idx) {
@ -88,13 +100,16 @@ int Vector_size(const Vector* this) {
void Vector_prune(Vector* this) {
assert(Vector_isConsistent(this));
if (this->owner) {
for (int i = 0; i < this->items; i++)
for (int i = 0; i < this->items; i++) {
if (this->array[i]) {
Object_delete(this->array[i]);
//this->array[i] = NULL;
this->array[i] = NULL;
}
}
}
this->items = 0;
this->dirty_index = -1;
this->dirty_count = 0;
}
//static int comparisons = 0;
@ -242,6 +257,58 @@ Object* Vector_remove(Vector* this, int idx) {
}
}
Object* Vector_softRemove(Vector* this, int idx) {
assert(idx >= 0 && idx < this->items);
Object* removed = this->array[idx];
assert(removed);
if (removed) {
this->array[idx] = NULL;
this->dirty_count++;
if (this->dirty_index < 0 || idx < this->dirty_index) {
this->dirty_index = idx;
}
if (this->owner) {
Object_delete(removed);
return NULL;
}
}
return removed;
}
void Vector_compact(Vector* this) {
if (!Vector_isDirty(this)) {
return;
}
const int size = this->items;
assert(0 <= this->dirty_index && this->dirty_index < size);
assert(this->array[this->dirty_index] == NULL);
int idx = this->dirty_index;
/* one deletion: use memmove, which should be faster */
if (this->dirty_count == 1) {
memmove(&this->array[idx], &this->array[idx + 1], (this->items - idx - 1) * sizeof(this->array[0]));
} else {
/* multiple deletions */
for (int i = idx + 1; i < size; i++) {
if (this->array[i]) {
this->array[idx++] = this->array[i];
}
}
}
this->items -= this->dirty_count;
this->dirty_index = -1;
this->dirty_count = 0;
assert(Vector_isConsistent(this));
}
void Vector_moveUp(Vector* this, int idx) {
assert(idx >= 0 && idx < this->items);
assert(Vector_isConsistent(this));

View File

@ -22,6 +22,11 @@ typedef struct Vector_ {
int arraySize;
int growthRate;
int items;
/* lowest index of a pending soft remove/delete operation,
used to speed up compaction */
int dirty_index;
/* count of soft deletes, required for Vector_count to work in debug mode */
int dirty_count;
bool owner;
} Vector;
@ -44,6 +49,15 @@ Object* Vector_take(Vector* this, int idx);
Object* Vector_remove(Vector* this, int idx);
/* Vector_softRemove marks the item at index idx for deletion without
reclaiming any space. If owned, the item is immediately freed.
Vector_compact must be called to reclaim space.*/
Object* Vector_softRemove(Vector* this, int idx);
/* Vector_compact reclaims space free'd up by Vector_softRemove, if any. */
void Vector_compact(Vector* this);
void Vector_moveUp(Vector* this, int idx);
void Vector_moveDown(Vector* this, int idx);
@ -54,7 +68,11 @@ void Vector_set(Vector* this, int idx, void* data_);
Object* Vector_get(const Vector* this, int idx);
int Vector_size(const Vector* this);
unsigned int Vector_count(const Vector* this);
/* Vector_countEquals returns true if the number of non-NULL items
in the Vector is equal to expectedCount. This is only for debugging
and consistency checks. */
bool Vector_countEquals(const Vector* this, unsigned int expectedCount);
#else /* NDEBUG */

View File

@ -115,7 +115,9 @@ inline bool String_contains_i(const char* s1, const char* s2, bool multi) {
char* String_cat(const char* s1, const char* s2) {
const size_t l1 = strlen(s1);
const size_t l2 = strlen(s2);
assert(SIZE_MAX - l1 > l2);
if (SIZE_MAX - l1 <= l2) {
fail();
}
char* out = xMalloc(l1 + l2 + 1);
memcpy(out, s1, l1);
memcpy(out + l1, s2, l2);

View File

@ -6,7 +6,7 @@
# ----------------------------------------------------------------------
AC_PREREQ([2.69])
AC_INIT([htop], [3.2.0], [htop@groups.io], [], [https://htop.dev/])
AC_INIT([htop], [3.2.1], [htop@groups.io], [], [https://htop.dev/])
AC_CONFIG_SRCDIR([htop.c])
AC_CONFIG_AUX_DIR([build-aux])

View File

@ -39,7 +39,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },

View File

@ -219,7 +219,7 @@ The primary user documentation should be the man file which you can find in `hto
Additional documentation, like this file, should be written in gh-style markdown.
Make each sentence one line.
Markdown will combined these in output formats.
Markdown will combine these in output formats.
It does only insert a paragraph if you insert a blank line into the source file.
This way git can better diff and present the changes when documentation is altered.

View File

@ -38,7 +38,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },

View File

@ -37,7 +37,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },

View File

@ -227,6 +227,7 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
void Platform_setMemoryValues(Meter* this) {
const ProcessList* pl = this->pl;
const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) pl;
this->total = pl->totalMem;
this->values[0] = pl->usedMem;
@ -234,6 +235,16 @@ void Platform_setMemoryValues(Meter* this) {
// this->values[2] = "shared memory, like tmpfs and shm"
this->values[3] = pl->cachedMem;
// this->values[4] = "available memory"
if (fpl->zfs.enabled) {
// ZFS does not shrink below the value of zfs_arc_min.
unsigned long long int shrinkableSize = 0;
if (fpl->zfs.size > fpl->zfs.min)
shrinkableSize = fpl->zfs.size - fpl->zfs.min;
this->values[0] -= shrinkableSize;
this->values[3] += shrinkableSize;
// this->values[4] += shrinkableSize;
}
}
void Platform_setSwapValues(Meter* this) {

View File

@ -15,6 +15,7 @@ in the source distribution for its full text.
static int MIB_kstat_zfs_misc_arcstats_size[5];
static int MIB_kstat_zfs_misc_arcstats_c_min[5];
static int MIB_kstat_zfs_misc_arcstats_c_max[5];
static int MIB_kstat_zfs_misc_arcstats_mfu_size[5];
static int MIB_kstat_zfs_misc_arcstats_mru_size[5];
@ -35,6 +36,7 @@ void openzfs_sysctl_init(ZfsArcStats* stats) {
len = 5;
sysctlnametomib("kstat.zfs.misc.arcstats.size", MIB_kstat_zfs_misc_arcstats_size, &len);
sysctlnametomib("kstat.zfs.misc.arcstats.c_min", MIB_kstat_zfs_misc_arcstats_c_min, &len);
sysctlnametomib("kstat.zfs.misc.arcstats.c_max", MIB_kstat_zfs_misc_arcstats_c_max, &len);
sysctlnametomib("kstat.zfs.misc.arcstats.mfu_size", MIB_kstat_zfs_misc_arcstats_mfu_size, &len);
sysctlnametomib("kstat.zfs.misc.arcstats.mru_size", MIB_kstat_zfs_misc_arcstats_mru_size, &len);
@ -61,6 +63,10 @@ void openzfs_sysctl_updateArcStats(ZfsArcStats* stats) {
sysctl(MIB_kstat_zfs_misc_arcstats_size, 5, &(stats->size), &len, NULL, 0);
stats->size /= 1024;
len = sizeof(stats->min);
sysctl(MIB_kstat_zfs_misc_arcstats_c_min, 5, &(stats->min), &len, NULL, 0);
stats->min /= 1024;
len = sizeof(stats->max);
sysctl(MIB_kstat_zfs_misc_arcstats_c_max, 5, &(stats->max), &len, NULL, 0);
stats->max /= 1024;

View File

@ -57,7 +57,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, },
[M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@ -86,9 +86,9 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, },
[IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, },
#ifdef HAVE_DELAYACCT
[PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD%", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
[PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
[PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWPD%", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
[PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
[PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
[PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWPD% ", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
#endif
[M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
[M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
@ -270,9 +270,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
break;
}
#ifdef HAVE_DELAYACCT
case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 4, &attr); break;
case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 4, &attr); break;
case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 4, &attr); break;
case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break;
case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break;
case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break;
#endif
case CTXT:
if (lp->ctxt_diff > 1000) {

View File

@ -166,6 +166,28 @@ static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) {
#endif
static unsigned int scanAvailableCPUsFromCPUinfo(LinuxProcessList* this) {
FILE* file = fopen(PROCCPUINFOFILE, "r");
if (file == NULL)
return this->super.existingCPUs;
unsigned int availableCPUs = 0;
while (!feof(file)) {
char buffer[PROC_LINE_LENGTH];
if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
break;
if (String_startsWith(buffer, "processor"))
availableCPUs++;
}
fclose(file);
return availableCPUs ? availableCPUs : 1;
}
static void LinuxProcessList_updateCPUcount(ProcessList* super) {
/* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF
* https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD
@ -240,6 +262,12 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) {
if (existing < 1)
return;
if (Running_containerized) {
/* LXC munges /proc/cpuinfo but not the /sys/devices/system/cpu/ files,
* so limit the visible CPUs to what the guest has been configured to see: */
currExisting = active = scanAvailableCPUsFromCPUinfo(this);
}
#ifdef HAVE_SENSORS_SENSORS_H
/* When started with offline CPUs, libsensors does not monitor those,
* even when they become online. */
@ -248,7 +276,7 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) {
#endif
super->activeCPUs = active;
assert(existing == currExisting);
assert(Running_containerized || (existing == currExisting));
super->existingCPUs = currExisting;
}
@ -1323,7 +1351,8 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc
if (process->procExeDeleted)
filename[filenameLen - markerLen] = '\0';
process->mergedCommand.exeChanged |= oldExeDeleted ^ process->procExeDeleted;
if (oldExeDeleted != process->procExeDeleted)
process->mergedCommand.lastUpdate = 0;
}
Process_updateExe(process, filename);
@ -1389,6 +1418,21 @@ static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned lo
return out;
}
static bool isOlderThan(const ProcessList* pl, const Process* proc, unsigned int seconds) {
assert(pl->realtimeMs > 0);
/* Starttime might not yet be parsed */
if (proc->starttime_ctime <= 0)
return false;
uint64_t realtime = pl->realtimeMs / 1000;
if (realtime < (uint64_t)proc->starttime_ctime)
return false;
return realtime - proc->starttime_ctime > seconds;
}
static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const char* dirname, const Process* parent, double period) {
ProcessList* pl = (ProcessList*) this;
const struct dirent* entry;
@ -1446,6 +1490,15 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
if (parent && pid == parent->pid)
continue;
#ifdef HAVE_OPENAT
int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
if (procFd < 0)
continue;
#else
char procFd[4096];
xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name);
#endif
bool preExisting;
Process* proc = ProcessList_getProcess(pl, pid, &preExisting, LinuxProcess_new);
LinuxProcess* lp = (LinuxProcess*) proc;
@ -1453,15 +1506,6 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
proc->tgid = parent ? parent->pid : pid;
proc->isUserlandThread = proc->pid != proc->tgid;
#ifdef HAVE_OPENAT
int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
if (procFd < 0)
goto errorReadingProcess;
#else
char procFd[4096];
xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name);
#endif
LinuxProcessList_recurseProcTree(this, procFd, "task", proc, period);
/*
@ -1497,7 +1541,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
bool prev = proc->usesDeletedLib;
if (!proc->isKernelThread && !proc->isUserlandThread &&
((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted))) {
((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(pl, proc, 10)))) {
// Check if we really should recalculate the M_LRS value for this process
uint64_t passedTimeInMs = pl->realtimeMs - lp->last_mlrs_calctime;
@ -1514,7 +1558,8 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
lp->m_lrs = (proc->isUserlandThread && parent) ? ((const LinuxProcess*)parent)->m_lrs : 0;
}
proc->mergedCommand.exeChanged |= prev ^ proc->usesDeletedLib;
if (prev != proc->usesDeletedLib)
proc->mergedCommand.lastUpdate = 0;
}
if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
@ -1653,8 +1698,13 @@ errorReadingProcess:
#endif
if (preExisting) {
ProcessList_remove(pl, proc);
/*
* The only real reason for coming here (apart from Linux violating the /proc API)
* would be the process going away with its /proc files disappearing (!HAVE_OPENAT).
* However, we want to keep in the process list for now for the "highlight dying" mode.
*/
} else {
/* A really short-lived process that we don't have full info about */
Process_delete((Object*)proc);
}
}
@ -1874,6 +1924,7 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
switch (buffer[0]) {
case 'c':
tryRead("c_min", &lpl->zfs.min);
tryRead("c_max", &lpl->zfs.max);
tryReadFlag("compressed_size", &lpl->zfs.compressed, lpl->zfs.isCompressed);
break;
@ -1908,6 +1959,7 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
lpl->zfs.enabled = (lpl->zfs.size > 0 ? 1 : 0);
lpl->zfs.size /= 1024;
lpl->zfs.min /= 1024;
lpl->zfs.max /= 1024;
lpl->zfs.MFU /= 1024;
lpl->zfs.MRU /= 1024;
@ -2101,16 +2153,11 @@ static void scanCPUFrequencyFromCPUinfo(LinuxProcessList* this) {
if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
break;
if (
(sscanf(buffer, "processor : %d", &cpuid) == 1) ||
(sscanf(buffer, "processor: %d", &cpuid) == 1)
) {
if (sscanf(buffer, "processor : %d", &cpuid) == 1) {
continue;
} else if (
(sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
(sscanf(buffer, "cpu MHz: %lf", &frequency) == 1) ||
(sscanf(buffer, "clock : %lfMHz", &frequency) == 1) ||
(sscanf(buffer, "clock: %lfMHz", &frequency) == 1)
(sscanf(buffer, "clock : %lfMHz", &frequency) == 1)
) {
if (cpuid < 0 || (unsigned int)cpuid > (existingCPUs - 1)) {
continue;

View File

@ -358,8 +358,13 @@ void Platform_setMemoryValues(Meter* this) {
this->values[4] = pl->availableMem;
if (lpl->zfs.enabled != 0 && !Running_containerized) {
this->values[0] -= lpl->zfs.size;
this->values[3] += lpl->zfs.size;
// ZFS does not shrink below the value of zfs_arc_min.
unsigned long long int shrinkableSize = 0;
if (lpl->zfs.size > lpl->zfs.min)
shrinkableSize = lpl->zfs.size - lpl->zfs.min;
this->values[0] -= shrinkableSize;
this->values[3] += shrinkableSize;
this->values[4] += shrinkableSize;
}
}
@ -1035,7 +1040,7 @@ bool Platform_init(void) {
char lineBuffer[256];
while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
// detect lxc or overlayfs and guess that this means we are running containerized
if (String_startsWith(lineBuffer, "lxcfs ") || String_startsWith(lineBuffer, "overlay ")) {
if (String_startsWith(lineBuffer, "lxcfs /proc") || String_startsWith(lineBuffer, "overlay ")) {
Running_containerized = true;
break;
}

View File

@ -51,6 +51,8 @@ extern const MeterClass* const Platform_meterTypes[];
bool Platform_init(void);
void Platform_done(void);
extern bool Running_containerized;
void Platform_setBindings(Htop_Action* keys);
int Platform_getUptime(void);

View File

@ -219,15 +219,18 @@ static void updateViaExec(void) {
exit(1);
dup2(fdnull, STDERR_FILENO);
close(fdnull);
execlp("systemctl",
"systemctl",
"show",
"--property=SystemState",
"--property=NFailedUnits",
"--property=NNames",
"--property=NJobs",
"--property=NInstalledJobs",
NULL);
// Use of NULL in variadic functions must have a pointer cast.
// The NULL constant is not required by standard to have a pointer type.
execlp(
"systemctl",
"systemctl",
"show",
"--property=SystemState",
"--property=NFailedUnits",
"--property=NNames",
"--property=NJobs",
"--property=NInstalledJobs",
(char *)NULL);
exit(127);
}
close(fdpair[1]);

View File

@ -144,7 +144,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
},
[PERCENT_CPU] = {
.name = "PERCENT_CPU",
.title = "CPU% ",
.title = " CPU%",
.description = "Percentage of the CPU time the process used in the last sampling",
.flags = 0,
.defaultSortDesc = true,

View File

@ -311,7 +311,13 @@ char* Platform_getProcessEnv(pid_t pid) {
for (char** p = ptr; *p; p++) {
size_t len = strlen(*p) + 1;
if (size + len > capacity) {
while (size + len > capacity) {
if (capacity > (SIZE_MAX / 2)) {
free(env);
env = NULL;
goto end;
}
capacity *= 2;
env = xRealloc(env, capacity);
}
@ -327,6 +333,7 @@ char* Platform_getProcessEnv(pid_t pid) {
env[size + 1] = 0;
}
end:
(void) kvm_close(kt);
return env;
}

View File

@ -142,7 +142,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
},
[PERCENT_CPU] = {
.name = "PERCENT_CPU",
.title = "CPU% ",
.title = " CPU%",
.description = "Percentage of the CPU time the process used in the last sampling",
.flags = 0,
.defaultSortDesc = true,

View File

@ -269,7 +269,13 @@ char* Platform_getProcessEnv(pid_t pid) {
for (char** p = ptr; *p; p++) {
size_t len = strlen(*p) + 1;
if (size + len > capacity) {
while (size + len > capacity) {
if (capacity > (SIZE_MAX / 2)) {
free(env);
env = NULL;
goto end;
}
capacity *= 2;
env = xRealloc(env, capacity);
}
@ -285,6 +291,7 @@ char* Platform_getProcessEnv(pid_t pid) {
env[size + 1] = 0;
}
end:
(void) kvm_close(kt);
return env;
}

View File

@ -81,6 +81,7 @@ typedef enum PCPMetric_ {
PCP_ZFS_ARC_BONUS_SIZE, /* zfs.arc.bonus_size */
PCP_ZFS_ARC_COMPRESSED_SIZE, /* zfs.arc.compressed_size */
PCP_ZFS_ARC_UNCOMPRESSED_SIZE, /* zfs.arc.uncompressed_size */
PCP_ZFS_ARC_C_MIN, /* zfs.arc.c_min */
PCP_ZFS_ARC_C_MAX, /* zfs.arc.c_max */
PCP_ZFS_ARC_DBUF_SIZE, /* zfs.arc.dbuf_size */
PCP_ZFS_ARC_DNODE_SIZE, /* zfs.arc.dnode_size */

View File

@ -54,7 +54,7 @@ const ProcessFieldData Process_fields[] = {
[M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, },
[M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@ -74,7 +74,7 @@ const ProcessFieldData Process_fields[] = {
[CGROUP] = { .name = "CGROUP", .title = " CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, },
[OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, },
[PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = 0, .defaultSortDesc = true, },
[PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, },
[PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, },
[PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWAPD% ", .description = "Swapin delay %", .flags = 0, .defaultSortDesc = true, },
[M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it.", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
[M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },

View File

@ -601,6 +601,8 @@ static inline void PCPProcessList_scanZfsArcstats(PCPProcessList* this) {
memset(&this->zfs, 0, sizeof(ZfsArcStats));
if (PCPMetric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64))
this->zfs.anon = value.ull / ONE_K;
if (PCPMetric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64))
this->zfs.min = value.ull / ONE_K;
if (PCPMetric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64))
this->zfs.max = value.ull / ONE_K;
if (PCPMetric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64))

View File

@ -178,6 +178,7 @@ static const char* Platform_metricNames[] = {
[PCP_ZFS_ARC_BONUS_SIZE] = "zfs.arc.bonus_size",
[PCP_ZFS_ARC_COMPRESSED_SIZE] = "zfs.arc.compressed_size",
[PCP_ZFS_ARC_UNCOMPRESSED_SIZE] = "zfs.arc.uncompressed_size",
[PCP_ZFS_ARC_C_MIN] = "zfs.arc.c_min",
[PCP_ZFS_ARC_C_MAX] = "zfs.arc.c_max",
[PCP_ZFS_ARC_DBUF_SIZE] = "zfs.arc.dbuf_size",
[PCP_ZFS_ARC_DNODE_SIZE] = "zfs.arc.dnode_size",
@ -510,8 +511,13 @@ void Platform_setMemoryValues(Meter* this) {
this->values[4] = pl->availableMem;
if (ppl->zfs.enabled != 0) {
this->values[0] -= ppl->zfs.size;
this->values[3] += ppl->zfs.size;
// ZFS does not shrink below the value of zfs_arc_min.
unsigned long long int shrinkableSize = 0;
if (ppl->zfs.size > ppl->zfs.min)
shrinkableSize = ppl->zfs.size - ppl->zfs.min;
this->values[0] -= shrinkableSize;
this->values[3] += shrinkableSize;
this->values[4] += shrinkableSize;
}
}

View File

@ -267,16 +267,21 @@ static int Platform_buildenv(void* accum, struct ps_prochandle* Phandle, uintptr
envAccum* accump = accum;
(void) Phandle;
(void) addr;
size_t thissz = strlen(str);
if ((thissz + 2) > (accump->capacity - accump->size)) {
accump->env = xRealloc(accump->env, accump->capacity *= 2);
while ((thissz + 2) > (accump->capacity - accump->size)) {
if (accump->capacity > (SIZE_MAX / 2))
return 1;
accump->capacity *= 2;
accump->env = xRealloc(accump->env, accump->capacity);
}
if ((thissz + 2) > (accump->capacity - accump->size)) {
return 1;
}
strlcpy( accump->env + accump->size, str, (accump->capacity - accump->size));
strlcpy( accump->env + accump->size, str, accump->capacity - accump->size);
strncpy( accump->env + accump->size + thissz + 1, "\n", 2);
accump->size = accump->size + thissz + 1;
accump->size += thissz + 1;
return 0;
}
@ -299,7 +304,8 @@ char* Platform_getProcessEnv(pid_t pid) {
Prelease(Phandle, 0);
strncpy( envBuilder.env + envBuilder.size, "\0", 1);
return envBuilder.env;
return xRealloc(envBuilder.env, envBuilder.size + 1);
}
char* Platform_getInodeFilename(pid_t pid, ino_t inode) {

View File

@ -40,7 +40,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },

View File

@ -468,7 +468,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo,
proc->percent_cpu = ((uint16_t)_psinfo->pr_pctcpu / (double)32768) * (double)100.0;
Process_updateCPUFieldWidths(proc->percent_cpu);
proc->time = _psinfo->pr_time.tv_sec;
proc->time = _psinfo->pr_time.tv_sec * 100 + _psinfo->pr_time.tv_nsec / 10000000;
if (!preExisting) { // Tasks done only for NEW processes
proc->isUserlandThread = false;
proc->starttime_ctime = _psinfo->pr_start.tv_sec;
@ -497,7 +497,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo,
proc->percent_cpu = ((uint16_t)_lwpsinfo->pr_pctcpu / (double)32768) * (double)100.0;
Process_updateCPUFieldWidths(proc->percent_cpu);
proc->time = _lwpsinfo->pr_time.tv_sec;
proc->time = _lwpsinfo->pr_time.tv_sec * 100 + _lwpsinfo->pr_time.tv_nsec / 10000000;
if (!preExisting) { // Tasks done only for NEW LWPs
proc->isUserlandThread = true;
proc->ppid = _psinfo->pr_pid * 1024;

View File

@ -35,7 +35,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },

View File

@ -10,6 +10,7 @@ in the source distribution for its full text.
typedef struct ZfsArcStats_ {
int enabled;
int isCompressed;
unsigned long long int min;
unsigned long long int max;
unsigned long long int size;
unsigned long long int MFU;