155 Commits
3.1.2 ... main

Author SHA1 Message Date
eebd5e8517 add rpi model 3b+ to tested list 2022-06-10 20:19:39 +03:00
7044d546ea rename ui names for cpu freq/temp 2022-06-10 20:14:06 +03:00
765e3de1e7 fix temp meter duplication 2022-06-10 20:12:16 +03:00
637550ed51 disclaimer 2022-06-07 20:14:57 +03:00
fc016ae780 new screenshot 2022-06-07 22:36:05 +03:00
36260b5814 CPU Temperature and Frequency meter for Linux platform 2022-06-07 22:16:47 +03:00
611ea4606f Prevent null pointer dereference on early error
If a fatal error occurs before CRT_init has been called, CRT_done
dereferences NULL in CRT_colors.

Simply check if CRT_crashSettings is not NULL, which is eventually
set when CRT_init has been called.

If it is not set, then do not try to disable ncurses.

Proof of Concept (on Linux):

$ ./configure --with-proc=/var/empty
$ make
$ ./htop
2022-06-03 20:11:24 +02:00
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
c7413fd677 Merge branch 'natoscott-changelog-3.2.0' 2022-05-01 16:31:20 +10:00
8f0475cd73 Merge branch 'changelog-3.2.0' of https://github.com/natoscott/htop into natoscott-changelog-3.2.0 2022-05-01 16:31:11 +10:00
a155fd0f8b Merge branch 'natoscott-coverity-scan' 2022-05-01 16:30:51 +10:00
549543f8e4 Merge branch 'coverity-scan' of https://github.com/natoscott/htop into natoscott-coverity-scan 2022-05-01 16:29:55 +10:00
10b541b5e4 Update Settings_newScreen with single-line sortKey checking.
Co-authored-by: BenBE <BenBE@geshi.org>
2022-05-01 16:21:13 +10:00
73f08debe0 Add note that the Tree view setting is per Screen tab now 2022-04-30 19:45:00 +02:00
8b98d3effb Document screen tab switching (TAB, Shift-TAB keys) 2022-04-30 17:06:59 +02:00
7e66ee1d28 FreeBSD: free emulation string 2022-04-30 17:05:59 +02:00
a7a6571d14 Fix typo 2022-04-30 17:03:29 +02:00
cde72dd0b0 Remove redundant null checks on Settings_write (covscan)
Coverity scan reports that there is dead code in Settings_write
checking for nulls that have already been dereferenced on every
code path leading to the check.  This is likely a hangover from
times when the screens pointer was only conditionally allocated
- they're not needed anymore.
2022-04-30 13:55:56 +10:00
cb61865bb9 Add array bounds checking for the Process_fields array (covscan)
Coverity scan reports there may be a code path that would cause
an overrun in the (relatively new) ScreenSettings code where it
evaluates default sort direction.  Add bounds check and default
to descending instead of a potentially invalid array access.
2022-04-30 13:50:25 +10:00
c144bf9ae5 Add changelog entries for pending htop-3.2.0 release, update version 2022-04-29 18:03:53 +10:00
ae518e20b7 Merge branch 'main' of thesamesam/htop 2022-04-26 13:56:40 +02:00
cdf3f3c50b Remove stray fprintf left from testing (introduced in 7039abe) 2022-04-26 13:35:35 +02:00
1f2f4fe891 Assume process just started when kproc->ki_start returns garbage 2022-04-21 08:56:56 +02:00
ec809b7f71 Avoid extremely large year values when printing time 2022-04-21 08:56:56 +02:00
b83ce85d89 Force elapsed time display to zero if process seems started in the future 2022-04-21 08:56:56 +02:00
ee1bf2f917 Process: Fix PID & UID column widths off-by-one error
If the max PID or UID value for a platform is exactly a power of ten
(10000, 100000, etc.) the column widths of PID and UID would be 1 char
less than the correct number of digits. This is caused by the wrong
rounding function (ceil(x)); change to the correct one (trunc(x) + 1).

Signed-off-by: Kang-Che Sung <explorer09@gmail.com>
2022-04-18 12:30:40 +02:00
afc4a9d657 README: Add macOS build dependency install command 2022-04-14 20:28:11 +02:00
3f3691886a Merge branch 'allow-multiple-filters-and-search-strings' into main
Closes #961
2022-04-14 16:45:29 +02:00
99aa906bc5 Merge branch 'improve-filter-label' into main 2022-04-14 16:44:51 +02:00
df955c8991 Make CLANG happy (-Wembedded-directive) 2022-04-07 12:33:22 +02:00
6a7b3fdc7d Do not show help for -M / --no-mouse if HAVE_GETMOUSE is undefined 2022-04-07 11:52:22 +02:00
72c56691ec Fix a compile warning: comparison of unsigned enum expression < 0 is always false
Get this warning when compiling Settings.c on the Mac OS X with clang-800.0.42.1.
Settings.c:447:28: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
         if (this->hLayout < 0 || this->hLayout >= LAST_HEADER_LAYOUT)
             ~~~~~~~~~~~~~ ^ ~

This patch fixes the problem.
2022-04-05 09:23:03 +02:00
4ccad46045 configure.ac: fix static build with hwloc
Retrieve hwloc dependencies through pkg-config to avoid the following
static build failure:

checking for hwloc_get_proc_cpubind in -lhwloc... no
configure: error: can not find required library libhwloc

This build failure is raised because without pkg-config, hwloc
dependencies such as libxml2 are not retrieved:

configure:8999: checking for hwloc_get_proc_cpubind in -lhwloc
configure:9022: /home/autobuild/autobuild/instance-0/output-1/host/bin/powerpc-buildroot-linux-uclibc-gcc -o conftest -D_GNU_SOURCE -I/home/autobuild/autobuild/instance-0/output-1/host/powerpc-buildroot-linux-uclibc/sysroot/usr/bin/../../usr/include -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -Og -g0  -static -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -static conftest.c -lhwloc  -llzma -L/home/autobuild/autobuild/instance-0/output-1/host/powerpc-buildroot-linux-uclibc/sysroot/usr/bin/../../usr/lib -lncurses -lm   >&5
/home/autobuild/autobuild/instance-0/output-1/host/lib/gcc/powerpc-buildroot-linux-uclibc/10.3.0/../../../../powerpc-buildroot-linux-uclibc/bin/ld: /home/autobuild/autobuild/instance-0/output-1/host/powerpc-buildroot-linux-uclibc/sysroot/usr/bin/../../usr/lib/libhwloc.a(topology-xml-libxml.o): in function `hwloc_libxml_free_buffer':
topology-xml-libxml.c:(.text+0x6a): undefined reference to `xmlFree'

Fixes:
 - http://autobuild.buildroot.org/results/5d815ec08c580005a863df6ac9ac29deff7d4128

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2022-04-03 12:59:30 +02:00
7039abe109 Guess lxc or docker from /proc/1/mounts
At the moment this is used to make the memory meter report sane values even
if the host has ZFS and that leaks through into a containerized environment

Fixes #863

