aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--.github/workflows/ci.yml79
-rw-r--r--.gitignore8
-rw-r--r--.travis.yml7
-rw-r--r--Action.c275
-rw-r--r--Action.h22
-rw-r--r--Affinity.c32
-rw-r--r--Affinity.h20
-rw-r--r--AffinityPanel.c17
-rw-r--r--AffinityPanel.h5
-rw-r--r--AvailableColumnsPanel.c53
-rw-r--r--AvailableColumnsPanel.h4
-rw-r--r--AvailableMetersPanel.c102
-rw-r--r--AvailableMetersPanel.h10
-rw-r--r--BatteryMeter.c6
-rw-r--r--BatteryMeter.h1
-rw-r--r--CONTRIBUTING.md6
-rw-r--r--COPYING58
-rw-r--r--CPUMeter.c154
-rw-r--r--CPUMeter.h1
-rw-r--r--CRT.c335
-rw-r--r--CRT.h38
-rw-r--r--CategoriesPanel.c78
-rw-r--r--CategoriesPanel.h3
-rw-r--r--ChangeLog152
-rw-r--r--ClockMeter.c11
-rw-r--r--ClockMeter.h1
-rw-r--r--ColorsPanel.c11
-rw-r--r--ColorsPanel.h5
-rw-r--r--ColumnsPanel.c37
-rw-r--r--ColumnsPanel.h1
-rw-r--r--CommandLine.c400
-rw-r--r--CommandLine.h14
-rw-r--r--CommandScreen.c2
-rw-r--r--Compat.c36
-rw-r--r--Compat.h2
-rw-r--r--DateMeter.c14
-rw-r--r--DateMeter.h1
-rw-r--r--DateTimeMeter.c14
-rw-r--r--DateTimeMeter.h1
-rw-r--r--DiskIOMeter.c51
-rw-r--r--DiskIOMeter.h7
-rw-r--r--DisplayOptionsPanel.c21
-rw-r--r--DisplayOptionsPanel.h1
-rw-r--r--DynamicColumn.c66
-rw-r--r--DynamicColumn.h34
-rw-r--r--DynamicMeter.c131
-rw-r--r--DynamicMeter.h28
-rw-r--r--EnvScreen.c5
-rw-r--r--EnvScreen.h1
-rw-r--r--FunctionBar.h1
-rw-r--r--Hashtable.c7
-rw-r--r--Header.c246
-rw-r--r--Header.h22
-rw-r--r--HeaderLayout.h77
-rw-r--r--HeaderOptionsPanel.c87
-rw-r--r--HeaderOptionsPanel.h26
-rw-r--r--HostnameMeter.c8
-rw-r--r--HostnameMeter.h1
-rw-r--r--IncSet.c17
-rw-r--r--IncSet.h3
-rw-r--r--InfoScreen.c34
-rw-r--r--ListItem.c6
-rw-r--r--ListItem.h1
-rw-r--r--LoadAverageMeter.c41
-rw-r--r--LoadAverageMeter.h1
-rw-r--r--Macros.h37
-rw-r--r--MainPanel.c18
-rw-r--r--MainPanel.h2
-rw-r--r--Makefile.am238
-rw-r--r--MemoryMeter.c39
-rw-r--r--MemoryMeter.h1
-rw-r--r--MemorySwapMeter.c107
-rw-r--r--MemorySwapMeter.h15
-rw-r--r--Meter.c111
-rw-r--r--Meter.h31
-rw-r--r--MetersPanel.c5
-rw-r--r--NetworkIOMeter.c90
-rw-r--r--NetworkIOMeter.h8
-rw-r--r--Object.c4
-rw-r--r--Object.h9
-rw-r--r--OpenFilesScreen.h1
-rw-r--r--OptionItem.c2
-rw-r--r--Panel.c24
-rw-r--r--Panel.h10
-rw-r--r--Process.c1052
-rw-r--r--Process.h256
-rw-r--r--ProcessList.c153
-rw-r--r--ProcessList.h61
-rw-r--r--ProvideCurses.h2
-rw-r--r--README155
-rw-r--r--RichString.c85
-rw-r--r--RichString.h30
-rw-r--r--ScreenManager.c58
-rw-r--r--ScreenManager.h5
-rw-r--r--Settings.c419
-rw-r--r--Settings.h29
-rw-r--r--SignalsPanel.h1
-rw-r--r--SwapMeter.c22
-rw-r--r--SwapMeter.h1
-rw-r--r--SysArchMeter.c44
-rw-r--r--SysArchMeter.h14
-rw-r--r--TESTPLAN4
-rw-r--r--TasksMeter.c52
-rw-r--r--TasksMeter.h1
-rw-r--r--TraceScreen.c11
-rw-r--r--TraceScreen.h2
-rw-r--r--UptimeMeter.c8
-rw-r--r--UptimeMeter.h1
-rw-r--r--UsersTable.h1
-rw-r--r--Vector.h4
-rw-r--r--XUtils.c42
-rw-r--r--XUtils.h33
-rw-r--r--configure.ac690
-rw-r--r--darwin/DarwinProcess.c131
-rw-r--r--darwin/DarwinProcess.h7
-rw-r--r--darwin/DarwinProcessList.c43
-rw-r--r--darwin/DarwinProcessList.h4
-rw-r--r--darwin/Platform.c178
-rw-r--r--darwin/Platform.h68
-rw-r--r--darwin/ProcessField.h2
-rw-r--r--docs/styleguide.md17
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.c44
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.h15
-rw-r--r--dragonflybsd/DragonFlyBSDProcessList.c201
-rw-r--r--dragonflybsd/DragonFlyBSDProcessList.h16
-rw-r--r--dragonflybsd/Platform.c69
-rw-r--r--dragonflybsd/Platform.h59
-rw-r--r--dragonflybsd/ProcessField.h2
-rw-r--r--freebsd/FreeBSDProcess.c53
-rw-r--r--freebsd/FreeBSDProcess.h16
-rw-r--r--freebsd/FreeBSDProcessList.c332
-rw-r--r--freebsd/FreeBSDProcessList.h15
-rw-r--r--freebsd/Platform.c69
-rw-r--r--freebsd/Platform.h54
-rw-r--r--freebsd/ProcessField.h2
-rw-r--r--generic/gettime.c57
-rw-r--r--generic/gettime.h18
-rw-r--r--generic/hostname.c17
-rw-r--r--generic/hostname.h15
-rw-r--r--generic/openzfs_sysctl.c (renamed from zfs/openzfs_sysctl.c)4
-rw-r--r--generic/openzfs_sysctl.h (renamed from zfs/openzfs_sysctl.h)3
-rw-r--r--generic/uname.c98
-rw-r--r--generic/uname.h12
-rw-r--r--htop.1.in127
-rw-r--r--htop.c353
-rw-r--r--iwyu/htop.imp4
-rw-r--r--linux/HugePageMeter.c106
-rw-r--r--linux/HugePageMeter.h15
-rw-r--r--linux/IOPriority.h2
-rw-r--r--linux/IOPriorityPanel.c3
-rw-r--r--linux/IOPriorityPanel.h2
-rw-r--r--linux/LibSensors.c218
-rw-r--r--linux/LibSensors.h11
-rw-r--r--linux/LinuxProcess.c756
-rw-r--r--linux/LinuxProcess.h82
-rw-r--r--linux/LinuxProcessList.c970
-rw-r--r--linux/LinuxProcessList.h17
-rw-r--r--linux/Platform.c387
-rw-r--r--linux/Platform.h75
-rw-r--r--linux/PressureStallMeter.c20
-rw-r--r--linux/PressureStallMeter.h1
-rw-r--r--linux/ProcessField.h6
-rw-r--r--linux/SELinuxMeter.c11
-rw-r--r--linux/SELinuxMeter.h1
-rw-r--r--linux/SystemdMeter.c98
-rw-r--r--linux/SystemdMeter.h1
-rw-r--r--linux/ZramMeter.c8
-rw-r--r--linux/ZramMeter.h1
-rw-r--r--linux/ZramStats.h6
-rw-r--r--netbsd/NetBSDProcess.c262
-rw-r--r--netbsd/NetBSDProcess.h32
-rw-r--r--netbsd/NetBSDProcessList.c498
-rw-r--r--netbsd/NetBSDProcessList.h58
-rw-r--r--netbsd/Platform.c499
-rw-r--r--netbsd/Platform.h115
-rw-r--r--netbsd/ProcessField.h15
-rw-r--r--netbsd/README.md32
-rw-r--r--openbsd/OpenBSDProcess.c39
-rw-r--r--openbsd/OpenBSDProcess.h9
-rw-r--r--openbsd/OpenBSDProcessList.c240
-rw-r--r--openbsd/OpenBSDProcessList.h9
-rw-r--r--openbsd/Platform.c57
-rw-r--r--openbsd/Platform.h54
-rw-r--r--pcp-htop.5.in237
-rw-r--r--pcp-htop.c26
-rw-r--r--pcp/PCPDynamicColumn.c341
-rw-r--r--pcp/PCPDynamicColumn.h35
-rw-r--r--pcp/PCPDynamicMeter.c460
-rw-r--r--pcp/PCPDynamicMeter.h44
-rw-r--r--pcp/PCPMetric.c177
-rw-r--r--pcp/PCPMetric.h179
-rw-r--r--pcp/PCPProcess.c291
-rw-r--r--pcp/PCPProcess.h102
-rw-r--r--pcp/PCPProcessList.c706
-rw-r--r--pcp/PCPProcessList.h74
-rw-r--r--pcp/Platform.c804
-rw-r--r--pcp/Platform.h157
-rw-r--r--pcp/ProcessField.h51
-rw-r--r--pcp/columns/container10
-rw-r--r--pcp/columns/delayacct10
-rw-r--r--pcp/columns/fdcount10
-rw-r--r--pcp/columns/guest17
-rw-r--r--pcp/columns/memory39
-rw-r--r--pcp/columns/sched10
-rw-r--r--pcp/columns/swap15
-rw-r--r--pcp/columns/tcp31
-rw-r--r--pcp/columns/udp31
-rw-r--r--pcp/columns/wchan17
-rw-r--r--pcp/meters/entropy9
-rw-r--r--pcp/meters/freespace11
-rw-r--r--pcp/meters/ipc13
-rw-r--r--pcp/meters/locks15
-rw-r--r--pcp/meters/memcache11
-rw-r--r--pcp/meters/mysql71
-rw-r--r--pcp/meters/postfix20
-rw-r--r--pcp/meters/redis39
-rw-r--r--pcp/meters/tcp21
-rw-r--r--scripts/htop_suppressions.valgrind50
-rwxr-xr-xscripts/run_valgrind.sh2
-rw-r--r--solaris/Platform.c81
-rw-r--r--solaris/Platform.h80
-rw-r--r--solaris/ProcessField.h2
-rw-r--r--solaris/SolarisProcess.c49
-rw-r--r--solaris/SolarisProcess.h22
-rw-r--r--solaris/SolarisProcessList.c243
-rw-r--r--solaris/SolarisProcessList.h33
-rw-r--r--unsupported/Platform.c58
-rw-r--r--unsupported/Platform.h55
-rw-r--r--unsupported/UnsupportedProcess.c90
-rw-r--r--unsupported/UnsupportedProcess.h14
-rw-r--r--unsupported/UnsupportedProcessList.c44
-rw-r--r--unsupported/UnsupportedProcessList.h7
-rw-r--r--zfs/ZfsArcMeter.c13
-rw-r--r--zfs/ZfsArcMeter.h3
-rw-r--r--zfs/ZfsArcStats.c10
-rw-r--r--zfs/ZfsCompressedArcMeter.c25
-rw-r--r--zfs/ZfsCompressedArcMeter.h3
238 files changed, 14520 insertions, 4197 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..6ba9da6
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+open_collective: htop
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5de7c19..617ed30 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,11 +13,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
- run: sudo apt-get install libncursesw5-dev
+ run: sudo apt-get install --no-install-recommends libncursesw5-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-linux-affinity --disable-unicode --without-sensors
+ run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors
- name: Enable compatibility modes
run: |
sed -i 's/#define HAVE_FSTATAT 1/#undef HAVE_FSTATAT/g' config.h
@@ -26,90 +26,125 @@ jobs:
- name: Build
run: make -k
- name: Distcheck
- run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror --enable-linux-affinity --disable-unicode --without-sensors"
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror --enable-affinity --disable-unicode --disable-sensors"
build-ubuntu-latest-minimal-clang:
runs-on: ubuntu-latest
env:
- CC: clang-11
+ CC: clang-12
steps:
- uses: actions/checkout@v2
- name: install clang repo
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -
- sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main' -y
sudo apt-get update -q
- name: Install Dependencies
- run: sudo apt-get install clang-11 libncursesw5-dev
+ run: sudo apt-get install --no-install-recommends clang-12 libncursesw5-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-linux-affinity --disable-unicode --without-sensors
+ run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors
- name: Build
run: make -k
- name: Distcheck
- run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror --enable-linux-affinity --disable-unicode --without-sensors"
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror --enable-affinity --disable-unicode --disable-sensors"
build-ubuntu-latest-full-featured-gcc:
runs-on: ubuntu-latest
# Enable LTO, might trigger additional warnings on advanced inlining
env:
CFLAGS: -O3 -g -flto
- LDFLAGS: -O3 -g -flto
+ LDFLAGS: -O3 -g -flto -Wl,--as-needed
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
- run: sudo apt-get install libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev
+ run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors
+ run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities
- name: Build
run: make -k
- name: Distcheck
- run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors'
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities'
build-ubuntu-latest-full-featured-clang:
runs-on: ubuntu-latest
env:
- CC: clang-11
+ CC: clang-12
steps:
- uses: actions/checkout@v2
- name: install clang repo
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -
- sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main' -y
sudo apt-get update -q
- name: Install Dependencies
- run: sudo apt-get install clang-11 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev
+ run: sudo apt-get install --no-install-recommends clang-12 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors
+ run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities
- name: Build
run: make -k
- name: Distcheck
- run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors'
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities'
+
+ build-ubuntu-latest-gcc-static:
+ runs-on: ubuntu-latest
+ # Enable LTO, might trigger additional warnings on advanced inlining
+ env:
+ CFLAGS: -O3 -g -flto
+ LDFLAGS: -O3 -g -flto
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install Dependencies
+ run: sudo apt-get install --no-install-recommends libncursesw5-dev libtinfo-dev libgpm-dev libsensors4-dev libcap-dev
+ - name: Bootstrap
+ run: ./autogen.sh
+ - name: Configure
+ run: ./configure --enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities
+ - name: Build
+ run: make -k
+ - name: Distcheck
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities'
+
+ build-ubuntu-latest-pcp:
+ # Turns out 'ubuntu-latest' can be older than 20.04, we want PCP v5+
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install Dependencies
+ run: sudo apt-get install --no-install-recommends libpcp3-dev libncursesw5-dev libtinfo-dev libgpm-dev
+ - 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
+ - name: Build
+ run: make -k
build-ubuntu-latest-clang-analyzer:
runs-on: ubuntu-latest
env:
- CC: clang-11
+ CC: clang-12
steps:
- uses: actions/checkout@v2
- name: install clang repo
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -
- sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main' -y
sudo apt-get update -q
- name: Install Dependencies
- run: sudo apt-get install clang-11 clang-tools-11 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev
+ run: sudo apt-get install --no-install-recommends clang-12 clang-tools-12 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: scan-build-11 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors
+ run: scan-build-12 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-delayacct --enable-sensors --enable-capabilities
- name: Build
- run: scan-build-11 -analyze-headers --status-bugs make -j"$(nproc)"
+ run: scan-build-12 -analyze-headers --status-bugs make -j"$(nproc)"
build-macos-latest-clang:
runs-on: macOS-latest
diff --git a/.gitignore b/.gitignore
index 3d522b0..8262449 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
-# the binary:
+# the binaries:
htop
+pcp-htop
# all object files
*.o
@@ -36,6 +37,7 @@ config.sub
configure
depcomp
htop.1
+pcp-htop.5
install-sh
libtool
ltmain.sh
@@ -45,3 +47,7 @@ stamp-h1
# files related to valgrind/callgrind
callgrind.out.*
+
+# IDE workspace configurations
+/.idea/
+/.vscode/
diff --git a/.travis.yml b/.travis.yml
index 2c79fa8..f62e42f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,8 @@ os:
script:
- ./autogen.sh
- # clang might warn about C11 generic selections in isnan()
- - CFLAGS=-Wno-c11-extensions ./configure --enable-werror
+ - ./configure --enable-werror
- make -k
- - CFLAGS=-Wno-c11-extensions make distcheck DISTCHECK_CONFIGURE_FLAGS=--enable-werror
+ - make distcheck DISTCHECK_CONFIGURE_FLAGS=--enable-werror
+ - sudo make install
+ - make installcheck
diff --git a/Action.c b/Action.c
index 88ccf6d..1d373ef 100644
--- a/Action.c
+++ b/Action.c
@@ -13,9 +13,10 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdlib.h>
+#include "CRT.h"
#include "CategoriesPanel.h"
#include "CommandScreen.h"
-#include "CRT.h"
+#include "DynamicColumn.h"
#include "EnvScreen.h"
#include "FunctionBar.h"
#include "Hashtable.h"
@@ -34,26 +35,25 @@ in the source distribution for its full text.
#include "Vector.h"
#include "XUtils.h"
-#if (defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY))
+#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
#include "Affinity.h"
#include "AffinityPanel.h"
#endif
Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) {
- Panel* panel = st->panel;
+ MainPanel* mainPanel = st->mainPanel;
Header* header = st->header;
- Settings* settings = st->settings;
- int y = panel->y;
- ScreenManager* scr = ScreenManager_new(header, settings, st, false);
+ int y = ((Panel*)mainPanel)->y;
+ ScreenManager* scr = ScreenManager_new(header, st->settings, st, false);
scr->allowFocusChange = false;
- ScreenManager_add(scr, list, x - 1);
- ScreenManager_add(scr, panel, -1);
+ ScreenManager_add(scr, list, x);
+ ScreenManager_add(scr, (Panel*)mainPanel, -1);
Panel* panelFocus;
int ch;
bool unfollow = false;
- int pid = followProcess ? MainPanel_selectedPid((MainPanel*)panel) : -1;
+ int pid = followProcess ? MainPanel_selectedPid(mainPanel) : -1;
if (followProcess && header->pl->following == -1) {
header->pl->following = pid;
unfollow = true;
@@ -63,11 +63,11 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess)
header->pl->following = -1;
}
ScreenManager_delete(scr);
- Panel_move(panel, 0, y);
- Panel_resize(panel, COLS, LINES - y - 1);
+ Panel_move((Panel*)mainPanel, 0, y);
+ Panel_resize((Panel*)mainPanel, COLS, LINES - y - 1);
if (panelFocus == list && ch == 13) {
if (followProcess) {
- Process* selected = (Process*)Panel_getSelected(panel);
+ const Process* selected = (const Process*)Panel_getSelected((Panel*)mainPanel);
if (selected && selected->pid == pid)
return Panel_getSelected(list);
@@ -84,12 +84,8 @@ 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* panelCategories = CategoriesPanel_new(scr, st->settings, st->header, st->pl);
- ScreenManager_add(scr, (Panel*) panelCategories, 16);
- CategoriesPanel_makeMetersPage(panelCategories);
- Panel* panelFocus;
- int ch;
- ScreenManager_run(scr, &panelFocus, &ch);
+ CategoriesPanel_new(scr, st->settings, st->header, st->pl);
+ ScreenManager_run(scr, NULL, NULL);
ScreenManager_delete(scr);
if (st->settings->changed) {
Header_writeBackToSettings(st->header);
@@ -141,7 +137,7 @@ static bool expandCollapse(Panel* panel) {
}
static bool collapseIntoParent(Panel* panel) {
- Process* p = (Process*) Panel_getSelected(panel);
+ const Process* p = (Process*) Panel_getSelected(panel);
if (!p)
return false;
@@ -168,16 +164,25 @@ 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");
- ProcessField* fields = st->settings->fields;
+ const ProcessField* fields = st->settings->fields;
+ Hashtable* dynamicColumns = st->settings->dynamicColumns;
for (int i = 0; fields[i]; i++) {
- char* name = String_trim(Process_fields[fields[i]].name);
+ char* name = NULL;
+ if (fields[i] >= LAST_PROCESSFIELD) {
+ DynamicColumn* column = Hashtable_get(dynamicColumns, fields[i]);
+ if (!column)
+ continue;
+ name = xStrdup(column->caption ? column->caption : column->name);
+ } else {
+ name = String_trim(Process_fields[fields[i]].name);
+ }
Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i]));
if (fields[i] == Settings_getActiveSortKey(st->settings))
Panel_setSelected(sortPanel, i);
free(name);
}
- ListItem* field = (ListItem*) Action_pickFromVector(st, sortPanel, 15, false);
+ const ListItem* field = (const ListItem*) Action_pickFromVector(st, sortPanel, 14, false);
if (field) {
reaction |= Action_setSortKey(st->settings, field->key);
}
@@ -207,12 +212,12 @@ static Htop_Reaction actionSortByTime(State* st) {
static Htop_Reaction actionToggleKernelThreads(State* st) {
st->settings->hideKernelThreads = !st->settings->hideKernelThreads;
- return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS;
+ return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionToggleUserlandThreads(State* st) {
st->settings->hideUserlandThreads = !st->settings->hideUserlandThreads;
- return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS;
+ return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionToggleProgramPath(State* st) {
@@ -227,34 +232,47 @@ static Htop_Reaction actionToggleMergedCommand(State* st) {
static Htop_Reaction actionToggleTreeView(State* st) {
st->settings->treeView = !st->settings->treeView;
- if (st->settings->treeView) {
- st->settings->treeDirection = 1;
- }
- ProcessList_expandTree(st->pl);
+ if (!st->settings->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)
+ ProcessList_collapseAllBranches(st->pl);
+ else
+ ProcessList_expandTree(st->pl);
+ return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
+}
+
static Htop_Reaction actionIncFilter(State* st) {
- IncSet* inc = ((MainPanel*)st->panel)->inc;
- IncSet_activate(inc, INC_FILTER, st->panel);
+ IncSet* inc = (st->mainPanel)->inc;
+ IncSet_activate(inc, INC_FILTER, (Panel*)st->mainPanel);
st->pl->incFilter = IncSet_filter(inc);
return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionIncSearch(State* st) {
- IncSet_reset(((MainPanel*)st->panel)->inc, INC_SEARCH);
- IncSet_activate(((MainPanel*)st->panel)->inc, INC_SEARCH, st->panel);
+ IncSet_reset(st->mainPanel->inc, INC_SEARCH);
+ IncSet_activate(st->mainPanel->inc, INC_SEARCH, (Panel*)st->mainPanel);
return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionHigherPriority(State* st) {
- bool changed = changePriority((MainPanel*)st->panel, -1);
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ bool changed = changePriority(st->mainPanel, -1);
return changed ? HTOP_REFRESH : HTOP_OK;
}
static Htop_Reaction actionLowerPriority(State* st) {
- bool changed = changePriority((MainPanel*)st->panel, 1);
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ bool changed = changePriority(st->mainPanel, 1);
return changed ? HTOP_REFRESH : HTOP_OK;
}
@@ -262,11 +280,11 @@ static Htop_Reaction actionInvertSortOrder(State* st) {
Settings_invertSortOrder(st->settings);
if (st->pauseProcessUpdate)
ProcessList_sort(st->pl);
- return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
+ return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionExpandOrCollapse(State* st) {
- bool changed = expandCollapse(st->panel);
+ bool changed = expandCollapse((Panel*)st->mainPanel);
return changed ? HTOP_RECALCULATE : HTOP_OK;
}
@@ -274,7 +292,7 @@ static Htop_Reaction actionCollapseIntoParent(State* st) {
if (!st->settings->treeView) {
return HTOP_OK;
}
- bool changed = collapseIntoParent(st->panel);
+ bool changed = collapseIntoParent((Panel*)st->mainPanel);
return changed ? HTOP_RECALCULATE : HTOP_OK;
}
@@ -287,13 +305,14 @@ static Htop_Reaction actionQuit(ATTR_UNUSED State* st) {
}
static Htop_Reaction actionSetAffinity(State* st) {
- if (st->pl->cpuCount == 1)
+ if (Settings_isReadonly())
return HTOP_OK;
-#if (defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY))
- Panel* panel = st->panel;
+ if (st->pl->activeCPUs == 1)
+ return HTOP_OK;
- Process* p = (Process*) Panel_getSelected(panel);
+#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
+ const Process* p = (const Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -303,33 +322,36 @@ static Htop_Reaction actionSetAffinity(State* st) {
int width;
Panel* affinityPanel = AffinityPanel_new(st->pl, affinity1, &width);
- width += 1; /* we add a gap between the panels */
Affinity_delete(affinity1);
- void* set = Action_pickFromVector(st, affinityPanel, width, true);
+ const void* set = Action_pickFromVector(st, affinityPanel, width, true);
if (set) {
Affinity* affinity2 = AffinityPanel_getAffinity(affinityPanel, st->pl);
- bool ok = MainPanel_foreachProcess((MainPanel*)panel, Affinity_set, (Arg) { .v = affinity2 }, NULL);
+ bool ok = MainPanel_foreachProcess(st->mainPanel, Affinity_set, (Arg) { .v = affinity2 }, NULL);
if (!ok)
beep();
Affinity_delete(affinity2);
}
Object_delete(affinityPanel);
-#endif
return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
+#else
+ return HTOP_OK;
+#endif
+
}
static Htop_Reaction actionKill(State* st) {
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
Panel* signalsPanel = SignalsPanel_new();
- ListItem* sgn = (ListItem*) Action_pickFromVector(st, signalsPanel, 15, true);
- if (sgn) {
- if (sgn->key != 0) {
- Panel_setHeader(st->panel, "Sending...");
- Panel_draw(st->panel, false, true, true, State_hideFunctionBar(st));
- refresh();
- MainPanel_foreachProcess((MainPanel*)st->panel, Process_sendSignal, (Arg) { .i = sgn->key }, NULL);
- napms(500);
- }
+ const ListItem* sgn = (ListItem*) Action_pickFromVector(st, signalsPanel, 14, true);
+ if (sgn && sgn->key != 0) {
+ Panel_setHeader((Panel*)st->mainPanel, "Sending...");
+ Panel_draw((Panel*)st->mainPanel, false, true, true, State_hideFunctionBar(st));
+ refresh();
+ MainPanel_foreachProcess(st->mainPanel, Process_sendSignal, (Arg) { .i = sgn->key }, NULL);
+ napms(500);
}
Panel_delete((Object*)signalsPanel);
return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
@@ -342,7 +364,7 @@ static Htop_Reaction actionFilterByUser(State* st) {
Vector_insertionSort(usersPanel->items);
ListItem* allUsers = ListItem_new("All users", -1);
Panel_insert(usersPanel, 0, (Object*) allUsers);
- ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 20, false);
+ const ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 19, false);
if (picked) {
if (picked == allUsers) {
st->pl->userId = (uid_t)-1;
@@ -355,22 +377,21 @@ static Htop_Reaction actionFilterByUser(State* st) {
}
Htop_Reaction Action_follow(State* st) {
- st->pl->following = MainPanel_selectedPid((MainPanel*)st->panel);
- Panel_setSelectionColor(st->panel, PANEL_SELECTION_FOLLOW);
+ st->pl->following = MainPanel_selectedPid(st->mainPanel);
+ Panel_setSelectionColor((Panel*)st->mainPanel, PANEL_SELECTION_FOLLOW);
return HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionSetup(State* st) {
Action_runSetup(st);
- // TODO: shouldn't need this, colors should be dynamic
- int headerHeight = Header_calculateHeight(st->header);
- Panel_move(st->panel, 0, headerHeight);
- Panel_resize(st->panel, COLS, LINES-headerHeight-1);
- return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
+ return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR | HTOP_RESIZE;
}
static Htop_Reaction actionLsof(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -383,8 +404,9 @@ static Htop_Reaction actionLsof(State* st) {
}
static Htop_Reaction actionShowLocks(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
- if (!p) return HTOP_OK;
+ const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
+ if (!p)
+ return HTOP_OK;
ProcessLocksScreen* pls = ProcessLocksScreen_new(p);
InfoScreen_run((InfoScreen*)pls);
ProcessLocksScreen_delete((Object*)pls);
@@ -394,7 +416,10 @@ static Htop_Reaction actionShowLocks(State* st) {
}
static Htop_Reaction actionStrace(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -410,12 +435,12 @@ static Htop_Reaction actionStrace(State* st) {
}
static Htop_Reaction actionTag(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
Process_toggleTag(p);
- Panel_onKey(st->panel, KEY_DOWN);
+ Panel_onKey((Panel*)st->mainPanel, KEY_DOWN);
return HTOP_OK;
}
@@ -431,49 +456,51 @@ static Htop_Reaction actionTogglePauseProcessUpdate(State* st) {
static const struct {
const char* key;
+ bool roInactive;
const char* info;
} helpLeft[] = {
- { .key = " Arrows: ", .info = "scroll process list" },
- { .key = " Digits: ", .info = "incremental PID search" },
- { .key = " F3 /: ", .info = "incremental name search" },
- { .key = " F4 \\: ",.info = "incremental name filtering" },
- { .key = " F5 t: ", .info = "tree view" },
- { .key = " p: ", .info = "toggle program path" },
- { .key = " m: ", .info = "toggle merged command" },
- { .key = " Z: ", .info = "pause/resume process updates" },
- { .key = " u: ", .info = "show processes of a single user" },
- { .key = " H: ", .info = "hide/show user process threads" },
- { .key = " K: ", .info = "hide/show kernel threads" },
- { .key = " F: ", .info = "cursor follows process" },
- { .key = " + -: ", .info = "expand/collapse tree" },
- { .key = "N P M T: ", .info = "sort by PID, CPU%, MEM% or TIME" },
- { .key = " I: ", .info = "invert sort order" },
- { .key = " F6 > .: ", .info = "select sort column" },
+ { .key = " Arrows: ", .roInactive = false, .info = "scroll process list" },
+ { .key = " Digits: ", .roInactive = false, .info = "incremental PID search" },
+ { .key = " F3 /: ", .roInactive = false, .info = "incremental name search" },
+ { .key = " F4 \\: ", .roInactive = false, .info = "incremental name filtering" },
+ { .key = " F5 t: ", .roInactive = false, .info = "tree view" },
+ { .key = " p: ", .roInactive = false, .info = "toggle program path" },
+ { .key = " m: ", .roInactive = false, .info = "toggle merged command" },
+ { .key = " Z: ", .roInactive = false, .info = "pause/resume process updates" },
+ { .key = " u: ", .roInactive = false, .info = "show processes of a single user" },
+ { .key = " H: ", .roInactive = false, .info = "hide/show user process threads" },
+ { .key = " K: ", .roInactive = false, .info = "hide/show kernel threads" },
+ { .key = " F: ", .roInactive = false, .info = "cursor follows process" },
+ { .key = " + - *: ", .roInactive = false, .info = "expand/collapse tree/toggle all" },
+ { .key = "N P M T: ", .roInactive = false, .info = "sort by PID, CPU%, MEM% or TIME" },
+ { .key = " I: ", .roInactive = false, .info = "invert sort order" },
+ { .key = " F6 > .: ", .roInactive = false, .info = "select sort column" },
{ .key = NULL, .info = NULL }
};
static const struct {
const char* key;
+ bool roInactive;
const char* info;
} helpRight[] = {
- { .key = " Space: ", .info = "tag process" },
- { .key = " c: ", .info = "tag process and its children" },
- { .key = " U: ", .info = "untag all processes" },
- { .key = " F9 k: ", .info = "kill process/tagged processes" },
- { .key = " F7 ]: ", .info = "higher priority (root only)" },
- { .key = " F8 [: ", .info = "lower priority (+ nice)" },
-#if (defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY))
- { .key = " a: ", .info = "set CPU affinity" },
+ { .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)" },
+#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
+ { .key = " a: ", .roInactive = true, .info = "set CPU affinity" },
#endif
- { .key = " e: ", .info = "show process environment" },
- { .key = " i: ", .info = "set IO priority" },
- { .key = " l: ", .info = "list open files with lsof" },
- { .key = " x: ", .info = "list file locks of process" },
- { .key = " s: ", .info = "trace syscalls with strace" },
- { .key = " w: ", .info = "wrap process command in multiple lines" },
- { .key = " F2 C S: ", .info = "setup" },
- { .key = " F1 h: ", .info = "show this help screen" },
- { .key = " F10 q: ", .info = "quit" },
+ { .key = " e: ", .roInactive = false, .info = "show process environment" },
+ { .key = " i: ", .roInactive = true, .info = "set IO priority" },
+ { .key = " l: ", .roInactive = true, .info = "list open files with lsof" },
+ { .key = " x: ", .roInactive = false, .info = "list file locks of process" },
+ { .key = " s: ", .roInactive = true, .info = "trace syscalls with strace" },
+ { .key = " w: ", .roInactive = false, .info = "wrap process command in multiple lines" },
+ { .key = " F2 C S: ", .roInactive = false, .info = "setup" },
+ { .key = " F1 h ?: ", .roInactive = false, .info = "show this help screen" },
+ { .key = " F10 q: ", .roInactive = false, .info = "quit" },
{ .key = NULL, .info = NULL }
};
@@ -483,8 +510,6 @@ static inline void addattrstr( int attr, const char* str) {
}
static Htop_Reaction actionHelp(State* st) {
- Settings* settings = st->settings;
-
clear();
attrset(CRT_colors[HELP_BOLD]);
@@ -501,7 +526,7 @@ static Htop_Reaction actionHelp(State* st) {
mvaddstr(line++, 0, "CPU usage bar: ");
addattrstr(CRT_colors[BAR_BORDER], "[");
- if (settings->detailedCPUTime) {
+ 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("/");
@@ -515,8 +540,8 @@ static Htop_Reaction actionHelp(State* st) {
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], "virtualiz");
- addattrstr(CRT_colors[BAR_SHADOW], " used%");
+ addattrstr(CRT_colors[CPU_GUEST], "virtualized");
+ addattrstr(CRT_colors[BAR_SHADOW], " used%");
}
addattrstr(CRT_colors[BAR_BORDER], "]");
attrset(CRT_colors[DEFAULT_COLOR]);
@@ -524,14 +549,21 @@ static Htop_Reaction actionHelp(State* st) {
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");
+ addattrstr(CRT_colors[BAR_SHADOW], " used/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");
+#ifdef HTOP_LINUX
+ addattrstr(CRT_colors[BAR_SHADOW], "/");
+ addattrstr(CRT_colors[SWAP_CACHE], "cache");
+ addattrstr(CRT_colors[BAR_SHADOW], " used/total");
+#else
addattrstr(CRT_colors[BAR_SHADOW], " used/total");
+#endif
addattrstr(CRT_colors[BAR_BORDER], "]");
attrset(CRT_colors[DEFAULT_COLOR]);
mvaddstr(line++, 0, "Type and layout of header meters are configurable in the setup screen.");
@@ -544,26 +576,28 @@ static Htop_Reaction actionHelp(State* st) {
line++;
+ const bool readonly = Settings_isReadonly();
+
int item;
for (item = 0; helpLeft[item].key; item++) {
- attrset(CRT_colors[DEFAULT_COLOR]);
+ attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[DEFAULT_COLOR]);
mvaddstr(line + item, 10, helpLeft[item].info);
- attrset(CRT_colors[HELP_BOLD]);
+ attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[HELP_BOLD]);
mvaddstr(line + item, 1, helpLeft[item].key);
if (String_eq(helpLeft[item].key, " H: ")) {
- attrset(CRT_colors[PROCESS_THREAD]);
+ attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[PROCESS_THREAD]);
mvaddstr(line + item, 33, "threads");
} else if (String_eq(helpLeft[item].key, " K: ")) {
- attrset(CRT_colors[PROCESS_THREAD]);
+ attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[PROCESS_THREAD]);
mvaddstr(line + item, 27, "threads");
}
}
int leftHelpItems = item;
for (item = 0; helpRight[item].key; item++) {
- attrset(CRT_colors[HELP_BOLD]);
+ attrset((helpRight[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[HELP_BOLD]);
mvaddstr(line + item, 41, helpRight[item].key);
- attrset(CRT_colors[DEFAULT_COLOR]);
+ attrset((helpRight[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[DEFAULT_COLOR]);
mvaddstr(line + item, 50, helpRight[item].info);
}
line += MAXIMUM(leftHelpItems, item);
@@ -576,28 +610,28 @@ static Htop_Reaction actionHelp(State* st) {
CRT_readKey();
clear();
- return HTOP_RECALCULATE | HTOP_REDRAW_BAR;
+ return HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionUntagAll(State* st) {
- for (int i = 0; i < Panel_size(st->panel); i++) {
- Process* p = (Process*) Panel_get(st->panel, i);
+ for (int i = 0; i < Panel_size((Panel*)st->mainPanel); i++) {
+ Process* p = (Process*) Panel_get((Panel*)st->mainPanel, i);
p->tag = false;
}
return HTOP_REFRESH;
}
static Htop_Reaction actionTagAllChildren(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
- tagAllChildren(st->panel, p);
+ tagAllChildren((Panel*)st->mainPanel, p);
return HTOP_OK;
}
static Htop_Reaction actionShowEnvScreen(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -610,7 +644,7 @@ static Htop_Reaction actionShowEnvScreen(State* st) {
}
static Htop_Reaction actionShowCommandScreen(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -624,6 +658,7 @@ static Htop_Reaction actionShowCommandScreen(State* st) {
void Action_setBindings(Htop_Action* keys) {
keys[' '] = actionTag;
+ keys['*'] = actionExpandOrCollapseAllBranches;
keys['+'] = actionExpandOrCollapse;
keys[','] = actionSetSortColumn;
keys['-'] = actionExpandOrCollapse;
diff --git a/Action.h b/Action.h
index 02d9030..4a59072 100644
--- a/Action.h
+++ b/Action.h
@@ -20,22 +20,26 @@ in the source distribution for its full text.
#include "Settings.h"
#include "UsersTable.h"
+
typedef enum {
- HTOP_OK = 0x00,
- HTOP_REFRESH = 0x01,
- HTOP_RECALCULATE = 0x03, // implies HTOP_REFRESH
- HTOP_SAVE_SETTINGS = 0x04,
- HTOP_KEEP_FOLLOWING = 0x08,
- HTOP_QUIT = 0x10,
- HTOP_REDRAW_BAR = 0x20,
- HTOP_UPDATE_PANELHDR = 0x41, // implies HTOP_REFRESH
+ HTOP_OK = 0x00,
+ HTOP_REFRESH = 0x01,
+ HTOP_RECALCULATE = 0x02 | HTOP_REFRESH,
+ HTOP_SAVE_SETTINGS = 0x04,
+ HTOP_KEEP_FOLLOWING = 0x08,
+ HTOP_QUIT = 0x10,
+ HTOP_REDRAW_BAR = 0x20,
+ HTOP_UPDATE_PANELHDR = 0x40 | HTOP_REFRESH,
+ HTOP_RESIZE = 0x80 | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR,
} Htop_Reaction;
+struct MainPanel_; // IWYU pragma: keep
+
typedef struct State_ {
Settings* settings;
UsersTable* ut;
ProcessList* pl;
- Panel* panel;
+ struct MainPanel_* mainPanel;
Header* header;
bool pauseProcessUpdate;
bool hideProcessSelection;
diff --git a/Affinity.c b/Affinity.c
index c157885..c1e73cc 100644
--- a/Affinity.c
+++ b/Affinity.c
@@ -14,7 +14,7 @@ in the source distribution for its full text.
#include "XUtils.h"
-#ifdef HAVE_LIBHWLOC
+#if defined(HAVE_LIBHWLOC)
#include <hwloc.h>
#include <hwloc/bitmap.h>
#ifdef __linux__
@@ -22,7 +22,7 @@ in the source distribution for its full text.
#else
#define HTOP_HWLOC_CPUBIND_FLAG HWLOC_CPUBIND_PROCESS
#endif
-#elif defined(HAVE_LINUX_AFFINITY)
+#elif defined(HAVE_AFFINITY)
#include <sched.h>
#endif
@@ -30,7 +30,7 @@ in the source distribution for its full text.
Affinity* Affinity_new(ProcessList* pl) {
Affinity* this = xCalloc(1, sizeof(Affinity));
this->size = 8;
- this->cpus = xCalloc(this->size, sizeof(int));
+ this->cpus = xCalloc(this->size, sizeof(unsigned int));
this->pl = pl;
return this;
}
@@ -40,32 +40,32 @@ void Affinity_delete(Affinity* this) {
free(this);
}
-void Affinity_add(Affinity* this, int id) {
+void Affinity_add(Affinity* this, unsigned int id) {
if (this->used == this->size) {
this->size *= 2;
- this->cpus = xRealloc(this->cpus, sizeof(int) * this->size);
+ this->cpus = xRealloc(this->cpus, sizeof(unsigned int) * this->size);
}
this->cpus[this->used] = id;
this->used++;
}
-#ifdef HAVE_LIBHWLOC
+#if defined(HAVE_LIBHWLOC)
-Affinity* Affinity_get(Process* proc, ProcessList* pl) {
+Affinity* Affinity_get(const Process* proc, ProcessList* pl) {
hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
bool ok = (hwloc_get_proc_cpubind(pl->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);
Affinity* affinity = NULL;
if (ok) {
affinity = Affinity_new(pl);
if (hwloc_bitmap_last(cpuset) == -1) {
- for (int i = 0; i < pl->cpuCount; i++) {
+ for (unsigned int i = 0; i < pl->existingCPUs; i++) {
Affinity_add(affinity, i);
}
} else {
- unsigned int id;
- hwloc_bitmap_foreach_begin(id, cpuset);
- Affinity_add(affinity, id);
+ int id;
+ hwloc_bitmap_foreach_begin(id, cpuset)
+ Affinity_add(affinity, (unsigned)id);
hwloc_bitmap_foreach_end();
}
}
@@ -76,7 +76,7 @@ Affinity* Affinity_get(Process* proc, ProcessList* pl) {
bool Affinity_set(Process* proc, Arg arg) {
Affinity* this = arg.v;
hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
- for (int i = 0; i < this->used; i++) {
+ for (unsigned int i = 0; i < this->used; i++) {
hwloc_bitmap_set(cpuset, this->cpus[i]);
}
bool ok = (hwloc_set_proc_cpubind(this->pl->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);
@@ -84,16 +84,16 @@ bool Affinity_set(Process* proc, Arg arg) {
return ok;
}
-#elif defined(HAVE_LINUX_AFFINITY)
+#elif defined(HAVE_AFFINITY)
-Affinity* Affinity_get(Process* proc, ProcessList* pl) {
+Affinity* Affinity_get(const Process* proc, ProcessList* pl) {
cpu_set_t cpuset;
bool ok = (sched_getaffinity(proc->pid, sizeof(cpu_set_t), &cpuset) == 0);
if (!ok)
return NULL;
Affinity* affinity = Affinity_new(pl);
- for (int i = 0; i < pl->cpuCount; i++) {
+ for (unsigned int i = 0; i < pl->existingCPUs; i++) {
if (CPU_ISSET(i, &cpuset)) {
Affinity_add(affinity, i);
}
@@ -105,7 +105,7 @@ bool Affinity_set(Process* proc, Arg arg) {
Affinity* this = arg.v;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
- for (int i = 0; i < this->used; i++) {
+ for (unsigned int i = 0; i < this->used; i++) {
CPU_SET(this->cpus[i], &cpuset);
}
bool ok = (sched_setaffinity(proc->pid, sizeof(unsigned long), &cpuset) == 0);
diff --git a/Affinity.h b/Affinity.h
index 97c8e46..cf08cce 100644
--- a/Affinity.h
+++ b/Affinity.h
@@ -12,7 +12,7 @@ in the source distribution for its full text.
#include "ProcessList.h"
-#if defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY)
+#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)
#include <stdbool.h>
#include "Object.h"
@@ -20,30 +20,30 @@ in the source distribution for its full text.
#endif
-#if defined(HAVE_LIBHWLOC) && defined(HAVE_LINUX_AFFINITY)
-#error hwloc and linux affinity are mutual exclusive.
+#if defined(HAVE_LIBHWLOC) && defined(HAVE_AFFINITY)
+#error hwloc and affinity support are mutual exclusive.
#endif
typedef struct Affinity_ {
ProcessList* pl;
- int size;
- int used;
- int* cpus;
+ unsigned int size;
+ unsigned int used;
+ unsigned int* cpus;
} Affinity;
Affinity* Affinity_new(ProcessList* pl);
void Affinity_delete(Affinity* this);
-void Affinity_add(Affinity* this, int id);
+void Affinity_add(Affinity* this, unsigned int id);
-#if defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY)
+#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)
-Affinity* Affinity_get(Process* proc, ProcessList* pl);
+Affinity* Affinity_get(const Process* proc, ProcessList* pl);
bool Affinity_set(Process* proc, Arg arg);
-#endif /* HAVE_LIBHWLOC || HAVE_LINUX_AFFINITY */
+#endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */
#endif
diff --git a/AffinityPanel.c b/AffinityPanel.c
index a4d6361..d50d554 100644
--- a/AffinityPanel.c
+++ b/AffinityPanel.c
@@ -357,7 +357,7 @@ static const char* const AffinityPanelFunctions[] = {
static const char* const AffinityPanelKeys[] = {"Enter", "Esc", "F1", "F2", "F3"};
static const int AffinityPanelEvents[] = {13, 27, KEY_F(1), KEY_F(2), KEY_F(3)};
-Panel* AffinityPanel_new(ProcessList* pl, Affinity* affinity, int* width) {
+Panel* AffinityPanel_new(ProcessList* pl, const Affinity* affinity, int* width) {
AffinityPanel* this = AllocThis(AffinityPanel);
Panel* super = (Panel*) this;
Panel_init(super, 1, 1, 1, 1, Class(MaskItem), false, FunctionBar_new(AffinityPanelFunctions, AffinityPanelKeys, AffinityPanelEvents));
@@ -382,8 +382,11 @@ Panel* AffinityPanel_new(ProcessList* pl, Affinity* affinity, int* width) {
Panel_setHeader(super, "Use CPUs:");
- int curCpu = 0;
- for (int i = 0; i < pl->cpuCount; i++) {
+ unsigned int curCpu = 0;
+ for (unsigned int i = 0; i < pl->existingCPUs; i++) {
+ if (!ProcessList_isCPUonline(this->pl, i))
+ continue;
+
char number[16];
xSnprintf(number, 9, "CPU %d", Settings_cpuId(pl->settings, i));
unsigned cpu_width = 4 + strlen(number);
@@ -418,17 +421,17 @@ Panel* AffinityPanel_new(ProcessList* pl, Affinity* affinity, int* width) {
}
Affinity* AffinityPanel_getAffinity(Panel* super, ProcessList* pl) {
- AffinityPanel* this = (AffinityPanel*) super;
+ const AffinityPanel* this = (AffinityPanel*) super;
Affinity* affinity = Affinity_new(pl);
#ifdef HAVE_LIBHWLOC
int i;
hwloc_bitmap_foreach_begin(i, this->workCpuset)
- Affinity_add(affinity, i);
+ Affinity_add(affinity, (unsigned)i);
hwloc_bitmap_foreach_end();
#else
- for (int i = 0; i < this->pl->cpuCount; i++) {
- MaskItem* item = (MaskItem*)Vector_get(this->cpuids, i);
+ for (int i = 0; i < Vector_size(this->cpuids); i++) {
+ const MaskItem* item = (const MaskItem*)Vector_get(this->cpuids, i);
if (item->value) {
Affinity_add(affinity, item->cpu);
}
diff --git a/AffinityPanel.h b/AffinityPanel.h
index fdefeae..183e447 100644
--- a/AffinityPanel.h
+++ b/AffinityPanel.h
@@ -7,13 +7,14 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "Panel.h"
#include "Affinity.h"
+#include "Panel.h"
#include "ProcessList.h"
+
extern const PanelClass AffinityPanel_class;
-Panel* AffinityPanel_new(ProcessList* pl, Affinity* affinity, int* width);
+Panel* AffinityPanel_new(ProcessList* pl, const Affinity* affinity, int* width);
Affinity* AffinityPanel_getAffinity(Panel* super, ProcessList* pl);
diff --git a/AvailableColumnsPanel.c b/AvailableColumnsPanel.c
index fb0357c..08cbb17 100644
--- a/AvailableColumnsPanel.c
+++ b/AvailableColumnsPanel.c
@@ -7,15 +7,17 @@ in the source distribution for its full text.
#include "AvailableColumnsPanel.h"
+#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include "ColumnsPanel.h"
+#include "DynamicColumn.h"
#include "FunctionBar.h"
+#include "Hashtable.h"
#include "ListItem.h"
#include "Object.h"
-#include "Platform.h"
#include "Process.h"
#include "ProvideCurses.h"
#include "XUtils.h"
@@ -30,6 +32,15 @@ static void AvailableColumnsPanel_delete(Object* object) {
free(this);
}
+static void AvailableColumnsPanel_insert(AvailableColumnsPanel* this, int at, int key) {
+ const char* name;
+ if (key >= LAST_PROCESSFIELD)
+ name = DynamicColumn_init(key);
+ else
+ name = Process_fields[key].name;
+ Panel_insert(this->columns, at, (Object*) ListItem_new(name, key));
+}
+
static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) {
AvailableColumnsPanel* this = (AvailableColumnsPanel*) super;
HandlerResult result = IGNORED;
@@ -43,10 +54,9 @@ static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) {
if (!selected)
break;
- int key = selected->key;
int at = Panel_getSelectedIndex(this->columns);
- Panel_insert(this->columns, at, (Object*) ListItem_new(Process_fields[key].name, key));
- Panel_setSelected(this->columns, at+1);
+ AvailableColumnsPanel_insert(this, at, selected->key);
+ Panel_setSelected(this->columns, at + 1);
ColumnsPanel_update(this->columns);
result = HANDLED;
break;
@@ -69,14 +79,25 @@ const PanelClass AvailableColumnsPanel_class = {
.eventHandler = AvailableColumnsPanel_eventHandler
};
-AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns) {
- AvailableColumnsPanel* this = AllocThis(AvailableColumnsPanel);
- Panel* super = (Panel*) this;
- FunctionBar* fuBar = FunctionBar_new(AvailableColumnsFunctions, NULL, NULL);
- Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
+static void AvailableColumnsPanel_addDynamicColumn(ht_key_t key, void* value, void* data) {
+ const DynamicColumn* column = (const DynamicColumn*) value;
+ Panel* super = (Panel*) data;
+ const char* title = column->caption ? column->caption : column->heading;
+ if (!title)
+ title = column->name; // fallback to the only mandatory field
+ char description[256];
+ xSnprintf(description, sizeof(description), "%s - %s", title, column->description);
+ Panel_add(super, (Object*) ListItem_new(description, key));
+}
- Panel_setHeader(super, "Available Columns");
+// Handle DynamicColumns entries in the AvailableColumnsPanel
+static void AvailableColumnsPanel_addDynamicColumns(Panel* super, Hashtable* dynamicColumns) {
+ assert(dynamicColumns);
+ Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, super);
+}
+// Handle remaining Platform Meter entries in the AvailableColumnsPanel
+static void AvailableColumnsPanel_addPlatformColumn(Panel* super) {
for (int i = 1; i < LAST_PROCESSFIELD; i++) {
if (i != COMM && Process_fields[i].description) {
char description[256];
@@ -84,6 +105,18 @@ AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns) {
Panel_add(super, (Object*) ListItem_new(description, i));
}
}
+}
+
+AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns) {
+ AvailableColumnsPanel* this = AllocThis(AvailableColumnsPanel);
+ Panel* super = (Panel*) this;
+ FunctionBar* fuBar = FunctionBar_new(AvailableColumnsFunctions, NULL, NULL);
+ Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
+
+ Panel_setHeader(super, "Available Columns");
+ AvailableColumnsPanel_addPlatformColumn(super);
+ AvailableColumnsPanel_addDynamicColumns(super, dynamicColumns);
+
this->columns = columns;
return this;
}
diff --git a/AvailableColumnsPanel.h b/AvailableColumnsPanel.h
index 8672eb9..828f7e6 100644
--- a/AvailableColumnsPanel.h
+++ b/AvailableColumnsPanel.h
@@ -7,8 +7,10 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include "Hashtable.h"
#include "Panel.h"
+
typedef struct AvailableColumnsPanel_ {
Panel super;
Panel* columns;
@@ -16,6 +18,6 @@ typedef struct AvailableColumnsPanel_ {
extern const PanelClass AvailableColumnsPanel_class;
-AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns);
+AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns);
#endif
diff --git a/AvailableMetersPanel.c b/AvailableMetersPanel.c
index 7c82229..c00aec1 100644
--- a/AvailableMetersPanel.c
+++ b/AvailableMetersPanel.c
@@ -12,9 +12,12 @@ in the source distribution for its full text.
#include <stdlib.h>
#include "CPUMeter.h"
+#include "DynamicMeter.h"
#include "FunctionBar.h"
+#include "Hashtable.h"
#include "Header.h"
#include "ListItem.h"
+#include "Macros.h"
#include "Meter.h"
#include "MetersPanel.h"
#include "Object.h"
@@ -27,14 +30,15 @@ static void AvailableMetersPanel_delete(Object* object) {
Panel* super = (Panel*) object;
AvailableMetersPanel* this = (AvailableMetersPanel*) object;
Panel_done(super);
+ free(this->meterPanels);
free(this);
}
-static inline void AvailableMetersPanel_addMeter(Header* header, Panel* panel, const MeterClass* type, int param, int column) {
- Meter* meter = Header_addMeterByClass(header, type, param, column);
- Panel_add(panel, (Object*) Meter_toListItem(meter, false));
- Panel_setSelected(panel, Panel_size(panel) - 1);
- MetersPanel_setMoving((MetersPanel*)panel, true);
+static inline void AvailableMetersPanel_addMeter(Header* header, MetersPanel* panel, const MeterClass* type, unsigned int param, size_t column) {
+ const Meter* meter = Header_addMeterByClass(header, type, param, column);
+ Panel_add((Panel*)panel, (Object*) Meter_toListItem(meter, false));
+ Panel_setSelected((Panel*)panel, Panel_size((Panel*)panel) - 1);
+ MetersPanel_setMoving(panel, true);
}
static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
@@ -45,7 +49,7 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
if (!selected)
return IGNORED;
- int param = selected->key & 0xff;
+ unsigned int param = selected->key & 0xffff;
int type = selected->key >> 16;
HandlerResult result = IGNORED;
bool update = false;
@@ -55,7 +59,7 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
case 'l':
case 'L':
{
- AvailableMetersPanel_addMeter(header, this->leftPanel, Platform_meterTypes[type], param, 0);
+ AvailableMetersPanel_addMeter(header, this->meterPanels[0], Platform_meterTypes[type], param, 0);
result = HANDLED;
update = true;
break;
@@ -67,7 +71,7 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
case 'r':
case 'R':
{
- AvailableMetersPanel_addMeter(header, this->rightPanel, Platform_meterTypes[type], param, 1);
+ AvailableMetersPanel_addMeter(header, this->meterPanels[this->columns - 1], Platform_meterTypes[type], param, this->columns - 1);
result = (KEY_LEFT << 16) | SYNTH_KEY;
update = true;
break;
@@ -76,8 +80,9 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
if (update) {
this->settings->changed = true;
Header_calculateHeight(header);
+ Header_updateData(header);
Header_draw(header);
- ScreenManager_resize(this->scr, this->scr->x1, header->height, this->scr->x2, this->scr->y2);
+ ScreenManager_resize(this->scr);
}
return result;
}
@@ -90,7 +95,51 @@ const PanelClass AvailableMetersPanel_class = {
.eventHandler = AvailableMetersPanel_eventHandler
};
-AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, Panel* leftMeters, Panel* rightMeters, ScreenManager* scr, ProcessList* pl) {
+// Handle (&CPUMeter_class) entries in the AvailableMetersPanel
+static void AvailableMetersPanel_addCPUMeters(Panel* super, const MeterClass* type, const ProcessList* pl) {
+ if (pl->existingCPUs > 1) {
+ Panel_add(super, (Object*) ListItem_new("CPU average", 0));
+ for (unsigned int i = 1; i <= pl->existingCPUs; i++) {
+ char buffer[50];
+ xSnprintf(buffer, sizeof(buffer), "%s %d", type->uiName, Settings_cpuId(pl->settings, i - 1));
+ Panel_add(super, (Object*) ListItem_new(buffer, i));
+ }
+ } else {
+ Panel_add(super, (Object*) ListItem_new(type->uiName, 1));
+ }
+}
+
+typedef struct {
+ Panel* super;
+ unsigned int id;
+ unsigned int offset;
+} DynamicIterator;
+
+static void AvailableMetersPanel_addDynamicMeter(ATTR_UNUSED ht_key_t key, void* value, void* data) {
+ const DynamicMeter* meter = (const DynamicMeter*)value;
+ DynamicIterator* iter = (DynamicIterator*)data;
+ unsigned int identifier = (iter->offset << 16) | iter->id;
+ const char* label = meter->description ? meter->description : meter->caption;
+ if (!label)
+ label = meter->name; /* last fallback to name, guaranteed set */
+ Panel_add(iter->super, (Object*) ListItem_new(label, identifier));
+ iter->id++;
+}
+
+// Handle (&DynamicMeter_class) entries in the AvailableMetersPanel
+static void AvailableMetersPanel_addDynamicMeters(Panel* super, const ProcessList* pl, unsigned int offset) {
+ DynamicIterator iter = { .super = super, .id = 1, .offset = offset };
+ assert(pl->dynamicMeters != NULL);
+ Hashtable_foreach(pl->dynamicMeters, AvailableMetersPanel_addDynamicMeter, &iter);
+}
+
+// Handle remaining Platform Meter entries in the AvailableMetersPanel
+static void AvailableMetersPanel_addPlatformMeter(Panel* super, const MeterClass* type, unsigned int offset) {
+ const char* label = type->description ? type->description : type->uiName;
+ Panel_add(super, (Object*) ListItem_new(label, offset << 16));
+}
+
+AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, size_t columns, MetersPanel** meterPanels, ScreenManager* scr, const ProcessList* pl) {
AvailableMetersPanel* this = AllocThis(AvailableMetersPanel);
Panel* super = (Panel*) this;
FunctionBar* fuBar = FunctionBar_newEnterEsc("Add ", "Done ");
@@ -98,31 +147,24 @@ AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* heade
this->settings = settings;
this->header = header;
- this->leftPanel = leftMeters;
- this->rightPanel = rightMeters;
+ this->columns = columns;
+ this->meterPanels = meterPanels;
this->scr = scr;
Panel_setHeader(super, "Available meters");
- // Platform_meterTypes[0] should be always (&CPUMeter_class), which we will
- // handle separately in the code below.
- for (int i = 1; Platform_meterTypes[i]; i++) {
+ // Platform_meterTypes[0] should be always (&CPUMeter_class) which we will
+ // handle separately in the code below. Likewise, identifiers for Dynamic
+ // Meters are handled separately - similar to CPUs, this allows generation
+ // of multiple different Meters (also using 'param' to distinguish them).
+ for (unsigned int i = 1; Platform_meterTypes[i]; i++) {
const MeterClass* type = Platform_meterTypes[i];
assert(type != &CPUMeter_class);
- const char* label = type->description ? type->description : type->uiName;
- Panel_add(super, (Object*) ListItem_new(label, i << 16));
- }
- // Handle (&CPUMeter_class)
- const MeterClass* type = &CPUMeter_class;
- int cpus = pl->cpuCount;
- if (cpus > 1) {
- Panel_add(super, (Object*) ListItem_new("CPU average", 0));
- for (int i = 1; i <= cpus; i++) {
- char buffer[50];
- xSnprintf(buffer, sizeof(buffer), "%s %d", type->uiName, Settings_cpuId(this->settings, i - 1));
- Panel_add(super, (Object*) ListItem_new(buffer, i));
- }
- } else {
- Panel_add(super, (Object*) ListItem_new("CPU", 1));
+ if (type == &DynamicMeter_class)
+ AvailableMetersPanel_addDynamicMeters(super, pl, i);
+ else
+ AvailableMetersPanel_addPlatformMeter(super, type, i);
}
+ AvailableMetersPanel_addCPUMeters(super, &CPUMeter_class, pl);
+
return this;
}
diff --git a/AvailableMetersPanel.h b/AvailableMetersPanel.h
index f735936..5d77c5d 100644
--- a/AvailableMetersPanel.h
+++ b/AvailableMetersPanel.h
@@ -7,24 +7,28 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include <stddef.h>
+
#include "Header.h"
+#include "MetersPanel.h"
#include "Panel.h"
#include "ProcessList.h"
#include "ScreenManager.h"
#include "Settings.h"
+
typedef struct AvailableMetersPanel_ {
Panel super;
ScreenManager* scr;
Settings* settings;
Header* header;
- Panel* leftPanel;
- Panel* rightPanel;
+ size_t columns;
+ MetersPanel** meterPanels;
} AvailableMetersPanel;
extern const PanelClass AvailableMetersPanel_class;
-AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, Panel* leftMeters, Panel* rightMeters, ScreenManager* scr, ProcessList* pl);
+AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, size_t columns, MetersPanel **meterPanels, ScreenManager* scr, const ProcessList* pl);
#endif
diff --git a/BatteryMeter.c b/BatteryMeter.c
index 2c808c7..26a5ac4 100644
--- a/BatteryMeter.c
+++ b/BatteryMeter.c
@@ -21,7 +21,7 @@ static const int BatteryMeter_attributes[] = {
BATTERY
};
-static void BatteryMeter_updateValues(Meter* this, char* buffer, size_t len) {
+static void BatteryMeter_updateValues(Meter* this) {
ACPresence isOnAC;
double percent;
@@ -29,7 +29,7 @@ static void BatteryMeter_updateValues(Meter* this, char* buffer, size_t len) {
if (isnan(percent)) {
this->values[0] = NAN;
- xSnprintf(buffer, len, "N/A");
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "N/A");
return;
}
@@ -49,7 +49,7 @@ static void BatteryMeter_updateValues(Meter* this, char* buffer, size_t len) {
break;
}
- xSnprintf(buffer, len, "%.1f%%%s", percent, text);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%%s", percent, text);
}
const MeterClass BatteryMeter_class = {
diff --git a/BatteryMeter.h b/BatteryMeter.h
index b6e8c52..fa6d503 100644
--- a/BatteryMeter.h
+++ b/BatteryMeter.h
@@ -11,6 +11,7 @@ This meter written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
#include "Meter.h"
+
typedef enum ACPresence_ {
AC_ABSENT,
AC_PRESENT,
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 65eb95c..dd759c2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -29,9 +29,9 @@ are always included, please send those in!
Feature Requests
----------------
-Please label Github issues that are feature requests with the [`feature
-request`](https://github.com/htop-dev/htop/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22feature+request%22+)
-label.
+Please label Github issues that are feature requests with one of the `feature request`
+labels. If you can't do this yourself, don't worry. The friendly folks from the
+core team will distribute and fixup Github labels as part of the regular reviews.
Style Guide
-----------
diff --git a/COPYING b/COPYING
index 1bd75b9..d159169 100644
--- a/COPYING
+++ b/COPYING
@@ -1,12 +1,12 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 675 Mass Ave, Cambridge, MA 02139, USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
- Preamble
+ Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) You can apply it to
+the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
-
- GNU GENERAL PUBLIC LICENSE
+
+ GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
- NO WARRANTY
+ NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
- END OF TERMS AND CONDITIONS
-
- Appendix: How to Apply These Terms to Your New Programs
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
@@ -291,7 +291,7 @@ convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
- Copyright (C) 19yy <name of author>
+ Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -303,16 +303,16 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
- Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -335,21 +335,5 @@ necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General
+library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
-
-
- Appendix 2: Special exception concerning PLPA
-
- In the following exception, "PLPA" means (i) code released by the
- Portable Linux Processor Affinity Project, or (ii) derivative works of
- such code, in both cases provided that the code is covered entirely by
- free software licensing terms.
-
- As a special exception to the GNU GPL, the licensors of htop give you
- permission to combine GNU GPL-licensed code in htop (and derivative
- works of such code) with PLPA. You may copy and distribute such a
- combined work following the terms of the GNU GPL for htop and the
- applicable licenses of the version of PLPA used in your combined work,
- provided that you include the source code of such version of PLPA when
- and as the GNU GPL requires distribution of source code.
diff --git a/CPUMeter.c b/CPUMeter.c
index d84e4db..28fb730 100644
--- a/CPUMeter.c
+++ b/CPUMeter.c
@@ -10,7 +10,6 @@ in the source distribution for its full text.
#include "CPUMeter.h"
#include <math.h>
-#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -35,37 +34,48 @@ static const int CPUMeter_attributes[] = {
};
typedef struct CPUMeterData_ {
- int cpus;
+ unsigned int cpus;
Meter** meters;
} CPUMeterData;
static void CPUMeter_init(Meter* this) {
- int cpu = this->param;
- if (this->pl->cpuCount > 1) {
+ unsigned int cpu = this->param;
+ if (cpu == 0) {
+ Meter_setCaption(this, "Avg");
+ } else if (this->pl->activeCPUs > 1) {
char caption[10];
- xSnprintf(caption, sizeof(caption), "%3d", Settings_cpuId(this->pl->settings, cpu - 1));
+ xSnprintf(caption, sizeof(caption), "%3u", Settings_cpuId(this->pl->settings, cpu - 1));
Meter_setCaption(this, caption);
}
- if (this->param == 0)
- Meter_setCaption(this, "Avg");
}
-static void CPUMeter_updateValues(Meter* this, char* buffer, size_t size) {
- int cpu = this->param;
- if (cpu > this->pl->cpuCount) {
- xSnprintf(buffer, size, "absent");
- for (uint8_t i = 0; i < this->curItems; i++)
- this->values[i] = 0;
+// Custom uiName runtime logic to include the param (processor)
+static void CPUMeter_getUiName(const Meter* this, char* buffer, size_t length) {
+ if (this->param > 0)
+ xSnprintf(buffer, length, "%s %u", Meter_uiName(this), this->param);
+ else
+ xSnprintf(buffer, length, "%s", Meter_uiName(this));
+}
+
+static void CPUMeter_updateValues(Meter* this) {
+ memset(this->values, 0, sizeof(double) * CPU_METER_ITEMCOUNT);
+
+ unsigned int cpu = this->param;
+ if (cpu > this->pl->existingCPUs) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "absent");
+ return;
+ }
+
+ double percent = Platform_setCPUValues(this, cpu);
+ if (isnan(percent)) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "offline");
return;
}
- memset(this->values, 0, sizeof(double) * CPU_METER_ITEMCOUNT);
char cpuUsageBuffer[8] = { 0 };
char cpuFrequencyBuffer[16] = { 0 };
char cpuTemperatureBuffer[16] = { 0 };
- double percent = Platform_setCPUValues(this, cpu);
-
if (this->pl->settings->showCPUUsage) {
xSnprintf(cpuUsageBuffer, sizeof(cpuUsageBuffer), "%.1f%%", percent);
}
@@ -79,7 +89,7 @@ static void CPUMeter_updateValues(Meter* this, char* buffer, size_t size) {
}
}
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
if (this->pl->settings->showCPUTemperature) {
double cpuTemperature = this->values[CPU_METER_TEMPERATURE];
if (isnan(cpuTemperature))
@@ -91,7 +101,7 @@ static void CPUMeter_updateValues(Meter* this, char* buffer, size_t size) {
}
#endif
- xSnprintf(buffer, size, "%s%s%s%s%s",
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s%s%s%s%s",
cpuUsageBuffer,
(cpuUsageBuffer[0] && (cpuFrequencyBuffer[0] || cpuTemperatureBuffer[0])) ? " " : "",
cpuFrequencyBuffer,
@@ -101,75 +111,82 @@ static void CPUMeter_updateValues(Meter* this, char* buffer, size_t size) {
static void CPUMeter_display(const Object* cast, RichString* out) {
char buffer[50];
+ int len;
const Meter* this = (const Meter*)cast;
- RichString_prune(out);
- if (this->param > this->pl->cpuCount) {
- RichString_appendAscii(out, CRT_colors[METER_TEXT], "absent");
+
+ if (this->param > this->pl->existingCPUs) {
+ RichString_appendAscii(out, CRT_colors[METER_SHADOW], " absent");
return;
}
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NORMAL]);
+
+ if (this->curItems == 0) {
+ RichString_appendAscii(out, CRT_colors[METER_SHADOW], " offline");
+ return;
+ }
+
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NORMAL]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], ":");
- RichString_appendAscii(out, CRT_colors[CPU_NORMAL], buffer);
+ RichString_appendnAscii(out, CRT_colors[CPU_NORMAL], buffer, len);
if (this->pl->settings->detailedCPUTime) {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_KERNEL]);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_KERNEL]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "sy:");
- RichString_appendAscii(out, CRT_colors[CPU_SYSTEM], buffer);
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
+ RichString_appendnAscii(out, CRT_colors[CPU_SYSTEM], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "ni:");
- RichString_appendAscii(out, CRT_colors[CPU_NICE_TEXT], buffer);
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]);
+ RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "hi:");
- RichString_appendAscii(out, CRT_colors[CPU_IRQ], buffer);
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_SOFTIRQ]);
+ RichString_appendnAscii(out, CRT_colors[CPU_IRQ], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_SOFTIRQ]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "si:");
- RichString_appendAscii(out, CRT_colors[CPU_SOFTIRQ], buffer);
+ RichString_appendnAscii(out, CRT_colors[CPU_SOFTIRQ], buffer, len);
if (!isnan(this->values[CPU_METER_STEAL])) {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_STEAL]);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_STEAL]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "st:");
- RichString_appendAscii(out, CRT_colors[CPU_STEAL], buffer);
+ RichString_appendnAscii(out, CRT_colors[CPU_STEAL], buffer, len);
}
if (!isnan(this->values[CPU_METER_GUEST])) {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_GUEST]);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_GUEST]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "gu:");
- RichString_appendAscii(out, CRT_colors[CPU_GUEST], buffer);
+ RichString_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len);
}
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IOWAIT]);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IOWAIT]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "wa:");
- RichString_appendAscii(out, CRT_colors[CPU_IOWAIT], buffer);
+ RichString_appendnAscii(out, CRT_colors[CPU_IOWAIT], buffer, len);
} else {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_KERNEL]);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_KERNEL]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "sys:");
- RichString_appendAscii(out, CRT_colors[CPU_SYSTEM], buffer);
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
+ RichString_appendnAscii(out, CRT_colors[CPU_SYSTEM], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "low:");
- RichString_appendAscii(out, CRT_colors[CPU_NICE_TEXT], buffer);
+ RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len);
if (!isnan(this->values[CPU_METER_IRQ])) {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "vir:");
- RichString_appendAscii(out, CRT_colors[CPU_GUEST], buffer);
+ RichString_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len);
}
}
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
if (this->pl->settings->showCPUTemperature) {
char cpuTemperatureBuffer[10];
double cpuTemperature = this->values[CPU_METER_TEMPERATURE];
if (isnan(cpuTemperature)) {
- xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A");
+ len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A");
} else if (this->pl->settings->degreeFahrenheit) {
- xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sF", cpuTemperature * 9 / 5 + 32, CRT_degreeSign);
+ len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sF", cpuTemperature * 9 / 5 + 32, CRT_degreeSign);
} else {
- xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sC", cpuTemperature, CRT_degreeSign);
+ len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sC", cpuTemperature, CRT_degreeSign);
}
RichString_appendAscii(out, CRT_colors[METER_TEXT], "temp:");
- RichString_appendWide(out, CRT_colors[METER_VALUE], cpuTemperatureBuffer);
+ RichString_appendnWide(out, CRT_colors[METER_VALUE], cpuTemperatureBuffer, len);
}
#endif
}
-static void AllCPUsMeter_getRange(Meter* this, int* start, int* count) {
- CPUMeterData* data = this->meterData;
- int cpus = data->cpus;
+static void AllCPUsMeter_getRange(const Meter* this, int* start, int* count) {
+ const CPUMeterData* data = this->meterData;
+ unsigned int cpus = data->cpus;
switch(Meter_name(this)[0]) {
default:
case 'A': // All
@@ -187,8 +204,17 @@ static void AllCPUsMeter_getRange(Meter* this, int* start, int* count) {
}
}
+static void AllCPUsMeter_updateValues(Meter* this) {
+ CPUMeterData* data = this->meterData;
+ Meter** meters = data->meters;
+ int start, count;
+ AllCPUsMeter_getRange(this, &start, &count);
+ for (int i = 0; i < count; i++)
+ Meter_updateValues(meters[i]);
+}
+
static void CPUMeterCommonInit(Meter* this, int ncol) {
- int cpus = this->pl->cpuCount;
+ unsigned int cpus = this->pl->existingCPUs;
CPUMeterData* data = this->meterData;
if (!data) {
data = this->meterData = xMalloc(sizeof(CPUMeterData));
@@ -316,6 +342,7 @@ const MeterClass CPUMeter_class = {
.display = CPUMeter_display
},
.updateValues = CPUMeter_updateValues,
+ .getUiName = CPUMeter_getUiName,
.defaultMode = BAR_METERMODE,
.maxItems = CPU_METER_ITEMCOUNT,
.total = 100.0,
@@ -332,6 +359,7 @@ const MeterClass AllCPUsMeter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -351,7 +379,9 @@ const MeterClass AllCPUs2Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "AllCPUs2",
@@ -370,7 +400,9 @@ const MeterClass LeftCPUsMeter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "LeftCPUs",
@@ -389,7 +421,9 @@ const MeterClass RightCPUsMeter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "RightCPUs",
@@ -408,7 +442,9 @@ const MeterClass LeftCPUs2Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "LeftCPUs2",
@@ -427,7 +463,9 @@ const MeterClass RightCPUs2Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "RightCPUs2",
@@ -446,7 +484,9 @@ const MeterClass AllCPUs4Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "AllCPUs4",
@@ -465,7 +505,9 @@ const MeterClass LeftCPUs4Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "LeftCPUs4",
@@ -484,7 +526,9 @@ const MeterClass RightCPUs4Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "RightCPUs4",
@@ -503,7 +547,9 @@ const MeterClass AllCPUs8Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "AllCPUs8",
@@ -522,7 +568,9 @@ const MeterClass LeftCPUs8Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "LeftCPUs8",
@@ -541,7 +589,9 @@ const MeterClass RightCPUs8Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "RightCPUs8",
diff --git a/CPUMeter.h b/CPUMeter.h
index 989451a..6dbf815 100644
--- a/CPUMeter.h
+++ b/CPUMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
typedef enum {
CPU_METER_NICE = 0,
CPU_METER_NORMAL = 1,
diff --git a/CRT.c b/CRT.c
index 85f2962..2533f61 100644
--- a/CRT.c
+++ b/CRT.c
@@ -10,6 +10,7 @@ in the source distribution for its full text.
#include "CRT.h"
#include <errno.h>
+#include <fcntl.h>
#include <langinfo.h>
#include <signal.h>
#include <stdio.h>
@@ -24,6 +25,10 @@ in the source distribution for its full text.
#include <execinfo.h>
#endif
+#if !defined(NDEBUG) && defined(HAVE_MEMFD_CREATE)
+#include <sys/mman.h>
+#endif
+
#define ColorIndex(i,j) ((7-(i))*8+(j))
@@ -76,6 +81,7 @@ bool CRT_utf8 = false;
const char* const* CRT_treeStr = CRT_treeStrAscii;
+static const Settings* CRT_crashSettings;
static const int* CRT_delay;
const char* CRT_degreeSign;
@@ -114,6 +120,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = A_BOLD | ColorPair(Cyan, Black),
[BATTERY] = A_BOLD | ColorPair(Cyan, Black),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Black),
+ [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
[METER_TEXT] = ColorPair(Cyan, Black),
[METER_VALUE] = A_BOLD | ColorPair(Cyan, Black),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),
@@ -131,7 +138,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = ColorPair(Green, Black),
[PROCESS_BASENAME] = A_BOLD | ColorPair(Cyan, Black),
[PROCESS_TREE] = ColorPair(Cyan, Black),
- [PROCESS_R_STATE] = ColorPair(Green, Black),
+ [PROCESS_RUN_STATE] = ColorPair(Green, Black),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),
@@ -144,17 +151,24 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = A_BOLD,
[BAR_SHADOW] = A_BOLD | ColorPairGrayBlack,
[SWAP] = ColorPair(Red, Black),
+ [SWAP_CACHE] = ColorPair(Yellow, Black),
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Black),
[GRAPH_2] = ColorPair(Cyan, Black),
[MEMORY_USED] = ColorPair(Green, Black),
[MEMORY_BUFFERS] = ColorPair(Blue, Black),
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Blue, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
+ [MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [HUGEPAGE_1] = ColorPair(Green, Black),
+ [HUGEPAGE_2] = ColorPair(Yellow, Black),
+ [HUGEPAGE_3] = ColorPair(Red, Black),
+ [HUGEPAGE_4] = ColorPair(Blue, Black),
[LOAD_AVERAGE_FIFTEEN] = ColorPair(Cyan, Black),
[LOAD_AVERAGE_FIVE] = A_BOLD | ColorPair(Cyan, Black),
[LOAD_AVERAGE_ONE] = A_BOLD | ColorPair(White, Black),
[LOAD] = A_BOLD,
[HELP_BOLD] = A_BOLD | ColorPair(Cyan, Black),
+ [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,
[CLOCK] = A_BOLD,
[DATE] = A_BOLD,
[DATETIME] = A_BOLD,
@@ -182,6 +196,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = ColorPair(Blue, Black),
[ZFS_RATIO] = ColorPair(Magenta, Black),
[ZRAM] = ColorPair(Yellow, Black),
+ [DYNAMIC_GRAY] = ColorPairGrayBlack,
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
+ [DYNAMIC_RED] = ColorPair(Red, Black),
+ [DYNAMIC_GREEN] = ColorPair(Green, Black),
+ [DYNAMIC_BLUE] = ColorPair(Blue, Black),
+ [DYNAMIC_CYAN] = ColorPair(Cyan, Black),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
+ [DYNAMIC_WHITE] = ColorPair(White, Black),
},
[COLORSCHEME_MONOCHROME] = {
[RESET_COLOR] = A_NORMAL,
@@ -199,6 +222,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = A_BOLD,
[BATTERY] = A_BOLD,
[LARGE_NUMBER] = A_BOLD,
+ [METER_SHADOW] = A_DIM,
[METER_TEXT] = A_NORMAL,
[METER_VALUE] = A_BOLD,
[METER_VALUE_ERROR] = A_BOLD,
@@ -216,7 +240,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = A_BOLD,
[PROCESS_BASENAME] = A_BOLD,
[PROCESS_TREE] = A_BOLD,
- [PROCESS_R_STATE] = A_BOLD,
+ [PROCESS_RUN_STATE] = A_BOLD,
[PROCESS_D_STATE] = A_BOLD,
[PROCESS_HIGH_PRIORITY] = A_BOLD,
[PROCESS_LOW_PRIORITY] = A_DIM,
@@ -229,17 +253,24 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = A_BOLD,
[BAR_SHADOW] = A_DIM,
[SWAP] = A_BOLD,
+ [SWAP_CACHE] = A_NORMAL,
[GRAPH_1] = A_BOLD,
[GRAPH_2] = A_NORMAL,
[MEMORY_USED] = A_BOLD,
[MEMORY_BUFFERS] = A_NORMAL,
[MEMORY_BUFFERS_TEXT] = A_NORMAL,
[MEMORY_CACHE] = A_NORMAL,
+ [MEMORY_SHARED] = A_NORMAL,
+ [HUGEPAGE_1] = A_BOLD,
+ [HUGEPAGE_2] = A_NORMAL,
+ [HUGEPAGE_3] = A_REVERSE | A_BOLD,
+ [HUGEPAGE_4] = A_REVERSE,
[LOAD_AVERAGE_FIFTEEN] = A_DIM,
[LOAD_AVERAGE_FIVE] = A_NORMAL,
[LOAD_AVERAGE_ONE] = A_BOLD,
[LOAD] = A_BOLD,
[HELP_BOLD] = A_BOLD,
+ [HELP_SHADOW] = A_DIM,
[CLOCK] = A_BOLD,
[DATE] = A_BOLD,
[DATETIME] = A_BOLD,
@@ -267,6 +298,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = A_BOLD,
[ZFS_RATIO] = A_BOLD,
[ZRAM] = A_NORMAL,
+ [DYNAMIC_GRAY] = A_DIM,
+ [DYNAMIC_DARKGRAY] = A_DIM,
+ [DYNAMIC_RED] = A_BOLD,
+ [DYNAMIC_GREEN] = A_NORMAL,
+ [DYNAMIC_BLUE] = A_NORMAL,
+ [DYNAMIC_CYAN] = A_BOLD,
+ [DYNAMIC_MAGENTA] = A_NORMAL,
+ [DYNAMIC_YELLOW] = A_NORMAL,
+ [DYNAMIC_WHITE] = A_BOLD,
},
[COLORSCHEME_BLACKONWHITE] = {
[RESET_COLOR] = ColorPair(Black, White),
@@ -284,6 +324,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = ColorPair(Yellow, White),
[BATTERY] = ColorPair(Yellow, White),
[LARGE_NUMBER] = ColorPair(Red, White),
+ [METER_SHADOW] = ColorPair(Blue, White),
[METER_TEXT] = ColorPair(Blue, White),
[METER_VALUE] = ColorPair(Black, White),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, White),
@@ -301,7 +342,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = ColorPair(Green, White),
[PROCESS_BASENAME] = ColorPair(Blue, White),
[PROCESS_TREE] = ColorPair(Green, White),
- [PROCESS_R_STATE] = ColorPair(Green, White),
+ [PROCESS_RUN_STATE] = ColorPair(Green, White),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, White),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, White),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, White),
@@ -314,17 +355,24 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = ColorPair(Blue, White),
[BAR_SHADOW] = ColorPair(Black, White),
[SWAP] = ColorPair(Red, White),
+ [SWAP_CACHE] = ColorPair(Yellow, White),
[GRAPH_1] = A_BOLD | ColorPair(Blue, White),
[GRAPH_2] = ColorPair(Blue, White),
[MEMORY_USED] = ColorPair(Green, White),
[MEMORY_BUFFERS] = ColorPair(Cyan, White),
[MEMORY_BUFFERS_TEXT] = ColorPair(Cyan, White),
[MEMORY_CACHE] = ColorPair(Yellow, White),
+ [MEMORY_SHARED] = ColorPair(Magenta, White),
+ [HUGEPAGE_1] = ColorPair(Green, White),
+ [HUGEPAGE_2] = ColorPair(Yellow, White),
+ [HUGEPAGE_3] = ColorPair(Red, White),
+ [HUGEPAGE_4] = ColorPair(Blue, White),
[LOAD_AVERAGE_FIFTEEN] = ColorPair(Black, White),
[LOAD_AVERAGE_FIVE] = ColorPair(Black, White),
[LOAD_AVERAGE_ONE] = ColorPair(Black, White),
[LOAD] = ColorPair(Black, White),
[HELP_BOLD] = ColorPair(Blue, White),
+ [HELP_SHADOW] = A_BOLD | ColorPair(Black, White),
[CLOCK] = ColorPair(Black, White),
[DATE] = ColorPair(Black, White),
[DATETIME] = ColorPair(Black, White),
@@ -351,7 +399,16 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_OTHER] = ColorPair(Magenta, White),
[ZFS_COMPRESSED] = ColorPair(Cyan, White),
[ZFS_RATIO] = ColorPair(Magenta, White),
- [ZRAM] = ColorPair(Yellow, White)
+ [ZRAM] = ColorPair(Yellow, White),
+ [DYNAMIC_GRAY] = ColorPair(Black, White),
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPair(Black, White),
+ [DYNAMIC_RED] = ColorPair(Red, White),
+ [DYNAMIC_GREEN] = ColorPair(Green, White),
+ [DYNAMIC_BLUE] = ColorPair(Blue, White),
+ [DYNAMIC_CYAN] = ColorPair(Yellow, White),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, White),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, White),
+ [DYNAMIC_WHITE] = A_BOLD | ColorPair(Black, White),
},
[COLORSCHEME_LIGHTTERMINAL] = {
[RESET_COLOR] = ColorPair(Black, Black),
@@ -369,6 +426,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = ColorPair(Yellow, Black),
[BATTERY] = ColorPair(Yellow, Black),
[LARGE_NUMBER] = ColorPair(Red, Black),
+ [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
[METER_TEXT] = ColorPair(Blue, Black),
[METER_VALUE] = ColorPair(Black, Black),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),
@@ -386,7 +444,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = ColorPair(Green, Black),
[PROCESS_BASENAME] = ColorPair(Green, Black),
[PROCESS_TREE] = ColorPair(Blue, Black),
- [PROCESS_R_STATE] = ColorPair(Green, Black),
+ [PROCESS_RUN_STATE] = ColorPair(Green, Black),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),
@@ -399,17 +457,24 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = ColorPair(Blue, Black),
[BAR_SHADOW] = ColorPairGrayBlack,
[SWAP] = ColorPair(Red, Black),
+ [SWAP_CACHE] = ColorPair(Yellow, Black),
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Black),
[GRAPH_2] = ColorPair(Cyan, Black),
[MEMORY_USED] = ColorPair(Green, Black),
[MEMORY_BUFFERS] = ColorPair(Cyan, Black),
[MEMORY_BUFFERS_TEXT] = ColorPair(Cyan, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
+ [MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [HUGEPAGE_1] = ColorPair(Green, Black),
+ [HUGEPAGE_2] = ColorPair(Yellow, Black),
+ [HUGEPAGE_3] = ColorPair(Red, Black),
+ [HUGEPAGE_4] = ColorPair(Blue, Black),
[LOAD_AVERAGE_FIFTEEN] = ColorPair(Black, Black),
[LOAD_AVERAGE_FIVE] = ColorPair(Black, Black),
[LOAD_AVERAGE_ONE] = ColorPair(Black, Black),
[LOAD] = ColorPairWhiteDefault,
[HELP_BOLD] = ColorPair(Blue, Black),
+ [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,
[CLOCK] = ColorPairWhiteDefault,
[DATE] = ColorPairWhiteDefault,
[DATETIME] = ColorPairWhiteDefault,
@@ -437,6 +502,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = ColorPair(Cyan, Black),
[ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Black),
[ZRAM] = ColorPair(Yellow, Black),
+ [DYNAMIC_GRAY] = ColorPairGrayBlack,
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
+ [DYNAMIC_RED] = ColorPair(Red, Black),
+ [DYNAMIC_GREEN] = ColorPair(Green, Black),
+ [DYNAMIC_BLUE] = ColorPair(Blue, Black),
+ [DYNAMIC_CYAN] = ColorPair(Cyan, Black),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
+ [DYNAMIC_WHITE] = ColorPairWhiteDefault,
},
[COLORSCHEME_MIDNIGHT] = {
[RESET_COLOR] = ColorPair(White, Blue),
@@ -454,6 +528,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = A_BOLD | ColorPair(Yellow, Blue),
[BATTERY] = A_BOLD | ColorPair(Yellow, Blue),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Blue),
+ [METER_SHADOW] = ColorPair(Cyan, Blue),
[METER_TEXT] = ColorPair(Cyan, Blue),
[METER_VALUE] = A_BOLD | ColorPair(Cyan, Blue),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Blue),
@@ -471,7 +546,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = ColorPair(Green, Blue),
[PROCESS_BASENAME] = A_BOLD | ColorPair(Cyan, Blue),
[PROCESS_TREE] = ColorPair(Cyan, Blue),
- [PROCESS_R_STATE] = ColorPair(Green, Blue),
+ [PROCESS_RUN_STATE] = ColorPair(Green, Blue),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Blue),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Blue),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Blue),
@@ -484,17 +559,24 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = A_BOLD | ColorPair(Yellow, Blue),
[BAR_SHADOW] = ColorPair(Cyan, Blue),
[SWAP] = ColorPair(Red, Blue),
+ [SWAP_CACHE] = A_BOLD | ColorPair(Yellow, Blue),
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Blue),
[GRAPH_2] = ColorPair(Cyan, Blue),
[MEMORY_USED] = A_BOLD | ColorPair(Green, Blue),
[MEMORY_BUFFERS] = A_BOLD | ColorPair(Cyan, Blue),
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Cyan, Blue),
[MEMORY_CACHE] = A_BOLD | ColorPair(Yellow, Blue),
+ [MEMORY_SHARED] = A_BOLD | ColorPair(Magenta, Blue),
+ [HUGEPAGE_1] = A_BOLD | ColorPair(Green, Blue),
+ [HUGEPAGE_2] = A_BOLD | ColorPair(Yellow, Blue),
+ [HUGEPAGE_3] = A_BOLD | ColorPair(Red, Blue),
+ [HUGEPAGE_4] = A_BOLD | ColorPair(White, Blue),
[LOAD_AVERAGE_FIFTEEN] = A_BOLD | ColorPair(Black, Blue),
[LOAD_AVERAGE_FIVE] = A_NORMAL | ColorPair(White, Blue),
[LOAD_AVERAGE_ONE] = A_BOLD | ColorPair(White, Blue),
[LOAD] = A_BOLD | ColorPair(White, Blue),
[HELP_BOLD] = A_BOLD | ColorPair(Cyan, Blue),
+ [HELP_SHADOW] = A_BOLD | ColorPair(Black, Blue),
[CLOCK] = ColorPair(White, Blue),
[DATE] = ColorPair(White, Blue),
[DATETIME] = ColorPair(White, Blue),
@@ -522,6 +604,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = A_BOLD | ColorPair(White, Blue),
[ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Blue),
[ZRAM] = A_BOLD | ColorPair(Yellow, Blue),
+ [DYNAMIC_GRAY] = ColorPairGrayBlack,
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
+ [DYNAMIC_RED] = ColorPair(Red, Blue),
+ [DYNAMIC_GREEN] = ColorPair(Green, Blue),
+ [DYNAMIC_BLUE] = ColorPair(Black, Blue),
+ [DYNAMIC_CYAN] = ColorPair(Cyan, Blue),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, Blue),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, Blue),
+ [DYNAMIC_WHITE] = ColorPair(White, Blue),
},
[COLORSCHEME_BLACKNIGHT] = {
[RESET_COLOR] = ColorPair(Cyan, Black),
@@ -539,6 +630,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = ColorPair(Green, Black),
[BATTERY] = ColorPair(Green, Black),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Black),
+ [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
[METER_TEXT] = ColorPair(Cyan, Black),
[METER_VALUE] = ColorPair(Green, Black),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),
@@ -560,7 +652,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, Black),
[PROCESS_COMM] = ColorPair(Magenta, Black),
[PROCESS_THREAD_COMM] = ColorPair(Yellow, Black),
- [PROCESS_R_STATE] = ColorPair(Green, Black),
+ [PROCESS_RUN_STATE] = ColorPair(Green, Black),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),
@@ -569,17 +661,24 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = A_BOLD | ColorPair(Green, Black),
[BAR_SHADOW] = ColorPair(Cyan, Black),
[SWAP] = ColorPair(Red, Black),
+ [SWAP_CACHE] = ColorPair(Yellow, Black),
[GRAPH_1] = A_BOLD | ColorPair(Green, Black),
[GRAPH_2] = ColorPair(Green, Black),
[MEMORY_USED] = ColorPair(Green, Black),
[MEMORY_BUFFERS] = ColorPair(Blue, Black),
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Blue, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
+ [MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [HUGEPAGE_1] = ColorPair(Green, Black),
+ [HUGEPAGE_2] = ColorPair(Yellow, Black),
+ [HUGEPAGE_3] = ColorPair(Red, Black),
+ [HUGEPAGE_4] = ColorPair(Blue, Black),
[LOAD_AVERAGE_FIFTEEN] = ColorPair(Green, Black),
[LOAD_AVERAGE_FIVE] = ColorPair(Green, Black),
[LOAD_AVERAGE_ONE] = A_BOLD | ColorPair(Green, Black),
[LOAD] = A_BOLD,
[HELP_BOLD] = A_BOLD | ColorPair(Cyan, Black),
+ [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,
[CLOCK] = ColorPair(Green, Black),
[CHECK_BOX] = ColorPair(Green, Black),
[CHECK_MARK] = A_BOLD | ColorPair(Green, Black),
@@ -605,6 +704,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = ColorPair(Blue, Black),
[ZFS_RATIO] = ColorPair(Magenta, Black),
[ZRAM] = ColorPair(Yellow, Black),
+ [DYNAMIC_GRAY] = ColorPairGrayBlack,
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
+ [DYNAMIC_RED] = ColorPair(Red, Black),
+ [DYNAMIC_GREEN] = ColorPair(Green, Black),
+ [DYNAMIC_BLUE] = ColorPair(Blue, Black),
+ [DYNAMIC_CYAN] = ColorPair(Cyan, Black),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
+ [DYNAMIC_WHITE] = ColorPair(White, Black),
},
[COLORSCHEME_BROKENGRAY] = { 0 } // dynamically generated.
};
@@ -618,53 +726,141 @@ int CRT_scrollWheelVAmount = 10;
ColorScheme CRT_colorScheme = COLORSCHEME_DEFAULT;
ATTR_NORETURN
-static void CRT_handleSIGTERM(int sgn) {
- (void) sgn;
+static void CRT_handleSIGTERM(ATTR_UNUSED int sgn) {
CRT_done();
- exit(0);
+ _exit(0);
}
-#ifdef HAVE_SETUID_ENABLED
+#ifndef NDEBUG
-static int CRT_euid = -1;
+static int stderrRedirectNewFd = -1;
+static int stderrRedirectBackupFd = -1;
-static int CRT_egid = -1;
+static int createStderrCacheFile(void) {
+#if defined(HAVE_MEMFD_CREATE)
+ return memfd_create("htop.stderr-redirect", 0);
+#elif defined(O_TMPFILE)
+ return open("/tmp", O_TMPFILE | O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
+#else
+ char tmpName[] = "htop.stderr-redirectXXXXXX";
+ mode_t curUmask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ int r = mkstemp(tmpName);
+ umask(curUmask);
+ if (r < 0)
+ return r;
-void CRT_dropPrivileges() {
- CRT_egid = getegid();
- CRT_euid = geteuid();
- if (setegid(getgid()) == -1) {
- CRT_fatalError("Fatal error: failed dropping group privileges");
- }
- if (seteuid(getuid()) == -1) {
- CRT_fatalError("Fatal error: failed dropping user privileges");
- }
+ (void) unlink(tmpName);
+
+ return r;
+#endif /* HAVE_MEMFD_CREATE */
}
-void CRT_restorePrivileges() {
- if (CRT_egid == -1 || CRT_euid == -1) {
- CRT_fatalError("Fatal error: internal inconsistency");
- }
- if (setegid(CRT_egid) == -1) {
- CRT_fatalError("Fatal error: failed restoring group privileges");
+static void redirectStderr(void) {
+ stderrRedirectNewFd = createStderrCacheFile();
+ if (stderrRedirectNewFd < 0) {
+ /* ignore failure */
+ return;
}
- if (seteuid(CRT_euid) == -1) {
- CRT_fatalError("Fatal error: failed restoring user privileges");
+
+ stderrRedirectBackupFd = dup(STDERR_FILENO);
+ dup2(stderrRedirectNewFd, STDERR_FILENO);
+}
+
+static void dumpStderr(void) {
+ if (stderrRedirectNewFd < 0)
+ return;
+
+ fsync(STDERR_FILENO);
+ dup2(stderrRedirectBackupFd, STDERR_FILENO);
+ close(stderrRedirectBackupFd);
+ stderrRedirectBackupFd = -1;
+ lseek(stderrRedirectNewFd, 0, SEEK_SET);
+
+ bool header = false;
+ char buffer[8192];
+ for (;;) {
+ errno = 0;
+ ssize_t res = read(stderrRedirectNewFd, buffer, sizeof(buffer));
+ if (res < 0) {
+ if (errno == EINTR)
+ continue;
+
+ break;
+ }
+
+ if (res == 0) {
+ break;
+ }
+
+ if (res > 0) {
+ if (!header) {
+ fprintf(stderr, ">>>>>>>>>> stderr output >>>>>>>>>>\n");
+ header = true;
+ }
+ (void)! write(STDERR_FILENO, buffer, res);
+ }
}
+
+ if (header)
+ fprintf(stderr, "\n<<<<<<<<<< stderr output <<<<<<<<<<\n");
+
+ close(stderrRedirectNewFd);
+ stderrRedirectNewFd = -1;
}
-#endif /* HAVE_SETUID_ENABLED */
+#else /* !NDEBUG */
+
+static void redirectStderr(void) {
+}
+
+static void dumpStderr(void) {
+}
+
+#endif /* !NDEBUG */
static struct sigaction old_sig_handler[32];
-// TODO: pass an instance of Settings instead.
+static void CRT_installSignalHandlers(void) {
+ struct sigaction act;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = (int)SA_RESETHAND | SA_NODEFER;
+ act.sa_handler = CRT_handleSIGSEGV;
+ sigaction (SIGSEGV, &act, &old_sig_handler[SIGSEGV]);
+ sigaction (SIGFPE, &act, &old_sig_handler[SIGFPE]);
+ sigaction (SIGILL, &act, &old_sig_handler[SIGILL]);
+ sigaction (SIGBUS, &act, &old_sig_handler[SIGBUS]);
+ sigaction (SIGPIPE, &act, &old_sig_handler[SIGPIPE]);
+ sigaction (SIGSYS, &act, &old_sig_handler[SIGSYS]);
+ sigaction (SIGABRT, &act, &old_sig_handler[SIGABRT]);
+
+ signal(SIGINT, CRT_handleSIGTERM);
+ signal(SIGTERM, CRT_handleSIGTERM);
+ signal(SIGQUIT, CRT_handleSIGTERM);
+}
+
+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);
+ sigaction (SIGBUS, &old_sig_handler[SIGBUS], NULL);
+ sigaction (SIGPIPE, &old_sig_handler[SIGPIPE], NULL);
+ sigaction (SIGSYS, &old_sig_handler[SIGSYS], NULL);
+ sigaction (SIGABRT, &old_sig_handler[SIGABRT], NULL);
+
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+}
+
+void CRT_init(const Settings* settings, bool allowUnicode) {
+ redirectStderr();
-void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
initscr();
noecho();
- CRT_delay = delay;
- CRT_colors = CRT_colorSchemes[colorScheme];
- CRT_colorScheme = colorScheme;
+ CRT_crashSettings = settings;
+ CRT_delay = &(settings->delay);
+ CRT_colors = CRT_colorSchemes[settings->colorScheme];
+ CRT_colorScheme = settings->colorScheme;
for (int i = 0; i < LAST_COLORELEMENT; i++) {
unsigned int color = CRT_colorSchemes[COLORSCHEME_DEFAULT][i];
@@ -675,7 +871,9 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
nonl();
intrflush(stdscr, false);
keypad(stdscr, true);
+#ifdef HAVE_GETMOUSE
mouseinterval(0);
+#endif
curs_set(0);
if (has_colors()) {
@@ -690,6 +888,10 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
}
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
+#endif
define_key("\033[H", KEY_HOME);
define_key("\033[F", KEY_END);
define_key("\033[7~", KEY_HOME);
@@ -698,6 +900,7 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
define_key("\033OQ", KEY_F(2));
define_key("\033OR", KEY_F(3));
define_key("\033OS", KEY_F(4));
+ define_key("\033O2R", KEY_F(15));
define_key("\033[11~", KEY_F(1));
define_key("\033[12~", KEY_F(2));
define_key("\033[13~", KEY_F(3));
@@ -709,22 +912,13 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
sequence[1] = c;
define_key(sequence, KEY_ALT('A' + (c - 'a')));
}
+#ifdef HTOP_NETBSD
+IGNORE_WCASTQUAL_END
+#undef define_key
+#endif
}
- struct sigaction act;
- sigemptyset (&act.sa_mask);
- act.sa_flags = (int)SA_RESETHAND | SA_NODEFER;
- act.sa_handler = CRT_handleSIGSEGV;
- sigaction (SIGSEGV, &act, &old_sig_handler[SIGSEGV]);
- sigaction (SIGFPE, &act, &old_sig_handler[SIGFPE]);
- sigaction (SIGILL, &act, &old_sig_handler[SIGILL]);
- sigaction (SIGBUS, &act, &old_sig_handler[SIGBUS]);
- sigaction (SIGPIPE, &act, &old_sig_handler[SIGPIPE]);
- sigaction (SIGSYS, &act, &old_sig_handler[SIGSYS]);
- sigaction (SIGABRT, &act, &old_sig_handler[SIGABRT]);
-
- signal(SIGTERM, CRT_handleSIGTERM);
- signal(SIGQUIT, CRT_handleSIGTERM);
+ CRT_installSignalHandlers();
use_default_colors();
if (!has_colors())
@@ -747,11 +941,13 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
#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_degreeSign = initDegreeSign();
}
@@ -759,6 +955,8 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
void CRT_done() {
curs_set(1);
endwin();
+
+ dumpStderr();
}
void CRT_fatalError(const char* note) {
@@ -818,15 +1016,14 @@ void CRT_handleSIGSEGV(int signal) {
"============================\n"
"Please check at https://htop.dev/issues whether this issue has already been reported.\n"
"If no similar issue has been reported before, please create a new issue with the following information:\n"
- "\n"
- "- Your htop version (htop --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 happened?)\n"
+ " - 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"
);
#ifdef HAVE_EXECINFO_H
- fprintf(stderr, "- Backtrace of the issue (see below)\n");
+ fprintf(stderr, " - Backtrace of the issue (see below)\n");
#endif
fprintf(stderr,
@@ -845,46 +1042,46 @@ void CRT_handleSIGSEGV(int signal) {
signal, signal_str
);
+ fprintf(stderr,
+ "Setting information:\n"
+ "--------------------\n");
+ Settings_write(CRT_crashSettings, true);
+ fprintf(stderr, "\n\n");
+
#ifdef HAVE_EXECINFO_H
fprintf(stderr,
"Backtrace information:\n"
"----------------------\n"
- "The following function calls were active when the issue was detected:\n"
- "---\n"
);
- void *backtraceArray[256];
+ void* backtraceArray[256];
size_t size = backtrace(backtraceArray, ARRAYSIZE(backtraceArray));
- backtrace_symbols_fd(backtraceArray, size, 2);
+ backtrace_symbols_fd(backtraceArray, size, STDERR_FILENO);
fprintf(stderr,
- "---\n"
"\n"
- "To make the above information more practical to work with,\n"
- "you should provide a disassembly of your binary.\n"
+ "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"
);
#ifdef HTOP_DARWIN
- fprintf(stderr, " otool -tvV `which htop` > ~/htop.otool\n");
+ fprintf(stderr, " otool -tvV `which "PACKAGE"` > ~/htop.otool\n");
#else
- fprintf(stderr, " objdump -d -S -w `which htop` > ~/htop.objdump\n");
+ fprintf(stderr, " objdump -d -S -w `which "PACKAGE"` > ~/htop.objdump\n");
#endif
fprintf(stderr,
"\n"
"Please include the generated file in your report.\n"
- "\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 htop!\n"
- "\n"
- "htop " VERSION " aborting.\n"
+ "Thank you for helping to improve "PACKAGE"!\n"
"\n"
);
diff --git a/CRT.h b/CRT.h
index 4806994..f2c2c22 100644
--- a/CRT.h
+++ b/CRT.h
@@ -13,6 +13,7 @@ in the source distribution for its full text.
#include "Macros.h"
#include "ProvideCurses.h"
+#include "Settings.h"
typedef enum TreeStr_ {
@@ -52,6 +53,7 @@ typedef enum ColorElements_ {
PANEL_SELECTION_FOLLOW,
PANEL_SELECTION_UNFOCUS,
LARGE_NUMBER,
+ METER_SHADOW,
METER_TEXT,
METER_VALUE,
METER_VALUE_ERROR,
@@ -65,13 +67,14 @@ typedef enum ColorElements_ {
BATTERY,
TASKS_RUNNING,
SWAP,
+ SWAP_CACHE,
PROCESS,
PROCESS_SHADOW,
PROCESS_TAG,
PROCESS_MEGABYTES,
PROCESS_GIGABYTES,
PROCESS_TREE,
- PROCESS_R_STATE,
+ PROCESS_RUN_STATE,
PROCESS_D_STATE,
PROCESS_BASENAME,
PROCESS_HIGH_PRIORITY,
@@ -90,6 +93,11 @@ typedef enum ColorElements_ {
MEMORY_BUFFERS,
MEMORY_BUFFERS_TEXT,
MEMORY_CACHE,
+ MEMORY_SHARED,
+ HUGEPAGE_1,
+ HUGEPAGE_2,
+ HUGEPAGE_3,
+ HUGEPAGE_4,
LOAD,
LOAD_AVERAGE_FIFTEEN,
LOAD_AVERAGE_FIVE,
@@ -101,6 +109,7 @@ typedef enum ColorElements_ {
DATE,
DATETIME,
HELP_BOLD,
+ HELP_SHADOW,
HOSTNAME,
CPU_NICE,
CPU_NICE_TEXT,
@@ -122,6 +131,15 @@ typedef enum ColorElements_ {
ZFS_COMPRESSED,
ZFS_RATIO,
ZRAM,
+ DYNAMIC_GRAY,
+ DYNAMIC_DARKGRAY,
+ DYNAMIC_RED,
+ DYNAMIC_GREEN,
+ DYNAMIC_BLUE,
+ DYNAMIC_CYAN,
+ DYNAMIC_MAGENTA,
+ DYNAMIC_YELLOW,
+ DYNAMIC_WHITE,
LAST_COLORELEMENT
} ColorElements;
@@ -154,24 +172,12 @@ extern int CRT_scrollWheelVAmount;
extern ColorScheme CRT_colorScheme;
-#ifdef HAVE_SETUID_ENABLED
-
-void CRT_dropPrivileges(void);
-
-void CRT_restorePrivileges(void);
-
-#else /* HAVE_SETUID_ENABLED */
-
-/* Turn setuid operations into NOPs */
-static inline void CRT_dropPrivileges(void) { }
-static inline void CRT_restorePrivileges(void) { }
-
-#endif /* HAVE_SETUID_ENABLED */
-
-void CRT_init(const int* delay, int colorScheme, bool allowUnicode);
+void CRT_init(const Settings* settings, bool allowUnicode);
void CRT_done(void);
+void CRT_resetSignalHandlers(void);
+
int CRT_readKey(void);
void CRT_disableDelay(void);
diff --git a/CategoriesPanel.c b/CategoriesPanel.c
index 4ee1ad4..21010b3 100644
--- a/CategoriesPanel.c
+++ b/CategoriesPanel.c
@@ -17,11 +17,16 @@ in the source distribution for its full text.
#include "ColumnsPanel.h"
#include "DisplayOptionsPanel.h"
#include "FunctionBar.h"
+#include "Header.h"
+#include "HeaderLayout.h"
+#include "HeaderOptionsPanel.h"
#include "ListItem.h"
+#include "Macros.h"
#include "MetersPanel.h"
#include "Object.h"
#include "ProvideCurses.h"
#include "Vector.h"
+#include "XUtils.h"
static const char* const CategoriesFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL};
@@ -33,14 +38,24 @@ static void CategoriesPanel_delete(Object* object) {
free(this);
}
-void CategoriesPanel_makeMetersPage(CategoriesPanel* this) {
- MetersPanel* leftMeters = MetersPanel_new(this->settings, "Left column", this->header->columns[0], this->scr);
- MetersPanel* rightMeters = MetersPanel_new(this->settings, "Right column", this->header->columns[1], this->scr);
- leftMeters->rightNeighbor = rightMeters;
- rightMeters->leftNeighbor = leftMeters;
- Panel* availableMeters = (Panel*) AvailableMetersPanel_new(this->settings, this->header, (Panel*) leftMeters, (Panel*) rightMeters, this->scr, this->pl);
- ScreenManager_add(this->scr, (Panel*) leftMeters, 20);
- ScreenManager_add(this->scr, (Panel*) rightMeters, 20);
+static void CategoriesPanel_makeMetersPage(CategoriesPanel* this) {
+ size_t columns = HeaderLayout_getColumns(this->scr->header->headerLayout);
+ MetersPanel** meterPanels = xMallocArray(columns, sizeof(MetersPanel*));
+
+ for (size_t i = 0; i < columns; i++) {
+ char titleBuffer[32];
+ xSnprintf(titleBuffer, sizeof(titleBuffer), "Column %zu", i + 1);
+ meterPanels[i] = MetersPanel_new(this->settings, titleBuffer, this->header->columns[i], this->scr);
+
+ if (i != 0) {
+ meterPanels[i]->leftNeighbor = meterPanels[i - 1];
+ meterPanels[i - 1]->rightNeighbor = meterPanels[i];
+ }
+
+ ScreenManager_add(this->scr, (Panel*) meterPanels[i], 20);
+ }
+
+ Panel* availableMeters = (Panel*) AvailableMetersPanel_new(this->settings, this->header, columns, meterPanels, this->scr, this->pl);
ScreenManager_add(this->scr, availableMeters, -1);
}
@@ -50,17 +65,36 @@ static void CategoriesPanel_makeDisplayOptionsPage(CategoriesPanel* this) {
}
static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) {
- Panel* colors = (Panel*) ColorsPanel_new(this->settings, this->scr);
+ Panel* colors = (Panel*) ColorsPanel_new(this->settings);
ScreenManager_add(this->scr, colors, -1);
}
static void CategoriesPanel_makeColumnsPage(CategoriesPanel* this) {
Panel* columns = (Panel*) ColumnsPanel_new(this->settings);
- Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns);
+ Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, this->settings->dynamicColumns);
ScreenManager_add(this->scr, columns, 20);
ScreenManager_add(this->scr, availableColumns, -1);
}
+static void CategoriesPanel_makeHeaderOptionsPage(CategoriesPanel* this) {
+ Panel* colors = (Panel*) HeaderOptionsPanel_new(this->settings, this->scr);
+ ScreenManager_add(this->scr, colors, -1);
+}
+
+typedef void (* CategoriesPanel_makePageFunc)(CategoriesPanel* ref);
+typedef struct CategoriesPanelPage_ {
+ const char* name;
+ CategoriesPanel_makePageFunc ctor;
+} CategoriesPanelPage;
+
+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 = "Colors", .ctor = CategoriesPanel_makeColorsPage },
+};
+
static HandlerResult CategoriesPanel_eventHandler(Panel* super, int ch) {
CategoriesPanel* this = (CategoriesPanel*) super;
@@ -98,19 +132,8 @@ static HandlerResult CategoriesPanel_eventHandler(Panel* super, int ch) {
for (int i = 1; i < size; i++)
ScreenManager_remove(this->scr, 1);
- switch (selected) {
- case 0:
- CategoriesPanel_makeMetersPage(this);
- break;
- case 1:
- CategoriesPanel_makeDisplayOptionsPage(this);
- break;
- case 2:
- CategoriesPanel_makeColorsPage(this);
- break;
- case 3:
- CategoriesPanel_makeColumnsPage(this);
- break;
+ if (selected >= 0 && (size_t)selected < ARRAYSIZE(categoriesPanelPages)) {
+ categoriesPanelPages[selected].ctor(this);
}
}
return result;
@@ -135,9 +158,10 @@ CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Settings* settings, Hea
this->header = header;
this->pl = pl;
Panel_setHeader(super, "Setup");
- Panel_add(super, (Object*) ListItem_new("Meters", 0));
- Panel_add(super, (Object*) ListItem_new("Display options", 0));
- Panel_add(super, (Object*) ListItem_new("Colors", 0));
- Panel_add(super, (Object*) ListItem_new("Columns", 0));
+ for (size_t i = 0; i < ARRAYSIZE(categoriesPanelPages); i++)
+ Panel_add(super, (Object*) ListItem_new(categoriesPanelPages[i].name, 0));
+
+ ScreenManager_add(scr, super, 16);
+ categoriesPanelPages[0].ctor(this);
return this;
}
diff --git a/CategoriesPanel.h b/CategoriesPanel.h
index 0582c64..451a483 100644
--- a/CategoriesPanel.h
+++ b/CategoriesPanel.h
@@ -13,6 +13,7 @@ in the source distribution for its full text.
#include "ScreenManager.h"
#include "Settings.h"
+
typedef struct CategoriesPanel_ {
Panel super;
ScreenManager* scr;
@@ -22,8 +23,6 @@ typedef struct CategoriesPanel_ {
ProcessList* pl;
} CategoriesPanel;
-void CategoriesPanel_makeMetersPage(CategoriesPanel* this);
-
extern const PanelClass CategoriesPanel_class;
CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Settings* settings, Header* header, ProcessList* pl);
diff --git a/ChangeLog b/ChangeLog
index 126f11b..b5725ec 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,153 @@
+What's new in version 3.1.0
+
+* Updated COPYING file to remove the PLPA exemption (appendix 2)
+ With this change the license is now GPLv2 without any additional wording.
+* Improved default sort ordering
+ Note for users: This may lead to an inverted sort order on startup of
+ htop 3.1.0 compared to previous versions.
+ This is due to what is stored in your htoprc file. Solution: Press I
+ (to invert sort order).
+ This changed setting will be saved by htop on exit as long as it can
+ write to your htoprc file.
+* The compile-time option to cater specifically for running htop as
+ setuid has been removed
+* Add read-only option
+ This allows htop to be run in an non-intrusive fashion where it acts only
+ as a process viewer disabling all functions to manipulate system state.
+ Note: This is not a security feature!
+* Move the code for handling the command line formatting related tasks
+ to be shared across all platforms
+ This means important features like stale binary/library highlighting
+ can now be available on all supported platforms.
+* Make the EXE and COMM columns available on all platforms
+ All supported platforms have the name of the executable (EXE) and a
+ self-chosen thread/command name (COMM) available one way or the other.
+ Moving this column to be handled as a platform-independently available
+ information simplifies the markup of the command line.
+* Introduce configuration file versioning and config_reader_min_version
+ Starting with this version the configuration file contains a version
+ identifying the minimum version of the configuration parser needed to
+ fully understand the configuration file format.
+ Old configuration file formats are automatically upgraded when
+ saving the config file (htoprc).
+* Make the configuration parser friendlier to users (thanks to Bart Bakker)
+ With this change only settings that cannot be parsed properly are
+ reset to their defaults.
+* Improve default display for systems with many CPUs
+* Add the process ELAPSED time column
+* Improve the process STATE column sorting
+* Reworked handling resize and redrawing of the UI
+* Fixed an issue where the LED meter mode could overflow allotted space
+* Allow text mode Meters to span empty neighbors to the right
+* Rescale graph meters when value of total changes
+ (thanks to Michael Schönitzer)
+* Update generic process field display
+ Usually "uninteresting" values in columns like 1 thread, nice value
+ of 0, CPU and memory of 0%, idle/sleeping state, etc. are shown with
+ reduced intensity (dark grey)
+* Option and key ("*") to collapse / expand all branches under PID 1
+ (and PID 2 if kernel threads are shown) (thanks to Krishna Chaitanya)
+* Keep following a process when inverting the sort order, displaying
+ the help screen or hiding/unhiding userland threads.
+ If a thread is currently selected the selection is updated to point
+ to the thread's parent process. (thanks to Gonzalo, et.al.)
+* Reorder process scanning to be performed before updating the display
+ of the meters in the header
+* Always check the user for a process for any changes.
+ This affects multiple platforms that previously didn't correctly handle
+ the user field for a process to change at runtime (e.g. due to seteuid
+ or similar syscalls).
+* Disable mouse option when support is unavailable
+* Support curses libraries without ncurses mouse support
+ (thanks to Santhosh Raju)
+* Support offline and hot-swapping of CPUs on all platforms
+* Fix the CPU Meter for machines with more than 256 CPUs
+* Supplemented the "show updated/deleted executables" feature (red basename)
+ to indicate when linked libraries were updated (yellow basename)
+* Apply the stale binary highlighting for the EXE column in addition to
+ the command line field
+* Add new combined Memory and Swap meter
+* Implement bar and graph mode for NetworkIO Meter
+ (thanks to Michael F. Schönitzer)
+* Rework TTY column to be more consistent across platforms
+* Make the CWD column generally available on all platforms
+ (thanks to Santhosh Raju et. al.)
+* Add Performance Co-Pilot (PCP) platform support
+ This is added via a separate pcp-htop(1) binary which provides remote host
+ analysis, new Meters for any PCP metric and new Columns for any PCP process
+ metric - see the pcp-htop(5) man page for further details.
+ (thanks to Sohaib Mohamed)
+* Add Linux columns and key bindings for process autogroup identifier
+ and nice value
+* Change available and used memory reporting on Linux to be based on
+ MemAvailable (Kernel 3.14+) (thanks to Chris Cheney and Tomas Wido)
+* Add a new SysArchMeter showing kernel and platform information
+ (thanks to ahgamut)
+* Linux memory usage explicitly treats tmpfs memory usage as shared memory
+ This is to make memory used by tmpfs visible as this cannot be freed
+ unlike normal filesystem cache data.
+* Exclude zram devices when calculating DiskIO on Linux
+* Use PATH lookup for systemctl in systemd meter (thanks to Scott Olson)
+* Add native platform support for NetBSD
+ This allows htop to run on NetBSD without the need for active Linux
+ emulation of the procfs filesystem.
+ (thanks to Santhosh Raju and Nia Alarie)
+* Add NetworkIO, DiskIO, CPU frequency, and battery meter support on NetBSD
+ (thanks to Nia Alarie)
+* Fix NetBSD display of in-use and cached memory (thanks to Nia Alarie)
+* Rework NetBSD CPU and memory accounting (thanks to Santhosh Raju)
+* Fix NetBSD accounting of user and kernel threads (thanks to Santhosh Raju)
+* Initial work to allow building with default libcurses on NetBSD
+ (thanks to Santhosh Raju)
+* FreeBSD updates - implement process majflt and processor column values
+* Add FreeBSD support for CPU frequency and temperature
+* Fixes and cleanups for ZFS Meters and metrics
+* Correctly color the ZFS ARC ratio (thanks to Ross Williams)
+* Bugfixes related to CPU time display/calculations for darwin on M1 systems
+ (thanks to Alexander Momchilov)
+* Harmonize the handling of multiple batteries across different platforms.
+ The system is now considered to run on AC if at least one power supply
+ marked as AC is found in the system.
+ Battery capacity is summed up over all batteries found.
+ This also changes the old behavior that batteries reported by the
+ system after the first AC adapter where sometimes ignored.
+* Correctly handle multiple batteries on Darwin.
+ Resolves a possible memory leak on systems with multiple batteries.
+* Handle Linux Shmem being part of Cached in the MemoryMeter
+* Add SwapCached to the Linux swap meter (thanks to David Zarzycki)
+* Convert process time to days if applicable (thanks to David Zarzycki)
+* Always show the number of threads in the TaskMeter, even when threads
+ are not shown in the process list
+* Fix Linux --drop-capabilities option handling
+* Correctly detect failure to initialize Linux boottime
+* Overhaul the Linux memory fields to partition them like free(1) now does
+* Improve the Linux process I/O column values
+* Rework the libsensors parsing on Linux
+* Update the MemoryMeter to display shared memory
+* Update OpenBSD platform - implement additional columns, scan LWP,
+ proper markup for STATE, show CPU frequency
+* Fix the tree view on OpenBSD when hiding kernel threads
+* Remove old InfoScreen lines before re-scanning (thanks to Øystein Hiåsen)
+* Document historic naming of Light-Weight Processes column aka threads
+* Improve user interaction when the last process entry is selected
+* Draw the panel header on the TraceScreen (thanks to Youngjae Lee)
+* Add mouse wheel scroll and fix mouse selection on the InfoScreen
+ (thanks to Youngjae Lee)
+* Add a HugepageMeter and subtract hugepages from normal memory
+* Display wide characters in LED meters and restore non-wide ncurses support
+* Add command line option to drop Linux capabilities
+* Support scheduler affinity on platforms beyond Linux
+* Report on any failure to write the configuration file
+* Cache stderr to be able to print assert messages.
+ These messages are shown in case htop terminates unexpectedly.
+* Print current settings on crash
+* Reset signal handlers on program exit
+* Add configure script option to create a static htop binary
+* Resolved longer-standing compilation issues on Solaris/Illumos
+* Check for availability of set_escdelay in configure
+ (thanks to Stefan Polluks)
+* Build system updates for autotools 2.70
+
What's new in version 3.0.5
* BUGFIX / SECURITY: InfoScreen: fix uncontrolled format string
@@ -11,7 +161,7 @@ What's new in version 3.0.5
* Drop usage of formatted error messages from <err.h>
* Show arrow indicating order of sorted process column
* Lots of plumbing around the internal Hashtable, hardening and code cleanups
-* LibSensors: add support for Ryzen CPUs (vor 5 Tagen)
+* LibSensors: add support for Ryzen CPUs
(thanks to Matej Dian)
* BUGFIX: Fix CPU percentage on M1 silicon Macs
(thanks to Luke Groeninger)
diff --git a/ClockMeter.c b/ClockMeter.c
index 023ca42..79bdecd 100644
--- a/ClockMeter.c
+++ b/ClockMeter.c
@@ -10,21 +10,24 @@ in the source distribution for its full text.
#include "ClockMeter.h"
#include <time.h>
+#include <sys/time.h>
#include "CRT.h"
#include "Object.h"
+#include "ProcessList.h"
static const int ClockMeter_attributes[] = {
CLOCK
};
-static void ClockMeter_updateValues(Meter* this, char* buffer, size_t size) {
- time_t t = time(NULL);
+static void ClockMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+
struct tm result;
- struct tm* lt = localtime_r(&t, &result);
+ const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
this->values[0] = lt->tm_hour * 60 + lt->tm_min;
- strftime(buffer, size, "%H:%M:%S", lt);
+ strftime(this->txtBuffer, sizeof(this->txtBuffer), "%H:%M:%S", lt);
}
const MeterClass ClockMeter_class = {
diff --git a/ClockMeter.h b/ClockMeter.h
index ecd4b6a..675df3d 100644
--- a/ClockMeter.h
+++ b/ClockMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass ClockMeter_class;
#endif
diff --git a/ColorsPanel.c b/ColorsPanel.c
index c076adc..79f001c 100644
--- a/ColorsPanel.c
+++ b/ColorsPanel.c
@@ -7,17 +7,16 @@ in the source distribution for its full text.
#include "ColorsPanel.h"
+#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include "CRT.h"
#include "FunctionBar.h"
-#include "Header.h"
+#include "Macros.h"
#include "Object.h"
#include "OptionItem.h"
#include "ProvideCurses.h"
-#include "RichString.h"
-#include "Vector.h"
// TO ADD A NEW SCHEME:
@@ -51,7 +50,7 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) {
ColorsPanel* this = (ColorsPanel*) super;
HandlerResult result = IGNORED;
- int mark = Panel_getSelectedIndex(super);
+ int mark;
switch(ch) {
case 0x0a:
@@ -60,6 +59,7 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) {
case KEY_MOUSE:
case KEY_RECLICK:
case ' ':
+ mark = Panel_getSelectedIndex(super);
assert(mark >= 0);
assert(mark < LAST_COLORSCHEME);
for (int i = 0; ColorSchemeNames[i] != NULL; i++)
@@ -86,14 +86,13 @@ const PanelClass ColorsPanel_class = {
.eventHandler = ColorsPanel_eventHandler
};
-ColorsPanel* ColorsPanel_new(Settings* settings, ScreenManager* scr) {
+ColorsPanel* ColorsPanel_new(Settings* settings) {
ColorsPanel* this = AllocThis(ColorsPanel);
Panel* super = (Panel*) this;
FunctionBar* fuBar = FunctionBar_new(ColorsFunctions, NULL, NULL);
Panel_init(super, 1, 1, 1, 1, Class(CheckItem), true, fuBar);
this->settings = settings;
- this->scr = scr;
assert(ARRAYSIZE(ColorSchemeNames) == LAST_COLORSCHEME + 1);
diff --git a/ColorsPanel.h b/ColorsPanel.h
index f63ca35..047f7af 100644
--- a/ColorsPanel.h
+++ b/ColorsPanel.h
@@ -8,18 +8,17 @@ in the source distribution for its full text.
*/
#include "Panel.h"
-#include "ScreenManager.h"
#include "Settings.h"
+
typedef struct ColorsPanel_ {
Panel super;
Settings* settings;
- ScreenManager* scr;
} ColorsPanel;
extern const PanelClass ColorsPanel_class;
-ColorsPanel* ColorsPanel_new(Settings* settings, ScreenManager* scr);
+ColorsPanel* ColorsPanel_new(Settings* settings);
#endif
diff --git a/ColumnsPanel.c b/ColumnsPanel.c
index ea56166..d0c2b88 100644
--- a/ColumnsPanel.c
+++ b/ColumnsPanel.c
@@ -7,11 +7,14 @@ in the source distribution for its full text.
#include "ColumnsPanel.h"
+#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include "CRT.h"
+#include "DynamicColumn.h"
#include "FunctionBar.h"
+#include "Hashtable.h"
#include "ListItem.h"
#include "Object.h"
#include "Process.h"
@@ -115,6 +118,26 @@ const PanelClass ColumnsPanel_class = {
.eventHandler = ColumnsPanel_eventHandler
};
+static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) {
+ const char* name;
+ if (key < LAST_PROCESSFIELD) {
+ name = Process_fields[key].name;
+ } else {
+ const DynamicColumn* column = Hashtable_get(columns, key);
+ assert(column);
+ if (!column) {
+ name = NULL;
+ } else {
+ name = column->caption ? column->caption : column->heading;
+ if (!name)
+ name = column->name; /* name is a mandatory field */
+ }
+ }
+ if (name == NULL)
+ name = "- ";
+ Panel_add(super, (Object*) ListItem_new(name, key));
+}
+
ColumnsPanel* ColumnsPanel_new(Settings* settings) {
ColumnsPanel* this = AllocThis(ColumnsPanel);
Panel* super = (Panel*) this;
@@ -125,12 +148,11 @@ ColumnsPanel* ColumnsPanel_new(Settings* settings) {
this->moving = false;
Panel_setHeader(super, "Active Columns");
- ProcessField* fields = this->settings->fields;
- for (; *fields; fields++) {
- if (Process_fields[*fields].name) {
- Panel_add(super, (Object*) ListItem_new(Process_fields[*fields].name, *fields));
- }
- }
+ Hashtable* dynamicColumns = settings->dynamicColumns;
+ const ProcessField* fields = settings->fields;
+ for (; *fields; fields++)
+ ColumnsPanel_add(super, *fields, dynamicColumns);
+
return this;
}
@@ -143,7 +165,8 @@ void ColumnsPanel_update(Panel* super) {
for (int i = 0; i < size; i++) {
int key = ((ListItem*) Panel_get(super, i))->key;
this->settings->fields[i] = key;
- this->settings->flags |= Process_fields[key].flags;
+ if (key < LAST_PROCESSFIELD)
+ this->settings->flags |= Process_fields[key].flags;
}
this->settings->fields[size] = 0;
}
diff --git a/ColumnsPanel.h b/ColumnsPanel.h
index 173e039..8bc806e 100644
--- a/ColumnsPanel.h
+++ b/ColumnsPanel.h
@@ -12,6 +12,7 @@ in the source distribution for its full text.
#include "Panel.h"
#include "Settings.h"
+
typedef struct ColumnsPanel_ {
Panel super;
diff --git a/CommandLine.c b/CommandLine.c
new file mode 100644
index 0000000..f0d2255
--- /dev/null
+++ b/CommandLine.c
@@ -0,0 +1,400 @@
+/*
+htop - CommandLine.c
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "CommandLine.h"
+
+#include <assert.h>
+#include <getopt.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "Action.h"
+#include "CRT.h"
+#include "DynamicColumn.h"
+#include "DynamicMeter.h"
+#include "Hashtable.h"
+#include "Header.h"
+#include "IncSet.h"
+#include "MainPanel.h"
+#include "MetersPanel.h"
+#include "Panel.h"
+#include "Platform.h"
+#include "Process.h"
+#include "ProcessList.h"
+#include "ProvideCurses.h"
+#include "ScreenManager.h"
+#include "Settings.h"
+#include "UsersTable.h"
+#include "XUtils.h"
+
+
+static void printVersionFlag(const char* name) {
+ printf("%s " VERSION "\n", name);
+}
+
+static void printHelpFlag(const char* name) {
+ printf("%s " VERSION "\n"
+ COPYRIGHT "\n"
+ "Released under the GNU GPLv2.\n\n"
+ "-C --no-color Use a monochrome color scheme\n"
+ "-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"
+ " --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);
+ Platform_longOptionsUsage(name);
+ printf("\n"
+ "Long options may be passed with a single dash.\n\n"
+ "Press F1 inside %s for online help.\n"
+ "See 'man %s' for more information.\n", name, name);
+}
+
+// ----------------------------------------
+
+typedef struct CommandLineSettings_ {
+ Hashtable* pidMatchList;
+ char* commFilter;
+ uid_t userId;
+ int sortKey;
+ int delay;
+ bool useColors;
+ bool enableMouse;
+ bool treeView;
+ bool allowUnicode;
+ bool highlightChanges;
+ int highlightDelaySecs;
+ bool readonly;
+} CommandLineSettings;
+
+static CommandLineSettings parseArguments(const char* program, int argc, char** argv) {
+
+ CommandLineSettings flags = {
+ .pidMatchList = NULL,
+ .commFilter = NULL,
+ .userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2))
+ .sortKey = 0,
+ .delay = -1,
+ .useColors = true,
+ .enableMouse = true,
+ .treeView = false,
+ .allowUnicode = true,
+ .highlightChanges = false,
+ .highlightDelaySecs = -1,
+ .readonly = false,
+ };
+
+ const struct option long_opts[] =
+ {
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {"delay", required_argument, 0, 'd'},
+ {"sort-key", required_argument, 0, 's'},
+ {"user", optional_argument, 0, 'u'},
+ {"no-color", no_argument, 0, 'C'},
+ {"no-colour", no_argument, 0, 'C'},
+ {"no-mouse", no_argument, 0, 'M'},
+ {"no-unicode", no_argument, 0, 'U'},
+ {"tree", no_argument, 0, 't'},
+ {"pid", required_argument, 0, 'p'},
+ {"filter", required_argument, 0, 'F'},
+ {"highlight-changes", optional_argument, 0, 'H'},
+ {"readonly", no_argument, 0, 128},
+ PLATFORM_LONG_OPTIONS
+ {0, 0, 0, 0}
+ };
+
+ int opt, opti = 0;
+ /* Parse arguments */
+ while ((opt = getopt_long(argc, argv, "hVMCs:td:u::Up:F:H::", long_opts, &opti))) {
+ if (opt == EOF)
+ break;
+ switch (opt) {
+ case 'h':
+ printHelpFlag(program);
+ exit(0);
+ case 'V':
+ printVersionFlag(program);
+ exit(0);
+ case 's':
+ assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
+ if (String_eq(optarg, "help")) {
+ for (int j = 1; j < LAST_PROCESSFIELD; j++) {
+ const char* name = Process_fields[j].name;
+ const char* description = Process_fields[j].description;
+ if (name) printf("%19s %s\n", name, description);
+ }
+ exit(0);
+ }
+ flags.sortKey = 0;
+ for (int j = 1; j < LAST_PROCESSFIELD; j++) {
+ if (Process_fields[j].name == NULL)
+ continue;
+ if (String_eq(optarg, Process_fields[j].name)) {
+ flags.sortKey = j;
+ break;
+ }
+ }
+ if (flags.sortKey == 0) {
+ fprintf(stderr, "Error: invalid column \"%s\".\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'd':
+ if (sscanf(optarg, "%16d", &(flags.delay)) == 1) {
+ if (flags.delay < 1) flags.delay = 1;
+ if (flags.delay > 100) flags.delay = 100;
+ } else {
+ fprintf(stderr, "Error: invalid delay value \"%s\".\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'u':
+ {
+ const char *username = optarg;
+ if (!username && optind < argc && argv[optind] != NULL &&
+ (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
+ username = argv[optind++];
+ }
+
+ if (!username) {
+ flags.userId = geteuid();
+ } else if (!Action_setUserOnly(username, &(flags.userId))) {
+ fprintf(stderr, "Error: invalid user \"%s\".\n", username);
+ exit(1);
+ }
+ break;
+ }
+ case 'C':
+ flags.useColors = false;
+ break;
+ case 'M':
+#ifdef HAVE_GETMOUSE
+ flags.enableMouse = false;
+#endif
+ break;
+ case 'U':
+ flags.allowUnicode = false;
+ break;
+ case 't':
+ flags.treeView = true;
+ break;
+ case 'p': {
+ assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
+ char* argCopy = xStrdup(optarg);
+ char* saveptr;
+ const char* pid = strtok_r(argCopy, ",", &saveptr);
+
+ if (!flags.pidMatchList) {
+ flags.pidMatchList = Hashtable_new(8, false);
+ }
+
+ while(pid) {
+ unsigned int num_pid = atoi(pid);
+ // deepcode ignore CastIntegerToAddress: we just want a non-NUll pointer here
+ Hashtable_put(flags.pidMatchList, num_pid, (void *) 1);
+ pid = strtok_r(NULL, ",", &saveptr);
+ }
+ free(argCopy);
+
+ break;
+ }
+ case 'F': {
+ assert(optarg);
+ free_and_xStrdup(&flags.commFilter, optarg);
+ break;
+ }
+ case 'H': {
+ const char *delay = optarg;
+ if (!delay && optind < argc && argv[optind] != NULL &&
+ (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
+ delay = argv[optind++];
+ }
+ if (delay) {
+ if (sscanf(delay, "%16d", &(flags.highlightDelaySecs)) == 1) {
+ if (flags.highlightDelaySecs < 1)
+ flags.highlightDelaySecs = 1;
+ } else {
+ fprintf(stderr, "Error: invalid highlight delay value \"%s\".\n", delay);
+ exit(1);
+ }
+ }
+ flags.highlightChanges = true;
+ break;
+ }
+ case 128:
+ flags.readonly = true;
+ break;
+
+ default:
+ if (Platform_getLongOption(opt, argc, argv) == false)
+ exit(1);
+ break;
+ }
+ }
+ return flags;
+}
+
+static void CommandLine_delay(ProcessList* pl, unsigned long millisec) {
+ struct timespec req = {
+ .tv_sec = 0,
+ .tv_nsec = millisec * 1000000L
+ };
+ while (nanosleep(&req, &req) == -1)
+ continue;
+ Platform_gettime_realtime(&pl->realtime, &pl->realtimeMs);
+}
+
+static void setCommFilter(State* state, char** commFilter) {
+ ProcessList* pl = state->pl;
+ IncSet* inc = state->mainPanel->inc;
+
+ IncSet_setFilter(inc, *commFilter);
+ pl->incFilter = IncSet_filter(inc);
+
+ free(*commFilter);
+ *commFilter = NULL;
+}
+
+int CommandLine_run(const char* name, int argc, char** argv) {
+
+ /* initialize locale */
+ const char* lc_ctype;
+ if ((lc_ctype = getenv("LC_CTYPE")) || (lc_ctype = getenv("LC_ALL")))
+ setlocale(LC_CTYPE, lc_ctype);
+ else
+ setlocale(LC_CTYPE, "");
+
+ CommandLineSettings flags = parseArguments(name, argc, argv);
+
+ if (flags.readonly)
+ Settings_enableReadonly();
+
+ Platform_init();
+
+ Process_setupColumnWidths();
+
+ UsersTable* ut = UsersTable_new();
+ Hashtable* dc = DynamicColumns_new();
+ Hashtable* dm = DynamicMeters_new();
+ if (!dc)
+ dc = Hashtable_new(0, true);
+
+ ProcessList* pl = ProcessList_new(ut, dm, dc, flags.pidMatchList, flags.userId);
+
+ Settings* settings = Settings_new(pl->activeCPUs, dc);
+ pl->settings = settings;
+
+ Header* header = Header_new(pl, settings, 2);
+
+ Header_populateFromSettings(header);
+
+ if (flags.delay != -1)
+ settings->delay = flags.delay;
+ if (!flags.useColors)
+ settings->colorScheme = COLORSCHEME_MONOCHROME;
+#ifdef HAVE_GETMOUSE
+ if (!flags.enableMouse)
+ settings->enableMouse = false;
+#endif
+ if (flags.treeView)
+ settings->treeView = true;
+ if (flags.highlightChanges)
+ settings->highlightChanges = true;
+ if (flags.highlightDelaySecs != -1)
+ settings->highlightDelaySecs = flags.highlightDelaySecs;
+ if (flags.sortKey > 0) {
+ // -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_setSortKey(settings, flags.sortKey);
+ }
+
+ CRT_init(settings, flags.allowUnicode);
+
+ MainPanel* panel = MainPanel_new();
+ ProcessList_setPanel(pl, (Panel*) panel);
+
+ MainPanel_updateTreeFunctions(panel, settings->treeView);
+
+ State state = {
+ .settings = settings,
+ .ut = ut,
+ .pl = pl,
+ .mainPanel = panel,
+ .header = header,
+ .pauseProcessUpdate = false,
+ .hideProcessSelection = false,
+ };
+
+ MainPanel_setState(panel, &state);
+ if (flags.commFilter)
+ setCommFilter(&state, &(flags.commFilter));
+
+ ScreenManager* scr = ScreenManager_new(header, settings, &state, true);
+ ScreenManager_add(scr, (Panel*) panel, -1);
+
+ ProcessList_scan(pl, false);
+ CommandLine_delay(pl, 75);
+ ProcessList_scan(pl, false);
+
+ if (settings->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();
+
+ Platform_done();
+
+ CRT_done();
+
+ if (settings->changed) {
+ int r = Settings_write(settings, false);
+ if (r < 0)
+ fprintf(stderr, "Can not save configuration to %s: %s\n", settings->filename, strerror(-r));
+ }
+
+ Header_delete(header);
+ ProcessList_delete(pl);
+
+ ScreenManager_delete(scr);
+ MetersPanel_cleanup();
+
+ UsersTable_delete(ut);
+
+ if (flags.pidMatchList)
+ Hashtable_delete(flags.pidMatchList);
+
+ CRT_resetSignalHandlers();
+
+ /* Delete these last, since they can get accessed in the crash handler */
+ Settings_delete(settings);
+ DynamicColumns_delete(dc);
+ DynamicMeters_delete(dm);
+
+ return 0;
+}
diff --git a/CommandLine.h b/CommandLine.h
new file mode 100644
index 0000000..b1f157e
--- /dev/null
+++ b/CommandLine.h
@@ -0,0 +1,14 @@
+#ifndef HEADER_CommandLine
+#define HEADER_CommandLine
+/*
+htop - CommandLine.h
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+int CommandLine_run(const char* name, int argc, char** argv);
+
+#endif
diff --git a/CommandScreen.c b/CommandScreen.c
index 12b1987..6a87d13 100644
--- a/CommandScreen.c
+++ b/CommandScreen.c
@@ -2,13 +2,13 @@
#include "CommandScreen.h"
+#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "Macros.h"
#include "Panel.h"
#include "ProvideCurses.h"
-#include "XUtils.h"
static void CommandScreen_scan(InfoScreen* this) {
diff --git a/Compat.c b/Compat.c
index 55be1b1..1077c08 100644
--- a/Compat.c
+++ b/Compat.c
@@ -11,18 +11,12 @@ in the source distribution for its full text.
#include <errno.h>
#include <fcntl.h> // IWYU pragma: keep
-#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h> // IWYU pragma: keep
#include "XUtils.h" // IWYU pragma: keep
-#ifdef HAVE_HOST_GET_CLOCK_SERVICE
-#include <mach/clock.h>
-#include <mach/mach.h>
-#endif
-
int Compat_faccessat(int dirfd,
const char* pathname,
@@ -43,7 +37,7 @@ int Compat_faccessat(int dirfd,
#endif
// Error out on unsupported configurations
- if (dirfd != AT_FDCWD || mode != F_OK) {
+ if (dirfd != (int)AT_FDCWD || mode != F_OK) {
errno = EINVAL;
return -1;
}
@@ -123,31 +117,3 @@ ssize_t Compat_readlinkat(int dirfd,
#endif
}
-
-int Compat_clock_monotonic_gettime(struct timespec *tp) {
-
-#if defined(HAVE_CLOCK_GETTIME)
-
- return clock_gettime(CLOCK_MONOTONIC, tp);
-
-#elif defined(HAVE_HOST_GET_CLOCK_SERVICE)
-
- clock_serv_t cclock;
- mach_timespec_t mts;
-
- host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
- clock_get_time(cclock, &mts);
- mach_port_deallocate(mach_task_self(), cclock);
-
- tp->tv_sec = mts.tv_sec;
- tp->tv_nsec = mts.tv_nsec;
-
- return 0;
-
-#else
-
-#error No Compat_clock_monotonic_gettime() implementation!
-
-#endif
-
-}
diff --git a/Compat.h b/Compat.h
index 9594735..94c2ee2 100644
--- a/Compat.h
+++ b/Compat.h
@@ -56,6 +56,4 @@ ssize_t Compat_readlinkat(int dirfd,
char* buf,
size_t bufsize);
-int Compat_clock_monotonic_gettime(struct timespec *tp);
-
#endif /* HEADER_Compat */
diff --git a/DateMeter.c b/DateMeter.c
index bd6a306..d01f464 100644
--- a/DateMeter.c
+++ b/DateMeter.c
@@ -10,28 +10,30 @@ in the source distribution for its full text.
#include "DateMeter.h"
#include <time.h>
+#include <sys/time.h>
#include "CRT.h"
#include "Object.h"
+#include "ProcessList.h"
static const int DateMeter_attributes[] = {
DATE
};
-static void DateMeter_updateValues(Meter* this, char* buffer, size_t size) {
- time_t t = time(NULL);
+static void DateMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+
struct tm result;
- struct tm* lt = localtime_r(&t, &result);
+ const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
this->values[0] = lt->tm_yday;
int year = lt->tm_year + 1900;
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
this->total = 366;
- }
- else {
+ } else {
this->total = 365;
}
- strftime(buffer, size, "%F", lt);
+ strftime(this->txtBuffer, sizeof(this->txtBuffer), "%F", lt);
}
const MeterClass DateMeter_class = {
diff --git a/DateMeter.h b/DateMeter.h
index 6345576..a6ce47a 100644
--- a/DateMeter.h
+++ b/DateMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass DateMeter_class;
#endif
diff --git a/DateTimeMeter.c b/DateTimeMeter.c
index 0d231cd..a042a61 100644
--- a/DateTimeMeter.c
+++ b/DateTimeMeter.c
@@ -10,28 +10,30 @@ in the source distribution for its full text.
#include "DateTimeMeter.h"
#include <time.h>
+#include <sys/time.h>
#include "CRT.h"
#include "Object.h"
+#include "ProcessList.h"
static const int DateTimeMeter_attributes[] = {
DATETIME
};
-static void DateTimeMeter_updateValues(Meter* this, char* buffer, size_t size) {
- time_t t = time(NULL);
+static void DateTimeMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+
struct tm result;
- struct tm* lt = localtime_r(&t, &result);
+ const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
int year = lt->tm_year + 1900;
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
this->total = 366;
- }
- else {
+ } else {
this->total = 365;
}
this->values[0] = lt->tm_yday;
- strftime(buffer, size, "%F %H:%M:%S", lt);
+ strftime(this->txtBuffer, sizeof(this->txtBuffer), "%F %H:%M:%S", lt);
}
const MeterClass DateTimeMeter_class = {
diff --git a/DateTimeMeter.h b/DateTimeMeter.h
index 6cb73c2..04cc327 100644
--- a/DateTimeMeter.h
+++ b/DateTimeMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass DateTimeMeter_class;
#endif
diff --git a/DiskIOMeter.c b/DiskIOMeter.c
index 04ca5a7..67122a1 100644
--- a/DiskIOMeter.c
+++ b/DiskIOMeter.c
@@ -9,12 +9,12 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdio.h>
-#include <sys/time.h>
#include "CRT.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
+#include "ProcessList.h"
#include "RichString.h"
#include "XUtils.h"
@@ -26,51 +26,55 @@ static const int DiskIOMeter_attributes[] = {
};
static bool hasData = false;
-static unsigned long int cached_read_diff = 0;
-static unsigned long int cached_write_diff = 0;
-static double cached_utilisation_diff = 0.0;
+static uint32_t cached_read_diff;
+static uint32_t cached_write_diff;
+static double cached_utilisation_diff;
-static void DiskIOMeter_updateValues(Meter* this, char* buffer, size_t len) {
- static unsigned long long int cached_last_update = 0;
+static void DiskIOMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
- struct timeval tv;
- gettimeofday(&tv, NULL);
- unsigned long long int timeInMilliSeconds = (unsigned long long int)tv.tv_sec * 1000 + (unsigned long long int)tv.tv_usec / 1000;
- unsigned long long int passedTimeInMs = timeInMilliSeconds - cached_last_update;
+ static uint64_t cached_last_update;
+ uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update;
/* update only every 500ms */
if (passedTimeInMs > 500) {
- static unsigned long int cached_read_total = 0;
- static unsigned long int cached_write_total = 0;
- static unsigned long int cached_msTimeSpend_total = 0;
+ static uint64_t cached_read_total;
+ static uint64_t cached_write_total;
+ static uint64_t cached_msTimeSpend_total;
+ uint64_t diff;
- cached_last_update = timeInMilliSeconds;
+ cached_last_update = pl->realtimeMs;
DiskIOData data;
hasData = Platform_getDiskIO(&data);
if (!hasData) {
this->values[0] = 0;
- xSnprintf(buffer, len, "no data");
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
return;
}
if (data.totalBytesRead > cached_read_total) {
- cached_read_diff = (data.totalBytesRead - cached_read_total) / 1024; /* Meter_humanUnit() expects unit in kilo */
+ diff = data.totalBytesRead - cached_read_total;
+ diff /= 1024; /* Meter_humanUnit() expects unit in kilo */
+ cached_read_diff = (uint32_t)diff;
} else {
cached_read_diff = 0;
}
cached_read_total = data.totalBytesRead;
if (data.totalBytesWritten > cached_write_total) {
- cached_write_diff = (data.totalBytesWritten - cached_write_total) / 1024; /* Meter_humanUnit() expects unit in kilo */
+ diff = data.totalBytesWritten - cached_write_total;
+ diff /= 1024; /* Meter_humanUnit() expects unit in kilo */
+ cached_write_diff = (uint32_t)diff;
} else {
cached_write_diff = 0;
}
cached_write_total = data.totalBytesWritten;
if (data.totalMsTimeSpend > cached_msTimeSpend_total) {
- cached_utilisation_diff = 100 * (double)(data.totalMsTimeSpend - cached_msTimeSpend_total) / passedTimeInMs;
+ diff = data.totalMsTimeSpend - cached_msTimeSpend_total;
+ cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs;
} else {
cached_utilisation_diff = 0.0;
}
@@ -83,20 +87,21 @@ static void DiskIOMeter_updateValues(Meter* this, char* buffer, size_t len) {
char bufferRead[12], bufferWrite[12];
Meter_humanUnit(bufferRead, cached_read_diff, sizeof(bufferRead));
Meter_humanUnit(bufferWrite, cached_write_diff, sizeof(bufferWrite));
- snprintf(buffer, len, "%sB %sB %.1f%%", bufferRead, bufferWrite, cached_utilisation_diff);
+ snprintf(this->txtBuffer, sizeof(this->txtBuffer), "%sB %sB %.1f%%", bufferRead, bufferWrite, cached_utilisation_diff);
}
-static void DIskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
+static void DiskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
if (!hasData) {
RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
return;
}
char buffer[16];
+ int len;
int color = cached_utilisation_diff > 40.0 ? METER_VALUE_NOTICE : METER_VALUE;
- xSnprintf(buffer, sizeof(buffer), "%.1f%%", cached_utilisation_diff);
- RichString_writeAscii(out, CRT_colors[color], buffer);
+ len = xSnprintf(buffer, sizeof(buffer), "%.1f%%", cached_utilisation_diff);
+ RichString_appendnAscii(out, CRT_colors[color], buffer, len);
RichString_appendAscii(out, CRT_colors[METER_TEXT], " read: ");
Meter_humanUnit(buffer, cached_read_diff, sizeof(buffer));
@@ -111,7 +116,7 @@ const MeterClass DiskIOMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
- .display = DIskIOMeter_display
+ .display = DiskIOMeter_display
},
.updateValues = DiskIOMeter_updateValues,
.defaultMode = TEXT_METERMODE,
diff --git a/DiskIOMeter.h b/DiskIOMeter.h
index b2b3e8d..68ea638 100644
--- a/DiskIOMeter.h
+++ b/DiskIOMeter.h
@@ -9,10 +9,11 @@ in the source distribution for its full text.
#include "Meter.h"
+
typedef struct DiskIOData_ {
- unsigned long int totalBytesRead;
- unsigned long int totalBytesWritten;
- unsigned long int totalMsTimeSpend;
+ uint64_t totalBytesRead;
+ uint64_t totalBytesWritten;
+ uint64_t totalMsTimeSpend;
} DiskIOData;
extern const MeterClass DiskIOMeter_class;
diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c
index 7e02b23..7e05ae0 100644
--- a/DisplayOptionsPanel.c
+++ b/DisplayOptionsPanel.c
@@ -72,8 +72,9 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {
Header* header = this->scr->header;
Header_calculateHeight(header);
Header_reinit(header);
+ Header_updateData(header);
Header_draw(header);
- ScreenManager_resize(this->scr, this->scr->x1, header->height, this->scr->x2, this->scr->y2);
+ ScreenManager_resize(this->scr);
}
return result;
}
@@ -98,6 +99,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
Panel_setHeader(super, "Display options");
Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->treeView)));
Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->treeViewAlwaysByPID)));
+ Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->allBranchesCollapsed)));
Panel_add(super, (Object*) CheckItem_newByRef("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)));
@@ -105,6 +107,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
Panel_add(super, (Object*) CheckItem_newByRef("Show custom thread names", &(settings->showThreadNames)));
Panel_add(super, (Object*) CheckItem_newByRef("Show program path", &(settings->showProgramPath)));
Panel_add(super, (Object*) CheckItem_newByRef("Highlight program \"basename\"", &(settings->highlightBaseName)));
+ Panel_add(super, (Object*) CheckItem_newByRef("Highlight out-dated/removed programs", &(settings->highlightDeletedExe)));
Panel_add(super, (Object*) CheckItem_newByRef("Merge exe, comm and cmdline in Command", &(settings->showMergedCommand)));
Panel_add(super, (Object*) CheckItem_newByRef("- Try to find comm in cmdline (when Command is merged)", &(settings->findCommInCmdline)));
Panel_add(super, (Object*) CheckItem_newByRef("- Try to strip exe from cmdline (when Command is merged)", &(settings->stripExeFromCmdline)));
@@ -116,14 +119,24 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
Panel_add(super, (Object*) CheckItem_newByRef("Add guest time in CPU meter percentage", &(settings->accountGuestInCPUMeter)));
Panel_add(super, (Object*) CheckItem_newByRef("Also show CPU percentage numerically", &(settings->showCPUUsage)));
Panel_add(super, (Object*) CheckItem_newByRef("Also show CPU frequency", &(settings->showCPUFrequency)));
- #ifdef HAVE_SENSORS_SENSORS_H
- Panel_add(super, (Object*) CheckItem_newByRef("Also show CPU temperature (requires libsensors)", &(settings->showCPUTemperature)));
+ #ifdef BUILD_WITH_CPU_TEMP
+ Panel_add(super, (Object*) CheckItem_newByRef(
+ #if defined(HTOP_LINUX)
+ "Also show CPU temperature (requires libsensors)",
+ #elif defined(HTOP_FREEBSD)
+ "Also show CPU temperature",
+ #else
+ #error Unknown temperature implementation!
+ #endif
+ &(settings->showCPUTemperature)));
Panel_add(super, (Object*) CheckItem_newByRef("- Show temperature in degree Fahrenheit instead of Celsius", &(settings->degreeFahrenheit)));
#endif
+ #ifdef HAVE_GETMOUSE
Panel_add(super, (Object*) CheckItem_newByRef("Enable the mouse", &(settings->enableMouse)));
+ #endif
Panel_add(super, (Object*) NumberItem_newByRef("Update interval (in seconds)", &(settings->delay), -1, 1, 255));
Panel_add(super, (Object*) CheckItem_newByRef("Highlight new and old processes", &(settings->highlightChanges)));
- Panel_add(super, (Object*) NumberItem_newByRef("- Highlight time (in seconds)", &(settings->highlightDelaySecs), 0, 1, 24*60*60));
+ Panel_add(super, (Object*) NumberItem_newByRef("- Highlight time (in seconds)", &(settings->highlightDelaySecs), 0, 1, 24 * 60 * 60));
Panel_add(super, (Object*) NumberItem_newByRef("Hide main function bar (0 - off, 1 - on ESC until next input, 2 - permanently)", &(settings->hideFunctionBar), 0, 0, 2));
#ifdef HAVE_LIBHWLOC
Panel_add(super, (Object*) CheckItem_newByRef("Show topology when selecting affinity by default", &(settings->topologyAffinity)));
diff --git a/DisplayOptionsPanel.h b/DisplayOptionsPanel.h
index 02b67a0..745f125 100644
--- a/DisplayOptionsPanel.h
+++ b/DisplayOptionsPanel.h
@@ -11,6 +11,7 @@ in the source distribution for its full text.
#include "ScreenManager.h"
#include "Settings.h"
+
typedef struct DisplayOptionsPanel_ {
Panel super;
diff --git a/DynamicColumn.c b/DynamicColumn.c
new file mode 100644
index 0000000..c6a0cc0
--- /dev/null
+++ b/DynamicColumn.c
@@ -0,0 +1,66 @@
+/*
+htop - DynamicColumn.c
+(C) 2021 Sohaib Mohammed
+(C) 2021 htop dev team
+(C) 2021 Red Hat, Inc. All Rights Reserved.
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "DynamicColumn.h"
+
+#include <stddef.h>
+
+#include "Platform.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+
+Hashtable* DynamicColumns_new(void) {
+ return Platform_dynamicColumns();
+}
+
+void DynamicColumns_delete(Hashtable* dynamics) {
+ if (dynamics) {
+ Platform_dynamicColumnsDone(dynamics);
+ Hashtable_delete(dynamics);
+ }
+}
+
+const char* DynamicColumn_init(unsigned int key) {
+ return Platform_dynamicColumnInit(key);
+}
+
+typedef struct {
+ const char* name;
+ const DynamicColumn* data;
+ unsigned int key;
+} DynamicIterator;
+
+static void DynamicColumn_compare(ht_key_t key, void* value, void* data) {
+ const DynamicColumn* column = (const DynamicColumn*)value;
+ DynamicIterator* iter = (DynamicIterator*)data;
+ if (String_eq(iter->name, column->name)) {
+ iter->data = column;
+ iter->key = key;
+ }
+}
+
+const DynamicColumn* DynamicColumn_search(Hashtable* dynamics, const char* name, unsigned int* key) {
+ DynamicIterator iter = { .key = 0, .data = NULL, .name = name };
+ if (dynamics)
+ Hashtable_foreach(dynamics, DynamicColumn_compare, &iter);
+ if (key)
+ *key = iter.key;
+ return iter.data;
+}
+
+const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key) {
+ return (const DynamicColumn*) Hashtable_get(dynamics, key);
+}
+
+bool DynamicColumn_writeField(const Process* proc, RichString* str, unsigned int key) {
+ return Platform_dynamicColumnWriteField(proc, str, key);
+}
diff --git a/DynamicColumn.h b/DynamicColumn.h
new file mode 100644
index 0000000..4760e6e
--- /dev/null
+++ b/DynamicColumn.h
@@ -0,0 +1,34 @@
+#ifndef HEADER_DynamicColumn
+#define HEADER_DynamicColumn
+
+#include <stdbool.h>
+
+#include "Hashtable.h"
+#include "Process.h"
+#include "RichString.h"
+
+
+#define DYNAMIC_MAX_COLUMN_WIDTH 28
+#define DYNAMIC_DEFAULT_COLUMN_WIDTH -5
+
+typedef struct DynamicColumn_ {
+ char name[32]; /* unique, internal-only name */
+ char* heading; /* displayed in main screen */
+ char* caption; /* displayed in setup menu (short name) */
+ char* description; /* displayed in setup menu (detail) */
+ int width; /* display width +/- for value alignment */
+} DynamicColumn;
+
+Hashtable* DynamicColumns_new(void);
+
+void DynamicColumns_delete(Hashtable* dynamics);
+
+const char* DynamicColumn_init(unsigned int key);
+
+const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key);
+
+const DynamicColumn* DynamicColumn_search(Hashtable* dynamics, const char* name, unsigned int* key);
+
+bool DynamicColumn_writeField(const Process* proc, RichString* str, unsigned int key);
+
+#endif
diff --git a/DynamicMeter.c b/DynamicMeter.c
new file mode 100644
index 0000000..16c27f8
--- /dev/null
+++ b/DynamicMeter.c
@@ -0,0 +1,131 @@
+/*
+htop - DynamicMeter.c
+(C) 2021 htop dev team
+(C) 2021 Red Hat, Inc. All Rights Reserved.
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "DynamicMeter.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "CRT.h"
+#include "Object.h"
+#include "Platform.h"
+#include "ProcessList.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+
+static const int DynamicMeter_attributes[] = {
+ DYNAMIC_GRAY,
+ DYNAMIC_DARKGRAY,
+ DYNAMIC_RED,
+ DYNAMIC_GREEN,
+ DYNAMIC_BLUE,
+ DYNAMIC_CYAN,
+ DYNAMIC_MAGENTA,
+ DYNAMIC_YELLOW,
+ DYNAMIC_WHITE
+};
+
+Hashtable* DynamicMeters_new(void) {
+ return Platform_dynamicMeters();
+}
+
+void DynamicMeters_delete(Hashtable* dynamics) {
+ if (dynamics) {
+ Platform_dynamicMetersDone(dynamics);
+ Hashtable_delete(dynamics);
+ }
+}
+
+typedef struct {
+ unsigned int key;
+ const char* name;
+ bool found;
+} DynamicIterator;
+
+static void DynamicMeter_compare(ht_key_t key, void* value, void* data) {
+ const DynamicMeter* meter = (const DynamicMeter*)value;
+ DynamicIterator* iter = (DynamicIterator*)data;
+ if (String_eq(iter->name, meter->name)) {
+ iter->found = true;
+ iter->key = key;
+ }
+}
+
+bool DynamicMeter_search(Hashtable* dynamics, const char* name, unsigned int* key) {
+ DynamicIterator iter = { .key = 0, .name = name, .found = false };
+ if (dynamics)
+ Hashtable_foreach(dynamics, DynamicMeter_compare, &iter);
+ if (key)
+ *key = iter.key;
+ return iter.found;
+}
+
+const char* DynamicMeter_lookup(Hashtable* dynamics, unsigned int key) {
+ const DynamicMeter* meter = Hashtable_get(dynamics, key);
+ return meter ? meter->name : NULL;
+}
+
+static void DynamicMeter_init(Meter* meter) {
+ Platform_dynamicMeterInit(meter);
+}
+
+static void DynamicMeter_updateValues(Meter* meter) {
+ Platform_dynamicMeterUpdateValues(meter);
+}
+
+static void DynamicMeter_display(const Object* cast, RichString* out) {
+ const Meter* meter = (const Meter*)cast;
+ Platform_dynamicMeterDisplay(meter, out);
+}
+
+static const char* DynamicMeter_getCaption(const Meter* this) {
+ const ProcessList* pl = this->pl;
+ const DynamicMeter* meter = Hashtable_get(pl->dynamicMeters, this->param);
+ if (meter)
+ return meter->caption ? meter->caption : meter->name;
+ return this->caption;
+}
+
+static void DynamicMeter_getUiName(const Meter* this, char* name, size_t length) {
+ const ProcessList* pl = this->pl;
+ const DynamicMeter* meter = Hashtable_get(pl->dynamicMeters, this->param);
+ if (meter) {
+ const char* uiName = meter->caption;
+ if (uiName) {
+ int len = strlen(uiName);
+ if (len > 2 && uiName[len - 2] == ':')
+ len -= 2;
+ xSnprintf(name, length, "%.*s", len, uiName);
+ } else {
+ xSnprintf(name, length, "%s", meter->name);
+ }
+ }
+}
+
+const MeterClass DynamicMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = DynamicMeter_display
+ },
+ .init = DynamicMeter_init,
+ .updateValues = DynamicMeter_updateValues,
+ .getCaption = DynamicMeter_getCaption,
+ .getUiName = DynamicMeter_getUiName,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 0,
+ .total = 100.0,
+ .attributes = DynamicMeter_attributes,
+ .name = "Dynamic",
+ .uiName = "Dynamic",
+ .caption = "",
+};
diff --git a/DynamicMeter.h b/DynamicMeter.h
new file mode 100644
index 0000000..3ef0176
--- /dev/null
+++ b/DynamicMeter.h
@@ -0,0 +1,28 @@
+#ifndef HEADER_DynamicMeter
+#define HEADER_DynamicMeter
+
+#include <stdbool.h>
+
+#include "Hashtable.h"
+#include "Meter.h"
+
+
+typedef struct DynamicMeter_ {
+ char name[32]; /* unique name, cannot contain spaces */
+ char* caption;
+ char* description;
+ unsigned int type;
+ double maximum;
+} DynamicMeter;
+
+Hashtable* DynamicMeters_new(void);
+
+void DynamicMeters_delete(Hashtable* dynamics);
+
+const char* DynamicMeter_lookup(Hashtable* dynamics, unsigned int key);
+
+bool DynamicMeter_search(Hashtable* dynamics, const char* name, unsigned int* key);
+
+extern const MeterClass DynamicMeter_class;
+
+#endif
diff --git a/EnvScreen.c b/EnvScreen.c
index acd1f33..0fcee83 100644
--- a/EnvScreen.c
+++ b/EnvScreen.c
@@ -5,7 +5,6 @@
#include <stdlib.h>
#include <string.h>
-#include "CRT.h"
#include "Macros.h"
#include "Panel.h"
#include "Platform.h"
@@ -34,11 +33,9 @@ static void EnvScreen_scan(InfoScreen* this) {
Panel_prune(panel);
- CRT_dropPrivileges();
char* env = Platform_getProcessEnv(this->process->pid);
- CRT_restorePrivileges();
if (env) {
- for (char* p = env; *p; p = strrchr(p, 0) + 1)
+ for (const char* p = env; *p; p = strrchr(p, 0) + 1)
InfoScreen_addLine(this, p);
free(env);
}
diff --git a/EnvScreen.h b/EnvScreen.h
index 66d263f..4d44c81 100644
--- a/EnvScreen.h
+++ b/EnvScreen.h
@@ -5,6 +5,7 @@
#include "Object.h"
#include "Process.h"
+
typedef struct EnvScreen_ {
InfoScreen super;
} EnvScreen;
diff --git a/FunctionBar.h b/FunctionBar.h
index 925e323..1f52658 100644
--- a/FunctionBar.h
+++ b/FunctionBar.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include <stdbool.h>
+
typedef struct FunctionBar_ {
int size;
char** functions;
diff --git a/Hashtable.c b/Hashtable.c
index 9b0882a..d040afa 100644
--- a/Hashtable.c
+++ b/Hashtable.c
@@ -11,7 +11,6 @@ in the source distribution for its full text.
#include <assert.h>
#include <stdint.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -19,6 +18,10 @@ in the source distribution for its full text.
#include "Macros.h"
#include "XUtils.h"
+#ifndef NDEBUG
+#include <stdio.h>
+#endif
+
typedef struct HashtableItem_ {
ht_key_t key;
@@ -95,7 +98,7 @@ static const uint64_t OEISprimes[] = {
34359738337, 68719476731, 137438953447
};
-static uint64_t nextPrime(size_t n) {
+static size_t nextPrime(size_t n) {
/* on 32-bit make sure we do not return primes not fitting in size_t */
for (size_t i = 0; i < ARRAYSIZE(OEISprimes) && OEISprimes[i] < SIZE_MAX; i++) {
if (n <= OEISprimes[i])
diff --git a/Header.c b/Header.c
index 24c4077..26cd923 100644
--- a/Header.c
+++ b/Header.c
@@ -7,12 +7,17 @@ in the source distribution for its full text.
#include "Header.h"
+#include <assert.h>
+#include <math.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CRT.h"
+#include "CPUMeter.h"
+#include "DynamicMeter.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
@@ -20,15 +25,17 @@ in the source distribution for its full text.
#include "XUtils.h"
-Header* Header_new(struct ProcessList_* pl, Settings* settings, int nrColumns) {
+Header* Header_new(ProcessList* pl, Settings* settings, HeaderLayout hLayout) {
Header* this = xCalloc(1, sizeof(Header));
- this->columns = xCalloc(nrColumns, sizeof(Vector*));
+ this->columns = xMallocArray(HeaderLayout_getColumns(hLayout), sizeof(Vector*));
this->settings = settings;
this->pl = pl;
- this->nrColumns = nrColumns;
+ this->headerLayout = hLayout;
+
Header_forEachColumn(this, i) {
this->columns[i] = Vector_new(Class(Meter), true, DEFAULT_SIZE);
}
+
return this;
}
@@ -36,42 +43,120 @@ void Header_delete(Header* this) {
Header_forEachColumn(this, i) {
Vector_delete(this->columns[i]);
}
+
free(this->columns);
free(this);
}
+void Header_setLayout(Header* this, HeaderLayout hLayout) {
+ size_t oldColumns = HeaderLayout_getColumns(this->headerLayout);
+ size_t newColumns = HeaderLayout_getColumns(hLayout);
+
+ this->headerLayout = hLayout;
+
+ if (newColumns == oldColumns)
+ return;
+
+ if (newColumns > oldColumns) {
+ this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*));
+ for (size_t i = oldColumns; i < newColumns; i++)
+ this->columns[i] = Vector_new(Class(Meter), true, DEFAULT_SIZE);
+ } else {
+ // move meters from to-be-deleted columns into last one
+ for (size_t i = newColumns; i < oldColumns; i++) {
+ for (int j = this->columns[i]->items - 1; j >= 0; j--) {
+ Vector_add(this->columns[newColumns - 1], Vector_take(this->columns[i], j));
+ }
+ Vector_delete(this->columns[i]);
+ }
+ this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*));
+ }
+
+ Header_calculateHeight(this);
+}
+
+static void Header_addMeterByName(Header* this, const char* name, MeterModeId mode, unsigned int column) {
+ assert(column < HeaderLayout_getColumns(this->headerLayout));
+
+ Vector* meters = this->columns[column];
+
+ const char* paren = strchr(name, '(');
+ unsigned int param = 0;
+ size_t nameLen;
+ if (paren) {
+ int ok = sscanf(paren, "(%10u)", &param); // CPUMeter
+ if (!ok) {
+ char dynamic[32] = {0};
+ if (sscanf(paren, "(%30s)", dynamic)) { // DynamicMeter
+ char* end;
+ if ((end = strrchr(dynamic, ')')) == NULL)
+ return; // htoprc parse failure
+ *end = '\0';
+ if (!DynamicMeter_search(this->pl->dynamicMeters, dynamic, &param))
+ return; // name lookup failure
+ } else {
+ param = 0;
+ }
+ }
+ nameLen = paren - name;
+ } else {
+ nameLen = strlen(name);
+ }
+
+ for (const MeterClass* const* type = Platform_meterTypes; *type; type++) {
+ if (0 == strncmp(name, (*type)->name, nameLen) && (*type)->name[nameLen] == '\0') {
+ Meter* meter = Meter_new(this->pl, param, *type);
+ if (mode != 0) {
+ Meter_setMode(meter, mode);
+ }
+ Vector_add(meters, meter);
+ break;
+ }
+ }
+}
+
void Header_populateFromSettings(Header* this) {
+ Header_setLayout(this, this->settings->hLayout);
+
Header_forEachColumn(this, col) {
- MeterColumnSettings* colSettings = &this->settings->columns[col];
- for (int i = 0; i < colSettings->len; i++) {
- Header_addMeterByName(this, colSettings->names[i], col);
- if (colSettings->modes[i] != 0) {
- Header_setMode(this, i, colSettings->modes[i], col);
- }
+ const MeterColumnSetting* colSettings = &this->settings->hColumns[col];
+ Vector_prune(this->columns[col]);
+ for (size_t i = 0; i < colSettings->len; i++) {
+ Header_addMeterByName(this, colSettings->names[i], colSettings->modes[i], col);
}
}
+
Header_calculateHeight(this);
}
void Header_writeBackToSettings(const Header* this) {
+ Settings_setHeaderLayout(this->settings, this->headerLayout);
+
Header_forEachColumn(this, col) {
- MeterColumnSettings* colSettings = &this->settings->columns[col];
+ MeterColumnSetting* colSettings = &this->settings->hColumns[col];
- String_freeArray(colSettings->names);
+ if (colSettings->names) {
+ for (size_t j = 0; j < colSettings->len; j++)
+ free(colSettings->names[j]);
+ free(colSettings->names);
+ }
free(colSettings->modes);
- Vector* vec = this->columns[col];
+ const Vector* vec = this->columns[col];
int len = Vector_size(vec);
- colSettings->names = xCalloc(len + 1, sizeof(char*));
- colSettings->modes = xCalloc(len, sizeof(int));
+ colSettings->names = len ? xCalloc(len, sizeof(char*)) : NULL;
+ colSettings->modes = len ? xCalloc(len, sizeof(int)) : NULL;
colSettings->len = len;
for (int i = 0; i < len; i++) {
- Meter* meter = (Meter*) Vector_get(vec, i);
+ const Meter* meter = (Meter*) Vector_get(vec, i);
char* name;
- if (meter->param) {
- xAsprintf(&name, "%s(%d)", As_Meter(meter)->name, meter->param);
+ if (meter->param && As_Meter(meter) == &DynamicMeter_class) {
+ const char* dynamic = DynamicMeter_lookup(this->pl->dynamicMeters, meter->param);
+ xAsprintf(&name, "%s(%s)", As_Meter(meter)->name, dynamic);
+ } else if (meter->param && As_Meter(meter) == &CPUMeter_class) {
+ xAsprintf(&name, "%s(%u)", As_Meter(meter)->name, meter->param);
} else {
xAsprintf(&name, "%s", As_Meter(meter)->name);
}
@@ -81,44 +166,9 @@ void Header_writeBackToSettings(const Header* this) {
}
}
-MeterModeId Header_addMeterByName(Header* this, char* name, int column) {
- Vector* meters = this->columns[column];
+Meter* Header_addMeterByClass(Header* this, const MeterClass* type, unsigned int param, unsigned int column) {
+ assert(column < HeaderLayout_getColumns(this->headerLayout));
- char* paren = strchr(name, '(');
- int param = 0;
- if (paren) {
- int ok = sscanf(paren, "(%10d)", &param);
- if (!ok)
- param = 0;
- *paren = '\0';
- }
- MeterModeId mode = TEXT_METERMODE;
- for (const MeterClass* const* type = Platform_meterTypes; *type; type++) {
- if (String_eq(name, (*type)->name)) {
- Meter* meter = Meter_new(this->pl, param, *type);
- Vector_add(meters, meter);
- mode = meter->mode;
- break;
- }
- }
-
- if (paren)
- *paren = '(';
-
- return mode;
-}
-
-void Header_setMode(Header* this, int i, MeterModeId mode, int column) {
- Vector* meters = this->columns[column];
-
- if (i >= Vector_size(meters))
- return;
-
- Meter* meter = (Meter*) Vector_get(meters, i);
- Meter_setMode(meter, mode);
-}
-
-Meter* Header_addMeterByClass(Header* this, const MeterClass* type, int param, int column) {
Vector* meters = this->columns[column];
Meter* meter = Meter_new(this->pl, param, type);
@@ -126,18 +176,6 @@ Meter* Header_addMeterByClass(Header* this, const MeterClass* type, int param, i
return meter;
}
-int Header_size(Header* this, int column) {
- Vector* meters = this->columns[column];
- return Vector_size(meters);
-}
-
-MeterModeId Header_readMeterMode(Header* this, int i, int column) {
- Vector* meters = this->columns[column];
-
- Meter* meter = (Meter*) Vector_get(meters, i);
- return meter->mode;
-}
-
void Header_reinit(Header* this) {
Header_forEachColumn(this, col) {
for (int i = 0; i < Vector_size(this->columns[col]); i++) {
@@ -150,35 +188,97 @@ void Header_reinit(Header* this) {
}
void Header_draw(const Header* this) {
- int height = this->height;
- int pad = this->pad;
+ const int height = this->height;
+ const int pad = this->pad;
attrset(CRT_colors[RESET_COLOR]);
for (int y = 0; y < height; y++) {
mvhline(y, 0, ' ', COLS);
}
- int width = COLS / this->nrColumns - (pad * this->nrColumns - 1) - 1;
+ const int width = COLS - pad;
int x = pad;
+ float roundingLoss = 0.0F;
Header_forEachColumn(this, col) {
Vector* meters = this->columns[col];
+ float colWidth = (float)width * HeaderLayout_layouts[this->headerLayout].widths[col] / 100.0F;
+
+ roundingLoss += colWidth - floorf(colWidth);
+ if (roundingLoss >= 1.0F) {
+ colWidth += 1.0F;
+ roundingLoss -= 1.0F;
+ }
+
for (int y = (pad / 2), i = 0; i < Vector_size(meters); i++) {
Meter* meter = (Meter*) Vector_get(meters, i);
- meter->draw(meter, x, y, width);
+
+ float actualWidth = colWidth;
+
+ /* Let meters in text mode expand to the right on empty neighbors;
+ except for multi column meters. */
+ if (meter->mode == TEXT_METERMODE && !Meter_isMultiColumn(meter)) {
+ for (int j = 1; j < meter->columnWidthCount; j++) {
+ actualWidth += (float)width * HeaderLayout_layouts[this->headerLayout].widths[col + j] / 100.0F;
+ }
+ }
+
+ assert(meter->draw);
+ meter->draw(meter, x, y, floorf(actualWidth));
y += meter->h;
}
- x += width + pad;
+
+ x += floorf(colWidth);
+ }
+}
+
+void Header_updateData(Header* this) {
+ Header_forEachColumn(this, col) {
+ Vector* meters = this->columns[col];
+ int items = Vector_size(meters);
+ for (int i = 0; i < items; i++) {
+ Meter* meter = (Meter*) Vector_get(meters, i);
+ Meter_updateValues(meter);
+ }
+ }
+}
+
+/*
+ * Calculate how many columns the current meter is allowed to span,
+ * by counting how many columns to the right are empty or contain a BlankMeter.
+ * Returns the number of columns to span, i.e. if the direct neighbor is occupied 1.
+ */
+static int calcColumnWidthCount(const Header* this, const Meter* curMeter, const int pad, const unsigned int curColumn, const int curHeight) {
+ for (size_t i = curColumn + 1; i < HeaderLayout_getColumns(this->headerLayout); i++) {
+ const Vector* meters = this->columns[i];
+
+ int height = pad;
+ for (int j = 0; j < Vector_size(meters); j++) {
+ const Meter* meter = (const Meter*) Vector_get(meters, j);
+
+ if (height >= curHeight + curMeter->h)
+ break;
+
+ height += meter->h;
+ if (height <= curHeight)
+ continue;
+
+ if (!Object_isA((const Object*) meter, (const ObjectClass*) &BlankMeter_class))
+ return i - curColumn;
+ }
}
+
+ return HeaderLayout_getColumns(this->headerLayout) - curColumn;
}
int Header_calculateHeight(Header* this) {
- int pad = this->settings->headerMargin ? 2 : 0;
+ const int pad = this->settings->headerMargin ? 2 : 0;
int maxHeight = pad;
Header_forEachColumn(this, col) {
- Vector* meters = this->columns[col];
+ const Vector* meters = this->columns[col];
int height = pad;
for (int i = 0; i < Vector_size(meters); i++) {
Meter* meter = (Meter*) Vector_get(meters, i);
+ meter->columnWidthCount = calcColumnWidthCount(this, meter, pad, col, height);
height += meter->h;
}
maxHeight = MAXIMUM(maxHeight, height);
diff --git a/Header.h b/Header.h
index 870fbd8..060ad02 100644
--- a/Header.h
+++ b/Header.h
@@ -7,44 +7,42 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include "HeaderLayout.h"
#include "Meter.h"
#include "ProcessList.h"
#include "Settings.h"
#include "Vector.h"
+
typedef struct Header_ {
Vector** columns;
Settings* settings;
ProcessList* pl;
- int nrColumns;
+ HeaderLayout headerLayout;
int pad;
int height;
} Header;
-#define Header_forEachColumn(this_, i_) for (int (i_)=0; (i_) < (this_)->nrColumns; ++(i_))
+#define Header_forEachColumn(this_, i_) for (size_t (i_)=0, H_fEC_numColumns_ = HeaderLayout_getColumns((this_)->headerLayout); (i_) < H_fEC_numColumns_; ++(i_))
-Header* Header_new(ProcessList* pl, Settings* settings, int nrColumns);
+Header* Header_new(ProcessList* pl, Settings* settings, HeaderLayout hLayout);
void Header_delete(Header* this);
+void Header_setLayout(Header* this, HeaderLayout hLayout);
+
void Header_populateFromSettings(Header* this);
void Header_writeBackToSettings(const Header* this);
-MeterModeId Header_addMeterByName(Header* this, char* name, int column);
-
-void Header_setMode(Header* this, int i, MeterModeId mode, int column);
-
-Meter* Header_addMeterByClass(Header* this, const MeterClass* type, int param, int column);
-
-int Header_size(Header* this, int column);
-
-MeterModeId Header_readMeterMode(Header* this, int i, int column);
+Meter* Header_addMeterByClass(Header* this, const MeterClass* type, unsigned int param, unsigned int column);
void Header_reinit(Header* this);
void Header_draw(const Header* this);
+void Header_updateData(Header* this);
+
int Header_calculateHeight(Header* this);
#endif
diff --git a/HeaderLayout.h b/HeaderLayout.h
new file mode 100644
index 0000000..7be236e
--- /dev/null
+++ b/HeaderLayout.h
@@ -0,0 +1,77 @@
+#ifndef HEADER_HeaderLayout
+#define HEADER_HeaderLayout
+/*
+htop - HeaderLayout.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "Macros.h"
+#include "XUtils.h"
+
+
+typedef enum HeaderLayout_ {
+ HF_TWO_50_50,
+ HF_TWO_33_67,
+ HF_TWO_67_33,
+ HF_THREE_33_34_33,
+ HF_THREE_25_25_50,
+ HF_THREE_25_50_25,
+ HF_THREE_50_25_25,
+ HF_THREE_40_20_40,
+ HF_FOUR_25_25_25_25,
+ LAST_HEADER_LAYOUT
+} HeaderLayout;
+
+static const struct {
+ uint8_t columns;
+ const uint8_t widths[4];
+ const char* name;
+ const char* description;
+} HeaderLayout_layouts[LAST_HEADER_LAYOUT] = {
+ [HF_TWO_50_50] = { 2, { 50, 50, 0, 0 }, "two_50_50", "2 columns - 50/50 (default)", },
+ [HF_TWO_33_67] = { 2, { 33, 67, 0, 0 }, "two_33_67", "2 columns - 33/67", },
+ [HF_TWO_67_33] = { 2, { 67, 33, 0, 0 }, "two_67_33", "2 columns - 67/33", },
+ [HF_THREE_33_34_33] = { 3, { 33, 34, 33, 0 }, "three_33_34_33", "3 columns - 33/34/33", },
+ [HF_THREE_25_25_50] = { 3, { 25, 25, 50, 0 }, "three_25_25_50", "3 columns - 25/25/50", },
+ [HF_THREE_25_50_25] = { 3, { 25, 50, 25, 0 }, "three_25_50_25", "3 columns - 25/50/25", },
+ [HF_THREE_50_25_25] = { 3, { 50, 25, 25, 0 }, "three_50_25_25", "3 columns - 50/25/25", },
+ [HF_THREE_40_20_40] = { 3, { 40, 20, 40, 0 }, "three_40_20_40", "3 columns - 40/20/40", },
+ [HF_FOUR_25_25_25_25] = { 4, { 25, 25, 25, 25 }, "four_25_25_25_25", "4 columns - 25/25/25/25", },
+};
+
+static inline size_t HeaderLayout_getColumns(HeaderLayout hLayout) {
+ /* assert the layout is initialized */
+ assert(0 <= hLayout);
+ assert(hLayout < LAST_HEADER_LAYOUT);
+ assert(HeaderLayout_layouts[hLayout].name[0]);
+ assert(HeaderLayout_layouts[hLayout].description[0]);
+ return HeaderLayout_layouts[hLayout].columns;
+}
+
+static inline const char* HeaderLayout_getName(HeaderLayout hLayout) {
+ /* assert the layout is initialized */
+ assert(0 <= hLayout);
+ assert(hLayout < LAST_HEADER_LAYOUT);
+ assert(HeaderLayout_layouts[hLayout].name[0]);
+ assert(HeaderLayout_layouts[hLayout].description[0]);
+ return HeaderLayout_layouts[hLayout].name;
+}
+
+static inline HeaderLayout HeaderLayout_fromName(const char* name) {
+ for (size_t i = 0; i < LAST_HEADER_LAYOUT; i++) {
+ if (String_eq(HeaderLayout_layouts[i].name, name))
+ return (HeaderLayout) i;
+ }
+
+ return LAST_HEADER_LAYOUT;
+}
+
+#endif /* HEADER_HeaderLayout */
diff --git a/HeaderOptionsPanel.c b/HeaderOptionsPanel.c
new file mode 100644
index 0000000..701f6d6
--- /dev/null
+++ b/HeaderOptionsPanel.c
@@ -0,0 +1,87 @@
+/*
+htop - HeaderOptionsPanel.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "HeaderOptionsPanel.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "CRT.h"
+#include "FunctionBar.h"
+#include "Header.h"
+#include "HeaderLayout.h"
+#include "Object.h"
+#include "OptionItem.h"
+#include "ProvideCurses.h"
+
+
+static const char* const HeaderOptionsFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL};
+
+static void HeaderOptionsPanel_delete(Object* object) {
+ Panel* super = (Panel*) object;
+ HeaderOptionsPanel* this = (HeaderOptionsPanel*) object;
+ Panel_done(super);
+ free(this);
+}
+
+static HandlerResult HeaderOptionsPanel_eventHandler(Panel* super, int ch) {
+ HeaderOptionsPanel* this = (HeaderOptionsPanel*) super;
+
+ HandlerResult result = IGNORED;
+ int mark;
+
+ switch(ch) {
+ case 0x0a:
+ case 0x0d:
+ case KEY_ENTER:
+ case KEY_MOUSE:
+ case KEY_RECLICK:
+ case ' ':
+ mark = Panel_getSelectedIndex(super);
+ assert(mark >= 0);
+ assert(mark < LAST_HEADER_LAYOUT);
+
+ for (int i = 0; i < LAST_HEADER_LAYOUT; i++)
+ CheckItem_set((CheckItem*)Panel_get(super, i), false);
+ CheckItem_set((CheckItem*)Panel_get(super, mark), true);
+
+ Header_setLayout(this->scr->header, mark);
+ this->settings->changed = true;
+
+ ScreenManager_resize(this->scr);
+
+ result = HANDLED;
+ }
+
+ return result;
+}
+
+const PanelClass HeaderOptionsPanel_class = {
+ .super = {
+ .extends = Class(Panel),
+ .delete = HeaderOptionsPanel_delete
+ },
+ .eventHandler = HeaderOptionsPanel_eventHandler
+};
+
+HeaderOptionsPanel* HeaderOptionsPanel_new(Settings* settings, ScreenManager* scr) {
+ HeaderOptionsPanel* this = AllocThis(HeaderOptionsPanel);
+ Panel* super = (Panel*) this;
+ FunctionBar* fuBar = FunctionBar_new(HeaderOptionsFunctions, NULL, NULL);
+ Panel_init(super, 1, 1, 1, 1, Class(CheckItem), true, fuBar);
+
+ this->scr = scr;
+ this->settings = settings;
+
+ Panel_setHeader(super, "Header Layout");
+ for (int i = 0; i < LAST_HEADER_LAYOUT; i++) {
+ Panel_add(super, (Object*) CheckItem_newByVal(HeaderLayout_layouts[i].description, false));
+ }
+ CheckItem_set((CheckItem*)Panel_get(super, scr->header->headerLayout), true);
+ return this;
+}
diff --git a/HeaderOptionsPanel.h b/HeaderOptionsPanel.h
new file mode 100644
index 0000000..0d15ace
--- /dev/null
+++ b/HeaderOptionsPanel.h
@@ -0,0 +1,26 @@
+#ifndef HEADER_HeaderOptionsPanel
+#define HEADER_HeaderOptionsPanel
+/*
+htop - ColorsPanel.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Panel.h"
+#include "ScreenManager.h"
+#include "Settings.h"
+
+
+typedef struct HeaderOptionsPanel_ {
+ Panel super;
+
+ ScreenManager* scr;
+ Settings* settings;
+} HeaderOptionsPanel;
+
+extern const PanelClass HeaderOptionsPanel_class;
+
+HeaderOptionsPanel* HeaderOptionsPanel_new(Settings* settings, ScreenManager* scr);
+
+#endif /* HEADER_HeaderOptionsPanel */
diff --git a/HostnameMeter.c b/HostnameMeter.c
index af8e349..b1570c8 100644
--- a/HostnameMeter.c
+++ b/HostnameMeter.c
@@ -9,19 +9,17 @@ in the source distribution for its full text.
#include "HostnameMeter.h"
-#include <unistd.h>
-
#include "CRT.h"
#include "Object.h"
+#include "Platform.h"
static const int HostnameMeter_attributes[] = {
HOSTNAME
};
-static void HostnameMeter_updateValues(Meter* this, char* buffer, size_t size) {
- (void) this;
- gethostname(buffer, size - 1);
+static void HostnameMeter_updateValues(Meter* this) {
+ Platform_getHostname(this->txtBuffer, sizeof(this->txtBuffer));
}
const MeterClass HostnameMeter_class = {
diff --git a/HostnameMeter.h b/HostnameMeter.h
index 77fe3da..85d94d8 100644
--- a/HostnameMeter.h
+++ b/HostnameMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass HostnameMeter_class;
#endif
diff --git a/IncSet.c b/IncSet.c
index aba5e75..9097413 100644
--- a/IncSet.c
+++ b/IncSet.c
@@ -29,6 +29,13 @@ void IncSet_reset(IncSet* this, IncType type) {
IncMode_reset(&this->modes[type]);
}
+void IncSet_setFilter(IncSet* this, const char* filter) {
+ IncMode* mode = &this->modes[INC_FILTER];
+ size_t len = String_safeStrncpy(mode->buffer, filter, sizeof(mode->buffer));
+ mode->index = len;
+ this->filtering = true;
+}
+
static const char* const searchFunctions[] = {"Next ", "Prev ", "Cancel ", " Search: ", NULL};
static const char* const searchKeys[] = {"F3", "S-F3", "Esc", " "};
static const int searchEvents[] = {KEY_F(3), KEY_F(15), 27, ERR};
@@ -70,8 +77,8 @@ void IncSet_delete(IncSet* this) {
free(this);
}
-static void updateWeakPanel(IncSet* this, Panel* panel, Vector* lines) {
- Object* selected = Panel_getSelected(panel);
+static void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) {
+ const Object* selected = Panel_getSelected(panel);
Panel_prune(panel);
if (this->filtering) {
int n = 0;
@@ -98,7 +105,7 @@ static void updateWeakPanel(IncSet* this, Panel* panel, Vector* lines) {
}
}
-static bool search(IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue) {
+static bool search(const IncMode* mode, 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)) {
@@ -110,7 +117,7 @@ static bool search(IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelVa
return false;
}
-static bool IncMode_find(IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue, int step) {
+static bool IncMode_find(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue, int step) {
int size = Panel_size(panel);
int here = Panel_getSelectedIndex(panel);
int i = here;
@@ -201,7 +208,7 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
}
const char* IncSet_getListItemValue(Panel* panel, int i) {
- ListItem* l = (ListItem*) Panel_get(panel, i);
+ const ListItem* l = (const ListItem*) Panel_get(panel, i);
return l ? l->value : "";
}
diff --git a/IncSet.h b/IncSet.h
index 2858002..e2062c6 100644
--- a/IncSet.h
+++ b/IncSet.h
@@ -14,6 +14,7 @@ in the source distribution for its full text.
#include "Panel.h"
#include "Vector.h"
+
#define INCMODE_MAX 40
typedef enum {
@@ -40,6 +41,8 @@ static inline const char* IncSet_filter(const IncSet* this) {
return this->filtering ? this->modes[INC_FILTER].buffer : NULL;
}
+void IncSet_setFilter(IncSet* this, const char* filter);
+
typedef const char* (*IncMode_GetPanelValue)(Panel*, int);
void IncSet_reset(IncSet* this, IncType type);
diff --git a/InfoScreen.c b/InfoScreen.c
index 9647b9f..a62b7c0 100644
--- a/InfoScreen.c
+++ b/InfoScreen.c
@@ -4,7 +4,6 @@
#include <stdarg.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#include "CRT.h"
@@ -28,7 +27,7 @@ InfoScreen* InfoScreen_init(InfoScreen* this, const Process* process, FunctionBa
}
this->display = Panel_new(0, 1, COLS, height, Class(ListItem), false, bar);
this->inc = IncSet_new(bar);
- this->lines = Vector_new(this->display->items->type, true, DEFAULT_SIZE);
+ this->lines = Vector_new(Vector_type(this->display->items), true, DEFAULT_SIZE);
Panel_setHeader(this->display, panelHeader);
return this;
}
@@ -95,7 +94,9 @@ void InfoScreen_run(InfoScreen* this) {
if (this->inc->active) {
(void) move(LINES - 1, CRT_cursorX);
}
+#ifdef HAVE_SET_ESCDELAY
set_escdelay(25);
+#endif
int ch = getch();
if (ch == ERR) {
@@ -105,18 +106,29 @@ void InfoScreen_run(InfoScreen* this) {
}
}
+#ifdef HAVE_GETMOUSE
if (ch == KEY_MOUSE) {
MEVENT mevent;
int ok = getmouse(&mevent);
if (ok == OK) {
- if (mevent.y >= panel->y && mevent.y < LINES - 1) {
- Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV);
- ch = 0;
- } else if (mevent.y == LINES - 1) {
- ch = IncSet_synthesizeEvent(this->inc, mevent.x);
+ if (mevent.bstate & BUTTON1_RELEASED) {
+ if (mevent.y >= panel->y && mevent.y < LINES - 1) {
+ Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV - 1);
+ ch = 0;
+ } else if (mevent.y == LINES - 1) {
+ ch = IncSet_synthesizeEvent(this->inc, mevent.x);
+ }
}
+ #if NCURSES_MOUSE_VERSION > 1
+ else if (mevent.bstate & BUTTON4_PRESSED) {
+ ch = KEY_WHEELUP;
+ } else if (mevent.bstate & BUTTON5_PRESSED) {
+ ch = KEY_WHEELDOWN;
+ }
+ #endif
}
}
+#endif
if (this->inc->active) {
IncSet_handleKey(this->inc, ch, panel, IncSet_getListItemValue, this->lines);
@@ -136,8 +148,10 @@ void InfoScreen_run(InfoScreen* this) {
break;
case KEY_F(5):
clear();
- if (As_InfoScreen(this)->scan)
+ if (As_InfoScreen(this)->scan) {
+ Vector_prune(this->lines);
InfoScreen_scan(this);
+ }
InfoScreen_draw(this);
break;
@@ -152,8 +166,10 @@ void InfoScreen_run(InfoScreen* this) {
break;
case KEY_RESIZE:
Panel_resize(panel, COLS, LINES - 2);
- if (As_InfoScreen(this)->scan)
+ if (As_InfoScreen(this)->scan) {
+ Vector_prune(this->lines);
InfoScreen_scan(this);
+ }
InfoScreen_draw(this);
break;
diff --git a/ListItem.c b/ListItem.c
index 7ccf8d7..e5464d2 100644
--- a/ListItem.c
+++ b/ListItem.c
@@ -31,11 +31,9 @@ static void ListItem_display(const Object* cast, RichString* out) {
if (this->moving) {
RichString_writeWide(out, CRT_colors[DEFAULT_COLOR],
#ifdef HAVE_LIBNCURSESW
- CRT_utf8 ? "↕ " :
+ CRT_utf8 ? "↕ " :
#endif
- "+ ");
- } else {
- RichString_prune(out);
+ "+ ");
}
RichString_appendWide(out, CRT_colors[DEFAULT_COLOR], this->value);
}
diff --git a/ListItem.h b/ListItem.h
index 87a7c07..0bbca2f 100644
--- a/ListItem.h
+++ b/ListItem.h
@@ -11,6 +11,7 @@ in the source distribution for its full text.
#include "Object.h"
+
typedef struct ListItem_ {
Object super;
char* value;
diff --git a/LoadAverageMeter.c b/LoadAverageMeter.c
index 0c7b833..bb3b05e 100644
--- a/LoadAverageMeter.c
+++ b/LoadAverageMeter.c
@@ -10,6 +10,7 @@ in the source distribution for its full text.
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
+#include "ProcessList.h"
#include "RichString.h"
#include "XUtils.h"
@@ -36,7 +37,7 @@ static const int High_attributes[] = {
METER_VALUE_ERROR
};
-static void LoadAverageMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void LoadAverageMeter_updateValues(Meter* this) {
Platform_getLoadAverage(&this->values[0], &this->values[1], &this->values[2]);
// only show bar for 1min value
@@ -46,29 +47,31 @@ static void LoadAverageMeter_updateValues(Meter* this, char* buffer, size_t size
if (this->values[0] < 1.0) {
this->curAttributes = OK_attributes;
this->total = 1.0;
- } else if (this->values[0] < this->pl->cpuCount) {
+ } else if (this->values[0] < this->pl->activeCPUs) {
this->curAttributes = Medium_attributes;
- this->total = this->pl->cpuCount;
+ this->total = this->pl->activeCPUs;
} else {
this->curAttributes = High_attributes;
- this->total = 2 * this->pl->cpuCount;
+ this->total = 2 * this->pl->activeCPUs;
}
- xSnprintf(buffer, size, "%.2f/%.2f/%.2f", this->values[0], this->values[1], this->values[2]);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.2f/%.2f/%.2f", this->values[0], this->values[1], this->values[2]);
}
static void LoadAverageMeter_display(const Object* cast, RichString* out) {
const Meter* this = (const Meter*)cast;
char buffer[20];
- xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[0]);
- RichString_writeAscii(out, CRT_colors[LOAD_AVERAGE_ONE], buffer);
- xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[1]);
- RichString_appendAscii(out, CRT_colors[LOAD_AVERAGE_FIVE], buffer);
- xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[2]);
- RichString_appendAscii(out, CRT_colors[LOAD_AVERAGE_FIFTEEN], buffer);
+ int len;
+
+ len = xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[0]);
+ RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_ONE], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[1]);
+ RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_FIVE], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[2]);
+ RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_FIFTEEN], buffer, len);
}
-static void LoadMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void LoadMeter_updateValues(Meter* this) {
double five, fifteen;
Platform_getLoadAverage(&this->values[0], &five, &fifteen);
@@ -76,22 +79,24 @@ static void LoadMeter_updateValues(Meter* this, char* buffer, size_t size) {
if (this->values[0] < 1.0) {
this->curAttributes = OK_attributes;
this->total = 1.0;
- } else if (this->values[0] < this->pl->cpuCount) {
+ } else if (this->values[0] < this->pl->activeCPUs) {
this->curAttributes = Medium_attributes;
- this->total = this->pl->cpuCount;
+ this->total = this->pl->activeCPUs;
} else {
this->curAttributes = High_attributes;
- this->total = 2 * this->pl->cpuCount;
+ this->total = 2 * this->pl->activeCPUs;
}
- xSnprintf(buffer, size, "%.2f", this->values[0]);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.2f", this->values[0]);
}
static void LoadMeter_display(const Object* cast, RichString* out) {
const Meter* this = (const Meter*)cast;
char buffer[20];
- xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[0]);
- RichString_writeAscii(out, CRT_colors[LOAD], buffer);
+ int len;
+
+ len = xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[0]);
+ RichString_appendnAscii(out, CRT_colors[LOAD], buffer, len);
}
const MeterClass LoadAverageMeter_class = {
diff --git a/LoadAverageMeter.h b/LoadAverageMeter.h
index 776c8bf..d575ad5 100644
--- a/LoadAverageMeter.h
+++ b/LoadAverageMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass LoadAverageMeter_class;
extern const MeterClass LoadMeter_class;
diff --git a/Macros.h b/Macros.h
index 64aaefa..5e8891a 100644
--- a/Macros.h
+++ b/Macros.h
@@ -4,27 +4,31 @@
#include <assert.h> // IWYU pragma: keep
#ifndef MINIMUM
-#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
+#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAXIMUM
-#define MAXIMUM(a, b) ((a) > (b) ? (a) : (b))
+#define MAXIMUM(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef CLAMP
-#define CLAMP(x, low, high) (assert((low) <= (high)), ((x) > (high)) ? (high) : MAXIMUM(x, low))
+#define CLAMP(x, low, high) (assert((low) <= (high)), ((x) > (high)) ? (high) : MAXIMUM(x, low))
#endif
#ifndef ARRAYSIZE
-#define ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
#ifndef SPACESHIP_NUMBER
-#define SPACESHIP_NUMBER(a, b) (((a) > (b)) - ((a) < (b)))
+#define SPACESHIP_NUMBER(a, b) (((a) > (b)) - ((a) < (b)))
#endif
#ifndef SPACESHIP_NULLSTR
-#define SPACESHIP_NULLSTR(a, b) strcmp((a) ? (a) : "", (b) ? (b) : "")
+#define SPACESHIP_NULLSTR(a, b) strcmp((a) ? (a) : "", (b) ? (b) : "")
+#endif
+
+#ifndef SPACESHIP_DEFAULTSTR
+#define SPACESHIP_DEFAULTSTR(a, b, s) strcmp((a) ? (a) : (s), (b) ? (b) : (s))
#endif
#ifdef __GNUC__ // defined by GCC and Clang
@@ -33,6 +37,7 @@
#define ATTR_NONNULL __attribute__((nonnull))
#define ATTR_NORETURN __attribute__((noreturn))
#define ATTR_UNUSED __attribute__((unused))
+#define ATTR_MALLOC __attribute__((malloc))
#else /* __GNUC__ */
@@ -40,13 +45,26 @@
#define ATTR_NONNULL
#define ATTR_NORETURN
#define ATTR_UNUSED
+#define ATTR_MALLOC
#endif /* __GNUC__ */
+#ifdef HAVE_ATTR_ALLOC_SIZE
+
+#define ATTR_ALLOC_SIZE1(a) __attribute__((alloc_size (a)))
+#define ATTR_ALLOC_SIZE2(a, b) __attribute__((alloc_size (a, b)))
+
+#else
+
+#define ATTR_ALLOC_SIZE1(a)
+#define ATTR_ALLOC_SIZE2(a, b)
+
+#endif /* HAVE_ATTR_ALLOC_SIZE */
+
// ignore casts discarding const specifier, e.g.
// const char [] -> char * / void *
// const char *[2]' -> char *const *
-#ifdef __clang__
+#if defined(__clang__)
#define IGNORE_WCASTQUAL_BEGIN _Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wcast-qual\"")
#define IGNORE_WCASTQUAL_END _Pragma("clang diagnostic pop")
@@ -59,4 +77,9 @@
#define IGNORE_WCASTQUAL_END
#endif
+/* This subtraction is used by Linux / NetBSD / OpenBSD for calculation of CPU usage items. */
+static inline unsigned long long saturatingSub(unsigned long long a, unsigned long long b) {
+ return a > b ? a - b : 0;
+}
+
#endif
diff --git a/MainPanel.c b/MainPanel.c
index 859c513..e7033bf 100644
--- a/MainPanel.c
+++ b/MainPanel.c
@@ -21,18 +21,19 @@ in the source distribution for its full text.
#include "XUtils.h"
-static const char* const MainFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL};
+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) {
FunctionBar* bar = MainPanel_getFunctionBar(this);
FunctionBar_setLabel(bar, KEY_F(5), mode ? "List " : "Tree ");
}
-void MainPanel_pidSearch(MainPanel* this, int ch) {
+static void MainPanel_pidSearch(MainPanel* this, int ch) {
Panel* super = (Panel*) this;
pid_t pid = ch - 48 + this->pidSearch;
for (int i = 0; i < Panel_size(super); i++) {
- Process* p = (Process*) Panel_get(super, i);
+ const Process* p = (const Process*) Panel_get(super, i);
if (p && p->pid == pid) {
Panel_setSelected(super, i);
break;
@@ -60,8 +61,8 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
if (ch == KEY_RESIZE)
return IGNORED;
- /* reset on every normal key */
- if (ch != ERR)
+ /* reset on every normal key, except mouse events while mouse support is disabled */
+ if (ch != ERR && (ch != KEY_MOUSE || this->state->settings->enableMouse))
this->state->hideProcessSelection = false;
if (EVENT_IS_HEADER_CLICK(ch)) {
@@ -111,6 +112,9 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
if (reaction & HTOP_REDRAW_BAR) {
MainPanel_updateTreeFunctions(this, this->state->settings->treeView);
}
+ if (reaction & HTOP_RESIZE) {
+ result |= RESIZE;
+ }
if (reaction & HTOP_UPDATE_PANELHDR) {
result |= REDRAW;
}
@@ -134,7 +138,7 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
}
int MainPanel_selectedPid(MainPanel* this) {
- Process* p = (Process*) Panel_getSelected((Panel*)this);
+ const Process* p = (const Process*) Panel_getSelected((Panel*)this);
if (p) {
return p->pid;
}
@@ -195,7 +199,7 @@ const PanelClass MainPanel_class = {
MainPanel* MainPanel_new() {
MainPanel* this = AllocThis(MainPanel);
- Panel_init((Panel*) this, 1, 1, 1, 1, Class(Process), false, FunctionBar_new(MainFunctions, NULL, NULL));
+ Panel_init((Panel*) this, 1, 1, 1, 1, Class(Process), false, FunctionBar_new(Settings_isReadonly() ? MainFunctions_ro : MainFunctions, NULL, NULL));
this->keys = xCalloc(KEY_MAX, sizeof(Htop_Action));
this->inc = IncSet_new(MainPanel_getFunctionBar(this));
diff --git a/MainPanel.h b/MainPanel.h
index 2427ca3..04f347d 100644
--- a/MainPanel.h
+++ b/MainPanel.h
@@ -34,8 +34,6 @@ typedef bool(*MainPanel_ForeachProcessFn)(Process*, Arg);
void MainPanel_updateTreeFunctions(MainPanel* this, bool mode);
-void MainPanel_pidSearch(MainPanel* this, int ch);
-
int MainPanel_selectedPid(MainPanel* this);
bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged);
diff --git a/Makefile.am b/Makefile.am
index fe70eef..81b9c93 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,10 +1,14 @@
-AUTOMAKE_OPTIONS = subdir-objects
-
-bin_PROGRAMS = htop
-
dist_man_MANS = htop.1
-EXTRA_DIST = $(dist_man_MANS) htop.desktop htop.png htop.svg \
-install-sh autogen.sh missing
+EXTRA_DIST = \
+ $(dist_man_MANS) \
+ autogen.sh \
+ htop.desktop \
+ htop.png \
+ htop.svg \
+ build-aux/compile \
+ build-aux/depcomp \
+ build-aux/install-sh \
+ build-aux/missing
applicationsdir = $(datadir)/applications
applications_DATA = htop.desktop
pixmapdir = $(datadir)/pixmaps
@@ -12,7 +16,7 @@ pixmap_DATA = htop.png
appicondir = $(datadir)/icons/hicolor/scalable/apps
appicon_DATA = htop.svg
-AM_CFLAGS += -pedantic -std=c99 -D_XOPEN_SOURCE_EXTENDED -DSYSCONFDIR=\"$(sysconfdir)\" -I"$(top_srcdir)/$(my_htop_platform)"
+AM_CFLAGS += -pedantic -std=c99 -D_XOPEN_SOURCE_EXTENDED -DSYSCONFDIR="\"$(sysconfdir)\"" -I"$(top_srcdir)/$(my_htop_platform)"
AM_LDFLAGS =
myhtopsources = \
@@ -26,6 +30,7 @@ myhtopsources = \
ClockMeter.c \
ColorsPanel.c \
ColumnsPanel.c \
+ CommandLine.c \
CommandScreen.c \
Compat.c \
CPUMeter.c \
@@ -34,18 +39,21 @@ myhtopsources = \
DateTimeMeter.c \
DiskIOMeter.c \
DisplayOptionsPanel.c \
+ DynamicColumn.c \
+ DynamicMeter.c \
EnvScreen.c \
FunctionBar.c \
Hashtable.c \
Header.c \
+ HeaderOptionsPanel.c \
HostnameMeter.c \
- htop.c \
IncSet.c \
InfoScreen.c \
ListItem.c \
LoadAverageMeter.c \
MainPanel.c \
MemoryMeter.c \
+ MemorySwapMeter.c \
Meter.c \
MetersPanel.c \
NetworkIOMeter.c \
@@ -61,6 +69,7 @@ myhtopsources = \
Settings.c \
SignalsPanel.c \
SwapMeter.c \
+ SysArchMeter.c \
TasksMeter.c \
TraceScreen.c \
UptimeMeter.c \
@@ -81,16 +90,21 @@ myhtopheaders = \
ClockMeter.h \
ColorsPanel.h \
ColumnsPanel.h \
+ CommandLine.h \
CommandScreen.h \
Compat.h \
DateMeter.h \
DateTimeMeter.h \
DiskIOMeter.h \
DisplayOptionsPanel.h \
+ DynamicColumn.h \
+ DynamicMeter.h \
EnvScreen.h \
FunctionBar.h \
Hashtable.h \
Header.h \
+ HeaderLayout.h \
+ HeaderOptionsPanel.h \
HostnameMeter.h \
IncSet.h \
InfoScreen.h \
@@ -99,6 +113,7 @@ myhtopheaders = \
Macros.h \
MainPanel.h \
MemoryMeter.h \
+ MemorySwapMeter.h \
Meter.h \
MetersPanel.h \
NetworkIOMeter.h \
@@ -115,6 +130,7 @@ myhtopheaders = \
Settings.h \
SignalsPanel.h \
SwapMeter.h \
+ SysArchMeter.h \
TasksMeter.h \
TraceScreen.h \
UptimeMeter.h \
@@ -126,6 +142,10 @@ myhtopheaders = \
# -----
linux_platform_headers = \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h \
+ linux/HugePageMeter.h \
linux/IOPriority.h \
linux/IOPriorityPanel.h \
linux/LibSensors.h \
@@ -142,9 +162,11 @@ linux_platform_headers = \
zfs/ZfsArcStats.h \
zfs/ZfsCompressedArcMeter.h
-if HTOP_LINUX
-AM_LDFLAGS += -rdynamic
-myhtopplatsources = \
+linux_platform_sources = \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c \
+ linux/HugePageMeter.c \
linux/IOPriorityPanel.c \
linux/LibSensors.c \
linux/LinuxProcess.c \
@@ -155,10 +177,13 @@ myhtopplatsources = \
linux/SystemdMeter.c \
linux/ZramMeter.c \
zfs/ZfsArcMeter.c \
- zfs/ZfsArcStats.c \
zfs/ZfsCompressedArcMeter.c
+if HTOP_LINUX
+AM_LDFLAGS += -rdynamic
+myhtopplatprogram = htop
myhtopplatheaders = $(linux_platform_headers)
+myhtopplatsources = $(linux_platform_sources)
endif
# FreeBSD
@@ -169,17 +194,29 @@ freebsd_platform_headers = \
freebsd/FreeBSDProcess.h \
freebsd/Platform.h \
freebsd/ProcessField.h \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/openzfs_sysctl.h \
+ generic/uname.h \
zfs/ZfsArcMeter.h \
- zfs/ZfsCompressedArcMeter.h \
zfs/ZfsArcStats.h \
- zfs/openzfs_sysctl.h
+ zfs/ZfsCompressedArcMeter.h
-if HTOP_FREEBSD
-myhtopplatsources = freebsd/Platform.c freebsd/FreeBSDProcessList.c \
-freebsd/FreeBSDProcess.c \
-zfs/ZfsArcMeter.c zfs/ZfsCompressedArcMeter.c zfs/ZfsArcStats.c zfs/openzfs_sysctl.c
+freebsd_platform_sources = \
+ freebsd/Platform.c \
+ freebsd/FreeBSDProcessList.c \
+ freebsd/FreeBSDProcess.c \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/openzfs_sysctl.c \
+ generic/uname.c \
+ zfs/ZfsArcMeter.c \
+ zfs/ZfsCompressedArcMeter.c
+if HTOP_FREEBSD
+myhtopplatprogram = htop
myhtopplatheaders = $(freebsd_platform_headers)
+myhtopplatsources = $(freebsd_platform_sources)
endif
# DragonFlyBSD
@@ -189,31 +226,75 @@ dragonflybsd_platform_headers = \
dragonflybsd/DragonFlyBSDProcessList.h \
dragonflybsd/DragonFlyBSDProcess.h \
dragonflybsd/Platform.h \
- dragonflybsd/ProcessField.h
+ dragonflybsd/ProcessField.h \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h
-if HTOP_DRAGONFLYBSD
-myhtopplatsources = \
- dragonflybsd/Platform.c \
+dragonflybsd_platform_sources = \
dragonflybsd/DragonFlyBSDProcessList.c \
- dragonflybsd/DragonFlyBSDProcess.c
+ dragonflybsd/DragonFlyBSDProcess.c \
+ dragonflybsd/Platform.c \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c
+if HTOP_DRAGONFLYBSD
+myhtopplatprogram = htop
myhtopplatheaders = $(dragonflybsd_platform_headers)
+myhtopplatsources = $(dragonflybsd_platform_sources)
+endif
+
+# NetBSD
+# -------
+
+netbsd_platform_headers = \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h \
+ netbsd/Platform.h \
+ netbsd/ProcessField.h \
+ netbsd/NetBSDProcess.h \
+ netbsd/NetBSDProcessList.h
+
+netbsd_platform_sources = \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c \
+ netbsd/Platform.c \
+ netbsd/NetBSDProcess.c \
+ netbsd/NetBSDProcessList.c
+
+if HTOP_NETBSD
+myhtopplatprogram = htop
+myhtopplatheaders = $(netbsd_platform_headers)
+myhtopplatsources = $(netbsd_platform_sources)
endif
# OpenBSD
# -------
openbsd_platform_headers = \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h \
openbsd/OpenBSDProcessList.h \
openbsd/OpenBSDProcess.h \
openbsd/Platform.h \
openbsd/ProcessField.h
-if HTOP_OPENBSD
-myhtopplatsources = openbsd/Platform.c openbsd/OpenBSDProcessList.c \
-openbsd/OpenBSDProcess.c
+openbsd_platform_sources = \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c \
+ openbsd/OpenBSDProcessList.c \
+ openbsd/OpenBSDProcess.c \
+ openbsd/Platform.c
+if HTOP_OPENBSD
+myhtopplatprogram = htop
myhtopplatheaders = $(openbsd_platform_headers)
+myhtopplatsources = $(openbsd_platform_sources)
endif
# Darwin
@@ -224,59 +305,126 @@ darwin_platform_headers = \
darwin/DarwinProcessList.h \
darwin/Platform.h \
darwin/ProcessField.h \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/openzfs_sysctl.h \
+ generic/uname.h \
zfs/ZfsArcMeter.h \
- zfs/ZfsCompressedArcMeter.h \
zfs/ZfsArcStats.h \
- zfs/openzfs_sysctl.h
+ zfs/ZfsCompressedArcMeter.h
+
+darwin_platform_sources = \
+ darwin/Platform.c \
+ darwin/DarwinProcess.c \
+ darwin/DarwinProcessList.c \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/openzfs_sysctl.c \
+ generic/uname.c \
+ zfs/ZfsArcMeter.c \
+ zfs/ZfsCompressedArcMeter.c
if HTOP_DARWIN
AM_LDFLAGS += -framework IOKit -framework CoreFoundation
-myhtopplatsources = darwin/Platform.c darwin/DarwinProcess.c \
-darwin/DarwinProcessList.c \
-zfs/ZfsArcMeter.c zfs/ZfsCompressedArcMeter.c zfs/ZfsArcStats.c zfs/openzfs_sysctl.c
-
+myhtopplatprogram = htop
myhtopplatheaders = $(darwin_platform_headers)
+myhtopplatsources = $(darwin_platform_sources)
endif
# Solaris
# -------
solaris_platform_headers = \
- solaris/Platform.h \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h \
solaris/ProcessField.h \
+ solaris/Platform.h \
solaris/SolarisProcess.h \
solaris/SolarisProcessList.h \
zfs/ZfsArcMeter.h \
- zfs/ZfsCompressedArcMeter.h \
- zfs/ZfsArcStats.h
+ zfs/ZfsArcStats.h \
+ zfs/ZfsCompressedArcMeter.h
-if HTOP_SOLARIS
-myhtopplatsources = solaris/Platform.c \
-solaris/SolarisProcess.c solaris/SolarisProcessList.c \
-zfs/ZfsArcMeter.c zfs/ZfsCompressedArcMeter.c zfs/ZfsArcStats.c
+solaris_platform_sources = \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c \
+ solaris/Platform.c \
+ solaris/SolarisProcess.c \
+ solaris/SolarisProcessList.c \
+ zfs/ZfsArcMeter.c \
+ zfs/ZfsCompressedArcMeter.c
+if HTOP_SOLARIS
+myhtopplatprogram = htop
myhtopplatheaders = $(solaris_platform_headers)
+myhtopplatsources = $(solaris_platform_sources)
+endif
+
+# Performance Co-Pilot (PCP)
+# --------------------------
+
+pcp_platform_headers = \
+ linux/PressureStallMeter.h \
+ linux/ZramMeter.h \
+ linux/ZramStats.h \
+ pcp/PCPDynamicColumn.h \
+ pcp/PCPDynamicMeter.h \
+ pcp/PCPMetric.h \
+ pcp/PCPProcess.h \
+ pcp/PCPProcessList.h \
+ pcp/Platform.h \
+ pcp/ProcessField.h \
+ zfs/ZfsArcMeter.h \
+ zfs/ZfsArcStats.h \
+ zfs/ZfsCompressedArcMeter.h
+
+pcp_platform_sources = \
+ linux/PressureStallMeter.c \
+ linux/ZramMeter.c \
+ pcp/PCPDynamicColumn.c \
+ pcp/PCPDynamicMeter.c \
+ pcp/PCPMetric.c \
+ pcp/PCPProcess.c \
+ pcp/PCPProcessList.c \
+ pcp/Platform.c \
+ zfs/ZfsArcMeter.c \
+ zfs/ZfsCompressedArcMeter.c
+
+if HTOP_PCP
+myhtopplatprogram = pcp-htop
+myhtopplatheaders = $(pcp_platform_headers)
+myhtopplatsources = $(pcp_platform_sources)
+pcp_htop_SOURCES = $(myhtopplatprogram).c $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
endif
# Unsupported
# -----------
unsupported_platform_headers = \
+ generic/gettime.h \
unsupported/Platform.h \
unsupported/ProcessField.h \
unsupported/UnsupportedProcess.h \
unsupported/UnsupportedProcessList.h
-if HTOP_UNSUPPORTED
-myhtopplatsources = unsupported/Platform.c \
-unsupported/UnsupportedProcess.c unsupported/UnsupportedProcessList.c
+unsupported_platform_sources = \
+ generic/gettime.c \
+ unsupported/Platform.c \
+ unsupported/UnsupportedProcess.c \
+ unsupported/UnsupportedProcessList.c
+if HTOP_UNSUPPORTED
+myhtopplatprogram = htop
+myhtopplatsources = $(unsupported_platform_sources)
myhtopplatheaders = $(unsupported_platform_headers)
endif
# ----
-htop_SOURCES = $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
+bin_PROGRAMS = $(myhtopplatprogram)
+htop_SOURCES = $(myhtopplatprogram).c $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
nodist_htop_SOURCES = config.h
target:
@@ -286,10 +434,10 @@ profile:
$(MAKE) all AM_CPPFLAGS="-pg -O2 -DNDEBUG"
debug:
- $(MAKE) all AM_CPPFLAGS="-ggdb -DDEBUG"
+ $(MAKE) all AM_CPPFLAGS="-ggdb3 -Og" CFLAGS="`printf ' %s ' "$(CFLAGS)"|sed -E 's#[[:space:]]-O[^[:space:]]+[[:space:]]# #g'` -ggdb3 -Og"
coverage:
- $(MAKE) all AM_CPPFLAGS="-fprofile-arcs -ftest-coverage -DDEBUG" AM_LDFLAGS="-lgcov"
+ $(MAKE) all AM_CPPFLAGS="-fprofile-arcs -ftest-coverage" AM_LDFLAGS="-lgcov"
cppcheck:
cppcheck -q -v . --enable=all -DHAVE_OPENVZ
diff --git a/MemoryMeter.c b/MemoryMeter.c
index e475442..75ad7e8 100644
--- a/MemoryMeter.c
+++ b/MemoryMeter.c
@@ -7,6 +7,9 @@ in the source distribution for its full text.
#include "MemoryMeter.h"
+#include <math.h>
+#include <stddef.h>
+
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
@@ -16,14 +19,24 @@ in the source distribution for its full text.
static const int MemoryMeter_attributes[] = {
MEMORY_USED,
MEMORY_BUFFERS,
+ MEMORY_SHARED,
MEMORY_CACHE
};
-static void MemoryMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void MemoryMeter_updateValues(Meter* this) {
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
int written;
+
+ /* shared and available memory are not supported on all platforms */
+ this->values[2] = NAN;
+ this->values[4] = NAN;
Platform_setMemoryValues(this);
- written = Meter_humanUnit(buffer, this->values[0], size);
+ /* Do not print available memory in bar mode */
+ this->curItems = 4;
+
+ written = Meter_humanUnit(buffer, isnan(this->values[4]) ? this->values[0] : this->total - this->values[4], size);
METER_BUFFER_CHECK(buffer, size, written);
METER_BUFFER_APPEND_CHR(buffer, size, '/');
@@ -34,18 +47,36 @@ static void MemoryMeter_updateValues(Meter* this, char* buffer, size_t size) {
static void MemoryMeter_display(const Object* cast, RichString* out) {
char buffer[50];
const Meter* this = (const Meter*)cast;
+
RichString_writeAscii(out, CRT_colors[METER_TEXT], ":");
Meter_humanUnit(buffer, this->total, sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+
Meter_humanUnit(buffer, this->values[0], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:");
RichString_appendAscii(out, CRT_colors[MEMORY_USED], buffer);
+
Meter_humanUnit(buffer, this->values[1], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " buffers:");
RichString_appendAscii(out, CRT_colors[MEMORY_BUFFERS_TEXT], buffer);
- Meter_humanUnit(buffer, this->values[2], sizeof(buffer));
+
+ /* shared memory is not supported on all platforms */
+ if (!isnan(this->values[2])) {
+ Meter_humanUnit(buffer, this->values[2], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " shared:");
+ RichString_appendAscii(out, CRT_colors[MEMORY_SHARED], buffer);
+ }
+
+ Meter_humanUnit(buffer, this->values[3], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " cache:");
RichString_appendAscii(out, CRT_colors[MEMORY_CACHE], buffer);
+
+ /* available memory is not supported on all platforms */
+ if (!isnan(this->values[4])) {
+ Meter_humanUnit(buffer, this->values[4], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " available:");
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+ }
}
const MeterClass MemoryMeter_class = {
@@ -56,7 +87,7 @@ const MeterClass MemoryMeter_class = {
},
.updateValues = MemoryMeter_updateValues,
.defaultMode = BAR_METERMODE,
- .maxItems = 3,
+ .maxItems = 5,
.total = 100.0,
.attributes = MemoryMeter_attributes,
.name = "Memory",
diff --git a/MemoryMeter.h b/MemoryMeter.h
index d299483..6d7dd82 100644
--- a/MemoryMeter.h
+++ b/MemoryMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass MemoryMeter_class;
#endif
diff --git a/MemorySwapMeter.c b/MemorySwapMeter.c
new file mode 100644
index 0000000..612549d
--- /dev/null
+++ b/MemorySwapMeter.c
@@ -0,0 +1,107 @@
+/*
+htop - MemorySwapMeter.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "MemorySwapMeter.h"
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "Macros.h"
+#include "MemoryMeter.h"
+#include "Object.h"
+#include "SwapMeter.h"
+#include "XUtils.h"
+
+
+typedef struct MemorySwapMeterData_ {
+ Meter* memoryMeter;
+ Meter* swapMeter;
+} MemorySwapMeterData;
+
+static void MemorySwapMeter_updateValues(Meter* this) {
+ MemorySwapMeterData* data = this->meterData;
+
+ Meter_updateValues(data->memoryMeter);
+ Meter_updateValues(data->swapMeter);
+}
+
+static void MemorySwapMeter_draw(Meter* this, int x, int y, int w) {
+ MemorySwapMeterData* data = this->meterData;
+
+ /* Use the same width for each sub meter to align with CPU meter */
+ const int colwidth = w / 2;
+ const int diff = w - colwidth * 2;
+
+ assert(data->memoryMeter->draw);
+ data->memoryMeter->draw(data->memoryMeter, x, y, colwidth);
+ assert(data->swapMeter->draw);
+ data->swapMeter->draw(data->swapMeter, x + colwidth + diff, y, colwidth);
+}
+
+static void MemorySwapMeter_init(Meter* this) {
+ MemorySwapMeterData* data = this->meterData;
+
+ if (!data) {
+ data = this->meterData = xMalloc(sizeof(MemorySwapMeterData));
+ data->memoryMeter = NULL;
+ data->swapMeter = NULL;
+ }
+
+ if (!data->memoryMeter)
+ data->memoryMeter = Meter_new(this->pl, 0, (const MeterClass*) Class(MemoryMeter));
+ if (!data->swapMeter)
+ data->swapMeter = Meter_new(this->pl, 0, (const MeterClass*) Class(SwapMeter));
+
+ if (Meter_initFn(data->memoryMeter))
+ Meter_init(data->memoryMeter);
+ if (Meter_initFn(data->swapMeter))
+ Meter_init(data->swapMeter);
+
+ if (this->mode == 0)
+ this->mode = BAR_METERMODE;
+
+ this->h = MAXIMUM(Meter_modes[data->memoryMeter->mode]->h, Meter_modes[data->swapMeter->mode]->h);
+}
+
+static void MemorySwapMeter_updateMode(Meter* this, int mode) {
+ MemorySwapMeterData* data = this->meterData;
+
+ this->mode = mode;
+
+ Meter_setMode(data->memoryMeter, mode);
+ Meter_setMode(data->swapMeter, mode);
+
+ this->h = MAXIMUM(Meter_modes[data->memoryMeter->mode]->h, Meter_modes[data->swapMeter->mode]->h);
+}
+
+static void MemorySwapMeter_done(Meter* this) {
+ MemorySwapMeterData* data = this->meterData;
+
+ Meter_delete((Object*)data->swapMeter);
+ Meter_delete((Object*)data->memoryMeter);
+
+ free(data);
+}
+
+const MeterClass MemorySwapMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ },
+ .updateValues = MemorySwapMeter_updateValues,
+ .defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
+ .name = "MemorySwap",
+ .uiName = "Memory & Swap",
+ .description = "Combined memory and swap usage",
+ .caption = "M&S",
+ .draw = MemorySwapMeter_draw,
+ .init = MemorySwapMeter_init,
+ .updateMode = MemorySwapMeter_updateMode,
+ .done = MemorySwapMeter_done
+};
diff --git a/MemorySwapMeter.h b/MemorySwapMeter.h
new file mode 100644
index 0000000..b4180a1
--- /dev/null
+++ b/MemorySwapMeter.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_MemorySwapMeter
+#define HEADER_MemorySwapMeter
+/*
+htop - MemorySwapMeter.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Meter.h"
+
+
+extern const MeterClass MemorySwapMeter_class;
+
+#endif
diff --git a/Meter.c b/Meter.c
index 2fe949e..73e99ce 100644
--- a/Meter.c
+++ b/Meter.c
@@ -32,7 +32,7 @@ const MeterClass Meter_class = {
}
};
-Meter* Meter_new(const struct ProcessList_* pl, int param, const MeterClass* type) {
+Meter* Meter_new(const struct ProcessList_* pl, unsigned int param, const MeterClass* type) {
Meter* this = xCalloc(1, sizeof(Meter));
Object_setClass(this, type);
this->h = 1;
@@ -93,15 +93,14 @@ void Meter_delete(Object* cast) {
}
void Meter_setCaption(Meter* this, const char* caption) {
- free(this->caption);
- this->caption = xStrdup(caption);
+ free_and_xStrdup(&this->caption, caption);
}
-static inline void Meter_displayBuffer(const Meter* this, const char* buffer, RichString* out) {
+static inline void Meter_displayBuffer(const Meter* this, RichString* out) {
if (Object_displayFn(this)) {
Object_display(this, out);
} else {
- RichString_writeWide(out, CRT_colors[Meter_attributes(this)[0]], buffer);
+ RichString_writeWide(out, CRT_colors[Meter_attributes(this)[0]], this->txtBuffer);
}
}
@@ -132,21 +131,20 @@ void Meter_setMode(Meter* this, int modeIndex) {
this->mode = modeIndex;
}
-ListItem* Meter_toListItem(Meter* this, bool moving) {
+ListItem* Meter_toListItem(const Meter* this, bool moving) {
char mode[20];
if (this->mode) {
xSnprintf(mode, sizeof(mode), " [%s]", Meter_modes[this->mode]->uiName);
} else {
mode[0] = '\0';
}
- char number[10];
- if (this->param > 0) {
- xSnprintf(number, sizeof(number), " %d", this->param);
- } else {
- number[0] = '\0';
- }
+ char name[32];
+ if (Meter_getUiNameFn(this))
+ Meter_getUiName(this, name, sizeof(name));
+ else
+ xSnprintf(name, sizeof(name), "%s", Meter_uiName(this));
char buffer[50];
- xSnprintf(buffer, sizeof(buffer), "%s%s%s", Meter_uiName(this), number, mode);
+ xSnprintf(buffer, sizeof(buffer), "%s%s", name, mode);
ListItem* li = ListItem_new(buffer, 0);
li->moving = moving;
return li;
@@ -155,23 +153,21 @@ ListItem* Meter_toListItem(Meter* this, bool moving) {
/* ---------- TextMeterMode ---------- */
static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
- char buffer[METER_BUFFER_LEN];
- Meter_updateValues(this, buffer, sizeof(buffer));
-
+ const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
- mvaddnstr(y, x, this->caption, w - 1);
+ mvaddnstr(y, x, caption, w - 1);
attrset(CRT_colors[RESET_COLOR]);
- int captionLen = strlen(this->caption);
+ int captionLen = strlen(caption);
x += captionLen;
w -= captionLen;
if (w <= 0)
return;
RichString_begin(out);
- Meter_displayBuffer(this, buffer, &out);
+ Meter_displayBuffer(this, &out);
RichString_printoffnVal(out, y, x, 0, w - 1);
- RichString_end(out);
+ RichString_delete(&out);
}
/* ---------- BarMeterMode ---------- */
@@ -179,13 +175,11 @@ static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
static const char BarMeterMode_characters[] = "|#*@$%&.";
static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
- char buffer[METER_BUFFER_LEN];
- Meter_updateValues(this, buffer, sizeof(buffer));
-
+ const char* caption = Meter_getCaption(this);
w -= 2;
attrset(CRT_colors[METER_TEXT]);
int captionLen = 3;
- mvaddnstr(y, x, this->caption, captionLen);
+ mvaddnstr(y, x, caption, captionLen);
x += captionLen;
w -= captionLen;
attrset(CRT_colors[BAR_BORDER]);
@@ -202,8 +196,8 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
// The text in the bar is right aligned;
// Pad with maximal spaces and then calculate needed starting position offset
RichString_begin(bar);
- RichString_appendChr(&bar, ' ', w);
- RichString_appendWide(&bar, 0, buffer);
+ RichString_appendChr(&bar, 0, ' ', w);
+ RichString_appendWide(&bar, 0, this->txtBuffer);
int startPos = RichString_sizeVal(bar) - w;
if (startPos > w) {
// Text is too large for bar
@@ -217,7 +211,7 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
}
}
- // If still to large, print the start not the end
+ // If still too large, print the start not the end
startPos = MINIMUM(startPos, w);
}
assert(startPos >= 0);
@@ -264,7 +258,7 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
RichString_printoffnVal(bar, y, x + offset, startPos + offset, w - offset);
}
- RichString_end(bar);
+ RichString_delete(&bar);
move(y, x + w + 1);
attrset(CRT_colors[RESET_COLOR]);
@@ -293,12 +287,13 @@ static const char* const GraphMeterMode_dotsAscii[] = {
};
static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
+ const ProcessList* pl = this->pl;
if (!this->drawData) {
this->drawData = xCalloc(1, sizeof(GraphData));
}
GraphData* data = this->drawData;
- const int nValues = METER_BUFFER_LEN;
+ const int nValues = METER_GRAPHDATA_SIZE;
const char* const* GraphMeterMode_dots;
int GraphMeterMode_pixPerRow;
@@ -313,29 +308,24 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
GraphMeterMode_pixPerRow = PIXPERROW_ASCII;
}
+ const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
int captionLen = 3;
- mvaddnstr(y, x, this->caption, captionLen);
+ mvaddnstr(y, x, caption, captionLen);
x += captionLen;
w -= captionLen;
- struct timeval now;
- gettimeofday(&now, NULL);
- if (!timercmp(&now, &(data->time), <)) {
+ if (!timercmp(&pl->realtime, &(data->time), <)) {
int globalDelay = this->pl->settings->delay;
- struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay - ((globalDelay / 10) * 10)) * 100000 };
- timeradd(&now, &delay, &(data->time));
+ struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay % 10) * 100000L };
+ timeradd(&pl->realtime, &delay, &(data->time));
for (int i = 0; i < nValues - 1; i++)
data->values[i] = data->values[i + 1];
- char buffer[METER_BUFFER_LEN];
- Meter_updateValues(this, buffer, sizeof(buffer));
-
double value = 0.0;
for (uint8_t i = 0; i < this->curItems; i++)
value += this->values[i];
- value /= this->total;
data->values[nValues - 1] = value;
}
@@ -346,8 +336,10 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
}
for (; i < nValues - 1; i += 2, k++) {
int pix = GraphMeterMode_pixPerRow * GRAPH_HEIGHT;
- int v1 = CLAMP((int) lround(data->values[i] * pix), 1, pix);
- int v2 = CLAMP((int) lround(data->values[i + 1] * pix), 1, pix);
+ if (this->total < 1)
+ this->total = 1;
+ int v1 = CLAMP((int) lround(data->values[i] / this->total * pix), 1, pix);
+ int v2 = CLAMP((int) lround(data->values[i + 1] / this->total * pix), 1, pix);
int colorIdx = GRAPH_1;
for (int line = 0; line < GRAPH_HEIGHT; line++) {
@@ -384,12 +376,10 @@ static const char* const* LEDMeterMode_digits;
static void LEDMeterMode_drawDigit(int x, int y, int n) {
for (int i = 0; i < 3; i++)
- mvaddstr(y+i, x, LEDMeterMode_digits[i * 10 + n]);
+ mvaddstr(y + i, x, LEDMeterMode_digits[i * 10 + n]);
}
static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
- (void) w;
-
#ifdef HAVE_LIBNCURSESW
if (CRT_utf8)
LEDMeterMode_digits = LEDMeterMode_digitsUtf8;
@@ -397,11 +387,8 @@ static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
#endif
LEDMeterMode_digits = LEDMeterMode_digitsAscii;
- char buffer[METER_BUFFER_LEN];
- Meter_updateValues(this, buffer, sizeof(buffer));
-
RichString_begin(out);
- Meter_displayBuffer(this, buffer, &out);
+ Meter_displayBuffer(this, &out);
int yText =
#ifdef HAVE_LIBNCURSESW
@@ -409,21 +396,32 @@ static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
#endif
y + 2;
attrset(CRT_colors[LED_COLOR]);
- mvaddstr(yText, x, this->caption);
- int xx = x + strlen(this->caption);
+ const char* caption = Meter_getCaption(this);
+ mvaddstr(yText, x, caption);
+ int xx = x + strlen(caption);
int len = RichString_sizeVal(out);
for (int i = 0; i < len; i++) {
int c = RichString_getCharVal(out, i);
if (c >= '0' && c <= '9') {
- LEDMeterMode_drawDigit(xx, y, c - 48);
+ if (xx - x + 4 > w)
+ break;
+
+ LEDMeterMode_drawDigit(xx, y, c - '0');
xx += 4;
} else {
+ if (xx - x + 1 > w)
+ break;
+#ifdef HAVE_LIBNCURSESW
+ const cchar_t wc = { .chars = { c, '\0' }, .attr = 0 }; /* use LED_COLOR from attrset() */
+ mvadd_wch(yText, xx, &wc);
+#else
mvaddch(yText, xx, c);
+#endif
xx += 1;
}
}
attrset(CRT_colors[RESET_COLOR]);
- RichString_end(out);
+ RichString_delete(&out);
}
static MeterMode BarMeterMode = {
@@ -461,14 +459,11 @@ const MeterMode* const Meter_modes[] = {
/* Blank meter */
-static void BlankMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t size) {
- if (size > 0) {
- *buffer = 0;
- }
+static void BlankMeter_updateValues(Meter* this) {
+ this->txtBuffer[0] = '\0';
}
-static void BlankMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
- RichString_prune(out);
+static void BlankMeter_display(ATTR_UNUSED const Object* cast, ATTR_UNUSED RichString* out) {
}
static const int BlankMeter_attributes[] = {
diff --git a/Meter.h b/Meter.h
index 2a78fd1..0e6b3f9 100644
--- a/Meter.h
+++ b/Meter.h
@@ -10,6 +10,7 @@ in the source distribution for its full text.
#include "config.h" // IWYU pragma: keep
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include <sys/time.h>
@@ -18,7 +19,8 @@ in the source distribution for its full text.
#include "ProcessList.h"
-#define METER_BUFFER_LEN 256
+#define METER_TXTBUFFER_LEN 256
+#define METER_GRAPHDATA_SIZE 256
#define METER_BUFFER_CHECK(buffer, size, written) \
do { \
@@ -49,16 +51,20 @@ typedef struct Meter_ Meter;
typedef void(*Meter_Init)(Meter*);
typedef void(*Meter_Done)(Meter*);
typedef void(*Meter_UpdateMode)(Meter*, int);
-typedef void(*Meter_UpdateValues)(Meter*, char*, size_t);
+typedef void(*Meter_UpdateValues)(Meter*);
typedef void(*Meter_Draw)(Meter*, int, int, int);
+typedef const char* (*Meter_GetCaption)(const Meter*);
+typedef void(*Meter_GetUiName)(const Meter*, char*, size_t);
typedef struct MeterClass_ {
const ObjectClass super;
const Meter_Init init;
const Meter_Done done;
const Meter_UpdateMode updateMode;
- const Meter_Draw draw;
const Meter_UpdateValues updateValues;
+ const Meter_Draw draw;
+ const Meter_GetCaption getCaption;
+ const Meter_GetUiName getUiName;
const int defaultMode;
const double total;
const int* const attributes;
@@ -67,6 +73,7 @@ typedef struct MeterClass_ {
const char* const caption; /* prefix in the actual header */
const char* const description; /* optional meter description in header setup menu */
const uint8_t maxItems;
+ const bool isMultiColumn; /* whether the meter draws multiple sub-columns (defaults to false) */
} MeterClass;
#define As_Meter(this_) ((const MeterClass*)((this_)->super.klass))
@@ -77,16 +84,20 @@ typedef struct MeterClass_ {
#define Meter_updateMode(this_, m_) As_Meter(this_)->updateMode((Meter*)(this_), m_)
#define Meter_drawFn(this_) As_Meter(this_)->draw
#define Meter_doneFn(this_) As_Meter(this_)->done
-#define Meter_updateValues(this_, buf_, sz_) \
- As_Meter(this_)->updateValues((Meter*)(this_), buf_, sz_)
+#define Meter_updateValues(this_) As_Meter(this_)->updateValues((Meter*)(this_))
+#define Meter_getUiNameFn(this_) As_Meter(this_)->getUiName
+#define Meter_getUiName(this_,n_,l_) As_Meter(this_)->getUiName((const Meter*)(this_),n_,l_)
+#define Meter_getCaptionFn(this_) As_Meter(this_)->getCaption
+#define Meter_getCaption(this_) (Meter_getCaptionFn(this_) ? As_Meter(this_)->getCaption((const Meter*)(this_)) : (this_)->caption)
#define Meter_defaultMode(this_) As_Meter(this_)->defaultMode
#define Meter_attributes(this_) As_Meter(this_)->attributes
#define Meter_name(this_) As_Meter(this_)->name
#define Meter_uiName(this_) As_Meter(this_)->uiName
+#define Meter_isMultiColumn(this_) As_Meter(this_)->isMultiColumn
typedef struct GraphData_ {
struct timeval time;
- double values[METER_BUFFER_LEN];
+ double values[METER_GRAPHDATA_SIZE];
} GraphData;
struct Meter_ {
@@ -95,12 +106,14 @@ struct Meter_ {
char* caption;
int mode;
- int param;
+ unsigned int param;
GraphData* drawData;
int h;
+ int columnWidthCount; /**< only used internally by the Header */
const ProcessList* pl;
uint8_t curItems;
const int* curAttributes;
+ char txtBuffer[METER_TXTBUFFER_LEN];
double* values;
double total;
void* meterData;
@@ -123,7 +136,7 @@ typedef enum {
extern const MeterClass Meter_class;
-Meter* Meter_new(const ProcessList* pl, int param, const MeterClass* type);
+Meter* Meter_new(const ProcessList* pl, unsigned int param, const MeterClass* type);
int Meter_humanUnit(char* buffer, unsigned long int value, size_t size);
@@ -133,7 +146,7 @@ void Meter_setCaption(Meter* this, const char* caption);
void Meter_setMode(Meter* this, int modeIndex);
-ListItem* Meter_toListItem(Meter* this, bool moving);
+ListItem* Meter_toListItem(const Meter* this, bool moving);
extern const MeterMode* const Meter_modes[];
diff --git a/MetersPanel.c b/MetersPanel.c
index 30991a0..13c102d 100644
--- a/MetersPanel.c
+++ b/MetersPanel.c
@@ -185,8 +185,7 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {
Header* header = this->scr->header;
this->settings->changed = true;
Header_calculateHeight(header);
- Header_draw(header);
- ScreenManager_resize(this->scr, this->scr->x1, header->height, this->scr->x2, this->scr->y2);
+ ScreenManager_resize(this->scr);
}
return result;
}
@@ -216,7 +215,7 @@ MetersPanel* MetersPanel_new(Settings* settings, const char* header, Vector* met
this->leftNeighbor = NULL;
Panel_setHeader(super, header);
for (int i = 0; i < Vector_size(meters); i++) {
- Meter* meter = (Meter*) Vector_get(meters, i);
+ const Meter* meter = (const Meter*) Vector_get(meters, i);
Panel_add(super, (Object*) Meter_toListItem(meter, false));
}
return this;
diff --git a/NetworkIOMeter.c b/NetworkIOMeter.c
index a898b31..dcba78d 100644
--- a/NetworkIOMeter.c
+++ b/NetworkIOMeter.c
@@ -1,13 +1,14 @@
#include "NetworkIOMeter.h"
#include <stdbool.h>
-#include <stddef.h>
-#include <sys/time.h>
+#include <stdint.h>
#include "CRT.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
+#include "Process.h"
+#include "ProcessList.h"
#include "RichString.h"
#include "XUtils.h"
@@ -19,71 +20,81 @@ static const int NetworkIOMeter_attributes[] = {
static bool hasData = false;
-static unsigned long int cached_rxb_diff = 0;
-static unsigned long int cached_rxp_diff = 0;
-static unsigned long int cached_txb_diff = 0;
-static unsigned long int cached_txp_diff = 0;
+static uint32_t cached_rxb_diff;
+static uint32_t cached_rxp_diff;
+static uint32_t cached_txb_diff;
+static uint32_t cached_txp_diff;
-static void NetworkIOMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t len) {
- static unsigned long long int cached_last_update = 0;
+static void NetworkIOMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+ static uint64_t cached_last_update = 0;
- struct timeval tv;
- gettimeofday(&tv, NULL);
- unsigned long long int timeInMilliSeconds = (unsigned long long int)tv.tv_sec * 1000 + (unsigned long long int)tv.tv_usec / 1000;
- unsigned long long int passedTimeInMs = timeInMilliSeconds - cached_last_update;
+ uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update;
/* update only every 500ms */
if (passedTimeInMs > 500) {
- static unsigned long int cached_rxb_total = 0;
- static unsigned long int cached_rxp_total = 0;
- static unsigned long int cached_txb_total = 0;
- static unsigned long int cached_txp_total = 0;
+ static uint64_t cached_rxb_total;
+ static uint64_t cached_rxp_total;
+ static uint64_t cached_txb_total;
+ static uint64_t cached_txp_total;
+ uint64_t diff;
- cached_last_update = timeInMilliSeconds;
+ cached_last_update = pl->realtimeMs;
- unsigned long int bytesReceived, packetsReceived, bytesTransmitted, packetsTransmitted;
-
- hasData = Platform_getNetworkIO(&bytesReceived, &packetsReceived, &bytesTransmitted, &packetsTransmitted);
+ NetworkIOData data;
+ hasData = Platform_getNetworkIO(&data);
if (!hasData) {
- xSnprintf(buffer, len, "no data");
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
return;
}
- if (bytesReceived > cached_rxb_total) {
- cached_rxb_diff = (bytesReceived - cached_rxb_total) / 1024; /* Meter_humanUnit() expects unit in kilo */
- cached_rxb_diff = 1000.0 * cached_rxb_diff / passedTimeInMs; /* convert to per second */
+ if (data.bytesReceived > cached_rxb_total) {
+ diff = data.bytesReceived - cached_rxb_total;
+ diff /= ONE_K; /* Meter_humanUnit() expects unit in kilo */
+ diff = (1000 * diff) / passedTimeInMs; /* convert to per second */
+ cached_rxb_diff = (uint32_t)diff;
} else {
cached_rxb_diff = 0;
}
- cached_rxb_total = bytesReceived;
+ cached_rxb_total = data.bytesReceived;
- if (packetsReceived > cached_rxp_total) {
- cached_rxp_diff = packetsReceived - cached_rxp_total;
+ if (data.packetsReceived > cached_rxp_total) {
+ diff = data.packetsReceived - cached_rxp_total;
+ cached_rxp_diff = (uint32_t)diff;
} else {
cached_rxp_diff = 0;
}
- cached_rxp_total = packetsReceived;
+ cached_rxp_total = data.packetsReceived;
- if (bytesTransmitted > cached_txb_total) {
- cached_txb_diff = (bytesTransmitted - cached_txb_total) / 1024; /* Meter_humanUnit() expects unit in kilo */
- cached_txb_diff = 1000.0 * cached_txb_diff / passedTimeInMs; /* convert to per second */
+ if (data.bytesTransmitted > cached_txb_total) {
+ diff = data.bytesTransmitted - cached_txb_total;
+ diff /= ONE_K; /* Meter_humanUnit() expects unit in kilo */
+ diff = (1000 * diff) / passedTimeInMs; /* convert to per second */
+ cached_txb_diff = (uint32_t)diff;
} else {
cached_txb_diff = 0;
}
- cached_txb_total = bytesTransmitted;
+ cached_txb_total = data.bytesTransmitted;
- if (packetsTransmitted > cached_txp_total) {
- cached_txp_diff = packetsTransmitted - cached_txp_total;
+ if (data.packetsTransmitted > cached_txp_total) {
+ diff = data.packetsTransmitted - cached_txp_total;
+ cached_txp_diff = (uint32_t)diff;
} else {
cached_txp_diff = 0;
}
- cached_txp_total = packetsTransmitted;
+ cached_txp_total = data.packetsTransmitted;
+ }
+
+ this->values[0] = cached_rxb_diff;
+ this->values[1] = cached_txb_diff;
+ if (cached_rxb_diff + cached_txb_diff > this->total) {
+ this->total = cached_rxb_diff + cached_txb_diff;
}
char bufferBytesReceived[12], bufferBytesTransmitted[12];
Meter_humanUnit(bufferBytesReceived, cached_rxb_diff, sizeof(bufferBytesReceived));
Meter_humanUnit(bufferBytesTransmitted, cached_txb_diff, sizeof(bufferBytesTransmitted));
- xSnprintf(buffer, len, "rx:%siB/s tx:%siB/s", bufferBytesReceived, bufferBytesTransmitted);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "rx:%siB/s tx:%siB/s", bufferBytesReceived, bufferBytesTransmitted);
}
static void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
@@ -93,6 +104,7 @@ static void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* o
}
char buffer[64];
+ int len;
RichString_writeAscii(out, CRT_colors[METER_TEXT], "rx: ");
Meter_humanUnit(buffer, cached_rxb_diff, sizeof(buffer));
@@ -104,8 +116,8 @@ static void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* o
RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], buffer);
RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s");
- xSnprintf(buffer, sizeof(buffer), " (%lu/%lu packets) ", cached_rxp_diff, cached_txp_diff);
- RichString_appendAscii(out, CRT_colors[METER_TEXT], buffer);
+ len = xSnprintf(buffer, sizeof(buffer), " (%u/%u packets) ", cached_rxp_diff, cached_txp_diff);
+ RichString_appendnAscii(out, CRT_colors[METER_TEXT], buffer, len);
}
const MeterClass NetworkIOMeter_class = {
@@ -116,7 +128,7 @@ const MeterClass NetworkIOMeter_class = {
},
.updateValues = NetworkIOMeter_updateValues,
.defaultMode = TEXT_METERMODE,
- .maxItems = 0,
+ .maxItems = 2,
.total = 100.0,
.attributes = NetworkIOMeter_attributes,
.name = "NetworkIO",
diff --git a/NetworkIOMeter.h b/NetworkIOMeter.h
index 311b5e6..18c23ce 100644
--- a/NetworkIOMeter.h
+++ b/NetworkIOMeter.h
@@ -3,6 +3,14 @@
#include "Meter.h"
+
+typedef struct NetworkIOData_ {
+ uint64_t bytesReceived;
+ uint64_t packetsReceived;
+ uint64_t bytesTransmitted;
+ uint64_t packetsTransmitted;
+} NetworkIOData;
+
extern const MeterClass NetworkIOMeter_class;
#endif /* HEADER_NetworkIOMeter */
diff --git a/Object.c b/Object.c
index 0a29d01..f361394 100644
--- a/Object.c
+++ b/Object.c
@@ -15,8 +15,6 @@ const ObjectClass Object_class = {
.extends = NULL
};
-#ifndef NDEBUG
-
bool Object_isA(const Object* o, const ObjectClass* klass) {
if (!o)
return false;
@@ -29,5 +27,3 @@ bool Object_isA(const Object* o, const ObjectClass* klass) {
return false;
}
-
-#endif /* NDEBUG */
diff --git a/Object.h b/Object.h
index 89933fa..e18fb97 100644
--- a/Object.h
+++ b/Object.h
@@ -11,14 +11,11 @@ in the source distribution for its full text.
#include "config.h" // IWYU pragma: keep
#include <assert.h>
+#include <stdbool.h>
#include "RichString.h"
#include "XUtils.h" // IWYU pragma: keep
-#ifndef NDEBUG
-#include <stdbool.h>
-#endif
-
struct Object_;
typedef struct Object_ Object;
@@ -57,10 +54,6 @@ typedef union {
extern const ObjectClass Object_class;
-#ifndef NDEBUG
-
bool Object_isA(const Object* o, const ObjectClass* klass);
-#endif /* NDEBUG */
-
#endif
diff --git a/OpenFilesScreen.h b/OpenFilesScreen.h
index 0fbafe0..f3c129c 100644
--- a/OpenFilesScreen.h
+++ b/OpenFilesScreen.h
@@ -13,6 +13,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "Process.h"
+
typedef struct OpenFilesScreen_ {
InfoScreen super;
pid_t pid;
diff --git a/OptionItem.c b/OptionItem.c
index 688fab8..437f7ea 100644
--- a/OptionItem.c
+++ b/OptionItem.c
@@ -53,7 +53,7 @@ static void NumberItem_display(const Object* cast, RichString* out) {
} else {
written = xSnprintf(buffer, sizeof(buffer), "%d", NumberItem_get(this));
}
- RichString_appendAscii(out, CRT_colors[CHECK_MARK], buffer);
+ RichString_appendnAscii(out, CRT_colors[CHECK_MARK], buffer, written);
RichString_appendAscii(out, CRT_colors[CHECK_BOX], "]");
for (int i = written; i < 5; i++) {
RichString_appendAscii(out, CRT_colors[CHECK_BOX], " ");
diff --git a/Panel.c b/Panel.c
index 5d10375..dea6401 100644
--- a/Panel.c
+++ b/Panel.c
@@ -69,7 +69,7 @@ void Panel_done(Panel* this) {
free(this->eventHandlerState);
Vector_delete(this->items);
FunctionBar_delete(this->defaultBar);
- RichString_end(this->header);
+ RichString_delete(&this->header);
}
void Panel_setSelectionColor(Panel* this, ColorElements colorId) {
@@ -172,13 +172,13 @@ void Panel_moveSelectedDown(Panel* this) {
}
}
-int Panel_getSelectedIndex(Panel* this) {
+int Panel_getSelectedIndex(const Panel* this) {
assert (this != NULL);
return this->selected;
}
-int Panel_size(Panel* this) {
+int Panel_size(const Panel* this) {
assert (this != NULL);
return Vector_size(this->items);
@@ -246,8 +246,8 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
if (this->scrollV < 0) {
this->scrollV = 0;
this->needsRedraw = true;
- } else if (this->scrollV >= size) {
- this->scrollV = MAXIMUM(size - 1, 0);
+ } else if (this->scrollV > size - h) {
+ this->scrollV = MAXIMUM(size - h, 0);
this->needsRedraw = true;
}
// ensure selection is on screen
@@ -269,7 +269,7 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
if (this->needsRedraw || force_redraw) {
int line = 0;
for (int i = first; line < h && i < upTo; i++) {
- Object* itemObj = Vector_get(this->items, i);
+ const Object* itemObj = Vector_get(this->items, i);
RichString_begin(item);
Object_display(itemObj, &item);
int itemLen = RichString_sizeVal(item);
@@ -287,7 +287,7 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
RichString_printoffnVal(item, y + line, x, scrollH, amt);
if (item.highlightAttr)
attrset(CRT_colors[RESET_COLOR]);
- RichString_end(item);
+ RichString_delete(&item);
line++;
}
while (line < h) {
@@ -296,11 +296,11 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
}
} else {
- Object* oldObj = Vector_get(this->items, this->oldSelected);
+ const Object* oldObj = Vector_get(this->items, this->oldSelected);
RichString_begin(old);
Object_display(oldObj, &old);
int oldLen = RichString_sizeVal(old);
- Object* newObj = Vector_get(this->items, this->selected);
+ const Object* newObj = Vector_get(this->items, this->selected);
RichString_begin(new);
Object_display(newObj, &new);
int newLen = RichString_sizeVal(new);
@@ -316,8 +316,8 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
RichString_printoffnVal(new, y + this->selected - first, x,
scrollH, MINIMUM(newLen - scrollH, this->w));
attrset(CRT_colors[RESET_COLOR]);
- RichString_end(new);
- RichString_end(old);
+ RichString_delete(&new);
+ RichString_delete(&old);
}
if (focus && (this->needsRedraw || force_redraw || !this->wasFocus)) {
@@ -454,7 +454,7 @@ HandlerResult Panel_selectByTyping(Panel* this, int ch) {
if (len < 99) {
buffer[len] = ch;
- buffer[len+1] = '\0';
+ buffer[len + 1] = '\0';
}
for (int try = 0; try < 2; try++) {
diff --git a/Panel.h b/Panel.h
index 945718a..41dba21 100644
--- a/Panel.h
+++ b/Panel.h
@@ -7,6 +7,9 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
+#include <assert.h>
#include <stdbool.h>
#include "CRT.h"
@@ -26,7 +29,8 @@ typedef enum HandlerResult_ {
REFRESH = 0x08,
REDRAW = 0x10,
RESCAN = 0x20,
- SYNTH_KEY = 0x40,
+ RESIZE = 0x40,
+ SYNTH_KEY = 0x80,
} HandlerResult;
#define EVENT_SET_SELECTED (-1)
@@ -112,9 +116,9 @@ void Panel_moveSelectedUp(Panel* this);
void Panel_moveSelectedDown(Panel* this);
-int Panel_getSelectedIndex(Panel* this);
+int Panel_getSelectedIndex(const Panel* this);
-int Panel_size(Panel* this);
+int Panel_size(const Panel* this);
void Panel_setSelected(Panel* this, int selected);
diff --git a/Process.c b/Process.c
index a78fd23..556acf4 100644
--- a/Process.c
+++ b/Process.c
@@ -17,6 +17,7 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/resource.h>
@@ -25,17 +26,19 @@ in the source distribution for its full text.
#include "Macros.h"
#include "Platform.h"
#include "ProcessList.h"
+#include "DynamicColumn.h"
#include "RichString.h"
#include "Settings.h"
#include "XUtils.h"
#if defined(MAJOR_IN_MKDEV)
#include <sys/mkdev.h>
-#elif defined(MAJOR_IN_SYSMACROS)
-#include <sys/sysmacros.h>
#endif
+/* Used to identify kernel threads in Comm and Exe columns */
+static const char* const kthreadID = "KTHREAD";
+
static uid_t Process_getuid = (uid_t)-1;
int Process_pidDigits = 7;
@@ -49,27 +52,31 @@ void Process_setupColumnWidths() {
assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS);
}
-void Process_humanNumber(RichString* str, unsigned long long number, bool coloring) {
- char buffer[10];
+void Process_printBytes(RichString* str, unsigned long long number, bool coloring) {
+ char buffer[16];
int len;
- int largeNumberColor = CRT_colors[LARGE_NUMBER];
- int processMegabytesColor = CRT_colors[PROCESS_MEGABYTES];
- int processGigabytesColor = CRT_colors[PROCESS_GIGABYTES];
+ int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
+ int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
+ int processGigabytesColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];
+ int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
int processColor = CRT_colors[PROCESS];
- if (!coloring) {
- largeNumberColor = CRT_colors[PROCESS];
- processMegabytesColor = CRT_colors[PROCESS];
- processGigabytesColor = CRT_colors[PROCESS];
+
+ if (number == ULLONG_MAX) {
+ //Invalid number
+ RichString_appendAscii(str, shadowColor, " N/A ");
+ return;
}
+ number /= ONE_K;
+
if (number < 1000) {
//Plain number, no markings
len = xSnprintf(buffer, sizeof(buffer), "%5llu ", number);
RichString_appendnAscii(str, processColor, buffer, len);
} else if (number < 100000) {
//2 digit MB, 3 digit KB
- len = xSnprintf(buffer, sizeof(buffer), "%2llu", number/1000);
+ len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 1000);
RichString_appendnAscii(str, processMegabytesColor, buffer, len);
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03llu ", number);
@@ -82,7 +89,7 @@ void Process_humanNumber(RichString* str, unsigned long long number, bool colori
} else if (number < 10000 * ONE_K) {
//1 digit GB, 3 digit MB
number /= ONE_K;
- len = xSnprintf(buffer, sizeof(buffer), "%1llu", number/1000);
+ len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000);
RichString_appendnAscii(str, processGigabytesColor, buffer, len);
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03lluM ", number);
@@ -90,7 +97,7 @@ void Process_humanNumber(RichString* str, unsigned long long number, bool colori
} else if (number < 100000 * ONE_K) {
//2 digit GB, 1 digit MB
number /= 100 * ONE_K;
- len = xSnprintf(buffer, sizeof(buffer), "%2llu", number/10);
+ len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10);
RichString_appendnAscii(str, processGigabytesColor, buffer, len);
number %= 10;
len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number);
@@ -104,31 +111,46 @@ void Process_humanNumber(RichString* str, unsigned long long number, bool colori
} else if (number < 10000ULL * ONE_M) {
//1 digit TB, 3 digit GB
number /= ONE_M;
- len = xSnprintf(buffer, sizeof(buffer), "%1llu", number/1000);
+ len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03lluG ", number);
RichString_appendnAscii(str, processGigabytesColor, buffer, len);
+ } else if (number < 100000 * ONE_M) {
+ //2 digit TB, 1 digit GB
+ number /= 100 * ONE_M;
+ len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
+ number %= 10;
+ len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number);
+ RichString_appendnAscii(str, processGigabytesColor, buffer, len);
+ RichString_appendAscii(str, largeNumberColor, "T ");
+ } else if (number < 10000ULL * ONE_G) {
+ //3 digit TB or 1 digit PB, 3 digit TB
+ number /= ONE_G;
+ len = xSnprintf(buffer, sizeof(buffer), "%4lluT ", number);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
} else {
- //2 digit TB and above
- len = xSnprintf(buffer, sizeof(buffer), "%4.1lfT ", (double)number/ONE_G);
+ //2 digit PB and above
+ len = xSnprintf(buffer, sizeof(buffer), "%4.1lfP ", (double)number / ONE_T);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
}
}
-void Process_colorNumber(RichString* str, unsigned long long number, bool coloring) {
+void Process_printKBytes(RichString* str, unsigned long long number, bool coloring) {
+ if (number == ULLONG_MAX)
+ Process_printBytes(str, ULLONG_MAX, coloring);
+ else
+ Process_printBytes(str, number * ONE_K, coloring);
+}
+
+void Process_printCount(RichString* str, unsigned long long number, bool coloring) {
char buffer[13];
- int largeNumberColor = CRT_colors[LARGE_NUMBER];
- int processMegabytesColor = CRT_colors[PROCESS_MEGABYTES];
+ int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
+ int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
int processColor = CRT_colors[PROCESS];
- int processShadowColor = CRT_colors[PROCESS_SHADOW];
-
- if (!coloring) {
- largeNumberColor = CRT_colors[PROCESS];
- processMegabytesColor = CRT_colors[PROCESS];
- processShadowColor = CRT_colors[PROCESS];
- }
+ int processShadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
if (number == ULLONG_MAX) {
RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A ");
@@ -138,41 +160,93 @@ void Process_colorNumber(RichString* str, unsigned long long number, bool colori
} else if (number >= 100LL * ONE_DECIMAL_T) {
xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M);
RichString_appendnAscii(str, largeNumberColor, buffer, 8);
- RichString_appendnAscii(str, processMegabytesColor, buffer+8, 4);
+ RichString_appendnAscii(str, processMegabytesColor, buffer + 8, 4);
} else if (number >= 10LL * ONE_DECIMAL_G) {
xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_K);
RichString_appendnAscii(str, largeNumberColor, buffer, 5);
- RichString_appendnAscii(str, processMegabytesColor, buffer+5, 3);
- RichString_appendnAscii(str, processColor, buffer+8, 4);
+ RichString_appendnAscii(str, processMegabytesColor, buffer + 5, 3);
+ RichString_appendnAscii(str, processColor, buffer + 8, 4);
} else {
xSnprintf(buffer, sizeof(buffer), "%11llu ", number);
RichString_appendnAscii(str, largeNumberColor, buffer, 2);
- RichString_appendnAscii(str, processMegabytesColor, buffer+2, 3);
- RichString_appendnAscii(str, processColor, buffer+5, 3);
- RichString_appendnAscii(str, processShadowColor, buffer+8, 4);
+ RichString_appendnAscii(str, processMegabytesColor, buffer + 2, 3);
+ RichString_appendnAscii(str, processColor, buffer + 5, 3);
+ RichString_appendnAscii(str, processShadowColor, buffer + 8, 4);
}
}
-void Process_printTime(RichString* str, unsigned long long totalHundredths) {
- unsigned long long totalSeconds = totalHundredths / 100;
+void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) {
+ char buffer[10];
+ int len;
+ unsigned long long totalSeconds = totalHundredths / 100;
unsigned long long hours = totalSeconds / 3600;
+ unsigned long long days = totalSeconds / 86400;
int minutes = (totalSeconds / 60) % 60;
int seconds = totalSeconds % 60;
int hundredths = totalHundredths - (totalSeconds * 100);
- char buffer[10];
- if (hours >= 100) {
- xSnprintf(buffer, sizeof(buffer), "%7lluh ", hours);
- RichString_appendAscii(str, CRT_colors[LARGE_NUMBER], buffer);
- } else {
- if (hours) {
- xSnprintf(buffer, sizeof(buffer), "%2lluh", hours);
- RichString_appendAscii(str, CRT_colors[LARGE_NUMBER], buffer);
- xSnprintf(buffer, sizeof(buffer), "%02d:%02d ", minutes, seconds);
+
+ int yearColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
+ int dayColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];
+ int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
+ int defColor = CRT_colors[PROCESS];
+
+ if (days >= /* Ignore leapyears */365) {
+ int years = days / 365;
+ int daysLeft = days - 365 * years;
+
+ if (daysLeft >= 100) {
+ len = xSnprintf(buffer, sizeof(buffer), "%3dy", years);
+ RichString_appendnAscii(str, yearColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ } else if (daysLeft >= 10) {
+ len = xSnprintf(buffer, sizeof(buffer), "%4dy", years);
+ RichString_appendnAscii(str, yearColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%2dd ", daysLeft);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ } else {
+ len = xSnprintf(buffer, sizeof(buffer), "%5dy", years);
+ RichString_appendnAscii(str, yearColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%1dd ", daysLeft);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ }
+ } else if (days >= 100) {
+ int hoursLeft = hours - days * 24;
+
+ if (hoursLeft >= 10) {
+ len = xSnprintf(buffer, sizeof(buffer), "%4llud", days);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%2dh ", hoursLeft);
+ RichString_appendnAscii(str, hourColor, buffer, len);
} else {
- xSnprintf(buffer, sizeof(buffer), "%2d:%02d.%02d ", minutes, seconds, hundredths);
+ len = xSnprintf(buffer, sizeof(buffer), "%5llud", days);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%1dh ", hoursLeft);
+ RichString_appendnAscii(str, hourColor, buffer, len);
}
- RichString_appendAscii(str, CRT_colors[DEFAULT_COLOR], buffer);
+ } else if (hours >= 100) {
+ int minutesLeft = totalSeconds / 60 - hours * 60;
+
+ if (minutesLeft >= 10) {
+ len = xSnprintf(buffer, sizeof(buffer), "%4lluh", hours);
+ RichString_appendnAscii(str, hourColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%2dm ", minutesLeft);
+ RichString_appendnAscii(str, defColor, buffer, len);
+ } else {
+ len = xSnprintf(buffer, sizeof(buffer), "%5lluh", hours);
+ RichString_appendnAscii(str, hourColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%1dm ", minutesLeft);
+ RichString_appendnAscii(str, defColor, buffer, len);
+ }
+ } else if (hours > 0) {
+ len = xSnprintf(buffer, sizeof(buffer), "%2lluh", hours);
+ RichString_appendnAscii(str, hourColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%02d:%02d ", minutes, seconds);
+ RichString_appendnAscii(str, defColor, buffer, len);
+ } else {
+ len = xSnprintf(buffer, sizeof(buffer), "%2d:%02d.%02d ", minutes, seconds, hundredths);
+ RichString_appendnAscii(str, defColor, buffer, len);
}
}
@@ -182,42 +256,419 @@ void Process_fillStarttimeBuffer(Process* this) {
strftime(this->starttime_show, sizeof(this->starttime_show) - 1, (this->starttime_ctime > (time(NULL) - 86400)) ? "%R " : "%b%d ", &date);
}
-static inline void Process_writeCommand(const Process* this, int attr, int baseattr, RichString* str) {
- int start = RichString_size(str);
- int len = 0;
- const char* comm = this->comm;
+/*
+ * TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is
+ * not available in an userspace header - so define it.
+ *
+ * Note: This is taken from LINUX headers, but implicitly taken for other platforms
+ * for sake of brevity.
+ *
+ * Note: when colorizing a basename with the comm prefix, the entire basename
+ * (not just the comm prefix) is colorized for better readability, and it is
+ * implicit that only upto (TASK_COMM_LEN - 1) could be comm.
+ */
+#define TASK_COMM_LEN 16
- if (this->settings->highlightBaseName || !this->settings->showProgramPath) {
- int basename = 0;
- for (int i = 0; i < this->basenameOffset; i++) {
- if (comm[i] == '/') {
- basename = i + 1;
- } else if (comm[i] == ':') {
- len = i + 1;
- break;
+static bool findCommInCmdline(const char* comm, const char* cmdline, int cmdlineBasenameStart, int* pCommStart, int* pCommEnd) {
+ /* Try to find procComm in tokenized cmdline - this might in rare cases
+ * mis-identify a string or fail, if comm or cmdline had been unsuitably
+ * modified by the process */
+ const char* tokenBase;
+ size_t tokenLen;
+ const size_t commLen = strlen(comm);
+
+ if (cmdlineBasenameStart < 0)
+ return false;
+
+ for (const char* token = cmdline + cmdlineBasenameStart; *token;) {
+ for (tokenBase = token; *token && *token != '\n'; ++token) {
+ if (*token == '/') {
+ tokenBase = token + 1;
}
}
- if (len == 0) {
- if (this->settings->showProgramPath) {
- start += basename;
- } else {
- comm += basename;
+ tokenLen = token - tokenBase;
+
+ if ((tokenLen == commLen || (tokenLen > commLen && commLen == (TASK_COMM_LEN - 1))) &&
+ strncmp(tokenBase, comm, commLen) == 0) {
+ *pCommStart = tokenBase - cmdline;
+ *pCommEnd = token - cmdline;
+ return true;
+ }
+
+ if (*token) {
+ do {
+ ++token;
+ } while (*token && '\n' == *token);
+ }
+ }
+ return false;
+}
+
+static int matchCmdlinePrefixWithExeSuffix(const char* cmdline, int cmdlineBaseOffset, const char* exe, int exeBaseOffset, int exeBaseLen) {
+ int matchLen; /* matching length to be returned */
+ char delim; /* delimiter following basename */
+
+ /* cmdline prefix is an absolute path: it must match whole exe. */
+ if (cmdline[0] == '/') {
+ matchLen = exeBaseLen + exeBaseOffset;
+ if (strncmp(cmdline, exe, matchLen) == 0) {
+ delim = cmdline[matchLen];
+ if (delim == 0 || delim == '\n' || delim == ' ') {
+ return matchLen;
+ }
+ }
+ return 0;
+ }
+
+ /* cmdline prefix is a relative path: We need to first match the basename at
+ * cmdlineBaseOffset and then reverse match the cmdline prefix with the exe
+ * suffix. But there is a catch: Some processes modify their cmdline in ways
+ * that make htop's identification of the basename in cmdline unreliable.
+ * For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to
+ * "gdm-session-worker [pam/gdm-autologin]" and htop ends up with
+ * proccmdlineBasenameEnd at "gdm-autologin]". This issue could arise with
+ * chrome as well as it stores in cmdline its concatenated argument vector,
+ * without NUL delimiter between the arguments (which may contain a '/')
+ *
+ * So if needed, we adjust cmdlineBaseOffset to the previous (if any)
+ * component of the cmdline relative path, and retry the procedure. */
+ bool delimFound; /* if valid basename delimiter found */
+ do {
+ /* match basename */
+ matchLen = exeBaseLen + cmdlineBaseOffset;
+ if (cmdlineBaseOffset < exeBaseOffset &&
+ strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) {
+ delim = cmdline[matchLen];
+ if (delim == 0 || delim == '\n' || delim == ' ') {
+ int i, j;
+ /* reverse match the cmdline prefix and exe suffix */
+ for (i = cmdlineBaseOffset - 1, j = exeBaseOffset - 1;
+ i >= 0 && j >= 0 && cmdline[i] == exe[j]; --i, --j)
+ ;
+
+ /* full match, with exe suffix being a valid relative path */
+ if (i < 0 && j >= 0 && exe[j] == '/')
+ return matchLen;
+ }
+ }
+
+ /* Try to find the previous potential cmdlineBaseOffset - it would be
+ * preceded by '/' or nothing, and delimited by ' ' or '\n' */
+ for (delimFound = false, cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) {
+ if (delimFound) {
+ if (cmdline[cmdlineBaseOffset - 1] == '/') {
+ break;
+ }
+ } else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\n') {
+ delimFound = true;
+ }
+ }
+ } while (delimFound);
+
+ return 0;
+}
+
+/* stpcpy, but also converts newlines to spaces */
+static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr) {
+ for (; *srcStr; ++srcStr) {
+ *dstStr++ = (*srcStr == '\n') ? ' ' : *srcStr;
+ }
+ *dstStr = 0;
+ return dstStr;
+}
+
+/*
+ * 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.
+ */
+void Process_makeCommandStr(Process* this) {
+ ProcessMergedCommand* mc = &this->mergedCommand;
+ const Settings* settings = this->settings;
+
+ bool showMergedCommand = settings->showMergedCommand;
+ bool showProgramPath = settings->showProgramPath;
+ bool searchCommInCmdline = settings->findCommInCmdline;
+ bool stripExeFromCmdline = settings->stripExeFromCmdline;
+ bool showThreadNames = settings->showThreadNames;
+
+ /* 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
+ * - a user thread and showThreadNames is not set */
+ if (Process_isKernelThread(this))
+ return;
+ if (this->state == 'Z' && !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
+ ) {
+ return;
+ }
+
+ /* The field separtor "│" has been chosen such that it will not match any
+ * valid string used for searching or filtering */
+ const char* SEPARATOR = CRT_treeStr[TREE_STR_VERT];
+ const int SEPARATOR_LEN = strlen(SEPARATOR);
+
+ /* Check for any changed fields since we last built this string */
+ if (mc->cmdlineChanged || mc->commChanged || mc->exeChanged) {
+ free(mc->str);
+ /* Accommodate the column text, two field separators and terminating NUL */
+ size_t maxLen = 2 * SEPARATOR_LEN + 1;
+ maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)");
+ maxLen += this->procComm ? strlen(this->procComm) : 0;
+ maxLen += this->procExe ? strlen(this->procExe) : 0;
+
+ mc->str = xCalloc(1, maxLen);
+ }
+
+ /* 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;
+ memset(mc->highlights, 0, sizeof(mc->highlights));
+
+ size_t mbMismatch = 0;
+ #define WRITE_HIGHLIGHT(_offset, _length, _attr, _flags) \
+ do { \
+ /* Check if we still have capacity */ \
+ assert(mc->highlightCount < ARRAYSIZE(mc->highlights)); \
+ if (mc->highlightCount >= ARRAYSIZE(mc->highlights)) \
+ break; \
+ \
+ mc->highlights[mc->highlightCount].offset = str - strStart + (_offset) - mbMismatch; \
+ mc->highlights[mc->highlightCount].length = _length; \
+ mc->highlights[mc->highlightCount].attr = _attr; \
+ mc->highlights[mc->highlightCount].flags = _flags; \
+ mc->highlightCount++; \
+ } while (0)
+
+ #define WRITE_SEPARATOR \
+ do { \
+ WRITE_HIGHLIGHT(0, 1, CRT_colors[FAILED_READ], CMDLINE_HIGHLIGHT_FLAG_SEPARATOR); \
+ mbMismatch += SEPARATOR_LEN - 1; \
+ str = stpcpy(str, SEPARATOR); \
+ } while (0)
+
+ const int baseAttr = Process_isThread(this) ? CRT_colors[PROCESS_THREAD_BASENAME] : CRT_colors[PROCESS_BASENAME];
+ const int commAttr = Process_isThread(this) ? CRT_colors[PROCESS_THREAD_COMM] : CRT_colors[PROCESS_COMM];
+ const int delExeAttr = CRT_colors[FAILED_READ];
+ const int delLibAttr = CRT_colors[PROCESS_TAG];
+
+ /* Establish some shortcuts to data we need */
+ const char* cmdline = this->cmdline;
+ const char* procComm = this->procComm;
+ const char* procExe = this->procExe;
+
+ char* strStart = mc->str;
+ char* str = strStart;
+
+ int cmdlineBasenameStart = this->cmdlineBasenameStart;
+ int cmdlineBasenameEnd = this->cmdlineBasenameEnd;
+
+ if (!cmdline) {
+ cmdlineBasenameStart = 0;
+ cmdlineBasenameEnd = 0;
+ cmdline = "(zombie)";
+ }
+
+ assert(cmdlineBasenameStart >= 0);
+ 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 (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);
+
+ WRITE_SEPARATOR;
}
- len = this->basenameOffset - basename;
}
+
+ if (cmdlineBasenameEnd > cmdlineBasenameStart)
+ WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
+ (void)stpcpyWithNewlineConversion(str, cmdline + (showProgramPath ? 0 : cmdlineBasenameStart));
+
+ return;
+ }
+
+ int exeLen = strlen(this->procExe);
+ int exeBasenameOffset = this->procExeBasenameOffset;
+ int exeBasenameLen = exeLen - exeBasenameOffset;
+
+ assert(exeBasenameOffset >= 0);
+ assert(exeBasenameOffset <= (int)strlen(procExe));
+
+ bool haveCommInExe = false;
+ if (procExe && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {
+ haveCommInExe = strncmp(procExe + exeBasenameOffset, procComm, TASK_COMM_LEN - 1) == 0;
+ }
+
+ /* Start with copying exe */
+ if (showProgramPath) {
+ if (haveCommInExe)
+ WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
+ WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
+ if (this->procExeDeleted)
+ WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ else if (this->usesDeletedLib)
+ WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ str = stpcpy(str, procExe);
+ } else {
+ if (haveCommInExe)
+ WRITE_HIGHLIGHT(0, exeBasenameLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
+ WRITE_HIGHLIGHT(0, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
+ if (this->procExeDeleted)
+ WRITE_HIGHLIGHT(0, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ else if (this->usesDeletedLib)
+ WRITE_HIGHLIGHT(0, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ str = stpcpy(str, procExe + exeBasenameOffset);
}
- RichString_appendWide(str, attr, comm);
+ bool haveCommInCmdline = false;
+ int commStart = 0;
+ int commEnd = 0;
+
+ /* Try to match procComm with procExe's basename: This is reliable (predictable) */
+ if (searchCommInCmdline) {
+ /* commStart/commEnd will be adjusted later along with cmdline */
+ haveCommInCmdline = (!Process_isUserlandThread(this) || showThreadNames) && findCommInCmdline(procComm, cmdline, cmdlineBasenameStart, &commStart, &commEnd);
+ }
+
+ int matchLen = matchCmdlinePrefixWithExeSuffix(cmdline, cmdlineBasenameStart, procExe, exeBasenameOffset, exeBasenameLen);
+
+ bool haveCommField = false;
+
+ if (!haveCommInExe && !haveCommInCmdline && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {
+ WRITE_SEPARATOR;
+ WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
+ str = stpcpy(str, procComm);
+ haveCommField = true;
+ }
+
+ if (matchLen) {
+ /* strip the matched exe prefix */
+ cmdline += matchLen;
+
+ commStart -= matchLen;
+ commEnd -= matchLen;
+ }
- if (this->settings->highlightBaseName) {
- RichString_setAttrn(str, baseattr, start, len);
+ if (!matchLen || (haveCommField && *cmdline)) {
+ /* cmdline will be a separate field */
+ WRITE_SEPARATOR;
}
+
+ if (!haveCommInExe && haveCommInCmdline && !haveCommField && (!Process_isUserlandThread(this) || showThreadNames))
+ WRITE_HIGHLIGHT(commStart, commEnd - commStart, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
+
+ /* Display cmdline if it hasn't been consumed by procExe */
+ if (*cmdline)
+ (void)stpcpyWithNewlineConversion(str, cmdline);
+
+ #undef WRITE_SEPARATOR
+ #undef WRITE_HIGHLIGHT
}
-void Process_outputRate(RichString* str, char* buffer, size_t n, double rate, int coloring) {
+void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str) {
+ (void)baseAttr;
+
+ const ProcessMergedCommand* mc = &this->mergedCommand;
+
+ int strStart = RichString_size(str);
+
+ const bool highlightBaseName = this->settings->highlightBaseName;
+ const bool highlightSeparator = true;
+ const bool highlightDeleted = this->settings->highlightDeletedExe;
+
+ if (!this->mergedCommand.str) {
+ int len = 0;
+ const char* cmdline = this->cmdline;
+
+ if (highlightBaseName || !this->settings->showProgramPath) {
+ int basename = 0;
+ for (int i = 0; i < this->cmdlineBasenameEnd; i++) {
+ if (cmdline[i] == '/') {
+ basename = i + 1;
+ } else if (cmdline[i] == ':') {
+ len = i + 1;
+ break;
+ }
+ }
+ if (len == 0) {
+ if (this->settings->showProgramPath) {
+ strStart += basename;
+ } else {
+ cmdline += basename;
+ }
+ len = this->cmdlineBasenameEnd - basename;
+ }
+ }
+
+ RichString_appendWide(str, attr, cmdline);
+
+ if (this->settings->highlightBaseName) {
+ RichString_setAttrn(str, baseAttr, strStart, len);
+ }
+
+ return;
+ }
+
+ RichString_appendWide(str, attr, this->mergedCommand.str);
+
+ for (size_t i = 0, hlCount = CLAMP(mc->highlightCount, 0, ARRAYSIZE(mc->highlights)); i < hlCount; i++) {
+ const ProcessCmdlineHighlight* hl = &mc->highlights[i];
+
+ if (!hl->length)
+ continue;
+
+ if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_SEPARATOR)
+ if (!highlightSeparator)
+ continue;
+
+ if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_BASENAME)
+ if (!highlightBaseName)
+ continue;
+
+ if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_DELETED)
+ if (!highlightDeleted)
+ continue;
+
+ RichString_setAttrn(str, hl->attr, strStart + hl->offset, hl->length);
+ }
+}
+
+void Process_printRate(RichString* str, double rate, bool coloring) {
+ char buffer[16];
+
int largeNumberColor = CRT_colors[LARGE_NUMBER];
int processMegabytesColor = CRT_colors[PROCESS_MEGABYTES];
int processColor = CRT_colors[PROCESS];
+ int shadowColor = CRT_colors[PROCESS_SHADOW];
if (!coloring) {
largeNumberColor = CRT_colors[PROCESS];
@@ -225,62 +676,66 @@ void Process_outputRate(RichString* str, char* buffer, size_t n, double rate, in
}
if (isnan(rate)) {
- RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A ");
+ RichString_appendAscii(str, shadowColor, " N/A ");
+ } else if (rate < 0.005) {
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
+ RichString_appendnAscii(str, shadowColor, buffer, len);
} else if (rate < ONE_K) {
- int len = snprintf(buffer, n, "%7.2f B/s ", rate);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
RichString_appendnAscii(str, processColor, buffer, len);
} else if (rate < ONE_M) {
- int len = snprintf(buffer, n, "%7.2f K/s ", rate / ONE_K);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K);
RichString_appendnAscii(str, processColor, buffer, len);
} else if (rate < ONE_G) {
- int len = snprintf(buffer, n, "%7.2f M/s ", rate / ONE_M);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M);
RichString_appendnAscii(str, processMegabytesColor, buffer, len);
} else if (rate < ONE_T) {
- int len = snprintf(buffer, n, "%7.2f G/s ", rate / ONE_G);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f G/s ", rate / ONE_G);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
+ } else if (rate < ONE_P) {
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f T/s ", rate / ONE_T);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
} else {
- int len = snprintf(buffer, n, "%7.2f T/s ", rate / ONE_T);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f P/s ", rate / ONE_P);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
}
}
void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) {
- int c = RichString_appendnWide(str, attr, content, MINIMUM(width, strlen(content)));
- RichString_appendChr(str, ' ', width + 1 - c);
+ int columns = width;
+ RichString_appendnWideColumns(str, attr, content, strlen(content), &columns);
+ RichString_appendChr(str, attr, ' ', width + 1 - columns);
+}
+
+void Process_printPercentage(float val, char* buffer, int n, int* attr) {
+ if (val >= 0) {
+ if (val < 99.9F) {
+ if (val < 0.05F) {
+ *attr = CRT_colors[PROCESS_SHADOW];
+ }
+ xSnprintf(buffer, n, "%4.1f ", val);
+ } else if (val < 999) {
+ *attr = CRT_colors[PROCESS_MEGABYTES];
+ xSnprintf(buffer, n, "%3d. ", (int)val);
+ } else {
+ *attr = CRT_colors[PROCESS_MEGABYTES];
+ xSnprintf(buffer, n, "%4d ", (int)val);
+ }
+ } else {
+ *attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, " N/A ");
+ }
}
void Process_writeField(const Process* this, RichString* str, ProcessField field) {
- char buffer[256]; buffer[255] = '\0';
+ char buffer[256];
+ size_t n = sizeof(buffer);
int attr = CRT_colors[DEFAULT_COLOR];
- int baseattr = CRT_colors[PROCESS_BASENAME];
- size_t n = sizeof(buffer) - 1;
bool coloring = this->settings->highlightMegabytes;
switch (field) {
- case PERCENT_CPU:
- case PERCENT_NORM_CPU: {
- float cpuPercentage = this->percent_cpu;
- if (field == PERCENT_NORM_CPU) {
- cpuPercentage /= this->processList->cpuCount;
- }
- if (cpuPercentage > 999.9) {
- xSnprintf(buffer, n, "%4u ", (unsigned int)cpuPercentage);
- } else if (cpuPercentage > 99.9) {
- xSnprintf(buffer, n, "%3u. ", (unsigned int)cpuPercentage);
- } else {
- xSnprintf(buffer, n, "%4.1f ", cpuPercentage);
- }
- break;
- }
- case PERCENT_MEM: {
- if (this->percent_mem > 99.9) {
- xSnprintf(buffer, n, "100. ");
- } else {
- xSnprintf(buffer, n, "%4.1f ", this->percent_mem);
- }
- break;
- }
case COMM: {
+ int baseattr = CRT_colors[PROCESS_BASENAME];
if (this->settings->highlightThreads && Process_isThread(this)) {
attr = CRT_colors[PROCESS_THREAD];
baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
@@ -288,84 +743,161 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
if (!this->settings->treeView || this->indent == 0) {
Process_writeCommand(this, attr, baseattr, str);
return;
- } else {
- char* buf = buffer;
- int maxIndent = 0;
- bool lastItem = (this->indent < 0);
- int indent = (this->indent < 0 ? -this->indent : this->indent);
-
- for (int i = 0; i < 32; i++) {
- if (indent & (1U << i)) {
- maxIndent = i+1;
- }
+ }
+
+ char* buf = buffer;
+ int maxIndent = 0;
+ bool lastItem = (this->indent < 0);
+ int indent = (this->indent < 0 ? -this->indent : this->indent);
+
+ for (int i = 0; i < 32; i++) {
+ if (indent & (1U << i)) {
+ maxIndent = i + 1;
}
+ }
- for (int i = 0; i < maxIndent - 1; i++) {
- int written, ret;
- if (indent & (1 << i)) {
- ret = snprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]);
- } else {
- ret = snprintf(buf, n, " ");
- }
- if (ret < 0 || (size_t)ret >= n) {
- written = n;
- } else {
- written = ret;
- }
- buf += written;
- n -= written;
+ for (int i = 0; i < maxIndent - 1; i++) {
+ int written, ret;
+ if (indent & (1 << i)) {
+ ret = xSnprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]);
+ } else {
+ ret = xSnprintf(buf, n, " ");
}
+ if (ret < 0 || (size_t)ret >= n) {
+ written = n;
+ } else {
+ written = ret;
+ }
+ buf += written;
+ n -= written;
+ }
- const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE];
- xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
- RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer);
- Process_writeCommand(this, attr, baseattr, str);
- return;
+ const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE];
+ xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
+ RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer);
+ Process_writeCommand(this, attr, baseattr, str);
+ return;
+ }
+ case PROC_COMM: {
+ const char* procComm;
+ if (this->procComm) {
+ attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
+ procComm = this->procComm;
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ procComm = Process_isKernelThread(this) ? kthreadID : "N/A";
+ }
+
+ Process_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1);
+ return;
+ }
+ case PROC_EXE: {
+ const char* procExe;
+ if (this->procExe) {
+ attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME];
+ if (this->settings->highlightDeletedExe) {
+ if (this->procExeDeleted)
+ attr = CRT_colors[FAILED_READ];
+ else if (this->usesDeletedLib)
+ attr = CRT_colors[PROCESS_TAG];
+ }
+ procExe = this->procExe + this->procExeBasenameOffset;
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ procExe = Process_isKernelThread(this) ? kthreadID : "N/A";
+ }
+
+ Process_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1);
+ return;
+ }
+ case CWD: {
+ const char* cwd;
+ if (!this->procCwd) {
+ attr = CRT_colors[PROCESS_SHADOW];
+ cwd = "N/A";
+ } else if (String_startsWith(this->procCwd, "/proc/") && strstr(this->procCwd, " (deleted)") != NULL) {
+ attr = CRT_colors[PROCESS_SHADOW];
+ cwd = "main thread terminated";
+ } else {
+ cwd = this->procCwd;
}
+ Process_printLeftAlignedField(str, attr, cwd, 25);
+ return;
}
- case MAJFLT: Process_colorNumber(str, this->majflt, coloring); return;
- case MINFLT: Process_colorNumber(str, this->minflt, coloring); return;
- case M_RESIDENT: Process_humanNumber(str, this->m_resident, coloring); return;
- case M_VIRT: Process_humanNumber(str, this->m_virt, coloring); return;
- case NICE: {
+ case ELAPSED: Process_printTime(str, /* convert to hundreds of a second */ this->processList->realtimeMs / 10 - 100 * this->starttime_ctime, 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;
+ case M_VIRT: Process_printKBytes(str, this->m_virt, coloring); return;
+ case NICE:
xSnprintf(buffer, n, "%3ld ", this->nice);
attr = this->nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]
: this->nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
- : attr;
+ : CRT_colors[PROCESS_SHADOW];
+ break;
+ case NLWP:
+ if (this->nlwp == 1)
+ attr = CRT_colors[PROCESS_SHADOW];
+
+ xSnprintf(buffer, n, "%4ld ", this->nlwp);
+ break;
+ case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, &attr); break;
+ case PERCENT_NORM_CPU: {
+ float cpuPercentage = this->percent_cpu / this->processList->activeCPUs;
+ Process_printPercentage(cpuPercentage, buffer, n, &attr);
break;
}
- case NLWP: xSnprintf(buffer, n, "%4ld ", this->nlwp); break;
+ case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, &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;
- case PRIORITY: {
- if(this->priority <= -100)
+ case PRIORITY:
+ if (this->priority <= -100)
xSnprintf(buffer, n, " RT ");
else
xSnprintf(buffer, n, "%3ld ", this->priority);
break;
- }
case PROCESSOR: xSnprintf(buffer, n, "%3d ", Settings_cpuId(this->settings, this->processor)); break;
case SESSION: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->session); break;
case STARTTIME: xSnprintf(buffer, n, "%s", this->starttime_show); break;
- case STATE: {
+ case STATE:
xSnprintf(buffer, n, "%c ", this->state);
- switch(this->state) {
- case 'R':
- attr = CRT_colors[PROCESS_R_STATE];
- break;
- case 'D':
- attr = CRT_colors[PROCESS_D_STATE];
- break;
+ switch (this->state) {
+#ifdef HTOP_NETBSD
+ case 'P':
+#else
+ case 'R':
+#endif
+ attr = CRT_colors[PROCESS_RUN_STATE];
+ break;
+ case 'D':
+ attr = CRT_colors[PROCESS_D_STATE];
+ break;
+ case 'I':
+ case 'S':
+ attr = CRT_colors[PROCESS_SHADOW];
+ break;
}
break;
- }
case ST_UID: xSnprintf(buffer, n, "%5d ", this->st_uid); break;
- case TIME: Process_printTime(str, this->time); return;
- case TGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tgid); break;
+ case TIME: Process_printTime(str, this->time, coloring); return;
+ case TGID:
+ if (this->tgid == this->pid)
+ attr = CRT_colors[PROCESS_SHADOW];
+
+ xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tgid);
+ break;
case TPGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tpgid); break;
- case TTY_NR: xSnprintf(buffer, n, "%3u:%3u ", major(this->tty_nr), minor(this->tty_nr)); break;
- case USER: {
+ case TTY:
+ if (!this->tty_name) {
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, "(no tty) ");
+ } else {
+ const char* name = String_startsWith(this->tty_name, "/dev/") ? (this->tty_name + strlen("/dev/")) : this->tty_name;
+ xSnprintf(buffer, n, "%-8s ", name);
+ }
+ break;
+ case USER:
if (Process_getuid != this->st_uid)
attr = CRT_colors[PROCESS_SHADOW];
@@ -376,17 +908,19 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
xSnprintf(buffer, n, "%-9d ", this->st_uid);
break;
- }
default:
+ if (DynamicColumn_writeField(this, str, field))
+ return;
+ assert(0 && "Process_writeField: default key reached"); /* should never be reached */
xSnprintf(buffer, n, "- ");
+ break;
}
- RichString_appendWide(str, attr, buffer);
+ RichString_appendAscii(str, attr, buffer);
}
void Process_display(const Object* cast, RichString* out) {
const Process* this = (const Process*) cast;
const ProcessField* fields = this->settings->fields;
- RichString_prune(out);
for (int i = 0; fields[i]; i++)
As_Process(this)->writeField(this, out, fields[i]);
@@ -406,16 +940,28 @@ void Process_display(const Object* cast, RichString* out) {
}
}
- assert(out->chlen > 0);
+ assert(RichString_size(out) > 0);
}
void Process_done(Process* this) {
assert (this != NULL);
- free(this->comm);
+ free(this->cmdline);
+ free(this->procComm);
+ free(this->procExe);
+ free(this->procCwd);
+ free(this->mergedCommand.str);
+ free(this->tty_name);
}
-static const char* Process_getCommandStr(const Process* p) {
- return p->comm ? p->comm : "";
+/* 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) {
+ if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !this->mergedCommand.str) {
+ return this->cmdline;
+ }
+
+ return this->mergedCommand.str;
}
const ProcessClass Process_class = {
@@ -429,13 +975,14 @@ const ProcessClass Process_class = {
.getCommandStr = Process_getCommandStr,
};
-void Process_init(Process* this, const struct Settings_* settings) {
+void Process_init(Process* this, const Settings* settings) {
this->settings = settings;
this->tag = false;
this->showChildren = true;
this->show = true;
this->updated = false;
- this->basenameOffset = -1;
+ this->cmdlineBasenameEnd = -1;
+ this->st_uid = (uid_t)-1;
if (Process_getuid == (uid_t)-1) {
Process_getuid = getuid();
@@ -448,21 +995,23 @@ void Process_toggleTag(Process* this) {
bool Process_isNew(const Process* this) {
assert(this->processList);
- if (this->processList->scanTs >= this->seenTs) {
- return this->processList->scanTs - this->seenTs <= 1000 * this->processList->settings->highlightDelaySecs;
+ if (this->processList->monotonicMs >= this->seenStampMs) {
+ return this->processList->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)this->processList->settings->highlightDelaySecs;
}
return false;
}
bool Process_isTomb(const Process* this) {
- return this->tombTs > 0;
+ return this->tombStampMs > 0;
}
bool Process_setPriority(Process* this, int priority) {
- CRT_dropPrivileges();
+ if (Settings_isReadonly())
+ return false;
+
int old_prio = getpriority(PRIO_PROCESS, this->pid);
int err = setpriority(PRIO_PROCESS, this->pid, priority);
- CRT_restorePrivileges();
+
if (err == 0 && old_prio != getpriority(PRIO_PROCESS, this->pid)) {
this->nice = priority;
}
@@ -474,10 +1023,7 @@ bool Process_changePriorityBy(Process* this, Arg delta) {
}
bool Process_sendSignal(Process* this, Arg sgn) {
- CRT_dropPrivileges();
- bool ok = (kill(this->pid, sgn.i) == 0);
- CRT_restorePrivileges();
- return ok;
+ return kill(this->pid, sgn.i) == 0;
}
int Process_pidCompare(const void* v1, const void* v2) {
@@ -488,16 +1034,10 @@ int Process_pidCompare(const void* v1, const void* v2) {
}
int Process_compare(const void* v1, const void* v2) {
- const Process *p1, *p2;
- const Settings *settings = ((const Process*)v1)->settings;
+ const Process* p1 = (const Process*)v1;
+ const Process* p2 = (const Process*)v2;
- if (Settings_getActiveDirection(settings) == 1) {
- p1 = (const Process*)v1;
- p2 = (const Process*)v2;
- } else {
- p2 = (const Process*)v1;
- p1 = (const Process*)v2;
- }
+ const Settings* settings = p1->settings;
ProcessField key = Settings_getActiveSortKey(settings);
@@ -505,9 +1045,47 @@ int Process_compare(const void* v1, const void* v2) {
// Implement tie-breaker (needed to make tree mode more stable)
if (!result)
- result = SPACESHIP_NUMBER(p1->pid, p2->pid);
+ return SPACESHIP_NUMBER(p1->pid, p2->pid);
- return result;
+ return (Settings_getActiveDirection(settings) == 1) ? result : -result;
+}
+
+static uint8_t stateCompareValue(char state) {
+ switch (state) {
+
+ case 'S':
+ return 10;
+
+ case 'I':
+ return 9;
+
+ case 'X':
+ return 8;
+
+ case 'Z':
+ return 7;
+
+ case 't':
+ return 6;
+
+ case 'T':
+ return 5;
+
+ case 'L':
+ return 4;
+
+ case 'D':
+ return 3;
+
+ case 'R':
+ return 2;
+
+ case '?':
+ return 1;
+
+ default:
+ return 0;
+ }
}
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) {
@@ -516,19 +1094,34 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
switch (key) {
case PERCENT_CPU:
case PERCENT_NORM_CPU:
- return SPACESHIP_NUMBER(p2->percent_cpu, p1->percent_cpu);
+ return SPACESHIP_NUMBER(p1->percent_cpu, p2->percent_cpu);
case PERCENT_MEM:
- return SPACESHIP_NUMBER(p2->m_resident, p1->m_resident);
+ return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
case COMM:
return SPACESHIP_NULLSTR(Process_getCommand(p1), Process_getCommand(p2));
+ case PROC_COMM: {
+ const char* comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : "");
+ const char* comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : "");
+ return SPACESHIP_NULLSTR(comm1, comm2);
+ }
+ case PROC_EXE: {
+ const char* exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : "");
+ const char* exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : "");
+ return SPACESHIP_NULLSTR(exe1, exe2);
+ }
+ case CWD:
+ return SPACESHIP_NULLSTR(p1->procCwd, p2->procCwd);
+ case ELAPSED:
+ r = -SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
+ return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid);
case MAJFLT:
- return SPACESHIP_NUMBER(p2->majflt, p1->majflt);
+ return SPACESHIP_NUMBER(p1->majflt, p2->majflt);
case MINFLT:
- return SPACESHIP_NUMBER(p2->minflt, p1->minflt);
+ return SPACESHIP_NUMBER(p1->minflt, p2->minflt);
case M_RESIDENT:
- return SPACESHIP_NUMBER(p2->m_resident, p1->m_resident);
+ return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
case M_VIRT:
- return SPACESHIP_NUMBER(p2->m_virt, p1->m_virt);
+ return SPACESHIP_NUMBER(p1->m_virt, p2->m_virt);
case NICE:
return SPACESHIP_NUMBER(p1->nice, p2->nice);
case NLWP:
@@ -549,20 +1142,93 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid);
case STATE:
- return SPACESHIP_NUMBER(Process_sortState(p1->state), Process_sortState(p2->state));
+ return SPACESHIP_NUMBER(stateCompareValue(p1->state), stateCompareValue(p2->state));
case ST_UID:
return SPACESHIP_NUMBER(p1->st_uid, p2->st_uid);
case TIME:
- return SPACESHIP_NUMBER(p2->time, p1->time);
+ return SPACESHIP_NUMBER(p1->time, p2->time);
case TGID:
return SPACESHIP_NUMBER(p1->tgid, p2->tgid);
case TPGID:
return SPACESHIP_NUMBER(p1->tpgid, p2->tpgid);
- case TTY_NR:
- return SPACESHIP_NUMBER(p1->tty_nr, p2->tty_nr);
+ case TTY:
+ /* Order no tty last */
+ return SPACESHIP_DEFAULTSTR(p1->tty_name, p2->tty_name, "\x7F");
case USER:
return SPACESHIP_NULLSTR(p1->user, p2->user);
default:
+ assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */
return SPACESHIP_NUMBER(p1->pid, p2->pid);
}
}
+
+void Process_updateComm(Process* this, const char* comm) {
+ if (!this->procComm && !comm)
+ return;
+
+ if (this->procComm && comm && String_eq(this->procComm, comm))
+ return;
+
+ free(this->procComm);
+ this->procComm = comm ? xStrdup(comm) : NULL;
+ this->mergedCommand.commChanged = true;
+}
+
+static int skipPotentialPath(const char* cmdline, int end) {
+ if (cmdline[0] != '/')
+ return 0;
+
+ int slash = 0;
+ for (int i = 1; i < end; i++) {
+ if (cmdline[i] == '/' && cmdline[i + 1] != '\0') {
+ slash = i + 1;
+ continue;
+ }
+
+ if (cmdline[i] == ' ' && cmdline[i - 1] != '\\')
+ return slash;
+
+ if (cmdline[i] == ':' && cmdline[i + 1] == ' ')
+ return slash;
+ }
+
+ return slash;
+}
+
+void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd) {
+ assert(basenameStart >= 0);
+ assert((cmdline && basenameStart < (int)strlen(cmdline)) || (!cmdline && basenameStart == 0));
+ assert((basenameEnd > basenameStart) || (basenameEnd == 0 && basenameStart == 0));
+ assert((cmdline && basenameEnd <= (int)strlen(cmdline)) || (!cmdline && basenameEnd == 0));
+
+ if (!this->cmdline && !cmdline)
+ return;
+
+ if (this->cmdline && cmdline && String_eq(this->cmdline, cmdline))
+ return;
+
+ free(this->cmdline);
+ this->cmdline = cmdline ? xStrdup(cmdline) : NULL;
+ this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);
+ this->cmdlineBasenameEnd = basenameEnd;
+ this->mergedCommand.cmdlineChanged = true;
+}
+
+void Process_updateExe(Process* this, const char* exe) {
+ if (!this->procExe && !exe)
+ return;
+
+ if (this->procExe && exe && String_eq(this->procExe, exe))
+ return;
+
+ free(this->procExe);
+ if (exe) {
+ this->procExe = xStrdup(exe);
+ const char* lastSlash = strrchr(exe, '/');
+ this->procExeBasenameOffset = (lastSlash && *(lastSlash + 1) != '\0' && lastSlash != exe) ? (lastSlash - exe + 1) : 0;
+ } else {
+ this->procExe = NULL;
+ this->procExeBasenameOffset = 0;
+ }
+ this->mergedCommand.exeChanged = true;
+}
diff --git a/Process.h b/Process.h
index 1a88a63..1ba36de 100644
--- a/Process.h
+++ b/Process.h
@@ -17,7 +17,9 @@ in the source distribution for its full text.
#include "RichString.h"
-#define PROCESS_FLAG_IO 0x0001
+#define PROCESS_FLAG_IO 0x00000001
+#define PROCESS_FLAG_CWD 0x00000002
+
#define DEFAULT_HIGHLIGHT_SECS 5
typedef enum ProcessField_ {
@@ -28,7 +30,7 @@ typedef enum ProcessField_ {
PPID = 4,
PGRP = 5,
SESSION = 6,
- TTY_NR = 7,
+ TTY = 7,
TPGID = 8,
MINFLT = 10,
MAJFLT = 12,
@@ -46,90 +48,240 @@ typedef enum ProcessField_ {
NLWP = 51,
TGID = 52,
PERCENT_NORM_CPU = 53,
+ ELAPSED = 54,
+ PROC_COMM = 124,
+ PROC_EXE = 125,
+ CWD = 126,
/* Platform specific fields, defined in ${platform}/ProcessField.h */
PLATFORM_PROCESS_FIELDS
+ /* Do not add new fields after this entry (dynamic entries follow) */
LAST_PROCESSFIELD
} ProcessField;
struct Settings_;
+/* Holds information about regions of the cmdline that should be
+ * highlighted (e.g. program basename, delimiter, comm). */
+typedef struct ProcessCmdlineHighlight_ {
+ size_t offset; /* first character to highlight */
+ size_t length; /* How many characters to highlight, zero if unused */
+ int attr; /* The attributes used to highlight */
+ int flags; /* Special flags used for selective highlighting, zero for always */
+} ProcessCmdlineHighlight;
+
+/* ProcessMergedCommand is populated by Process_makeCommandStr: It
+ * contains the merged Command string, and the information needed by
+ * Process_writeCommand to color the string. str will be NULL for kernel
+ * threads and zombies */
+typedef struct ProcessMergedCommand_ {
+ 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_ {
+ /* Super object for emulated OOP */
Object super;
+ /* Pointer to quasi-global data structures */
const struct ProcessList_* processList;
const struct Settings_* settings;
- unsigned long long int time;
+ /* Process identifier */
pid_t pid;
+
+ /* Parent process identifier */
pid_t ppid;
+
+ /* Thread group identifier */
pid_t tgid;
- char* comm; /* use Process_getCommand() for Command actually displayed */
- int commLen;
- int indent;
- int basenameOffset;
- bool updated;
+ /* Process group identifier */
+ int pgrp;
- char state;
- bool tag;
- bool showChildren;
- bool show;
- bool wasShown;
- unsigned int pgrp;
- unsigned int session;
- unsigned int tty_nr;
+ /* Session identifier */
+ int session;
+
+ /* Foreground group identifier of the controlling terminal */
int tpgid;
+
+ /* This is a kernel (helper) task */
+ bool isKernelThread;
+
+ /* This is a userland thread / LWP */
+ bool isUserlandThread;
+
+ /* Controlling terminal identifier of the process */
+ unsigned long int tty_nr;
+
+ /* Controlling terminal name of the process */
+ char* tty_name;
+
+ /* User identifier */
uid_t st_uid;
- unsigned long int flags;
+
+ /* User name */
+ const char* user;
+
+ /* Process runtime (in hundredth of a second) */
+ unsigned long long int time;
+
+ /*
+ * Process name including arguments.
+ * Use Process_getCommand() for Command actually displayed.
+ */
+ char* cmdline;
+
+ /* End Offset in cmdline of the process basename */
+ int cmdlineBasenameEnd;
+
+ /* Start Offset in cmdline of the process basename */
+ int cmdlineBasenameStart;
+
+ /* The process' "command" name */
+ char* procComm;
+
+ /* The main process executable */
+ char* procExe;
+
+ /* The process/thread working directory */
+ char* procCwd;
+
+ /* Offset in procExe of the process basename */
+ int procExeBasenameOffset;
+
+ /* Tells if the executable has been replaced in the filesystem since start */
+ bool procExeDeleted;
+
+ /* Tells if the process uses replaced shared libraries since start */
+ bool usesDeletedLib;
+
+ /* CPU number last executed on */
int processor;
+ /* CPU usage during last cycle (in percent) */
float percent_cpu;
+
+ /* Memory usage during last cycle (in percent) */
float percent_mem;
- const char* user;
+ /* Scheduling priority */
long int priority;
+
+ /* Nice value */
long int nice;
+
+ /* Number of threads in this process */
long int nlwp;
- char starttime_show[8];
+
+ /* Process start time (in seconds elapsed since the Epoch) */
time_t starttime_ctime;
- long m_virt;
- long m_resident;
+ /* Process start time (cached formatted string) */
+ char starttime_show[8];
- int exit_signal;
+ /* Total program size (in kilobytes) */
+ long m_virt;
- time_t seenTs;
- time_t tombTs;
+ /* Resident set size (in kilobytes) */
+ long m_resident;
+ /* Number of minor faults the process has made which have not required loading a memory page from disk */
unsigned long int minflt;
+
+ /* Number of major faults the process has made which have required loading a memory page from disk */
unsigned long int majflt;
+ /*
+ * Process state (platform dependent):
+ * D - Waiting
+ * I - Idle
+ * L - Acquiring lock
+ * R - Running
+ * S - Sleeping
+ * T - Stopped (on a signal)
+ * X - Dead
+ * Z - Zombie
+ * t - Tracing stop
+ * ? - Unknown
+ */
+ char state;
+
+ /* Whether the process was updated during the current scan */
+ bool updated;
+
+ /* Whether the process was tagged by the user */
+ bool tag;
+
+ /* Whether to display this process */
+ bool show;
+
+ /* Whether this process was shown last cycle */
+ bool wasShown;
+
+ /* Whether to show children of this process in tree-mode */
+ bool showChildren;
+
+ /*
+ * Internal time counts for showing new and exited processes.
+ */
+ uint64_t seenStampMs;
+ uint64_t tombStampMs;
+
+ /*
+ * Internal state for tree-mode.
+ */
+ int indent;
unsigned int tree_left;
unsigned int tree_right;
unsigned int tree_depth;
unsigned int tree_index;
+
+ /*
+ * Internal state for merged Command display
+ */
+ ProcessMergedCommand mergedCommand;
} Process;
typedef struct ProcessFieldData_ {
+ /* Name (displayed in setup menu) */
const char* name;
+
+ /* Title (display in main screen); must have same width as the printed values */
const char* title;
+
+ /* Description (displayed in setup menu) */
const char* description;
+
+ /* Scan flag to enable scan-method otherwise not run */
uint32_t flags;
+
+ /* Whether the values are process identifiers; adjusts the width of title and values if true */
bool pidColumn;
+
+ /* Whether the column should be sorted in descending order by default */
+ bool defaultSortDesc;
} ProcessFieldData;
// Implemented in platform-specific code:
void Process_writeField(const Process* this, RichString* str, ProcessField field);
int Process_compare(const void* v1, const void* v2);
void Process_delete(Object* cast);
-bool Process_isThread(const Process* this);
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
#define PROCESS_MAX_PID_DIGITS 19
extern int Process_pidDigits;
-typedef Process*(*Process_New)(const struct Settings_*);
+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*);
@@ -143,7 +295,7 @@ typedef struct 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_)) : ((const Process*)(this_))->comm)
+#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) {
@@ -154,33 +306,58 @@ static inline bool Process_isChildOf(const Process* this, pid_t pid) {
return pid == Process_getParentPid(this);
}
-#define Process_sortState(state) ((state) == 'I' ? 0x100 : (state))
+static inline bool Process_isKernelThread(const Process* this) {
+ return this->isKernelThread;
+}
+static inline bool Process_isUserlandThread(const Process* this) {
+ return this->isUserlandThread;
+}
+
+static inline bool Process_isThread(const Process* this) {
+ return Process_isUserlandThread(this) || Process_isKernelThread(this);
+}
+
+#define CMDLINE_HIGHLIGHT_FLAG_SEPARATOR 0x00000001
+#define CMDLINE_HIGHLIGHT_FLAG_BASENAME 0x00000002
+#define CMDLINE_HIGHLIGHT_FLAG_COMM 0x00000004
+#define CMDLINE_HIGHLIGHT_FLAG_DELETED 0x00000008
#define ONE_K 1024UL
#define ONE_M (ONE_K * ONE_K)
#define ONE_G (ONE_M * ONE_K)
#define ONE_T (1ULL * ONE_G * ONE_K)
+#define ONE_P (1ULL * ONE_T * ONE_K)
#define ONE_DECIMAL_K 1000UL
#define ONE_DECIMAL_M (ONE_DECIMAL_K * ONE_DECIMAL_K)
#define ONE_DECIMAL_G (ONE_DECIMAL_M * ONE_DECIMAL_K)
#define ONE_DECIMAL_T (1ULL * ONE_DECIMAL_G * ONE_DECIMAL_K)
+#define ONE_DECIMAL_P (1ULL * ONE_DECIMAL_T * ONE_DECIMAL_K)
void Process_setupColumnWidths(void);
-void Process_humanNumber(RichString* str, unsigned long long number, bool coloring);
+/* Takes number in bytes (base 1024). Prints 6 columns. */
+void Process_printBytes(RichString* str, unsigned long long number, bool coloring);
-void Process_colorNumber(RichString* str, unsigned long long number, bool coloring);
+/* Takes number in kilo bytes (base 1024). Prints 6 columns. */
+void Process_printKBytes(RichString* str, unsigned long long number, bool coloring);
-void Process_printTime(RichString* str, unsigned long long totalHundredths);
+/* Takes number as count (base 1000). Prints 12 columns. */
+void Process_printCount(RichString* str, unsigned long long number, bool coloring);
-void Process_fillStarttimeBuffer(Process* this);
+/* Takes time in hundredths of a seconds. Prints 9 columns. */
+void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring);
-void Process_outputRate(RichString* str, char* buffer, size_t n, double rate, int coloring);
+/* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */
+void Process_printRate(RichString* str, double rate, bool coloring);
+
+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_display(const Object* cast, RichString* out);
void Process_done(Process* this);
@@ -205,4 +382,17 @@ int Process_pidCompare(const void* v1, const void* v2);
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key);
+// Avoid direct calls, use Process_getCommand instead
+const char* Process_getCommandStr(const Process* this);
+
+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 */
+void Process_makeCommandStr(Process* this);
+
+void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str);
+
#endif
diff --git a/ProcessList.c b/ProcessList.c
index 055ad1f..daf871a 100644
--- a/ProcessList.c
+++ b/ProcessList.c
@@ -11,15 +11,16 @@ in the source distribution for its full text.
#include <stdlib.h>
#include <string.h>
-#include "Compat.h"
#include "CRT.h"
+#include "DynamicColumn.h"
#include "Hashtable.h"
#include "Macros.h"
+#include "Platform.h"
#include "Vector.h"
#include "XUtils.h"
-ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+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
@@ -29,13 +30,18 @@ ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, Users
this->usersTable = usersTable;
this->pidMatchList = pidMatchList;
+ this->dynamicMeters = dynamicMeters;
+ this->dynamicColumns = dynamicColumns;
this->userId = userId;
// set later by platform-specific code
- this->cpuCount = 0;
+ this->activeCPUs = 0;
+ this->existingCPUs = 0;
+ this->monotonicMs = 0;
- this->scanTs = 0;
+ // always maintain valid realtime timestamps
+ Platform_gettime_realtime(&this->realtime, &this->realtimeMs);
#ifdef HAVE_LIBHWLOC
this->topologyOk = false;
@@ -79,7 +85,22 @@ void ProcessList_setPanel(ProcessList* this, Panel* panel) {
this->panel = panel;
}
-static const char* alignedProcessFieldTitle(ProcessField field) {
+static const char* alignedDynamicColumnTitle(const ProcessList* this, int key) {
+ 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);
+ return titleBuffer;
+}
+
+static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessField field) {
+ if (field >= LAST_PROCESSFIELD)
+ return alignedDynamicColumnTitle(this, field);
+
const char* title = Process_fields[field].title;
if (!title)
return "- ";
@@ -93,8 +114,8 @@ static const char* alignedProcessFieldTitle(ProcessField field) {
return titleBuffer;
}
-void ProcessList_printHeader(ProcessList* this, RichString* header) {
- RichString_prune(header);
+void ProcessList_printHeader(const ProcessList* this, RichString* header) {
+ RichString_rewind(header, RichString_size(header));
const Settings* settings = this->settings;
const ProcessField* fields = settings->fields;
@@ -111,12 +132,12 @@ void ProcessList_printHeader(ProcessList* this, RichString* header) {
color = CRT_colors[PANEL_HEADER_FOCUS];
}
- RichString_appendWide(header, color, alignedProcessFieldTitle(fields[i]));
+ RichString_appendWide(header, color, alignedProcessFieldTitle(this, fields[i]));
if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') {
- header->chlen--; // rewind to override space
+ 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_DESC : TREE_STR_ASC],
+ CRT_treeStr[Settings_getActiveDirection(this->settings) == 1 ? TREE_STR_ASC : TREE_STR_DESC],
1);
}
if (COMM == fields[i] && settings->showMergedCommand) {
@@ -131,7 +152,7 @@ void ProcessList_add(ProcessList* this, Process* p) {
p->processList = this;
// highlighting processes found in first scan by first scan marked "far in the past"
- p->seenTs = this->scanTs;
+ p->seenStampMs = this->monotonicMs;
Vector_add(this->processes, p);
Hashtable_put(this->processTable, p->pid, p);
@@ -141,11 +162,11 @@ void ProcessList_add(ProcessList* this, Process* p) {
assert(Hashtable_count(this->processTable) == Vector_count(this->processes));
}
-void ProcessList_remove(ProcessList* this, Process* p) {
+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);
- Process* pp = Hashtable_remove(this->processTable, p->pid);
+ const Process* pp = Hashtable_remove(this->processTable, p->pid);
assert(pp == p); (void)pp;
pid_t pid = p->pid;
@@ -165,14 +186,6 @@ void ProcessList_remove(ProcessList* this, Process* p) {
assert(Hashtable_count(this->processTable) == Vector_count(this->processes));
}
-Process* ProcessList_get(ProcessList* this, int idx) {
- return (Process*)Vector_get(this->processes, idx);
-}
-
-int ProcessList_size(ProcessList* this) {
- return Vector_size(this->processes);
-}
-
// ProcessList_updateTreeSetLayer sorts this->displayTreeSet,
// relying only on itself.
//
@@ -202,7 +215,7 @@ static void ProcessList_updateTreeSetLayer(ProcessList* this, unsigned int leftB
if (layerSize == 0)
return;
- Vector* layer = Vector_new(this->processes->type, false, layerSize);
+ 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`).
@@ -311,6 +324,11 @@ static void ProcessList_updateTreeSet(ProcessList* this) {
}
static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, int direction, bool show, int* node_counter, int* node_index) {
+ // On OpenBSD the kernel thread 'swapper' has pid 0.
+ // Do not treat it as root of any tree.
+ if (pid == 0)
+ return;
+
Vector* children = Vector_new(Class(Process), false, DEFAULT_SIZE);
for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
@@ -361,15 +379,15 @@ static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level,
}
static int ProcessList_treeProcessCompare(const void* v1, const void* v2) {
- const Process *p1 = (const Process*)v1;
- const Process *p2 = (const Process*)v2;
+ const Process* p1 = (const Process*)v1;
+ const Process* p2 = (const Process*)v2;
return SPACESHIP_NUMBER(p1->tree_left, p2->tree_left);
}
static int ProcessList_treeProcessCompareByPID(const void* v1, const void* v2) {
- const Process *p1 = (const Process*)v1;
- const Process *p2 = (const Process*)v2;
+ const Process* p1 = (const Process*)v1;
+ const Process* p2 = (const Process*)v2;
return SPACESHIP_NUMBER(p1->pid, p2->pid);
}
@@ -477,7 +495,7 @@ ProcessField ProcessList_keyAt(const ProcessList* this, int at) {
const ProcessField* fields = this->settings->fields;
ProcessField field;
for (int i = 0; (field = fields[i]); i++) {
- int len = strlen(alignedProcessFieldTitle(field));
+ int len = strlen(alignedProcessFieldTitle(this, field));
if (at >= x && at <= x + len) {
return field;
}
@@ -494,17 +512,40 @@ void ProcessList_expandTree(ProcessList* this) {
}
}
+void ProcessList_collapseAllBranches(ProcessList* this) {
+ int size = Vector_size(this->processes);
+ for (int i = 0; i < size; i++) {
+ Process* process = (Process*) Vector_get(this->processes, i);
+ // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1
+ if (process->tree_depth > 0 && process->pid > 1)
+ process->showChildren = false;
+ }
+}
+
void ProcessList_rebuildPanel(ProcessList* this) {
const char* incFilter = this->incFilter;
- int currPos = Panel_getSelectedIndex(this->panel);
- int currScrollV = this->panel->scrollV;
+ const int currPos = Panel_getSelectedIndex(this->panel);
+ const int currScrollV = this->panel->scrollV;
+ const int currSize = Panel_size(this->panel);
Panel_prune(this->panel);
- int size = ProcessList_size(this);
+
+ /* Follow main process if followed a userland thread and threads are now hidden */
+ const Settings* settings = this->settings;
+ if (this->following != -1 && settings->hideUserlandThreads) {
+ const Process* followedProcess = (const Process*) Hashtable_get(this->processTable, this->following);
+ if (followedProcess && Process_isThread(followedProcess) && Hashtable_get(this->processTable, followedProcess->tgid) != NULL) {
+ this->following = followedProcess->tgid;
+ }
+ }
+
+ const int processCount = Vector_size(this->processes);
int idx = 0;
- for (int i = 0; i < size; i++) {
- Process* p = ProcessList_get(this, i);
+ bool foundFollowed = false;
+
+ for (int i = 0; i < processCount; i++) {
+ Process* p = (Process*) Vector_get(this->processes, i);
if ( (!p->show)
|| (this->userId != (uid_t) -1 && (p->st_uid != this->userId))
@@ -513,31 +554,47 @@ void ProcessList_rebuildPanel(ProcessList* this) {
continue;
Panel_set(this->panel, idx, (Object*)p);
- if ((this->following == -1 && idx == currPos) || (this->following != -1 && p->pid == this->following)) {
+
+ if (this->following != -1 && p->pid == this->following) {
+ foundFollowed = true;
Panel_setSelected(this->panel, idx);
this->panel->scrollV = currScrollV;
}
idx++;
}
+
+ if (this->following != -1 && !foundFollowed) {
+ /* Reset if current followed pid not found */
+ this->following = -1;
+ Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS);
+ }
+
+ if (this->following == -1) {
+ /* If the last item was selected, keep the new last item selected */
+ if (currPos > 0 && currPos == currSize - 1)
+ Panel_setSelected(this->panel, Panel_size(this->panel) - 1);
+ else
+ Panel_setSelected(this->panel, currPos);
+
+ this->panel->scrollV = currScrollV;
+ }
}
Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process_New constructor) {
Process* proc = (Process*) Hashtable_get(this->processTable, pid);
- *preExisting = proc;
+ *preExisting = proc != NULL;
if (proc) {
assert(Vector_indexOf(this->processes, proc, Process_pidCompare) != -1);
assert(proc->pid == pid);
} else {
proc = constructor(this->settings);
- assert(proc->comm == NULL);
+ assert(proc->cmdline == NULL);
proc->pid = pid;
}
return proc;
}
void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
- struct timespec now;
-
// in pause mode only gather global data for meters (CPU/memory/...)
if (pauseProcessUpdate) {
ProcessList_goThroughEntries(this, true);
@@ -558,37 +615,35 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
this->runningTasks = 0;
- // set scanTs
+ // set scan timestamp
static bool firstScanDone = false;
- if (!firstScanDone) {
- this->scanTs = 0;
+ if (firstScanDone) {
+ Platform_gettime_monotonic(&this->monotonicMs);
+ } else {
+ this->monotonicMs = 0;
firstScanDone = true;
- } else if (Compat_clock_monotonic_gettime(&now) == 0) {
- // save time in millisecond, so with a delay in deciseconds
- // there are no irregularities
- this->scanTs = 1000 * now.tv_sec + now.tv_nsec / 1000000;
}
ProcessList_goThroughEntries(this, false);
for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
Process* p = (Process*) Vector_get(this->processes, i);
- if (p->tombTs > 0) {
+ Process_makeCommandStr(p);
+
+ if (p->tombStampMs > 0) {
// remove tombed process
- if (this->scanTs >= p->tombTs) {
+ if (this->monotonicMs >= p->tombStampMs) {
ProcessList_remove(this, p);
}
} else if (p->updated == false) {
// process no longer exists
if (this->settings->highlightChanges && p->wasShown) {
// mark tombed
- p->tombTs = this->scanTs + 1000 * this->settings->highlightDelaySecs;
+ p->tombStampMs = this->monotonicMs + 1000 * this->settings->highlightDelaySecs;
} else {
// immediately remove
ProcessList_remove(this, p);
}
- } else {
- p->updated = false;
}
}
diff --git a/ProcessList.h b/ProcessList.h
index b7ae400..7cd2fab 100644
--- a/ProcessList.h
+++ b/ProcessList.h
@@ -9,7 +9,10 @@ in the source distribution for its full text.
#include "config.h" // IWYU pragma: keep
+#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
+#include <sys/time.h>
#include <sys/types.h>
#include "Hashtable.h"
@@ -34,6 +37,9 @@ in the source distribution for its full text.
#define MAX_READ 2048
#endif
+typedef unsigned long long int memory_t;
+#define MEMORY_MAX ULLONG_MAX
+
typedef struct ProcessList_ {
const Settings* settings;
@@ -45,6 +51,13 @@ typedef struct ProcessList_ {
Hashtable* displayTreeSet;
Hashtable* draftingTreeSet;
+ Hashtable* dynamicMeters; /* runtime-discovered meters */
+ Hashtable* dynamicColumns; /* runtime-discovered Columns */
+
+ struct timeval realtime; /* time of the current sample */
+ uint64_t realtimeMs; /* current time in milliseconds */
+ uint64_t monotonicMs; /* same, but from monotonic clock */
+
Panel* panel;
int following;
uid_t userId;
@@ -56,44 +69,44 @@ typedef struct ProcessList_ {
bool topologyOk;
#endif
- int totalTasks;
- int runningTasks;
- int userlandThreads;
- int kernelThreads;
+ unsigned int totalTasks;
+ unsigned int runningTasks;
+ unsigned int userlandThreads;
+ unsigned int kernelThreads;
- unsigned long long int totalMem;
- unsigned long long int usedMem;
- unsigned long long int buffersMem;
- unsigned long long int cachedMem;
- unsigned long long int totalSwap;
- unsigned long long int usedSwap;
- unsigned long long int freeSwap;
+ memory_t totalMem;
+ memory_t usedMem;
+ memory_t buffersMem;
+ memory_t cachedMem;
+ memory_t sharedMem;
+ memory_t availableMem;
- int cpuCount;
+ memory_t totalSwap;
+ memory_t usedSwap;
+ memory_t cachedSwap;
- time_t scanTs;
+ unsigned int activeCPUs;
+ unsigned int existingCPUs;
} ProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+/* Implemented by platforms */
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* pl);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
-ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_done(ProcessList* this);
void ProcessList_setPanel(ProcessList* this, Panel* panel);
-void ProcessList_printHeader(ProcessList* this, RichString* header);
+void ProcessList_printHeader(const ProcessList* this, RichString* header);
void ProcessList_add(ProcessList* this, Process* p);
-void ProcessList_remove(ProcessList* this, Process* p);
-
-Process* ProcessList_get(ProcessList* this, int idx);
-
-int ProcessList_size(ProcessList* this);
+void ProcessList_remove(ProcessList* this, const Process* p);
void ProcessList_sort(ProcessList* this);
@@ -101,10 +114,16 @@ ProcessField ProcessList_keyAt(const ProcessList* this, int at);
void ProcessList_expandTree(ProcessList* this);
+void ProcessList_collapseAllBranches(ProcessList* this);
+
void ProcessList_rebuildPanel(ProcessList* this);
Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process_New constructor);
void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate);
+static inline Process* ProcessList_findProcess(ProcessList* this, pid_t pid) {
+ return (Process*) Hashtable_get(this->processTable, pid);
+}
+
#endif
diff --git a/ProvideCurses.h b/ProvideCurses.h
index c35d696..4eb08a2 100644
--- a/ProvideCurses.h
+++ b/ProvideCurses.h
@@ -12,7 +12,7 @@ in the source distribution for its full text.
// IWYU pragma: begin_exports
-#ifdef HAVE_NCURSESW_CURSES_H
+#if defined(HAVE_NCURSESW_CURSES_H)
#include <ncursesw/curses.h>
#elif defined(HAVE_NCURSES_NCURSES_H)
#include <ncurses/ncurses.h>
diff --git a/README b/README
index 880597d..c797f56 100644
--- a/README
+++ b/README
@@ -1,11 +1,12 @@
-# [![htop](htop.png)](https://htop.dev)
+# [![htop logo](htop.png)](https://htop.dev)
[![CI](https://github.com/htop-dev/htop/workflows/CI/badge.svg)](https://github.com/htop-dev/htop/actions)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/21665/badge.svg)](https://scan.coverity.com/projects/21665)
[![Mailing List](https://img.shields.io/badge/Mailing%20List-htop-blue.svg)](https://groups.io/g/htop)
-[![IRC #htop](https://img.shields.io/badge/IRC-htop-blue.svg)](https://webchat.freenode.net/#htop)
-[![Github Release](https://img.shields.io/github/release/htop-dev/htop.svg)](https://github.com/htop-dev/htop/releases/latest)
-[![Download](https://api.bintray.com/packages/htop/source/htop/images/download.svg)](https://bintray.com/htop/source/htop/_latestVersion)
+[![IRC #htop](https://img.shields.io/badge/IRC-htop-blue.svg)](https://web.libera.chat/#htop)
+[![GitHub Release](https://img.shields.io/github/release/htop-dev/htop.svg)](https://github.com/htop-dev/htop/releases/latest)
+[![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)
@@ -14,54 +15,160 @@
`htop` is a cross-platform interactive process viewer.
`htop` allows scrolling the list of processes vertically and horizontally to see their full command lines and related information like memory and CPU consumption.
+Also system wide information, like load average or swap usage, is shown.
The information displayed is configurable through a graphical setup and can be sorted and filtered interactively.
Tasks related to processes (e.g. killing and renicing) can be done without entering their PIDs.
-Running `htop` requires `ncurses` libraries (typically named libncursesw*).
+Running `htop` requires `ncurses` libraries, typically named libncurses(w).
-For more information and details on how to contribute to `htop` visit [htop.dev](https://htop.dev).
+`htop` is written in C.
-## Build instructions
-
-This program is distributed as a standard GNU autotools-based package.
+For more information and details visit [htop.dev](https://htop.dev).
-Compiling `htop` requires the header files for `ncurses` (libncursesw*-dev). Install these and other required packages for C development from your package manager.
-
-Then, when compiling from a [release tarball](https://bintray.com/htop/source/htop), run:
+## Build instructions
+### Prerequisite
+List of build-time dependencies:
+ * standard GNU autotools-based C toolchain
+ - C99 compliant compiler
+ - `autoconf`
+ - `autotools`
+ * `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.
+>
+> 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`
+* `libnl-3`
+
+Install these and other required packages for C development from your package manager.
+
+**Debian/Ubuntu**
~~~ shell
-./configure && make
+sudo apt install libncursesw5-dev autotools-dev autoconf build-essential
~~~
-Alternatively, for compiling sources downloaded from the Git repository (`git clone` or downloads from [Github releases](https://github.com/htop-dev/htop/releases/)),
-install the header files for `ncurses` (libncursesw*-dev) and other required development packages from your distribution's package manager. Then run:
+**Fedora/RHEL**
+~~~ shell
+sudo dnf install ncurses-devel 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
~~~
-By default `make install` will install into `/usr/local`, for changing the path use `./configure --prefix=/some/path`.
-
-See the manual page (`man htop`) or the on-line help ('F1' or 'h' inside `htop`) for a list of supported key commands.
+### 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
+
+`htop` has several build-time options to enable/disable additional features.
+
+#### Generic
+
+ * `--enable-unicode`:
+ enable Unicode support
+ - dependency: *libncursesw*
+ - default: *yes*
+ * `--enable-affinity`:
+ enable `sched_setaffinity(2)` and `sched_getaffinity(2)` for affinity support; conflicts with hwloc
+ - default: *check*
+ * `--enable-hwloc`:
+ enable hwloc support for CPU affinity; disables affinity support
+ - dependency: *libhwloc*
+ - default: *no*
+ * `--enable-static`:
+ build a static htop binary; hwloc and delay accounting are not supported
+ - default: *no*
+ * `--enable-debug`:
+ Enable asserts and internal sanity checks; implies a performance penalty
+ - default: *no*
+
+#### Performance Co-Pilot
+
+ * `--enable-pcp`:
+ enable Performance Co-Pilot support via a new pcp-htop utility
+ - dependency: *libpcp*
+ - default: *no*
+
+#### Linux
+
+ * `--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 Linux capabilities support
+ - dependency: *libcap*
+ - default: *check*
+ * `--with-proc`:
+ location of a Linux-compatible proc filesystem
+ - default: */proc*
+ * `--enable-openvz`:
+ enable OpenVZ support
+ - default: *no*
+ * `--enable-vserver`:
+ enable VServer support
+ - default: *no*
+ * `--enable-ancient-vserver`:
+ enable ancient VServer support (implies `--enable-vserver`)
+ - default: *no*
+ * `--enable-delayacct`:
+ enable Linux delay accounting support
+ - dependencies: *pkg-config*(build-time), *libnl-3* and *libnl-genl-3*
+ - default: *check*
+
+
+## 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:
+`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.
+
+`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
-If you have trouble running `htop` please consult your Operating System / Linux distribution documentation for getting support and filing bugs.
+If you have trouble running `htop` please consult your operating system / Linux distribution documentation for getting support and filing bugs.
## Bugs, development feedback
-We have a [development mailing list](https://htop.dev/mailinglist.html). Feel free to subscribe for release announcements or asking questions on the development of htop.
+We have a [development mailing list](https://htop.dev/mailinglist.html). Feel free to subscribe for release announcements or asking questions on the development of `htop`.
-You can also join our IRC channel #htop on freenode and talk to the developers there.
+You can also join our IRC channel [#htop on Libera.Chat](https://web.libera.chat/#htop) and talk to the developers there.
-If you have found an issue with the source of htop, please check whether this has already been reported in our [Github issue tracker](https://github.com/htop-dev/htop/issues).
-If not, please file a new issue describing the problem you have found, the location in the source code you are referring to and a possible fix.
+If you have found an issue within the source of `htop`, please check whether this has already been reported in our [GitHub issue tracker](https://github.com/htop-dev/htop/issues).
+If not, please file a new issue describing the problem you have found, the potential location in the source code you are referring to and a possible fix if available.
## History
-`htop` was invented, developed and maintained by Hisham Muhammad from 2004 to 2019. His [legacy repository](https://github.com/hishamhm/htop/) has been archived to preserve the history.
+`htop` was invented, developed and maintained by [Hisham Muhammad](https://hisham.hm/) from 2004 to 2019. His [legacy repository](https://github.com/hishamhm/htop/) has been archived to preserve the history.
In 2020 a [team](https://github.com/orgs/htop-dev/people) took over the development amicably and continues to maintain `htop` collaboratively.
diff --git a/RichString.c b/RichString.c
index 0130580..558c743 100644
--- a/RichString.c
+++ b/RichString.c
@@ -46,6 +46,10 @@ static void RichString_setLen(RichString* this, int len) {
}
}
+void RichString_rewind(RichString* this, int count) {
+ RichString_setLen(this, this->chlen - count);
+}
+
#ifdef HAVE_LIBNCURSESW
static inline int RichString_writeFromWide(RichString* this, int attrs, const char* data_c, int from, int len) {
@@ -60,7 +64,37 @@ static inline int RichString_writeFromWide(RichString* this, int attrs, const ch
this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (iswprint(data[j]) ? data[j] : '?') } };
}
- return wcswidth(data, len);
+ return len;
+}
+
+int RichString_appendnWideColumns(RichString* this, int attrs, const char* data_c, int len, int* columns) {
+ wchar_t data[len + 1];
+ len = mbstowcs(data, data_c, len);
+ if (len <= 0)
+ return 0;
+
+ int from = this->chlen;
+ int newLen = from + len;
+ RichString_setLen(this, newLen);
+ int columnsWritten = 0;
+ int pos = from;
+ for (int j = 0; j < len; j++) {
+ wchar_t c = iswprint(data[j]) ? data[j] : '?';
+ int cwidth = wcwidth(c);
+ if (cwidth > *columns)
+ break;
+
+ *columns -= cwidth;
+ columnsWritten += cwidth;
+
+ this->chptr[pos] = (CharType) { .attr = attrs & 0xffffff, .chars = { c, '\0' } };
+ pos++;
+ }
+
+ RichString_setLen(this, pos);
+ *columns = columnsWritten;
+
+ return pos - from;
}
static inline int RichString_writeFromAscii(RichString* this, int attrs, const char* data, int from, int len) {
@@ -80,9 +114,18 @@ inline void RichString_setAttrn(RichString* this, int attrs, int start, int char
}
}
-int RichString_findChar(RichString* this, char c, int start) {
- wchar_t wc = btowc(c);
- cchar_t* ch = this->chptr + start;
+void RichString_appendChr(RichString* this, int attrs, char c, int count) {
+ int from = this->chlen;
+ int newLen = from + count;
+ RichString_setLen(this, newLen);
+ for (int i = from; i < newLen; i++) {
+ this->chptr[i] = (CharType) { .attr = attrs, .chars = { c, 0 } };
+ }
+}
+
+int RichString_findChar(const RichString* this, char c, int start) {
+ const wchar_t wc = btowc(c);
+ const cchar_t* ch = this->chptr + start;
for (int i = start; i < this->chlen; i++) {
if (ch->chars[0] == wc)
return i;
@@ -104,6 +147,12 @@ static inline int RichString_writeFromWide(RichString* this, int attrs, const ch
return len;
}
+int RichString_appendnWideColumns(RichString* this, int attrs, const char* data_c, int len, int* columns) {
+ int written = RichString_writeFromWide(this, attrs, data_c, this->chlen, MINIMUM(len, *columns));
+ *columns = written;
+ return written;
+}
+
static inline int RichString_writeFromAscii(RichString* this, int attrs, const char* data_c, int from, int len) {
return RichString_writeFromWide(this, attrs, data_c, from, len);
}
@@ -115,8 +164,17 @@ void RichString_setAttrn(RichString* this, int attrs, int start, int charcount)
}
}
-int RichString_findChar(RichString* this, char c, int start) {
- chtype* ch = this->chptr + start;
+void RichString_appendChr(RichString* this, int attrs, char c, int count) {
+ int from = this->chlen;
+ int newLen = from + count;
+ RichString_setLen(this, newLen);
+ for (int i = from; i < newLen; i++) {
+ this->chptr[i] = c | attrs;
+ }
+}
+
+int RichString_findChar(const RichString* this, char c, int start) {
+ const chtype* ch = this->chptr + start;
for (int i = start; i < this->chlen; i++) {
if ((*ch & 0xff) == (chtype) c)
return i;
@@ -127,19 +185,10 @@ int RichString_findChar(RichString* this, char c, int start) {
#endif /* HAVE_LIBNCURSESW */
-void RichString_prune(RichString* this) {
- if (this->chlen > RICHSTRING_MAXLEN)
+void RichString_delete(RichString* this) {
+ if (this->chlen > RICHSTRING_MAXLEN) {
free(this->chptr);
- memset(this, 0, sizeof(RichString));
- this->chptr = this->chstr;
-}
-
-void RichString_appendChr(RichString* this, char c, int count) {
- int from = this->chlen;
- int newLen = from + count;
- RichString_setLen(this, newLen);
- for (int i = from; i < newLen; i++) {
- RichString_setChar(this, i, c);
+ this->chptr = this->chstr;
}
}
diff --git a/RichString.h b/RichString.h
index 4145b0d..5c456d2 100644
--- a/RichString.h
+++ b/RichString.h
@@ -15,20 +15,25 @@ in the source distribution for its full text.
#define RichString_size(this) ((this)->chlen)
#define RichString_sizeVal(this) ((this).chlen)
-#define RichString_begin(this) RichString (this); RichString_beginAllocated(this)
-#define RichString_beginAllocated(this) do { memset(&(this), 0, sizeof(RichString)); (this).chptr = (this).chstr; } while(0)
-#define RichString_end(this) RichString_prune(&(this))
+#define RichString_begin(this) RichString this; RichString_beginAllocated(this)
+#define RichString_beginAllocated(this) \
+ do { \
+ (this).chlen = 0, \
+ (this).chptr = (this).chstr; \
+ RichString_setChar(&(this), 0, 0); \
+ (this).highlightAttr = 0; \
+ } while(0)
#ifdef HAVE_LIBNCURSESW
#define RichString_printVal(this, y, x) mvadd_wchstr(y, x, (this).chptr)
#define RichString_printoffnVal(this, y, x, off, n) mvadd_wchnstr(y, x, (this).chptr + (off), n)
-#define RichString_getCharVal(this, i) ((this).chptr[i].chars[0] & 255)
+#define RichString_getCharVal(this, i) ((this).chptr[i].chars[0])
#define RichString_setChar(this, at, ch) do { (this)->chptr[(at)] = (CharType) { .chars = { ch, 0 } }; } while (0)
#define CharType cchar_t
#else
#define RichString_printVal(this, y, x) mvaddchstr(y, x, (this).chptr)
#define RichString_printoffnVal(this, y, x, off, n) mvaddchnstr(y, x, (this).chptr + (off), n)
-#define RichString_getCharVal(this, i) ((this).chptr[i])
+#define RichString_getCharVal(this, i) ((this).chptr[i] & 0xff)
#define RichString_setChar(this, at, ch) do { (this)->chptr[(at)] = ch; } while (0)
#define CharType chtype
#endif
@@ -42,20 +47,27 @@ typedef struct RichString_ {
int highlightAttr;
} RichString;
-void RichString_setAttrn(RichString* this, int attrs, int start, int charcount);
+void RichString_delete(RichString* this);
+
+void RichString_rewind(RichString* this, int count);
-int RichString_findChar(RichString* this, char c, int start);
+void RichString_setAttrn(RichString* this, int attrs, int start, int charcount);
-void RichString_prune(RichString* this);
+int RichString_findChar(const RichString* this, char c, int start);
void RichString_setAttr(RichString* this, int attrs);
-void RichString_appendChr(RichString* this, char c, int count);
+void RichString_appendChr(RichString* this, int attrs, char c, int count);
+
+/* All appending and writing functions return the number of written characters (not columns). */
int RichString_appendWide(RichString* this, int attrs, const char* data);
int RichString_appendnWide(RichString* this, int attrs, const char* data, int len);
+/* columns takes the maximum number of columns to write and contains on return the number of columns written. */
+int RichString_appendnWideColumns(RichString* this, int attrs, const char* data, int len, int* columns);
+
int RichString_writeWide(RichString* this, int attrs, const char* data);
int RichString_appendAscii(RichString* this, int attrs, const char* data);
diff --git a/ScreenManager.c b/ScreenManager.c
index 0ab5231..914c510 100644
--- a/ScreenManager.c
+++ b/ScreenManager.c
@@ -5,6 +5,8 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "ScreenManager.h"
#include <assert.h>
@@ -15,6 +17,7 @@ in the source distribution for its full text.
#include "CRT.h"
#include "FunctionBar.h"
#include "Object.h"
+#include "Platform.h"
#include "ProcessList.h"
#include "ProvideCurses.h"
#include "XUtils.h"
@@ -24,7 +27,7 @@ ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const
ScreenManager* this;
this = xMalloc(sizeof(ScreenManager));
this->x1 = 0;
- this->y1 = header->height;
+ this->y1 = 0;
this->x2 = 0;
this->y2 = -1;
this->panels = Vector_new(Class(Panel), owner, DEFAULT_SIZE);
@@ -32,7 +35,6 @@ ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const
this->header = header;
this->settings = settings;
this->state = state;
- this->owner = owner;
this->allowFocusChange = true;
return this;
}
@@ -42,23 +44,23 @@ void ScreenManager_delete(ScreenManager* this) {
free(this);
}
-inline int ScreenManager_size(ScreenManager* this) {
+inline int ScreenManager_size(const ScreenManager* this) {
return this->panelCount;
}
void ScreenManager_add(ScreenManager* this, Panel* item, int size) {
int lastX = 0;
if (this->panelCount > 0) {
- Panel* last = (Panel*) Vector_get(this->panels, this->panelCount - 1);
+ const Panel* last = (const Panel*) Vector_get(this->panels, this->panelCount - 1);
lastX = last->x + last->w + 1;
}
- int height = LINES - this->y1 + this->y2;
+ 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);
}
- Panel_move(item, lastX, this->y1);
+ Panel_move(item, lastX, this->y1 + (this->header ? this->header->height : 0));
Vector_add(this->panels, item);
item->needsRedraw = true;
this->panelCount++;
@@ -71,30 +73,26 @@ Panel* ScreenManager_remove(ScreenManager* this, int idx) {
return panel;
}
-void ScreenManager_resize(ScreenManager* this, int x1, int y1, int x2, int y2) {
- this->x1 = x1;
- this->y1 = y1;
- this->x2 = x2;
- this->y2 = y2;
+void ScreenManager_resize(ScreenManager* this) {
+ int y1_header = this->y1 + (this->header ? this->header->height : 0);
int panels = this->panelCount;
int lastX = 0;
for (int i = 0; i < panels - 1; i++) {
Panel* panel = (Panel*) Vector_get(this->panels, i);
- Panel_resize(panel, panel->w, LINES - y1 + y2);
- Panel_move(panel, lastX, y1);
+ Panel_resize(panel, panel->w, LINES - y1_header + this->y2);
+ Panel_move(panel, lastX, y1_header);
lastX = panel->x + panel->w + 1;
}
Panel* panel = (Panel*) Vector_get(this->panels, panels - 1);
- Panel_resize(panel, COLS - x1 + x2 - lastX, LINES - y1 + y2);
- Panel_move(panel, lastX, y1);
+ Panel_resize(panel, COLS - this->x1 + this->x2 - lastX, LINES - y1_header + this->y2);
+ Panel_move(panel, lastX, y1_header);
}
static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut) {
ProcessList* pl = this->header->pl;
- struct timeval tv;
- gettimeofday(&tv, NULL);
- double newTime = ((double)tv.tv_sec * 10) + ((double)tv.tv_usec / 100000);
+ Platform_gettime_realtime(&pl->realtime, &pl->realtimeMs);
+ double newTime = ((double)pl->realtime.tv_sec * 10) + ((double)pl->realtime.tv_usec / 100000);
*timedOut = (newTime - *oldTime > this->settings->delay);
*rescan |= *timedOut;
@@ -105,7 +103,10 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi
if (*rescan) {
*oldTime = newTime;
+ // 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;
@@ -123,7 +124,11 @@ static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_
const int nPanels = this->panelCount;
for (int i = 0; i < nPanels; i++) {
Panel* panel = (Panel*) Vector_get(this->panels, i);
- Panel_draw(panel, force_redraw, i == focus, !((panel == this->state->panel) && this->state->hideProcessSelection), State_hideFunctionBar(this->state));
+ Panel_draw(panel,
+ force_redraw,
+ i == focus,
+ panel != (Panel*)this->state->mainPanel || !this->state->hideProcessSelection,
+ State_hideFunctionBar(this->state));
mvvline(panel->y, panel->x + panel->w, ' ', panel->h + (State_hideFunctionBar(this->state) ? 1 : 0));
}
}
@@ -157,10 +162,13 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
}
int prevCh = ch;
+#ifdef HAVE_SET_ESCDELAY
set_escdelay(25);
+#endif
ch = getch();
HandlerResult result = IGNORED;
+#ifdef HAVE_GETMOUSE
if (ch == KEY_MOUSE && this->settings->enableMouse) {
ch = ERR;
MEVENT mevent;
@@ -181,7 +189,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
if (panel == panelFocus || this->allowFocusChange) {
focus = i;
panelFocus = panel;
- Object* oldSelection = Panel_getSelected(panel);
+ const Object* oldSelection = Panel_getSelected(panel);
Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV - 1);
if (Panel_getSelected(panel) == oldSelection) {
ch = KEY_RECLICK;
@@ -201,8 +209,10 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
}
}
}
+#endif
if (ch == ERR) {
- sortTimeout--;
+ if (sortTimeout > 0)
+ sortTimeout--;
if (prevCh == ch && !timedOut) {
closeTimeout++;
if (closeTimeout == 100) {
@@ -233,6 +243,10 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
if (result & REDRAW) {
force_redraw = true;
}
+ if (result & RESIZE) {
+ ScreenManager_resize(this);
+ force_redraw = true;
+ }
if (result & RESCAN) {
rescan = true;
sortTimeout = 0;
@@ -247,7 +261,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
switch (ch) {
case KEY_RESIZE:
{
- ScreenManager_resize(this, this->x1, this->y1, this->x2, this->y2);
+ ScreenManager_resize(this);
continue;
}
case KEY_LEFT:
diff --git a/ScreenManager.h b/ScreenManager.h
index 7dda4fc..bf55f17 100644
--- a/ScreenManager.h
+++ b/ScreenManager.h
@@ -26,7 +26,6 @@ typedef struct ScreenManager_ {
Header* header;
const Settings* settings;
const State* state;
- bool owner;
bool allowFocusChange;
} ScreenManager;
@@ -34,13 +33,13 @@ ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const
void ScreenManager_delete(ScreenManager* this);
-int ScreenManager_size(ScreenManager* this);
+int ScreenManager_size(const ScreenManager* this);
void ScreenManager_add(ScreenManager* this, Panel* item, int size);
Panel* ScreenManager_remove(ScreenManager* this, int idx);
-void ScreenManager_resize(ScreenManager* this, int x1, int y1, int x2, int y2);
+void ScreenManager_resize(ScreenManager* this);
void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey);
diff --git a/Settings.c b/Settings.c
index ef607f0..5ca998a 100644
--- a/Settings.c
+++ b/Settings.c
@@ -7,12 +7,17 @@ in the source distribution for its full text.
#include "Settings.h"
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "CRT.h"
+#include "DynamicColumn.h"
#include "Macros.h"
#include "Meter.h"
#include "Platform.h"
@@ -22,21 +27,27 @@ in the source distribution for its full text.
void Settings_delete(Settings* this) {
free(this->filename);
free(this->fields);
- for (unsigned int i = 0; i < ARRAYSIZE(this->columns); i++) {
- String_freeArray(this->columns[i].names);
- free(this->columns[i].modes);
+ for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
+ if (this->hColumns[i].names) {
+ for (uint8_t j = 0; j < this->hColumns[i].len; j++)
+ free(this->hColumns[i].names[j]);
+ free(this->hColumns[i].names);
+ }
+ free(this->hColumns[i].modes);
}
+ free(this->hColumns);
free(this);
}
-static void Settings_readMeters(Settings* this, char* line, int column) {
+static void Settings_readMeters(Settings* this, const char* line, unsigned int column) {
char* trim = String_trim(line);
char** ids = String_split(trim, ' ', NULL);
free(trim);
- this->columns[column].names = ids;
+ column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);
+ this->hColumns[column].names = ids;
}
-static void Settings_readMeterModes(Settings* this, char* line, int column) {
+static void Settings_readMeterModes(Settings* this, const char* line, unsigned int column) {
char* trim = String_trim(line);
char** ids = String_split(trim, ' ', NULL);
free(trim);
@@ -44,86 +55,121 @@ static void Settings_readMeterModes(Settings* this, char* line, int column) {
for (int i = 0; ids[i]; i++) {
len++;
}
- this->columns[column].len = len;
- int* modes = xCalloc(len, sizeof(int));
+ column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);
+ this->hColumns[column].len = len;
+ int* modes = len ? xCalloc(len, sizeof(int)) : NULL;
for (int i = 0; i < len; i++) {
modes[i] = atoi(ids[i]);
}
String_freeArray(ids);
- this->columns[column].modes = modes;
+ this->hColumns[column].modes = modes;
}
-static void Settings_defaultMeters(Settings* this, int initialCpuCount) {
+static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) {
int sizes[] = { 3, 3 };
- if (initialCpuCount > 4) {
+ if (initialCpuCount > 4 && initialCpuCount <= 128) {
sizes[1]++;
}
for (int i = 0; i < 2; i++) {
- this->columns[i].names = xCalloc(sizes[i] + 1, sizeof(char*));
- this->columns[i].modes = xCalloc(sizes[i], sizeof(int));
- this->columns[i].len = sizes[i];
+ this->hColumns[i].names = xCalloc(sizes[i] + 1, sizeof(char*));
+ this->hColumns[i].modes = xCalloc(sizes[i], sizeof(int));
+ this->hColumns[i].len = sizes[i];
}
int r = 0;
- if (initialCpuCount > 8) {
- this->columns[0].names[0] = xStrdup("LeftCPUs2");
- this->columns[0].modes[0] = BAR_METERMODE;
- this->columns[1].names[r] = xStrdup("RightCPUs2");
- this->columns[1].modes[r++] = BAR_METERMODE;
+
+ if (initialCpuCount > 128) {
+ // Just show the average, ricers need to config for impressive screenshots
+ this->hColumns[0].names[0] = xStrdup("CPU");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ } else if (initialCpuCount > 32) {
+ this->hColumns[0].names[0] = xStrdup("LeftCPUs8");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("RightCPUs8");
+ this->hColumns[1].modes[r++] = BAR_METERMODE;
+ } else if (initialCpuCount > 16) {
+ this->hColumns[0].names[0] = xStrdup("LeftCPUs4");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("RightCPUs4");
+ this->hColumns[1].modes[r++] = BAR_METERMODE;
+ } else if (initialCpuCount > 8) {
+ this->hColumns[0].names[0] = xStrdup("LeftCPUs2");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("RightCPUs2");
+ this->hColumns[1].modes[r++] = BAR_METERMODE;
} else if (initialCpuCount > 4) {
- this->columns[0].names[0] = xStrdup("LeftCPUs");
- this->columns[0].modes[0] = BAR_METERMODE;
- this->columns[1].names[r] = xStrdup("RightCPUs");
- this->columns[1].modes[r++] = BAR_METERMODE;
+ this->hColumns[0].names[0] = xStrdup("LeftCPUs");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("RightCPUs");
+ this->hColumns[1].modes[r++] = BAR_METERMODE;
} else {
- this->columns[0].names[0] = xStrdup("AllCPUs");
- this->columns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[0].names[0] = xStrdup("AllCPUs");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
}
- this->columns[0].names[1] = xStrdup("Memory");
- this->columns[0].modes[1] = BAR_METERMODE;
- this->columns[0].names[2] = xStrdup("Swap");
- this->columns[0].modes[2] = BAR_METERMODE;
- this->columns[1].names[r] = xStrdup("Tasks");
- this->columns[1].modes[r++] = TEXT_METERMODE;
- this->columns[1].names[r] = xStrdup("LoadAverage");
- this->columns[1].modes[r++] = TEXT_METERMODE;
- this->columns[1].names[r] = xStrdup("Uptime");
- this->columns[1].modes[r++] = TEXT_METERMODE;
+ this->hColumns[0].names[1] = xStrdup("Memory");
+ this->hColumns[0].modes[1] = BAR_METERMODE;
+ this->hColumns[0].names[2] = xStrdup("Swap");
+ this->hColumns[0].modes[2] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("Tasks");
+ this->hColumns[1].modes[r++] = TEXT_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("LoadAverage");
+ this->hColumns[1].modes[r++] = TEXT_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("Uptime");
+ this->hColumns[1].modes[r++] = TEXT_METERMODE;
}
-static void readFields(ProcessField* fields, uint32_t* flags, const char* line) {
+static void Settings_readFields(Settings* settings, const char* line) {
char* trim = String_trim(line);
char** ids = String_split(trim, ' ', NULL);
free(trim);
- int i, j;
- *flags = 0;
- for (j = 0, i = 0; i < LAST_PROCESSFIELD && ids[i]; i++) {
+
+ settings->flags = 0;
+
+ unsigned int i, j;
+ for (j = 0, i = 0; ids[i]; i++) {
+ if (j >= UINT_MAX / sizeof(ProcessField))
+ continue;
+ if (j >= LAST_PROCESSFIELD) {
+ settings->fields = xRealloc(settings->fields, j * sizeof(ProcessField));
+ memset(&settings->fields[j], 0, sizeof(ProcessField));
+ }
+
+ // Dynamically-defined columns are always stored by-name.
+ char dynamic[32] = {0};
+ if (sscanf(ids[i], "Dynamic(%30s)", dynamic)) {
+ char* end;
+ if ((end = strrchr(dynamic, ')')) == NULL)
+ continue;
+ *end = '\0';
+ unsigned int key;
+ if (!DynamicColumn_search(settings->dynamicColumns, dynamic, &key))
+ continue;
+ settings->fields[j++] = key;
+ continue;
+ }
// This "+1" is for compatibility with the older enum format.
int id = atoi(ids[i]) + 1;
if (id > 0 && id < LAST_PROCESSFIELD && Process_fields[id].name) {
- fields[j] = id;
- *flags |= Process_fields[id].flags;
- j++;
+ settings->flags |= Process_fields[id].flags;
+ settings->fields[j++] = id;
}
}
- fields[j] = NULL_PROCESSFIELD;
+ settings->fields[j] = NULL_PROCESSFIELD;
String_freeArray(ids);
}
-static bool Settings_read(Settings* this, const char* fileName, int initialCpuCount) {
- FILE* fd;
- CRT_dropPrivileges();
- fd = fopen(fileName, "r");
- CRT_restorePrivileges();
+static bool Settings_read(Settings* this, const char* fileName, unsigned int initialCpuCount) {
+ FILE* fd = fopen(fileName, "r");
if (!fd)
return false;
bool didReadMeters = false;
- bool didReadFields = false;
+ bool didReadAny = false;
for (;;) {
char* line = String_readLine(fd);
if (!line) {
break;
}
+ didReadAny = true;
size_t nOptions;
char** option = String_split(line, '=', &nOptions);
free (line);
@@ -131,9 +177,18 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
String_freeArray(option);
continue;
}
- if (String_eq(option[0], "fields")) {
- readFields(this->fields, &(this->flags), option[1]);
- didReadFields = true;
+ if (String_eq(option[0], "config_reader_min_version")) {
+ this->config_version = atoi(option[1]);
+ if (this->config_version > CONFIG_READER_MIN_VERSION) {
+ // the version of the config file on disk is newer than what we can read
+ fprintf(stderr, "WARNING: %s specifies configuration format version v%d, but this %s binary supports up to v%d\n.", fileName, this->config_version, PACKAGE, CONFIG_READER_MIN_VERSION);
+ fprintf(stderr, " The configuration version will be downgraded to v%d when %s exits.\n", CONFIG_READER_MIN_VERSION, PACKAGE);
+ String_freeArray(option);
+ fclose(fd);
+ return false;
+ }
+ } else if (String_eq(option[0], "fields")) {
+ Settings_readFields(this, option[1]);
} else if (String_eq(option[0], "sort_key")) {
// This "+1" is for compatibility with the older enum format.
this->sortKey = atoi(option[1]) + 1;
@@ -148,6 +203,8 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
this->treeView = atoi(option[1]);
} else if (String_eq(option[0], "tree_view_always_by_pid")) {
this->treeViewAlwaysByPID = atoi(option[1]);
+ } else if (String_eq(option[0], "all_branches_collapsed")) {
+ this->allBranchesCollapsed = atoi(option[1]);
} else if (String_eq(option[0], "hide_kernel_threads")) {
this->hideKernelThreads = atoi(option[1]);
} else if (String_eq(option[0], "hide_userland_threads")) {
@@ -160,6 +217,8 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
this->showProgramPath = atoi(option[1]);
} else if (String_eq(option[0], "highlight_base_name")) {
this->highlightBaseName = atoi(option[1]);
+ } else if (String_eq(option[0], "highlight_deleted_exe")) {
+ this->highlightDeletedExe = atoi(option[1]);
} else if (String_eq(option[0], "highlight_megabytes")) {
this->highlightMegabytes = atoi(option[1]);
} else if (String_eq(option[0], "highlight_threads")) {
@@ -167,7 +226,7 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
} else if (String_eq(option[0], "highlight_changes")) {
this->highlightChanges = atoi(option[1]);
} else if (String_eq(option[0], "highlight_changes_delay_secs")) {
- this->highlightDelaySecs = CLAMP(atoi(option[1]), 1, 24*60*60);
+ this->highlightDelaySecs = CLAMP(atoi(option[1]), 1, 24 * 60 * 60);
} else if (String_eq(option[0], "find_comm_in_cmdline")) {
this->findCommInCmdline = atoi(option[1]);
} else if (String_eq(option[0], "strip_exe_from_cmdline")) {
@@ -190,7 +249,7 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
this->showCPUUsage = atoi(option[1]);
} else if (String_eq(option[0], "show_cpu_frequency")) {
this->showCPUFrequency = atoi(option[1]);
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
} else if (String_eq(option[0], "show_cpu_temperature")) {
this->showCPUTemperature = atoi(option[1]);
} else if (String_eq(option[0], "degree_fahrenheit")) {
@@ -207,8 +266,16 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
if (this->colorScheme < 0 || this->colorScheme >= LAST_COLORSCHEME) {
this->colorScheme = 0;
}
+ #ifdef HAVE_GETMOUSE
} else if (String_eq(option[0], "enable_mouse")) {
this->enableMouse = atoi(option[1]);
+ #endif
+ } else if (String_eq(option[0], "header_layout")) {
+ this->hLayout = isdigit((unsigned char)option[1][0]) ? ((HeaderLayout) atoi(option[1])) : HeaderLayout_fromName(option[1]);
+ if (this->hLayout < 0 || this->hLayout >= LAST_HEADER_LAYOUT)
+ this->hLayout = HF_TWO_50_50;
+ free(this->hColumns);
+ this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
} else if (String_eq(option[0], "left_meters")) {
Settings_readMeters(this, option[1], 0);
didReadMeters = true;
@@ -221,6 +288,12 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
} else if (String_eq(option[0], "right_meter_modes")) {
Settings_readMeterModes(this, option[1], 1);
didReadMeters = true;
+ } else if (String_startsWith(option[0], "column_meters_")) {
+ Settings_readMeters(this, option[1], atoi(option[0] + strlen("column_meters_")));
+ didReadMeters = true;
+ } else if (String_startsWith(option[0], "column_meter_modes_")) {
+ Settings_readMeterModes(this, option[1], atoi(option[0] + strlen("column_meter_modes_")));
+ didReadMeters = true;
} else if (String_eq(option[0], "hide_function_bar")) {
this->hideFunctionBar = atoi(option[1]);
#ifdef HAVE_LIBHWLOC
@@ -234,116 +307,160 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
if (!didReadMeters) {
Settings_defaultMeters(this, initialCpuCount);
}
- return didReadFields;
+ return didReadAny;
}
-static void writeFields(FILE* fd, ProcessField* fields, const char* name) {
+static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, const char* name, char separator) {
fprintf(fd, "%s=", name);
const char* sep = "";
- for (int i = 0; fields[i]; i++) {
- // This "-1" is for compatibility with the older enum format.
- fprintf(fd, "%s%d", sep, (int) fields[i] - 1);
+ for (unsigned int i = 0; fields[i]; i++) {
+ if (fields[i] >= LAST_PROCESSFIELD) {
+ const DynamicColumn* column = DynamicColumn_lookup(columns, fields[i]);
+ fprintf(fd, "%sDynamic(%s)", sep, column->name);
+ } else {
+ // This "-1" is for compatibility with the older enum format.
+ fprintf(fd, "%s%d", sep, (int) fields[i] - 1);
+ }
sep = " ";
}
- fprintf(fd, "\n");
+ fputc(separator, fd);
}
-static void writeMeters(Settings* this, FILE* fd, int column) {
+static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) {
const char* sep = "";
- for (int i = 0; i < this->columns[column].len; i++) {
- fprintf(fd, "%s%s", sep, this->columns[column].names[i]);
+ for (uint8_t i = 0; i < this->hColumns[column].len; i++) {
+ fprintf(fd, "%s%s", sep, this->hColumns[column].names[i]);
sep = " ";
}
- fprintf(fd, "\n");
+ fputc(separator, fd);
}
-static void writeMeterModes(Settings* this, FILE* fd, int column) {
+static void writeMeterModes(const Settings* this, FILE* fd, char separator, unsigned int column) {
const char* sep = "";
- for (int i = 0; i < this->columns[column].len; i++) {
- fprintf(fd, "%s%d", sep, this->columns[column].modes[i]);
+ for (uint8_t i = 0; i < this->hColumns[column].len; i++) {
+ fprintf(fd, "%s%d", sep, this->hColumns[column].modes[i]);
sep = " ";
}
- fprintf(fd, "\n");
+ fputc(separator, fd);
}
-bool Settings_write(Settings* this) {
+int Settings_write(const Settings* this, bool onCrash) {
FILE* fd;
+ char separator;
+ if (onCrash) {
+ fd = stderr;
+ separator = ';';
+ } else {
+ fd = fopen(this->filename, "w");
+ if (fd == NULL)
+ return -errno;
+ separator = '\n';
+ }
- CRT_dropPrivileges();
- fd = fopen(this->filename, "w");
- CRT_restorePrivileges();
+ #define printSettingInteger(setting_, value_) \
+ fprintf(fd, setting_ "=%d%c", (int) (value_), separator)
+ #define printSettingString(setting_, value_) \
+ fprintf(fd, setting_ "=%s%c", value_, separator)
- if (fd == NULL) {
- return false;
+ if (!onCrash) {
+ fprintf(fd, "# Beware! This file is rewritten by htop when settings are changed in the interface.\n");
+ fprintf(fd, "# The parser is also very primitive, and not human-friendly.\n");
}
- fprintf(fd, "# Beware! This file is rewritten by htop when settings are changed in the interface.\n");
- fprintf(fd, "# The parser is also very primitive, and not human-friendly.\n");
- writeFields(fd, this->fields, "fields");
+ printSettingString("htop_version", VERSION);
+ printSettingInteger("config_reader_min_version", CONFIG_READER_MIN_VERSION);
+ writeFields(fd, this->fields, this->dynamicColumns, "fields", separator);
// This "-1" is for compatibility with the older enum format.
- fprintf(fd, "sort_key=%d\n", (int) this->sortKey - 1);
- fprintf(fd, "sort_direction=%d\n", (int) this->direction);
- fprintf(fd, "tree_sort_key=%d\n", (int) this->treeSortKey - 1);
- fprintf(fd, "tree_sort_direction=%d\n", (int) this->treeDirection);
- fprintf(fd, "hide_kernel_threads=%d\n", (int) this->hideKernelThreads);
- fprintf(fd, "hide_userland_threads=%d\n", (int) this->hideUserlandThreads);
- fprintf(fd, "shadow_other_users=%d\n", (int) this->shadowOtherUsers);
- fprintf(fd, "show_thread_names=%d\n", (int) this->showThreadNames);
- fprintf(fd, "show_program_path=%d\n", (int) this->showProgramPath);
- fprintf(fd, "highlight_base_name=%d\n", (int) this->highlightBaseName);
- fprintf(fd, "highlight_megabytes=%d\n", (int) this->highlightMegabytes);
- fprintf(fd, "highlight_threads=%d\n", (int) this->highlightThreads);
- fprintf(fd, "highlight_changes=%d\n", (int) this->highlightChanges);
- fprintf(fd, "highlight_changes_delay_secs=%d\n", (int) this->highlightDelaySecs);
- fprintf(fd, "find_comm_in_cmdline=%d\n", (int) this->findCommInCmdline);
- fprintf(fd, "strip_exe_from_cmdline=%d\n", (int) this->stripExeFromCmdline);
- fprintf(fd, "show_merged_command=%d\n", (int) this->showMergedCommand);
- fprintf(fd, "tree_view=%d\n", (int) this->treeView);
- fprintf(fd, "tree_view_always_by_pid=%d\n", (int) this->treeViewAlwaysByPID);
- fprintf(fd, "header_margin=%d\n", (int) this->headerMargin);
- fprintf(fd, "detailed_cpu_time=%d\n", (int) this->detailedCPUTime);
- fprintf(fd, "cpu_count_from_one=%d\n", (int) this->countCPUsFromOne);
- fprintf(fd, "show_cpu_usage=%d\n", (int) this->showCPUUsage);
- fprintf(fd, "show_cpu_frequency=%d\n", (int) this->showCPUFrequency);
- #ifdef HAVE_SENSORS_SENSORS_H
- fprintf(fd, "show_cpu_temperature=%d\n", (int) this->showCPUTemperature);
- fprintf(fd, "degree_fahrenheit=%d\n", (int) this->degreeFahrenheit);
+ printSettingInteger("sort_key", this->sortKey - 1);
+ printSettingInteger("sort_direction", this->direction);
+ printSettingInteger("tree_sort_key", this->treeSortKey - 1);
+ printSettingInteger("tree_sort_direction", this->treeDirection);
+ printSettingInteger("hide_kernel_threads", this->hideKernelThreads);
+ printSettingInteger("hide_userland_threads", this->hideUserlandThreads);
+ printSettingInteger("shadow_other_users", this->shadowOtherUsers);
+ printSettingInteger("show_thread_names", this->showThreadNames);
+ printSettingInteger("show_program_path", this->showProgramPath);
+ printSettingInteger("highlight_base_name", this->highlightBaseName);
+ printSettingInteger("highlight_deleted_exe", this->highlightDeletedExe);
+ printSettingInteger("highlight_megabytes", this->highlightMegabytes);
+ printSettingInteger("highlight_threads", this->highlightThreads);
+ printSettingInteger("highlight_changes", this->highlightChanges);
+ printSettingInteger("highlight_changes_delay_secs", this->highlightDelaySecs);
+ printSettingInteger("find_comm_in_cmdline", this->findCommInCmdline);
+ printSettingInteger("strip_exe_from_cmdline", this->stripExeFromCmdline);
+ printSettingInteger("show_merged_command", this->showMergedCommand);
+ printSettingInteger("tree_view", this->treeView);
+ printSettingInteger("tree_view_always_by_pid", this->treeViewAlwaysByPID);
+ printSettingInteger("all_branches_collapsed", this->allBranchesCollapsed);
+ printSettingInteger("header_margin", this->headerMargin);
+ printSettingInteger("detailed_cpu_time", this->detailedCPUTime);
+ printSettingInteger("cpu_count_from_one", this->countCPUsFromOne);
+ printSettingInteger("show_cpu_usage", this->showCPUUsage);
+ printSettingInteger("show_cpu_frequency", this->showCPUFrequency);
+ #ifdef BUILD_WITH_CPU_TEMP
+ printSettingInteger("show_cpu_temperature", this->showCPUTemperature);
+ printSettingInteger("degree_fahrenheit", this->degreeFahrenheit);
#endif
- fprintf(fd, "update_process_names=%d\n", (int) this->updateProcessNames);
- fprintf(fd, "account_guest_in_cpu_meter=%d\n", (int) this->accountGuestInCPUMeter);
- fprintf(fd, "color_scheme=%d\n", (int) this->colorScheme);
- fprintf(fd, "enable_mouse=%d\n", (int) this->enableMouse);
- fprintf(fd, "delay=%d\n", (int) this->delay);
- fprintf(fd, "left_meters="); writeMeters(this, fd, 0);
- fprintf(fd, "left_meter_modes="); writeMeterModes(this, fd, 0);
- fprintf(fd, "right_meters="); writeMeters(this, fd, 1);
- fprintf(fd, "right_meter_modes="); writeMeterModes(this, fd, 1);
- fprintf(fd, "hide_function_bar=%d\n", (int) this->hideFunctionBar);
+ printSettingInteger("update_process_names", this->updateProcessNames);
+ printSettingInteger("account_guest_in_cpu_meter", this->accountGuestInCPUMeter);
+ printSettingInteger("color_scheme", this->colorScheme);
+ #ifdef HAVE_GETMOUSE
+ printSettingInteger("enable_mouse", this->enableMouse);
+ #endif
+ printSettingInteger("delay", (int) this->delay);
+ printSettingInteger("hide_function_bar", (int) this->hideFunctionBar);
#ifdef HAVE_LIBHWLOC
- fprintf(fd, "topology_affinity=%d\n", (int) this->topologyAffinity);
+ printSettingInteger("topology_affinity", this->topologyAffinity);
#endif
- fclose(fd);
- return true;
+
+ printSettingString("header_layout", HeaderLayout_getName(this->hLayout));
+ for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
+ fprintf(fd, "column_meters_%u=", i);
+ writeMeters(this, fd, separator, i);
+ fprintf(fd, "column_meter_modes_%u=", i);
+ writeMeterModes(this, fd, separator, i);
+ }
+
+ #undef printSettingString
+ #undef printSettingInteger
+
+ if (onCrash)
+ return 0;
+
+ int r = 0;
+
+ if (ferror(fd) != 0)
+ r = (errno != 0) ? -errno : -EBADF;
+
+ if (fclose(fd) != 0)
+ r = r ? r : -errno;
+
+ return r;
}
-Settings* Settings_new(int initialCpuCount) {
+Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns) {
Settings* this = xCalloc(1, sizeof(Settings));
+ this->dynamicColumns = dynamicColumns;
+ this->hLayout = HF_TWO_50_50;
+ this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
this->sortKey = PERCENT_CPU;
this->treeSortKey = PID;
- this->direction = 1;
+ this->direction = -1;
this->treeDirection = 1;
this->shadowOtherUsers = false;
this->showThreadNames = false;
- this->hideKernelThreads = false;
+ this->hideKernelThreads = true;
this->hideUserlandThreads = false;
this->treeView = false;
+ this->allBranchesCollapsed = false;
this->highlightBaseName = false;
- this->highlightMegabytes = false;
+ this->highlightDeletedExe = true;
+ this->highlightMegabytes = true;
this->detailedCPUTime = false;
this->countCPUsFromOne = false;
this->showCPUUsage = true;
this->showCPUFrequency = false;
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
this->showCPUTemperature = false;
this->degreeFahrenheit = false;
#endif
@@ -356,6 +473,7 @@ Settings* Settings_new(int initialCpuCount) {
this->stripExeFromCmdline = true;
this->showMergedCommand = false;
this->hideFunctionBar = 0;
+ this->headerMargin = true;
#ifdef HAVE_LIBHWLOC
this->topologyAffinity = false;
#endif
@@ -370,7 +488,7 @@ Settings* Settings_new(int initialCpuCount) {
}
char* legacyDotfile = NULL;
- char* rcfile = getenv("HTOPRC");
+ const char* rcfile = getenv("HTOPRC");
if (rcfile) {
this->filename = xStrdup(rcfile);
} else {
@@ -391,7 +509,6 @@ Settings* Settings_new(int initialCpuCount) {
htopDir = String_cat(home, "/.config/htop");
}
legacyDotfile = String_cat(home, "/.htoprc");
- CRT_dropPrivileges();
(void) mkdir(configDir, 0700);
(void) mkdir(htopDir, 0700);
free(htopDir);
@@ -402,10 +519,11 @@ Settings* Settings_new(int initialCpuCount) {
free(legacyDotfile);
legacyDotfile = NULL;
}
- CRT_restorePrivileges();
}
this->colorScheme = 0;
+#ifdef HAVE_GETMOUSE
this->enableMouse = true;
+#endif
this->changed = false;
this->delay = DEFAULT_DELAY;
bool ok = false;
@@ -413,7 +531,7 @@ Settings* Settings_new(int initialCpuCount) {
ok = Settings_read(this, legacyDotfile, initialCpuCount);
if (ok) {
// Transition to new location and delete old configuration file
- if (Settings_write(this)) {
+ if (Settings_write(this, false) == 0) {
unlink(legacyDotfile);
}
}
@@ -424,20 +542,10 @@ Settings* Settings_new(int initialCpuCount) {
}
if (!ok) {
this->changed = true;
- // TODO: how to get SYSCONFDIR correctly through Autoconf?
- char* systemSettings = String_cat(SYSCONFDIR, "/htoprc");
- ok = Settings_read(this, systemSettings, initialCpuCount);
- free(systemSettings);
+ ok = Settings_read(this, SYSCONFDIR "/htoprc", initialCpuCount);
}
if (!ok) {
Settings_defaultMeters(this, initialCpuCount);
- this->hideKernelThreads = true;
- this->highlightMegabytes = true;
- this->highlightThreads = true;
- this->findCommInCmdline = true;
- this->stripExeFromCmdline = true;
- this->showMergedCommand = false;
- this->headerMargin = true;
}
return this;
}
@@ -450,10 +558,43 @@ void Settings_invertSortOrder(Settings* this) {
void Settings_setSortKey(Settings* this, ProcessField sortKey) {
if (this->treeViewAlwaysByPID || !this->treeView) {
this->sortKey = sortKey;
- this->direction = 1;
+ this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
this->treeView = false;
} else {
this->treeSortKey = sortKey;
- this->treeDirection = 1;
+ this->treeDirection = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
}
}
+
+static bool readonly = false;
+
+void Settings_enableReadonly(void) {
+ readonly = true;
+}
+
+bool Settings_isReadonly(void) {
+ return readonly;
+}
+
+void Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout) {
+ unsigned int oldColumns = HeaderLayout_getColumns(this->hLayout);
+ unsigned int newColumns = HeaderLayout_getColumns(hLayout);
+
+ if (newColumns > oldColumns) {
+ this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));
+ memset(this->hColumns + oldColumns, 0, (newColumns - oldColumns) * sizeof(MeterColumnSetting));
+ } else if (newColumns < oldColumns) {
+ for (unsigned int i = newColumns; i < oldColumns; i++) {
+ if (this->hColumns[i].names) {
+ for (uint8_t j = 0; j < this->hColumns[i].len; j++)
+ free(this->hColumns[i].names[j]);
+ free(this->hColumns[i].names);
+ }
+ free(this->hColumns[i].modes);
+ }
+ this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));
+ }
+
+ this->hLayout = hLayout;
+ this->changed = true;
+}
diff --git a/Settings.h b/Settings.h
index fdaf3e3..20f583c 100644
--- a/Settings.h
+++ b/Settings.h
@@ -12,20 +12,27 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdint.h>
+#include "Hashtable.h"
+#include "HeaderLayout.h"
#include "Process.h"
#define DEFAULT_DELAY 15
+#define CONFIG_READER_MIN_VERSION 2
+
typedef struct {
- int len;
+ uint8_t len;
char** names;
int* modes;
-} MeterColumnSettings;
+} MeterColumnSetting;
typedef struct Settings_ {
char* filename;
- MeterColumnSettings columns[2];
+ int config_version;
+ HeaderLayout hLayout;
+ MeterColumnSetting* hColumns;
+ Hashtable* dynamicColumns;
ProcessField* fields;
uint32_t flags;
@@ -41,18 +48,20 @@ typedef struct Settings_ {
bool detailedCPUTime;
bool showCPUUsage;
bool showCPUFrequency;
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
bool showCPUTemperature;
bool degreeFahrenheit;
#endif
bool treeView;
bool treeViewAlwaysByPID;
+ bool allBranchesCollapsed;
bool showProgramPath;
bool shadowOtherUsers;
bool showThreadNames;
bool hideKernelThreads;
bool hideUserlandThreads;
bool highlightBaseName;
+ bool highlightDeletedExe;
bool highlightMegabytes;
bool highlightThreads;
bool highlightChanges;
@@ -63,7 +72,9 @@ typedef struct Settings_ {
bool updateProcessNames;
bool accountGuestInCPUMeter;
bool headerMargin;
+ #ifdef HAVE_GETMOUSE
bool enableMouse;
+ #endif
int hideFunctionBar; // 0 - off, 1 - on ESC until next input, 2 - permanently
#ifdef HAVE_LIBHWLOC
bool topologyAffinity;
@@ -86,12 +97,18 @@ static inline int Settings_getActiveDirection(const Settings* this) {
void Settings_delete(Settings* this);
-bool Settings_write(Settings* this);
+int Settings_write(const Settings* this, bool onCrash);
-Settings* Settings_new(int initialCpuCount);
+Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns);
void Settings_invertSortOrder(Settings* this);
void Settings_setSortKey(Settings* this, ProcessField sortKey);
+void Settings_enableReadonly(void);
+
+bool Settings_isReadonly(void);
+
+void Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout);
+
#endif
diff --git a/SignalsPanel.h b/SignalsPanel.h
index 0a90b08..0dfe24c 100644
--- a/SignalsPanel.h
+++ b/SignalsPanel.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Panel.h"
+
typedef struct SignalItem_ {
const char* name;
int number;
diff --git a/SwapMeter.c b/SwapMeter.c
index e39cfd9..63f58c5 100644
--- a/SwapMeter.c
+++ b/SwapMeter.c
@@ -5,8 +5,13 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "SwapMeter.h"
+#include <math.h>
+#include <stddef.h>
+
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
@@ -14,11 +19,16 @@ in the source distribution for its full text.
static const int SwapMeter_attributes[] = {
- SWAP
+ SWAP,
+ SWAP_CACHE
};
-static void SwapMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void SwapMeter_updateValues(Meter* this) {
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
int written;
+
+ this->values[1] = NAN; /* 'cached' not present on all platforms */
Platform_setSwapValues(this);
written = Meter_humanUnit(buffer, this->values[0], size);
@@ -38,6 +48,12 @@ static void SwapMeter_display(const Object* cast, RichString* out) {
Meter_humanUnit(buffer, this->values[0], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:");
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+
+ if (!isnan(this->values[1])) {
+ Meter_humanUnit(buffer, this->values[1], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " cache:");
+ RichString_appendAscii(out, CRT_colors[SWAP_CACHE], buffer);
+ }
}
const MeterClass SwapMeter_class = {
@@ -48,7 +64,7 @@ const MeterClass SwapMeter_class = {
},
.updateValues = SwapMeter_updateValues,
.defaultMode = BAR_METERMODE,
- .maxItems = 1,
+ .maxItems = 2,
.total = 100.0,
.attributes = SwapMeter_attributes,
.name = "Swap",
diff --git a/SwapMeter.h b/SwapMeter.h
index 623a036..9c4e312 100644
--- a/SwapMeter.h
+++ b/SwapMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass SwapMeter_class;
#endif
diff --git a/SysArchMeter.c b/SysArchMeter.c
new file mode 100644
index 0000000..64b96c9
--- /dev/null
+++ b/SysArchMeter.c
@@ -0,0 +1,44 @@
+/*
+htop - SysArchMeter.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "SysArchMeter.h"
+
+#include <stddef.h>
+
+#include "CRT.h"
+#include "Object.h"
+#include "Platform.h"
+#include "XUtils.h"
+
+
+static const int SysArchMeter_attributes[] = {HOSTNAME};
+
+static void SysArchMeter_updateValues(Meter* this) {
+ static char* string;
+
+ if (string == NULL)
+ Platform_getRelease(&string);
+
+ String_safeStrncpy(this->txtBuffer, string, sizeof(this->txtBuffer));
+}
+
+const MeterClass SysArchMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete
+ },
+ .updateValues = SysArchMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 0,
+ .total = 100.0,
+ .attributes = SysArchMeter_attributes,
+ .name = "System",
+ .uiName = "System",
+ .caption = "System: ",
+};
diff --git a/SysArchMeter.h b/SysArchMeter.h
new file mode 100644
index 0000000..fa6adfe
--- /dev/null
+++ b/SysArchMeter.h
@@ -0,0 +1,14 @@
+#ifndef HEADER_SysArchMeter
+#define HEADER_SysArchMeter
+/*
+htop - SysArchMeter.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+#include "Meter.h"
+
+
+extern const MeterClass SysArchMeter_class;
+
+#endif
diff --git a/TESTPLAN b/TESTPLAN
index 7669485..88fe039 100644
--- a/TESTPLAN
+++ b/TESTPLAN
@@ -55,7 +55,9 @@ Main screen:
<l> - enter LSOF screen.
- <m>, <n>, <o>, <p> - do nothing.
+ <m>, <n>, <o> - do nothing.
+
+ <p> - hide/show program path.
<F10>, <q> - quit program.
diff --git a/TasksMeter.c b/TasksMeter.c
index a760ce0..29d3dd1 100644
--- a/TasksMeter.c
+++ b/TasksMeter.c
@@ -23,51 +23,39 @@ static const int TasksMeter_attributes[] = {
TASKS_RUNNING
};
-static void TasksMeter_updateValues(Meter* this, char* buffer, size_t len) {
+static void TasksMeter_updateValues(Meter* this) {
const ProcessList* pl = this->pl;
this->values[0] = pl->kernelThreads;
this->values[1] = pl->userlandThreads;
this->values[2] = pl->totalTasks - pl->kernelThreads - pl->userlandThreads;
- this->values[3] = MINIMUM(pl->runningTasks, pl->cpuCount);
- if (pl->totalTasks > this->total) {
- this->total = pl->totalTasks;
- }
- if (pl->settings->hideKernelThreads) {
- this->values[0] = 0;
- }
- xSnprintf(buffer, len, "%d/%d", (int) this->values[3], (int) this->total);
+ this->values[3] = MINIMUM(pl->runningTasks, pl->activeCPUs);
+ this->total = pl->totalTasks;
+
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%u/%u", MINIMUM(pl->runningTasks, pl->activeCPUs), pl->totalTasks);
}
static void TasksMeter_display(const Object* cast, RichString* out) {
const Meter* this = (const Meter*)cast;
const Settings* settings = this->pl->settings;
char buffer[20];
+ int len;
+
+ len = xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[2]);
+ RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, len);
+
+ RichString_appendAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], ", ");
+ len = xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[1]);
+ RichString_appendnAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[TASKS_RUNNING], buffer, len);
+ RichString_appendAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], " thr");
- int processes = (int) this->values[2];
+ RichString_appendAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], ", ");
+ len = xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[0]);
+ RichString_appendnAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[TASKS_RUNNING], buffer, len);
+ RichString_appendAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], " kthr");
- xSnprintf(buffer, sizeof(buffer), "%d", processes);
- RichString_writeAscii(out, CRT_colors[METER_VALUE], buffer);
- int threadValueColor = CRT_colors[METER_VALUE];
- int threadCaptionColor = CRT_colors[METER_TEXT];
- if (settings->highlightThreads) {
- threadValueColor = CRT_colors[PROCESS_THREAD_BASENAME];
- threadCaptionColor = CRT_colors[PROCESS_THREAD];
- }
- if (!settings->hideUserlandThreads) {
- RichString_appendAscii(out, CRT_colors[METER_TEXT], ", ");
- xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[1]);
- RichString_appendAscii(out, threadValueColor, buffer);
- RichString_appendAscii(out, threadCaptionColor, " thr");
- }
- if (!settings->hideKernelThreads) {
- RichString_appendAscii(out, CRT_colors[METER_TEXT], ", ");
- xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[0]);
- RichString_appendAscii(out, threadValueColor, buffer);
- RichString_appendAscii(out, threadCaptionColor, " kthr");
- }
RichString_appendAscii(out, CRT_colors[METER_TEXT], "; ");
- xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[3]);
- RichString_appendAscii(out, CRT_colors[TASKS_RUNNING], buffer);
+ len = xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[3]);
+ RichString_appendnAscii(out, CRT_colors[TASKS_RUNNING], buffer, len);
RichString_appendAscii(out, CRT_colors[METER_TEXT], " running");
}
diff --git a/TasksMeter.h b/TasksMeter.h
index cecb401..4ae6ce6 100644
--- a/TasksMeter.h
+++ b/TasksMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass TasksMeter_class;
#endif
diff --git a/TraceScreen.c b/TraceScreen.c
index da98e02..93981c5 100644
--- a/TraceScreen.c
+++ b/TraceScreen.c
@@ -18,12 +18,10 @@ in the source distribution for its full text.
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
-#include <sys/time.h>
#include <sys/wait.h>
#include "CRT.h"
#include "FunctionBar.h"
-#include "IncSet.h"
#include "Panel.h"
#include "ProvideCurses.h"
#include "XUtils.h"
@@ -35,14 +33,14 @@ static const char* const TraceScreenKeys[] = {"F3", "F4", "F8", "F9", "Esc"};
static const int TraceScreenEvents[] = {KEY_F(3), KEY_F(4), KEY_F(8), KEY_F(9), 27};
-TraceScreen* TraceScreen_new(Process* process) {
+TraceScreen* TraceScreen_new(const Process* process) {
// This initializes all TraceScreen variables to "false" so only default = true ones need to be set below
TraceScreen* this = xCalloc(1, sizeof(TraceScreen));
Object_setClass(this, Class(TraceScreen));
this->tracing = true;
FunctionBar* fuBar = FunctionBar_new(TraceScreenFunctions, TraceScreenKeys, TraceScreenEvents);
CRT_disableDelay();
- return (TraceScreen*) InfoScreen_init(&this->super, process, fuBar, LINES - 2, "");
+ return (TraceScreen*) InfoScreen_init(&this->super, process, fuBar, LINES - 2, " ");
}
void TraceScreen_delete(Object* cast) {
@@ -87,16 +85,13 @@ bool TraceScreen_forkTracer(TraceScreen* this) {
dup2(fdpair[1], STDERR_FILENO);
close(fdpair[1]);
- CRT_dropPrivileges();
-
char buffer[32] = {0};
xSnprintf(buffer, sizeof(buffer), "%d", this->super.process->pid);
execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, NULL);
// Should never reach here, unless execlp fails ...
const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH.";
- ssize_t written = write(STDERR_FILENO, message, strlen(message));
- (void) written;
+ (void)! write(STDERR_FILENO, message, strlen(message));
exit(127);
}
diff --git a/TraceScreen.h b/TraceScreen.h
index 3a2902a..fd34cee 100644
--- a/TraceScreen.h
+++ b/TraceScreen.h
@@ -28,7 +28,7 @@ typedef struct TraceScreen_ {
extern const InfoScreenClass TraceScreen_class;
-TraceScreen* TraceScreen_new(Process* process);
+TraceScreen* TraceScreen_new(const Process* process);
void TraceScreen_delete(Object* cast);
diff --git a/UptimeMeter.c b/UptimeMeter.c
index 37740c2..9036e53 100644
--- a/UptimeMeter.c
+++ b/UptimeMeter.c
@@ -17,10 +17,10 @@ static const int UptimeMeter_attributes[] = {
UPTIME
};
-static void UptimeMeter_updateValues(Meter* this, char* buffer, size_t len) {
+static void UptimeMeter_updateValues(Meter* this) {
int totalseconds = Platform_getUptime();
- if (totalseconds == -1) {
- xSnprintf(buffer, len, "(unknown)");
+ if (totalseconds <= 0) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "(unknown)");
return;
}
int seconds = totalseconds % 60;
@@ -41,7 +41,7 @@ static void UptimeMeter_updateValues(Meter* this, char* buffer, size_t len) {
} else {
daysbuf[0] = '\0';
}
- xSnprintf(buffer, len, "%s%02d:%02d:%02d", daysbuf, hours, minutes, seconds);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s%02d:%02d:%02d", daysbuf, hours, minutes, seconds);
}
const MeterClass UptimeMeter_class = {
diff --git a/UptimeMeter.h b/UptimeMeter.h
index 49300bb..36a5d88 100644
--- a/UptimeMeter.h
+++ b/UptimeMeter.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Meter.h"
+
extern const MeterClass UptimeMeter_class;
#endif
diff --git a/UsersTable.h b/UsersTable.h
index 0cac7dc..ef7d0bb 100644
--- a/UsersTable.h
+++ b/UsersTable.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "Hashtable.h"
+
typedef struct UsersTable_ {
Hashtable* users;
} UsersTable;
diff --git a/Vector.h b/Vector.h
index 875f361..5347ad7 100644
--- a/Vector.h
+++ b/Vector.h
@@ -68,6 +68,10 @@ static inline int Vector_size(const Vector* this) {
#endif /* NDEBUG */
+static inline const ObjectClass* Vector_type(const Vector* this) {
+ return this->type;
+}
+
void Vector_add(Vector* this, void* data_);
int Vector_indexOf(const Vector* this, const void* search_, Object_Compare compare);
diff --git a/XUtils.c b/XUtils.c
index 01f3342..c589d78 100644
--- a/XUtils.c
+++ b/XUtils.c
@@ -78,6 +78,26 @@ void* xReallocArray(void* ptr, size_t nmemb, size_t size) {
return xRealloc(ptr, nmemb * size);
}
+void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size) {
+ assert((ptr == NULL) == (prevmemb == 0));
+
+ if (prevmemb == newmemb) {
+ return ptr;
+ }
+
+ void* ret = xReallocArray(ptr, newmemb, size);
+
+ if (newmemb > prevmemb) {
+ memset((unsigned char*)ret + prevmemb * size, '\0', (newmemb - prevmemb) * size);
+ }
+
+ return ret;
+}
+
+inline bool String_contains_i(const char* s1, const char* s2) {
+ 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);
@@ -173,7 +193,7 @@ char* String_readLine(FILE* fd) {
char* buffer = xMalloc(step + 1);
char* at = buffer;
for (;;) {
- char* ok = fgets(at, step + 1, fd);
+ const char* ok = fgets(at, step + 1, fd);
if (!ok) {
free(buffer);
return NULL;
@@ -193,6 +213,18 @@ char* String_readLine(FILE* fd) {
}
}
+size_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size) {
+ assert(size > 0);
+
+ size_t i = 0;
+ for (; i < size - 1 && src[i]; i++)
+ dest[i] = src[i];
+
+ dest[i] = '\0';
+
+ return i;
+}
+
int xAsprintf(char** strp, const char* fmt, ...) {
va_list vl;
va_start(vl, fmt);
@@ -227,6 +259,14 @@ char* xStrdup(const char* str) {
return data;
}
+void free_and_xStrdup(char** ptr, const char* str) {
+ if (*ptr && String_eq(*ptr, str))
+ return;
+
+ free(*ptr);
+ *ptr = xStrdup(str);
+}
+
char* xStrndup(const char* str, size_t len) {
char* data = strndup(str, len);
if (!data) {
diff --git a/XUtils.h b/XUtils.h
index 9e5e62c..13f4be4 100644
--- a/XUtils.h
+++ b/XUtils.h
@@ -13,7 +13,6 @@ in the source distribution for its full text.
#include <stdio.h>
#include <stdlib.h> // IWYU pragma: keep
#include <string.h> // IWYU pragma: keep
-#include <sys/types.h>
#include "Compat.h"
#include "Macros.h"
@@ -21,15 +20,17 @@ in the source distribution for its full text.
void fail(void) ATTR_NORETURN;
-void* xMalloc(size_t size);
+void* xMalloc(size_t size) ATTR_ALLOC_SIZE1(1) ATTR_MALLOC;
-void* xMallocArray(size_t nmemb, size_t size);
+void* xMallocArray(size_t nmemb, size_t size) ATTR_ALLOC_SIZE2(1, 2) ATTR_MALLOC;
-void* xCalloc(size_t nmemb, size_t size);
+void* xCalloc(size_t nmemb, size_t size) ATTR_ALLOC_SIZE2(1, 2) ATTR_MALLOC;
-void* xRealloc(void* ptr, size_t size);
+void* xRealloc(void* ptr, size_t size) ATTR_ALLOC_SIZE1(2);
-void* xReallocArray(void* ptr, size_t nmemb, size_t size);
+void* xReallocArray(void* ptr, size_t nmemb, size_t size) ATTR_ALLOC_SIZE2(2, 3);
+
+void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size) ATTR_ALLOC_SIZE2(3, 4);
/*
* String_startsWith gives better performance if strlen(match) can be computed
@@ -39,25 +40,26 @@ static inline bool String_startsWith(const char* s, const char* match) {
return strncmp(s, match, strlen(match)) == 0;
}
-static inline bool String_contains_i(const char* s1, const char* s2) {
- return strcasestr(s1, s2) != NULL;
-}
+bool String_contains_i(const char* s1, const char* s2);
static inline bool String_eq(const char* s1, const char* s2) {
return strcmp(s1, s2) == 0;
}
-char* String_cat(const char* s1, const char* s2);
+char* String_cat(const char* s1, const char* s2) ATTR_MALLOC;
-char* String_trim(const char* in);
+char* String_trim(const char* in) ATTR_MALLOC;
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);
+char* String_getToken(const char* line, unsigned short int numMatch) ATTR_MALLOC;
+
+char* String_readLine(FILE* fd) ATTR_MALLOC;
-char* String_readLine(FILE* fd);
+/* Always null-terminates dest. Caller must pass a strictly positive size. */
+size_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size);
ATTR_FORMAT(printf, 2, 3)
int xAsprintf(char** strp, const char* fmt, ...);
@@ -65,9 +67,10 @@ int xAsprintf(char** strp, const char* fmt, ...);
ATTR_FORMAT(printf, 3, 4)
int xSnprintf(char* buf, size_t len, const char* fmt, ...);
-char* xStrdup(const char* str) ATTR_NONNULL;
+char* xStrdup(const char* str) ATTR_NONNULL ATTR_MALLOC;
+void free_and_xStrdup(char** ptr, const char* str);
-char* xStrndup(const char* str, size_t len) ATTR_NONNULL;
+char* xStrndup(const char* str, size_t len) ATTR_NONNULL ATTR_MALLOC;
ssize_t xReadfile(const char* pathname, void* buffer, size_t count);
ssize_t xReadfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size_t count);
diff --git a/configure.ac b/configure.ac
index be406c6..e93afbd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,71 +1,146 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.65)
-AC_INIT([htop],[3.0.5],[htop@groups.io])
+# ----------------------------------------------------------------------
+# Autoconf initialization.
+# ----------------------------------------------------------------------
+
+AC_PREREQ([2.69])
+AC_INIT([htop], [3.1.0], [htop@groups.io], [], [https://htop.dev/])
AC_CONFIG_SRCDIR([htop.c])
-AC_CONFIG_AUX_DIR([.])
+AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_HEADERS([config.h])
-# Required by hwloc scripts
AC_CANONICAL_TARGET
+AM_INIT_AUTOMAKE([-Wall std-options subdir-objects])
-AM_INIT_AUTOMAKE([1.11])
-
-# Checks for programs.
# ----------------------------------------------------------------------
-AC_PROG_CC
-AM_PROG_CC_C_O
-# Required by hwloc scripts
-AC_USE_SYSTEM_EXTENSIONS
+# ----------------------------------------------------------------------
# Checks for platform.
# ----------------------------------------------------------------------
+
case "$target_os" in
linux*|gnu*)
my_htop_platform=linux
- AC_DEFINE([HTOP_LINUX], [], [Building for Linux])
+ AC_DEFINE([HTOP_LINUX], [], [Building for Linux.])
;;
freebsd*|kfreebsd*)
my_htop_platform=freebsd
- AC_DEFINE([HTOP_FREEBSD], [], [Building for FreeBSD])
+ AC_DEFINE([HTOP_FREEBSD], [], [Building for FreeBSD.])
+ ;;
+netbsd*)
+ my_htop_platform=netbsd
+ AC_DEFINE([HTOP_NETBSD], [], [Building for NetBSD.])
;;
openbsd*)
my_htop_platform=openbsd
- AC_DEFINE([HTOP_OPENBSD], [], [Building for OpenBSD])
+ AC_DEFINE([HTOP_OPENBSD], [], [Building for OpenBSD.])
;;
dragonfly*)
my_htop_platform=dragonflybsd
- AC_DEFINE([HTOP_DRAGONFLYBSD], [], [Building for DragonFlyBSD])
+ AC_DEFINE([HTOP_DRAGONFLYBSD], [], [Building for DragonFlyBSD.])
;;
darwin*)
my_htop_platform=darwin
- AC_DEFINE([HTOP_DARWIN], [], [Building for Darwin])
+ AC_DEFINE([HTOP_DARWIN], [], [Building for Darwin.])
;;
solaris*)
my_htop_platform=solaris
- AC_DEFINE([HTOP_SOLARIS], [], [Building for Solaris])
+ AC_DEFINE([HTOP_SOLARIS], [], [Building for Solaris.])
;;
*)
my_htop_platform=unsupported
- AC_DEFINE([HTOP_UNSUPPORTED], [], [Building for an unsupported platform])
+ AC_DEFINE([HTOP_UNSUPPORTED], [], [Building for an unsupported platform.])
;;
esac
-# Checks for libraries.
+# Enable extensions, required by hwloc scripts
+AC_USE_SYSTEM_EXTENSIONS
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for compiler.
+# ----------------------------------------------------------------------
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+m4_version_prereq([2.70], [], [AC_PROG_CC_C99])
+AS_IF([test "x$ac_cv_prog_cc_c99" = xno], [AC_MSG_ERROR([htop is written in C99. A newer compiler is required.])])
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for static build.
+# ----------------------------------------------------------------------
+
+AC_ARG_ENABLE([static],
+ [AS_HELP_STRING([--enable-static],
+ [build a static htop binary @<:@default=no@:>@])],
+ [],
+ [enable_static=no])
+case "$enable_static" in
+ no)
+ ;;
+ yes)
+ AC_DEFINE([BUILD_STATIC], [1], [Define if building static binary.])
+ CFLAGS="$CFLAGS -static"
+ LDFLAGS="$LDFLAGS -static"
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_static' for --enable-static option])
+ ;;
+esac
+
# ----------------------------------------------------------------------
-AC_CHECK_LIB([m], [ceil], [], [missing_libraries="$missing_libraries libm"])
-# Checks for header files.
# ----------------------------------------------------------------------
+# Checks for a PCP-based htop build. (https://pcp.io)
+# ----------------------------------------------------------------------
+
+AC_ARG_ENABLE([pcp],
+ [AS_HELP_STRING([--enable-pcp],
+ [build a pcp-htop binary @<:@default=no@:>@])],
+ [],
+ [enable_pcp=no])
+case "$enable_pcp" in
+ no)
+ ;;
+ yes)
+ AC_CHECK_HEADERS([pcp/pmapi.h], [my_htop_platform=pcp],
+ [AC_MSG_ERROR([can not find PCP header file])])
+ AC_SEARCH_LIBS([pmNewContext], [pcp], [], [AC_MSG_ERROR([can not find PCP library])])
+ AC_DEFINE([HTOP_PCP], [1], [Define if building pcp-htop binary.])
+ AC_CONFIG_FILES([pcp-htop.5])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_pcp' for --enable-pcp option])
+ ;;
+esac
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for generic header files.
+# ----------------------------------------------------------------------
+
AC_HEADER_DIRENT
-AC_HEADER_STDC
-AC_CHECK_HEADERS([stdlib.h string.h strings.h sys/param.h sys/time.h unistd.h],[:],[
- missing_headers="$missing_headers $ac_header"
-])
-AC_CHECK_HEADERS([execinfo.h],[:],[:])
+m4_version_prereq([2.70], [AC_CHECK_INCLUDES_DEFAULT], [AC_HEADER_STDC])
+AC_CHECK_HEADERS([ \
+ stdlib.h \
+ string.h \
+ strings.h \
+ sys/param.h \
+ sys/time.h \
+ sys/utsname.h \
+ unistd.h
+ ], [], [AC_MSG_ERROR([can not find required generic header files])])
AC_HEADER_MAJOR
dnl glibc 2.25 deprecates 'major' and 'minor' in <sys/types.h> and requires to
@@ -73,14 +148,25 @@ dnl include <sys/sysmacros.h>. However the logic in AC_HEADER_MAJOR has not yet
dnl been updated in Autoconf 2.69, so use a workaround:
m4_version_prereq([2.70], [],
[if test "x$ac_cv_header_sys_mkdev_h" != xyes; then
- AC_CHECK_HEADER(sys/sysmacros.h, [AC_DEFINE(MAJOR_IN_SYSMACROS, 1,
+ AC_CHECK_HEADER([sys/sysmacros.h], [AC_DEFINE([MAJOR_IN_SYSMACROS], [1],
[Define to 1 if `major', `minor', and `makedev' are declared in <sys/sysmacros.h>.])])
fi])
+# Optional Section
+
+AC_CHECK_HEADERS([execinfo.h])
+
+if test "$my_htop_platform" = darwin; then
+ AC_CHECK_HEADERS([mach/mach_time.h])
+fi
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
# Checks for typedefs, structures, and compiler characteristics.
# ----------------------------------------------------------------------
-AC_HEADER_STDBOOL
-AC_C_CONST
+
AC_TYPE_PID_T
AC_TYPE_UID_T
AC_TYPE_UINT8_T
@@ -88,64 +174,111 @@ AC_TYPE_UINT16_T
AC_TYPE_UINT32_T
AC_TYPE_UINT64_T
-# Checks for library functions and compiler features.
-# ----------------------------------------------------------------------
-AC_FUNC_CLOSEDIR_VOID
-AC_FUNC_STAT
-AC_CHECK_FUNCS([\
- clock_gettime\
- faccessat\
- fstatat\
- host_get_clock_service\
- openat\
- readlinkat\
-])
+AC_MSG_CHECKING(for alloc_size)
+old_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -Wno-error -Werror=attributes"
+AC_COMPILE_IFELSE([
+ AC_LANG_SOURCE(
+ [
+ __attribute__((alloc_size(1))) char* my_alloc(int size) { return 0; }
+ ],[]
+ )],
+ AC_DEFINE([HAVE_ATTR_ALLOC_SIZE], 1, [The alloc_size attribute is supported.])
+ AC_MSG_RESULT(yes),
+ AC_MSG_RESULT(no))
+CFLAGS="$old_CFLAGS"
+
+AC_MSG_CHECKING(for NaN support)
+AC_RUN_IFELSE([
+ AC_LANG_PROGRAM(
+ [[
+ #include <math.h>
+ ]],
+ [[
+ double x = NAN; return !isnan(x);
+ ]]
+ )],
+ [AC_MSG_RESULT(yes)],
+ [
+ AC_MSG_RESULT(no)
+ AC_MSG_WARN([Compiler does not respect NaN, some functionality might break; consider using '-fno-finite-math-only'])
+ ],
+ [AC_MSG_RESULT(skipped)])
-AC_SEARCH_LIBS([dlopen], [dl dld])
-AC_SEARCH_LIBS([clock_gettime], [rt])
+# ----------------------------------------------------------------------
-save_cflags="${CFLAGS}"
-CFLAGS="${CFLAGS} -std=c99"
-AC_MSG_CHECKING([whether cc -std=c99 option works])
-AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
- [AC_INCLUDES_DEFAULT], [[char *a; a = strdup("foo"); int i = 0; i++; // C99]])],
- [AC_MSG_RESULT([yes])],
- [AC_MSG_ERROR([htop is written in C99. A newer compiler is required.])])
-CFLAGS="$save_cflags"
-# Add -lexecinfo if needed
-AC_SEARCH_LIBS([backtrace], [execinfo])
+# ----------------------------------------------------------------------
+# Checks for generic library functions.
+# ----------------------------------------------------------------------
-# Add -ldevstat if needed
-AC_SEARCH_LIBS([devstat_checkversion], [devstat])
+AC_CHECK_LIB([m], [ceil], [], [AC_MSG_ERROR([can not find required function ceil()])])
-# Checks for features and flags.
-# ----------------------------------------------------------------------
-PROCDIR=/proc
+if test "$my_htop_platform" = dragonflybsd; then
+ AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
+fi
-AC_ARG_WITH(proc, [AS_HELP_STRING([--with-proc=DIR], [Location of a Linux-compatible proc filesystem (default=/proc).])],
- if test -n "$withval"; then
- AC_DEFINE_UNQUOTED(PROCDIR, "$withval", [Path of proc filesystem])
- PROCDIR="$withval"
- fi,
- AC_DEFINE(PROCDIR, "/proc", [Path of proc filesystem]))
+if test "$my_htop_platform" = freebsd; then
+ if test "$enable_static" = yes; then
+ AC_SEARCH_LIBS([elf_version], [elf], [], [AC_MSG_ERROR([can not find required function elf_version()])])
+ fi
+ AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
+ AC_SEARCH_LIBS([devstat_checkversion], [devstat], [], [AC_MSG_ERROR([can not find required function devstat_checkversion()])])
+fi
-AC_ARG_ENABLE(openvz, [AS_HELP_STRING([--enable-openvz], [enable OpenVZ support])], ,enable_openvz="no")
-if test "x$enable_openvz" = xyes; then
- AC_DEFINE(HAVE_OPENVZ, 1, [Define if openvz support enabled.])
+if test "$my_htop_platform" = linux; then
+ if test "$enable_static" != yes; then
+ AC_SEARCH_LIBS([dlopen], [dl dld], [], [AC_MSG_ERROR([can not find required function dlopen()])])
+ fi
fi
-AC_ARG_ENABLE(vserver, [AS_HELP_STRING([--enable-vserver], [enable VServer support])], ,enable_vserver="no")
-if test "x$enable_vserver" = xyes; then
- AC_DEFINE(HAVE_VSERVER, 1, [Define if vserver support enabled.])
+if test "$my_htop_platform" = netbsd; then
+ AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
+ AC_SEARCH_LIBS([prop_dictionary_get], [prop], [], [AC_MSG_ERROR([can not find required function prop_dictionary_get()])])
fi
-AC_ARG_ENABLE(ancient_vserver, [AS_HELP_STRING([--enable-ancient-vserver], [enable ancient VServer support (implies --enable-vserver)])], ,enable_ancient_vserver="no")
-if test "x$enable_ancient_vserver" = xyes; then
- AC_DEFINE(HAVE_VSERVER, 1, [Define if vserver support enabled.])
- AC_DEFINE(HAVE_ANCIENT_VSERVER, 1, [Define if ancient vserver support enabled.])
+if test "$my_htop_platform" = openbsd; then
+ AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
+fi
+
+if test "$my_htop_platform" = solaris; then
+ AC_SEARCH_LIBS([kstat_open], [kstat], [], [AC_MSG_ERROR([can not find required function kstat_open()])])
+ AC_SEARCH_LIBS([Pgrab_error], [proc], [], [AC_MSG_ERROR([can not find required function Pgrab_error()])])
+ AC_SEARCH_LIBS([free], [malloc], [], [AC_MSG_ERROR([can not find required function free()])])
+fi
+
+# Optional Section
+
+AC_SEARCH_LIBS([clock_gettime], [rt])
+
+AC_CHECK_FUNCS([ \
+ clock_gettime \
+ faccessat \
+ fstatat \
+ host_get_clock_service \
+ memfd_create\
+ openat \
+ readlinkat \
+ ])
+
+# Add -lexecinfo if needed
+AC_SEARCH_LIBS([backtrace], [execinfo])
+
+if test "$my_htop_platform" = darwin; then
+ AC_CHECK_FUNCS([mach_timebase_info])
fi
+if test "$my_htop_platform" = linux && test "x$enable_static" = xyes; then
+ AC_CHECK_LIB([systemd], [sd_bus_open_system])
+fi
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for cross-platform features and flags.
+# ----------------------------------------------------------------------
+
# HTOP_CHECK_SCRIPT(LIBNAME, FUNCTION, DEFINE, CONFIG_SCRIPT, ELSE_PART)
m4_define([HTOP_CHECK_SCRIPT],
[
@@ -158,10 +291,8 @@ m4_define([HTOP_CHECK_SCRIPT],
htop_config_script_cflags=$([$4] --cflags 2> /dev/null)
fi
htop_script_success=no
- htop_save_LDFLAGS="$LDFLAGS"
htop_save_CFLAGS="$CFLAGS"
if test ! "x$htop_config_script_libs" = x; then
- LDFLAGS="$htop_config_script_libs $LDFLAGS"
CFLAGS="$htop_config_script_cflags $CFLAGS"
AC_CHECK_LIB([$1], [$2], [
AC_DEFINE([$3], 1, [The library is present.])
@@ -169,8 +300,9 @@ m4_define([HTOP_CHECK_SCRIPT],
htop_script_success=yes
], [
CFLAGS="$htop_save_CFLAGS"
+ ], [
+ $htop_config_script_libs
])
- LDFLAGS="$htop_save_LDFLAGS"
fi
if test "x$htop_script_success" = xno; then
[$5]
@@ -181,100 +313,75 @@ m4_define([HTOP_CHECK_SCRIPT],
m4_define([HTOP_CHECK_LIB],
[
AC_CHECK_LIB([$1], [$2], [
- AC_DEFINE([$3], 1, [The library is present.])
+ AC_DEFINE([$3], [1], [The library is present.])
LIBS="-l[$1] $LIBS "
], [$4])
])
-dnl https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
-AC_DEFUN([AX_CHECK_COMPILE_FLAG],
-[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
-AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
-AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
- ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
- _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
- AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
- [AS_VAR_SET(CACHEVAR,[yes])],
- [AS_VAR_SET(CACHEVAR,[no])])
- _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
-AS_VAR_IF(CACHEVAR,yes,
- [m4_default([$2], :)],
- [m4_default([$3], :)])
-AS_VAR_POPDEF([CACHEVAR])dnl
-])dnl AX_CHECK_COMPILE_FLAGS
-
-AC_ARG_ENABLE(unicode, [AS_HELP_STRING([--enable-unicode], [enable Unicode support])], ,enable_unicode="yes")
+AC_ARG_ENABLE([unicode],
+ [AS_HELP_STRING([--enable-unicode],
+ [enable Unicode support @<:@default=yes@:>@])],
+ [],
+ [enable_unicode=yes])
if test "x$enable_unicode" = xyes; then
- HTOP_CHECK_SCRIPT([ncursesw6], [addnwstr], [HAVE_LIBNCURSESW], "ncursesw6-config",
- HTOP_CHECK_SCRIPT([ncursesw], [addnwstr], [HAVE_LIBNCURSESW], "ncursesw6-config",
- HTOP_CHECK_SCRIPT([ncursesw], [addnwstr], [HAVE_LIBNCURSESW], "ncursesw5-config",
- HTOP_CHECK_SCRIPT([ncurses], [addnwstr], [HAVE_LIBNCURSESW], "ncurses5-config",
+ HTOP_CHECK_SCRIPT([ncursesw6], [waddwstr], [HAVE_LIBNCURSESW], "ncursesw6-config",
+ HTOP_CHECK_SCRIPT([ncursesw], [waddwstr], [HAVE_LIBNCURSESW], "ncursesw6-config",
+ HTOP_CHECK_SCRIPT([ncursesw], [wadd_wch], [HAVE_LIBNCURSESW], "ncursesw5-config",
+ HTOP_CHECK_SCRIPT([ncurses], [wadd_wch], [HAVE_LIBNCURSESW], "ncurses5-config",
HTOP_CHECK_LIB([ncursesw6], [addnwstr], [HAVE_LIBNCURSESW],
HTOP_CHECK_LIB([ncursesw], [addnwstr], [HAVE_LIBNCURSESW],
HTOP_CHECK_LIB([ncurses], [addnwstr], [HAVE_LIBNCURSESW],
- missing_libraries="$missing_libraries libncursesw"
- AC_MSG_ERROR([You may want to use --disable-unicode or install libncursesw.])
+ AC_MSG_ERROR([can not find required library libncursesw; you may want to use --disable-unicode])
)))))))
- AC_CHECK_HEADERS([ncursesw/curses.h],[:],
- [AC_CHECK_HEADERS([ncurses/ncurses.h],[:],
- [AC_CHECK_HEADERS([ncurses/curses.h],[:],
- [AC_CHECK_HEADERS([ncurses.h],[:],[missing_headers="$missing_headers $ac_header"])])])])
+ AC_CHECK_HEADERS([ncursesw/curses.h], [],
+ [AC_CHECK_HEADERS([ncurses/ncurses.h], [],
+ [AC_CHECK_HEADERS([ncurses/curses.h], [],
+ [AC_CHECK_HEADERS([ncurses.h], [],
+ [AC_MSG_ERROR([can not find required ncurses header file])])])])])
# check if additional linker flags are needed for keypad(3)
# (at this point we already link against a working ncurses library with wide character support)
AC_SEARCH_LIBS([keypad], [tinfow tinfo])
else
- HTOP_CHECK_SCRIPT([ncurses6], [refresh], [HAVE_LIBNCURSES], "ncurses6-config",
- HTOP_CHECK_SCRIPT([ncurses], [refresh], [HAVE_LIBNCURSES], "ncurses5-config",
- HTOP_CHECK_LIB([ncurses6], [refresh], [HAVE_LIBNCURSES],
- HTOP_CHECK_LIB([ncurses], [refresh], [HAVE_LIBNCURSES],
- missing_libraries="$missing_libraries libncurses"
- ))))
-
- AC_CHECK_HEADERS([curses.h],[:],
- [AC_CHECK_HEADERS([ncurses/curses.h],[:],
- [AC_CHECK_HEADERS([ncurses/ncurses.h],[:],
- [AC_CHECK_HEADERS([ncurses.h],[:],[missing_headers="$missing_headers $ac_header"])])])])
+ HTOP_CHECK_SCRIPT([ncurses6], [wnoutrefresh], [HAVE_LIBNCURSES], [ncurses6-config],
+ HTOP_CHECK_SCRIPT([ncurses], [wnoutrefresh], [HAVE_LIBNCURSES], [ncurses5-config],
+ HTOP_CHECK_LIB([ncurses6], [doupdate], [HAVE_LIBNCURSES],
+ HTOP_CHECK_LIB([ncurses], [doupdate], [HAVE_LIBNCURSES],
+ HTOP_CHECK_LIB([curses], [doupdate], [HAVE_LIBNCURSES],
+ AC_MSG_ERROR([can not find required curses/ncurses library])
+ )))))
+
+ AC_CHECK_HEADERS([curses.h], [],
+ [AC_CHECK_HEADERS([ncurses/curses.h], [],
+ [AC_CHECK_HEADERS([ncurses/ncurses.h], [],
+ [AC_CHECK_HEADERS([ncurses.h] ,[],
+ [AC_MSG_ERROR([can not find required ncurses header file])])])])])
# check if additional linker flags are needed for keypad(3)
# (at this point we already link against a working ncurses library)
AC_SEARCH_LIBS([keypad], [tinfo])
fi
-
-if test "$my_htop_platform" = "darwin"; then
- AC_CHECK_HEADERS([mach/mach_time.h])
- AC_CHECK_FUNCS([mach_timebase_info])
-fi
-
-if test "$my_htop_platform" = "dragonflybsd"; then
- AC_CHECK_LIB([kvm], [kvm_open], [], [missing_libraries="$missing_libraries libkvm"])
-fi
-
-if test "$my_htop_platform" = "freebsd"; then
- AC_CHECK_LIB([kvm], [kvm_open], [], [missing_libraries="$missing_libraries libkvm"])
+if test "$enable_static" = yes; then
+ AC_SEARCH_LIBS([Gpm_GetEvent], [gpm])
fi
-
-if test "$my_htop_platform" = "openbsd"; then
- AC_CHECK_LIB([kvm], [kvm_open], [], [missing_libraries="$missing_libraries libkvm"])
-fi
-
if test "$my_htop_platform" = "solaris"; then
- AC_CHECK_LIB([kstat], [kstat_open], [], [missing_libraries="$missing_libraries libkstat"])
- AC_CHECK_LIB([proc], [Pgrab_error], [], [missing_libraries="$missing_libraries libproc"])
- AC_CHECK_LIB([malloc], [free], [], [missing_libraries="$missing_libraries libmalloc"])
+ # On OmniOS /usr/include/sys/regset.h redefines ERR to 13 - \r, breaking the Enter key.
+ # Since ncurses macros use the ERR macro, we can not use another name.
+ AC_DEFINE([ERR], [(-1)], [Predefine ncurses macro.])
fi
+AC_CHECK_FUNCS( [set_escdelay] )
+AC_CHECK_FUNCS( [getmouse] )
-AC_ARG_ENABLE(hwloc, [AS_HELP_STRING([--enable-hwloc], [enable hwloc support for CPU affinity, disables Linux affinity])],, enable_hwloc="no")
-if test "x$enable_hwloc" = xyes; then
- AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [missing_libraries="$missing_libraries libhwloc"])
- AC_CHECK_HEADERS([hwloc.h],[:], [missing_headers="$missing_headers $ac_header"])
-fi
-AC_ARG_ENABLE(linux_affinity, [AS_HELP_STRING([--enable-linux-affinity], [enable Linux sched_setaffinity and sched_getaffinity for affinity support, conflicts with hwloc])], ,enable_linux_affinity="check")
-if test "x$enable_linux_affinity" = xcheck; then
+AC_ARG_ENABLE([affinity],
+ [AS_HELP_STRING([--enable-affinity],
+ [enable sched_setaffinity and sched_getaffinity for affinity support, conflicts with hwloc @<:@default=check@:>@])],
+ [],
+ [enable_affinity=check])
+if test "x$enable_affinity" = xcheck; then
if test "x$enable_hwloc" = xyes; then
- enable_linux_affinity=no
+ enable_affinity=no
else
AC_MSG_CHECKING([for usable sched_setaffinity])
AC_RUN_IFELSE([
@@ -287,50 +394,211 @@ if test "x$enable_linux_affinity" = xcheck; then
sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
if (errno == ENOSYS) return 1;
]])],
- [enable_linux_affinity=yes
+ [enable_affinity=yes
AC_MSG_RESULT([yes])],
- [enable_linux_affinity=no
+ [enable_affinity=no
AC_MSG_RESULT([no])],
[AC_MSG_RESULT([yes (assumed while cross compiling)])])
fi
fi
-if test "x$enable_linux_affinity" = xyes; then
- AC_DEFINE(HAVE_LINUX_AFFINITY, 1, [Define if Linux sched_setaffinity and sched_getaffinity are to be used.])
+if test "x$enable_affinity" = xyes; then
+ if test "x$enable_hwloc" = xyes; then
+ AC_MSG_ERROR([--enable-hwloc and --enable-affinity are mutual exclusive. Specify at most one of them.])
+ fi
+ AC_DEFINE([HAVE_AFFINITY], [1], [Define if sched_setaffinity and sched_getaffinity are to be used.])
+fi
+
+
+AC_ARG_ENABLE([hwloc],
+ [AS_HELP_STRING([--enable-hwloc],
+ [enable hwloc support for CPU affinity; disables affinity support; requires libhwloc @<:@default=no@:>@])],
+ [],
+ [enable_hwloc=no])
+case "$enable_hwloc" in
+ no)
+ ;;
+ yes)
+ AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])])
+ AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_hwloc' for --enable-hwloc])
+ ;;
+esac
+
+AC_ARG_WITH([os-release],
+ [AS_HELP_STRING([--with-os-release=FILE],
+ [location of an os-release file @<:@default=/etc/os-release@:>@])],
+ [],
+ [with_os_release=/etc/os-release])
+if test -n "$with_os_release" && test ! -f "$with_os_release"; then
+ if test -f "/usr/lib/os-release"; then
+ with_os_release="/usr/lib/os-release"
+ fi
fi
+AC_DEFINE_UNQUOTED([OSRELEASEFILE], ["$with_os_release"], [File with OS release details.])
+
+# ----------------------------------------------------------------------
-if test "x$enable_linux_affinity" = xyes -a "x$enable_hwloc" = xyes; then
- AC_MSG_ERROR([--enable-hwloc and --enable-linux-affinity are mutual exclusive. Specify at most one of them.])
+
+# ----------------------------------------------------------------------
+# Checks for Linux features and flags.
+# ----------------------------------------------------------------------
+
+AC_ARG_WITH([proc],
+ [AS_HELP_STRING([--with-proc=DIR],
+ [location of a Linux-compatible proc filesystem @<:@default=/proc@:>@])],
+ [],
+ [with_proc=/proc])
+if test -z "$with_proc"; then
+ AC_MSG_ERROR([bad empty value for --with-proc option])
fi
+AC_DEFINE_UNQUOTED([PROCDIR], ["$with_proc"], [Path of proc filesystem.])
+
-AC_ARG_ENABLE(setuid, [AS_HELP_STRING([--enable-setuid], [enable setuid support for platforms that need it])],, enable_setuid="no")
-if test "x$enable_setuid" = xyes; then
- AC_DEFINE(HAVE_SETUID_ENABLED, 1, [Define if setuid support should be enabled.])
+AC_ARG_ENABLE([openvz],
+ [AS_HELP_STRING([--enable-openvz],
+ [enable OpenVZ support @<:@default=no@:>@])],
+ [],
+ [enable_openvz=no])
+if test "x$enable_openvz" = xyes; then
+ AC_DEFINE([HAVE_OPENVZ], [1], [Define if openvz support enabled.])
fi
-AC_ARG_ENABLE(delayacct, [AS_HELP_STRING([--enable-delayacct], [enable Linux delay accounting])],, enable_delayacct="no")
-if test "x$enable_delayacct" = xyes; then
- m4_ifdef([PKG_PROG_PKG_CONFIG], [
- PKG_PROG_PKG_CONFIG()
- PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [missing_libraries="$missing_libraries libnl-3"])
- PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [missing_libraries="$missing_libraries libnl-genl-3"])
- CFLAGS="$CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
- LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
- AC_DEFINE(HAVE_DELAYACCT, 1, [Define if delay accounting support should be enabled.])
- ], [
- pkg_m4_absent=1
- m4_warning([configure is generated without pkg.m4. 'make dist' target will be disabled.])
- AC_MSG_ERROR([htop on Linux requires pkg-config for checking delayacct requirements. Please install pkg-config and run ./autogen.sh to rebuild the configure script.])
- ])
+
+AC_ARG_ENABLE([vserver],
+ [AS_HELP_STRING([--enable-vserver],
+ [enable VServer support @<:@default=no@:>@])],
+ [],
+ [enable_vserver=no])
+if test "x$enable_vserver" = xyes; then
+ AC_DEFINE([HAVE_VSERVER], [1], [Define if VServer support enabled.])
+fi
+
+
+AC_ARG_ENABLE([ancient_vserver],
+ [AS_HELP_STRING([--enable-ancient-vserver],
+ [enable ancient VServer support (implies --enable-vserver) @<:@default=no@:>@])],
+ [],
+ [enable_ancient_vserver=no])
+if test "x$enable_ancient_vserver" = xyes; then
+ if test "x$enable_vserver" != xyes; then
+ enable_vserver=implied
+ fi
+ AC_DEFINE([HAVE_VSERVER], [1], [Define if VServer support enabled.])
+ AC_DEFINE([HAVE_ANCIENT_VSERVER], [1], [Define if ancient vserver support enabled.])
fi
-AC_ARG_WITH(sensors, [AS_HELP_STRING([--with-sensors], [Compile with libsensors support for reading temperature data. Only requires libsensors headers at compile time, at runtime libsensors is loaded via dlopen.])],, with_sensors="check")
-if test "x$with_sensors" = xyes; then
- AC_CHECK_HEADERS([sensors/sensors.h], [], [missing_headers="$missing_headers $ac_header"])
-elif test "x$with_sensors" = xcheck; then
- with_sensors=yes
- AC_CHECK_HEADERS([sensors/sensors.h], [], [with_sensors=no])
+
+AC_ARG_ENABLE([capabilities],
+ [AS_HELP_STRING([--enable-capabilities],
+ [enable Linux capabilities support; requires libcap @<:@default=check@:>@])],
+ [],
+ [enable_capabilities=check])
+case "$enable_capabilities" in
+ no)
+ ;;
+ check)
+ enable_capabilities=yes
+ AC_CHECK_LIB([cap], [cap_init], [], [enable_capabilities=no])
+ AC_CHECK_HEADERS([sys/capability.h], [], [enable_capabilities=no])
+ ;;
+ yes)
+ AC_CHECK_LIB([cap], [cap_init], [], [AC_MSG_ERROR([can not find required library libcap])])
+ AC_CHECK_HEADERS([sys/capability.h], [], [AC_MSG_ERROR([can not find required header file sys/capability.h])])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_capabilities' for --enable-capabilities])
+ ;;
+esac
+
+
+AC_ARG_ENABLE([delayacct],
+ [AS_HELP_STRING([--enable-delayacct],
+ [enable Linux delay accounting support; requires pkg-config, libnl-3 and libnl-genl-3 @<:@default=check@:>@])],
+ [],
+ [enable_delayacct=check])
+case "$enable_delayacct" in
+ no)
+ ;;
+ check)
+ if test "$my_htop_platform" != linux; then
+ enable_delayacct=no
+ elif test "$enable_static" = yes; then
+ enable_delayacct=no
+ else
+ m4_ifdef([PKG_PROG_PKG_CONFIG], [
+ enable_delayacct=yes
+ PKG_PROG_PKG_CONFIG()
+ PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [enable_delayacct=no])
+ PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [enable_delayacct=no])
+ if test "$enable_delayacct" = yes; then
+ CFLAGS="$CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
+ LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
+ AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
+ fi
+ ], [
+ enable_delayacct=no
+ AC_MSG_NOTICE([Linux delay accounting support can not be enabled, cause pkg-config is required for checking its availability])
+ ])
+ fi
+ ;;
+ yes)
+ m4_ifdef([PKG_PROG_PKG_CONFIG], [
+ PKG_PROG_PKG_CONFIG()
+ PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [AC_MSG_ERROR([can not find required library libnl3])])
+ PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [AC_MSG_ERROR([can not find required library libnl3genl])])
+ CFLAGS="$CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
+ LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
+ AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
+ ], [
+ pkg_m4_absent=1
+ m4_warning([configure is generated without pkg.m4. 'make dist' target will be disabled.])
+ AC_MSG_ERROR([htop on Linux requires pkg-config for checking delayacct requirements. Please install pkg-config and run ./autogen.sh to rebuild the configure script.])
+ ])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_delayacct' for --enable-delayacct])
+ ;;
+esac
+
+
+AC_ARG_ENABLE([sensors],
+ [AS_HELP_STRING([--enable-sensors],
+ [enable libsensors support for reading temperature data; requires only libsensors headers at compile time, at runtime libsensors is loaded via dlopen @<:@default=check@:>@])],
+ [],
+ [enable_sensors=check])
+case "$enable_sensors" in
+ no)
+ ;;
+ check)
+ enable_sensors=yes
+ if test "$enable_static" = yes; then
+ AC_CHECK_LIB([sensors], [sensors_init], [], [enable_sensors=no])
+ fi
+ AC_CHECK_HEADERS([sensors/sensors.h], [], [enable_sensors=no])
+ ;;
+ yes)
+ if test "$enable_static" = yes; then
+ AC_CHECK_LIB([sensors], [sensors_init], [], [AC_MSG_ERROR([can not find required library libsensors])])
+ fi
+ AC_CHECK_HEADERS([sensors/sensors.h], [], [AC_MSG_ERROR([can not find required header file sensors/sensors.h])])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_sensors' for --enable-sensors])
+ ;;
+esac
+if test "$enable_sensors" = yes || test "$my_htop_platform" = freebsd; then
+ AC_DEFINE([BUILD_WITH_CPU_TEMP], [1], [Define if CPU temperature option should be enabled.])
fi
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for compiler warnings.
+# ----------------------------------------------------------------------
+
AM_CFLAGS="\
-Wall\
-Wcast-align\
@@ -338,6 +606,7 @@ AM_CFLAGS="\
-Wextra\
-Wfloat-equal\
-Wformat=2\
+ -Winit-self\
-Wmissing-format-attribute\
-Wmissing-noreturn\
-Wmissing-prototypes\
@@ -348,43 +617,78 @@ AM_CFLAGS="\
-Wunused\
-Wwrite-strings"
+# FreeBSD uses C11 _Generic in its isnan implementation, even with -std=c99
+if test "$my_htop_platform" = freebsd; then
+ AM_CFLAGS="$AM_CFLAGS -Wno-c11-extensions"
+fi
+
+dnl https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+ _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+ AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+ [AS_VAR_SET(CACHEVAR,[yes])],
+ [AS_VAR_SET(CACHEVAR,[no])])
+ _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
+
AX_CHECK_COMPILE_FLAG([-Wnull-dereference], [AM_CFLAGS="$AM_CFLAGS -Wnull-dereference"], , [-Werror])
-AC_ARG_ENABLE([werror], [AS_HELP_STRING([--enable-werror], [Treat warnings as errors (default: warnings are not errors)])], [enable_werror="$enableval"], [enable_werror=no])
-AS_IF([test "x$enable_werror" = "xyes"], [AM_CFLAGS="$AM_CFLAGS -Werror"])
+AC_ARG_ENABLE([werror],
+ [AS_HELP_STRING([--enable-werror],
+ [Treat warnings as errors @<:@default=no@:>@])],
+ [],
+ [enable_werror=no])
+if test "x$enable_werror" = xyes; then
+ AM_CFLAGS="$AM_CFLAGS -Werror"
+fi
-AC_SUBST([AM_CFLAGS])
+AC_ARG_ENABLE([debug],
+ [AS_HELP_STRING([--enable-debug],
+ [Enable compiling with maximum debug info, asserts and internal sanity checks @<:@default=no@:>@])],
+ [],
+ [enable_debug=no])
+if test "x$enable_debug" != xyes; then
+ AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG"
+else
+ AM_CPPFLAGS="$AM_CPPFLAGS -ggdb3"
+fi
-AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [Enable asserts (default: asserts are disabled)])], [enable_debug="$enableval"], [enable_debug=no])
-AS_IF([test "x$enable_debug" = "xyes"], , [AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG"])
+AC_SUBST([AM_CFLAGS])
AC_SUBST([AM_CPPFLAGS])
-# Bail out on errors.
# ----------------------------------------------------------------------
-if test ! -z "$missing_libraries"; then
- AC_MSG_ERROR([missing libraries: $missing_libraries])
-fi
-if test ! -z "$missing_headers"; then
- AC_MSG_ERROR([missing headers: $missing_headers])
-fi
-AC_DEFINE_UNQUOTED(COPYRIGHT, "(C) 2004-2019 Hisham Muhammad. (C) 2020 htop dev team.", [Copyright message.])
+# ----------------------------------------------------------------------
# We're done, let's go!
# ----------------------------------------------------------------------
+
+AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2021 htop dev team."], [Copyright message.])
+
AM_CONDITIONAL([HTOP_LINUX], [test "$my_htop_platform" = linux])
AM_CONDITIONAL([HTOP_FREEBSD], [test "$my_htop_platform" = freebsd])
AM_CONDITIONAL([HTOP_DRAGONFLYBSD], [test "$my_htop_platform" = dragonflybsd])
+AM_CONDITIONAL([HTOP_NETBSD], [test "$my_htop_platform" = netbsd])
AM_CONDITIONAL([HTOP_OPENBSD], [test "$my_htop_platform" = openbsd])
AM_CONDITIONAL([HTOP_DARWIN], [test "$my_htop_platform" = darwin])
AM_CONDITIONAL([HTOP_SOLARIS], [test "$my_htop_platform" = solaris])
+AM_CONDITIONAL([HTOP_PCP], [test "$my_htop_platform" = pcp])
AM_CONDITIONAL([HTOP_UNSUPPORTED], [test "$my_htop_platform" = unsupported])
+
AC_SUBST(my_htop_platform)
AC_CONFIG_FILES([Makefile htop.1])
AC_OUTPUT
-if test "$my_htop_platform" = "unsupported"; then
+if test "$my_htop_platform" = unsupported; then
echo ""
echo "****************************************************************"
echo "WARNING! This platform is not currently supported by htop."
@@ -401,15 +705,17 @@ AC_MSG_RESULT([
${PACKAGE_NAME} ${VERSION}
platform: $my_htop_platform
- (Linux) proc directory: $PROCDIR
+ os-release file: $with_os_release
+ (Linux) proc directory: $with_proc
(Linux) openvz: $enable_openvz
(Linux) vserver: $enable_vserver
(Linux) ancient vserver: $enable_ancient_vserver
- (Linux) affinity: $enable_linux_affinity
(Linux) delay accounting: $enable_delayacct
- (Linux) sensors: $with_sensors
+ (Linux) sensors: $enable_sensors
+ (Linux) capabilities: $enable_capabilities
unicode: $enable_unicode
+ affinity: $enable_affinity
hwloc: $enable_hwloc
- setuid: $enable_setuid
debug: $enable_debug
+ static: $enable_static
])
diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c
index bedfefe..20e91f7 100644
--- a/darwin/DarwinProcess.c
+++ b/darwin/DarwinProcess.c
@@ -5,7 +5,7 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "DarwinProcess.h"
+#include "darwin/DarwinProcess.h"
#include <libproc.h>
#include <stdio.h>
@@ -14,8 +14,8 @@ in the source distribution for its full text.
#include <mach/mach.h>
#include "CRT.h"
-#include "Platform.h"
#include "Process.h"
+#include "darwin/Platform.h"
const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
@@ -26,24 +26,27 @@ 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_NR] = { .name = "TTY_NR", .title = " TTY ", .description = "Controlling terminal", .flags = 0, },
+ [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, },
[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, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
+ [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, },
[PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
[NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
[STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
-
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
+ [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, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .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_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, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
+ [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, },
[TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, },
};
@@ -95,16 +98,42 @@ static int DarwinProcess_compareByKey(const Process* v1, const Process* v2, Proc
}
}
-bool Process_isThread(const Process* this) {
- (void) this;
- return false;
+static void DarwinProcess_updateExe(pid_t pid, Process* proc) {
+ char path[PROC_PIDPATHINFO_MAXSIZE];
+
+ int r = proc_pidpath(pid, path, sizeof(path));
+ if (r <= 0)
+ return;
+
+ Process_updateExe(proc, path);
}
-static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameOffset) {
+static void DarwinProcess_updateCwd(pid_t pid, Process* proc) {
+ struct proc_vnodepathinfo vpi;
+
+ int r = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
+ if (r <= 0) {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ if (!vpi.pvi_cdir.vip_path[0]) {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ free_and_xStrdup(&proc->procCwd, vpi.pvi_cdir.vip_path);
+}
+
+static void DarwinProcess_updateCmdLine(const struct kinfo_proc* k, Process* proc) {
+ Process_updateComm(proc, k->kp_proc.p_comm);
+
/* This function is from the old Mac version of htop. Originally from ps? */
int mib[3], argmax, nargs, c = 0;
size_t size;
- char *procargs, *sp, *np, *cp, *retval;
+ char *procargs, *sp, *np, *cp;
/* Get the maximum process arguments size. */
mib[0] = CTL_KERN;
@@ -198,7 +227,7 @@ static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameO
/* Save where the argv[0] string starts. */
sp = cp;
- *basenameOffset = 0;
+ int end = 0;
for ( np = NULL; c < nargs && cp < &procargs[size]; cp++ ) {
if ( *cp == '\0' ) {
c++;
@@ -208,8 +237,8 @@ static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameO
}
/* Note location of current '\0'. */
np = cp;
- if (*basenameOffset == 0) {
- *basenameOffset = cp - sp;
+ if (end == 0) {
+ end = cp - sp;
}
}
}
@@ -222,25 +251,29 @@ static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameO
/* Empty or unterminated string. */
goto ERROR_B;
}
- if (*basenameOffset == 0) {
- *basenameOffset = np - sp;
+ if (end == 0) {
+ end = np - sp;
}
- /* Make a copy of the string. */
- retval = xStrdup(sp);
+ Process_updateCmdline(proc, sp, 0, end);
/* Clean up. */
free( procargs );
- return retval;
+ return;
ERROR_B:
free( procargs );
+
ERROR_A:
- retval = xStrdup(k->kp_proc.p_comm);
- *basenameOffset = strlen(retval);
+ Process_updateCmdline(proc, k->kp_proc.p_comm, 0, strlen(k->kp_proc.p_comm));
+}
- return retval;
+// Converts nanoseconds to hundredths of a second (centiseconds) as needed by the "time" field of the Process struct.
+static long long int nanosecondsToCentiseconds(uint64_t nanoseconds) {
+ const uint64_t centiseconds_per_second = 100;
+ const uint64_t nanoseconds_per_second = 1e9;
+ return nanoseconds / nanoseconds_per_second * centiseconds_per_second;
}
void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) {
@@ -270,16 +303,28 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
proc->session = 0; /* TODO Get the session id */
proc->tpgid = ps->kp_eproc.e_tpgid;
proc->tgid = proc->pid;
- proc->st_uid = ps->kp_eproc.e_ucred.cr_uid;
- /* e_tdev = (major << 24) | (minor & 0xffffff) */
- /* e_tdev == -1 for "no device" */
- proc->tty_nr = ps->kp_eproc.e_tdev & 0xff; /* TODO tty_nr is unsigned */
+ 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);
- proc->comm = DarwinProcess_getCmdLine(ps, &(proc->basenameOffset));
+ DarwinProcess_updateExe(ep->p_pid, proc);
+ DarwinProcess_updateCmdLine(ps, proc);
+
+ if (proc->settings->flags & PROCESS_FLAG_CWD) {
+ DarwinProcess_updateCwd(ep->p_pid, proc);
+ }
}
/* Mutable information */
@@ -292,21 +337,25 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
proc->updated = true;
}
-void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double time_interval) {
+void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS) {
struct proc_taskinfo pti;
if (sizeof(pti) == proc_pidinfo(proc->super.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) {
- uint64_t total_existing_time = proc->stime + proc->utime;
- uint64_t total_current_time = pti.pti_total_system + pti.pti_total_user;
+ uint64_t total_existing_time_ns = proc->stime + proc->utime;
+
+ uint64_t user_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_user);
+ uint64_t system_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_system);
+
+ uint64_t total_current_time_ns = user_time_ns + system_time_ns;
- if (total_existing_time && 1E-6 < time_interval) {
- uint64_t total_time_diff = total_current_time - total_existing_time;
- proc->super.percent_cpu = ((double)total_time_diff / time_interval) * 100.0;
+ if (total_existing_time_ns && 1E-6 < timeIntervalNS) {
+ uint64_t total_time_diff_ns = total_current_time_ns - total_existing_time_ns;
+ proc->super.percent_cpu = ((double)total_time_diff_ns / timeIntervalNS) * 100.0;
} else {
proc->super.percent_cpu = 0.0;
}
- proc->super.time = total_current_time / 10000000;
+ proc->super.time = nanosecondsToCentiseconds(total_current_time_ns);
proc->super.nlwp = pti.pti_threadnum;
proc->super.m_virt = pti.pti_virtual_size / ONE_K;
proc->super.m_resident = pti.pti_resident_size / ONE_K;
@@ -314,8 +363,8 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList*
proc->super.percent_mem = (double)pti.pti_resident_size * 100.0
/ (double)dpl->host_info.max_mem;
- proc->stime = pti.pti_total_system;
- proc->utime = pti.pti_total_user;
+ proc->stime = system_time_ns;
+ proc->utime = user_time_ns;
dpl->super.kernelThreads += 0; /*pti.pti_threads_system;*/
dpl->super.userlandThreads += pti.pti_threadnum; /*pti.pti_threads_user;*/
diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h
index f87dd18..0a49eea 100644
--- a/darwin/DarwinProcess.h
+++ b/darwin/DarwinProcess.h
@@ -9,9 +9,8 @@ in the source distribution for its full text.
#include <sys/sysctl.h>
-#include "DarwinProcessList.h"
#include "Settings.h"
-
+#include "darwin/DarwinProcessList.h"
typedef struct DarwinProcess_ {
Process super;
@@ -30,11 +29,9 @@ Process* DarwinProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-bool Process_isThread(const Process* this);
-
void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists);
-void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double time_interval);
+void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS);
/*
* Scan threads for process state information.
diff --git a/darwin/DarwinProcessList.c b/darwin/DarwinProcessList.c
index 4333710..7dd86ff 100644
--- a/darwin/DarwinProcessList.c
+++ b/darwin/DarwinProcessList.c
@@ -5,7 +5,7 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "DarwinProcessList.h"
+#include "darwin/DarwinProcessList.h"
#include <errno.h>
#include <libproc.h>
@@ -19,10 +19,10 @@ in the source distribution for its full text.
#include <sys/sysctl.h>
#include "CRT.h"
-#include "DarwinProcess.h"
-#include "Platform.h"
#include "ProcessList.h"
-#include "zfs/openzfs_sysctl.h"
+#include "darwin/DarwinProcess.h"
+#include "darwin/Platform.h"
+#include "generic/openzfs_sysctl.h"
#include "zfs/ZfsArcStats.h"
@@ -128,13 +128,15 @@ static struct kinfo_proc* ProcessList_getKInfoProcs(size_t* count) {
CRT_fatalError("Unable to get kinfo_procs");
}
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
DarwinProcessList* this = xCalloc(1, sizeof(DarwinProcessList));
- ProcessList_init(&this->super, Class(DarwinProcess), usersTable, pidMatchList, userId);
+ ProcessList_init(&this->super, Class(DarwinProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
/* Initialize the CPU information */
- this->super.cpuCount = ProcessList_allocateCPULoadInfo(&this->prev_load);
+ this->super.activeCPUs = ProcessList_allocateCPULoadInfo(&this->prev_load);
+ // TODO: support offline CPUs and hot swapping
+ this->super.existingCPUs = this->super.activeCPUs;
ProcessList_getHostInfo(&this->host_info);
ProcessList_allocateCPULoadInfo(&this->curr_load);
@@ -158,11 +160,6 @@ void ProcessList_delete(ProcessList* this) {
free(this);
}
-static double ticksToNanoseconds(const double ticks) {
- const double nanos_per_sec = 1e9;
- return (ticks / Platform_timebaseToNS) * (nanos_per_sec / (double) Platform_clockTicksPerSec);
-}
-
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
DarwinProcessList* dpl = (DarwinProcessList*)super;
bool preExisting = true;
@@ -184,13 +181,13 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
/* Get the time difference */
dpl->global_diff = 0;
- for (int i = 0; i < dpl->super.cpuCount; ++i) {
+ for (unsigned int i = 0; i < dpl->super.existingCPUs; ++i) {
for (size_t j = 0; j < CPU_STATE_MAX; ++j) {
dpl->global_diff += dpl->curr_load[i].cpu_ticks[j] - dpl->prev_load[i].cpu_ticks[j];
}
}
- const double time_interval = ticksToNanoseconds(dpl->global_diff) / (double) dpl->super.cpuCount;
+ const double time_interval_ns = Platform_schedulerTicksToNanoseconds(dpl->global_diff) / (double) dpl->super.activeCPUs;
/* Clear the thread counts */
super->kernelThreads = 0;
@@ -211,7 +208,12 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc = (DarwinProcess*)ProcessList_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new);
DarwinProcess_setFromKInfoProc(&proc->super, &ps[i], preExisting);
- DarwinProcess_setFromLibprocPidinfo(proc, dpl, time_interval);
+ DarwinProcess_setFromLibprocPidinfo(proc, dpl, time_interval_ns);
+
+ if (proc->super.st_uid != ps[i].kp_eproc.e_ucred.cr_uid) {
+ proc->super.st_uid = ps[i].kp_eproc.e_ucred.cr_uid;
+ proc->super.user = UsersTable_getRef(super->usersTable, proc->super.st_uid);
+ }
// Disabled for High Sierra due to bug in macOS High Sierra
bool isScanThreadSupported = ! ( CompareKernelVersion(17, 0, 0) >= 0 && CompareKernelVersion(17, 5, 0) < 0);
@@ -223,11 +225,18 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
super->totalTasks += 1;
if (!preExisting) {
- proc->super.user = UsersTable_getRef(super->usersTable, proc->super.st_uid);
-
ProcessList_add(super, &proc->super);
}
}
free(ps);
}
+
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ // TODO: support offline CPUs and hot swapping
+ (void) super; (void) id;
+
+ return true;
+}
diff --git a/darwin/DarwinProcessList.h b/darwin/DarwinProcessList.h
index 1ae2f2b..af1140b 100644
--- a/darwin/DarwinProcessList.h
+++ b/darwin/DarwinProcessList.h
@@ -28,10 +28,12 @@ typedef struct DarwinProcessList_ {
ZfsArcStats zfs;
} DarwinProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* this);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/darwin/Platform.c b/darwin/Platform.c
index a4ed464..93262bb 100644
--- a/darwin/Platform.c
+++ b/darwin/Platform.c
@@ -8,7 +8,7 @@ in the source distribution for its full text.
#include "config.h" // IWYU pragma: keep
-#include "Platform.h"
+#include "darwin/Platform.h"
#include <errno.h>
#include <math.h>
@@ -22,20 +22,26 @@ in the source distribution for its full text.
#include "ClockMeter.h"
#include "CPUMeter.h"
#include "CRT.h"
-#include "DarwinProcessList.h"
#include "DateMeter.h"
#include "DateTimeMeter.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
#include "ProcessLocksScreen.h"
#include "SwapMeter.h"
+#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
+#include "darwin/DarwinProcessList.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsCompressedArcMeter.h"
+#ifdef HAVE_HOST_GET_CLOCK_SERVICE
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
#ifdef HAVE_MACH_MACH_TIME_H
#include <mach/mach_time.h>
#endif
@@ -90,9 +96,11 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
+ &MemorySwapMeter_class,
&TasksMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&UptimeMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
@@ -112,9 +120,9 @@ const MeterClass* const Platform_meterTypes[] = {
NULL
};
-double Platform_timebaseToNS = 1.0;
+static double Platform_nanosecondsPerMachTick = 1.0;
-long Platform_clockTicksPerSec = -1;
+static double Platform_nanosecondsPerSchedulerTick = -1;
void Platform_init(void) {
// Check if we can determine the timebase used on this system.
@@ -122,18 +130,33 @@ void Platform_init(void) {
#ifdef HAVE_MACH_TIMEBASE_INFO
mach_timebase_info_data_t info;
mach_timebase_info(&info);
- Platform_timebaseToNS = (double)info.numer / (double)info.denom;
+ Platform_nanosecondsPerMachTick = (double)info.numer / (double)info.denom;
#else
- Platform_timebaseToNS = 1.0;
+ Platform_nanosecondsPerMachTick = 1.0;
#endif
- // Determine the number of clock ticks per second
+ // Determine the number of scheduler clock ticks per second
errno = 0;
- Platform_clockTicksPerSec = sysconf(_SC_CLK_TCK);
+ long scheduler_ticks_per_sec = sysconf(_SC_CLK_TCK);
- if (errno || Platform_clockTicksPerSec < 1) {
+ if (errno || scheduler_ticks_per_sec < 1) {
CRT_fatalError("Unable to retrieve clock tick rate");
}
+
+ const double nanos_per_sec = 1e9;
+ Platform_nanosecondsPerSchedulerTick = nanos_per_sec / scheduler_ticks_per_sec;
+}
+
+// Converts ticks in the Mach "timebase" to nanoseconds.
+// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant.
+uint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks) {
+ return (uint64_t) ((double) mach_ticks * Platform_nanosecondsPerMachTick);
+}
+
+// Converts "scheduler ticks" to nanoseconds.
+// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant.
+double Platform_schedulerTicksToNanoseconds(const double scheduler_ticks) {
+ return scheduler_ticks * Platform_nanosecondsPerSchedulerTick;
}
void Platform_done(void) {
@@ -180,24 +203,24 @@ int Platform_getMaxPid() {
static double Platform_setCPUAverageValues(Meter* mtr) {
const ProcessList* dpl = mtr->pl;
- int cpus = dpl->cpuCount;
+ unsigned int activeCPUs = dpl->activeCPUs;
double sumNice = 0.0;
double sumNormal = 0.0;
double sumKernel = 0.0;
double sumPercent = 0.0;
- for (int i = 1; i <= cpus; i++) {
+ for (unsigned int i = 1; i <= dpl->existingCPUs; i++) {
sumPercent += Platform_setCPUValues(mtr, i);
sumNice += mtr->values[CPU_METER_NICE];
sumNormal += mtr->values[CPU_METER_NORMAL];
sumKernel += mtr->values[CPU_METER_KERNEL];
}
- mtr->values[CPU_METER_NICE] = sumNice / cpus;
- mtr->values[CPU_METER_NORMAL] = sumNormal / cpus;
- mtr->values[CPU_METER_KERNEL] = sumKernel / cpus;
- return sumPercent / cpus;
+ mtr->values[CPU_METER_NICE] = sumNice / activeCPUs;
+ mtr->values[CPU_METER_NORMAL] = sumNormal / activeCPUs;
+ mtr->values[CPU_METER_KERNEL] = sumKernel / activeCPUs;
+ return sumPercent / activeCPUs;
}
-double Platform_setCPUValues(Meter* mtr, int cpu) {
+double Platform_setCPUValues(Meter* mtr, unsigned int cpu) {
if (cpu == 0) {
return Platform_setCPUAverageValues(mtr);
@@ -239,7 +262,9 @@ void Platform_setMemoryValues(Meter* mtr) {
mtr->total = dpl->host_info.max_mem / 1024;
mtr->values[0] = (double)(vm->active_count + vm->wire_count) * page_K;
mtr->values[1] = (double)vm->purgeable_count * page_K;
- mtr->values[2] = (double)vm->inactive_count * page_K;
+ // mtr->values[2] = "shared memory, like tmpfs and shm"
+ mtr->values[3] = (double)vm->inactive_count * page_K;
+ // mtr->values[4] = "available memory"
}
void Platform_setSwapValues(Meter* mtr) {
@@ -316,14 +341,14 @@ char* Platform_getProcessEnv(pid_t pid) {
}
char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
- (void)pid;
- (void)inode;
- return NULL;
+ (void)pid;
+ (void)inode;
+ return NULL;
}
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -332,78 +357,83 @@ bool Platform_getDiskIO(DiskIOData* data) {
return false;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
+bool Platform_getNetworkIO(NetworkIOData* data) {
// TODO
- *bytesReceived = 0;
- *packetsReceived = 0;
- *bytesTransmitted = 0;
- *packetsTransmitted = 0;
+ (void)data;
return false;
}
void Platform_getBattery(double* percent, ACPresence* isOnAC) {
- CFTypeRef power_sources = IOPSCopyPowerSourcesInfo();
-
*percent = NAN;
*isOnAC = AC_ERROR;
- if (NULL == power_sources)
- return;
+ CFArrayRef list = NULL;
- CFArrayRef list = IOPSCopyPowerSourcesList(power_sources);
- CFDictionaryRef battery = NULL;
- int len;
-
- if (NULL == list) {
- CFRelease(power_sources);
+ CFTypeRef power_sources = IOPSCopyPowerSourcesInfo();
+ if (!power_sources)
+ goto cleanup;
- return;
- }
+ list = IOPSCopyPowerSourcesList(power_sources);
+ if (!list)
+ goto cleanup;
- len = CFArrayGetCount(list);
+ double cap_current = 0.0;
+ double cap_max = 0.0;
/* Get the battery */
- for (int i = 0; i < len && battery == NULL; ++i) {
- CFDictionaryRef candidate = IOPSGetPowerSourceDescription(power_sources,
- CFArrayGetValueAtIndex(list, i)); /* GET rule */
- CFStringRef type;
-
- if (NULL != candidate) {
- type = (CFStringRef) CFDictionaryGetValue(candidate,
- CFSTR(kIOPSTransportTypeKey)); /* GET rule */
-
- if (kCFCompareEqualTo == CFStringCompare(type, CFSTR(kIOPSInternalType), 0)) {
- CFRetain(candidate);
- battery = candidate;
- }
- }
- }
+ for (int i = 0, len = CFArrayGetCount(list); i < len; ++i) {
+ CFDictionaryRef power_source = IOPSGetPowerSourceDescription(power_sources, CFArrayGetValueAtIndex(list, i)); /* GET rule */
+
+ if (!power_source)
+ continue;
+
+ CFStringRef power_type = CFDictionaryGetValue(power_source, CFSTR(kIOPSTransportTypeKey)); /* GET rule */
+
+ if (kCFCompareEqualTo != CFStringCompare(power_type, CFSTR(kIOPSInternalType), 0))
+ continue;
- if (NULL != battery) {
/* Determine the AC state */
- CFStringRef power_state = CFDictionaryGetValue(battery, CFSTR(kIOPSPowerSourceStateKey));
+ CFStringRef power_state = CFDictionaryGetValue(power_source, CFSTR(kIOPSPowerSourceStateKey));
- *isOnAC = (kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0))
- ? AC_PRESENT
- : AC_ABSENT;
+ if (*isOnAC != AC_PRESENT)
+ *isOnAC = (kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0)) ? AC_PRESENT : AC_ABSENT;
/* Get the percentage remaining */
- double current;
- double max;
+ double tmp;
+ CFNumberGetValue(CFDictionaryGetValue(power_source, CFSTR(kIOPSCurrentCapacityKey)), kCFNumberDoubleType, &tmp);
+ cap_current += tmp;
+ CFNumberGetValue(CFDictionaryGetValue(power_source, CFSTR(kIOPSMaxCapacityKey)), kCFNumberDoubleType, &tmp);
+ cap_max += tmp;
+ }
- CFNumberGetValue(CFDictionaryGetValue(battery, CFSTR(kIOPSCurrentCapacityKey)),
- kCFNumberDoubleType, &current);
- CFNumberGetValue(CFDictionaryGetValue(battery, CFSTR(kIOPSMaxCapacityKey)),
- kCFNumberDoubleType, &max);
+ if (cap_max > 0.0)
+ *percent = 100.0 * cap_current / cap_max;
- *percent = (current * 100.0) / max;
+cleanup:
+ if (list)
+ CFRelease(list);
- CFRelease(battery);
- }
+ if (power_sources)
+ CFRelease(power_sources);
+}
+
+void Platform_gettime_monotonic(uint64_t* msec) {
+
+#ifdef HAVE_HOST_GET_CLOCK_SERVICE
+
+ clock_serv_t cclock;
+ mach_timespec_t mts;
+
+ host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
+ clock_get_time(cclock, &mts);
+ mach_port_deallocate(mach_task_self(), cclock);
+
+ *msec = ((uint64_t)mts.tv_sec * 1000) + ((uint64_t)mts.tv_nsec / 1000000);
+
+#else
+
+ Generic_gettime_monotonic(msec);
+
+#endif
- CFRelease(list);
- CFRelease(power_sources);
}
diff --git a/darwin/Platform.h b/darwin/Platform.h
index 623063b..e7647db 100644
--- a/darwin/Platform.h
+++ b/darwin/Platform.h
@@ -14,18 +14,19 @@ in the source distribution for its full text.
#include "Action.h"
#include "BatteryMeter.h"
#include "CPUMeter.h"
-#include "DarwinProcess.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "NetworkIOMeter.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "darwin/DarwinProcess.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
extern const ProcessField Platform_defaultFields[];
-extern double Platform_timebaseToNS;
-
-extern long Platform_clockTicksPerSec;
-
extern const SignalItem Platform_signals[];
extern const unsigned int Platform_numberOfSignals;
@@ -34,6 +35,14 @@ extern const MeterClass* const Platform_meterTypes[];
void Platform_init(void);
+// Converts ticks in the Mach "timebase" to nanoseconds.
+// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant.
+uint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks);
+
+// Converts "scheduler ticks" to nanoseconds.
+// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant.
+double Platform_schedulerTicksToNanoseconds(const double scheduler_ticks);
+
void Platform_done(void);
void Platform_setBindings(Htop_Action* keys);
@@ -44,7 +53,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* mtr, int cpu);
+double Platform_setCPUValues(Meter* mtr, unsigned int cpu);
void Platform_setMemoryValues(Meter* mtr);
@@ -62,11 +71,48 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC);
+
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return false;
+}
+
+static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+void Platform_gettime_monotonic(uint64_t* msec);
+
+static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) { return NULL; }
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; }
-void Platform_getBattery(double *percent, ACPresence *isOnAC);
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
#endif
diff --git a/darwin/ProcessField.h b/darwin/ProcessField.h
index 5a8090f..25dbb45 100644
--- a/darwin/ProcessField.h
+++ b/darwin/ProcessField.h
@@ -10,6 +10,8 @@ in the source distribution for its full text.
#define PLATFORM_PROCESS_FIELDS \
TRANSLATED = 100, \
+ \
+ DUMMY_BUMP_FIELD = CWD, \
// End of list
diff --git a/docs/styleguide.md b/docs/styleguide.md
index a0856c4..d8fe264 100644
--- a/docs/styleguide.md
+++ b/docs/styleguide.md
@@ -222,3 +222,20 @@ 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.
Documentation files reside in the `docs/` directory and have a `.md` extension.
+
+Writing pull-requests (PRs)
+---------------------------
+
+When writing your PR or patch, the set of patches should contain the minimal changes required.
+Each patch in itself should ideally be self-contained and runable.
+
+A PR should not contain any merge commits.
+To follow the upstream branch of your PR rebase your work instead.
+
+Avoid small commits that just fix typos that another of your commits introduced.
+Instead squash those changes in the appropriate commit that introduced that mistake.
+Git offers `git commit --fixup=<commit>` and `git rebase -i --autosquash` to help you with this.
+
+Your final PR should contain a minimal set of reasonably sized commits that by themselves are easy to review.
+
+Rebase early. Rebase often.
diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c
index 15b1a69..13cef0a 100644
--- a/dragonflybsd/DragonFlyBSDProcess.c
+++ b/dragonflybsd/DragonFlyBSDProcess.c
@@ -6,17 +6,17 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "Process.h"
-#include "ProcessList.h"
-#include "DragonFlyBSDProcess.h"
-#include "Platform.h"
-#include "CRT.h"
+#include "dragonflybsd/DragonFlyBSDProcess.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
+#include "CRT.h"
+
+#include "dragonflybsd/Platform.h"
+
const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, },
@@ -26,24 +26,28 @@ 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_NR] = { .name = "TTY_NR", .title = " TTY ", .description = "Controlling terminal", .flags = 0, },
+ [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, },
[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, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
+ [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, },
[PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
[NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
[STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
+ [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, },
- [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, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .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_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, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
+ [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process", .flags = 0, },
+ [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, },
+ [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, },
};
@@ -69,7 +73,7 @@ static void DragonFlyBSDProcess_writeField(const Process* this, RichString* str,
size_t n = sizeof(buffer) - 1;
switch (field) {
// add Platform-specific fields here
- case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, (fp->kernel ? -1 : this->pid)); break;
+ case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_isKernelThread(this) ? -1 : this->pid); break;
case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break;
case JAIL: Process_printLeftAlignedField(str, attr, fp->jname, 11); return;
default:
@@ -94,16 +98,6 @@ static int DragonFlyBSDProcess_compareByKey(const Process* v1, const Process* v2
}
}
-bool Process_isThread(const Process* this) {
- const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) this;
-
- if (fp->kernel == 1 ) {
- return 1;
- } else {
- return (Process_isUserlandThread(this));
- }
-}
-
const ProcessClass DragonFlyBSDProcess_class = {
.super = {
.extends = Class(Process),
diff --git a/dragonflybsd/DragonFlyBSDProcess.h b/dragonflybsd/DragonFlyBSDProcess.h
index dc40a99..22cf975 100644
--- a/dragonflybsd/DragonFlyBSDProcess.h
+++ b/dragonflybsd/DragonFlyBSDProcess.h
@@ -8,18 +8,19 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include <stdbool.h>
+
+#include "Object.h"
+#include "Process.h"
+#include "Settings.h"
+
+
typedef struct DragonFlyBSDProcess_ {
Process super;
- int kernel;
int jid;
char* jname;
} DragonFlyBSDProcess;
-#define Process_isKernelThread(_process) (_process->kernel == 1)
-
-//#define Process_isUserlandThread(_process) (_process->pid != _process->tgid)
-#define Process_isUserlandThread(_process) (_process->nlwp > 1)
-
extern const ProcessClass DragonFlyBSDProcess_class;
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
@@ -28,6 +29,4 @@ Process* DragonFlyBSDProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-bool Process_isThread(const Process* this);
-
#endif
diff --git a/dragonflybsd/DragonFlyBSDProcessList.c b/dragonflybsd/DragonFlyBSDProcessList.c
index edf2c86..08e3d7b 100644
--- a/dragonflybsd/DragonFlyBSDProcessList.c
+++ b/dragonflybsd/DragonFlyBSDProcessList.c
@@ -6,23 +6,24 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "ProcessList.h"
-#include "DragonFlyBSDProcessList.h"
-#include "DragonFlyBSDProcess.h"
+#include "dragonflybsd/DragonFlyBSDProcessList.h"
-#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <string.h>
#include <sys/param.h>
#include "CRT.h"
#include "Macros.h"
+#include "dragonflybsd/DragonFlyBSDProcess.h"
+
static int MIB_hw_physmem[2];
static int MIB_vm_stats_vm_v_page_count[4];
@@ -41,12 +42,12 @@ static int MIB_kern_cp_time[2];
static int MIB_kern_cp_times[2];
static int kernelFScale;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
size_t len;
char errbuf[_POSIX2_LINE_MAX];
DragonFlyBSDProcessList* dfpl = xCalloc(1, sizeof(DragonFlyBSDProcessList));
ProcessList* pl = (ProcessList*) dfpl;
- ProcessList_init(pl, Class(DragonFlyBSDProcess), usersTable, pidMatchList, userId);
+ ProcessList_init(pl, Class(DragonFlyBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
// physical memory in system: hw.physmem
// physical page size: hw.pagesize
@@ -94,13 +95,15 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
sysctl(MIB_kern_cp_times, 2, dfpl->cp_times_o, &len, NULL, 0);
}
- pl->cpuCount = MAXIMUM(cpus, 1);
+ pl->existingCPUs = MAXIMUM(cpus, 1);
+ // TODO: support offline CPUs and hot swapping
+ pl->activeCPUs = pl->existingCPUs;
if (cpus == 1 ) {
dfpl->cpus = xRealloc(dfpl->cpus, sizeof(CPUData));
} else {
// on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)
- dfpl->cpus = xRealloc(dfpl->cpus, (pl->cpuCount + 1) * sizeof(CPUData));
+ dfpl->cpus = xRealloc(dfpl->cpus, (pl->existingCPUs + 1) * sizeof(CPUData));
}
len = sizeof(kernelFScale);
@@ -139,8 +142,8 @@ void ProcessList_delete(ProcessList* this) {
static inline void DragonFlyBSDProcessList_scanCPUTime(ProcessList* pl) {
const DragonFlyBSDProcessList* dfpl = (DragonFlyBSDProcessList*) pl;
- int cpus = pl->cpuCount; // actual CPU count
- int maxcpu = cpus; // max iteration (in case we have average + smp)
+ unsigned int cpus = pl->existingCPUs; // actual CPU count
+ unsigned int maxcpu = cpus; // max iteration (in case we have average + smp)
int cp_times_offset;
assert(cpus > 0);
@@ -167,7 +170,7 @@ static inline void DragonFlyBSDProcessList_scanCPUTime(ProcessList* pl) {
sysctl(MIB_kern_cp_times, 2, dfpl->cp_times_n, &sizeof_cp_time_array, NULL, 0);
}
- for (int i = 0; i < maxcpu; i++) {
+ for (unsigned int i = 0; i < maxcpu; i++) {
if (cpus == 1) {
// single CPU box
cp_time_n = dfpl->cp_time_n;
@@ -261,29 +264,88 @@ static inline void DragonFlyBSDProcessList_scanMemoryInfo(ProcessList* pl) {
pl->usedSwap *= pageSizeKb;
}
-static char* DragonFlyBSDProcessList_readProcessName(kvm_t* kd, const struct kinfo_proc* kproc, int* basenameEnd) {
+//static void DragonFlyBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) {
+// const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, kproc->kp_pid };
+// char buffer[2048];
+// size_t size = sizeof(buffer);
+// if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+// Process_updateExe(proc, NULL);
+// return;
+// }
+//
+// /* Kernel threads return an empty buffer */
+// if (buffer[0] == '\0') {
+// Process_updateExe(proc, NULL);
+// return;
+// }
+//
+// Process_updateExe(proc, buffer);
+//}
+
+static void DragonFlyBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) {
+ if (Process_isKernelThread(proc))
+ return;
+
+ char path[32];
+ xSnprintf(path, sizeof(path), "/proc/%d/file", kproc->kp_pid);
+
+ char target[PATH_MAX];
+ ssize_t ret = readlink(path, target, sizeof(target) - 1);
+ if (ret <= 0)
+ return;
+
+ target[ret] = '\0';
+ Process_updateExe(proc, target);
+}
+
+static void DragonFlyBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
+ const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, kproc->kp_pid };
+ char buffer[2048];
+ size_t size = sizeof(buffer);
+ if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ /* Kernel threads return an empty buffer */
+ if (buffer[0] == '\0') {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ free_and_xStrdup(&proc->procCwd, buffer);
+}
+
+static void DragonFlyBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
+ Process_updateComm(proc, kproc->kp_comm);
+
char** argv = kvm_getargv(kd, kproc, 0);
- if (!argv) {
- return xStrdup(kproc->kp_comm);
+ if (!argv || !argv[0]) {
+ Process_updateCmdline(proc, kproc->kp_comm, 0, strlen(kproc->kp_comm));
+ return;
}
- int len = 0;
+
+ size_t len = 0;
for (int i = 0; argv[i]; i++) {
len += strlen(argv[i]) + 1;
}
- char* comm = xMalloc(len);
- char* at = comm;
- *basenameEnd = 0;
+
+ char* cmdline = xMalloc(len);
+ char* at = cmdline;
+ int end = 0;
for (int i = 0; argv[i]; i++) {
at = stpcpy(at, argv[i]);
- if (!*basenameEnd) {
- *basenameEnd = at - comm;
+ if (end == 0) {
+ end = at - cmdline;
}
- *at = ' ';
- at++;
+ *at++ = ' ';
}
at--;
*at = '\0';
- return comm;
+
+ Process_updateCmdline(proc, cmdline, 0, end);
}
static inline void DragonFlyBSDProcessList_scanJails(DragonFlyBSDProcessList* dfpl) {
@@ -295,6 +357,7 @@ static inline void DragonFlyBSDProcessList_scanJails(DragonFlyBSDProcessList* df
if (sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1) {
CRT_fatalError("initial sysctlbyname / jail.list failed");
}
+
retry:
if (len == 0)
return;
@@ -312,11 +375,13 @@ retry:
if (dfpl->jails) {
Hashtable_delete(dfpl->jails);
}
+
dfpl->jails = Hashtable_new(20, true);
curpos = jls;
while (curpos) {
int jailid;
char* str_hostname;
+
nextpos = strchr(curpos, '\n');
if (nextpos) {
*nextpos++ = 0;
@@ -346,6 +411,7 @@ static char* DragonFlyBSDProcessList_readJailName(DragonFlyBSDProcessList* dfpl,
} else {
jname = xStrdup("-");
}
+
return jname;
}
@@ -366,7 +432,6 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
int count = 0;
- // TODO Kernel Threads seem to be skipped, need to figure out the correct flag
const struct kinfo_proc* kprocs = kvm_getprocs(dfpl->kd, KERN_PROC_ALL | (!hideUserlandThreads ? KERN_PROC_FLAG_LWP : 0), 0, &count);
for (int i = 0; i < count; i++) {
@@ -378,32 +443,47 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
Process* proc = ProcessList_getProcess(super, kproc->kp_ktaddr ? (pid_t)kproc->kp_ktaddr : kproc->kp_pid, &preExisting, DragonFlyBSDProcess_new);
DragonFlyBSDProcess* dfp = (DragonFlyBSDProcess*) proc;
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(dfp)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
if (!preExisting) {
dfp->jid = kproc->kp_jailid;
if (kproc->kp_ktaddr && kproc->kp_flags & P_SYSTEM) {
// dfb kernel threads all have the same pid, so we misuse the kernel thread address to give them a unique identifier
proc->pid = (pid_t)kproc->kp_ktaddr;
- dfp->kernel = 1;
+ proc->isKernelThread = true;
} else {
proc->pid = kproc->kp_pid; // process ID
- dfp->kernel = 0;
+ proc->isKernelThread = false;
}
- proc->ppid = kproc->kp_ppid; // parent process id
+ proc->isUserlandThread = kproc->kp_nthreads > 1;
+ proc->ppid = kproc->kp_ppid; // parent process id
proc->tpgid = kproc->kp_tpgid; // tty process group id
//proc->tgid = kproc->kp_lwp.kl_tid; // thread group id
proc->tgid = kproc->kp_pid; // thread group id
proc->pgrp = kproc->kp_pgid; // process group id
proc->session = kproc->kp_sid;
- proc->tty_nr = kproc->kp_tdev; // control terminal device number
proc->st_uid = kproc->kp_uid; // user ID
proc->processor = kproc->kp_lwp.kl_origcpu;
proc->starttime_ctime = kproc->kp_start.tv_sec;
+ Process_fillStarttimeBuffer(proc);
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
+ proc->tty_nr = kproc->kp_tdev; // control terminal device number
+ const char* name = (kproc->kp_tdev != NODEV) ? devname(kproc->kp_tdev, S_IFCHR) : NULL;
+ if (!name) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
+
+ DragonFlyBSDProcessList_updateExe(kproc, proc);
+ DragonFlyBSDProcessList_updateProcessName(dfpl->kd, kproc, proc);
+
+ if (settings->flags & PROCESS_FLAG_CWD) {
+ DragonFlyBSDProcessList_updateCwd(kproc, proc);
+ }
+
ProcessList_add(super, proc);
- proc->comm = DragonFlyBSDProcessList_readProcessName(dfpl->kd, kproc, &proc->basenameOffset);
+
dfp->jname = DragonFlyBSDProcessList_readJailName(dfpl, kproc->kp_jailid);
} else {
proc->processor = kproc->kp_lwp.kl_cpuid;
@@ -419,8 +499,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
}
if (settings->updateProcessNames) {
- free(proc->comm);
- proc->comm = DragonFlyBSDProcessList_readProcessName(dfpl->kd, kproc, &proc->basenameOffset);
+ DragonFlyBSDProcessList_updateProcessName(dfpl->kd, kproc, proc);
}
}
@@ -434,7 +513,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
if (proc->percent_cpu > 0.1) {
// system idle process should own all CPU time left regardless of CPU count
- if ( strcmp("idle", kproc->kp_comm) == 0 ) {
+ if (String_eq("idle", kproc->kp_comm)) {
isIdleProcess = true;
}
}
@@ -445,18 +524,18 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc->priority = -kproc->kp_lwp.kl_tdprio;
switch(kproc->kp_lwp.kl_rtprio.type) {
- case RTP_PRIO_REALTIME:
- proc->nice = PRIO_MIN - 1 - RTP_PRIO_MAX + kproc->kp_lwp.kl_rtprio.prio;
- break;
- case RTP_PRIO_IDLE:
- proc->nice = PRIO_MAX + 1 + kproc->kp_lwp.kl_rtprio.prio;
- break;
- case RTP_PRIO_THREAD:
- proc->nice = PRIO_MIN - 1 - RTP_PRIO_MAX - kproc->kp_lwp.kl_rtprio.prio;
- break;
- default:
- proc->nice = kproc->kp_nice;
- break;
+ case RTP_PRIO_REALTIME:
+ proc->nice = PRIO_MIN - 1 - RTP_PRIO_MAX + kproc->kp_lwp.kl_rtprio.prio;
+ break;
+ case RTP_PRIO_IDLE:
+ proc->nice = PRIO_MAX + 1 + kproc->kp_lwp.kl_rtprio.prio;
+ break;
+ case RTP_PRIO_THREAD:
+ proc->nice = PRIO_MIN - 1 - RTP_PRIO_MAX - kproc->kp_lwp.kl_rtprio.prio;
+ break;
+ default:
+ proc->nice = kproc->kp_nice;
+ break;
}
// would be nice if we could store multiple states in proc->state (as enum) and have writeField render them
@@ -501,23 +580,31 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
default: proc->state = '?';
}
- if (kproc->kp_flags & P_SWAPPEDOUT) {
+ if (kproc->kp_flags & P_SWAPPEDOUT)
proc->state = 'W';
- }
- if (kproc->kp_flags & P_TRACED) {
+ if (kproc->kp_flags & P_TRACED)
proc->state = 'T';
- }
- if (kproc->kp_flags & P_JAILED) {
+ if (kproc->kp_flags & P_JAILED)
proc->state = 'J';
- }
- if (Process_isKernelThread(dfp)) {
+ if (Process_isKernelThread(proc))
super->kernelThreads++;
- }
super->totalTasks++;
+
if (proc->state == 'R')
super->runningTasks++;
+
+ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
proc->updated = true;
}
}
+
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ // TODO: support offline CPUs and hot swapping
+ (void) super; (void) id;
+
+ return true;
+}
diff --git a/dragonflybsd/DragonFlyBSDProcessList.h b/dragonflybsd/DragonFlyBSDProcessList.h
index 80fb9f3..8a5b31c 100644
--- a/dragonflybsd/DragonFlyBSDProcessList.h
+++ b/dragonflybsd/DragonFlyBSDProcessList.h
@@ -8,14 +8,20 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include <sys/types.h> // required for kvm.h
#include <kvm.h>
-#include <sys/param.h>
#include <osreldate.h>
+#include <stdbool.h>
#include <sys/jail.h>
-#include <sys/uio.h>
+#include <sys/param.h>
#include <sys/resource.h>
+#include <sys/uio.h>
+
#include "Hashtable.h"
-#include "DragonFlyBSDProcess.h"
+#include "ProcessList.h"
+#include "UsersTable.h"
+
+#include "dragonflybsd/DragonFlyBSDProcess.h"
typedef struct CPUData_ {
@@ -47,10 +53,12 @@ typedef struct DragonFlyBSDProcessList_ {
Hashtable* jails;
} DragonFlyBSDProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* this);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c
index 8ce216c..62a1fb6 100644
--- a/dragonflybsd/Platform.c
+++ b/dragonflybsd/Platform.c
@@ -6,29 +6,31 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "Platform.h"
-#include "Macros.h"
-#include "Meter.h"
+#include "dragonflybsd/Platform.h"
+
+#include <math.h>
+#include <time.h>
+#include <sys/resource.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <vm/vm_param.h>
+
+#include "ClockMeter.h"
#include "CPUMeter.h"
+#include "DateMeter.h"
+#include "DateTimeMeter.h"
+#include "HostnameMeter.h"
+#include "LoadAverageMeter.h"
#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
+#include "ProcessList.h"
#include "SwapMeter.h"
+#include "SysArchMeter.h"
#include "TasksMeter.h"
-#include "LoadAverageMeter.h"
#include "UptimeMeter.h"
-#include "ClockMeter.h"
-#include "DateMeter.h"
-#include "DateTimeMeter.h"
-#include "HostnameMeter.h"
-#include "DragonFlyBSDProcess.h"
-#include "DragonFlyBSDProcessList.h"
-
-#include <sys/types.h>
-#include <sys/sysctl.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <vm/vm_param.h>
-#include <time.h>
-#include <math.h>
+#include "dragonflybsd/DragonFlyBSDProcess.h"
+#include "dragonflybsd/DragonFlyBSDProcessList.h"
const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
@@ -80,11 +82,13 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadAverageMeter_class,
&LoadMeter_class,
&MemoryMeter_class,
+ &MemorySwapMeter_class,
&SwapMeter_class,
&TasksMeter_class,
&UptimeMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
&AllCPUs4Meter_class,
@@ -155,9 +159,9 @@ int Platform_getMaxPid() {
return maxPid;
}
-double Platform_setCPUValues(Meter* this, int cpu) {
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
const DragonFlyBSDProcessList* fpl = (const DragonFlyBSDProcessList*) this->pl;
- int cpus = this->pl->cpuCount;
+ unsigned int cpus = this->pl->activeCPUs;
const CPUData* cpuData;
if (cpus == 1) {
@@ -198,13 +202,16 @@ void Platform_setMemoryValues(Meter* this) {
this->total = pl->totalMem;
this->values[0] = pl->usedMem;
this->values[1] = pl->buffersMem;
- this->values[2] = pl->cachedMem;
+ // this->values[2] = "shared memory, like tmpfs and shm"
+ this->values[3] = pl->cachedMem;
+ // this->values[4] = "available memory"
}
void Platform_setSwapValues(Meter* this) {
const ProcessList* pl = this->pl;
this->total = pl->totalSwap;
this->values[0] = pl->usedSwap;
+ this->values[1] = NAN;
}
char* Platform_getProcessEnv(pid_t pid) {
@@ -214,14 +221,14 @@ char* Platform_getProcessEnv(pid_t pid) {
}
char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
- (void)pid;
- (void)inode;
- return NULL;
+ (void)pid;
+ (void)inode;
+ return NULL;
}
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -230,15 +237,9 @@ bool Platform_getDiskIO(DiskIOData* data) {
return false;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
+bool Platform_getNetworkIO(NetworkIOData* data) {
// TODO
- *bytesReceived = 0;
- *packetsReceived = 0;
- *bytesTransmitted = 0;
- *packetsTransmitted = 0;
+ (void)data;
return false;
}
diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h
index 3c5d9cb..48ed1e9 100644
--- a/dragonflybsd/Platform.h
+++ b/dragonflybsd/Platform.h
@@ -9,13 +9,23 @@ in the source distribution for its full text.
*/
#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
#include <sys/types.h>
#include "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "Macros.h"
+#include "Meter.h"
+#include "NetworkIOMeter.h"
+#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
extern const ProcessField Platform_defaultFields[];
@@ -38,7 +48,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* this, int cpu);
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
void Platform_setMemoryValues(Meter* this);
@@ -52,11 +62,50 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
void Platform_getBattery(double* percent, ACPresence* isOnAC);
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return false;
+}
+
+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) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) { return NULL; }
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; }
+
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+
#endif
diff --git a/dragonflybsd/ProcessField.h b/dragonflybsd/ProcessField.h
index a32e3d3..03dfc14 100644
--- a/dragonflybsd/ProcessField.h
+++ b/dragonflybsd/ProcessField.h
@@ -11,6 +11,8 @@ in the source distribution for its full text.
#define PLATFORM_PROCESS_FIELDS \
JID = 100, \
JAIL = 101, \
+ \
+ DUMMY_BUMP_FIELD = CWD, \
// End of list
diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c
index 28d482e..9f0386b 100644
--- a/freebsd/FreeBSDProcess.c
+++ b/freebsd/FreeBSDProcess.c
@@ -5,7 +5,7 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "FreeBSDProcess.h"
+#include "freebsd/FreeBSDProcess.h"
#include <stdlib.h>
@@ -26,25 +26,27 @@ 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_NR] = { .name = "TTY_NR", .title = " TTY ", .description = "Controlling terminal", .flags = PROCESS_FLAG_FREEBSD_TTY, },
+ [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, },
[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, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
+ [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of copy-on-write faults", .flags = 0, .defaultSortDesc = true, },
[PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
[NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
[STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
-
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
+ [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, },
- [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, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .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_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, },
- [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
+ [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
+ [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process", .flags = 0, },
+ [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, },
+ [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, },
};
@@ -65,25 +67,16 @@ void Process_delete(Object* cast) {
static void FreeBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
const FreeBSDProcess* fp = (const FreeBSDProcess*) this;
- char buffer[256]; buffer[255] = '\0';
+ char buffer[256];
+ size_t n = sizeof(buffer);
int attr = CRT_colors[DEFAULT_COLOR];
- int n = sizeof(buffer) - 1;
+
switch (field) {
// add FreeBSD-specific fields here
case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break;
case JAIL:
Process_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11);
return;
- case TTY_NR:
- if (fp->ttyPath) {
- if (fp->ttyPath == nodevStr)
- attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "%-8s", fp->ttyPath);
- } else {
- attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "? ");
- }
- break;
default:
Process_writeField(this, str, field);
return;
@@ -101,23 +94,11 @@ 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 TTY_NR:
- return SPACESHIP_NULLSTR(p1->ttyPath, p2->ttyPath);
default:
return Process_compareByKey_Base(v1, v2, key);
}
}
-bool Process_isThread(const Process* this) {
- const FreeBSDProcess* fp = (const FreeBSDProcess*) this;
-
- if (fp->kernel == 1 ) {
- return 1;
- } else {
- return Process_isUserlandThread(this);
- }
-}
-
const ProcessClass FreeBSDProcess_class = {
.super = {
.extends = Class(Process),
diff --git a/freebsd/FreeBSDProcess.h b/freebsd/FreeBSDProcess.h
index 6bf2c93..0e0bd59 100644
--- a/freebsd/FreeBSDProcess.h
+++ b/freebsd/FreeBSDProcess.h
@@ -14,26 +14,12 @@ in the source distribution for its full text.
#include "Settings.h"
-#define PROCESS_FLAG_FREEBSD_TTY 0x0100
-
-extern const char* const nodevStr;
-
typedef struct FreeBSDProcess_ {
Process super;
- int kernel;
int jid;
char* jname;
- const char* ttyPath;
} FreeBSDProcess;
-static inline bool Process_isKernelThread(const Process* this) {
- return ((const FreeBSDProcess*)this)->kernel == 1;
-}
-
-static inline bool Process_isUserlandThread(const Process* this) {
- return this->pid != this->tgid;
-}
-
extern const ProcessClass FreeBSDProcess_class;
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
@@ -42,6 +28,4 @@ Process* FreeBSDProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-bool Process_isThread(const Process* this);
-
#endif
diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessList.c
index 679f640..48c0648 100644
--- a/freebsd/FreeBSDProcessList.c
+++ b/freebsd/FreeBSDProcessList.c
@@ -5,11 +5,13 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "FreeBSDProcessList.h"
+#include "config.h" // IWYU pragma: keep
+
+#include "freebsd/FreeBSDProcessList.h"
#include <assert.h>
-#include <dirent.h>
#include <limits.h>
+#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <sys/_iovec.h>
@@ -19,7 +21,6 @@ in the source distribution for its full text.
#include <sys/priority.h>
#include <sys/proc.h>
#include <sys/resource.h>
-#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -34,11 +35,9 @@ in the source distribution for its full text.
#include "ProcessList.h"
#include "Settings.h"
#include "XUtils.h"
+#include "generic/openzfs_sysctl.h"
#include "zfs/ZfsArcStats.h"
-#include "zfs/openzfs_sysctl.h"
-
-char jail_errmsg[JAIL_ERRMSGLEN];
static int MIB_hw_physmem[2];
static int MIB_vm_stats_vm_v_page_count[4];
@@ -57,12 +56,12 @@ static int MIB_kern_cp_time[2];
static int MIB_kern_cp_times[2];
static int kernelFScale;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* DynamicColumns, Hashtable* pidMatchList, uid_t userId) {
size_t len;
char errbuf[_POSIX2_LINE_MAX];
FreeBSDProcessList* fpl = xCalloc(1, sizeof(FreeBSDProcessList));
ProcessList* pl = (ProcessList*) fpl;
- ProcessList_init(pl, Class(FreeBSDProcess), usersTable, pidMatchList, userId);
+ ProcessList_init(pl, Class(FreeBSDProcess), usersTable, dynamicMeters, DynamicColumns, pidMatchList, userId);
// physical memory in system: hw.physmem
// physical page size: hw.pagesize
@@ -126,13 +125,15 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
sysctl(MIB_kern_cp_times, 2, fpl->cp_times_o, &len, NULL, 0);
}
- pl->cpuCount = MAXIMUM(cpus, 1);
+ pl->existingCPUs = MAXIMUM(cpus, 1);
+ // TODO: support offline CPUs and hot swapping
+ pl->activeCPUs = pl->existingCPUs;
if (cpus == 1 ) {
fpl->cpus = xRealloc(fpl->cpus, sizeof(CPUData));
} else {
// on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)
- fpl->cpus = xRealloc(fpl->cpus, (pl->cpuCount + 1) * sizeof(CPUData));
+ fpl->cpus = xRealloc(fpl->cpus, (pl->existingCPUs + 1) * sizeof(CPUData));
}
@@ -147,16 +148,12 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
CRT_fatalError("kvm_openfiles() failed");
}
- fpl->ttys = Hashtable_new(20, true);
-
return pl;
}
void ProcessList_delete(ProcessList* this) {
const FreeBSDProcessList* fpl = (FreeBSDProcessList*) this;
- Hashtable_delete(fpl->ttys);
-
if (fpl->kd) {
kvm_close(fpl->kd);
}
@@ -171,11 +168,11 @@ void ProcessList_delete(ProcessList* this) {
free(this);
}
-static inline void FreeBSDProcessList_scanCPUTime(ProcessList* pl) {
+static inline void FreeBSDProcessList_scanCPU(ProcessList* pl) {
const FreeBSDProcessList* fpl = (FreeBSDProcessList*) pl;
- int cpus = pl->cpuCount; // actual CPU count
- int maxcpu = cpus; // max iteration (in case we have average + smp)
+ unsigned int cpus = pl->existingCPUs; // actual CPU count
+ unsigned int maxcpu = cpus; // max iteration (in case we have average + smp)
int cp_times_offset;
assert(cpus > 0);
@@ -202,7 +199,7 @@ static inline void FreeBSDProcessList_scanCPUTime(ProcessList* pl) {
sysctl(MIB_kern_cp_times, 2, fpl->cp_times_n, &sizeof_cp_time_array, NULL, 0);
}
- for (int i = 0; i < maxcpu; i++) {
+ for (unsigned int i = 0; i < maxcpu; i++) {
if (cpus == 1) {
// single CPU box
cp_time_n = fpl->cp_time_n;
@@ -248,8 +245,69 @@ static inline void FreeBSDProcessList_scanCPUTime(ProcessList* pl) {
cpuData->systemPercent = cp_time_p[CP_SYS];
cpuData->irqPercent = cp_time_p[CP_INTR];
cpuData->systemAllPercent = cp_time_p[CP_SYS] + cp_time_p[CP_INTR];
- // this one is not really used, but we store it anyway
- cpuData->idlePercent = cp_time_p[CP_IDLE];
+ // this one is not really used
+ //cpuData->idlePercent = cp_time_p[CP_IDLE];
+
+ cpuData->temperature = NAN;
+ cpuData->frequency = NAN;
+
+ const int coreId = (cpus == 1) ? 0 : ((int)i - 1);
+ if (coreId < 0)
+ continue;
+
+ // TODO: test with hyperthreading and multi-cpu systems
+ if (pl->settings->showCPUTemperature) {
+ int temperature;
+ size_t len = sizeof(temperature);
+ char mibBuffer[32];
+ xSnprintf(mibBuffer, sizeof(mibBuffer), "dev.cpu.%d.temperature", coreId);
+ int r = sysctlbyname(mibBuffer, &temperature, &len, NULL, 0);
+ if (r == 0)
+ cpuData->temperature = (double)(temperature - 2732) / 10.0; // convert from deci-Kelvin to Celsius
+ }
+
+ // TODO: test with hyperthreading and multi-cpu systems
+ if (pl->settings->showCPUFrequency) {
+ int frequency;
+ size_t len = sizeof(frequency);
+ char mibBuffer[32];
+ xSnprintf(mibBuffer, sizeof(mibBuffer), "dev.cpu.%d.freq", coreId);
+ int r = sysctlbyname(mibBuffer, &frequency, &len, NULL, 0);
+ if (r == 0)
+ cpuData->frequency = frequency; // keep in MHz
+ }
+ }
+
+ // calculate max temperature and avg frequency for average meter and
+ // propagate frequency to all cores if only supplied for CPU 0
+ if (cpus > 1) {
+ if (pl->settings->showCPUTemperature) {
+ double maxTemp = NAN;
+ for (unsigned int i = 1; i < maxcpu; i++) {
+ const double coreTemp = fpl->cpus[i].temperature;
+ if (isnan(coreTemp))
+ continue;
+
+ maxTemp = MAXIMUM(maxTemp, coreTemp);
+ }
+
+ fpl->cpus[0].temperature = maxTemp;
+ }
+
+ if (pl->settings->showCPUFrequency) {
+ const double coreZeroFreq = fpl->cpus[1].frequency;
+ double freqSum = coreZeroFreq;
+ if (!isnan(coreZeroFreq)) {
+ for (unsigned int i = 2; i < maxcpu; i++) {
+ if (isnan(fpl->cpus[i].frequency))
+ fpl->cpus[i].frequency = coreZeroFreq;
+
+ freqSum += fpl->cpus[i].frequency;
+ }
+
+ fpl->cpus[0].frequency = freqSum / (maxcpu - 1);
+ }
+ }
}
}
@@ -321,132 +379,96 @@ static inline void FreeBSDProcessList_scanMemoryInfo(ProcessList* pl) {
pl->usedSwap *= pageSizeKb;
}
-static void FreeBSDProcessList_scanTTYs(ProcessList* pl) {
- FreeBSDProcessList* fpl = (FreeBSDProcessList*) pl;
-
- // scan /dev/tty*
- {
- DIR* dirPtr = opendir("/dev");
- if (!dirPtr)
- return;
-
- int dirFd = dirfd(dirPtr);
- if (dirFd < 0)
- goto err1;
-
- const struct dirent* entry;
- while ((entry = readdir(dirPtr))) {
- if (!String_startsWith(entry->d_name, "tty"))
- continue;
-
- struct stat info;
- if (Compat_fstatat(dirFd, "/dev", entry->d_name, &info, 0) < 0)
- continue;
+static void FreeBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) {
+ if (Process_isKernelThread(proc)) {
+ Process_updateExe(proc, NULL);
+ return;
+ }
- if (!S_ISCHR(info.st_mode))
- continue;
+ const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, kproc->ki_pid };
+ char buffer[2048];
+ size_t size = sizeof(buffer);
+ if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+ Process_updateExe(proc, NULL);
+ return;
+ }
- if (!Hashtable_get(fpl->ttys, info.st_rdev))
- Hashtable_put(fpl->ttys, info.st_rdev, xStrdup(entry->d_name));
- }
+ Process_updateExe(proc, buffer);
+}
-err1:
- closedir(dirPtr);
+static void FreeBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
+ const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, kproc->ki_pid };
+ char buffer[2048];
+ size_t size = sizeof(buffer);
+ if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
}
- // scan /dev/pts/*
- {
- DIR* dirPtr = opendir("/dev/pts");
- if (!dirPtr)
- return;
-
- int dirFd = dirfd(dirPtr);
- if (dirFd < 0)
- goto err2;
-
- const struct dirent* entry;
- while ((entry = readdir(dirPtr))) {
- struct stat info;
- if (Compat_fstatat(dirFd, "/dev/pts", entry->d_name, &info, 0) < 0)
- continue;
-
- if (!S_ISCHR(info.st_mode))
- continue;
-
- if (!Hashtable_get(fpl->ttys, info.st_rdev)) {
- char* path;
- xAsprintf(&path, "pts/%s", entry->d_name);
- Hashtable_put(fpl->ttys, info.st_rdev, path);
- }
- }
-
-err2:
- closedir(dirPtr);
+ /* Kernel threads return an empty buffer */
+ if (buffer[0] == '\0') {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
}
+
+ free_and_xStrdup(&proc->procCwd, buffer);
}
-static char* FreeBSDProcessList_readProcessName(kvm_t* kd, const struct kinfo_proc* kproc, int* basenameEnd) {
+static void FreeBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
+ Process_updateComm(proc, kproc->ki_comm);
+
char** argv = kvm_getargv(kd, kproc, 0);
- if (!argv) {
- return xStrdup(kproc->ki_comm);
+ if (!argv || !argv[0]) {
+ Process_updateCmdline(proc, kproc->ki_comm, 0, strlen(kproc->ki_comm));
+ return;
}
- int len = 0;
+
+ size_t len = 0;
for (int i = 0; argv[i]; i++) {
len += strlen(argv[i]) + 1;
}
- char* comm = xMalloc(len);
- char* at = comm;
- *basenameEnd = 0;
+
+ char* cmdline = xMalloc(len);
+ char* at = cmdline;
+ int end = 0;
for (int i = 0; argv[i]; i++) {
at = stpcpy(at, argv[i]);
- if (!*basenameEnd) {
- *basenameEnd = at - comm;
+ if (end == 0) {
+ end = at - cmdline;
}
- *at = ' ';
- at++;
+ *at++ = ' ';
}
at--;
*at = '\0';
- return comm;
+
+ Process_updateCmdline(proc, cmdline, 0, end);
}
static char* FreeBSDProcessList_readJailName(const struct kinfo_proc* kproc) {
- char* jname = NULL;
- char jnamebuf[MAXHOSTNAMELEN];
+ if (kproc->ki_jid == 0)
+ return xStrdup("-");
- if (kproc->ki_jid != 0) {
- struct iovec jiov[6];
+ char jnamebuf[MAXHOSTNAMELEN] = {0};
+ struct iovec jiov[4];
- memset(jnamebuf, 0, sizeof(jnamebuf));
IGNORE_WCASTQUAL_BEGIN
- *(const void**)&jiov[0].iov_base = "jid";
- jiov[0].iov_len = sizeof("jid");
- jiov[1].iov_base = (void*) &kproc->ki_jid;
- jiov[1].iov_len = sizeof(kproc->ki_jid);
- *(const void**)&jiov[2].iov_base = "name";
- jiov[2].iov_len = sizeof("name");
- jiov[3].iov_base = jnamebuf;
- jiov[3].iov_len = sizeof(jnamebuf);
- *(const void**)&jiov[4].iov_base = "errmsg";
- jiov[4].iov_len = sizeof("errmsg");
- jiov[5].iov_base = jail_errmsg;
- jiov[5].iov_len = JAIL_ERRMSGLEN;
+ *(const void**)&jiov[0].iov_base = "jid";
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = (void*) &kproc->ki_jid;
+ jiov[1].iov_len = sizeof(kproc->ki_jid);
+ *(const void**)&jiov[2].iov_base = "name";
+ jiov[2].iov_len = sizeof("name");
+ jiov[3].iov_base = jnamebuf;
+ jiov[3].iov_len = sizeof(jnamebuf);
IGNORE_WCASTQUAL_END
- jail_errmsg[0] = 0;
- int jid = jail_get(jiov, 6, 0);
- if (jid < 0) {
- if (!jail_errmsg[0]) {
- xSnprintf(jail_errmsg, JAIL_ERRMSGLEN, "jail_get: %s", strerror(errno));
- }
- } else if (jid == kproc->ki_jid) {
- jname = xStrdup(jnamebuf);
- }
- } else {
- jname = xStrdup("-");
- }
+ int jid = jail_get(jiov, 4, 0);
+ if (jid == kproc->ki_jid)
+ return xStrdup(jnamebuf);
- return jname;
+ return NULL;
}
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
@@ -457,37 +479,27 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
openzfs_sysctl_updateArcStats(&fpl->zfs);
FreeBSDProcessList_scanMemoryInfo(super);
- FreeBSDProcessList_scanCPUTime(super);
+ FreeBSDProcessList_scanCPU(super);
// in pause mode only gather global data for meters (CPU/memory/...)
if (pauseProcessUpdate) {
return;
}
- if (settings->flags & PROCESS_FLAG_FREEBSD_TTY) {
- FreeBSDProcessList_scanTTYs(super);
- }
-
int count = 0;
- struct kinfo_proc* kprocs = kvm_getprocs(fpl->kd, KERN_PROC_PROC, 0, &count);
+ const struct kinfo_proc* kprocs = kvm_getprocs(fpl->kd, KERN_PROC_PROC, 0, &count);
for (int i = 0; i < count; i++) {
- struct kinfo_proc* kproc = &kprocs[i];
+ const struct kinfo_proc* kproc = &kprocs[i];
bool preExisting = false;
- // TODO: bool isIdleProcess = false;
Process* proc = ProcessList_getProcess(super, kproc->ki_pid, &preExisting, FreeBSDProcess_new);
FreeBSDProcess* fp = (FreeBSDProcess*) proc;
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
if (!preExisting) {
fp->jid = kproc->ki_jid;
proc->pid = kproc->ki_pid;
- if ( ! ((kproc->ki_pid == 0) || (kproc->ki_pid == 1) ) && kproc->ki_flag & P_SYSTEM) {
- fp->kernel = 1;
- } else {
- fp->kernel = 0;
- }
+ proc->isKernelThread = kproc->ki_pid != 1 && (kproc->ki_flag & P_SYSTEM);
+ proc->isUserlandThread = false;
proc->ppid = kproc->ki_ppid;
proc->tpgid = kproc->ki_tpgid;
proc->tgid = kproc->ki_pid;
@@ -498,8 +510,24 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
Process_fillStarttimeBuffer(proc);
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
ProcessList_add(super, proc);
- proc->comm = FreeBSDProcessList_readProcessName(fpl->kd, kproc, &proc->basenameOffset);
+
+ FreeBSDProcessList_updateExe(kproc, proc);
+ FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
+
+ if (settings->flags & PROCESS_FLAG_CWD) {
+ FreeBSDProcessList_updateCwd(kproc, proc);
+ }
+
fp->jname = FreeBSDProcessList_readJailName(kproc);
+
+ proc->tty_nr = kproc->ki_tdev;
+ const char* name = (kproc->ki_tdev != NODEV) ? devname(kproc->ki_tdev, S_IFCHR) : NULL;
+ if (!name) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
} else {
if (fp->jid != kproc->ki_jid) {
// process can enter jail anytime
@@ -515,8 +543,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
}
if (settings->updateProcessNames) {
- free(proc->comm);
- proc->comm = FreeBSDProcessList_readProcessName(fpl->kd, kproc, &proc->basenameOffset);
+ FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
}
}
@@ -529,19 +556,17 @@ 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);
- /*
- * TODO
- * if (proc->percent_cpu > 0.1) {
- * // system idle process should own all CPU time left regardless of CPU count
- * if ( strcmp("idle", kproc->ki_comm) == 0 ) {
- * isIdleProcess = true;
- * }
- * }
- */
+ if (kproc->ki_stat == SRUN && kproc->ki_oncpu != NOCPU) {
+ proc->processor = kproc->ki_oncpu;
+ } else {
+ proc->processor = kproc->ki_lastcpu;
+ }
+
+ proc->majflt = kproc->ki_cow;
proc->priority = kproc->ki_pri.pri_level - PZERO;
- if (strcmp("intr", kproc->ki_comm) == 0 && kproc->ki_flag & P_SYSTEM) {
+ if (String_eq("intr", kproc->ki_comm) && (kproc->ki_flag & P_SYSTEM)) {
proc->nice = 0; //@etosan: intr kernel process (not thread) has weird nice value
} else if (kproc->ki_pri.pri_class == PRI_TIMESHARE) {
proc->nice = kproc->ki_nice - NZERO;
@@ -562,16 +587,23 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
default: proc->state = '?';
}
- if (settings->flags & PROCESS_FLAG_FREEBSD_TTY) {
- fp->ttyPath = (kproc->ki_tdev == NODEV) ? nodevStr : Hashtable_get(fpl->ttys, kproc->ki_tdev);
- }
-
if (Process_isKernelThread(proc))
super->kernelThreads++;
+ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+
super->totalTasks++;
if (proc->state == 'R')
super->runningTasks++;
proc->updated = true;
}
}
+
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ // TODO: support offline CPUs and hot swapping
+ (void) super; (void) id;
+
+ return true;
+}
diff --git a/freebsd/FreeBSDProcessList.h b/freebsd/FreeBSDProcessList.h
index f18275d..ae82205 100644
--- a/freebsd/FreeBSDProcessList.h
+++ b/freebsd/FreeBSDProcessList.h
@@ -17,16 +17,15 @@ in the source distribution for its full text.
#include "zfs/ZfsArcStats.h"
-#define JAIL_ERRMSGLEN 1024
-extern char jail_errmsg[JAIL_ERRMSGLEN];
-
typedef struct CPUData_ {
double userPercent;
double nicePercent;
double systemPercent;
double irqPercent;
- double idlePercent;
double systemAllPercent;
+
+ double frequency;
+ double temperature;
} CPUData;
typedef struct FreeBSDProcessList_ {
@@ -35,15 +34,11 @@ typedef struct FreeBSDProcessList_ {
unsigned long long int memWire;
unsigned long long int memActive;
- unsigned long long int memInactive;
- unsigned long long int memFree;
ZfsArcStats zfs;
CPUData* cpus;
- Hashtable* ttys;
-
unsigned long* cp_time_o;
unsigned long* cp_time_n;
@@ -52,10 +47,12 @@ typedef struct FreeBSDProcessList_ {
} FreeBSDProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* this);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/freebsd/Platform.c b/freebsd/Platform.c
index c74ab6d..527a455 100644
--- a/freebsd/Platform.c
+++ b/freebsd/Platform.c
@@ -5,7 +5,9 @@ Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "Platform.h"
+#include "config.h" // IWYU pragma: keep
+
+#include "freebsd/Platform.h"
#include <devstat.h>
#include <math.h>
@@ -29,20 +31,22 @@ in the source distribution for its full text.
#include "DateMeter.h"
#include "DateTimeMeter.h"
#include "DiskIOMeter.h"
-#include "FreeBSDProcess.h"
-#include "FreeBSDProcessList.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
#include "Meter.h"
#include "NetworkIOMeter.h"
#include "ProcessList.h"
#include "Settings.h"
#include "SwapMeter.h"
+#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
#include "XUtils.h"
+#include "freebsd/FreeBSDProcess.h"
+#include "freebsd/FreeBSDProcessList.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsCompressedArcMeter.h"
@@ -97,10 +101,12 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
+ &MemorySwapMeter_class,
&TasksMeter_class,
&UptimeMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
&AllCPUs4Meter_class,
@@ -136,7 +142,7 @@ void Platform_setBindings(Htop_Action* keys) {
int Platform_getUptime() {
struct timeval bootTime, currTime;
- int mib[2] = { CTL_KERN, KERN_BOOTTIME };
+ const int mib[2] = { CTL_KERN, KERN_BOOTTIME };
size_t size = sizeof(bootTime);
int err = sysctl(mib, 2, &bootTime, &size, NULL, 0);
@@ -150,7 +156,7 @@ int Platform_getUptime() {
void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
struct loadavg loadAverage;
- int mib[2] = { CTL_VM, VM_LOADAVG };
+ const int mib[2] = { CTL_VM, VM_LOADAVG };
size_t size = sizeof(loadAverage);
int err = sysctl(mib, 2, &loadAverage, &size, NULL, 0);
@@ -175,9 +181,9 @@ int Platform_getMaxPid() {
return maxPid;
}
-double Platform_setCPUValues(Meter* this, int cpu) {
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) this->pl;
- int cpus = this->pl->cpuCount;
+ unsigned int cpus = this->pl->activeCPUs;
const CPUData* cpuData;
if (cpus == 1) {
@@ -205,26 +211,28 @@ double Platform_setCPUValues(Meter* this, int cpu) {
percent = CLAMP(percent, 0.0, 100.0);
- v[CPU_METER_FREQUENCY] = NAN;
- v[CPU_METER_TEMPERATURE] = NAN;
+ v[CPU_METER_FREQUENCY] = cpuData->frequency;
+ v[CPU_METER_TEMPERATURE] = cpuData->temperature;
return percent;
}
void Platform_setMemoryValues(Meter* this) {
- // TODO
const ProcessList* pl = this->pl;
this->total = pl->totalMem;
this->values[0] = pl->usedMem;
this->values[1] = pl->buffersMem;
- this->values[2] = pl->cachedMem;
+ // this->values[2] = "shared memory, like tmpfs and shm"
+ this->values[3] = pl->cachedMem;
+ // this->values[4] = "available memory"
}
void Platform_setSwapValues(Meter* this) {
const ProcessList* pl = this->pl;
this->total = pl->totalSwap;
this->values[0] = pl->usedSwap;
+ this->values[1] = NAN;
}
void Platform_setZfsArcValues(Meter* this) {
@@ -240,7 +248,7 @@ void Platform_setZfsCompressedArcValues(Meter* this) {
}
char* Platform_getProcessEnv(pid_t pid) {
- int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ENV, pid };
+ const int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ENV, pid };
size_t capacity = ARG_MAX;
char* env = xMalloc(capacity);
@@ -261,14 +269,14 @@ char* Platform_getProcessEnv(pid_t pid) {
}
char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
- (void)pid;
- (void)inode;
- return NULL;
+ (void)pid;
+ (void)inode;
+ return NULL;
}
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -285,7 +293,7 @@ bool Platform_getDiskIO(DiskIOData* data) {
int count = current.dinfo->numdevs;
- unsigned long int bytesReadSum = 0, bytesWriteSum = 0, timeSpendSum = 0;
+ unsigned long long int bytesReadSum = 0, bytesWriteSum = 0, timeSpendSum = 0;
// get data
for (int i = 0; i < count; i++) {
@@ -311,24 +319,17 @@ bool Platform_getDiskIO(DiskIOData* data) {
return true;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
- int r;
-
+bool Platform_getNetworkIO(NetworkIOData* data) {
// get number of interfaces
int count;
size_t countLen = sizeof(count);
const int countMib[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_SYSTEM, IFMIB_IFCOUNT };
- r = sysctl(countMib, ARRAYSIZE(countMib), &count, &countLen, NULL, 0);
+ int r = sysctl(countMib, ARRAYSIZE(countMib), &count, &countLen, NULL, 0);
if (r < 0)
return false;
-
- unsigned long int bytesReceivedSum = 0, packetsReceivedSum = 0, bytesTransmittedSum = 0, packetsTransmittedSum = 0;
-
+ memset(data, 0, sizeof(NetworkIOData));
for (int i = 1; i <= count; i++) {
struct ifmibdata ifmd;
size_t ifmdLen = sizeof(ifmd);
@@ -342,16 +343,12 @@ bool Platform_getNetworkIO(unsigned long int* bytesReceived,
if (ifmd.ifmd_flags & IFF_LOOPBACK)
continue;
- bytesReceivedSum += ifmd.ifmd_data.ifi_ibytes;
- packetsReceivedSum += ifmd.ifmd_data.ifi_ipackets;
- bytesTransmittedSum += ifmd.ifmd_data.ifi_obytes;
- packetsTransmittedSum += ifmd.ifmd_data.ifi_opackets;
+ data->bytesReceived += ifmd.ifmd_data.ifi_ibytes;
+ data->packetsReceived += ifmd.ifmd_data.ifi_ipackets;
+ data->bytesTransmitted += ifmd.ifmd_data.ifi_obytes;
+ data->packetsTransmitted += ifmd.ifmd_data.ifi_opackets;
}
- *bytesReceived = bytesReceivedSum;
- *packetsReceived = packetsReceivedSum;
- *bytesTransmitted = bytesTransmittedSum;
- *packetsTransmitted = packetsTransmittedSum;
return true;
}
diff --git a/freebsd/Platform.h b/freebsd/Platform.h
index 36895b8..e7e274c 100644
--- a/freebsd/Platform.h
+++ b/freebsd/Platform.h
@@ -13,10 +13,15 @@ in the source distribution for its full text.
#include "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
#include "Meter.h"
+#include "NetworkIOMeter.h"
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
extern const ProcessField Platform_defaultFields[];
@@ -39,7 +44,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* this, int cpu);
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
void Platform_setMemoryValues(Meter* this);
@@ -57,11 +62,50 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
void Platform_getBattery(double* percent, ACPresence* isOnAC);
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return false;
+}
+
+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) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) { return NULL; }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; }
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+
#endif
diff --git a/freebsd/ProcessField.h b/freebsd/ProcessField.h
index cc89282..6fba6bc 100644
--- a/freebsd/ProcessField.h
+++ b/freebsd/ProcessField.h
@@ -11,6 +11,8 @@ in the source distribution for its full text.
#define PLATFORM_PROCESS_FIELDS \
JID = 100, \
JAIL = 101, \
+ \
+ DUMMY_BUMP_FIELD = CWD, \
// End of list
diff --git a/generic/gettime.c b/generic/gettime.c
new file mode 100644
index 0000000..af06ef3
--- /dev/null
+++ b/generic/gettime.c
@@ -0,0 +1,57 @@
+/*
+htop - generic/gettime.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+#include "config.h" // IWYU pragma: keep
+
+#include "generic/gettime.h"
+
+#include <string.h>
+#include <time.h>
+
+
+void Generic_gettime_realtime(struct timeval* tvp, uint64_t* msec) {
+
+#if defined(HAVE_CLOCK_GETTIME)
+
+ struct timespec ts;
+ if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
+ tvp->tv_sec = ts.tv_sec;
+ tvp->tv_usec = ts.tv_nsec / 1000;
+ *msec = ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000);
+ } else {
+ memset(tvp, 0, sizeof(struct timeval));
+ *msec = 0;
+ }
+
+#else /* lower resolution gettimeofday(2) is always available */
+
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) == 0) {
+ *tvp = tv; /* struct copy */
+ *msec = ((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000);
+ } else {
+ memset(tvp, 0, sizeof(struct timeval));
+ *msec = 0;
+ }
+
+#endif
+}
+
+void Generic_gettime_monotonic(uint64_t* msec) {
+#if defined(HAVE_CLOCK_GETTIME)
+
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
+ *msec = ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000);
+ else
+ *msec = 0;
+
+#else
+
+# error "No monotonic clock available"
+
+#endif
+}
diff --git a/generic/gettime.h b/generic/gettime.h
new file mode 100644
index 0000000..c124d03
--- /dev/null
+++ b/generic/gettime.h
@@ -0,0 +1,18 @@
+#ifndef HEADER_gettime
+#define HEADER_gettime
+/*
+htop - generic/gettime.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdint.h>
+#include <sys/time.h>
+
+
+void Generic_gettime_realtime(struct timeval* tvp, uint64_t* msec);
+
+void Generic_gettime_monotonic(uint64_t* msec);
+
+#endif
diff --git a/generic/hostname.c b/generic/hostname.c
new file mode 100644
index 0000000..2e3d5c3
--- /dev/null
+++ b/generic/hostname.c
@@ -0,0 +1,17 @@
+/*
+htop - generic/hostname.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+#include "config.h" // IWYU pragma: keep
+
+#include "generic/hostname.h"
+
+#include <unistd.h>
+
+
+void Generic_hostname(char* buffer, size_t size) {
+ gethostname(buffer, size - 1);
+ buffer[size - 1] = '\0';
+}
diff --git a/generic/hostname.h b/generic/hostname.h
new file mode 100644
index 0000000..32f0c46
--- /dev/null
+++ b/generic/hostname.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_hostname
+#define HEADER_hostname
+/*
+htop - generic/hostname.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stddef.h>
+
+
+void Generic_hostname(char* buffer, size_t size);
+
+#endif
diff --git a/zfs/openzfs_sysctl.c b/generic/openzfs_sysctl.c
index fd00d61..c7d79bc 100644
--- a/zfs/openzfs_sysctl.c
+++ b/generic/openzfs_sysctl.c
@@ -1,11 +1,11 @@
/*
-htop - zfs/openzfs_sysctl.c
+htop - generic/openzfs_sysctl.c
(C) 2014 Hisham H. Muhammad
Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "zfs/openzfs_sysctl.h"
+#include "generic/openzfs_sysctl.h"
#include <stdlib.h>
#include <sys/types.h> // IWYU pragma: keep
diff --git a/zfs/openzfs_sysctl.h b/generic/openzfs_sysctl.h
index b49128e..dab1305 100644
--- a/zfs/openzfs_sysctl.h
+++ b/generic/openzfs_sysctl.h
@@ -1,7 +1,7 @@
#ifndef HEADER_openzfs_sysctl
#define HEADER_openzfs_sysctl
/*
-htop - zfs/openzfs_sysctl.h
+htop - generic/openzfs_sysctl.h
(C) 2014 Hisham H. Muhammad
Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include "zfs/ZfsArcStats.h"
+
void openzfs_sysctl_init(ZfsArcStats* stats);
void openzfs_sysctl_updateArcStats(ZfsArcStats* stats);
diff --git a/generic/uname.c b/generic/uname.c
new file mode 100644
index 0000000..9263d82
--- /dev/null
+++ b/generic/uname.c
@@ -0,0 +1,98 @@
+/*
+htop - generic/uname.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+#include "config.h" // IWYU pragma: keep
+
+#include "generic/uname.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "Macros.h"
+#include "XUtils.h"
+
+#ifdef HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+
+
+#ifndef OSRELEASEFILE
+#define OSRELEASEFILE "/etc/os-release"
+#endif
+
+static void parseOSRelease(char* buffer, size_t bufferLen) {
+ FILE* stream = fopen(OSRELEASEFILE, "r");
+ if (!stream) {
+ xSnprintf(buffer, bufferLen, "No OS Release");
+ return;
+ }
+
+ char name[64] = {'\0'};
+ char version[64] = {'\0'};
+ char lineBuffer[256];
+ while (fgets(lineBuffer, sizeof(lineBuffer), stream)) {
+ if (String_startsWith(lineBuffer, "PRETTY_NAME=\"")) {
+ const char* start = lineBuffer + strlen("PRETTY_NAME=\"");
+ const char* stop = strrchr(lineBuffer, '"');
+ if (!stop || stop <= start)
+ continue;
+ String_safeStrncpy(buffer, start, MINIMUM(bufferLen, (size_t)(stop - start + 1)));
+ fclose(stream);
+ return;
+ }
+ if (String_startsWith(lineBuffer, "NAME=\"")) {
+ const char* start = lineBuffer + strlen("NAME=\"");
+ const char* stop = strrchr(lineBuffer, '"');
+ if (!stop || stop <= start)
+ continue;
+ String_safeStrncpy(name, start, MINIMUM(sizeof(name), (size_t)(stop - start + 1)));
+ continue;
+ }
+ if (String_startsWith(lineBuffer, "VERSION=\"")) {
+ const char* start = lineBuffer + strlen("VERSION=\"");
+ const char* stop = strrchr(lineBuffer, '"');
+ if (!stop || stop <= start)
+ continue;
+ String_safeStrncpy(version, start, MINIMUM(sizeof(version), (size_t)(stop - start + 1)));
+ continue;
+ }
+ }
+ fclose(stream);
+
+ snprintf(buffer, bufferLen, "%s%s%s", name[0] ? name : "", name[0] && version[0] ? " " : "", version);
+}
+
+char* Generic_uname(void) {
+ static char savedString[
+ /* uname structure fields - manpages recommend sizeof */
+ sizeof(((struct utsname*)0)->sysname) +
+ sizeof(((struct utsname*)0)->release) +
+ sizeof(((struct utsname*)0)->machine) +
+ 16/*markup*/ +
+ 128/*distro*/] = {'\0'};
+ static bool loaded_data = false;
+
+ if (!loaded_data) {
+ struct utsname uname_info;
+ int uname_result = uname(&uname_info);
+
+ char distro[128];
+ parseOSRelease(distro, sizeof(distro));
+
+ 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)
+ snprintf(savedString + written, sizeof(savedString) - written, " @ %s", distro);
+ } else {
+ snprintf(savedString, sizeof(savedString), "%s", distro);
+ }
+
+ loaded_data = true;
+ }
+
+ return savedString;
+}
diff --git a/generic/uname.h b/generic/uname.h
new file mode 100644
index 0000000..820d085
--- /dev/null
+++ b/generic/uname.h
@@ -0,0 +1,12 @@
+#ifndef HEADER_uname
+#define HEADER_uname
+/*
+htop - generic/uname.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2, see the COPYING file
+in the source distribution for its full text.
+*/
+
+char* Generic_uname(void);
+
+#endif
diff --git a/htop.1.in b/htop.1.in
index cd98478..ceec216 100644
--- a/htop.1.in
+++ b/htop.1.in
@@ -1,9 +1,13 @@
.TH "HTOP" "1" "2020" "@PACKAGE_STRING@" "User Commands"
.SH "NAME"
-htop \- interactive process viewer
+htop, pcp-htop \- interactive process viewer
.SH "SYNOPSIS"
.B htop
.RB [ \-dCFhpustvH ]
+.br
+.B pcp\ htop
+.RB [ \-dCFhpustvH ]
+.RB [ \-\-host/-h\ host ]
.SH "DESCRIPTION"
.B htop
is a cross-platform ncurses-based process viewer.
@@ -14,17 +18,30 @@ but allows you to scroll vertically and horizontally, and interact using
a pointing device (mouse).
You can observe all processes running on the system, along with their
command line arguments, as well as view them in a tree format, select
-multiple processes and acting on them all at once.
+multiple processes and act on them all at once.
.LP
Tasks related to processes (killing, renicing) can be done without
entering their PIDs.
+.LP
+.B pcp-htop
+is a version of
+.B htop
+built using the Performance Co-Pilot (PCP) Metrics API (see \c
+.BR PCPIntro (1),
+.BR PMAPI (3)),
+allowing to extend
+.B htop
+to display values from arbitrary metrics.
+See the section below titled
+.B "CONFIG FILES"
+for further details.
.br
.SH "COMMAND-LINE OPTIONS"
Mandatory arguments to long options are mandatory for short options too.
.TP
\fB\-d \-\-delay=DELAY\fR
-Delay between updates, in tenths of seconds. If the delay value is
-less than 1 it is increased to 1, i.e. 1/10 second. If the delay value
+Delay between updates, in tenths of a second. If the delay value is
+less than 1, it is increased to 1, i.e. 1/10 second. If the delay value
is greater than 100, it is decreased to 100, i.e. 10 seconds.
.TP
\fB\-C \-\-no-color \-\-no-colour\fR
@@ -54,6 +71,9 @@ Do not use unicode but ASCII characters for graph meters
\fB\-M \-\-no-mouse\fR
Disable support of mouse control
.TP
+\fB\-\-readonly\fR
+Disable all system and process changing features
+.TP
\fB\-V \-\-version
Output version information and exit
.TP
@@ -63,6 +83,14 @@ requesting a sort order with -s.
.TP
\fB\-H \-\-highlight-changes=DELAY\fR
Highlight new and old processes
+.TP
+\fB \-\-drop-capabilities[=off|basic|strict]\fR
+Linux only; requires libcap support.
+.br
+Drop unneeded Linux capabilities.
+In strict mode features like killing, changing process priorities, and reading
+process delay accounting information will not work, due to less capabilities
+held.
.SH "INTERACTIVE COMMANDS"
The following commands are supported while in
.BR htop :
@@ -165,6 +193,13 @@ This can only be done by the superuser.
.B F8, [
Decrease the selected process's priority (add to 'nice' value)
.TP
+.B Shift-F7, }
+Increase the selected process's autogroup priority (subtract from autogroup 'nice' value).
+This can only be done by the superuser.
+.TP
+.B Shift-F8, {
+Decrease the selected process's autogroup priority (add to autogroup 'nice' value)
+.TP
.B F9, k
"Kill" process: sends a signal which is selected in a menu, to one or a group
of processes. If processes were tagged, sends the signal to all tagged processes.
@@ -177,9 +212,11 @@ Quit
Invert the sort order: if sort order is increasing, switch to decreasing, and
vice-versa.
.TP
-.B +, \-
+.B +, \-, *
When in tree view mode, expand or collapse subtree. When a subtree is collapsed
a "+" sign shows to the left of the process name.
+Pressing "*" will expand or collapse all children of PIDs without parents, so
+typically PID 1 (init) and PID 2 (kthreadd on Linux, if kernel threads are shown).
.TP
.B a (on multiprocessor machines)
Set CPU affinity: mark which CPUs a process is allowed to use.
@@ -241,10 +278,14 @@ shown in
main screen, it is shown below in parenthesis.
.TP 5
.B Command
-The full command line of the process (i.e. program name and arguments). If the
-option 'Merge exe, comm and cmdline in Command' (toggled by the 'm' key) is set,
-and if readable, the executable path (/proc/[pid]/exe) and the command name
-(/proc/[pid]/comm) are also shown merged with the command line.
+The full command line of the process (i.e. program name and arguments).
+
+If the option 'Merge exe, comm and cmdline in Command' (toggled by the 'm' key)
+is active, the executable path (/proc/[pid]/exe) and the command name
+(/proc/[pid]/comm) are also shown merged with the command line, if available.
+
+The program basename is highlighted if set in the configuration. Additional
+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.
@@ -253,6 +294,10 @@ The command name of the process obtained from /proc/[pid]/comm, if readable.
The abbreviated basename of the executable of the process, obtained from
/proc/[pid]/exe, if readable. htop is able to read this file on linux for ALL
the processes only if it has the capability CAP_SYS_PTRACE or root privileges.
+
+The basename is marked in red if the executable used to run the process has
+been replaced or deleted on disk since the process started. This additional
+markup can be configured.
.TP
.B PID
The process ID.
@@ -275,7 +320,7 @@ The process's group ID.
.B SESSION (SID)
The process's session ID.
.TP
-.B TTY_NR (TTY)
+.B TTY
The controlling terminal of the process.
.TP
.B TPGID
@@ -347,9 +392,6 @@ except the process's executable instructions).
.B M_LRS (LIB)
The library size of the process.
.TP
-.B M_DT (DIRTY)
-The size of the dirty pages of the process.
-.TP
.B M_SWAP (SWAP)
The size of the process's swapped pages.
.TP
@@ -366,6 +408,15 @@ The user ID of the process owner.
.TP
.B PERCENT_CPU (CPU%)
The percentage of the CPU time that the process is currently using.
+This is the default way to represent CPU usage in Linux. Each process can
+consume up to 100% which means the full capacity of the core it is running
+on. This is sometimes called "Irix mode" e.g. in
+.BR top (1).
+.TP
+.B PERCENT_NORM_CPU (NCPU%)
+The percentage of the CPU time that the process is currently using normalized
+by CPU count. This is sometimes called "Solaris mode" e.g. in
+.BR top (1).
.TP
.B PERCENT_MEM (MEM%)
The percentage of memory the process is currently using (based on the process's
@@ -380,7 +431,7 @@ The time, measured in clock ticks that the process has spent in user and system
time (see UTIME, STIME above).
.TP
.B NLWP
-The number of threads in the process.
+The number of Light-Weight Processes (=threads) in the process.
.TP
.B TGID
The thread group ID.
@@ -454,6 +505,12 @@ The command name for the process. Requires Linux kernel 2.6.33 or newer.
.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
+.B ANI
+The autogroup nice value for the process autogroup. Requires Linux CFS to be enabled.
+.TP
.B All other flags
Currently unsupported (always displays '-').
.SH "EXTERNAL LIBRARIES"
@@ -497,7 +554,7 @@ Summary: build time dependency on
C header files, optional runtime dependency on
.B libsensors(3)
via dynamic loading.
-.SH "CONFIG FILE"
+.SH "CONFIG FILES"
By default
.B htop
reads its configuration from the XDG-compliant path
@@ -514,6 +571,36 @@ and as a last resort, falls back to its hard coded defaults.
You may override the location of the configuration file using the $HTOPRC
environment variable (so you can have multiple configurations for different
machines that share the same home directory, for example).
+.LP
+The
+.B pcp-htop
+utility makes use of
+.I htoprc
+in exactly the same way.
+In addition, it supports additional configuration files allowing
+new meters and columns to be added to the display via the usual
+Setup function, which will display additional Available Meters
+and Available Column entries for each runtime configured meter
+or column.
+.LP
+These
+.B pcp-htop
+configuration files are read once at startup.
+The format of these files is described in detail in the
+.BR pcp-htop (5)
+manual page.
+.LP
+This functionality makes available many thousands of Performance
+Co-Pilot metrics for display by
+.BR pcp-htop ,
+as well as the ability to display custom metrics added at individual sites.
+Applications and services instrumented using the OpenMetrics format
+.B https://openmetrics.io
+can also be displayed by
+.B pcp-htop
+if the
+.BR pmdaopenmetrics (1)
+component is configured.
.SH "MEMORY SIZES"
Memory sizes in
.B htop
@@ -531,7 +618,17 @@ space and make memory size representations consistent throughout
.BR uptime (1)
and
.BR limits.conf (5).
+.SH "SEE ALSO FOR PCP"
+.BR pmdaopenmetrics (1),
+.BR PCPIntro (1),
+.BR PMAPI (3),
+and
+.BR pcp-htop (5).
.SH "AUTHORS"
.B htop
was originally developed by Hisham Muhammad.
Nowadays it is maintained by the community at <htop@groups.io>.
+.LP
+.B pcp-htop
+is maintained as a collaboration between the <htop@groups.io> and <pcp@groups.io>
+communities, and forms part of the Performance Co-Pilot suite of tools.
diff --git a/htop.c b/htop.c
index 00ff1da..426b59a 100644
--- a/htop.c
+++ b/htop.c
@@ -1,363 +1,16 @@
/*
htop - htop.c
(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2021 htop dev team
Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
#include "config.h" // IWYU pragma: keep
-#include <assert.h>
-#include <getopt.h>
-#include <locale.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
+#include "CommandLine.h"
-#include "Action.h"
-#include "CRT.h"
-#include "Hashtable.h"
-#include "Header.h"
-#include "IncSet.h"
-#include "MainPanel.h"
-#include "MetersPanel.h"
-#include "Panel.h"
-#include "Platform.h"
-#include "Process.h"
-#include "ProcessList.h"
-#include "ProvideCurses.h"
-#include "ScreenManager.h"
-#include "Settings.h"
-#include "UsersTable.h"
-#include "XUtils.h"
-
-static void printVersionFlag(void) {
- fputs(PACKAGE " " VERSION "\n", stdout);
-}
-
-static void printHelpFlag(void) {
- fputs(PACKAGE " " VERSION "\n"
- COPYRIGHT "\n"
- "Released under the GNU GPLv2.\n\n"
- "-C --no-color Use a monochrome color scheme\n"
- "-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"
- "-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"
- "\n"
- "Long options may be passed with a single dash.\n\n"
- "Press F1 inside " PACKAGE " for online help.\n"
- "See 'man " PACKAGE "' for more information.\n",
- stdout);
-}
-
-// ----------------------------------------
-
-typedef struct CommandLineSettings_ {
- Hashtable* pidMatchList;
- char* commFilter;
- uid_t userId;
- int sortKey;
- int delay;
- bool useColors;
- bool enableMouse;
- bool treeView;
- bool allowUnicode;
- bool highlightChanges;
- int highlightDelaySecs;
-} CommandLineSettings;
-
-static CommandLineSettings parseArguments(int argc, char** argv) {
-
- CommandLineSettings flags = {
- .pidMatchList = NULL,
- .commFilter = NULL,
- .userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2))
- .sortKey = 0,
- .delay = -1,
- .useColors = true,
- .enableMouse = true,
- .treeView = false,
- .allowUnicode = true,
- .highlightChanges = false,
- .highlightDelaySecs = -1,
- };
-
- const struct option long_opts[] =
- {
- {"help", no_argument, 0, 'h'},
- {"version", no_argument, 0, 'V'},
- {"delay", required_argument, 0, 'd'},
- {"sort-key", required_argument, 0, 's'},
- {"user", optional_argument, 0, 'u'},
- {"no-color", no_argument, 0, 'C'},
- {"no-colour", no_argument, 0, 'C'},
- {"no-mouse", no_argument, 0, 'M'},
- {"no-unicode", no_argument, 0, 'U'},
- {"tree", no_argument, 0, 't'},
- {"pid", required_argument, 0, 'p'},
- {"filter", required_argument, 0, 'F'},
- {"highlight-changes", optional_argument, 0, 'H'},
- {0,0,0,0}
- };
-
- int opt, opti=0;
- /* Parse arguments */
- while ((opt = getopt_long(argc, argv, "hVMCs:td:u::Up:F:H::", long_opts, &opti))) {
- if (opt == EOF) break;
- switch (opt) {
- case 'h':
- printHelpFlag();
- exit(0);
- case 'V':
- printVersionFlag();
- exit(0);
- case 's':
- assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
- if (String_eq(optarg, "help")) {
- for (int j = 1; j < LAST_PROCESSFIELD; j++) {
- const char* name = Process_fields[j].name;
- if (name) printf ("%s\n", name);
- }
- exit(0);
- }
- flags.sortKey = 0;
- for (int j = 1; j < LAST_PROCESSFIELD; j++) {
- if (Process_fields[j].name == NULL)
- continue;
- if (String_eq(optarg, Process_fields[j].name)) {
- flags.sortKey = j;
- break;
- }
- }
- if (flags.sortKey == 0) {
- fprintf(stderr, "Error: invalid column \"%s\".\n", optarg);
- exit(1);
- }
- break;
- case 'd':
- if (sscanf(optarg, "%16d", &(flags.delay)) == 1) {
- if (flags.delay < 1) flags.delay = 1;
- if (flags.delay > 100) flags.delay = 100;
- } else {
- fprintf(stderr, "Error: invalid delay value \"%s\".\n", optarg);
- exit(1);
- }
- break;
- case 'u':
- {
- const char *username = optarg;
- if (!username && optind < argc && argv[optind] != NULL &&
- (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
- username = argv[optind++];
- }
-
- if (!username) {
- flags.userId = geteuid();
- } else if (!Action_setUserOnly(username, &(flags.userId))) {
- fprintf(stderr, "Error: invalid user \"%s\".\n", username);
- exit(1);
- }
- break;
- }
- case 'C':
- flags.useColors = false;
- break;
- case 'M':
- flags.enableMouse = false;
- break;
- case 'U':
- flags.allowUnicode = false;
- break;
- case 't':
- flags.treeView = true;
- break;
- case 'p': {
- assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
- char* argCopy = xStrdup(optarg);
- char* saveptr;
- char* pid = strtok_r(argCopy, ",", &saveptr);
-
- if(!flags.pidMatchList) {
- flags.pidMatchList = Hashtable_new(8, false);
- }
-
- while(pid) {
- unsigned int num_pid = atoi(pid);
- // deepcode ignore CastIntegerToAddress: we just want a non-NUll pointer here
- Hashtable_put(flags.pidMatchList, num_pid, (void *) 1);
- pid = strtok_r(NULL, ",", &saveptr);
- }
- free(argCopy);
-
- break;
- }
- case 'F': {
- assert(optarg);
- free(flags.commFilter);
- flags.commFilter = xStrdup(optarg);
-
- break;
- }
- case 'H': {
- const char *delay = optarg;
- if (!delay && optind < argc && argv[optind] != NULL &&
- (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
- delay = argv[optind++];
- }
- if (delay) {
- if (sscanf(delay, "%16d", &(flags.highlightDelaySecs)) == 1) {
- if (flags.highlightDelaySecs < 1)
- flags.highlightDelaySecs = 1;
- } else {
- fprintf(stderr, "Error: invalid highlight delay value \"%s\".\n", delay);
- exit(1);
- }
- }
- flags.highlightChanges = true;
- break;
- }
- default:
- exit(1);
- }
- }
- return flags;
-}
-
-static void millisleep(unsigned long millisec) {
- struct timespec req = {
- .tv_sec = 0,
- .tv_nsec = millisec * 1000000L
- };
- while(nanosleep(&req,&req)==-1) {
- continue;
- }
-}
-
-static void setCommFilter(State* state, char** commFilter) {
- Main