Includes a clever check for magic PROC_PID_INIT_INO in /proc/self/ns/pid thanks to Pavel Snajdr (snajpa)
2022-04-02 14:02:06 +02:00
2b7504b522 Merge branch 'full_meter' of cgzones/htop into main
Closes #786
2022-04-02 12:45:07 +02:00
8b927ba596 Merge branch 'main' (Linux: fix crash in LXD, fixes #965) of er-azh/htop into main 2022-04-02 12:38:00 +02:00
0ffd772d28 Fix header layout and meters reset if a header column is empty
Closes #880
Patch from BenBE and cgzones
2022-04-02 12:30:30 +02:00
64fb7181ee use xCalloc for allocating cpuData 2022-03-27 12:23:56 +04:30
ba4c67942c Linux: allocate cpuData before reading cpu count. 2022-03-26 17:27:36 +04:30
3f0c172a60 Linux: fix crash in LXD 2022-03-26 15:48:12 +04:30
7c43e02591 Improve String_contains_i to allow for multiple terms
This enables:
* Multiple filters in the main panel and strace etc. views
* Multiple search terms

The search terms are separated by "|" and are still fixed strings
matched case-insensitive.

Added a multi flag at request of BenBE.
2022-03-25 17:19:59 +01:00
c6f946edd2 Improve MainPanel Label on active inc filter (Filter <-> FILTER) 2022-03-25 14:51:14 +01:00
a2ca7583a9 README: Add Archlinux build dependency install command
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-03-24 15:49:13 +01:00
469ae7a0bd Make sure License is correctly specified as GNU GPLv2+ in some more file headers 2022-03-14 07:33:40 +01:00
c4239335b9 Skip system slice name
This shortens paths like /system.slice/system-postgres.slice/postgres@12-main.service to /[S]/postgres@12-main.
Without this some cgroup names for getty processes explode in length.
2022-03-06 19:56:25 +01:00
23b56193d7 Reduce column width spam by snapd 2022-03-06 19:56:25 +01:00
edf319e53d Auto-size (normalized) CPU usage columns 2022-03-06 19:56:25 +01:00
b6c0667eae Linux: dynamically adjust column width of CGROUP C(ompressed)CGROUP 2022-03-06 19:56:25 +01:00
3ba695293c Linux: dynamically adjust the SECATTR column width
SELinux contexts can be quite long; adjust the column width dynamically
at each cycle to the longest value.

Also with the recent addition of multiple screens, over-long columns can
be moved into their own screen.
2022-03-06 19:56:25 +01:00
6133cac721 Process: Handle rounding ambiguity between 99.9 and 100.0
Depending upon default behavior of the compiler and floating-point
environment, compiler could round down the value between "99.9" and
"100.0" to "99.0", instead of rounding it to the nearest value, "100.0".

Note: The floating-point environment access and modification is only
meaningful when "#pragma STD FENV_ACCESS" is set to "ON"[1]. Otherwise
implementation is free to assume that floating-point control modes are
always the default. So it would be a good idea to address the rounding
ambiguity between "99.9" and "100.0" to become compiler agnostic.

[1]: https://en.cppreference.com/w/c/numeric/fenv

Credits: @Explorer09, thanks for the suggestion.
2022-02-19 12:21:26 +01:00
da653f8148 Process: Show only integer value when CPU% more than 99.9%
When we run a process which utilizes CPU between 100.0% and 999.9%, htop
shows an unnecessary decimal character at the end of the value. For
example, '100.x' and '247.x' become '100.' and '247.' respectively.

When CPU utilization is less than and equal to '99.9%', show the result
with single digit precision and if result is less than four characters,
pad it with the blank space. When CPU utilization is greater than
'99.9%', show only integral part of the result and if it's less than
four characters, pad it with the blank space.

Closes: #946
2022-02-19 12:21:26 +01:00
d35db47c9a darwin: lazily set process TTY name
Fetching the TTY name of a process is extremely expensive on darwin and
the call to devname accounts for 95% of htop's CPU usage when there is
high process turnover (this is mostly due to devname calling lstat,
which is incredibly slow). This can make htop unresponsive.

To mitigate this only set the process TTY name if the it is being
actively displayed (PROCESS_FLAG_TTY), which by default it is not
on darwin.
2022-02-18 09:07:41 +01:00
978a7c894f ProcessList_buildTreeBranch: drop extra assert
It essentially only checks Vector_add, and there's already an assert for
that in the vector code.
2022-02-13 19:50:16 +01:00
79a6f6cb5b ProcessList_buildTree: skip hashtable if known root
Don't waste time looking up the parent if the current process is already
known to be a root process.
2022-02-13 19:50:16 +01:00
deb05fe7c2 ProcessList: delay tree rebuild until panel rebuild 2022-02-13 19:50:16 +01:00
82d34deaf1 ProcessList: cleanup the tree set sorting remains
They're no longer needed as rebuilding the tree from scratch is just as
fast.
2022-02-13 19:50:16 +01:00
fa3e0d06c2 ProcessList_buildTree: produce sorted tree
ProcessList_buildTree does not need any particular sort order for
children of the same process or roots. Switching these to the sort order
configured by the user produces sorted tree automatically, making repeat
sort unnecessary.
2022-02-13 19:50:16 +01:00
82dce5cf8e ProcessList_buildTree: sort by parent for fast search
ProcessList_buildTreeBranch used to search for children with a linear
scan of the process table, which made tree build time quadratic in
process count. Pre-sorting the list by parent PID (if known) makes it
possible to select the correct slice by bisection much faster.
2022-02-13 19:50:16 +01:00
8d987864e4 ProcessList_buildTree: drop sort direction checking
This is only a partial check that does not take into account the sort
field used and is overridden later anyway.
2022-02-13 19:50:16 +01:00
58b42e4cac ProcessList: introduce displayList
Separate `processes` (the vector owning the processes, sorted in
whatever order is needed right now internally) and `displayList` (a
vector referencing the processes in the same order they're to be
displayed).
2022-02-13 19:50:16 +01:00
2477a5a018 ProcessList_buildTree: handle every process once
Special-casing hidden processes does not serve any obvious purpose and
depends on the move from processes to processes2 which will be removed
in a later commit.
2022-02-13 19:50:16 +01:00
1a403eb7eb ProcessList_buildTree: lookup parent via hashtable
While this change does not significantly affect performance, it removes
the internal requirement to have the process list sorted by PID.
2022-02-13 19:50:16 +01:00
a3a7958721 ProcessList: sort before panel rebuild if needed 2022-02-13 19:50:16 +01:00
4aeb146ce8 Remove duplicate sections on COMM and EXE
Closes #934

Thank you, Narendran Gopalakrishnan (gnarendran)!
2022-02-13 17:22:39 +01:00
8c99683b04 Fix custom thread name display issue
Fixes #877
2022-02-13 16:59:29 +01:00
265a7b8a50 Fix division by zero when calculating IO rates
Fixes #935
2022-02-03 17:48:18 +01:00
939685dff9 build: use AC_CANONICAL_HOST, not AC_CANONICAL_TARGET
htop is a program which will be run on CHOST after cross-compilation;
CTARGET is only for a small number of cases where a program itself outputs
code (so you might cross-compile a compiler which spits out code for a third
architecture/platform).

We want to use AC_CANONICAL_HOST to check CHOST for the platform currently
being used to build htop.

The confusion around this issue was compounded by a mistake in autoconf-archive
which has since been fixed (AX_PTHREAD pulled it in incorrectly).

See: https://github.com/libstatgrab/libstatgrab/pull/131
See: https://github.com/fenrus75/powertop/pull/90#discussion_r705803725
Signed-off-by: Sam James <sam@gentoo.org>
2022-01-22 03:57:14 +00:00
3e1a27a981 freebsd/dragonfly: Stop aligning equals signs in PLATFORM_PROCESS_FIELDS
ProcessField doesn't do this, nor does any other OS, and it just makes
it more annoying to add a new field.
2022-01-18 07:23:36 +01:00
9512fd7930 FreeBSD: Add support for showing process emulation
This displays the same output as ps's -o emul, which is the system call
emulation environment, or ABI, in use. This will typically be FreeBSD
ELF32 or ELF64, but can also be Linux ELF32 or Linux ELF64 when running
Linux binaries under FreeBSD's Linuxulator binary compatibility layer.
The column width of 16 is chosen to match KI_EMULNAMELEN's value of 16,
most of which is normally used up as FreeBSD ELF32/64 is 13 characters.
2022-01-16 17:21:18 +01:00
4a664c0df8 Help: Linux swap consistency
On the help screen's depiction of the swap bar, the / separator between
used and cache should be coloured for consistency with the other bars.

I tried removing the coloured /s from the other bars to make them
consistent, but found that less visually appealing.
2022-01-13 19:45:01 +01:00
d0d9f202c5 Avoid zombie processes on signal races
The system curses library can handle terminal size changes with
SIGWINCH without asking system calls to restart, which effectively
stops system calls with -1 and EINTR. An example is ncurses on
Linux systems.

One of these system calls is waitpid. While waiting for the lsof child
to complete, a badly timed SIGWINCH can interrupt the waitpid call,
effectively never clearing the state of the child, keeping the zombie
until htop exits.

Proof of Concept:

 #include <unistd.h>
 int main(void) {
   close(1); close(2);
   sleep(5);
   return 0;
 }

Compile this as a replacement "lsof" and put it into your path. Make
sure that it's called instead of the real lsof.

Press "l" to list open files and resize your terminal within the next
5 seconds. You will see that a zombie process is kept by htop when the
timeout finishes.
2022-01-11 22:56:27 +01:00
a0ad0697a8 Always set SIGCHLD to default handling
The parent process of htop might have set SIGCHLD to ignore, which can
be inherited by htop (Linux does this, OpenBSD resets to default).

If SIGCHLD is ignored then waitpid returns -1 which is not properly
handled by htop for lsof output.

Proof of Concept (Linux):

 #include <signal.h>
 #include <unistd.h>
 int main(void) {
   char *arg[] = { "htop", NULL };
   signal(SIGCHLD, SIG_IGN);
   execv("htop", arg);
   return 1;
 }

If you run htop with ignored SIGCHLD then pressing "l" always fails,
i.e. it is not possible to list open files even if lsof is installed.
2022-01-11 22:56:27 +01:00
a133ffd829 Removed unused String_getToken function
Since String_getToken is not used anymore and currently only supports
a 50 char token, simply remove it for now.
2022-01-11 21:42:57 +01:00
fde1243443 Fix out of boundary writes in XUtils
It is possible to exceed the unsigned int data type on 64 bit systems
with enough available RAM. Use size_t in all places instead.

Proof of Concept: Create a 4 GB line in .htoprc file and run htop

$ dd if=/dev/zero bs=1024 count=4194304 | tr '\0' 'a' > ~/.htoprc
$ htop
Segmentation fault

Also avoid overflow of stack based "match" array in String_getToken.
2022-01-11 21:42:57 +01:00
6eab39c0ab Fix typo
This typo has been found with codespell.
2022-01-11 19:56:27 +01:00
2c3a64ac9c Year 2022 updates 2022-01-03 18:01:18 +01:00
442c1596f6 GH Actions: enable Werror in PCP build
Just exclude the singe warning type currently issued.

Avoids e64269df ("Fix process state handling compiler warning on PCP platform")
2021-12-20 10:54:12 +01:00
f782f821f7 LinuxProcessList: do not collect LRS per thread
It's a memory map property, so it's process-wide and collecting it just
once should be enough.
2021-12-18 12:31:36 +01:00
5b78ad2d53 Set correct default sorting direction
Respect the field option defaultSortDesc for the default screen sort
direction, e.g. for CPU%.
2021-12-17 14:45:15 +01:00
1ef8c0e12f Drop getCommandStr member of Process
Formatting the merged command string is now implemented in an platform
independent way. Drop the Process member getCommandStr designed for
overrides of individual platforms.
2021-12-17 14:45:15 +01:00
6fcb1994c8 Do not combine default and configuration process fields
When reading a configuration file with the syntax previous to the
screens update Settings_defaultScreens() will add the default fields and
later ScreenSettings_readFields() will add the ones from the
configuration file. This will duplicate some fields and corrupt the
columns due to the boundless Command field.
2021-12-17 14:45:15 +01:00
5bc988ad6d Use correct command field as default field
The default htop command process field has the enum identifier `COMM`
but the name `Command` (`COMM` is the field name for /proc/<PID>/comm).
2021-12-17 14:45:15 +01:00
6e9a5e9e49 Mark ScreenDefaults const 2021-12-17 14:45:15 +01:00
14f428a172 Drop unused Platform variables 2021-12-17 14:45:15 +01:00
6388033e10 configure: support libunwind of LLVM
The libunwind headers of LLVM are located in the subdirectory
/usr/include/libunwind. Search that subdirectory when the default
header test fails. Also extend the include path due to the transitive
include of `<__libunwind_config.h>`.

Closes: #894
2021-12-16 17:51:15 +01:00
b45eaf2fe1 Hashtable: use a minimum size of 7
With a size of 2 or 3 the grow factor does not reach 70% for one empty
entry. This will cause the following assert violation:

    htop: Hashtable.c:236: void Hashtable_put(Hashtable *, ht_key_t, void *): Assertion `this->size > this->items' failed.
2021-12-13 21:17:58 +01:00
230dc9c3c1 Hashtable: adjust shrink-on-remove factor
Due to the use of prime numbers Hashtable_remove used to never shrink
from some sizes. For example, a size 8191 hashtable would try to shrink
to 4095, which nextPrime would round back to 8191 instead of the
intended 4093. A factor of 3 is enough to allow every prime size used to
shrink to the previous one.
2021-12-13 21:05:22 +01:00
d084a80023 Hashtable: skip rehashing if the size is the same
Hashtable_setSize should not rehash if the actual size stays the same as
the number of buckets and natural positions stay the same too.
2021-12-13 21:05:22 +01:00
5c8670717a Do not leave empty last column in header
Do not leave empty last column in header meters by refactoring the width
and separator logic.

Closes: #784
2021-12-09 17:52:00 +01:00
bc08c7dc2a Merge pull request #889 from cgzones/screens_update
Screens update
2021-12-09 08:38:25 +11:00
1e94b92226 Linux: read generic sysfs batteries
Not all batteries entries in /sys/class/power_supply start with either
BAT or AC, but might have device specific names, e.g. CMB1.
Detect the types of those entries and parse them accordingly.

Closes: #881
Fixes: 3e70de64 ("Code clean up for reading battery info")
2021-12-08 20:50:11 +01:00
63fafb4844 IOMeters: rework initial display
Show a non highlighted string at the start of htop, not the failure
text.
Also the original fix only handled the text mode, not the bar mode.

Improves: 2977414d ("Discard stale information from DiskIO and NetworkIO meters")
Related: #860
2021-12-08 19:08:20 +01:00
c85e5bbf5c ScreenPanels: free ScreenSettings of deleted screens 2021-12-08 16:34:15 +01:00
c9e0bd2002 ScreenPanel: misc updates
- use ASCII escape sequences
- use array allocation wrappers
2021-12-08 16:34:15 +01:00
df1914f429 Add ScreenSettings_delete helper 2021-12-08 16:34:15 +01:00
3cfdf66d9a Settings: initialize default sort key for new screenpanel
Use C99 struct initialization, which also makes using calloc redundant.

htop: Process.c:1179: int Process_compareByKey_Base(const Process *, const Process *, ProcessField): Assertion `0 && "Process_compareByKey_Base: default key reached"' failed.
2021-12-08 14:55:14 +01:00
fa9f260f63 Process: print default key 2021-12-08 14:48:20 +01:00
1da78b5818 CRT: add debug printing function 2021-12-08 14:48:20 +01:00
2ae1906479 Panel: initialize cursorOn member
Panel.c:496:14: runtime error: load of value 190, which is not a valid value for type 'bool'
2021-12-08 12:40:13 +01:00
0e58784224 Fix memory leak on shutdown in new screen settings code.
Also tidy up the calloc call parameters in the initial allocation
of this pointer, thanks to @BenBE for noticing.
2021-12-07 17:04:49 +11:00
4ef5e4296e fix CI issue
Signed-off-by: Sohaib Mohamed <sohaib.amhmd@gmail.com>
2021-12-07 17:04:49 +11:00
ba3a1df806 Fix misc styleguide issues and add missing header files
Signed-off-by: Sohaib Mohamed <sohaib.amhmd@gmail.com>
2021-12-07 17:04:49 +11:00
b672e60886 Enable tabs for a fresh install of htop Only
If the new htop is configured with htoprc having no tabs (eg on upgrade)
then the interface will not automatically introduce/enable them.
However, for a fresh install of htop, enabling them automatically

Signed-off-by: Sohaib Mohamed <sohaib.amhmd@gmail.com>
2021-12-07 17:04:49 +11:00
cd6457ef88 Fixup tabs with dynamic Columns - add missing Dynamic() 2021-12-07 17:04:49 +11:00
31fe29c5a7 Pass correct ColorElements values to Panel_setSelectionColor 2021-12-07 17:04:49 +11:00
cc2547fcf0 Improvements to the tab code after initial feedback 2021-12-07 17:04:49 +11:00
72ba20fa5f Introduce screen tabs
This is a forward port (by nathans) of Hisham's original code.
2021-12-07 17:04:49 +11:00
ff4f44b22a Pre-select the last sent signal in SignalsPanel
Instead of pre-selecting SIGTERM every time, select the signal last
send in the same htop session.

Closes: #862
2021-12-05 19:36:36 +01:00
a38f48481e Process: highlight UNINTERRUPTIBLE_WAIT state (D)
Commit d8dfbbd3 ("Tidy up process state handling") did change the
highlighting of the UNINTERRUPTIBLE_WAIT state (D) from red to gray.
As this state might means the process probably still has work to do and
can hint at bottlenecks, revert this particular change.

Fixes: d8dfbbd3 ("Tidy up process state handling")
2021-12-05 19:29:10 +01:00
61c9fe44a3 CGroupUtils: avoid unsigned integer underflow
Do not underflow count at the last iteration, which triggers UBSAN when
using -fsanitize=unsigned-integer-overflow. This is useful as those
underflows can be a result of a flawed counting logic (e.g. a counter
gets reduced more than increased).
2021-12-05 19:28:07 +01:00
ff0ea41c86 Fix issue where last line is not cleared when SIGINT is received
When we close the application using the quit function F10, the last line
is cleared so that on terminals which do not support ALTBUF the last
line is not clobbered. This do not happen when the application exits as
a result of a signal (SIGINT,SIGTERM,SIGQUIT).

Move the logic to clear the last line into the CRT_done function. This
ensures that it will be executed when the CRT_handleSIGTERM is called.
2021-12-01 17:08:13 +01:00
43e9be5a8f Update version number to 3.2.0-dev to identify git repo builds 2021-11-30 12:10:54 +11:00
110 changed files with 4212 additions and 2263 deletions

View File

@ -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

166
Action.c
View File

@ -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,9 +85,10 @@ 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) {
CRT_setMouse(st->settings->enableMouse);
Header_writeBackToSettings(st->header);
}
}
@ -154,7 +155,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 +165,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 +179,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 +190,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 +232,21 @@ 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;
if (!ss->treeView) {
return HTOP_OK;
}
ss->allBranchesCollapsed = !ss->allBranchesCollapsed;
if (ss->allBranchesCollapsed)
ProcessList_collapseAllBranches(st->pl);
else
ProcessList_expandTree(st->pl);
@ -277,9 +283,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 +294,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 +302,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 +388,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 +506,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,12 +531,13 @@ 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" },
{ .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
@ -525,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
addattrstr(CRT_colors[BAR_SHADOW], "/");
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) {
@ -572,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();
@ -711,4 +785,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;
}

View File

@ -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);

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);

244
CRT.c
View File

@ -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>
@ -37,7 +38,6 @@ in the source distribution for its full text.
#include <execinfo.h>
#endif
#define ColorIndex(i, j) ((7 - (i)) * 8 + (j))
#define ColorPair(i, j) COLOR_PAIR(ColorIndex(i, j))
@ -94,7 +94,8 @@ static const int* CRT_delay;
const char *CRT_degreeSign;
static const char* initDegreeSign(void) {
static const char *initDegreeSign(void)
{
#ifdef HAVE_LIBNCURSESW
if (CRT_utf8)
return "\xc2\xb0";
@ -126,6 +127,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[FAILED_READ] = A_BOLD | ColorPair(Red, Black),
[PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),
[UPTIME] = A_BOLD | ColorPair(Cyan, Black),
[TEMP] = A_BOLD | ColorPair(Cyan, Black),
[FREQ] = A_BOLD | ColorPair(Cyan, Black),
[BATTERY] = A_BOLD | ColorPair(Cyan, Black),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Black),
[METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
@ -193,6 +196,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),
@ -228,6 +236,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[FAILED_READ] = A_BOLD,
[PAUSED] = A_BOLD | A_REVERSE,
[UPTIME] = A_BOLD,
[TEMP] = A_BOLD,
[FREQ] = A_BOLD,
[BATTERY] = A_BOLD,
[LARGE_NUMBER] = A_BOLD,
[METER_SHADOW] = A_DIM,
@ -295,6 +305,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,
@ -330,6 +345,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[FAILED_READ] = ColorPair(Red, White),
[PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),
[UPTIME] = ColorPair(Yellow, White),
[TEMP] = ColorPair(Yellow, White),
[FREQ] = ColorPair(Yellow, White),
[BATTERY] = ColorPair(Yellow, White),
[LARGE_NUMBER] = ColorPair(Red, White),
[METER_SHADOW] = ColorPair(Blue, White),
@ -397,6 +414,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),
@ -432,6 +454,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[FAILED_READ] = ColorPair(Red, Black),
[PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),
[UPTIME] = ColorPair(Yellow, Black),
[TEMP] = ColorPair(Yellow, Black),
[FREQ] = ColorPair(Yellow, Black),
[BATTERY] = ColorPair(Yellow, Black),
[LARGE_NUMBER] = ColorPair(Red, Black),
[METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
@ -499,6 +523,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),
@ -534,6 +563,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[FAILED_READ] = A_BOLD | ColorPair(Red, Blue),
[PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),
[UPTIME] = A_BOLD | ColorPair(Yellow, Blue),
[TEMP] = A_BOLD | ColorPair(Yellow, Blue),
[FREQ] = A_BOLD | ColorPair(Yellow, Blue),
[BATTERY] = A_BOLD | ColorPair(Yellow, Blue),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Blue),
[METER_SHADOW] = ColorPair(Cyan, Blue),
@ -601,6 +632,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),
@ -636,6 +672,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[FAILED_READ] = A_BOLD | ColorPair(Red, Black),
[PAUSED] = A_BOLD | ColorPair(Yellow, Green),
[UPTIME] = ColorPair(Green, Black),
[TEMP] = ColorPair(Green, Black),
[FREQ] = ColorPair(Green, Black),
[BATTERY] = ColorPair(Green, Black),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Black),
[METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
@ -701,6 +739,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 +768,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;
@ -734,7 +775,8 @@ int CRT_scrollWheelVAmount = 10;
ColorScheme CRT_colorScheme = COLORSCHEME_DEFAULT;
ATTR_NORETURN
static void CRT_handleSIGTERM(ATTR_UNUSED int sgn) {
static void CRT_handleSIGTERM(ATTR_UNUSED int sgn)
{
CRT_done();
_exit(0);
}
@ -744,7 +786,8 @@ static void CRT_handleSIGTERM(ATTR_UNUSED int sgn) {
static int stderrRedirectNewFd = -1;
static int stderrRedirectBackupFd = -1;
static int createStderrCacheFile(void) {
static int createStderrCacheFile(void)
{
#if defined(HAVE_MEMFD_CREATE)
return memfd_create("htop.stderr-redirect", 0);
#elif defined(O_TMPFILE)
@ -763,9 +806,11 @@ static int createStderrCacheFile(void) {
#endif /* HAVE_MEMFD_CREATE */
}
static void redirectStderr(void) {
static void redirectStderr(void)
{
stderrRedirectNewFd = createStderrCacheFile();
if (stderrRedirectNewFd < 0) {
if (stderrRedirectNewFd < 0)
{
/* ignore failure */
return;
}
@ -774,7 +819,8 @@ static void redirectStderr(void) {
dup2(stderrRedirectNewFd, STDERR_FILENO);
}
static void dumpStderr(void) {
static void dumpStderr(void)
{
if (stderrRedirectNewFd < 0)
return;
@ -786,22 +832,27 @@ static void dumpStderr(void) {
bool header = false;
char buffer[8192];
for (;;) {
for (;;)
{
errno = 0;
ssize_t res = read(stderrRedirectNewFd, buffer, sizeof(buffer));
if (res < 0) {
if (res < 0)
{
if (errno == EINTR)
continue;
break;
}
if (res == 0) {
if (res == 0)
{
break;
}
if (res > 0) {
if (!header) {
if (res > 0)
{
if (!header)
{
fprintf(stderr, ">>>>>>>>>> stderr output >>>>>>>>>>\n");
header = true;
}
@ -816,19 +867,33 @@ static void dumpStderr(void) {
stderrRedirectNewFd = -1;
}
#else /* !NDEBUG */
void CRT_debug_impl(const char *file, size_t lineno, const char *func, const char *fmt, ...)
{
va_list args;
static void redirectStderr(void) {
fprintf(stderr, "[%s:%zu (%s)]: ", file, lineno, func);
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
static void dumpStderr(void) {
#else /* !NDEBUG */
static void redirectStderr(void)
{
}
static void dumpStderr(void)
{
}
#endif /* !NDEBUG */
static struct sigaction old_sig_handler[32];
static void CRT_installSignalHandlers(void) {
static void CRT_installSignalHandlers(void)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = (int)SA_RESETHAND | SA_NODEFER;
@ -841,12 +906,14 @@ 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);
}
void CRT_resetSignalHandlers(void) {
void CRT_resetSignalHandlers(void)
{
sigaction(SIGSEGV, &old_sig_handler[SIGSEGV], NULL);
sigaction(SIGFPE, &old_sig_handler[SIGFPE], NULL);
sigaction(SIGILL, &old_sig_handler[SIGILL], NULL);
@ -860,7 +927,26 @@ void CRT_resetSignalHandlers(void) {
signal(SIGQUIT, SIG_DFL);
}
void CRT_init(const Settings* settings, bool allowUnicode) {
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();
initscr();
@ -870,7 +956,8 @@ void CRT_init(const Settings* settings, bool allowUnicode) {
CRT_colors = CRT_colorSchemes[settings->colorScheme];
CRT_colorScheme = settings->colorScheme;
for (int i = 0; i < LAST_COLORELEMENT; i++) {
for (int i = 0; i < LAST_COLORELEMENT; i++)
{
unsigned int color = CRT_colorSchemes[COLORSCHEME_DEFAULT][i];
CRT_colorSchemes[COLORSCHEME_BROKENGRAY][i] = color == (A_BOLD | ColorPairGrayBlack) ? ColorPair(White, Black) : color;
}
@ -884,18 +971,23 @@ void CRT_init(const Settings* settings, bool allowUnicode) {
#endif
curs_set(0);
if (has_colors()) {
if (has_colors())
{
start_color();
}
const char *termType = getenv("TERM");
if (termType && String_eq(termType, "linux")) {
if (termType && String_eq(termType, "linux"))
{
CRT_scrollHAmount = 20;
} else {
}
else
{
CRT_scrollHAmount = 5;
}
if (termType && (String_startsWith(termType, "xterm") || String_eq(termType, "vt220"))) {
if (termType && (String_startsWith(termType, "xterm") || String_eq(termType, "vt220")))
{
#ifdef HTOP_NETBSD
#define define_key(s_, k_) define_key((char *)s_, k_)
IGNORE_WCASTQUAL_BEGIN
@ -915,8 +1007,10 @@ 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++) {
for (char c = 'a'; c <= 'z'; c++)
{
sequence[1] = c;
define_key(sequence, KEY_ALT('A' + (c - 'a')));
}
@ -925,6 +1019,10 @@ IGNORE_WCASTQUAL_END
#undef define_key
#endif
}
if (termType && (String_startsWith(termType, "rxvt")))
{
define_key("\033[Z", KEY_SHIFT_TAB);
}
CRT_installSignalHandlers();
@ -934,9 +1032,12 @@ IGNORE_WCASTQUAL_END
CRT_setColors(CRT_colorScheme);
#ifdef HAVE_LIBNCURSESW
if (allowUnicode && String_eq(nl_langinfo(CODESET), "UTF-8")) {
if (allowUnicode && String_eq(nl_langinfo(CODESET), "UTF-8"))
{
CRT_utf8 = true;
} else {
}
else
{
CRT_utf8 = false;
}
#else
@ -949,32 +1050,36 @@ 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();
}
void CRT_done() {
void CRT_done()
{
int resetColor = CRT_colors ? CRT_colors[RESET_COLOR] : CRT_colorSchemes[COLORSCHEME_DEFAULT][RESET_COLOR];
attron(resetColor);
mvhline(LINES - 1, 0, ' ', COLS);
attroff(resetColor);
refresh();
curs_set(1);
endwin();
dumpStderr();
}
void CRT_fatalError(const char* note) {
void CRT_fatalError(const char *note)
{
const char *sysMsg = strerror(errno);
CRT_done();
fprintf(stderr, "%s: %s\n", note, sysMsg);
exit(2);
}
int CRT_readKey() {
int CRT_readKey()
{
nocbreak();
cbreak();
nodelay(stdscr, FALSE);
@ -983,22 +1088,28 @@ int CRT_readKey() {
return ret;
}
void CRT_disableDelay() {
void CRT_disableDelay()
{
nocbreak();
cbreak();
nodelay(stdscr, TRUE);
}
void CRT_enableDelay() {
void CRT_enableDelay()
{
halfdelay(*CRT_delay);
}
void CRT_setColors(int colorScheme) {
void CRT_setColors(int colorScheme)
{
CRT_colorScheme = colorScheme;
for (short int i = 0; i < 8; i++) {
for (short int j = 0; j < 8; j++) {
if (ColorIndex(i, j) != ColorIndexGrayBlack && ColorIndex(i, j) != ColorIndexWhiteDefault) {
for (short int i = 0; i < 8; i++)
{
for (short int j = 0; j < 8; j++)
{
if (ColorIndex(i, j) != ColorIndexGrayBlack && ColorIndex(i, j) != ColorIndexWhiteDefault)
{
short int bg = (colorScheme != COLORSCHEME_BLACKNIGHT)
? (j == 0 ? -1 : j)
: j;
@ -1017,7 +1128,8 @@ void CRT_setColors(int colorScheme) {
}
#ifdef PRINT_BACKTRACE
static void print_backtrace(void) {
static void print_backtrace(void)
{
#if defined(HAVE_LIBUNWIND_H) && defined(HAVE_LIBUNWIND)
unw_context_t context;
unw_getcontext(&context);
@ -1027,7 +1139,8 @@ static void print_backtrace(void) {
unsigned int item = 0;
while (unw_step(&cursor) > 0) {
while (unw_step(&cursor) > 0)
{
unw_word_t pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
if (pc == 0)
@ -1042,7 +1155,8 @@ static void print_backtrace(void) {
const char *fname = "?";
const void *ptr = 0;
if (unw_get_proc_info(&cursor, &pip) == 0) {
if (unw_get_proc_info(&cursor, &pip) == 0)
{
ptr = (const void *)(pip.start_ip + offset);
#ifdef HAVE_DLADDR
@ -1069,7 +1183,8 @@ static void print_backtrace(void) {
}
#endif
void CRT_handleSIGSEGV(int signal) {
void CRT_handleSIGSEGV(int signal)
{
CRT_done();
fprintf(stderr, "\n\n"
@ -1080,19 +1195,18 @@ void CRT_handleSIGSEGV(int signal) {
" - Your " PACKAGE " version: '" VERSION "'\n"
" - Your OS and kernel version (uname -a)\n"
" - Your distribution and release (lsb_release -a)\n"
" - Likely steps to reproduce (How did it happen?)\n"
);
" - Likely steps to reproduce (How did it happen?)\n");
#ifdef PRINT_BACKTRACE
fprintf(stderr, " - Backtrace of the issue (see below)\n");
#endif
fprintf(stderr,
"\n"
);
"\n");
const char *signal_str = strsignal(signal);
if (!signal_str) {
if (!signal_str)
{
signal_str = "unknown reason";
}
fprintf(stderr,
@ -1100,8 +1214,7 @@ void CRT_handleSIGSEGV(int signal) {
"------------------\n"
"A signal %d (%s) was received.\n"
"\n",
signal, signal_str
);
signal, signal_str);
fprintf(stderr,
"Setting information:\n"
@ -1112,8 +1225,7 @@ void CRT_handleSIGSEGV(int signal) {
#ifdef PRINT_BACKTRACE
fprintf(stderr,
"Backtrace information:\n"
"----------------------\n"
);
"----------------------\n");
print_backtrace();
@ -1122,8 +1234,7 @@ void CRT_handleSIGSEGV(int signal) {
"To make the above information more practical to work with, "
"please also provide a disassembly of your " PACKAGE " binary. "
"This can usually be done by running the following command:\n"
"\n"
);
"\n");
#ifdef HTOP_DARWIN
fprintf(stderr, " otool -tvV `which " PACKAGE "` > ~/htop.otool\n");
@ -1133,23 +1244,21 @@ void CRT_handleSIGSEGV(int signal) {
fprintf(stderr,
"\n"
"Please include the generated file in your report.\n"
);
"Please include the generated file in your report.\n");
#endif
fprintf(stderr,
"Running this program with debug symbols or inside a debugger may provide further insights.\n"
"\n"
"Thank you for helping to improve " PACKAGE "!\n"
"\n"
);
"\n");
/* Call old sigsegv handler; may be default exit or third party one (e.g. ASAN) */
if (sigaction (signal, &old_sig_handler[signal], NULL) < 0) {
if (sigaction(signal, &old_sig_handler[signal], NULL) < 0)
{
/* This avoids an infinite loop in case the handler could not be reset. */
fprintf(stderr,
"!!! Chained handler could not be restored. Forcing exit.\n"
);
"!!! Chained handler could not be restored. Forcing exit.\n");
_exit(1);
}
@ -1158,7 +1267,6 @@ void CRT_handleSIGSEGV(int signal) {
// Always terminate, even if installed handler returns
fprintf(stderr,
"!!! Chained handler did not exit. Forcing exit.\n"
);
"!!! Chained handler did not exit. Forcing exit.\n");
_exit(1);
}

27
CRT.h
View File

@ -15,8 +15,8 @@ in the source distribution for its full text.
#include "ProvideCurses.h"
#include "Settings.h"
typedef enum TreeStr_ {
typedef enum TreeStr_
{
TREE_STR_VERT,
TREE_STR_RTEE,
TREE_STR_BEND,
@ -28,7 +28,8 @@ typedef enum TreeStr_ {
LAST_TREE_STR
} TreeStr;
typedef enum ColorScheme_ {
typedef enum ColorScheme_
{
COLORSCHEME_DEFAULT,
COLORSCHEME_MONOCHROME,
COLORSCHEME_BLACKONWHITE,
@ -39,7 +40,8 @@ typedef enum ColorScheme_ {
LAST_COLORSCHEME
} ColorScheme;
typedef enum ColorElements_ {
typedef enum ColorElements_
{
RESET_COLOR,
DEFAULT_COLOR,
FUNCTION_BAR,
@ -64,6 +66,8 @@ typedef enum ColorElements_ {
METER_VALUE_WARN,
LED_COLOR,
UPTIME,
TEMP,
FREQ,
BATTERY,
TASKS_RUNNING,
SWAP,
@ -120,6 +124,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 +154,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;
@ -172,6 +189,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

@ -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));

View File

@ -1,3 +1,57 @@
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
* 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

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

@ -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;
}

View File

@ -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);

View File

@ -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();

View File

@ -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;
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;
DiskIOData data;
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];

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,18 @@ 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)));
#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)));
Panel_add(super, (Object*) CheckItem_newByRef("Hide userland process threads", &(settings->hideUserlandThreads)));

29
FreqMeter.c Normal file
View File

@ -0,0 +1,29 @@
#include "FreqMeter.h"
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
#include "XUtils.h"
static const int FreqMeter_attributes[] = {
FREQ};
static void FreqMeter_updateValues(Meter *this)
{
float freq = Platform_getFreq();
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1lf GHz", freq);
}
const MeterClass FreqMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete},
.updateValues = FreqMeter_updateValues,
.defaultMode = TEXT_METERMODE,
.maxItems = 1,
.total = 100.0,
.attributes = FreqMeter_attributes,
.name = "Freq",
.uiName = "CPU Frequency",
.caption = "CPU/Frequency: "};

8
FreqMeter.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef HEADER_FreqMeter
#define HEADER_FreqMeter
#include "Meter.h"
extern const MeterClass FreqMeter_class;
#endif

View File

@ -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) {

View File

@ -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);

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++;
@ -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;
}

View File

@ -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;

View File

@ -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,

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

@ -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);
}

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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]);
}

View File

@ -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);

View File

@ -74,6 +74,7 @@ myhtopsources = \
ProcessLocksScreen.c \
RichString.c \
ScreenManager.c \
ScreensPanel.c \
Settings.c \
SignalsPanel.c \
SwapMeter.c \
@ -81,6 +82,8 @@ myhtopsources = \
TasksMeter.c \
TraceScreen.c \
UptimeMeter.c \
FreqMeter.c \
TempMeter.c \
UsersTable.c \
Vector.c \
XUtils.c
@ -135,6 +138,7 @@ myhtopheaders = \
ProvideCurses.h \
RichString.h \
ScreenManager.h \
ScreensPanel.h \
Settings.h \
SignalsPanel.h \
SwapMeter.h \
@ -142,6 +146,8 @@ myhtopheaders = \
TasksMeter.h \
TraceScreen.h \
UptimeMeter.h \
FreqMeter.h \
TempMeter.h \
UsersTable.h \
Vector.h \
XUtils.h

10
Meter.c
View File

@ -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;

View File

@ -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);

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

@ -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];

View File

@ -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>
@ -119,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]);
@ -197,7 +200,8 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
fclose(fd);
int wstatus;
if (waitpid(child, &wstatus, 0) == -1) {
while (waitpid(child, &wstatus, 0) == -1)
if (errno != EINTR) {
pdata->error = 1;
return pdata;
}

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);

22
Panel.c
View File

@ -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();
}

10
Panel.h
View File

@ -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

163
Process.c
View File

@ -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;
@ -409,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
@ -417,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))
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;
free(mc->str);
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;
/* Reset all locations that need extra handling when actually displaying */
mc->highlightCount = 0;
@ -516,11 +498,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;
}
}
@ -593,11 +578,15 @@ void Process_makeCommandStr(Process* this) {
}
if (matchLen) {
if (stripExeFromCmdline) {
/* strip the matched exe prefix */
cmdline += matchLen;
commStart -= matchLen;
commEnd -= matchLen;
} else {
matchLen = 0;
}
}
if (!matchLen || (haveCommField && *cmdline)) {
@ -729,23 +718,25 @@ 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) {
if (val < 0.05F)
*attr = CRT_colors[PROCESS_SHADOW];
}
xSnprintf(buffer, n, "%4.1f ", val);
} else if (val < 999) {
else if (val >= 99.9F)
*attr = CRT_colors[PROCESS_MEGABYTES];
xSnprintf(buffer, n, "%3d. ", (int)val);
} else {
*attr = CRT_colors[PROCESS_MEGABYTES];
xSnprintf(buffer, n, "%4d ", (int)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, " N/A ");
xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
}
}
@ -784,7 +775,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 +860,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 +885,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 +916,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 +974,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 +1010,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 +1026,6 @@ const ProcessClass Process_class = {
.compare = Process_compare
},
.writeField = Process_writeField,
.getCommandStr = Process_getCommandStr,
};
void Process_init(Process* this, const Settings* settings) {
@ -1080,20 +1079,14 @@ 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;
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 +1094,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 +1166,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);
}
@ -1187,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) {
@ -1227,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) {
@ -1246,5 +1242,40 @@ 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 };
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.9F) {
Process_updateFieldWidth(PERCENT_CPU, 4);
Process_updateFieldWidth(PERCENT_NORM_CPU, 4);
return;
}
// 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_ {
@ -250,10 +243,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 +272,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,28 +282,26 @@ 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
#define PROCESS_MAX_UID_DIGITS 19
#define PROCESS_MAX_UID_DIGITS 20
extern int Process_pidDigits;
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 +365,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);
@ -393,21 +387,28 @@ 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);
// 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

View File

@ -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);
}
@ -85,38 +82,48 @@ 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) {
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;
}
return title;
}
@ -124,13 +131,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 +148,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) {
@ -153,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;
@ -163,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;
@ -189,316 +196,148 @@ 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)));
}
// 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);
// 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++;
}
for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
for (int i = l; i < r; 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);
}
}
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)++;
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);
// Find all processes whose parent is not visible
int size;
while ((size = Vector_size(this->processes))) {
int i;
for (i = 0; i < size; i++) {
for (int i = 0; i < vsize; 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;
process->isRoot = false;
// 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 Mac OS X 10.11.6) regard this process as root.
if (process->pid == ppid) {
process->isRoot = true;
continue;
}
// 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;
if (!ppid) {
process->isRoot = true;
continue;
}
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;
}
// 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 (l >= r) {
process = (Process*)Vector_take(this->processes, i);
if (process->isRoot) {
process = (Process*)Vector_get(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;
Vector_add(this->displayList, process);
ProcessList_buildTreeBranch(this, process->pid, 0, 0, process->showChildren);
continue;
}
}
// There should be no loop in the process tree
assert(i < size);
}
// 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 {
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));
@ -518,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);
@ -529,6 +371,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 +390,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;
@ -590,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);
@ -620,6 +464,7 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
this->kernelThreads = 0;
this->runningTasks = 0;
Process_resetFieldWidths();
// set scan timestamp
static bool firstScanDone = false;
@ -644,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
@ -653,21 +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);
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);
}
}

View File

@ -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 */
@ -106,9 +106,7 @@ 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_sort(ProcessList* this);
void ProcessList_updateDisplayList(ProcessList* this);
ProcessField ProcessList_keyAt(const ProcessList* this, int at);

101
README
View File

@ -8,7 +8,22 @@
[![Packaging status](https://repology.org/badge/tiny-repos/htop.svg)](https://repology.org/project/htop/versions)
[![License: GPL v2+](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](COPYING?raw=true)
![Screenshot of htop](docs/images/screenshot.png?raw=true)
![Screenshot of htop](https://i.imgur.com/dCSsvSv.png)
## **Warning!**
This fork was created for personal use; correct operation is not guaranteed.
<details>
<summary>Tested on</summary>
- Manjaro ARM Minimal (Raspberry Pi 4)
- Debian 11 (Raspberry Pi 4)
- Ubuntu Server 22.04 (Raspberry Pi 4)
- Raspbian 64bit (Raspberry Pi 3B+)
- Arch Linux (x86 PC), used for the screenshot above
</details>
## Introduction
@ -30,45 +45,66 @@ For more information and details visit [htop.dev](https://htop.dev).
## Build instructions
### Prerequisite
List of build-time dependencies:
* standard GNU autotools-based C toolchain
- standard GNU autotools-based C toolchain
- C99 compliant compiler
- `autoconf`
- `autotools`
* `ncurses`
- `ncurses`
**Note about `ncurses`:**
> `htop` requires `ncurses` 6.0. Be aware the appropriate package is sometimes still called libncurses5 (on Debian/Ubuntu). Also `ncurses` usually comes in two flavours:
>* With Unicode support.
>* Without Unicode support.
>
>- With Unicode support.
>- Without Unicode support.
>
> This is also something that is reflected in the package name on Debian/Ubuntu (via the additional 'w' - 'w'ide character support).
List of additional build-time dependencies (based on feature flags):
* `sensors`
* `hwloc`
* `libcap` (v2.21 or later)
* `libnl-3`
- `sensors`
- `hwloc`
- `libcap` (v2.21 or later)
- `libnl-3`
Install these and other required packages for C development from your package manager.
**Debian/Ubuntu**
~~~ shell
sudo apt install libncursesw5-dev autotools-dev autoconf build-essential
~~~
**Fedora/RHEL**
~~~ shell
sudo dnf install ncurses-devel automake autoconf gcc
~~~
### Compile from source:
**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
./autogen.sh && ./configure && make
~~~
### Install
To install on the local system run `make install`. By default `make install` installs into `/usr/local`. To change this path use `./configure --prefix=/some/path`.
### Build Options
@ -77,80 +113,85 @@ To install on the local system run `make install`. By default `make install` ins
#### Generic
* `--enable-unicode`:
- `--enable-unicode`:
enable Unicode support
- dependency: *libncursesw*
- default: *yes*
* `--enable-affinity`:
- `--enable-affinity`:
enable `sched_setaffinity(2)` and `sched_getaffinity(2)` for affinity support; conflicts with hwloc
- default: *check*
* `--enable-hwloc`:
- `--enable-hwloc`:
enable hwloc support for CPU affinity; disables affinity support
- dependency: *libhwloc*
- default: *no*
* `--enable-static`:
- `--enable-static`:
build a static htop binary; hwloc and delay accounting are not supported
- default: *no*
* `--enable-debug`:
- `--enable-debug`:
Enable asserts and internal sanity checks; implies a performance penalty
- default: *no*
#### Performance Co-Pilot
* `--enable-pcp`:
- `--enable-pcp`:
enable Performance Co-Pilot support via a new pcp-htop utility
- dependency: *libpcp*
- default: *no*
#### Linux
* `--enable-sensors`:
- `--enable-sensors`:
enable libsensors(3) support for reading temperature data
- dependencies: *libsensors-dev*(build-time), at runtime *libsensors* is loaded via `dlopen(3)` if available
- default: *check*
* `--enable-capabilities`:
- `--enable-capabilities`:
enable Linux capabilities support
- dependency: *libcap*
- default: *check*
* `--with-proc`:
- `--with-proc`:
location of a Linux-compatible proc filesystem
- default: */proc*
* `--enable-openvz`:
- `--enable-openvz`:
enable OpenVZ support
- default: *no*
* `--enable-vserver`:
- `--enable-vserver`:
enable VServer support
- default: *no*
* `--enable-ancient-vserver`:
- `--enable-ancient-vserver`:
enable ancient VServer support (implies `--enable-vserver`)
- default: *no*
* `--enable-delayacct`:
- `--enable-delayacct`:
enable Linux delay accounting support
- dependencies: *pkg-config*(build-time), *libnl-3* and *libnl-genl-3*
- default: *check*
## Runtime dependencies
## Runtime dependencies:
`htop` has a set of fixed minimum runtime dependencies, which is kept as minimal as possible:
* `ncurses` libraries for terminal handling (wide character support).
### Runtime optional dependencies:
- `ncurses` libraries for terminal handling (wide character support).
### Runtime optional dependencies
`htop` has a set of fixed optional dependencies, depending on build/configure option used:
#### Linux
* `libdl`, if not building a static binary, is always required when support for optional dependencies (i.e. `libsensors`, `libsystemd`) is present.
* `libcap`, user-space interfaces to POSIX 1003.1e capabilities, is always required when `--enable-capabilities` was used to configure `htop`.
* `libsensors`, readout of temperatures and CPU speeds, is optional even when `--enable-sensors` was used to configure `htop`.
* `libsystemd` is optional when `--enable-static` was not used to configure `htop`. If building statically and `libsystemd` is not found by `configure`, support for the systemd meter is disabled entirely.
- `libdl`, if not building a static binary, is always required when support for optional dependencies (i.e. `libsensors`, `libsystemd`) is present.
- `libcap`, user-space interfaces to POSIX 1003.1e capabilities, is always required when `--enable-capabilities` was used to configure `htop`.
- `libsensors`, readout of temperatures and CPU speeds, is optional even when `--enable-sensors` was used to configure `htop`.
- `libsystemd` is optional when `--enable-static` was not used to configure `htop`. If building statically and `libsystemd` is not found by `configure`, support for the systemd meter is disabled entirely.
`htop` checks for the availability of the actual runtime libraries as `htop` runs.
#### BSD
On most BSD systems `kvm` is a requirement to read kernel information.
More information on required and optional dependencies can be found in [configure.ac](configure.ac).
## Usage
See the manual page (`man htop`) or the help menu (**F1** or **h** inside `htop`) for a list of supported key commands.
## Support

View File

@ -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) {

View File

@ -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

328
ScreensPanel.c Normal file
View File

@ -0,0 +1,328 @@
/*
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->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);
ScreenSettings* ss = item->ss;
free(ss->name);
this->settings->screens[i] = ss;
ss->name = xStrdup(((ListItem*) item)->value);
}
this->settings->screens[size] = NULL;
}

53
ScreensPanel.h Normal file
View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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
@ -81,17 +96,18 @@ typedef struct Settings_ {
#endif
bool changed;
uint64_t lastUpdate;
} 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 +117,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 Settings_setSortKey(Settings* this, ProcessField sortKey);
void ScreenSettings_delete(ScreenSettings* this);
void ScreenSettings_invertSortOrder(ScreenSettings* this);
void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey);
void Settings_enableReadonly(void);

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"
@ -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;
}
}

View File

@ -7,6 +7,12 @@ 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"
@ -15,6 +21,8 @@ typedef struct SignalItem_ {
int number;
} SignalItem;
Panel* SignalsPanel_new(void);
#define SIGNALSPANEL_INITSELECTEDSIGNAL SIGTERM
Panel* SignalsPanel_new(int preSelectedSignal);
#endif

29
TempMeter.c Normal file
View File

@ -0,0 +1,29 @@
#include "TempMeter.h"
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
#include "XUtils.h"
static const int TempMeter_attributes[] = {
TEMP};
static void TempMeter_updateValues(Meter *this)
{
float temp_c = Platform_getTemp();
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1lf °C", temp_c);
}
const MeterClass TempMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete},
.updateValues = TempMeter_updateValues,
.defaultMode = TEXT_METERMODE,
.maxItems = 1,
.total = 100.0,
.attributes = TempMeter_attributes,
.name = "Temp",
.uiName = "CPU Temperature",
.caption = "CPU/Temperature: "};

8
TempMeter.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef HEADER_TempMeter
#define HEADER_TempMeter
#include "Meter.h"
extern const MeterClass TempMeter_class;
#endif

View File

@ -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) {
@ -87,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

@ -94,13 +94,30 @@ 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) {
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);
if (SIZE_MAX - l1 <= l2) {
fail();
}
char* out = xMalloc(l1 + l2 + 1);
memcpy(out, s1, l1);
memcpy(out + l1, s2, l2);
@ -122,10 +139,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 +177,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 (;;) {

View File

@ -17,7 +17,6 @@ in the source distribution for its full text.
#include "Compat.h"
#include "Macros.h"
void fail(void) ATTR_NORETURN;
void *xMalloc(size_t size) ATTR_ALLOC_SIZE1(1) ATTR_MALLOC;
@ -36,13 +35,15 @@ void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size)
* String_startsWith gives better performance if strlen(match) can be computed
* at compile time (e.g. when they are immutable string literals). :)
*/
static inline bool String_startsWith(const char* s, const char* match) {
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) {
static inline bool String_eq(const char *s1, const char *s2)
{
return strcmp(s1, s2) == 0;
}
@ -54,8 +55,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. */

View File

@ -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.1], [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)
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])

View File

@ -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->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;

View File

@ -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;

View File

@ -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 },

View File

@ -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[];

View File

@ -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.
*/

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,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, },

View File

@ -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

View File

@ -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 },

View File

@ -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[];

View File

@ -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);
}

View File

@ -18,6 +18,7 @@ typedef struct FreeBSDProcess_ {
Process super;
int jid;
char* jname;
char* emul;
} FreeBSDProcess;
extern const ProcessClass FreeBSDProcess_class;

View File

@ -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;

View File

@ -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 },
@ -220,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;
@ -227,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

@ -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[];

View File

@ -11,6 +11,7 @@ in the source distribution for its full text.
#define PLATFORM_PROCESS_FIELDS \
JID = 100, \
JAIL = 101, \
EMULATION = 102, \
\
DUMMY_BUMP_FIELD = CWD, \
// End of list

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

@ -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);

View File

@ -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

View File

@ -33,7 +33,7 @@ static bool StrBuf_putc_write(StrBuf_state* p, char c) {
}
static bool StrBuf_putsn(StrBuf_state* p, StrBuf_putc_t w, const char* s, size_t count) {
while (count--)
for (; count; count--)
if (!w(p, *s++))
return false;
@ -66,6 +66,7 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
const char* str_user_slice = "user.slice";
const char* str_machine_slice = "machine.slice";
const char* str_user_slice_prefix = "/user-";
const char* str_system_slice_prefix = "/system-";
const char* str_lxc_monitor_legacy = "lxc.monitor";
const char* str_lxc_payload_legacy = "lxc.payload";
@ -76,6 +77,8 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
const char* str_nspawn_monitor_label = "/supervisor";
const char* str_nspawn_payload_label = "/payload";
const char* str_snap_scope_prefix = "snap.";
const char* str_service_suffix = ".service";
const char* str_scope_suffix = ".scope";
@ -100,6 +103,11 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
if (!StrBuf_putsz(s, w, "[S]"))
return false;
if (String_startsWith(cgroup, str_system_slice_prefix)) {
cgroup = strchrnul(cgroup + 1, '/');
continue;
}
continue;
}
@ -266,6 +274,22 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
else if (String_startsWith(nextSlash, str_nspawn_payload_label))
cgroup += strlen(str_nspawn_payload_label);
continue;
} else if(Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) {
const char* nextDot = strchrnul(labelStart + strlen(str_snap_scope_prefix), '.');
if (!StrBuf_putsz(s, w, "!snap:"))
return false;
if (nextDot >= labelStart + scopeNameLen) {
nextDot = labelStart + scopeNameLen;
}
if (!StrBuf_putsn(s, w, labelStart + strlen(str_snap_scope_prefix), nextDot - (labelStart + strlen(str_snap_scope_prefix))))
return false;
cgroup = nextSlash;
continue;
}

View File

@ -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, 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) {
@ -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);

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
@ -174,21 +196,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 +258,16 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) {
closedir(dir);
// return if no CPU is found
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. */
@ -241,7 +276,7 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) {
#endif
super->activeCPUs = active;
assert(existing == currExisting);
assert(Running_containerized || (existing == currExisting));
super->existingCPUs = currExisting;
}
@ -493,6 +528,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 +539,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 +547,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 +937,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 +1075,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;
}
@ -1300,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);
@ -1366,10 +1418,26 @@ 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;
const Settings* settings = pl->settings;
const ScreenSettings* ss = settings->ss;
#ifdef HAVE_OPENAT
int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
@ -1422,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;
@ -1429,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);
/*
@ -1463,7 +1531,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 +1540,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 && 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;
@ -1481,17 +1550,19 @@ 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 (prev != proc->usesDeletedLib)
proc->mergedCommand.lastUpdate = 0;
}
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 +1592,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 +1600,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 +1608,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 +1639,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);
}
@ -1626,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);
}
}
@ -1847,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;
@ -1881,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;
@ -1994,7 +2073,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 +2136,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;
@ -2074,15 +2153,10 @@ static void scanCPUFreqencyFromCPUinfo(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)
) {
if (cpuid < 0 || (unsigned int)cpuid > (existingCPUs - 1)) {
@ -2114,11 +2188,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 +2220,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

View File

@ -49,6 +49,8 @@ in the source distribution for its full text.
#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
#include "FreqMeter.h"
#include "TempMeter.h"
#include "XUtils.h"
#include "linux/IOPriority.h"
#include "linux/IOPriorityPanel.h"
@ -70,16 +72,35 @@ 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 {
enum CapMode
{
CAP_MODE_OFF,
CAP_MODE_BASIC,
CAP_MODE_STRICT
};
#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},
@ -120,7 +141,9 @@ const SignalItem Platform_signals[] = {
const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
static enum { BAT_PROC, BAT_SYS, BAT_ERR } Platform_Battery_method = BAT_PROC;
static enum { BAT_PROC,
BAT_SYS,
BAT_ERR } Platform_Battery_method = BAT_PROC;
static time_t Platform_Battery_cacheTime;
static double Platform_Battery_cachePercent = NAN;
static ACPresence Platform_Battery_cacheIsOnAC;
@ -129,7 +152,8 @@ static ACPresence Platform_Battery_cacheIsOnAC;
static enum CapMode Platform_capabilitiesMode = CAP_MODE_BASIC;
#endif
static Htop_Reaction Platform_actionSetIOPriority(State* st) {
static Htop_Reaction Platform_actionSetIOPriority(State *st)
{
if (Settings_isReadonly())
return HTOP_OK;
@ -140,10 +164,12 @@ static Htop_Reaction Platform_actionSetIOPriority(State* st) {
IOPriority ioprio1 = p->ioPriority;
Panel *ioprioPanel = IOPriorityPanel_new(ioprio1);
const void *set = Action_pickFromVector(st, ioprioPanel, 20, true);
if (set) {
if (set)
{
IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel);
bool ok = MainPanel_foreachProcess(st->mainPanel, LinuxProcess_setIOPriority, (Arg){.i = ioprio2}, NULL);
if (!ok) {
if (!ok)
{
beep();
}
}
@ -151,8 +177,10 @@ static Htop_Reaction Platform_actionSetIOPriority(State* st) {
return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
}
static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) {
if (LinuxProcess_isAutogroupEnabled() == false) {
static bool Platform_changeAutogroupPriority(MainPanel *panel, int delta)
{
if (LinuxProcess_isAutogroupEnabled() == false)
{
beep();
return false;
}
@ -163,7 +191,8 @@ static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) {
return anyTagged;
}
static Htop_Reaction Platform_actionHigherAutogroupPriority(State* st) {
static Htop_Reaction Platform_actionHigherAutogroupPriority(State *st)
{
if (Settings_isReadonly())
return HTOP_OK;
@ -171,7 +200,8 @@ static Htop_Reaction Platform_actionHigherAutogroupPriority(State* st) {
return changed ? HTOP_REFRESH : HTOP_OK;
}
static Htop_Reaction Platform_actionLowerAutogroupPriority(State* st) {
static Htop_Reaction Platform_actionLowerAutogroupPriority(State *st)
{
if (Settings_isReadonly())
return HTOP_OK;
@ -179,7 +209,8 @@ static Htop_Reaction Platform_actionLowerAutogroupPriority(State* st) {
return changed ? HTOP_REFRESH : HTOP_OK;
}
void Platform_setBindings(Htop_Action* keys) {
void Platform_setBindings(Htop_Action *keys)
{
keys['i'] = Platform_actionSetIOPriority;
keys['{'] = Platform_actionLowerAutogroupPriority;
keys['}'] = Platform_actionHigherAutogroupPriority;
@ -201,6 +232,8 @@ const MeterClass* const Platform_meterTypes[] = {
&HugePageMeter_class,
&TasksMeter_class,
&UptimeMeter_class,
&FreqMeter_class,
&TempMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
&AllCPUsMeter_class,
@ -228,23 +261,78 @@ const MeterClass* const Platform_meterTypes[] = {
&NetworkIOMeter_class,
&SELinuxMeter_class,
&SystemdMeter_class,
NULL
};
NULL};
int Platform_getUptime() {
int Platform_getUptime()
{
double uptime = 0;
FILE *fd = fopen(PROCDIR "/uptime", "r");
if (fd) {
if (fd)
{
int n = fscanf(fd, "%64lf", &uptime);
fclose(fd);
if (n <= 0) {
if (n <= 0)
{
return 0;
}
}
return floor(uptime);
}
void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
float Platform_getTemp()
{
float ftemp = 0;
FILE *fd = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
if (!fd)
{
return ftemp;
}
int itemp = 0;
fscanf(fd, "%d", &itemp);
ftemp = itemp;
if (ftemp >= 1000)
{
ftemp /= 1000;
}
fclose(fd);
return ftemp;
}
float Platform_getFreq()
{
float freq = 0;
FILE *fd = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", "r");
if (!fd)
{
return freq;
}
int ifreq = 0;
fscanf(fd, "%d", &ifreq);
freq = ifreq;
fclose(fd);
if (freq < 10)
{
freq *= 1000000;
}
else
{
freq /= 1000000;
}
return freq;
}
void Platform_getLoadAverage(double *one, double *five, double *fifteen)
{
FILE *fd = fopen(PROCDIR "/loadavg", "r");
if (!fd)
goto err;
@ -266,7 +354,8 @@ err:
*fifteen = NAN;
}
int Platform_getMaxPid() {
int Platform_getMaxPid()
{
FILE *file = fopen(PROCDIR "/sys/kernel/pid_max", "r");
if (!file)
return -1;
@ -278,21 +367,24 @@ int Platform_getMaxPid() {
return maxPid;
}
double Platform_setCPUValues(Meter* this, unsigned int cpu) {
double Platform_setCPUValues(Meter *this, unsigned int cpu)
{
const LinuxProcessList *pl = (const LinuxProcessList *)this->pl;
const CPUData *cpuData = &(pl->cpuData[cpu]);
double total = (double)(cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod);
double percent;
double *v = this->values;
if (!cpuData->online) {
if (!cpuData->online)
{
this->curItems = 0;
return NAN;
}
v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;
v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;
if (this->pl->settings->detailedCPUTime) {
if (this->pl->settings->detailedCPUTime)
{
v[CPU_METER_KERNEL] = cpuData->systemPeriod / total * 100.0;
v[CPU_METER_IRQ] = cpuData->irqPeriod / total * 100.0;
v[CPU_METER_SOFTIRQ] = cpuData->softIrqPeriod / total * 100.0;
@ -300,19 +392,25 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
v[CPU_METER_GUEST] = cpuData->guestPeriod / total * 100.0;
v[CPU_METER_IOWAIT] = cpuData->ioWaitPeriod / total * 100.0;
this->curItems = 8;
if (this->pl->settings->accountGuestInCPUMeter) {
if (this->pl->settings->accountGuestInCPUMeter)
{
percent = v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6];
} else {
}
else
{
percent = v[0] + v[1] + v[2] + v[3] + v[4];
}
} else {
}
else
{
v[2] = cpuData->systemAllPeriod / total * 100.0;
v[3] = (cpuData->stealPeriod + cpuData->guestPeriod) / total * 100.0;
this->curItems = 4;
percent = v[0] + v[1] + v[2] + v[3];
}
percent = CLAMP(percent, 0.0, 100.0);
if (isnan(percent)) {
if (isnan(percent))
{
percent = 0.0;
}
@ -327,7 +425,8 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
return percent;
}
void Platform_setMemoryValues(Meter* this) {
void Platform_setMemoryValues(Meter *this)
{
const ProcessList *pl = this->pl;
const LinuxProcessList *lpl = (const LinuxProcessList *)pl;
@ -338,39 +437,50 @@ void Platform_setMemoryValues(Meter* this) {
this->values[3] = pl->cachedMem;
this->values[4] = pl->availableMem;
if (lpl->zfs.enabled != 0) {
this->values[0] -= lpl->zfs.size;
this->values[3] += lpl->zfs.size;
if (lpl->zfs.enabled != 0 && !Running_containerized)
{
// 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;
}
}
void Platform_setSwapValues(Meter* this) {
void Platform_setSwapValues(Meter *this)
{
const ProcessList *pl = this->pl;
this->total = pl->totalSwap;
this->values[0] = pl->usedSwap;
this->values[1] = pl->cachedSwap;
}
void Platform_setZramValues(Meter* this) {
void Platform_setZramValues(Meter *this)
{
const LinuxProcessList *lpl = (const LinuxProcessList *)this->pl;
this->total = lpl->zram.totalZram;
this->values[0] = lpl->zram.usedZramComp;
this->values[1] = lpl->zram.usedZramOrig;
}
void Platform_setZfsArcValues(Meter* this) {
void Platform_setZfsArcValues(Meter *this)
{
const LinuxProcessList *lpl = (const LinuxProcessList *)this->pl;
ZfsArcMeter_readStats(this, &(lpl->zfs));
}
void Platform_setZfsCompressedArcValues(Meter* this) {
void Platform_setZfsCompressedArcValues(Meter *this)
{
const LinuxProcessList *lpl = (const LinuxProcessList *)this->pl;
ZfsCompressedArcMeter_readStats(this, &(lpl->zfs));
}
char* Platform_getProcessEnv(pid_t pid) {
char *Platform_getProcessEnv(pid_t pid)
{
char procname[128];
xSnprintf(procname, sizeof(procname), PROCDIR "/%d/environ", pid);
FILE *fd = fopen(procname, "r");
@ -383,7 +493,8 @@ char* Platform_getProcessEnv(pid_t pid) {
size_t size = 0;
ssize_t bytes = 0;
do {
do
{
size += bytes;
capacity += 4096;
env = xRealloc(env, capacity);
@ -391,7 +502,8 @@ char* Platform_getProcessEnv(pid_t pid) {
fclose(fd);
if (bytes < 0) {
if (bytes < 0)
{
free(env);
return NULL;
}
@ -412,7 +524,8 @@ char* Platform_getProcessEnv(pid_t pid) {
* Based on implementation of lslocks from util-linux:
* https://sources.debian.org/src/util-linux/2.36-3/misc-utils/lslocks.c/#L162
*/
char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
char *Platform_getInodeFilename(pid_t pid, ino_t inode)
{
struct stat sb;
const struct dirent *de;
DIR *dirp;
@ -436,7 +549,8 @@ char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
if ((fd = dirfd(dirp)) < 0)
goto out;
while ((de = readdir(dirp))) {
while ((de = readdir(dirp)))
{
if (String_eq(de->d_name, ".") || String_eq(de->d_name, ".."))
continue;
@ -461,18 +575,21 @@ out:
return ret;
}
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
FileLocks_ProcessData *Platform_getProcessLocks(pid_t pid)
{
FileLocks_ProcessData *pdata = xCalloc(1, sizeof(FileLocks_ProcessData));
FILE *f = fopen(PROCDIR "/locks", "r");
if (!f) {
if (!f)
{
pdata->error = true;
return pdata;
}
char buffer[1024];
FileLocks_LockData **data_ref = &pdata->locks;
while(fgets(buffer, sizeof(buffer), f)) {
while (fgets(buffer, sizeof(buffer), f))
{
if (!strchr(buffer, '\n'))
continue;
@ -506,9 +623,12 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
data->dev[1] = lock_dev[1];
data->inode = lock_inode;
data->start = strtoull(lock_start, NULL, 10);
if (!String_eq(lock_end, "EOF")) {
if (!String_eq(lock_end, "EOF"))
{
data->end = strtoull(lock_end, NULL, 10);
} else {
}
else
{
data->end = ULLONG_MAX;
}
@ -520,17 +640,20 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
return pdata;
}
void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) {
void Platform_getPressureStall(const char *file, bool some, double *ten, double *sixty, double *threehundred)
{
*ten = *sixty = *threehundred = 0;
char procname[128];
xSnprintf(procname, sizeof(procname), PROCDIR "/pressure/%s", file);
FILE *fd = fopen(procname, "r");
if (!fd) {
if (!fd)
{
*ten = *sixty = *threehundred = NAN;
return;
}
int total = fscanf(fd, "some avg10=%32lf avg60=%32lf avg300=%32lf total=%*f ", ten, sixty, threehundred);
if (!some) {
if (!some)
{
total = fscanf(fd, "full avg10=%32lf avg60=%32lf avg300=%32lf total=%*f ", ten, sixty, threehundred);
}
(void)total;
@ -538,7 +661,8 @@ void Platform_getPressureStall(const char* file, bool some, double* ten, double*
fclose(fd);
}
bool Platform_getDiskIO(DiskIOData* data) {
bool Platform_getDiskIO(DiskIOData *data)
{
FILE *fd = fopen(PROCDIR "/diskstats", "r");
if (!fd)
return false;
@ -547,10 +671,12 @@ bool Platform_getDiskIO(DiskIOData* data) {
unsigned long long int read_sum = 0, write_sum = 0, timeSpend_sum = 0;
char lineBuffer[256];
while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
while (fgets(lineBuffer, sizeof(lineBuffer), fd))
{
char diskname[32];
unsigned long long int read_tmp, write_tmp, timeSpend_tmp;
if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu %*u %*u %llu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) {
if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu %*u %*u %llu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4)
{
if (String_startsWith(diskname, "dm-"))
continue;
@ -577,14 +703,16 @@ bool Platform_getDiskIO(DiskIOData* data) {
return true;
}
bool Platform_getNetworkIO(NetworkIOData* data) {
bool Platform_getNetworkIO(NetworkIOData *data)
{
FILE *fd = fopen(PROCDIR "/net/dev", "r");
if (!fd)
return false;
memset(data, 0, sizeof(NetworkIOData));
char lineBuffer[512];
while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
while (fgets(lineBuffer, sizeof(lineBuffer), fd))
{
char interfaceName[32];
unsigned long long int bytesReceived, packetsReceived, bytesTransmitted, packetsTransmitted;
if (sscanf(lineBuffer, "%31s %llu %llu %*u %*u %*u %*u %*u %*u %llu %llu",
@ -620,7 +748,8 @@ bool Platform_getNetworkIO(NetworkIOData* data) {
// READ FROM /proc
// ----------------------------------------
static double Platform_Battery_getProcBatInfo(void) {
static double Platform_Battery_getProcBatInfo(void)
{
DIR *batteryDir = opendir(PROC_BATTERY_DIR);
if (!batteryDir)
return NAN;
@ -629,7 +758,8 @@ static double Platform_Battery_getProcBatInfo(void) {
uint64_t totalRemain = 0;
struct dirent *dirEntry = NULL;
while ((dirEntry = readdir(batteryDir))) {
while ((dirEntry = readdir(batteryDir)))
{
const char *entryName = dirEntry->d_name;
if (!String_startsWith(entryName, "BAT"))
continue;
@ -651,13 +781,15 @@ static double Platform_Battery_getProcBatInfo(void) {
// Getting total charge for all batteries
char *buf = bufInfo;
while ((line = strsep(&buf, "\n")) != NULL) {
while ((line = strsep(&buf, "\n")) != NULL)
{
char field[100] = {0};
int val = 0;
if (2 != sscanf(line, "%99[^:]:%d", field, &val))
continue;
if (String_eq(field, "last full capacity")) {
if (String_eq(field, "last full capacity"))
{
totalFull += val;
break;
}
@ -665,13 +797,15 @@ static double Platform_Battery_getProcBatInfo(void) {
// Getting remaining charge for all batteries
buf = bufState;
while ((line = strsep(&buf, "\n")) != NULL) {
while ((line = strsep(&buf, "\n")) != NULL)
{
char field[100] = {0};
int val = 0;
if (2 != sscanf(line, "%99[^:]:%d", field, &val))
continue;
if (String_eq(field, "remaining capacity")) {
if (String_eq(field, "remaining capacity"))
{
totalRemain += val;
break;
}
@ -683,7 +817,8 @@ static double Platform_Battery_getProcBatInfo(void) {
return totalFull > 0 ? ((double)totalRemain * 100.0) / (double)totalFull : NAN;
}
static ACPresence procAcpiCheck(void) {
static ACPresence procAcpiCheck(void)
{
char buffer[1024] = {0};
ssize_t r = xReadfile(PROC_POWERSUPPLY_ACSTATE_FILE, buffer, sizeof(buffer));
if (r < 1)
@ -692,7 +827,8 @@ static ACPresence procAcpiCheck(void) {
return String_eq(buffer, "on-line") ? AC_PRESENT : AC_ABSENT;
}
static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {
static void Platform_Battery_getProcData(double *percent, ACPresence *isOnAC)
{
*isOnAC = procAcpiCheck();
*percent = AC_ERROR != *isOnAC ? Platform_Battery_getProcBatInfo() : NAN;
}
@ -701,7 +837,8 @@ static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {
// READ FROM /sys
// ----------------------------------------
static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
static void Platform_Battery_getSysData(double *percent, ACPresence *isOnAC)
{
*percent = NAN;
*isOnAC = AC_ERROR;
@ -712,18 +849,58 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
uint64_t totalFull = 0;
uint64_t totalRemain = 0;
struct dirent* dirEntry = NULL;
while ((dirEntry = readdir(dir))) {
const struct dirent *dirEntry;
while ((dirEntry = readdir(dir)))
{
const char *entryName = dirEntry->d_name;
if (String_startsWith(entryName, "BAT")) {
char buffer[1024] = {0};
char filePath[256];
xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/uevent", entryName);
ssize_t r = xReadfile(filePath, buffer, sizeof(buffer));
if (r < 0)
#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"))
{
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;
}
if (type == BAT)
{
char buffer[1024];
ssize_t r = xReadfileat(entryFd, "uevent", buffer, sizeof(buffer));
if (r < 0)
goto next;
bool full = false;
bool now = false;
@ -733,18 +910,21 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
const char *line;
char *buf = buffer;
while ((line = strsep(&buf, "\n")) != NULL) {
while ((line = strsep(&buf, "\n")) != NULL)
{
char field[100] = {0};
int val = 0;
if (2 != sscanf(line, "POWER_SUPPLY_%99[^=]=%d", field, &val))
continue;
if (String_eq(field, "CAPACITY")) {
if (String_eq(field, "CAPACITY"))
{
capacityLevel = val / 100.0;
continue;
}
if (String_eq(field, "ENERGY_FULL") || String_eq(field, "CHARGE_FULL")) {
if (String_eq(field, "ENERGY_FULL") || String_eq(field, "CHARGE_FULL"))
{
fullCharge = val;
totalFull += fullCharge;
full = true;
@ -753,7 +933,8 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
continue;
}
if (String_eq(field, "ENERGY_NOW") || String_eq(field, "CHARGE_NOW")) {
if (String_eq(field, "ENERGY_NOW") || String_eq(field, "CHARGE_NOW"))
{
totalRemain += val;
now = true;
if (full)
@ -764,19 +945,18 @@ 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;
goto next;
char filePath[256];
xSnprintf(filePath, sizeof(filePath), SYS_POWERSUPPLY_DIR "/%s/online", entryName);
ssize_t r = xReadfile(filePath, buffer, sizeof(buffer));
if (r < 1) {
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 +964,9 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
else if (buffer[0] == '1')
*isOnAC = AC_PRESENT;
}
next:
Compat_openatArgClose(entryFd);
}
closedir(dir);
@ -791,29 +974,36 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
*percent = totalFull > 0 ? ((double)totalRemain * 100.0) / (double)totalFull : NAN;
}
void Platform_getBattery(double* percent, ACPresence* isOnAC) {
void Platform_getBattery(double *percent, ACPresence *isOnAC)
{
time_t now = time(NULL);
// update battery reading is slow. Update it each 10 seconds only.
if (now < Platform_Battery_cacheTime + 10) {
if (now < Platform_Battery_cacheTime + 10)
{
*percent = Platform_Battery_cachePercent;
*isOnAC = Platform_Battery_cacheIsOnAC;
return;
}
if (Platform_Battery_method == BAT_PROC) {
if (Platform_Battery_method == BAT_PROC)
{
Platform_Battery_getProcData(percent, isOnAC);
if (isnan(*percent))
Platform_Battery_method = BAT_SYS;
}
if (Platform_Battery_method == BAT_SYS) {
if (Platform_Battery_method == BAT_SYS)
{
Platform_Battery_getSysData(percent, isOnAC);
if (isnan(*percent))
Platform_Battery_method = BAT_ERR;
}
if (Platform_Battery_method == BAT_ERR) {
if (Platform_Battery_method == BAT_ERR)
{
*percent = NAN;
*isOnAC = AC_ERROR;
} else {
}
else
{
*percent = CLAMP(*percent, 0.0, 100.0);
}
Platform_Battery_cachePercent = *percent;
@ -829,34 +1019,46 @@ void Platform_longOptionsUsage(const char* name)
" off - do not drop any capabilities\n"
" basic (default) - drop all capabilities not needed by %s\n"
" strict - drop all capabilities except those needed for\n"
" core functionality\n", name);
" core functionality\n",
name);
#else
(void)name;
#endif
}
CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) {
CommandLineStatus Platform_getLongOption(int opt, int argc, char **argv)
{
#ifndef HAVE_LIBCAP
(void)argc;
(void)argv;
#endif
switch (opt) {
switch (opt)
{
#ifdef HAVE_LIBCAP
case 160: {
case 160:
{
const char *mode = optarg;
if (!mode && optind < argc && argv[optind] != NULL &&
(argv[optind][0] != '\0' && argv[optind][0] != '-')) {
(argv[optind][0] != '\0' && argv[optind][0] != '-'))
{
mode = argv[optind++];
}
if (!mode || String_eq(mode, "basic")) {
if (!mode || String_eq(mode, "basic"))
{
Platform_capabilitiesMode = CAP_MODE_BASIC;
} else if (String_eq(mode, "off")) {
}
else if (String_eq(mode, "off"))
{
Platform_capabilitiesMode = CAP_MODE_OFF;
} else if (String_eq(mode, "strict")) {
}
else if (String_eq(mode, "strict"))
{
Platform_capabilitiesMode = CAP_MODE_STRICT;
} else {
}
else
{
fprintf(stderr, "Error: invalid capabilities mode \"%s\".\n", mode);
return STATUS_ERROR_EXIT;
}
@ -871,7 +1073,8 @@ CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) {
}
#ifdef HAVE_LIBCAP
static int dropCapabilities(enum CapMode mode) {
static int dropCapabilities(enum CapMode mode)
{
if (mode == CAP_MODE_OFF)
return 0;
@ -894,30 +1097,35 @@ static int dropCapabilities(enum CapMode mode) {
const size_t ncap = (mode == CAP_MODE_BASIC) ? ARRAYSIZE(keepcapsBasic) : ARRAYSIZE(keepcapsStrict);
cap_t caps = cap_init();
if (caps == NULL) {
if (caps == NULL)
{
fprintf(stderr, "Error: can not initialize capabilities: %s\n", strerror(errno));
return -1;
}
if (cap_clear(caps) < 0) {
if (cap_clear(caps) < 0)
{
fprintf(stderr, "Error: can not clear capabilities: %s\n", strerror(errno));
cap_free(caps);
return -1;
}
cap_t currCaps = cap_get_proc();
if (currCaps == NULL) {
if (currCaps == NULL)
{
fprintf(stderr, "Error: can not get current process capabilities: %s\n", strerror(errno));
cap_free(caps);
return -1;
}
for (size_t i = 0; i < ncap; i++) {
for (size_t i = 0; i < ncap; i++)
{
if (!CAP_IS_SUPPORTED(keepcaps[i]))
continue;
cap_flag_value_t current;
if (cap_get_flag(currCaps, keepcaps[i], CAP_PERMITTED, &current) < 0) {
if (cap_get_flag(currCaps, keepcaps[i], CAP_PERMITTED, &current) < 0)
{
fprintf(stderr, "Error: can not get current value of capability %d: %s\n", keepcaps[i], strerror(errno));
cap_free(currCaps);
cap_free(caps);
@ -927,14 +1135,16 @@ static int dropCapabilities(enum CapMode mode) {
if (current != CAP_SET)
continue;
if (cap_set_flag(caps, CAP_PERMITTED, 1, &keepcaps[i], CAP_SET) < 0) {
if (cap_set_flag(caps, CAP_PERMITTED, 1, &keepcaps[i], CAP_SET) < 0)
{
fprintf(stderr, "Error: can not set permitted capability %d: %s\n", keepcaps[i], strerror(errno));
cap_free(currCaps);
cap_free(caps);
return -1;
}
if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &keepcaps[i], CAP_SET) < 0) {
if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &keepcaps[i], CAP_SET) < 0)
{
fprintf(stderr, "Error: can not set effective capability %d: %s\n", keepcaps[i], strerror(errno));
cap_free(currCaps);
cap_free(caps);
@ -942,7 +1152,8 @@ static int dropCapabilities(enum CapMode mode) {
}
}
if (cap_set_proc(caps) < 0) {
if (cap_set_proc(caps) < 0)
{
fprintf(stderr, "Error: can not set process capabilities: %s\n", strerror(errno));
cap_free(currCaps);
cap_free(caps);
@ -956,13 +1167,15 @@ static int dropCapabilities(enum CapMode mode) {
}
#endif
bool Platform_init(void) {
bool Platform_init(void)
{
#ifdef HAVE_LIBCAP
if (dropCapabilities(Platform_capabilitiesMode) < 0)
return false;
#endif
if (access(PROCDIR, R_OK) != 0) {
if (access(PROCDIR, R_OK) != 0)
{
fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR);
return false;
}
@ -971,10 +1184,40 @@ 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 /proc") || String_startsWith(lineBuffer, "overlay "))
{
Running_containerized = true;
break;
}
}
fclose(fd);
} // if (fd)
return true;
}
void Platform_done(void) {
void Platform_done(void)
{
#ifdef HAVE_SENSORS_SENSORS_H
LibSensors_cleanup();
#endif

View File

@ -37,8 +37,9 @@ in the source distribution for its full text.
#define PATH_MAX 4096
#endif
extern const ScreenDefaults Platform_defaultScreens[];
extern const ProcessField Platform_defaultFields[];
extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@ -49,10 +50,16 @@ 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);
float Platform_getTemp(void);
float Platform_getFreq(void);
void Platform_getLoadAverage(double *one, double *five, double *fifteen);
int Platform_getMaxPid(void);
@ -83,11 +90,13 @@ bool Platform_getNetworkIO(NetworkIOData* data);
void Platform_getBattery(double *percent, ACPresence *isOnAC);
static inline void Platform_getHostname(char* buffer, size_t size) {
static inline void Platform_getHostname(char *buffer, size_t size)
{
Generic_hostname(buffer, size);
}
static inline void Platform_getRelease(char** string) {
static inline void Platform_getRelease(char **string)
{
*string = Generic_uname();
}
@ -102,11 +111,13 @@ void Platform_longOptionsUsage(const char* name);
CommandLineStatus Platform_getLongOption(int opt, int argc, char **argv);
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
static inline void Platform_gettime_realtime(struct timeval *tv, uint64_t *msec)
{
Generic_gettime_realtime(tv, msec);
}
static inline void Platform_gettime_monotonic(uint64_t* msec) {
static inline void Platform_gettime_monotonic(uint64_t *msec)
{
Generic_gettime_monotonic(msec);
}

View File

@ -219,7 +219,10 @@ static void updateViaExec(void) {
exit(1);
dup2(fdnull, STDERR_FILENO);
close(fdnull);
execlp("systemctl",
// 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",
@ -227,7 +230,7 @@ static void updateViaExec(void) {
"--property=NNames",
"--property=NJobs",
"--property=NInstalledJobs",
NULL);
(char *)NULL);
exit(127);
}
close(fdpair[1]);

View File

@ -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",

View File

@ -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));

View File

@ -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
@ -303,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);
}
@ -319,6 +333,7 @@ char* Platform_getProcessEnv(pid_t pid) {
env[size + 1] = 0;
}
end:
(void) kvm_close(kt);
return env;
}

View File

@ -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[];

View File

@ -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",

View File

@ -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;

View File

@ -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
@ -261,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);
}
@ -277,6 +291,7 @@ char* Platform_getProcessEnv(pid_t pid) {
env[size + 1] = 0;
}
end:
(void) kvm_close(kt);
return env;
}

View File

@ -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[];

View File

@ -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"

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,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, },

View File

@ -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]) {
@ -600,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))
@ -676,16 +679,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);

View File

@ -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 },
@ -167,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",
@ -499,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

@ -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[];

View File

@ -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,
@ -259,16 +267,21 @@ static int Platform_buildenv(void* accum, struct ps_prochandle* Phandle, uintptr
envAccum* accump = accum;
(void) Phandle;
(void) addr;
size_t thissz = strlen(str);
if ((thissz + 2) > (accump->capacity - accump->size)) {
accump->env = xRealloc(accump->env, accump->capacity *= 2);
}
if ((thissz + 2) > (accump->capacity - accump->size)) {
while ((thissz + 2) > (accump->capacity - accump->size)) {
if (accump->capacity > (SIZE_MAX / 2))
return 1;
accump->capacity *= 2;
accump->env = xRealloc(accump->env, accump->capacity);
}
strlcpy( accump->env + accump->size, str, (accump->capacity - accump->size));
strlcpy( accump->env + accump->size, str, accump->capacity - accump->size);
strncpy( accump->env + accump->size + thissz + 1, "\n", 2);
accump->size = accump->size + thissz + 1;
accump->size += thissz + 1;
return 0;
}
@ -291,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) {

Some files were not shown because too many files have changed in this diff Show More