summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/FUNDING.yml2
-rw-r--r--.github/dependabot.yml9
-rw-r--r--.github/workflows/build_release.yml2
-rwxr-xr-x[-rw-r--r--].github/workflows/ci.yml225
-rw-r--r--.github/workflows/codeql-analysis.yml49
-rw-r--r--.github/workflows/htoprc71
-rw-r--r--.gitignore3
-rw-r--r--Action.c364
-rw-r--r--Action.h21
-rw-r--r--Affinity.c47
-rw-r--r--Affinity.h16
-rw-r--r--AffinityPanel.c110
-rw-r--r--AffinityPanel.h6
-rw-r--r--AvailableColumnsPanel.c60
-rw-r--r--AvailableColumnsPanel.h2
-rw-r--r--AvailableMetersPanel.c39
-rw-r--r--AvailableMetersPanel.h8
-rw-r--r--BatteryMeter.c25
-rw-r--r--CPUMeter.c160
-rw-r--r--CRT.c295
-rw-r--r--CRT.h23
-rw-r--r--CategoriesPanel.c44
-rw-r--r--CategoriesPanel.h9
-rw-r--r--ChangeLog95
-rw-r--r--ClockMeter.c6
-rw-r--r--ColorsPanel.c48
-rw-r--r--ColumnsPanel.c37
-rw-r--r--ColumnsPanel.h1
-rw-r--r--CommandLine.c152
-rw-r--r--CommandLine.h4
-rw-r--r--CommandScreen.c10
-rw-r--r--CommandScreen.h7
-rw-r--r--Compat.c43
-rw-r--r--Compat.h31
-rw-r--r--DateMeter.c6
-rw-r--r--DateTimeMeter.c6
-rw-r--r--DiskIOMeter.c134
-rw-r--r--DiskIOMeter.h2
-rw-r--r--DisplayOptionsPanel.c62
-rw-r--r--DynamicColumn.c16
-rw-r--r--DynamicColumn.h25
-rw-r--r--DynamicMeter.c11
-rw-r--r--DynamicMeter.h7
-rw-r--r--DynamicScreen.c68
-rw-r--r--DynamicScreen.h38
-rw-r--r--EnvScreen.c13
-rw-r--r--EnvScreen.h8
-rw-r--r--FileDescriptorMeter.c118
-rw-r--r--FileDescriptorMeter.h15
-rw-r--r--FunctionBar.c14
-rw-r--r--FunctionBar.h2
-rw-r--r--Hashtable.c16
-rw-r--r--Header.c52
-rw-r--r--Header.h10
-rw-r--r--HeaderLayout.h10
-rw-r--r--HeaderOptionsPanel.c46
-rw-r--r--IncSet.h2
-rw-r--r--InfoScreen.c88
-rw-r--r--InfoScreen.h7
-rw-r--r--ListItem.h1
-rw-r--r--LoadAverageMeter.c16
-rw-r--r--Machine.c129
-rw-r--r--Machine.h97
-rw-r--r--Macros.h64
-rw-r--r--MainPanel.c119
-rw-r--r--MainPanel.h18
-rw-r--r--Makefile.am113
-rw-r--r--MemoryMeter.c58
-rw-r--r--MemoryMeter.h9
-rw-r--r--MemorySwapMeter.c28
-rw-r--r--Meter.c391
-rw-r--r--Meter.h46
-rw-r--r--MeterMode.h22
-rw-r--r--MetersPanel.c38
-rw-r--r--NetworkIOMeter.c170
-rw-r--r--NetworkIOMeter.h8
-rw-r--r--Object.c2
-rw-r--r--Object.h2
-rw-r--r--OpenFilesScreen.c195
-rw-r--r--OptionItem.c2
-rw-r--r--Panel.c126
-rw-r--r--Panel.h2
-rw-r--r--Process.c787
-rw-r--r--Process.h226
-rw-r--r--ProcessList.c511
-rw-r--r--ProcessList.h127
-rw-r--r--ProcessLocksScreen.c19
-rw-r--r--ProcessLocksScreen.h4
-rw-r--r--ProcessTable.c92
-rw-r--r--ProcessTable.h52
-rw-r--r--ProvideCurses.h4
-rw-r--r--ProvideTerm.h24
l---------[-rw-r--r--]README188
-rw-r--r--[l---------]README.md190
-rw-r--r--RichString.c65
-rw-r--r--RichString.h7
-rw-r--r--Row.c560
-rw-r--r--Row.h184
-rw-r--r--RowField.h56
-rw-r--r--Scheduling.c165
-rw-r--r--Scheduling.h50
-rw-r--r--ScreenManager.c87
-rw-r--r--ScreenManager.h8
-rw-r--r--ScreenTabsPanel.c374
-rw-r--r--ScreenTabsPanel.h61
-rw-r--r--ScreensPanel.c156
-rw-r--r--ScreensPanel.h8
-rw-r--r--Settings.c359
-rw-r--r--Settings.h43
-rw-r--r--SignalsPanel.c2
-rw-r--r--SignalsPanel.h4
-rw-r--r--SwapMeter.c23
-rw-r--r--SwapMeter.h6
-rw-r--r--TESTPLAN2
-rw-r--r--Table.c371
-rw-r--r--Table.h95
-rw-r--r--TasksMeter.c23
-rw-r--r--TraceScreen.c44
-rw-r--r--UptimeMeter.c2
-rw-r--r--UsersTable.c2
-rw-r--r--Vector.c68
-rw-r--r--XUtils.c76
-rw-r--r--XUtils.h65
-rw-r--r--configure.ac192
-rw-r--r--darwin/DarwinMachine.c119
-rw-r--r--darwin/DarwinMachine.h28
-rw-r--r--darwin/DarwinProcess.c200
-rw-r--r--darwin/DarwinProcess.h10
-rw-r--r--darwin/DarwinProcessList.c202
-rw-r--r--darwin/DarwinProcessList.h39
-rw-r--r--darwin/DarwinProcessTable.c126
-rw-r--r--darwin/DarwinProcessTable.h22
-rw-r--r--darwin/Platform.c250
-rw-r--r--darwin/Platform.h34
-rw-r--r--darwin/PlatformHelpers.c14
-rw-r--r--darwin/PlatformHelpers.h2
-rw-r--r--docs/images/screenshot.pngbin80937 -> 46142 bytes
-rw-r--r--docs/styleguide.md19
-rw-r--r--dragonflybsd/DragonFlyBSDMachine.c349
-rw-r--r--dragonflybsd/DragonFlyBSDMachine.h (renamed from dragonflybsd/DragonFlyBSDProcessList.h)33
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.c39
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.h4
-rw-r--r--dragonflybsd/DragonFlyBSDProcessList.c615
-rw-r--r--dragonflybsd/DragonFlyBSDProcessTable.c322
-rw-r--r--dragonflybsd/DragonFlyBSDProcessTable.h21
-rw-r--r--dragonflybsd/Platform.c160
-rw-r--r--dragonflybsd/Platform.h34
-rw-r--r--freebsd/FreeBSDMachine.c393
-rw-r--r--freebsd/FreeBSDMachine.h (renamed from freebsd/FreeBSDProcessList.h)26
-rw-r--r--freebsd/FreeBSDProcess.c47
-rw-r--r--freebsd/FreeBSDProcess.h4
-rw-r--r--freebsd/FreeBSDProcessList.c618
-rw-r--r--freebsd/FreeBSDProcessTable.c288
-rw-r--r--freebsd/FreeBSDProcessTable.h21
-rw-r--r--freebsd/Platform.c93
-rw-r--r--freebsd/Platform.h34
-rw-r--r--generic/fdstat_sysctl.c80
-rw-r--r--generic/fdstat_sysctl.h13
-rw-r--r--generic/gettime.c1
-rw-r--r--generic/hostname.c1
-rw-r--r--generic/uname.c11
-rw-r--r--htop.1.in33
-rw-r--r--htop.c4
-rw-r--r--htop.pngbin3537 -> 2616 bytes
-rw-r--r--iwyu/htop.imp9
-rwxr-xr-xiwyu/run_iwyu.sh1
-rw-r--r--linux/CGroupUtils.c255
-rw-r--r--linux/CGroupUtils.h6
-rw-r--r--linux/GPU.c246
-rw-r--r--linux/GPU.h17
-rw-r--r--linux/GPUMeter.c179
-rw-r--r--linux/GPUMeter.h19
-rw-r--r--linux/HugePageMeter.c14
-rw-r--r--linux/IOPriorityPanel.c2
-rw-r--r--linux/LibNl.c239
-rw-r--r--linux/LibNl.h18
-rw-r--r--linux/LibSensors.c51
-rw-r--r--linux/LibSensors.h8
-rw-r--r--linux/LinuxMachine.c715
-rw-r--r--linux/LinuxMachine.h (renamed from linux/LinuxProcessList.h)62
-rw-r--r--linux/LinuxProcess.c240
-rw-r--r--linux/LinuxProcess.h28
-rw-r--r--linux/LinuxProcessList.c2249
-rw-r--r--linux/LinuxProcessTable.c1800
-rw-r--r--linux/LinuxProcessTable.h35
-rw-r--r--linux/Platform.c432
-rw-r--r--linux/Platform.h39
-rw-r--r--linux/PressureStallMeter.c21
-rw-r--r--linux/PressureStallMeter.h2
-rw-r--r--linux/ProcessField.h5
-rw-r--r--linux/SELinuxMeter.c2
-rw-r--r--linux/SystemdMeter.c190
-rw-r--r--linux/SystemdMeter.h2
-rw-r--r--linux/ZramMeter.c31
-rw-r--r--linux/ZramMeter.h12
-rw-r--r--linux/ZramStats.h8
-rw-r--r--linux/ZswapStats.h19
-rw-r--r--netbsd/NetBSDMachine.c285
-rw-r--r--netbsd/NetBSDMachine.h (renamed from netbsd/NetBSDProcessList.h)28
-rw-r--r--netbsd/NetBSDProcess.c34
-rw-r--r--netbsd/NetBSDProcess.h4
-rw-r--r--netbsd/NetBSDProcessList.c504
-rw-r--r--netbsd/NetBSDProcessTable.c273
-rw-r--r--netbsd/NetBSDProcessTable.h24
-rw-r--r--netbsd/Platform.c63
-rw-r--r--netbsd/Platform.h34
-rw-r--r--openbsd/OpenBSDMachine.c290
-rw-r--r--openbsd/OpenBSDMachine.h (renamed from openbsd/OpenBSDProcessList.h)29
-rw-r--r--openbsd/OpenBSDProcess.c37
-rw-r--r--openbsd/OpenBSDProcess.h4
-rw-r--r--openbsd/OpenBSDProcessList.c486
-rw-r--r--openbsd/OpenBSDProcessTable.c245
-rw-r--r--openbsd/OpenBSDProcessTable.h21
-rw-r--r--openbsd/Platform.c85
-rw-r--r--openbsd/Platform.h34
-rw-r--r--pcp-htop.5.in2
-rw-r--r--pcp-htop.c7
-rw-r--r--pcp/InDomTable.c99
-rw-r--r--pcp/InDomTable.h34
-rw-r--r--pcp/Instance.c163
-rw-r--r--pcp/Instance.h37
-rw-r--r--pcp/Metric.c (renamed from pcp/PCPMetric.c)52
-rw-r--r--pcp/Metric.h (renamed from pcp/PCPMetric.h)41
-rw-r--r--pcp/PCPDynamicColumn.c275
-rw-r--r--pcp/PCPDynamicColumn.h21
-rw-r--r--pcp/PCPDynamicMeter.c47
-rw-r--r--pcp/PCPDynamicMeter.h6
-rw-r--r--pcp/PCPDynamicScreen.c407
-rw-r--r--pcp/PCPDynamicScreen.h56
-rw-r--r--pcp/PCPMachine.c345
-rw-r--r--pcp/PCPMachine.h (renamed from pcp/PCPProcessList.h)31
-rw-r--r--pcp/PCPProcess.c159
-rw-r--r--pcp/PCPProcess.h9
-rw-r--r--pcp/PCPProcessList.c727
-rw-r--r--pcp/PCPProcessTable.c486
-rw-r--r--pcp/PCPProcessTable.h24
-rw-r--r--pcp/Platform.c334
-rw-r--r--pcp/Platform.h26
-rw-r--r--pcp/ProcessField.h3
-rw-r--r--pcp/screens/biosnoop41
-rw-r--r--pcp/screens/cgroups45
-rw-r--r--pcp/screens/cgroupsio49
-rw-r--r--pcp/screens/cgroupsmem48
-rw-r--r--pcp/screens/devices114
-rw-r--r--pcp/screens/execsnoop37
-rw-r--r--pcp/screens/exitsnoop48
-rw-r--r--pcp/screens/filesystems50
-rw-r--r--pcp/screens/opensnoop27
-rw-r--r--solaris/Platform.c71
-rw-r--r--solaris/Platform.h38
-rw-r--r--solaris/SolarisMachine.c340
-rw-r--r--solaris/SolarisMachine.h (renamed from solaris/SolarisProcessList.h)33
-rw-r--r--solaris/SolarisProcess.c43
-rw-r--r--solaris/SolarisProcess.h7
-rw-r--r--solaris/SolarisProcessList.c560
-rw-r--r--solaris/SolarisProcessTable.c269
-rw-r--r--solaris/SolarisProcessTable.h31
-rw-r--r--unsupported/Platform.c17
-rw-r--r--unsupported/Platform.h37
-rw-r--r--unsupported/UnsupportedMachine.c56
-rw-r--r--unsupported/UnsupportedMachine.h17
-rw-r--r--unsupported/UnsupportedProcess.c34
-rw-r--r--unsupported/UnsupportedProcess.h4
-rw-r--r--unsupported/UnsupportedProcessList.h21
-rw-r--r--unsupported/UnsupportedProcessTable.c (renamed from unsupported/UnsupportedProcessList.c)56
-rw-r--r--unsupported/UnsupportedProcessTable.h17
-rw-r--r--zfs/ZfsArcMeter.c2
-rw-r--r--zfs/ZfsCompressedArcMeter.c2
268 files changed, 18434 insertions, 10654 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 6ba9da64..0e89766e 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1 @@
-open_collective: htop
+custom: ["https://hcb.hackclub.com/donations/start/htop"]
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..5516fd63
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+---
+
+version: 2
+
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: weekly
diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml
index 8c879156..2bdf443d 100644
--- a/.github/workflows/build_release.yml
+++ b/.github/workflows/build_release.yml
@@ -11,7 +11,7 @@ jobs:
name: build
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v4
with:
submodules: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index af8ed883..1b20581d 100644..100755
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,13 +11,13 @@ jobs:
build-ubuntu-latest-minimal-gcc:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Install Dependencies
run: sudo apt-get install --no-install-recommends libncursesw5-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors
+ run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors || (cat config.log; exit 1)
- name: Enable compatibility modes
run: |
sed -i 's/#define HAVE_FSTATAT 1/#undef HAVE_FSTATAT/g' config.h
@@ -31,20 +31,20 @@ jobs:
build-ubuntu-latest-minimal-clang:
runs-on: ubuntu-latest
env:
- CC: clang-12
+ CC: clang-18
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- 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/focal/ llvm-toolchain-focal-12 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' -y
sudo apt-get update -q
- name: Install Dependencies
- run: sudo apt-get install --no-install-recommends clang-12 libncursesw5-dev
+ run: sudo apt-get install --no-install-recommends clang-18 libncursesw5-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors
+ run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors || ( cat config.log; exit 1; )
- name: Build
run: make -k
- name: Distcheck
@@ -57,13 +57,13 @@ jobs:
CFLAGS: -O3 -g -flto
LDFLAGS: -O3 -g -flto -Wl,--as-needed
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Install Dependencies
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-delayacct --enable-sensors --enable-capabilities
+ run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )
- name: Build
run: make -k
- name: Distcheck
@@ -72,20 +72,20 @@ jobs:
build-ubuntu-latest-full-featured-clang:
runs-on: ubuntu-latest
env:
- CC: clang-12
+ CC: clang-18
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- 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/focal/ llvm-toolchain-focal-12 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' -y
sudo apt-get update -q
- name: Install Dependencies
- 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
+ run: sudo apt-get install --no-install-recommends clang-18 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-delayacct --enable-sensors --enable-capabilities
+ run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )
- name: Build
run: make -k
- name: Distcheck
@@ -98,76 +98,233 @@ jobs:
CFLAGS: -O3 -g -flto
LDFLAGS: -O3 -g -flto
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- 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
+ run: ./configure --enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )
- 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
- env:
- # Until Ubuntu catches up with pcp-5.2.3+:
- # pcp/Platform.c:309:45: warning: passing argument 2 of ‘pmLookupName’ from incompatible pointer type [-Wincompatible-pointer-types]
- CFLAGS: -Wno-error=incompatible-pointer-types
+ # we want PCP v5.2.3+
+ runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- 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
- run: ./configure --enable-werror --enable-pcp --enable-unicode
+ run: ./configure --enable-werror --enable-pcp --enable-unicode || ( cat config.log; exit 1; )
- name: Build
run: make -k
build-ubuntu-latest-clang-analyzer:
runs-on: ubuntu-latest
env:
- CC: clang-12
+ CC: clang-18
+ steps:
+ - uses: actions/checkout@v4
+ - 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/jammy/ llvm-toolchain-jammy-18 main' -y
+ sudo apt-get update -q
+ - name: Install Dependencies
+ run: sudo apt-get install --no-install-recommends clang-18 clang-tools-18 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev
+ - name: Bootstrap
+ run: ./autogen.sh
+ - name: Configure
+ run: scan-build-18 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )
+ - name: Build
+ run: scan-build-18 -analyze-headers --status-bugs make -j"$(nproc)"
+
+ build-ubuntu-latest-clang-sanitizer:
+ runs-on: ubuntu-latest
+ env:
+ CC: clang-18
+ CFLAGS: '-O1 -g -ftrivial-auto-var-init=pattern -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-address-use-after-return=always -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=nullability -fsanitize=implicit-conversion -fsanitize=integer -fsanitize=float-divide-by-zero -fsanitize=local-bounds'
+ LDFLAGS: '-ftrivial-auto-var-init=pattern -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-address-use-after-return=always -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=nullability -fsanitize=implicit-conversion -fsanitize=integer -fsanitize=float-divide-by-zero -fsanitize=local-bounds'
+ ASAN_OPTIONS: strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
+ UBSAN_OPTIONS: print_stacktrace=1:print_summary=1:halt_on_error=1
+ TERM: xterm-color
+ HTOPRC: .github/workflows/htoprc
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- 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/focal/ llvm-toolchain-focal-12 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' -y
sudo apt-get update -q
+ - name: Install LLVM Toolchain
+ run: sudo apt-get install --no-install-recommends clang-18 libclang-rt-18-dev llvm-18
- name: Install Dependencies
- 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
+ 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: 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
+ run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )
- name: Build
- run: scan-build-12 -analyze-headers --status-bugs make -j"$(nproc)"
+ run: make -k
+ - name: Run sanitized htop (1)
+ run: ./htop -h
+ - name: Run sanitized htop (2)
+ run: ./htop -n 5
+ - name: Run sanitized htop (3)
+ run: ./htop -n 5 -t
+ - name: Run sanitized htop (4)
+ run: ./htop -d 1 -n 50
build-macos-latest-clang:
runs-on: macOS-latest
env:
CC: clang
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Install Dependencies
- run: brew install automake
+ run: brew install automake pkg-config
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror
+ run: ./configure --enable-werror || ( cat config.log; exit 1; )
- name: Build
run: make -k
- name: Distcheck
run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror"
+ build-dragonflybsd-latest-gcc:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Compile
+ uses: vmactions/dragonflybsd-vm@v1
+ with:
+ release: '6.4.0'
+ usesh: true
+ prepare: |
+ pkg install -y gmake autoconf automake ncurses git
+ git config --global --add safe.directory /home/runner/work/htop/htop
+ run: |
+ set -e
+ export LDFLAGS="-L/usr/local/lib"
+ export CFLAGS="-I/usr/local/include"
+ ./autogen.sh
+ ./configure --enable-unicode --enable-werror
+ gmake -k
+
+ build-freebsd-latest-clang:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Compile
+ uses: vmactions/freebsd-vm@v1
+ with:
+ release: '14.0'
+ usesh: true
+ prepare: |
+ pkg install -y gmake autoconf automake git
+ git config --global --add safe.directory /home/runner/work/htop/htop
+ run: |
+ set -e
+ ./autogen.sh
+ ./configure --enable-unicode --enable-werror
+ gmake -k
+
+ build-netbsd-latest-gcc:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Compile
+ uses: vmactions/netbsd-vm@v1
+ with:
+ release: '9.3'
+ usesh: true
+ prepare: |
+ PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH"
+ PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/9.3/All/"
+ export PATH PKG_PATH
+ /usr/sbin/pkg_add pkgin
+ pkgin -y install autoconf automake libtool ncurses ncursesw gmake git
+ git config --global --add safe.directory /home/runner/work/htop/htop
+ run: |
+ set -e
+ ./autogen.sh
+ CPPFLAGS="-I/usr/pkg/include" ./configure --enable-unicode --enable-werror
+ gmake -k
+
+ build-openbsd-latest-clang:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Compile
+ uses: vmactions/openbsd-vm@v1
+ with:
+ release: '7.4'
+ usesh: true
+ prepare: |
+ pkg_add gmake git
+ git config --global --add safe.directory /home/runner/work/htop/htop
+ run: |
+ set -e
+ autoconf_version_full=$(pkg_info -Q autoconf |
+ LC_ALL=C sed 's/^autoconf-\([0-9]*\.[0-9]*\)\(p[.0-9]*\)\{0,1\}$/\1\2/p; d' |
+ LC_ALL=C sort -n -r -t . -k 1,1 -k 2,2 |
+ sed '1 q')
+ automake_version_full=$(pkg_info -Q automake |
+ LC_ALL=C sed 's/^automake-\([0-9]*\.[0-9]*\)\(\.[0-9]*\)\{0,1\}\(p[0-9]*\)\{0,1\}$/\1\2\3/p; d' |
+ LC_ALL=C sort -n -r -t . -k 1,1 -k 2,2 -k 3,3 |
+ sed '1 q')
+ pkg_add -v autoconf-${autoconf_version_full} automake-${automake_version_full}
+ export AUTOCONF_VERSION=$(echo ${autoconf_version_full} |
+ LC_ALL=C sed 's/^\([0-9]*\.[0-9]*\).*$/\1/')
+ # Must not include the third version field in $AUTOMAKE_VERSION
+ export AUTOMAKE_VERSION=$(echo ${automake_version_full} |
+ LC_ALL=C sed 's/^\([0-9]*\.[0-9]*\).*$/\1/')
+ ./autogen.sh
+ ./configure --enable-unicode --enable-werror
+ gmake -k
+
+ build-solaris-latest-gcc:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Compile
+ uses: vmactions/solaris-vm@v1
+ with:
+ release: '11.4'
+ usesh: true
+ prepare: |
+ pkg install gnu-make autoconf automake ncurses git gcc
+ git config --global --add safe.directory /home/runner/work/htop/htop
+ run: |
+ set -e
+ ./autogen.sh
+ ./configure --enable-unicode --enable-werror
+ gmake -k
+
whitespace_check:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: check-whitespaces
run: git diff-tree --check $(git hash-object -t tree /dev/null) HEAD
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000..47383a78
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,49 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: '0 1 * * 0'
+
+permissions:
+ contents: read
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ security-events: write
+
+ env:
+ # Enable format attributes in ncurses headers
+ # Enable fortified memory/string handling
+ CPPFLAGS: -DGCC_PRINTF -DGCC_SCANF -D_FORTIFY_SOURCE=2
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: cpp
+
+ - name: Install Dependencies
+ 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-delayacct --enable-sensors --enable-capabilities
+
+ - name: Build
+ run: make
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/htoprc b/.github/workflows/htoprc
new file mode 100644
index 00000000..6c058ecb
--- /dev/null
+++ b/.github/workflows/htoprc
@@ -0,0 +1,71 @@
+# Beware! This file is rewritten by htop when settings are changed in the interface.
+# The parser is also very primitive, and not human-friendly.
+htop_version=3.3.0-dev
+config_reader_min_version=3
+fields=0 48 17 18 38 39 40 2 46 47 49 1
+hide_kernel_threads=1
+hide_userland_threads=0
+hide_running_in_container=0
+shadow_other_users=1
+show_thread_names=1
+show_program_path=1
+highlight_base_name=1
+highlight_deleted_exe=1
+shadow_distribution_path_prefix=1
+highlight_megabytes=1
+highlight_threads=1
+highlight_changes=1
+highlight_changes_delay_secs=5
+find_comm_in_cmdline=1
+strip_exe_from_cmdline=1
+show_merged_command=1
+header_margin=1
+screen_tabs=1
+detailed_cpu_time=1
+cpu_count_from_one=0
+show_cpu_usage=1
+show_cpu_frequency=1
+show_cpu_temperature=1
+degree_fahrenheit=0
+update_process_names=1
+account_guest_in_cpu_meter=1
+color_scheme=0
+enable_mouse=1
+delay=15
+hide_function_bar=0
+header_layout=two_50_50
+column_meters_0=LeftCPUs4 CPU Memory Swap MemorySwap Zram Clock Date DateTime ZFSARC ZFSCARC SELinux SystemdUser FileDescriptors
+column_meter_modes_0=1 1 1 1 1 1 2 2 2 2 2 2 2 2
+column_meters_1=RightCPUs4 Tasks LoadAverage Load Uptime Battery System HugePages Hostname Blank PressureStallCPUSome PressureStallIOSome PressureStallIOFull PressureStallIRQFull PressureStallMemorySome PressureStallMemoryFull DiskIO NetworkIO
+column_meter_modes_1=1 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2
+tree_view=0
+sort_key=46
+tree_sort_key=0
+sort_direction=-1
+tree_sort_direction=1
+tree_view_always_by_pid=0
+all_branches_collapsed=0
+screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command
+.sort_key=PERCENT_CPU
+.tree_sort_key=PID
+.tree_view_always_by_pid=0
+.tree_view=0
+.sort_direction=-1
+.tree_sort_direction=1
+.all_branches_collapsed=0
+screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command
+.sort_key=IO_RATE
+.tree_sort_key=PID
+.tree_view_always_by_pid=0
+.tree_view=0
+.sort_direction=-1
+.tree_sort_direction=1
+.all_branches_collapsed=0
+screen:Dump=PID SCHEDULERPOLICY SYSCR CGROUP CCGROUP OOM IO_PRIORITY PERCENT_CPU_DELAY CTXT SECATTR CWD AUTOGROUP_ID Command
+.sort_key=PID
+.tree_sort_key=PID
+.tree_view_always_by_pid=0
+.tree_view=0
+.sort_direction=1
+.tree_sort_direction=1
+.all_branches_collapsed=0
diff --git a/.gitignore b/.gitignore
index 82624490..50f3bd15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,6 @@ callgrind.out.*
# IDE workspace configurations
/.idea/
/.vscode/
+
+# Language Servers
+/.cache/clangd/
diff --git a/Action.c b/Action.c
index b7652487..144ef210 100644
--- a/Action.c
+++ b/Action.c
@@ -9,9 +9,11 @@ in the source distribution for its full text.
#include "Action.h"
+#include <assert.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdlib.h>
+#include <string.h>
#include "CRT.h"
#include "CategoriesPanel.h"
@@ -29,9 +31,14 @@ in the source distribution for its full text.
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "ProvideCurses.h"
+#include "Row.h"
+#include "RowField.h"
+#include "Scheduling.h"
#include "ScreenManager.h"
#include "SignalsPanel.h"
+#include "Table.h"
#include "TraceScreen.h"
+#include "UsersTable.h"
#include "Vector.h"
#include "XUtils.h"
@@ -41,34 +48,35 @@ in the source distribution for its full text.
#endif
-Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) {
+Object* Action_pickFromVector(State* st, Panel* list, int x, bool follow) {
MainPanel* mainPanel = st->mainPanel;
Header* header = st->header;
+ Machine* host = st->host;
int y = ((Panel*)mainPanel)->y;
- ScreenManager* scr = ScreenManager_new(header, st->settings, st, false);
+ ScreenManager* scr = ScreenManager_new(header, host, st, false);
scr->allowFocusChange = false;
ScreenManager_add(scr, list, x);
ScreenManager_add(scr, (Panel*)mainPanel, -1);
Panel* panelFocus;
int ch;
bool unfollow = false;
- int pid = followProcess ? MainPanel_selectedPid(mainPanel) : -1;
- if (followProcess && header->pl->following == -1) {
- header->pl->following = pid;
+ int row = follow ? MainPanel_selectedRow(mainPanel) : -1;
+ if (follow && host->activeTable->following == -1) {
+ host->activeTable->following = row;
unfollow = true;
}
ScreenManager_run(scr, &panelFocus, &ch, NULL);
if (unfollow) {
- header->pl->following = -1;
+ host->activeTable->following = -1;
}
ScreenManager_delete(scr);
Panel_move((Panel*)mainPanel, 0, y);
Panel_resize((Panel*)mainPanel, COLS, LINES - y - 1);
if (panelFocus == list && ch == 13) {
- if (followProcess) {
- const Process* selected = (const Process*)Panel_getSelected((Panel*)mainPanel);
- if (selected && selected->pid == pid)
+ if (follow) {
+ const Row* selected = (const Row*)Panel_getSelected((Panel*)mainPanel);
+ if (selected && selected->id == row)
return Panel_getSelected(list);
beep();
@@ -83,19 +91,20 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess)
// ----------------------------------------
static void Action_runSetup(State* st) {
- ScreenManager* scr = ScreenManager_new(st->header, st->settings, st, true);
- CategoriesPanel_new(scr, st->settings, st->header, st->pl);
+ const Settings* settings = st->host->settings;
+ ScreenManager* scr = ScreenManager_new(st->header, st->host, st, true);
+ CategoriesPanel_new(scr, st->header, st->host);
ScreenManager_run(scr, NULL, NULL, "Setup");
ScreenManager_delete(scr);
- if (st->settings->changed) {
- CRT_setMouse(st->settings->enableMouse);
+ if (settings->changed) {
+ CRT_setMouse(settings->enableMouse);
Header_writeBackToSettings(st->header);
}
}
static bool changePriority(MainPanel* panel, int delta) {
bool anyTagged;
- bool ok = MainPanel_foreachProcess(panel, Process_changePriorityBy, (Arg) { .i = delta }, &anyTagged);
+ bool ok = MainPanel_foreachRow(panel, Process_rowChangePriorityBy, (Arg) { .i = delta }, &anyTagged);
if (!ok)
beep();
return anyTagged;
@@ -117,36 +126,36 @@ bool Action_setUserOnly(const char* userName, uid_t* userId) {
return false;
}
-static void tagAllChildren(Panel* panel, Process* parent) {
+static void tagAllChildren(Panel* panel, Row* parent) {
parent->tag = true;
- pid_t ppid = parent->pid;
+ int parent_id = parent->id;
for (int i = 0; i < Panel_size(panel); i++) {
- Process* p = (Process*) Panel_get(panel, i);
- if (!p->tag && Process_isChildOf(p, ppid)) {
- tagAllChildren(panel, p);
+ Row* row = (Row*) Panel_get(panel, i);
+ if (!row->tag && Row_isChildOf(row, parent_id)) {
+ tagAllChildren(panel, row);
}
}
}
static bool expandCollapse(Panel* panel) {
- Process* p = (Process*) Panel_getSelected(panel);
- if (!p)
+ Row* row = (Row*) Panel_getSelected(panel);
+ if (!row)
return false;
- p->showChildren = !p->showChildren;
+ row->showChildren = !row->showChildren;
return true;
}
static bool collapseIntoParent(Panel* panel) {
- const Process* p = (Process*) Panel_getSelected(panel);
- if (!p)
+ const Row* r = (Row*) Panel_getSelected(panel);
+ if (!r)
return false;
- pid_t ppid = Process_getParentPid(p);
+ int parent_id = Row_getGroupOrParent(r);
for (int i = 0; i < Panel_size(panel); i++) {
- Process* q = (Process*) Panel_get(panel, i);
- if (q->pid == ppid) {
- q->showChildren = false;
+ Row* row = (Row*) Panel_get(panel, i);
+ if (row->id == parent_id) {
+ row->showChildren = false;
Panel_setSelected(panel, i);
return true;
}
@@ -155,22 +164,34 @@ static bool collapseIntoParent(Panel* panel) {
}
Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) {
- ScreenSettings_setSortKey(settings->ss, sortKey);
+ ScreenSettings_setSortKey(settings->ss, (RowField) sortKey);
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_UPDATE_PANELHDR | HTOP_KEEP_FOLLOWING;
}
// ----------------------------------------
+static bool Action_writeableProcess(State* st) {
+ const Settings* settings = st->host->settings;
+ bool readonly = Settings_isReadonly() || settings->ss->dynamic;
+ return !readonly;
+}
+
+static bool Action_readableProcess(State* st) {
+ const Settings* settings = st->host->settings;
+ return !settings->ss->dynamic;
+}
+
static Htop_Reaction actionSetSortColumn(State* st) {
Htop_Reaction reaction = HTOP_OK;
Panel* sortPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Sort ", "Cancel "));
Panel_setHeader(sortPanel, "Sort by");
- const Settings* settings = st->settings;
- const ProcessField* fields = settings->ss->fields;
+ Machine* host = st->host;
+ Settings* settings = host->settings;
+ const RowField* fields = settings->ss->fields;
Hashtable* dynamicColumns = settings->dynamicColumns;
for (int i = 0; fields[i]; i++) {
char* name = NULL;
- if (fields[i] >= LAST_PROCESSFIELD) {
+ if (fields[i] >= ROW_DYNAMIC_FIELDS) {
DynamicColumn* column = Hashtable_get(dynamicColumns, fields[i]);
if (!column)
continue;
@@ -186,77 +207,112 @@ static Htop_Reaction actionSetSortColumn(State* st) {
}
const ListItem* field = (const ListItem*) Action_pickFromVector(st, sortPanel, 14, false);
if (field) {
- reaction |= Action_setSortKey(st->settings, field->key);
+ reaction |= Action_setSortKey(settings, field->key);
}
Object_delete(sortPanel);
- st->pl->needsSort = true;
+ host->activeTable->needsSort = true;
return reaction | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
}
static Htop_Reaction actionSortByPID(State* st) {
- return Action_setSortKey(st->settings, PID);
+ return Action_setSortKey(st->host->settings, PID);
}
static Htop_Reaction actionSortByMemory(State* st) {
- return Action_setSortKey(st->settings, PERCENT_MEM);
+ return Action_setSortKey(st->host->settings, PERCENT_MEM);
}
static Htop_Reaction actionSortByCPU(State* st) {
- return Action_setSortKey(st->settings, PERCENT_CPU);
+ return Action_setSortKey(st->host->settings, PERCENT_CPU);
}
static Htop_Reaction actionSortByTime(State* st) {
- return Action_setSortKey(st->settings, TIME);
+ return Action_setSortKey(st->host->settings, TIME);
}
static Htop_Reaction actionToggleKernelThreads(State* st) {
- st->settings->hideKernelThreads = !st->settings->hideKernelThreads;
+ Settings* settings = st->host->settings;
+ settings->hideKernelThreads = !settings->hideKernelThreads;
+ settings->lastUpdate++;
+
+ Machine_scanTables(st->host); // needed to not have a visible delay showing wrong data
+
return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionToggleUserlandThreads(State* st) {
- st->settings->hideUserlandThreads = !st->settings->hideUserlandThreads;
+ Settings* settings = st->host->settings;
+ settings->hideUserlandThreads = !settings->hideUserlandThreads;
+ settings->lastUpdate++;
+
+ Machine_scanTables(st->host); // needed to not have a visible delay showing wrong data
+
+ return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
+}
+
+static Htop_Reaction actionToggleRunningInContainer(State* st) {
+ Settings* settings = st->host->settings;
+ settings->hideRunningInContainer = !settings->hideRunningInContainer;
+ settings->lastUpdate++;
+
return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionToggleProgramPath(State* st) {
- st->settings->showProgramPath = !st->settings->showProgramPath;
- return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
+ Settings* settings = st->host->settings;
+ settings->showProgramPath = !settings->showProgramPath;
+ settings->lastUpdate++;
+
+ return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionToggleMergedCommand(State* st) {
- st->settings->showMergedCommand = !st->settings->showMergedCommand;
- return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
+ Settings* settings = st->host->settings;
+ settings->showMergedCommand = !settings->showMergedCommand;
+ settings->lastUpdate++;
+
+ return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_UPDATE_PANELHDR;
}
static Htop_Reaction actionToggleTreeView(State* st) {
- ScreenSettings* ss = st->settings->ss;
+ Machine* host = st->host;
+ ScreenSettings* ss = host->settings->ss;
ss->treeView = !ss->treeView;
if (!ss->allBranchesCollapsed)
- ProcessList_expandTree(st->pl);
+ Table_expandTree(host->activeTable);
+
+ host->activeTable->needsSort = true;
+
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
}
+static Htop_Reaction actionToggleHideMeters(State* st) {
+ st->hideMeters = !st->hideMeters;
+ return HTOP_RESIZE | HTOP_KEEP_FOLLOWING;
+}
+
static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) {
- ScreenSettings* ss = st->settings->ss;
+ Machine* host = st->host;
+ ScreenSettings* ss = host->settings->ss;
if (!ss->treeView) {
return HTOP_OK;
}
ss->allBranchesCollapsed = !ss->allBranchesCollapsed;
if (ss->allBranchesCollapsed)
- ProcessList_collapseAllBranches(st->pl);
+ Table_collapseAllBranches(host->activeTable);
else
- ProcessList_expandTree(st->pl);
+ Table_expandTree(host->activeTable);
return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
}
static Htop_Reaction actionIncFilter(State* st) {
+ Machine* host = st->host;
IncSet* inc = (st->mainPanel)->inc;
IncSet_activate(inc, INC_FILTER, (Panel*)st->mainPanel);
- st->pl->incFilter = IncSet_filter(inc);
+ host->activeTable->incFilter = IncSet_filter(inc);
return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;
}
@@ -267,7 +323,7 @@ static Htop_Reaction actionIncSearch(State* st) {
}
static Htop_Reaction actionHigherPriority(State* st) {
- if (Settings_isReadonly())
+ if (!Action_writeableProcess(st))
return HTOP_OK;
bool changed = changePriority(st->mainPanel, -1);
@@ -275,7 +331,7 @@ static Htop_Reaction actionHigherPriority(State* st) {
}
static Htop_Reaction actionLowerPriority(State* st) {
- if (Settings_isReadonly())
+ if (!Action_writeableProcess(st))
return HTOP_OK;
bool changed = changePriority(st->mainPanel, 1);
@@ -283,18 +339,22 @@ static Htop_Reaction actionLowerPriority(State* st) {
}
static Htop_Reaction actionInvertSortOrder(State* st) {
- ScreenSettings_invertSortOrder(st->settings->ss);
- st->pl->needsSort = true;
- return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
+ Machine* host = st->host;
+ ScreenSettings_invertSortOrder(host->settings->ss);
+ host->activeTable->needsSort = true;
+ return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_UPDATE_PANELHDR;
}
static Htop_Reaction actionExpandOrCollapse(State* st) {
+ if (!st->host->settings->ss->treeView)
+ return HTOP_OK;
+
bool changed = expandCollapse((Panel*)st->mainPanel);
return changed ? HTOP_RECALCULATE : HTOP_OK;
}
static Htop_Reaction actionCollapseIntoParent(State* st) {
- if (!st->settings->ss->treeView) {
+ if (!st->host->settings->ss->treeView) {
return HTOP_OK;
}
bool changed = collapseIntoParent((Panel*)st->mainPanel);
@@ -302,42 +362,57 @@ static Htop_Reaction actionCollapseIntoParent(State* st) {
}
static Htop_Reaction actionExpandCollapseOrSortColumn(State* st) {
- return st->settings->ss->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st);
+ return st->host->settings->ss->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st);
+}
+
+static inline void setActiveScreen(Settings* settings, State* st, unsigned int ssIdx) {
+ assert(settings->ssIndex == ssIdx);
+ Machine* host = st->host;
+
+ settings->ss = settings->screens[ssIdx];
+ if (!settings->ss->table)
+ settings->ss->table = host->processTable;
+ host->activeTable = settings->ss->table;
+
+ // set correct functionBar - readonly if requested, and/or with non-process screens
+ bool readonly = Settings_isReadonly() || (host->activeTable != host->processTable);
+ MainPanel_setFunctionBar(st->mainPanel, readonly);
}
static Htop_Reaction actionNextScreen(State* st) {
- Settings* settings = st->settings;
+ Settings* settings = st->host->settings;
settings->ssIndex++;
if (settings->ssIndex == settings->nScreens) {
settings->ssIndex = 0;
}
- settings->ss = settings->screens[settings->ssIndex];
- return HTOP_REFRESH;
+ setActiveScreen(settings, st, settings->ssIndex);
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;
}
static Htop_Reaction actionPrevScreen(State* st) {
- Settings* settings = st->settings;
+ Settings* settings = st->host->settings;
if (settings->ssIndex == 0) {
settings->ssIndex = settings->nScreens - 1;
} else {
settings->ssIndex--;
}
- settings->ss = settings->screens[settings->ssIndex];
- return HTOP_REFRESH;
+ setActiveScreen(settings, st, settings->ssIndex);
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;
}
-Htop_Reaction Action_setScreenTab(Settings* settings, int x) {
+Htop_Reaction Action_setScreenTab(State* st, int x) {
+ Settings* settings = st->host->settings;
int s = 2;
for (unsigned int i = 0; i < settings->nScreens; i++) {
if (x < s) {
return 0;
}
- const char* name = settings->screens[i]->name;
- int len = strlen(name);
+ const char* tab = settings->screens[i]->heading;
+ int len = strlen(tab);
if (x <= s + len + 1) {
settings->ssIndex = i;
- settings->ss = settings->screens[i];
- return HTOP_REFRESH;
+ setActiveScreen(settings, st, i);
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;
}
s += len + 3;
}
@@ -349,29 +424,30 @@ static Htop_Reaction actionQuit(ATTR_UNUSED State* st) {
}
static Htop_Reaction actionSetAffinity(State* st) {
- if (Settings_isReadonly())
+ if (!Action_writeableProcess(st))
return HTOP_OK;
- if (st->pl->activeCPUs == 1)
+ Machine* host = st->host;
+ if (host->activeCPUs == 1)
return HTOP_OK;
#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
- const Process* p = (const Process*) Panel_getSelected((Panel*)st->mainPanel);
- if (!p)
+ const Row* row = (const Row*) Panel_getSelected((Panel*)st->mainPanel);
+ if (!row)
return HTOP_OK;
- Affinity* affinity1 = Affinity_get(p, st->pl);
+ Affinity* affinity1 = Affinity_rowGet(row, host);
if (!affinity1)
return HTOP_OK;
int width;
- Panel* affinityPanel = AffinityPanel_new(st->pl, affinity1, &width);
+ Panel* affinityPanel = AffinityPanel_new(host, affinity1, &width);
Affinity_delete(affinity1);
const void* set = Action_pickFromVector(st, affinityPanel, width, true);
if (set) {
- Affinity* affinity2 = AffinityPanel_getAffinity(affinityPanel, st->pl);
- bool ok = MainPanel_foreachProcess(st->mainPanel, Affinity_set, (Arg) { .v = affinity2 }, NULL);
+ Affinity* affinity2 = AffinityPanel_getAffinity(affinityPanel, host);
+ bool ok = MainPanel_foreachRow(st->mainPanel, Affinity_rowSet, (Arg) { .v = affinity2 }, NULL);
if (!ok)
beep();
Affinity_delete(affinity2);
@@ -381,11 +457,55 @@ static Htop_Reaction actionSetAffinity(State* st) {
#else
return HTOP_OK;
#endif
+}
+
+#ifdef SCHEDULER_SUPPORT
+static Htop_Reaction actionSetSchedPolicy(State* st) {
+ if (!Action_writeableProcess(st))
+ return HTOP_KEEP_FOLLOWING;
+
+ static int preSelectedPolicy = SCHEDULINGPANEL_INITSELECTEDPOLICY;
+ static int preSelectedPriority = SCHEDULINGPANEL_INITSELECTEDPRIORITY;
+
+ Panel* schedPanel = Scheduling_newPolicyPanel(preSelectedPolicy);
+
+ const ListItem* policy;
+ for (;;) {
+ policy = (const ListItem*) Action_pickFromVector(st, schedPanel, 18, true);
+ if (!policy || policy->key != -1)
+ break;
+
+ Scheduling_togglePolicyPanelResetOnFork(schedPanel);
+ }
+
+ if (policy) {
+ preSelectedPolicy = policy->key;
+
+ Panel* prioPanel = Scheduling_newPriorityPanel(policy->key, preSelectedPriority);
+ if (prioPanel) {
+ const ListItem* prio = (const ListItem*) Action_pickFromVector(st, prioPanel, 14, true);
+ if (prio)
+ preSelectedPriority = prio->key;
+
+ Panel_delete((Object*) prioPanel);
+ }
+
+ SchedulingArg v = { .policy = preSelectedPolicy, .priority = preSelectedPriority };
+
+ bool ok = MainPanel_foreachRow(st->mainPanel, Scheduling_rowSetPolicy, (Arg) { .v = &v }, NULL);
+ if (!ok)
+ beep();
+ }
+
+ Panel_delete((Object*)schedPanel);
+
+ return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING;
}
+#endif /* SCHEDULER_SUPPORT */
static Htop_Reaction actionKill(State* st) {
- if (Settings_isReadonly())
+ if (!Action_writeableProcess(st))
return HTOP_OK;
static int preSelectedSignal = SIGNALSPANEL_INITSELECTEDSIGNAL;
@@ -397,26 +517,31 @@ static Htop_Reaction actionKill(State* st) {
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);
+ bool ok = MainPanel_foreachRow(st->mainPanel, Process_rowSendSignal, (Arg) { .i = sgn->key }, NULL);
+ if (!ok) {
+ beep();
+ }
napms(500);
}
Panel_delete((Object*)signalsPanel);
+
return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
}
static Htop_Reaction actionFilterByUser(State* st) {
Panel* usersPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Show ", "Cancel "));
Panel_setHeader(usersPanel, "Show processes of:");
- UsersTable_foreach(st->ut, addUserToVector, usersPanel);
+ Machine* host = st->host;
+ UsersTable_foreach(host->usersTable, addUserToVector, usersPanel);
Vector_insertionSort(usersPanel->items);
ListItem* allUsers = ListItem_new("All users", -1);
Panel_insert(usersPanel, 0, (Object*) allUsers);
const ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 19, false);
if (picked) {
if (picked == allUsers) {
- st->pl->userId = (uid_t)-1;
+ host->userId = (uid_t)-1;
} else {
- Action_setUserOnly(ListItem_getRef(picked), &(st->pl->userId));
+ Action_setUserOnly(ListItem_getRef(picked), &host->userId);
}
}
Panel_delete((Object*)usersPanel);
@@ -424,7 +549,7 @@ static Htop_Reaction actionFilterByUser(State* st) {
}
Htop_Reaction Action_follow(State* st) {
- st->pl->following = MainPanel_selectedPid(st->mainPanel);
+ st->host->activeTable->following = MainPanel_selectedRow(st->mainPanel);
Panel_setSelectionColor((Panel*)st->mainPanel, PANEL_SELECTION_FOLLOW);
return HTOP_KEEP_FOLLOWING;
}
@@ -435,13 +560,15 @@ static Htop_Reaction actionSetup(State* st) {
}
static Htop_Reaction actionLsof(State* st) {
- if (Settings_isReadonly())
+ if (!Action_writeableProcess(st))
return HTOP_OK;
const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+
OpenFilesScreen* ofs = OpenFilesScreen_new(p);
InfoScreen_run((InfoScreen*)ofs);
OpenFilesScreen_delete((Object*)ofs);
@@ -451,9 +578,15 @@ static Htop_Reaction actionLsof(State* st) {
}
static Htop_Reaction actionShowLocks(State* st) {
+ if (!Action_readableProcess(st))
+ return HTOP_OK;
+
const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
+
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+
ProcessLocksScreen* pls = ProcessLocksScreen_new(p);
InfoScreen_run((InfoScreen*)pls);
ProcessLocksScreen_delete((Object*)pls);
@@ -463,13 +596,15 @@ static Htop_Reaction actionShowLocks(State* st) {
}
static Htop_Reaction actionStrace(State* st) {
- if (Settings_isReadonly())
+ if (!Action_writeableProcess(st))
return HTOP_OK;
const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+
TraceScreen* ts = TraceScreen_new(p);
bool ok = TraceScreen_forkTracer(ts);
if (ok) {
@@ -482,22 +617,23 @@ static Htop_Reaction actionStrace(State* st) {
}
static Htop_Reaction actionTag(State* st) {
- Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
- if (!p)
+ Row* r = (Row*) Panel_getSelected((Panel*)st->mainPanel);
+ if (!r)
return HTOP_OK;
- Process_toggleTag(p);
+ Row_toggleTag(r);
Panel_onKey((Panel*)st->mainPanel, KEY_DOWN);
return HTOP_OK;
}
static Htop_Reaction actionRedraw(ATTR_UNUSED State* st) {
clear();
- return HTOP_REFRESH | HTOP_REDRAW_BAR;
+ // HTOP_RECALCULATE here to make Ctrl-L also refresh the data and not only redraw
+ return HTOP_RECALCULATE | HTOP_REFRESH | HTOP_REDRAW_BAR;
}
-static Htop_Reaction actionTogglePauseProcessUpdate(State* st) {
- st->pauseProcessUpdate = !st->pauseProcessUpdate;
+static Htop_Reaction actionTogglePauseUpdate(State* st) {
+ st->pauseUpdate = !st->pauseUpdate;
return HTOP_REFRESH | HTOP_REDRAW_BAR;
}
@@ -506,6 +642,7 @@ static const struct {
bool roInactive;
const char* info;
} helpLeft[] = {
+ { .key = " #: ", .roInactive = false, .info = "hide/show header meters" },
{ .key = " Tab: ", .roInactive = false, .info = "switch to next screen tab" },
{ .key = " Arrows: ", .roInactive = false, .info = "scroll process list" },
{ .key = " Digits: ", .roInactive = false, .info = "incremental PID search" },
@@ -518,6 +655,7 @@ static const struct {
{ .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 = " O: ", .roInactive = false, .info = "hide/show processes in containers" },
{ .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" },
@@ -547,6 +685,9 @@ static const struct {
{ .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" },
+#ifdef SCHEDULER_SUPPORT
+ { .key = " Y: ", .roInactive = true, .info = "set scheduling policy" },
+#endif
{ .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" },
@@ -584,7 +725,7 @@ static Htop_Reaction actionHelp(State* st) {
addbartext(CRT_colors[CPU_NICE_TEXT], "", "low");
addbartext(CRT_colors[CPU_NORMAL], "/", "normal");
addbartext(CRT_colors[CPU_SYSTEM], "/", "kernel");
- if (st->settings->detailedCPUTime) {
+ if (st->host->settings->detailedCPUTime) {
addbartext(CRT_colors[CPU_IRQ], "/", "irq");
addbartext(CRT_colors[CPU_SOFTIRQ], "/", "soft-irq");
addbartext(CRT_colors[CPU_STEAL], "/", "steal");
@@ -593,7 +734,7 @@ static Htop_Reaction actionHelp(State* st) {
addbartext(CRT_colors[BAR_SHADOW], " ", "used%");
} else {
addbartext(CRT_colors[CPU_GUEST], "/", "guest");
- addbartext(CRT_colors[BAR_SHADOW], " ", "used%");
+ addbartext(CRT_colors[BAR_SHADOW], " ", "used%");
}
addattrstr(CRT_colors[BAR_BORDER], "]");
@@ -601,10 +742,11 @@ static Htop_Reaction actionHelp(State* st) {
mvaddstr(line++, 0, "Memory bar: ");
addattrstr(CRT_colors[BAR_BORDER], "[");
addbartext(CRT_colors[MEMORY_USED], "", "used");
- addbartext(CRT_colors[MEMORY_BUFFERS_TEXT], "/", "buffers");
addbartext(CRT_colors[MEMORY_SHARED], "/", "shared");
+ addbartext(CRT_colors[MEMORY_COMPRESSED], "/", "compressed");
+ addbartext(CRT_colors[MEMORY_BUFFERS_TEXT], "/", "buffers");
addbartext(CRT_colors[MEMORY_CACHE], "/", "cache");
- addbartext(CRT_colors[BAR_SHADOW], " ", "used");
+ addbartext(CRT_colors[BAR_SHADOW], " ", "used");
addbartext(CRT_colors[BAR_SHADOW], "/", "total");
addattrstr(CRT_colors[BAR_BORDER], "]");
@@ -614,10 +756,11 @@ static Htop_Reaction actionHelp(State* st) {
addbartext(CRT_colors[SWAP], "", "used");
#ifdef HTOP_LINUX
addbartext(CRT_colors[SWAP_CACHE], "/", "cache");
+ addbartext(CRT_colors[SWAP_FRONTSWAP], "/", "frontswap");
#else
addbartext(CRT_colors[SWAP_CACHE], " ", "");
#endif
- addbartext(CRT_colors[BAR_SHADOW], " ", "used");
+ addbartext(CRT_colors[BAR_SHADOW], " ", "used");
addbartext(CRT_colors[BAR_SHADOW], "/", "total");
addattrstr(CRT_colors[BAR_BORDER], "]");
@@ -670,9 +813,9 @@ static Htop_Reaction actionHelp(State* st) {
for (item = 0; helpRight[item].key; item++) {
attrset((helpRight[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[HELP_BOLD]);
- mvaddstr(line + item, 41, helpRight[item].key);
+ mvaddstr(line + item, 43, helpRight[item].key);
attrset((helpRight[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[DEFAULT_COLOR]);
- mvaddstr(line + item, 50, helpRight[item].info);
+ mvaddstr(line + item, 52, helpRight[item].info);
}
line += MAXIMUM(leftHelpItems, item);
line++;
@@ -689,26 +832,31 @@ static Htop_Reaction actionHelp(State* st) {
static Htop_Reaction actionUntagAll(State* st) {
for (int i = 0; i < Panel_size((Panel*)st->mainPanel); i++) {
- Process* p = (Process*) Panel_get((Panel*)st->mainPanel, i);
- p->tag = false;
+ Row* row = (Row*) Panel_get((Panel*)st->mainPanel, i);
+ row->tag = false;
}
return HTOP_REFRESH;
}
static Htop_Reaction actionTagAllChildren(State* st) {
- Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
- if (!p)
+ Row* row = (Row*) Panel_getSelected((Panel*)st->mainPanel);
+ if (!row)
return HTOP_OK;
- tagAllChildren((Panel*)st->mainPanel, p);
+ tagAllChildren((Panel*)st->mainPanel, row);
return HTOP_OK;
}
static Htop_Reaction actionShowEnvScreen(State* st) {
+ if (!Action_readableProcess(st))
+ return HTOP_OK;
+
Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+
EnvScreen* es = EnvScreen_new(p);
InfoScreen_run((InfoScreen*)es);
EnvScreen_delete((Object*)es);
@@ -718,10 +866,15 @@ static Htop_Reaction actionShowEnvScreen(State* st) {
}
static Htop_Reaction actionShowCommandScreen(State* st) {
+ if (!Action_readableProcess(st))
+ return HTOP_OK;
+
Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+
CommandScreen* cmdScr = CommandScreen_new(p);
InfoScreen_run((InfoScreen*)cmdScr);
CommandScreen_delete((Object*)cmdScr);
@@ -732,6 +885,7 @@ static Htop_Reaction actionShowCommandScreen(State* st) {
void Action_setBindings(Htop_Action* keys) {
keys[' '] = actionTag;
+ keys['#'] = actionToggleHideMeters;
keys['*'] = actionExpandOrCollapseAllBranches;
keys['+'] = actionExpandOrCollapse;
keys[','] = actionSetSortColumn;
@@ -749,11 +903,15 @@ void Action_setBindings(Htop_Action* keys) {
keys['K'] = actionToggleKernelThreads;
keys['M'] = actionSortByMemory;
keys['N'] = actionSortByPID;
+ keys['O'] = actionToggleRunningInContainer;
keys['P'] = actionSortByCPU;
keys['S'] = actionSetup;
keys['T'] = actionSortByTime;
keys['U'] = actionUntagAll;
- keys['Z'] = actionTogglePauseProcessUpdate;
+#ifdef SCHEDULER_SUPPORT
+ keys['Y'] = actionSetSchedPolicy;
+#endif
+ keys['Z'] = actionTogglePauseUpdate;
keys['['] = actionLowerPriority;
keys['\014'] = actionRedraw; // Ctrl+L
keys['\177'] = actionCollapseIntoParent;
diff --git a/Action.h b/Action.h
index 06af1886..caade03c 100644
--- a/Action.h
+++ b/Action.h
@@ -7,18 +7,15 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <stdbool.h>
#include <sys/types.h>
#include "Header.h"
+#include "Machine.h"
#include "Object.h"
#include "Panel.h"
#include "Process.h"
-#include "ProcessList.h"
#include "Settings.h"
-#include "UsersTable.h"
typedef enum {
@@ -36,28 +33,28 @@ typedef enum {
struct MainPanel_; // IWYU pragma: keep
typedef struct State_ {
- Settings* settings;
- UsersTable* ut;
- ProcessList* pl;
+ Machine* host;
struct MainPanel_* mainPanel;
Header* header;
- bool pauseProcessUpdate;
- bool hideProcessSelection;
+ bool pauseUpdate;
+ bool hideSelection;
+ bool hideMeters;
} State;
static inline bool State_hideFunctionBar(const State* st) {
- return st->settings->hideFunctionBar == 2 || (st->settings->hideFunctionBar == 1 && st->hideProcessSelection);
+ const Settings* settings = st->host->settings;
+ return settings->hideFunctionBar == 2 || (settings->hideFunctionBar == 1 && st->hideSelection);
}
typedef Htop_Reaction (*Htop_Action)(State* st);
-Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess);
+Object* Action_pickFromVector(State* st, Panel* list, int x, bool follow);
bool Action_setUserOnly(const char* userName, uid_t* userId);
Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey);
-Htop_Reaction Action_setScreenTab(Settings* settings, int x);
+Htop_Reaction Action_setScreenTab(State* st, int x);
Htop_Reaction Action_follow(State* st);
diff --git a/Affinity.c b/Affinity.c
index dc6a7fba..fab239eb 100644
--- a/Affinity.c
+++ b/Affinity.c
@@ -10,8 +10,10 @@ in the source distribution for its full text.
#include "Affinity.h"
+#include <assert.h>
#include <stdlib.h>
+#include "Process.h"
#include "XUtils.h"
#if defined(HAVE_LIBHWLOC)
@@ -27,11 +29,11 @@ in the source distribution for its full text.
#endif
-Affinity* Affinity_new(ProcessList* pl) {
+Affinity* Affinity_new(Machine* host) {
Affinity* this = xCalloc(1, sizeof(Affinity));
this->size = 8;
this->cpus = xCalloc(this->size, sizeof(unsigned int));
- this->pl = pl;
+ this->host = host;
return this;
}
@@ -49,17 +51,16 @@ void Affinity_add(Affinity* this, unsigned int id) {
this->used++;
}
-
#if defined(HAVE_LIBHWLOC)
-Affinity* Affinity_get(const Process* proc, ProcessList* pl) {
+static Affinity* Affinity_get(const Process* p, Machine* host) {
hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
- bool ok = (hwloc_get_proc_cpubind(pl->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);
+ bool ok = (hwloc_get_proc_cpubind(host->topology, Process_getPid(p), cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);
Affinity* affinity = NULL;
if (ok) {
- affinity = Affinity_new(pl);
+ affinity = Affinity_new(host);
if (hwloc_bitmap_last(cpuset) == -1) {
- for (unsigned int i = 0; i < pl->existingCPUs; i++) {
+ for (unsigned int i = 0; i < host->existingCPUs; i++) {
Affinity_add(affinity, i);
}
} else {
@@ -73,27 +74,27 @@ Affinity* Affinity_get(const Process* proc, ProcessList* pl) {
return affinity;
}
-bool Affinity_set(Process* proc, Arg arg) {
+static bool Affinity_set(Process* p, Arg arg) {
Affinity* this = arg.v;
hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
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);
+ bool ok = (hwloc_set_proc_cpubind(this->host->topology, Process_getPid(p), cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);
hwloc_bitmap_free(cpuset);
return ok;
}
#elif defined(HAVE_AFFINITY)
-Affinity* Affinity_get(const Process* proc, ProcessList* pl) {
+static Affinity* Affinity_get(const Process* p, Machine* host) {
cpu_set_t cpuset;
- bool ok = (sched_getaffinity(proc->pid, sizeof(cpu_set_t), &cpuset) == 0);
+ bool ok = (sched_getaffinity(Process_getPid(p), sizeof(cpu_set_t), &cpuset) == 0);
if (!ok)
return NULL;
- Affinity* affinity = Affinity_new(pl);
- for (unsigned int i = 0; i < pl->existingCPUs; i++) {
+ Affinity* affinity = Affinity_new(host);
+ for (unsigned int i = 0; i < host->existingCPUs; i++) {
if (CPU_ISSET(i, &cpuset)) {
Affinity_add(affinity, i);
}
@@ -101,15 +102,31 @@ Affinity* Affinity_get(const Process* proc, ProcessList* pl) {
return affinity;
}
-bool Affinity_set(Process* proc, Arg arg) {
+static bool Affinity_set(Process* p, Arg arg) {
Affinity* this = arg.v;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
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);
+ bool ok = (sched_setaffinity(Process_getPid(p), sizeof(unsigned long), &cpuset) == 0);
return ok;
}
#endif
+
+#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)
+
+bool Affinity_rowSet(Row* row, Arg arg) {
+ Process* p = (Process*) row;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+ return Affinity_set(p, arg);
+}
+
+Affinity* Affinity_rowGet(const Row* row, Machine* host) {
+ const Process* p = (const Process*) row;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+ return Affinity_get(p, host);
+}
+
+#endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */
diff --git a/Affinity.h b/Affinity.h
index 5e7bfe2e..c2189062 100644
--- a/Affinity.h
+++ b/Affinity.h
@@ -3,20 +3,18 @@
/*
htop - Affinity.h
(C) 2004-2011 Hisham H. Muhammad
-(C) 2020 Red Hat, Inc. All Rights Reserved.
+(C) 2020,2023 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 "ProcessList.h"
+#include "Machine.h"
#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)
#include <stdbool.h>
#include "Object.h"
-#include "Process.h"
+#include "Row.h"
#endif
@@ -26,13 +24,13 @@ in the source distribution for its full text.
typedef struct Affinity_ {
- ProcessList* pl;
+ Machine* host;
unsigned int size;
unsigned int used;
unsigned int* cpus;
} Affinity;
-Affinity* Affinity_new(ProcessList* pl);
+Affinity* Affinity_new(Machine* host);
void Affinity_delete(Affinity* this);
@@ -40,9 +38,9 @@ void Affinity_add(Affinity* this, unsigned int id);
#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)
-Affinity* Affinity_get(const Process* proc, ProcessList* pl);
+Affinity* Affinity_rowGet(const Row* row, Machine* host);
-bool Affinity_set(Process* proc, Arg arg);
+bool Affinity_rowSet(Row* row, Arg arg);
#endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */
diff --git a/AffinityPanel.c b/AffinityPanel.c
index 9875a5ce..f4009080 100644
--- a/AffinityPanel.c
+++ b/AffinityPanel.c
@@ -122,7 +122,7 @@ static MaskItem* MaskItem_newSingleton(const char* text, int cpu, bool isSet) {
typedef struct AffinityPanel_ {
Panel super;
- ProcessList* pl;
+ Machine* host;
bool topoView;
Vector* cpuids;
unsigned width;
@@ -201,58 +201,58 @@ static HandlerResult AffinityPanel_eventHandler(Panel* super, int ch) {
MaskItem* selected = (MaskItem*) Panel_getSelected(super);
bool keepSelected = true;
- switch(ch) {
- case KEY_MOUSE:
- case KEY_RECLICK:
- case ' ':
- #ifdef HAVE_LIBHWLOC
- if (selected->value == 2) {
- /* Item was selected, so remove this mask from the top cpuset. */
- hwloc_bitmap_andnot(this->workCpuset, this->workCpuset, selected->cpuset);
- selected->value = 0;
- } else {
- /* Item was not or only partial selected, so set all bits from this object
- in the top cpuset. */
- hwloc_bitmap_or(this->workCpuset, this->workCpuset, selected->cpuset);
- selected->value = 2;
- }
- #else
- selected->value = selected->value ? 0 : 2; /* toggle between 0 and 2 */
- #endif
+ switch (ch) {
+ case KEY_MOUSE:
+ case KEY_RECLICK:
+ case ' ':
+ #ifdef HAVE_LIBHWLOC
+ if (selected->value == 2) {
+ /* Item was selected, so remove this mask from the top cpuset. */
+ hwloc_bitmap_andnot(this->workCpuset, this->workCpuset, selected->cpuset);
+ selected->value = 0;
+ } else {
+ /* Item was not or only partial selected, so set all bits from this object
+ in the top cpuset. */
+ hwloc_bitmap_or(this->workCpuset, this->workCpuset, selected->cpuset);
+ selected->value = 2;
+ }
+ #else
+ selected->value = selected->value ? 0 : 2; /* toggle between 0 and 2 */
+ #endif
- result = HANDLED;
- break;
+ result = HANDLED;
+ break;
- #ifdef HAVE_LIBHWLOC
+#ifdef HAVE_LIBHWLOC
- case KEY_F(1):
- hwloc_bitmap_copy(this->workCpuset, this->allCpuset);
- result = HANDLED;
- break;
+ case KEY_F(1):
+ hwloc_bitmap_copy(this->workCpuset, this->allCpuset);
+ result = HANDLED;
+ break;
- case KEY_F(2):
- this->topoView = !this->topoView;
- keepSelected = false;
+ case KEY_F(2):
+ this->topoView = !this->topoView;
+ keepSelected = false;
- result = HANDLED;
- break;
+ result = HANDLED;
+ break;
- case KEY_F(3):
- case '-':
- case '+':
- if (selected->sub_tree)
- selected->sub_tree = 1 + !(selected->sub_tree - 1); /* toggle between 1 and 2 */
+ case KEY_F(3):
+ case '-':
+ case '+':
+ if (selected->sub_tree)
+ selected->sub_tree = 1 + !(selected->sub_tree - 1); /* toggle between 1 and 2 */
- result = HANDLED;
- break;
+ result = HANDLED;
+ break;
- #endif
+#endif
- case 0x0a:
- case 0x0d:
- case KEY_ENTER:
- result = BREAK_LOOP;
- break;
+ case 0x0a:
+ case 0x0d:
+ case KEY_ENTER:
+ result = BREAK_LOOP;
+ break;
}
if (HANDLED == result)
@@ -272,7 +272,7 @@ static MaskItem* AffinityPanel_addObject(AffinityPanel* this, hwloc_obj_t obj, u
char buf[64], indent_buf[left + 1];
if (obj->type == HWLOC_OBJ_PU) {
- index = Settings_cpuId(this->pl->settings, obj->os_index);
+ index = Settings_cpuId(this->host->settings, obj->os_index);
type_name = "CPU";
index_prefix = "";
}
@@ -357,12 +357,12 @@ 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, const Affinity* affinity, int* width) {
+Panel* AffinityPanel_new(Machine* host, 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));
- this->pl = pl;
+ this->host = host;
/* defaults to 15, this also includes the gap between the panels,
* but this will be added by the caller */
this->width = 14;
@@ -370,25 +370,25 @@ Panel* AffinityPanel_new(ProcessList* pl, const Affinity* affinity, int* width)
this->cpuids = Vector_new(Class(MaskItem), true, DEFAULT_SIZE);
#ifdef HAVE_LIBHWLOC
- this->topoView = pl->settings->topologyAffinity;
+ this->topoView = host->settings->topologyAffinity;
#else
this->topoView = false;
#endif
#ifdef HAVE_LIBHWLOC
- this->allCpuset = hwloc_topology_get_complete_cpuset(pl->topology);
+ this->allCpuset = hwloc_topology_get_complete_cpuset(host->topology);
this->workCpuset = hwloc_bitmap_alloc();
#endif
Panel_setHeader(super, "Use CPUs:");
unsigned int curCpu = 0;
- for (unsigned int i = 0; i < pl->existingCPUs; i++) {
- if (!ProcessList_isCPUonline(this->pl, i))
+ for (unsigned int i = 0; i < host->existingCPUs; i++) {
+ if (!Machine_isCPUonline(host, i))
continue;
char number[16];
- xSnprintf(number, 9, "CPU %d", Settings_cpuId(pl->settings, i));
+ xSnprintf(number, 9, "CPU %d", Settings_cpuId(host->settings, i));
unsigned cpu_width = 4 + strlen(number);
if (cpu_width > this->width) {
this->width = cpu_width;
@@ -408,7 +408,7 @@ Panel* AffinityPanel_new(ProcessList* pl, const Affinity* affinity, int* width)
}
#ifdef HAVE_LIBHWLOC
- this->topoRoot = AffinityPanel_buildTopology(this, hwloc_get_root_obj(pl->topology), 0, NULL);
+ this->topoRoot = AffinityPanel_buildTopology(this, hwloc_get_root_obj(host->topology), 0, NULL);
#endif
if (width) {
@@ -420,9 +420,9 @@ Panel* AffinityPanel_new(ProcessList* pl, const Affinity* affinity, int* width)
return super;
}
-Affinity* AffinityPanel_getAffinity(Panel* super, ProcessList* pl) {
+Affinity* AffinityPanel_getAffinity(Panel* super, Machine* host) {
const AffinityPanel* this = (AffinityPanel*) super;
- Affinity* affinity = Affinity_new(pl);
+ Affinity* affinity = Affinity_new(host);
#ifdef HAVE_LIBHWLOC
int i;
diff --git a/AffinityPanel.h b/AffinityPanel.h
index 87b1b850..f5b97b93 100644
--- a/AffinityPanel.h
+++ b/AffinityPanel.h
@@ -8,14 +8,14 @@ in the source distribution for its full text.
*/
#include "Affinity.h"
+#include "Machine.h"
#include "Panel.h"
-#include "ProcessList.h"
extern const PanelClass AffinityPanel_class;
-Panel* AffinityPanel_new(ProcessList* pl, const Affinity* affinity, int* width);
+Panel* AffinityPanel_new(Machine* host, const Affinity* affinity, int* width);
-Affinity* AffinityPanel_getAffinity(Panel* super, ProcessList* pl);
+Affinity* AffinityPanel_getAffinity(Panel* super, Machine* host);
#endif
diff --git a/AvailableColumnsPanel.c b/AvailableColumnsPanel.c
index 7720bef4..545ef7d7 100644
--- a/AvailableColumnsPanel.c
+++ b/AvailableColumnsPanel.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 "AvailableColumnsPanel.h"
#include <assert.h>
@@ -18,8 +20,10 @@ in the source distribution for its full text.
#include "Hashtable.h"
#include "ListItem.h"
#include "Object.h"
+#include "Platform.h"
#include "Process.h"
#include "ProvideCurses.h"
+#include "RowField.h"
#include "XUtils.h"
@@ -34,8 +38,8 @@ static void AvailableColumnsPanel_delete(Object* object) {
static void AvailableColumnsPanel_insert(AvailableColumnsPanel* this, int at, int key) {
const char* name;
- if (key >= LAST_PROCESSFIELD)
- name = DynamicColumn_init(key);
+ if (key >= ROW_DYNAMIC_FIELDS)
+ name = DynamicColumn_name(key);
else
name = Process_fields[key].name;
Panel_insert(this->columns, at, (Object*) ListItem_new(name, key));
@@ -45,11 +49,10 @@ static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) {
AvailableColumnsPanel* this = (AvailableColumnsPanel*) super;
HandlerResult result = IGNORED;
- switch(ch) {
+ switch (ch) {
case 13:
case KEY_ENTER:
- case KEY_F(5):
- {
+ case KEY_F(5): {
const ListItem* selected = (ListItem*) Panel_getSelected(super);
if (!selected)
break;
@@ -62,11 +65,9 @@ static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) {
break;
}
default:
- {
if (0 < ch && ch < 255 && isgraph((unsigned char)ch))
result = Panel_selectByTyping(super, ch);
break;
- }
}
return result;
}
@@ -81,42 +82,61 @@ const PanelClass AvailableColumnsPanel_class = {
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
+ if (column->table) /* DynamicScreen, handled differently */
+ return;
+ AvailableColumnsPanel* this = (AvailableColumnsPanel*) data;
+ const char* title = column->heading ? column->heading : column->name;
+ const char* text = column->description ? column->description : column->caption;
char description[256];
- xSnprintf(description, sizeof(description), "%s - %s", title, column->description);
- Panel_add(super, (Object*) ListItem_new(description, key));
+ if (text)
+ xSnprintf(description, sizeof(description), "%s - %s", title, text);
+ else
+ xSnprintf(description, sizeof(description), "%s", title);
+ Panel_add(&this->super, (Object*) ListItem_new(description, key));
}
// Handle DynamicColumns entries in the AvailableColumnsPanel
-static void AvailableColumnsPanel_addDynamicColumns(Panel* super, Hashtable* dynamicColumns) {
+static void AvailableColumnsPanel_addDynamicColumns(AvailableColumnsPanel* this, Hashtable* dynamicColumns) {
assert(dynamicColumns);
- Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, super);
+ Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, this);
}
// Handle remaining Platform Meter entries in the AvailableColumnsPanel
-static void AvailableColumnsPanel_addPlatformColumn(Panel* super) {
+static void AvailableColumnsPanel_addPlatformColumns(AvailableColumnsPanel* this) {
for (int i = 1; i < LAST_PROCESSFIELD; i++) {
if (i != COMM && Process_fields[i].description) {
char description[256];
xSnprintf(description, sizeof(description), "%s - %s", Process_fields[i].name, Process_fields[i].description);
- Panel_add(super, (Object*) ListItem_new(description, i));
+ Panel_add(&this->super, (Object*) ListItem_new(description, i));
}
}
}
+// Handle DynamicColumns entries associated with DynamicScreens
+static void AvailableColumnsPanel_addDynamicScreens(AvailableColumnsPanel* this, const char* screen) {
+ Platform_addDynamicScreenAvailableColumns(&this->super, screen);
+}
+
+void AvailableColumnsPanel_fill(AvailableColumnsPanel* this, const char* dynamicScreen, Hashtable* dynamicColumns) {
+ Panel* super = (Panel*) this;
+ Panel_prune(super);
+ if (dynamicScreen) {
+ AvailableColumnsPanel_addDynamicScreens(this, dynamicScreen);
+ } else {
+ AvailableColumnsPanel_addPlatformColumns(this);
+ AvailableColumnsPanel_addDynamicColumns(this, dynamicColumns);
+ }
+}
+
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;
+ AvailableColumnsPanel_fill(this, NULL, dynamicColumns);
+
return this;
}
diff --git a/AvailableColumnsPanel.h b/AvailableColumnsPanel.h
index aca59060..0d8709fe 100644
--- a/AvailableColumnsPanel.h
+++ b/AvailableColumnsPanel.h
@@ -20,4 +20,6 @@ extern const PanelClass AvailableColumnsPanel_class;
AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns);
+void AvailableColumnsPanel_fill(AvailableColumnsPanel* this, const char* dynamicScreen, Hashtable* dynamicColumns);
+
#endif
diff --git a/AvailableMetersPanel.c b/AvailableMetersPanel.c
index 368f494f..9a1d367c 100644
--- a/AvailableMetersPanel.c
+++ b/AvailableMetersPanel.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 "AvailableMetersPanel.h"
#include <assert.h>
@@ -23,6 +25,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "Platform.h"
#include "ProvideCurses.h"
+#include "Settings.h"
#include "XUtils.h"
@@ -54,37 +57,36 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
HandlerResult result = IGNORED;
bool update = false;
- switch(ch) {
+ switch (ch) {
case KEY_F(5):
case 'l':
case 'L':
- {
AvailableMetersPanel_addMeter(header, this->meterPanels[0], Platform_meterTypes[type], param, 0);
result = HANDLED;
update = true;
break;
- }
case 0x0a:
case 0x0d:
case KEY_ENTER:
case KEY_F(6):
case 'r':
case 'R':
- {
AvailableMetersPanel_addMeter(header, this->meterPanels[this->columns - 1], Platform_meterTypes[type], param, this->columns - 1);
result = (KEY_LEFT << 16) | SYNTH_KEY;
update = true;
break;
- }
}
+
if (update) {
- this->settings->changed = true;
- this->settings->lastUpdate++;
+ Settings* settings = this->host->settings;
+ settings->changed = true;
+ settings->lastUpdate++;
Header_calculateHeight(header);
Header_updateData(header);
Header_draw(header);
ScreenManager_resize(this->scr);
}
+
return result;
}
@@ -97,12 +99,12 @@ const PanelClass AvailableMetersPanel_class = {
};
// Handle (&CPUMeter_class) entries in the AvailableMetersPanel
-static void AvailableMetersPanel_addCPUMeters(Panel* super, const MeterClass* type, const ProcessList* pl) {
- if (pl->existingCPUs > 1) {
+static void AvailableMetersPanel_addCPUMeters(Panel* super, const MeterClass* type, const Machine* host) {
+ if (host->existingCPUs > 1) {
Panel_add(super, (Object*) ListItem_new("CPU average", 0));
- for (unsigned int i = 1; i <= pl->existingCPUs; i++) {
+ for (unsigned int i = 1; i <= host->existingCPUs; i++) {
char buffer[50];
- xSnprintf(buffer, sizeof(buffer), "%s %d", type->uiName, Settings_cpuId(pl->settings, i - 1));
+ xSnprintf(buffer, sizeof(buffer), "%s %d", type->uiName, Settings_cpuId(host->settings, i - 1));
Panel_add(super, (Object*) ListItem_new(buffer, i));
}
} else {
@@ -128,10 +130,11 @@ static void AvailableMetersPanel_addDynamicMeter(ATTR_UNUSED ht_key_t key, void*
}
// Handle (&DynamicMeter_class) entries in the AvailableMetersPanel
-static void AvailableMetersPanel_addDynamicMeters(Panel* super, const ProcessList* pl, unsigned int offset) {
+static void AvailableMetersPanel_addDynamicMeters(Panel* super, const Settings* settings, unsigned int offset) {
DynamicIterator iter = { .super = super, .id = 1, .offset = offset };
- assert(pl->dynamicMeters != NULL);
- Hashtable_foreach(pl->dynamicMeters, AvailableMetersPanel_addDynamicMeter, &iter);
+ Hashtable* dynamicMeters = settings->dynamicColumns;
+ assert(dynamicMeters != NULL);
+ Hashtable_foreach(dynamicMeters, AvailableMetersPanel_addDynamicMeter, &iter);
}
// Handle remaining Platform Meter entries in the AvailableMetersPanel
@@ -140,13 +143,13 @@ static void AvailableMetersPanel_addPlatformMeter(Panel* super, const MeterClass
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* AvailableMetersPanel_new(Machine* host, Header* header, size_t columns, MetersPanel** meterPanels, ScreenManager* scr) {
AvailableMetersPanel* this = AllocThis(AvailableMetersPanel);
Panel* super = (Panel*) this;
FunctionBar* fuBar = FunctionBar_newEnterEsc("Add ", "Done ");
Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
- this->settings = settings;
+ this->host = host;
this->header = header;
this->columns = columns;
this->meterPanels = meterPanels;
@@ -161,11 +164,11 @@ AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* heade
const MeterClass* type = Platform_meterTypes[i];
assert(type != &CPUMeter_class);
if (type == &DynamicMeter_class)
- AvailableMetersPanel_addDynamicMeters(super, pl, i);
+ AvailableMetersPanel_addDynamicMeters(super, host->settings, i);
else
AvailableMetersPanel_addPlatformMeter(super, type, i);
}
- AvailableMetersPanel_addCPUMeters(super, &CPUMeter_class, pl);
+ AvailableMetersPanel_addCPUMeters(super, &CPUMeter_class, host);
return this;
}
diff --git a/AvailableMetersPanel.h b/AvailableMetersPanel.h
index f5f7a2dd..fad1e6e8 100644
--- a/AvailableMetersPanel.h
+++ b/AvailableMetersPanel.h
@@ -10,18 +10,16 @@ in the source distribution for its full text.
#include <stddef.h>
#include "Header.h"
+#include "Machine.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;
+ Machine* host;
Header* header;
size_t columns;
MetersPanel** meterPanels;
@@ -29,6 +27,6 @@ typedef struct AvailableMetersPanel_ {
extern const PanelClass AvailableMetersPanel_class;
-AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, size_t columns, MetersPanel **meterPanels, ScreenManager* scr, const ProcessList* pl);
+AvailableMetersPanel* AvailableMetersPanel_new(Machine* host, Header* header, size_t columns, MetersPanel** meterPanels, ScreenManager* scr);
#endif
diff --git a/BatteryMeter.c b/BatteryMeter.c
index 33d17b73..dad7754a 100644
--- a/BatteryMeter.c
+++ b/BatteryMeter.c
@@ -7,11 +7,14 @@ in the source distribution for its full text.
This meter written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
*/
+#include "config.h" // IWYU pragma: keep
+
#include "BatteryMeter.h"
#include <math.h>
#include "CRT.h"
+#include "Macros.h"
#include "Object.h"
#include "Platform.h"
#include "XUtils.h"
@@ -27,7 +30,7 @@ static void BatteryMeter_updateValues(Meter* this) {
Platform_getBattery(&percent, &isOnAC);
- if (isnan(percent)) {
+ if (!isNonnegative(percent)) {
this->values[0] = NAN;
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "N/A");
return;
@@ -37,16 +40,16 @@ static void BatteryMeter_updateValues(Meter* this) {
const char* text;
switch (isOnAC) {
- case AC_PRESENT:
- text = this->mode == TEXT_METERMODE ? " (Running on A/C)" : "(A/C)";
- break;
- case AC_ABSENT:
- text = this->mode == TEXT_METERMODE ? " (Running on battery)" : "(bat)";
- break;
- case AC_ERROR:
- default:
- text = "";
- break;
+ case AC_PRESENT:
+ text = this->mode == TEXT_METERMODE ? " (Running on A/C)" : "(A/C)";
+ break;
+ case AC_ABSENT:
+ text = this->mode == TEXT_METERMODE ? " (Running on battery)" : "(bat)";
+ break;
+ case AC_ERROR:
+ default:
+ text = "";
+ break;
}
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%%s", percent, text);
diff --git a/CPUMeter.c b/CPUMeter.c
index 9974db99..1077f4e1 100644
--- a/CPUMeter.c
+++ b/CPUMeter.c
@@ -9,14 +9,17 @@ in the source distribution for its full text.
#include "CPUMeter.h"
-#include <math.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "CRT.h"
+#include "Machine.h"
+#include "Macros.h"
#include "Object.h"
#include "Platform.h"
-#include "ProcessList.h"
#include "RichString.h"
#include "Settings.h"
#include "XUtils.h"
@@ -40,11 +43,12 @@ typedef struct CPUMeterData_ {
static void CPUMeter_init(Meter* this) {
unsigned int cpu = this->param;
+ const Machine* host = this->host;
if (cpu == 0) {
Meter_setCaption(this, "Avg");
- } else if (this->pl->activeCPUs > 1) {
+ } else if (host->activeCPUs > 1) {
char caption[10];
- xSnprintf(caption, sizeof(caption), "%3u", Settings_cpuId(this->pl->settings, cpu - 1));
+ xSnprintf(caption, sizeof(caption), "%3u", Settings_cpuId(host->settings, cpu - 1));
Meter_setCaption(this, caption);
}
}
@@ -60,14 +64,17 @@ static void CPUMeter_getUiName(const Meter* this, char* buffer, size_t length) {
static void CPUMeter_updateValues(Meter* this) {
memset(this->values, 0, sizeof(double) * CPU_METER_ITEMCOUNT);
+ const Machine* host = this->host;
+ const Settings* settings = host->settings;
+
unsigned int cpu = this->param;
- if (cpu > this->pl->existingCPUs) {
+ if (cpu > host->existingCPUs) {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "absent");
return;
}
double percent = Platform_setCPUValues(this, cpu);
- if (isnan(percent)) {
+ if (!isNonnegative(percent)) {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "offline");
return;
}
@@ -76,25 +83,25 @@ static void CPUMeter_updateValues(Meter* this) {
char cpuFrequencyBuffer[16] = { 0 };
char cpuTemperatureBuffer[16] = { 0 };
- if (this->pl->settings->showCPUUsage) {
+ if (settings->showCPUUsage) {
xSnprintf(cpuUsageBuffer, sizeof(cpuUsageBuffer), "%.1f%%", percent);
}
- if (this->pl->settings->showCPUFrequency) {
+ if (settings->showCPUFrequency) {
double cpuFrequency = this->values[CPU_METER_FREQUENCY];
- if (isnan(cpuFrequency)) {
- xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A");
- } else {
+ if (isNonnegative(cpuFrequency)) {
xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "%4uMHz", (unsigned)cpuFrequency);
+ } else {
+ xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A");
}
}
#ifdef BUILD_WITH_CPU_TEMP
- if (this->pl->settings->showCPUTemperature) {
+ if (settings->showCPUTemperature) {
double cpuTemperature = this->values[CPU_METER_TEMPERATURE];
- if (isnan(cpuTemperature))
+ if (isNaN(cpuTemperature))
xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A");
- else if (this->pl->settings->degreeFahrenheit)
+ else if (settings->degreeFahrenheit)
xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%3d%sF", (int)(cpuTemperature * 9 / 5 + 32), CRT_degreeSign);
else
xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%d%sC", (int)cpuTemperature, CRT_degreeSign);
@@ -113,8 +120,10 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
char buffer[50];
int len;
const Meter* this = (const Meter*)cast;
+ const Machine* host = this->host;
+ const Settings* settings = host->settings;
- if (this->param > this->pl->existingCPUs) {
+ if (this->param > host->existingCPUs) {
RichString_appendAscii(out, CRT_colors[METER_SHADOW], " absent");
return;
}
@@ -127,7 +136,7 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NORMAL]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], ":");
RichString_appendnAscii(out, CRT_colors[CPU_NORMAL], buffer, len);
- if (this->pl->settings->detailedCPUTime) {
+ if (settings->detailedCPUTime) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_KERNEL]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "sy:");
RichString_appendnAscii(out, CRT_colors[CPU_SYSTEM], buffer, len);
@@ -140,12 +149,12 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_SOFTIRQ]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "si:");
RichString_appendnAscii(out, CRT_colors[CPU_SOFTIRQ], buffer, len);
- if (!isnan(this->values[CPU_METER_STEAL])) {
+ if (isNonnegative(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_appendnAscii(out, CRT_colors[CPU_STEAL], buffer, len);
}
- if (!isnan(this->values[CPU_METER_GUEST])) {
+ if (isNonnegative(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_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len);
@@ -160,20 +169,32 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "low:");
RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len);
- if (!isnan(this->values[CPU_METER_IRQ])) {
+ if (isNonnegative(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_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len);
}
}
+ if (settings->showCPUFrequency) {
+ char cpuFrequencyBuffer[10];
+ double cpuFrequency = this->values[CPU_METER_FREQUENCY];
+ if (isNonnegative(cpuFrequency)) {
+ len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "%4uMHz ", (unsigned)cpuFrequency);
+ } else {
+ len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A ");
+ }
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "freq: ");
+ RichString_appendnWide(out, CRT_colors[METER_VALUE], cpuFrequencyBuffer, len);
+ }
+
#ifdef BUILD_WITH_CPU_TEMP
- if (this->pl->settings->showCPUTemperature) {
+ if (settings->showCPUTemperature) {
char cpuTemperatureBuffer[10];
double cpuTemperature = this->values[CPU_METER_TEMPERATURE];
- if (isnan(cpuTemperature)) {
+ if (isNaN(cpuTemperature)) {
len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A");
- } else if (this->pl->settings->degreeFahrenheit) {
+ } else if (settings->degreeFahrenheit) {
len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sF", cpuTemperature * 9 / 5 + 32, CRT_degreeSign);
} else {
len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sC", cpuTemperature, CRT_degreeSign);
@@ -187,7 +208,7 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
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]) {
+ switch (Meter_name(this)[0]) {
default:
case 'A': // All
*start = 0;
@@ -195,10 +216,10 @@ static void AllCPUsMeter_getRange(const Meter* this, int* start, int* count) {
break;
case 'L': // First Half
*start = 0;
- *count = (cpus+1) / 2;
+ *count = (cpus + 1) / 2;
break;
case 'R': // Second Half
- *start = (cpus+1) / 2;
+ *start = (cpus + 1) / 2;
*count = cpus / 2;
break;
}
@@ -213,8 +234,8 @@ static void AllCPUsMeter_updateValues(Meter* this) {
Meter_updateValues(meters[i]);
}
-static void CPUMeterCommonInit(Meter* this, int ncol) {
- unsigned int cpus = this->pl->existingCPUs;
+static void CPUMeterCommonInit(Meter* this) {
+ unsigned int cpus = this->host->existingCPUs;
CPUMeterData* data = this->meterData;
if (!data) {
data = this->meterData = xMalloc(sizeof(CPUMeterData));
@@ -226,28 +247,23 @@ static void CPUMeterCommonInit(Meter* this, int ncol) {
AllCPUsMeter_getRange(this, &start, &count);
for (int i = 0; i < count; i++) {
if (!meters[i])
- meters[i] = Meter_new(this->pl, start + i + 1, (const MeterClass*) Class(CPUMeter));
+ meters[i] = Meter_new(this->host, start + i + 1, (const MeterClass*) Class(CPUMeter));
Meter_init(meters[i]);
}
-
- if (this->mode == 0)
- this->mode = BAR_METERMODE;
-
- int h = Meter_modes[this->mode]->h;
- this->h = h * ((count + ncol - 1) / ncol);
}
-static void CPUMeterCommonUpdateMode(Meter* this, int mode, int ncol) {
+static void CPUMeterCommonUpdateMode(Meter* this, MeterModeId mode, int ncol) {
CPUMeterData* data = this->meterData;
Meter** meters = data->meters;
this->mode = mode;
- int h = Meter_modes[mode]->h;
int start, count;
AllCPUsMeter_getRange(this, &start, &count);
for (int i = 0; i < count; i++) {
Meter_setMode(meters[i], mode);
}
+ int h = meters[0]->h;
+ assert(h > 0);
this->h = h * ((count + ncol - 1) / ncol);
}
@@ -262,35 +278,19 @@ static void AllCPUsMeter_done(Meter* this) {
free(data);
}
-static void SingleColCPUsMeter_init(Meter* this) {
- CPUMeterCommonInit(this, 1);
-}
-
-static void SingleColCPUsMeter_updateMode(Meter* this, int mode) {
+static void SingleColCPUsMeter_updateMode(Meter* this, MeterModeId mode) {
CPUMeterCommonUpdateMode(this, mode, 1);
}
-static void DualColCPUsMeter_init(Meter* this) {
- CPUMeterCommonInit(this, 2);
-}
-
-static void DualColCPUsMeter_updateMode(Meter* this, int mode) {
+static void DualColCPUsMeter_updateMode(Meter* this, MeterModeId mode) {
CPUMeterCommonUpdateMode(this, mode, 2);
}
-static void QuadColCPUsMeter_init(Meter* this) {
- CPUMeterCommonInit(this, 4);
-}
-
-static void QuadColCPUsMeter_updateMode(Meter* this, int mode) {
+static void QuadColCPUsMeter_updateMode(Meter* this, MeterModeId mode) {
CPUMeterCommonUpdateMode(this, mode, 4);
}
-static void OctoColCPUsMeter_init(Meter* this) {
- CPUMeterCommonInit(this, 8);
-}
-
-static void OctoColCPUsMeter_updateMode(Meter* this, int mode) {
+static void OctoColCPUsMeter_updateMode(Meter* this, MeterModeId mode) {
CPUMeterCommonUpdateMode(this, mode, 8);
}
@@ -360,7 +360,7 @@ const MeterClass AllCPUsMeter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "AllCPUs",
@@ -368,7 +368,7 @@ const MeterClass AllCPUsMeter_class = {
.description = "CPUs (1/1): all CPUs",
.caption = "CPU",
.draw = SingleColCPUsMeter_draw,
- .init = SingleColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = SingleColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -380,7 +380,7 @@ const MeterClass AllCPUs2Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -389,7 +389,7 @@ const MeterClass AllCPUs2Meter_class = {
.description = "CPUs (1&2/2): all CPUs in 2 shorter columns",
.caption = "CPU",
.draw = DualColCPUsMeter_draw,
- .init = DualColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = DualColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -401,7 +401,7 @@ const MeterClass LeftCPUsMeter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -410,7 +410,7 @@ const MeterClass LeftCPUsMeter_class = {
.description = "CPUs (1/2): first half of list",
.caption = "CPU",
.draw = SingleColCPUsMeter_draw,
- .init = SingleColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = SingleColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -422,7 +422,7 @@ const MeterClass RightCPUsMeter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -431,7 +431,7 @@ const MeterClass RightCPUsMeter_class = {
.description = "CPUs (2/2): second half of list",
.caption = "CPU",
.draw = SingleColCPUsMeter_draw,
- .init = SingleColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = SingleColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -443,7 +443,7 @@ const MeterClass LeftCPUs2Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -452,7 +452,7 @@ const MeterClass LeftCPUs2Meter_class = {
.description = "CPUs (1&2/4): first half in 2 shorter columns",
.caption = "CPU",
.draw = DualColCPUsMeter_draw,
- .init = DualColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = DualColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -464,7 +464,7 @@ const MeterClass RightCPUs2Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -473,7 +473,7 @@ const MeterClass RightCPUs2Meter_class = {
.description = "CPUs (3&4/4): second half in 2 shorter columns",
.caption = "CPU",
.draw = DualColCPUsMeter_draw,
- .init = DualColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = DualColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -485,7 +485,7 @@ const MeterClass AllCPUs4Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -494,7 +494,7 @@ const MeterClass AllCPUs4Meter_class = {
.description = "CPUs (1&2&3&4/4): all CPUs in 4 shorter columns",
.caption = "CPU",
.draw = QuadColCPUsMeter_draw,
- .init = QuadColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = QuadColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -506,7 +506,7 @@ const MeterClass LeftCPUs4Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -515,7 +515,7 @@ const MeterClass LeftCPUs4Meter_class = {
.description = "CPUs (1-4/8): first half in 4 shorter columns",
.caption = "CPU",
.draw = QuadColCPUsMeter_draw,
- .init = QuadColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = QuadColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -527,7 +527,7 @@ const MeterClass RightCPUs4Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -536,7 +536,7 @@ const MeterClass RightCPUs4Meter_class = {
.description = "CPUs (5-8/8): second half in 4 shorter columns",
.caption = "CPU",
.draw = QuadColCPUsMeter_draw,
- .init = QuadColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = QuadColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -548,7 +548,7 @@ const MeterClass AllCPUs8Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -557,7 +557,7 @@ const MeterClass AllCPUs8Meter_class = {
.description = "CPUs (1-8/8): all CPUs in 8 shorter columns",
.caption = "CPU",
.draw = OctoColCPUsMeter_draw,
- .init = OctoColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = OctoColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -569,7 +569,7 @@ const MeterClass LeftCPUs8Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -578,7 +578,7 @@ const MeterClass LeftCPUs8Meter_class = {
.description = "CPUs (1-8/16): first half in 8 shorter columns",
.caption = "CPU",
.draw = OctoColCPUsMeter_draw,
- .init = OctoColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = OctoColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
@@ -590,7 +590,7 @@ const MeterClass RightCPUs8Meter_class = {
.display = CPUMeter_display
},
.updateValues = AllCPUsMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -599,7 +599,7 @@ const MeterClass RightCPUs8Meter_class = {
.description = "CPUs (9-16/16): second half in 8 shorter columns",
.caption = "CPU",
.draw = OctoColCPUsMeter_draw,
- .init = OctoColCPUsMeter_init,
+ .init = CPUMeterCommonInit,
.updateMode = OctoColCPUsMeter_updateMode,
.done = AllCPUsMeter_done
};
diff --git a/CRT.c b/CRT.c
index 95f8cc13..149cd519 100644
--- a/CRT.c
+++ b/CRT.c
@@ -19,7 +19,9 @@ in the source distribution for its full text.
#include <string.h>
#include <unistd.h>
+#include "CommandLine.h"
#include "ProvideCurses.h"
+#include "ProvideTerm.h"
#include "XUtils.h"
#if !defined(NDEBUG) && defined(HAVE_MEMFD_CREATE)
@@ -134,7 +136,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[METER_VALUE] = A_BOLD | ColorPair(Cyan, Black),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),
[METER_VALUE_IOREAD] = ColorPair(Green, Black),
- [METER_VALUE_IOWRITE] = ColorPair(Blue, Black),
+ [METER_VALUE_IOWRITE] = A_BOLD | ColorPair(Blue, Black),
[METER_VALUE_NOTICE] = A_BOLD | ColorPair(White, Black),
[METER_VALUE_OK] = ColorPair(Green, Black),
[METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, Black),
@@ -156,22 +158,25 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_THREAD] = ColorPair(Green, Black),
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Green, Black),
[PROCESS_COMM] = ColorPair(Magenta, Black),
- [PROCESS_THREAD_COMM] = ColorPair(Blue, Black),
+ [PROCESS_THREAD_COMM] = A_BOLD | ColorPair(Blue, Black),
+ [PROCESS_PRIV] = ColorPair(Magenta, Black),
[BAR_BORDER] = A_BOLD,
[BAR_SHADOW] = A_BOLD | ColorPairGrayBlack,
[SWAP] = ColorPair(Red, Black),
[SWAP_CACHE] = ColorPair(Yellow, Black),
+ [SWAP_FRONTSWAP] = A_BOLD | ColorPairGrayBlack,
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Black),
[GRAPH_2] = ColorPair(Cyan, Black),
[MEMORY_USED] = ColorPair(Green, Black),
- [MEMORY_BUFFERS] = ColorPair(Blue, Black),
+ [MEMORY_BUFFERS] = A_BOLD | ColorPair(Blue, Black),
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Blue, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
[MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [MEMORY_COMPRESSED] = A_BOLD | ColorPairGrayBlack,
[HUGEPAGE_1] = ColorPair(Green, Black),
[HUGEPAGE_2] = ColorPair(Yellow, Black),
[HUGEPAGE_3] = ColorPair(Red, Black),
- [HUGEPAGE_4] = ColorPair(Blue, Black),
+ [HUGEPAGE_4] = A_BOLD | 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),
@@ -185,7 +190,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CHECK_MARK] = A_BOLD,
[CHECK_TEXT] = A_NORMAL,
[HOSTNAME] = A_BOLD,
- [CPU_NICE] = ColorPair(Blue, Black),
+ [CPU_NICE] = A_BOLD | ColorPair(Blue, Black),
[CPU_NICE_TEXT] = A_BOLD | ColorPair(Blue, Black),
[CPU_NORMAL] = ColorPair(Green, Black),
[CPU_SYSTEM] = ColorPair(Red, Black),
@@ -194,6 +199,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Magenta, Black),
[CPU_STEAL] = ColorPair(Cyan, Black),
[CPU_GUEST] = ColorPair(Cyan, Black),
+ [GPU_ENGINE_1] = ColorPair(Green, Black),
+ [GPU_ENGINE_2] = ColorPair(Yellow, Black),
+ [GPU_ENGINE_3] = ColorPair(Red, Black),
+ [GPU_ENGINE_4] = A_BOLD | ColorPair(Blue, Black),
+ [GPU_RESIDUE] = ColorPair(Magenta, Black),
[PANEL_EDIT] = ColorPair(White, Blue),
[SCREENS_OTH_BORDER] = ColorPair(Blue, Blue),
[SCREENS_OTH_TEXT] = ColorPair(Black, Blue),
@@ -202,19 +212,22 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Cyan, Black),
[PRESSURE_STALL_SIXTY] = A_BOLD | ColorPair(Cyan, Black),
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Black),
- [ZFS_MFU] = ColorPair(Blue, Black),
+ [FILE_DESCRIPTOR_USED] = ColorPair(Green, Black),
+ [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Blue, Black),
+ [ZFS_MFU] = A_BOLD | ColorPair(Blue, Black),
[ZFS_MRU] = ColorPair(Yellow, Black),
[ZFS_ANON] = ColorPair(Magenta, Black),
[ZFS_HEADER] = ColorPair(Cyan, Black),
[ZFS_OTHER] = ColorPair(Magenta, Black),
- [ZFS_COMPRESSED] = ColorPair(Blue, Black),
+ [ZFS_COMPRESSED] = A_BOLD | ColorPair(Blue, Black),
[ZFS_RATIO] = ColorPair(Magenta, Black),
- [ZRAM] = ColorPair(Yellow, Black),
+ [ZRAM_COMPRESSED] = A_BOLD | ColorPair(Blue, Black),
+ [ZRAM_UNCOMPRESSED] = 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_BLUE] = A_BOLD | ColorPair(Blue, Black),
[DYNAMIC_CYAN] = ColorPair(Cyan, Black),
[DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
[DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
@@ -264,10 +277,12 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_THREAD_BASENAME] = A_REVERSE,
[PROCESS_COMM] = A_BOLD,
[PROCESS_THREAD_COMM] = A_REVERSE,
+ [PROCESS_PRIV] = A_BOLD,
[BAR_BORDER] = A_BOLD,
[BAR_SHADOW] = A_DIM,
[SWAP] = A_BOLD,
[SWAP_CACHE] = A_NORMAL,
+ [SWAP_FRONTSWAP] = A_DIM,
[GRAPH_1] = A_BOLD,
[GRAPH_2] = A_NORMAL,
[MEMORY_USED] = A_BOLD,
@@ -275,6 +290,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[MEMORY_BUFFERS_TEXT] = A_NORMAL,
[MEMORY_CACHE] = A_NORMAL,
[MEMORY_SHARED] = A_NORMAL,
+ [MEMORY_COMPRESSED] = A_DIM,
[HUGEPAGE_1] = A_BOLD,
[HUGEPAGE_2] = A_NORMAL,
[HUGEPAGE_3] = A_REVERSE | A_BOLD,
@@ -301,6 +317,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = A_BOLD,
[CPU_STEAL] = A_DIM,
[CPU_GUEST] = A_DIM,
+ [GPU_ENGINE_1] = A_BOLD,
+ [GPU_ENGINE_2] = A_NORMAL,
+ [GPU_ENGINE_3] = A_REVERSE | A_BOLD,
+ [GPU_ENGINE_4] = A_REVERSE,
+ [GPU_RESIDUE] = A_BOLD,
[PANEL_EDIT] = A_BOLD,
[SCREENS_OTH_BORDER] = A_DIM,
[SCREENS_OTH_TEXT] = A_DIM,
@@ -309,6 +330,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PRESSURE_STALL_THREEHUNDRED] = A_DIM,
[PRESSURE_STALL_SIXTY] = A_NORMAL,
[PRESSURE_STALL_TEN] = A_BOLD,
+ [FILE_DESCRIPTOR_USED] = A_BOLD,
+ [FILE_DESCRIPTOR_MAX] = A_BOLD,
[ZFS_MFU] = A_NORMAL,
[ZFS_MRU] = A_NORMAL,
[ZFS_ANON] = A_DIM,
@@ -316,7 +339,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_OTHER] = A_DIM,
[ZFS_COMPRESSED] = A_BOLD,
[ZFS_RATIO] = A_BOLD,
- [ZRAM] = A_NORMAL,
+ [ZRAM_COMPRESSED] = A_NORMAL,
+ [ZRAM_UNCOMPRESSED] = A_NORMAL,
[DYNAMIC_GRAY] = A_DIM,
[DYNAMIC_DARKGRAY] = A_DIM,
[DYNAMIC_RED] = A_BOLD,
@@ -371,10 +395,12 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, White),
[PROCESS_COMM] = ColorPair(Magenta, White),
[PROCESS_THREAD_COMM] = ColorPair(Green, White),
+ [PROCESS_PRIV] = ColorPair(Magenta, White),
[BAR_BORDER] = ColorPair(Blue, White),
[BAR_SHADOW] = ColorPair(Black, White),
[SWAP] = ColorPair(Red, White),
[SWAP_CACHE] = ColorPair(Yellow, White),
+ [SWAP_FRONTSWAP] = A_BOLD | ColorPair(Black, White),
[GRAPH_1] = A_BOLD | ColorPair(Blue, White),
[GRAPH_2] = ColorPair(Blue, White),
[MEMORY_USED] = ColorPair(Green, White),
@@ -382,6 +408,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[MEMORY_BUFFERS_TEXT] = ColorPair(Cyan, White),
[MEMORY_CACHE] = ColorPair(Yellow, White),
[MEMORY_SHARED] = ColorPair(Magenta, White),
+ [MEMORY_COMPRESSED] = A_BOLD | ColorPair(Black, White),
[HUGEPAGE_1] = ColorPair(Green, White),
[HUGEPAGE_2] = ColorPair(Yellow, White),
[HUGEPAGE_3] = ColorPair(Red, White),
@@ -408,14 +435,21 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Blue, White),
[CPU_STEAL] = ColorPair(Cyan, White),
[CPU_GUEST] = ColorPair(Cyan, White),
- [PANEL_EDIT] = ColorPair(White,Blue),
- [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Black,White),
- [SCREENS_OTH_TEXT] = A_BOLD | ColorPair(Black,White),
- [SCREENS_CUR_BORDER] = ColorPair(Green,Green),
- [SCREENS_CUR_TEXT] = ColorPair(Black,Green),
+ [GPU_ENGINE_1] = ColorPair(Green, White),
+ [GPU_ENGINE_2] = ColorPair(Yellow, White),
+ [GPU_ENGINE_3] = ColorPair(Red, White),
+ [GPU_ENGINE_4] = ColorPair(Blue, White),
+ [GPU_RESIDUE] = ColorPair(Magenta, White),
+ [PANEL_EDIT] = ColorPair(White, Blue),
+ [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Black, White),
+ [SCREENS_OTH_TEXT] = A_BOLD | ColorPair(Black, White),
+ [SCREENS_CUR_BORDER] = ColorPair(Green, Green),
+ [SCREENS_CUR_TEXT] = ColorPair(Black, Green),
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, White),
[PRESSURE_STALL_SIXTY] = ColorPair(Black, White),
[PRESSURE_STALL_TEN] = ColorPair(Black, White),
+ [FILE_DESCRIPTOR_USED] = ColorPair(Green, White),
+ [FILE_DESCRIPTOR_MAX] = ColorPair(Blue, White),
[ZFS_MFU] = ColorPair(Cyan, White),
[ZFS_MRU] = ColorPair(Yellow, White),
[ZFS_ANON] = ColorPair(Magenta, White),
@@ -423,7 +457,8 @@ 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_COMPRESSED] = ColorPair(Cyan, White),
+ [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, White),
[DYNAMIC_GRAY] = ColorPair(Black, White),
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPair(Black, White),
[DYNAMIC_RED] = ColorPair(Red, White),
@@ -478,10 +513,12 @@ 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_PRIV] = ColorPair(Magenta, Black),
[BAR_BORDER] = ColorPair(Blue, Black),
[BAR_SHADOW] = ColorPairGrayBlack,
[SWAP] = ColorPair(Red, Black),
[SWAP_CACHE] = ColorPair(Yellow, Black),
+ [SWAP_FRONTSWAP] = ColorPairGrayBlack,
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Black),
[GRAPH_2] = ColorPair(Cyan, Black),
[MEMORY_USED] = ColorPair(Green, Black),
@@ -489,6 +526,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[MEMORY_BUFFERS_TEXT] = ColorPair(Cyan, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
[MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [MEMORY_COMPRESSED] = ColorPairGrayBlack,
[HUGEPAGE_1] = ColorPair(Green, Black),
[HUGEPAGE_2] = ColorPair(Yellow, Black),
[HUGEPAGE_3] = ColorPair(Red, Black),
@@ -515,14 +553,21 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Blue, Black),
[CPU_STEAL] = ColorPair(Black, Black),
[CPU_GUEST] = ColorPair(Black, Black),
- [PANEL_EDIT] = ColorPair(White,Blue),
- [SCREENS_OTH_BORDER] = ColorPair(Blue,Black),
- [SCREENS_OTH_TEXT] = ColorPair(Blue,Black),
- [SCREENS_CUR_BORDER] = ColorPair(Green,Green),
- [SCREENS_CUR_TEXT] = ColorPair(Black,Green),
+ [GPU_ENGINE_1] = ColorPair(Green, Black),
+ [GPU_ENGINE_2] = ColorPair(Yellow, Black),
+ [GPU_ENGINE_3] = ColorPair(Red, Black),
+ [GPU_ENGINE_4] = ColorPair(Blue, Black),
+ [GPU_RESIDUE] = ColorPair(Magenta, Black),
+ [PANEL_EDIT] = ColorPair(White, Blue),
+ [SCREENS_OTH_BORDER] = ColorPair(Blue, Black),
+ [SCREENS_OTH_TEXT] = ColorPair(Blue, Black),
+ [SCREENS_CUR_BORDER] = ColorPair(Green, Green),
+ [SCREENS_CUR_TEXT] = ColorPair(Black, Green),
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, Black),
[PRESSURE_STALL_SIXTY] = ColorPair(Black, Black),
[PRESSURE_STALL_TEN] = ColorPair(Black, Black),
+ [FILE_DESCRIPTOR_USED] = ColorPair(Green, Black),
+ [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Blue, Black),
[ZFS_MFU] = ColorPair(Cyan, Black),
[ZFS_MRU] = ColorPair(Yellow, Black),
[ZFS_ANON] = A_BOLD | ColorPair(Magenta, Black),
@@ -530,7 +575,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_OTHER] = A_BOLD | ColorPair(Magenta, Black),
[ZFS_COMPRESSED] = ColorPair(Cyan, Black),
[ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Black),
- [ZRAM] = ColorPair(Yellow, Black),
+ [ZRAM_COMPRESSED] = ColorPair(Cyan, Black),
+ [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, Black),
[DYNAMIC_GRAY] = ColorPairGrayBlack,
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
[DYNAMIC_RED] = ColorPair(Red, Black),
@@ -585,10 +631,12 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Green, Blue),
[PROCESS_COMM] = ColorPair(Magenta, Blue),
[PROCESS_THREAD_COMM] = ColorPair(Black, Blue),
+ [PROCESS_PRIV] = ColorPair(Magenta, Blue),
[BAR_BORDER] = A_BOLD | ColorPair(Yellow, Blue),
[BAR_SHADOW] = ColorPair(Cyan, Blue),
[SWAP] = ColorPair(Red, Blue),
[SWAP_CACHE] = A_BOLD | ColorPair(Yellow, Blue),
+ [SWAP_FRONTSWAP] = A_BOLD | ColorPair(Black, Blue),
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Blue),
[GRAPH_2] = ColorPair(Cyan, Blue),
[MEMORY_USED] = A_BOLD | ColorPair(Green, Blue),
@@ -596,6 +644,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Cyan, Blue),
[MEMORY_CACHE] = A_BOLD | ColorPair(Yellow, Blue),
[MEMORY_SHARED] = A_BOLD | ColorPair(Magenta, Blue),
+ [MEMORY_COMPRESSED] = A_BOLD | ColorPair(Black, Blue),
[HUGEPAGE_1] = A_BOLD | ColorPair(Green, Blue),
[HUGEPAGE_2] = A_BOLD | ColorPair(Yellow, Blue),
[HUGEPAGE_3] = A_BOLD | ColorPair(Red, Blue),
@@ -622,14 +671,21 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Black, Blue),
[CPU_STEAL] = ColorPair(White, Blue),
[CPU_GUEST] = ColorPair(White, Blue),
- [PANEL_EDIT] = ColorPair(White,Blue),
- [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Yellow,Blue),
- [SCREENS_OTH_TEXT] = ColorPair(Cyan,Blue),
- [SCREENS_CUR_BORDER] = ColorPair(Cyan,Cyan),
- [SCREENS_CUR_TEXT] = ColorPair(Black,Cyan),
+ [GPU_ENGINE_1] = A_BOLD | ColorPair(Green, Blue),
+ [GPU_ENGINE_2] = A_BOLD | ColorPair(Yellow, Blue),
+ [GPU_ENGINE_3] = A_BOLD | ColorPair(Red, Blue),
+ [GPU_ENGINE_4] = A_BOLD | ColorPair(White, Blue),
+ [GPU_RESIDUE] = A_BOLD | ColorPair(Magenta, Blue),
+ [PANEL_EDIT] = ColorPair(White, Blue),
+ [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Yellow, Blue),
+ [SCREENS_OTH_TEXT] = ColorPair(Cyan, Blue),
+ [SCREENS_CUR_BORDER] = ColorPair(Cyan, Cyan),
+ [SCREENS_CUR_TEXT] = ColorPair(Black, Cyan),
[PRESSURE_STALL_THREEHUNDRED] = A_BOLD | ColorPair(Black, Blue),
[PRESSURE_STALL_SIXTY] = A_NORMAL | ColorPair(White, Blue),
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Blue),
+ [FILE_DESCRIPTOR_USED] = A_BOLD | ColorPair(Green, Blue),
+ [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Red, Blue),
[ZFS_MFU] = A_BOLD | ColorPair(White, Blue),
[ZFS_MRU] = A_BOLD | ColorPair(Yellow, Blue),
[ZFS_ANON] = A_BOLD | ColorPair(Magenta, Blue),
@@ -637,7 +693,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_OTHER] = A_BOLD | ColorPair(Magenta, Blue),
[ZFS_COMPRESSED] = A_BOLD | ColorPair(White, Blue),
[ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Blue),
- [ZRAM] = A_BOLD | ColorPair(Yellow, Blue),
+ [ZRAM_COMPRESSED] = ColorPair(Cyan, Blue),
+ [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, Blue),
[DYNAMIC_GRAY] = ColorPairGrayBlack,
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
[DYNAMIC_RED] = ColorPair(Red, Blue),
@@ -692,10 +749,12 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),
[PROCESS_NEW] = ColorPair(Black, Green),
[PROCESS_TOMB] = ColorPair(Black, Red),
+ [PROCESS_PRIV] = ColorPair(Magenta, Black),
[BAR_BORDER] = A_BOLD | ColorPair(Green, Black),
[BAR_SHADOW] = ColorPair(Cyan, Black),
[SWAP] = ColorPair(Red, Black),
[SWAP_CACHE] = ColorPair(Yellow, Black),
+ [SWAP_FRONTSWAP] = ColorPair(Yellow, Black),
[GRAPH_1] = A_BOLD | ColorPair(Green, Black),
[GRAPH_2] = ColorPair(Green, Black),
[MEMORY_USED] = ColorPair(Green, Black),
@@ -703,6 +762,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Blue, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
[MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [MEMORY_COMPRESSED] = ColorPair(Yellow, Black),
[HUGEPAGE_1] = ColorPair(Green, Black),
[HUGEPAGE_2] = ColorPair(Yellow, Black),
[HUGEPAGE_3] = ColorPair(Red, Black),
@@ -727,14 +787,21 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Blue, Black),
[CPU_STEAL] = ColorPair(Cyan, Black),
[CPU_GUEST] = ColorPair(Cyan, Black),
- [PANEL_EDIT] = ColorPair(White,Cyan),
- [SCREENS_OTH_BORDER] = ColorPair(White,Black),
- [SCREENS_OTH_TEXT] = ColorPair(Cyan,Black),
- [SCREENS_CUR_BORDER] = A_BOLD | ColorPair(White,Black),
- [SCREENS_CUR_TEXT] = A_BOLD | ColorPair(Green,Black),
+ [GPU_ENGINE_1] = ColorPair(Green, Black),
+ [GPU_ENGINE_2] = ColorPair(Yellow, Black),
+ [GPU_ENGINE_3] = ColorPair(Red, Black),
+ [GPU_ENGINE_4] = ColorPair(Blue, Black),
+ [GPU_RESIDUE] = ColorPair(Magenta, Black),
+ [PANEL_EDIT] = ColorPair(White, Cyan),
+ [SCREENS_OTH_BORDER] = ColorPair(White, Black),
+ [SCREENS_OTH_TEXT] = ColorPair(Cyan, Black),
+ [SCREENS_CUR_BORDER] = A_BOLD | ColorPair(White, Black),
+ [SCREENS_CUR_TEXT] = A_BOLD | ColorPair(Green, Black),
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Green, Black),
[PRESSURE_STALL_SIXTY] = ColorPair(Green, Black),
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(Green, Black),
+ [FILE_DESCRIPTOR_USED] = ColorPair(Green, Black),
+ [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Blue, Black),
[ZFS_MFU] = ColorPair(Blue, Black),
[ZFS_MRU] = ColorPair(Yellow, Black),
[ZFS_ANON] = ColorPair(Magenta, Black),
@@ -742,7 +809,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_OTHER] = ColorPair(Magenta, Black),
[ZFS_COMPRESSED] = ColorPair(Blue, Black),
[ZFS_RATIO] = ColorPair(Magenta, Black),
- [ZRAM] = ColorPair(Yellow, Black),
+ [ZRAM_COMPRESSED] = ColorPair(Blue, Black),
+ [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, Black),
[DYNAMIC_GRAY] = ColorPairGrayBlack,
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
[DYNAMIC_RED] = ColorPair(Red, Black),
@@ -756,6 +824,8 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[COLORSCHEME_BROKENGRAY] = { 0 } // dynamically generated.
};
+static bool CRT_retainScreenOnExit = false;
+
int CRT_scrollHAmount = 5;
int CRT_scrollWheelVAmount = 10;
@@ -763,8 +833,21 @@ int CRT_scrollWheelVAmount = 10;
ColorScheme CRT_colorScheme = COLORSCHEME_DEFAULT;
ATTR_NORETURN
-static void CRT_handleSIGTERM(ATTR_UNUSED int sgn) {
+static void CRT_handleSIGTERM(int sgn) {
CRT_done();
+
+ if (!CRT_crashSettings->changed)
+ _exit(0);
+
+ const char* signal_str = strsignal(sgn);
+ if (!signal_str)
+ signal_str = "unknown reason";
+
+ char err_buf[512];
+ snprintf(err_buf, sizeof(err_buf),
+ "A signal %d (%s) was received, exiting without persisting settings to htoprc.\n",
+ sgn, signal_str);
+ full_write_str(STDERR_FILENO, err_buf);
_exit(0);
}
@@ -831,15 +914,15 @@ static void dumpStderr(void) {
if (res > 0) {
if (!header) {
- fprintf(stderr, ">>>>>>>>>> stderr output >>>>>>>>>>\n");
+ full_write_str(STDERR_FILENO, ">>>>>>>>>> stderr output >>>>>>>>>>\n");
header = true;
}
- (void)! write(STDERR_FILENO, buffer, res);
+ full_write(STDERR_FILENO, buffer, res);
}
}
if (header)
- fprintf(stderr, "\n<<<<<<<<<< stderr output <<<<<<<<<<\n");
+ full_write_str(STDERR_FILENO, "\n<<<<<<<<<< stderr output <<<<<<<<<<\n");
close(stderrRedirectNewFd);
stderrRedirectNewFd = -1;
@@ -869,39 +952,43 @@ static struct sigaction old_sig_handler[32];
static void CRT_installSignalHandlers(void) {
struct sigaction act;
- sigemptyset (&act.sa_mask);
+ 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]);
+ 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(SIGCHLD, SIG_DFL);
signal(SIGINT, CRT_handleSIGTERM);
signal(SIGTERM, CRT_handleSIGTERM);
signal(SIGQUIT, CRT_handleSIGTERM);
+ signal(SIGUSR1, SIG_IGN);
+ signal(SIGUSR2, SIG_IGN);
}
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);
+ 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);
+ signal(SIGUSR1, SIG_DFL);
+ signal(SIGUSR2, SIG_DFL);
}
-void CRT_setMouse(bool enabled) {
#ifdef HAVE_GETMOUSE
+void CRT_setMouse(bool enabled) {
if (enabled) {
#if NCURSES_MOUSE_VERSION > 1
mousemask(BUTTON1_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);
@@ -911,13 +998,23 @@ void CRT_setMouse(bool enabled) {
} else {
mousemask(0, NULL);
}
-#endif
}
+#endif
-void CRT_init(const Settings* settings, bool allowUnicode) {
- redirectStderr();
-
+void CRT_init(const Settings* settings, bool allowUnicode, bool retainScreenOnExit) {
initscr();
+
+ if (retainScreenOnExit) {
+ CRT_retainScreenOnExit = true;
+ refresh();
+ tputs(exit_ca_mode, 0, putchar);
+ tputs(clear_screen, 0, putchar);
+ fflush(stdout);
+ enter_ca_mode = 0;
+ exit_ca_mode = 0;
+ }
+
+ redirectStderr();
noecho();
CRT_crashSettings = settings;
CRT_delay = &(settings->delay);
@@ -975,6 +1072,8 @@ IGNORE_WCASTQUAL_BEGIN
sequence[1] = c;
define_key(sequence, KEY_ALT('A' + (c - 'a')));
}
+ define_key("\033[I", KEY_FOCUS_IN);
+ define_key("\033[O", KEY_FOCUS_OUT);
#ifdef HTOP_NETBSD
IGNORE_WCASTQUAL_END
#undef define_key
@@ -1012,7 +1111,7 @@ IGNORE_WCASTQUAL_END
CRT_degreeSign = initDegreeSign();
}
-void CRT_done() {
+void CRT_done(void) {
int resetColor = CRT_colors ? CRT_colors[RESET_COLOR] : CRT_colorSchemes[COLORSCHEME_DEFAULT][RESET_COLOR];
attron(resetColor);
@@ -1020,6 +1119,10 @@ void CRT_done() {
attroff(resetColor);
refresh();
+ if (CRT_retainScreenOnExit) {
+ mvcur(-1, -1, LINES - 1, 0);
+ }
+
curs_set(1);
endwin();
@@ -1033,7 +1136,7 @@ void CRT_fatalError(const char* note) {
exit(2);
}
-int CRT_readKey() {
+int CRT_readKey(void) {
nocbreak();
cbreak();
nodelay(stdscr, FALSE);
@@ -1042,13 +1145,13 @@ int CRT_readKey() {
return ret;
}
-void CRT_disableDelay() {
+void CRT_disableDelay(void) {
nocbreak();
cbreak();
nodelay(stdscr, TRUE);
}
-void CRT_enableDelay() {
+void CRT_enableDelay(void) {
halfdelay(*CRT_delay);
}
@@ -1058,9 +1161,7 @@ void CRT_setColors(int colorScheme) {
for (short int i = 0; i < 8; i++) {
for (short int j = 0; j < 8; j++) {
if (ColorIndex(i, j) != ColorIndexGrayBlack && ColorIndex(i, j) != ColorIndexWhiteDefault) {
- short int bg = (colorScheme != COLORSCHEME_BLACKNIGHT)
- ? (j == 0 ? -1 : j)
- : j;
+ short int bg = (colorScheme != COLORSCHEME_BLACKNIGHT) && (j == 0) ? -1 : j;
init_pair(ColorIndex(i, j), i, bg);
}
}
@@ -1086,6 +1187,8 @@ static void print_backtrace(void) {
unsigned int item = 0;
+ char err_buf[1024];
+
while (unw_step(&cursor) > 0) {
unw_word_t pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
@@ -1097,7 +1200,7 @@ static void print_backtrace(void) {
unw_get_proc_name(&cursor, symbolName, sizeof(symbolName), &offset);
unw_proc_info_t pip;
- pip.unwind_info = NULL;
+ pip.unwind_info = 0;
const char* fname = "?";
const void* ptr = 0;
@@ -1111,11 +1214,11 @@ static void print_backtrace(void) {
#endif
}
- const char* frame = "";
- if (unw_is_signal_frame(&cursor) > 0)
- frame = "{signal frame}";
+ const bool is_signal_frame = unw_is_signal_frame(&cursor) > 0;
+ const char* frame = is_signal_frame ? " {signal frame}" : "";
- fprintf(stderr, "%2u: %#14lx %s (%s+%#lx) [%p]%s%s\n", item++, pc, fname, symbolName, offset, ptr, frame ? " " : "", frame);
+ snprintf(err_buf, sizeof(err_buf), "%2u: %#14lx %s (%s+%#lx) [%p]%s\n", item++, pc, fname, symbolName, offset, ptr, frame);
+ full_write_str(STDERR_FILENO, err_buf);
}
#elif defined(HAVE_EXECINFO_H)
void* backtraceArray[256];
@@ -1131,22 +1234,26 @@ static void print_backtrace(void) {
void CRT_handleSIGSEGV(int signal) {
CRT_done();
- fprintf(stderr, "\n\n"
+ char err_buf[512];
+
+ snprintf(err_buf, sizeof(err_buf), "\n\n"
"FATAL PROGRAM ERROR DETECTED\n"
"============================\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"
- " - Your "PACKAGE" version: '"VERSION"'\n"
+ " - Your %s version: '"VERSION"'\n"
" - Your OS and kernel version (uname -a)\n"
" - Your distribution and release (lsb_release -a)\n"
- " - Likely steps to reproduce (How did it happen?)\n"
+ " - Likely steps to reproduce (How did it happen?)\n",
+ program
);
+ full_write_str(STDERR_FILENO, err_buf);
#ifdef PRINT_BACKTRACE
- fprintf(stderr, " - Backtrace of the issue (see below)\n");
+ full_write_str(STDERR_FILENO, " - Backtrace of the issue (see below)\n");
#endif
- fprintf(stderr,
+ full_write_str(STDERR_FILENO,
"\n"
);
@@ -1154,59 +1261,65 @@ void CRT_handleSIGSEGV(int signal) {
if (!signal_str) {
signal_str = "unknown reason";
}
- fprintf(stderr,
+ snprintf(err_buf, sizeof(err_buf),
"Error information:\n"
"------------------\n"
"A signal %d (%s) was received.\n"
"\n",
signal, signal_str
);
+ full_write_str(STDERR_FILENO, err_buf);
- fprintf(stderr,
+ full_write_str(STDERR_FILENO,
"Setting information:\n"
"--------------------\n");
Settings_write(CRT_crashSettings, true);
- fprintf(stderr, "\n\n");
+ full_write_str(STDERR_FILENO, "\n\n");
#ifdef PRINT_BACKTRACE
- fprintf(stderr,
+ full_write_str(STDERR_FILENO,
"Backtrace information:\n"
"----------------------\n"
);
print_backtrace();
- fprintf(stderr,
+ snprintf(err_buf, sizeof(err_buf),
"\n"
"To make the above information more practical to work with, "
- "please also provide a disassembly of your "PACKAGE" binary. "
+ "please also provide a disassembly of your %s binary. "
"This can usually be done by running the following command:\n"
- "\n"
+ "\n",
+ program
);
+ full_write_str(STDERR_FILENO, err_buf);
#ifdef HTOP_DARWIN
- fprintf(stderr, " otool -tvV `which "PACKAGE"` > ~/htop.otool\n");
+ snprintf(err_buf, sizeof(err_buf), " otool -tvV `which %s` > ~/%s.otool\n", program, program);
#else
- fprintf(stderr, " objdump -d -S -w `which "PACKAGE"` > ~/htop.objdump\n");
+ snprintf(err_buf, sizeof(err_buf), " objdump -d -S -w `which %s` > ~/%s.objdump\n", program, program);
#endif
+ full_write_str(STDERR_FILENO, err_buf);
- fprintf(stderr,
+ full_write_str(STDERR_FILENO,
"\n"
"Please include the generated file in your report.\n"
);
#endif
- fprintf(stderr,
+ snprintf(err_buf, sizeof(err_buf),
"Running this program with debug symbols or inside a debugger may provide further insights.\n"
"\n"
- "Thank you for helping to improve "PACKAGE"!\n"
- "\n"
+ "Thank you for helping to improve %s!\n"
+ "\n",
+ program
);
+ full_write_str(STDERR_FILENO, err_buf);
/* Call old sigsegv handler; may be default exit or third party one (e.g. ASAN) */
- if (sigaction (signal, &old_sig_handler[signal], NULL) < 0) {
+ if (sigaction(signal, &old_sig_handler[signal], NULL) < 0) {
/* This avoids an infinite loop in case the handler could not be reset. */
- fprintf(stderr,
+ full_write_str(STDERR_FILENO,
"!!! Chained handler could not be restored. Forcing exit.\n"
);
_exit(1);
@@ -1216,7 +1329,7 @@ void CRT_handleSIGSEGV(int signal) {
raise(signal);
// Always terminate, even if installed handler returns
- fprintf(stderr,
+ full_write_str(STDERR_FILENO,
"!!! Chained handler did not exit. Forcing exit.\n"
);
_exit(1);
diff --git a/CRT.h b/CRT.h
index 297e8964..eb3df1d7 100644
--- a/CRT.h
+++ b/CRT.h
@@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h"
-
#include <stdbool.h>
#include "Macros.h"
@@ -68,6 +66,7 @@ typedef enum ColorElements_ {
TASKS_RUNNING,
SWAP,
SWAP_CACHE,
+ SWAP_FRONTSWAP,
PROCESS,
PROCESS_SHADOW,
PROCESS_TAG,
@@ -85,6 +84,7 @@ typedef enum ColorElements_ {
PROCESS_THREAD_BASENAME,
PROCESS_COMM,
PROCESS_THREAD_COMM,
+ PROCESS_PRIV,
BAR_BORDER,
BAR_SHADOW,
GRAPH_1,
@@ -94,6 +94,7 @@ typedef enum ColorElements_ {
MEMORY_BUFFERS_TEXT,
MEMORY_CACHE,
MEMORY_SHARED,
+ MEMORY_COMPRESSED,
HUGEPAGE_1,
HUGEPAGE_2,
HUGEPAGE_3,
@@ -120,6 +121,11 @@ typedef enum ColorElements_ {
CPU_SOFTIRQ,
CPU_STEAL,
CPU_GUEST,
+ GPU_ENGINE_1,
+ GPU_ENGINE_2,
+ GPU_ENGINE_3,
+ GPU_ENGINE_4,
+ GPU_RESIDUE,
PANEL_EDIT,
SCREENS_OTH_BORDER,
SCREENS_OTH_TEXT,
@@ -128,6 +134,8 @@ typedef enum ColorElements_ {
PRESSURE_STALL_TEN,
PRESSURE_STALL_SIXTY,
PRESSURE_STALL_THREEHUNDRED,
+ FILE_DESCRIPTOR_USED,
+ FILE_DESCRIPTOR_MAX,
ZFS_MFU,
ZFS_MRU,
ZFS_ANON,
@@ -135,7 +143,8 @@ typedef enum ColorElements_ {
ZFS_OTHER,
ZFS_COMPRESSED,
ZFS_RATIO,
- ZRAM,
+ ZRAM_COMPRESSED,
+ ZRAM_UNCOMPRESSED,
DYNAMIC_GRAY,
DYNAMIC_DARKGRAY,
DYNAMIC_RED,
@@ -164,6 +173,8 @@ void CRT_handleSIGSEGV(int signal) ATTR_NORETURN;
#define KEY_RECLICK KEY_F(32)
#define KEY_SHIFT_TAB KEY_F(33)
#define KEY_ALT(x) (KEY_F(64 - 26) + ((x) - 'A'))
+#define KEY_FOCUS_IN (KEY_MAX + 'I')
+#define KEY_FOCUS_OUT (KEY_MAX + 'O')
extern const char* CRT_degreeSign;
@@ -185,9 +196,13 @@ extern int CRT_scrollWheelVAmount;
extern ColorScheme CRT_colorScheme;
+#ifdef HAVE_GETMOUSE
void CRT_setMouse(bool enabled);
+#else
+#define CRT_setMouse(enabled)
+#endif
-void CRT_init(const Settings* settings, bool allowUnicode);
+void CRT_init(const Settings* settings, bool allowUnicode, bool retainScreenOnExit);
void CRT_done(void);
diff --git a/CategoriesPanel.c b/CategoriesPanel.c
index 6e905ce9..64a3f062 100644
--- a/CategoriesPanel.c
+++ b/CategoriesPanel.c
@@ -5,13 +5,14 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "CategoriesPanel.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
-#include "AvailableColumnsPanel.h"
#include "AvailableMetersPanel.h"
#include "ColorsPanel.h"
#include "DisplayOptionsPanel.h"
@@ -25,6 +26,8 @@ in the source distribution for its full text.
#include "Object.h"
#include "ProvideCurses.h"
#include "ScreensPanel.h"
+#include "ScreenTabsPanel.h"
+#include "Settings.h"
#include "Vector.h"
#include "XUtils.h"
@@ -41,11 +44,12 @@ static void CategoriesPanel_delete(Object* object) {
static void CategoriesPanel_makeMetersPage(CategoriesPanel* this) {
size_t columns = HeaderLayout_getColumns(this->scr->header->headerLayout);
MetersPanel** meterPanels = xMallocArray(columns, sizeof(MetersPanel*));
+ Settings* settings = this->host->settings;
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);
+ meterPanels[i] = MetersPanel_new(settings, titleBuffer, this->header->columns[i], this->scr);
if (i != 0) {
meterPanels[i]->leftNeighbor = meterPanels[i - 1];
@@ -55,31 +59,45 @@ static void CategoriesPanel_makeMetersPage(CategoriesPanel* this) {
ScreenManager_add(this->scr, (Panel*) meterPanels[i], 20);
}
- Panel* availableMeters = (Panel*) AvailableMetersPanel_new(this->settings, this->header, columns, meterPanels, this->scr, this->pl);
+ Panel* availableMeters = (Panel*) AvailableMetersPanel_new(this->host, this->header, columns, meterPanels, this->scr);
ScreenManager_add(this->scr, availableMeters, -1);
}
static void CategoriesPanel_makeDisplayOptionsPage(CategoriesPanel* this) {
- Panel* displayOptions = (Panel*) DisplayOptionsPanel_new(this->settings, this->scr);
+ Settings* settings = this->host->settings;
+ Panel* displayOptions = (Panel*) DisplayOptionsPanel_new(settings, this->scr);
ScreenManager_add(this->scr, displayOptions, -1);
}
static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) {
- Panel* colors = (Panel*) ColorsPanel_new(this->settings);
+ Settings* settings = this->host->settings;
+ Panel* colors = (Panel*) ColorsPanel_new(settings);
ScreenManager_add(this->scr, colors, -1);
}
+#if defined(HTOP_PCP) /* all platforms supporting dynamic screens */
+static void CategoriesPanel_makeScreenTabsPage(CategoriesPanel* this) {
+ Settings* settings = this->host->settings;
+ Panel* screenTabs = (Panel*) ScreenTabsPanel_new(settings);
+ Panel* screenNames = (Panel*) ((ScreenTabsPanel*)screenTabs)->names;
+ ScreenManager_add(this->scr, screenTabs, 20);
+ ScreenManager_add(this->scr, screenNames, -1);
+}
+#endif
+
static void CategoriesPanel_makeScreensPage(CategoriesPanel* this) {
- Panel* screens = (Panel*) ScreensPanel_new(this->settings);
+ Settings* settings = this->host->settings;
+ Panel* screens = (Panel*) ScreensPanel_new(settings);
Panel* columns = (Panel*) ((ScreensPanel*)screens)->columns;
- Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, this->settings->dynamicColumns);
+ Panel* availableColumns = (Panel*) ((ScreensPanel*)screens)->availableColumns;
ScreenManager_add(this->scr, screens, 20);
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);
+ Settings* settings = this->host->settings;
+ Panel* colors = (Panel*) HeaderOptionsPanel_new(settings, this->scr);
ScreenManager_add(this->scr, colors, -1);
}
@@ -89,10 +107,13 @@ typedef struct CategoriesPanelPage_ {
CategoriesPanel_makePageFunc ctor;
} CategoriesPanelPage;
-static const CategoriesPanelPage categoriesPanelPages[] = {
+static CategoriesPanelPage categoriesPanelPages[] = {
{ .name = "Display options", .ctor = CategoriesPanel_makeDisplayOptionsPage },
{ .name = "Header layout", .ctor = CategoriesPanel_makeHeaderOptionsPage },
{ .name = "Meters", .ctor = CategoriesPanel_makeMetersPage },
+#if defined(HTOP_PCP) /* all platforms supporting dynamic screens */
+ { .name = "Screen tabs", .ctor = CategoriesPanel_makeScreenTabsPage },
+#endif
{ .name = "Screens", .ctor = CategoriesPanel_makeScreensPage },
{ .name = "Colors", .ctor = CategoriesPanel_makeColorsPage },
};
@@ -149,16 +170,15 @@ const PanelClass CategoriesPanel_class = {
.eventHandler = CategoriesPanel_eventHandler
};
-CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Settings* settings, Header* header, ProcessList* pl) {
+CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Header* header, Machine* host) {
CategoriesPanel* this = AllocThis(CategoriesPanel);
Panel* super = (Panel*) this;
FunctionBar* fuBar = FunctionBar_new(CategoriesFunctions, NULL, NULL);
Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
this->scr = scr;
- this->settings = settings;
+ this->host = host;
this->header = header;
- this->pl = pl;
Panel_setHeader(super, "Categories");
for (size_t i = 0; i < ARRAYSIZE(categoriesPanelPages); i++)
Panel_add(super, (Object*) ListItem_new(categoriesPanelPages[i].name, 0));
diff --git a/CategoriesPanel.h b/CategoriesPanel.h
index 825cd069..1f50b8a0 100644
--- a/CategoriesPanel.h
+++ b/CategoriesPanel.h
@@ -8,23 +8,20 @@ in the source distribution for its full text.
*/
#include "Header.h"
+#include "Machine.h"
#include "Panel.h"
-#include "ProcessList.h"
#include "ScreenManager.h"
-#include "Settings.h"
typedef struct CategoriesPanel_ {
Panel super;
ScreenManager* scr;
-
- Settings* settings;
+ Machine* host;
Header* header;
- ProcessList* pl;
} CategoriesPanel;
extern const PanelClass CategoriesPanel_class;
-CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Settings* settings, Header* header, ProcessList* pl);
+CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Header* header, Machine* host);
#endif
diff --git a/ChangeLog b/ChangeLog
index 8f5266cb..b4cdf6a2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,96 @@
+What's new in version 3.3.0
+
+* Multiple refactorings and code improvements
+* Shorten docker container IDs to 12 characters
+* Settings: preserve empty header
+* Fix execlp() argument without pointer cast
+* OpenFilesScreen: Make column sizing dynamic for file size, offset and inode
+* Add support for "truss" (FreeBSD equivalent of "strace")
+* Darwin: add NetworkIOMeter support
+* HeaderLayout: add "3 columns - 40/30/30", "... 30/40/30" & "... 30/30/40"
+* Meter: use correct unicode characters for digit '9'
+* Note in manual re default memory units of KiB
+* Add column for process container name
+* Add logic to filter the container name (+type) from the CGroup name
+* Change NetworkIOMeter value unit from KiB/s to bytes/second
+* Cap DiskIOMeter "utilisation" percentage at 100%
+* PCP platform implementation of frontswap and zswap accounting
+* Shorten podman/libpod container IDs to 12 characters
+* Write configuration to temporary file first
+* Incorporate shared memory in bar text
+* Move shared memory next to used memory
+* Correct order of memory meter in help
+* Add recalculate to Ctrl-L refresh
+* Update process list on thread visibility toggling
+* Support dynamic screens with 'top-most' entities beyond processes
+* Introduce Row and Table classes for screens beyond top-processes
+* Rework ZramMeter and remove MeterClass.comprisedValues
+* More robust logic for CPU process percentages (Linux & PCP)
+* Show year as start time for processes older than a year
+* Short-term fix for docker container detection
+* default color preset: use bold blue for better visibility
+* Document 'O' keyboard shortcut
+* Implement logic for '--max-iterations'
+* Update F5 key label on tab switch (Tree <-> List)
+* Force re-sorting of the process list view after switching between list/treeview mode
+* Linux: (hack) work around the fact that Zswapped pages may be SwapCached
+* Linux: implement zswap support
+* {Memory,Swap}Meter: add "compressed memory" metrics
+* Darwin: add DiskIOMeter support
+* Fix scroll relative to followed process
+* ZramMeter: update bar mode
+* Use shared real memory on FreeBSD
+* Increase Search and Filter max string length to 128
+* Improve CPU computation code
+* Remove LXC special handling for the CPU count
+* Create new File Descriptor meter
+* PCP: add IRQ PSI meter
+* Linux: add IRQ PSI meter
+* Linux: highlight username if process has elevated privileges
+* Add support for scheduling policies
+* Add a systemd user meter to monitor user units.
+* FreeBSD: remove duplicate zfs ARC size subtraction
+
+What's new in version 3.2.2
+
+* CPUMeter now can show frequency in text mode
+* Add option to render distribution path prefixes shadowed
+* DiskIOMeter converts to bytes per second (not per interval)
+* DiskIOMeter uses complete units, including missing "iB/s"
+* DiskIOMeter indicates read and write in meter mode
+* NetworkIOMeter converts to packets per second, shows packet rate
+* Allow continued process following when changing display settings
+* Update the panel header when changing to another tab
+* Drop margin around the header if there are no meters
+* Use Unicode replacement character for non-printable characters
+* Default color preset uses bold blue for better visibility
+* Update the Panel header on sort order inversions ('I')
+* Toggle the header meters with pound key
+* Fix ScreenPanel to handle quitting the panel while renaming
+* Add fallback for HOME environment variable using passwd database
+* Replace meaningless ID column with FD column in lock screen
+* Use device format in the lock screen matching the files screen
+* On Linux, improvements to file-descriptor lock detection
+* On Linux, further distinguish systemd states in the SystemdMeter
+* On Linux, improvements to cgroup and container identification
+* On Linux, support openat(2) without readlinkat(2) platforms
+* On Darwin, fix current process buffer handling for busy systems
+* On DragonFly BSD, fix incorrect processor time of processes
+* On FreeBSD, fix an issue with the memory graph not showing correctly
+* On FreeBSD, add support for displaying shared memory usage
+* On PCP, use pmLookupDescs(3) if available for efficiency
+* On PCP, normalize generic columns values for consistent display
+* On PCP, changes preparing for configurable, dynamic screens
+* Handle invalid process columns from the configuration file
+* Avoid undefined behaviour with deeply nested processes
+* Fix crash when removing the currently active screen
+* Prevent possible crash on a very early error path
+* Include automake for Debian/Ubuntu
+* Restore non-mouse support
+* Reject unsupported command line arguments
+* Document idle process state
+* Clarify M_TRS/M_DRS columns
+
What's new in version 3.2.1
* Fix setting to show all branches collapsed by default
@@ -13,7 +106,7 @@ What's new in version 3.2.1
* On Solaris, fix the build
* On NetBSD, OpenBSD and Solaris ensure env buffer size is sufficient
* On Linux, resolve processes exiting interfering with sampling
-* Fix ProcessList quadratic removal when scanning processes
+* Fix ProcessTable quadratic removal when scanning processes
* Under LXC, limit CPU count to that given by /proc/cpuinfo
* Improve container detection for LXC
* Some minor documentation fixes
diff --git a/ClockMeter.c b/ClockMeter.c
index 8e3b66e6..38f0591f 100644
--- a/ClockMeter.c
+++ b/ClockMeter.c
@@ -13,8 +13,8 @@ in the source distribution for its full text.
#include <sys/time.h>
#include "CRT.h"
+#include "Machine.h"
#include "Object.h"
-#include "ProcessList.h"
static const int ClockMeter_attributes[] = {
@@ -22,10 +22,10 @@ static const int ClockMeter_attributes[] = {
};
static void ClockMeter_updateValues(Meter* this) {
- const ProcessList* pl = this->pl;
+ const Machine* host = this->host;
struct tm result;
- const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
+ const struct tm* lt = localtime_r(&host->realtime.tv_sec, &result);
this->values[0] = lt->tm_hour * 60 + lt->tm_min;
strftime(this->txtBuffer, sizeof(this->txtBuffer), "%H:%M:%S", lt);
}
diff --git a/ColorsPanel.c b/ColorsPanel.c
index 59008842..581c3a00 100644
--- a/ColorsPanel.c
+++ b/ColorsPanel.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 "ColorsPanel.h"
#include <assert.h>
@@ -13,7 +15,6 @@ in the source distribution for its full text.
#include "CRT.h"
#include "FunctionBar.h"
-#include "Macros.h"
#include "Object.h"
#include "OptionItem.h"
#include "ProvideCurses.h"
@@ -50,30 +51,31 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) {
ColorsPanel* this = (ColorsPanel*) 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_COLORSCHEME);
- for (int i = 0; ColorSchemeNames[i] != NULL; i++)
- CheckItem_set((CheckItem*)Panel_get(super, i), false);
- CheckItem_set((CheckItem*)Panel_get(super, mark), true);
-
- this->settings->colorScheme = mark;
- this->settings->changed = true;
- this->settings->lastUpdate++;
-
- CRT_setColors(mark);
- clear();
-
- result = HANDLED | REDRAW;
+ case 0x0a:
+ case 0x0d:
+ case KEY_ENTER:
+ case KEY_MOUSE:
+ case KEY_RECLICK:
+ case ' ': {
+ int mark = Panel_getSelectedIndex(super);
+ assert(mark >= 0);
+ assert(mark < LAST_COLORSCHEME);
+
+ for (int i = 0; ColorSchemeNames[i] != NULL; i++)
+ CheckItem_set((CheckItem*)Panel_get(super, i), false);
+ CheckItem_set((CheckItem*)Panel_get(super, mark), true);
+
+ this->settings->colorScheme = mark;
+ this->settings->changed = true;
+ this->settings->lastUpdate++;
+
+ CRT_setColors(mark);
+ clear();
+
+ result = HANDLED | REDRAW;
+ }
}
return result;
diff --git a/ColumnsPanel.c b/ColumnsPanel.c
index 24826936..66625666 100644
--- a/ColumnsPanel.c
+++ b/ColumnsPanel.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 "ColumnsPanel.h"
#include <assert.h>
@@ -19,6 +21,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "Process.h"
#include "ProvideCurses.h"
+#include "RowField.h"
#include "XUtils.h"
@@ -38,13 +41,12 @@ static HandlerResult ColumnsPanel_eventHandler(Panel* super, int ch) {
HandlerResult result = IGNORED;
int size = Panel_size(super);
- switch(ch) {
+ switch (ch) {
case 0x0a:
case 0x0d:
case KEY_ENTER:
case KEY_MOUSE:
case KEY_RECLICK:
- {
if (selected < size - 1) {
this->moving = !(this->moving);
Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS);
@@ -54,59 +56,45 @@ static HandlerResult ColumnsPanel_eventHandler(Panel* super, int ch) {
result = HANDLED;
}
break;
- }
case KEY_UP:
- {
- if (!this->moving) {
+ if (!this->moving)
break;
- }
- }
/* else fallthrough */
case KEY_F(7):
case '[':
case '-':
- {
if (selected < size - 1)
Panel_moveSelectedUp(super);
result = HANDLED;
break;
- }
case KEY_DOWN:
- {
- if (!this->moving) {
+ if (!this->moving)
break;
- }
- }
/* else fallthrough */
case KEY_F(8):
case ']':
case '+':
- {
if (selected < size - 2)
Panel_moveSelectedDown(super);
result = HANDLED;
break;
- }
case KEY_F(9):
case KEY_DC:
- {
- if (selected < size - 1) {
+ if (selected < size - 1)
Panel_remove(super, selected);
- }
result = HANDLED;
break;
- }
default:
- {
if (0 < ch && ch < 255 && isgraph((unsigned char)ch))
result = Panel_selectByTyping(super, ch);
if (result == BREAK_LOOP)
result = IGNORED;
break;
- }
}
+
if (result == HANDLED)
ColumnsPanel_update(super);
+
return result;
}
@@ -128,9 +116,8 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns)
if (!column) {
name = NULL;
} else {
- name = column->caption ? column->caption : column->heading;
- if (!name)
- name = column->name; /* name is a mandatory field */
+ /* heading preferred here but name is always available */
+ name = column->heading ? column->heading : column->name;
}
}
if (name == NULL)
@@ -141,7 +128,7 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns)
void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns) {
Panel* super = (Panel*) this;
Panel_prune(super);
- for (const ProcessField* fields = ss->fields; *fields; fields++)
+ for (const RowField* fields = ss->fields; *fields; fields++)
ColumnsPanel_add(super, *fields, columns);
this->ss = ss;
}
diff --git a/ColumnsPanel.h b/ColumnsPanel.h
index 63f6f92b..723369fd 100644
--- a/ColumnsPanel.h
+++ b/ColumnsPanel.h
@@ -9,6 +9,7 @@ in the source distribution for its full text.
#include <stdbool.h>
+#include "Hashtable.h"
#include "Panel.h"
#include "Settings.h"
diff --git a/CommandLine.c b/CommandLine.c
index d21f4821..69b80c1e 100644
--- a/CommandLine.c
+++ b/CommandLine.c
@@ -25,18 +25,20 @@ in the source distribution for its full text.
#include "CRT.h"
#include "DynamicColumn.h"
#include "DynamicMeter.h"
+#include "DynamicScreen.h"
#include "Hashtable.h"
#include "Header.h"
#include "IncSet.h"
+#include "Machine.h"
#include "MainPanel.h"
#include "MetersPanel.h"
#include "Panel.h"
#include "Platform.h"
#include "Process.h"
-#include "ProcessList.h"
-#include "ProvideCurses.h"
+#include "ProcessTable.h"
#include "ScreenManager.h"
#include "Settings.h"
+#include "Table.h"
#include "UsersTable.h"
#include "XUtils.h"
@@ -57,7 +59,8 @@ static void printHelpFlag(const char* name) {
#ifdef HAVE_GETMOUSE
printf("-M --no-mouse Disable the mouse\n");
#endif
- printf("-p --pid=PID[,PID,PID...] Show only the given PIDs\n"
+ printf("-n --max-iterations=NUMBER Exit htop after NUMBER iterations/frame updates\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"
@@ -66,7 +69,6 @@ static void printHelpFlag(const char* name) {
"-V --version Print version info\n");
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);
}
@@ -79,8 +81,11 @@ typedef struct CommandLineSettings_ {
uid_t userId;
int sortKey;
int delay;
+ int iterationsRemaining;
bool useColors;
+#ifdef HAVE_GETMOUSE
bool enableMouse;
+#endif
bool treeView;
bool allowUnicode;
bool highlightChanges;
@@ -88,7 +93,7 @@ typedef struct CommandLineSettings_ {
bool readonly;
} CommandLineSettings;
-static CommandLineStatus parseArguments(const char* program, int argc, char** argv, CommandLineSettings* flags) {
+static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettings* flags) {
*flags = (CommandLineSettings) {
.pidMatchList = NULL,
@@ -96,8 +101,11 @@ static CommandLineStatus parseArguments(const char* program, int argc, char** ar
.userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2))
.sortKey = 0,
.delay = -1,
+ .iterationsRemaining = -1,
.useColors = true,
+#ifdef HAVE_GETMOUSE
.enableMouse = true,
+#endif
.treeView = false,
.allowUnicode = true,
.highlightChanges = false,
@@ -110,6 +118,7 @@ static CommandLineStatus parseArguments(const char* program, int argc, char** ar
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{"delay", required_argument, 0, 'd'},
+ {"max-iterations", required_argument, 0, 'n'},
{"sort-key", required_argument, 0, 's'},
{"user", optional_argument, 0, 'u'},
{"no-color", no_argument, 0, 'C'},
@@ -127,9 +136,10 @@ static CommandLineStatus parseArguments(const char* program, int argc, char** ar
int opt, opti = 0;
/* Parse arguments */
- while ((opt = getopt_long(argc, argv, "hVMCs:td:u::Up:F:H::", long_opts, &opti))) {
+ while ((opt = getopt_long(argc, argv, "hVMCs:td:n:u::Up:F:H::", long_opts, &opti))) {
if (opt == EOF)
break;
+
switch (opt) {
case 'h':
printHelpFlag(program);
@@ -143,7 +153,8 @@ static CommandLineStatus parseArguments(const char* program, int argc, char** ar
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);
+ if (name)
+ printf("%19s %s\n", name, description);
}
return STATUS_OK_EXIT;
}
@@ -163,16 +174,28 @@ static CommandLineStatus parseArguments(const char* program, int argc, char** ar
break;
case 'd':
if (sscanf(optarg, "%16d", &(flags->delay)) == 1) {
- if (flags->delay < 1) flags->delay = 1;
- if (flags->delay > 100) flags->delay = 100;
+ if (flags->delay < 1)
+ flags->delay = 1;
+ if (flags->delay > 100)
+ flags->delay = 100;
} else {
fprintf(stderr, "Error: invalid delay value \"%s\".\n", optarg);
return STATUS_ERROR_EXIT;
}
break;
- case 'u':
- {
- const char *username = optarg;
+ case 'n':
+ if (sscanf(optarg, "%16d", &flags->iterationsRemaining) == 1) {
+ if (flags->iterationsRemaining <= 0) {
+ fprintf(stderr, "Error: maximum iteration count must be positive.\n");
+ return STATUS_ERROR_EXIT;
+ }
+ } else {
+ fprintf(stderr, "Error: invalid maximum iteration count \"%s\".\n", optarg);
+ return STATUS_ERROR_EXIT;
+ }
+ break;
+ case 'u': {
+ const char* username = optarg;
if (!username && optind < argc && argv[optind] != NULL &&
(argv[optind][0] != '\0' && argv[optind][0] != '-')) {
username = argv[optind++];
@@ -181,7 +204,7 @@ static CommandLineStatus parseArguments(const char* program, int argc, char** ar
if (!username) {
flags->userId = geteuid();
} else if (!Action_setUserOnly(username, &(flags->userId))) {
- for (const char *itr = username; *itr; ++itr)
+ for (const char* itr = username; *itr; ++itr)
if (!isdigit((unsigned char)*itr)) {
fprintf(stderr, "Error: invalid user \"%s\".\n", username);
return STATUS_ERROR_EXIT;
@@ -214,35 +237,34 @@ static CommandLineStatus parseArguments(const char* program, int argc, char** ar
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);
+ 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': {
+ case 'F':
assert(optarg);
free_and_xStrdup(&flags->commFilter, optarg);
break;
- }
case 'H': {
- const char *delay = optarg;
+ const char* delay = optarg;
if (!delay && optind < argc && argv[optind] != NULL &&
(argv[optind][0] != '\0' && argv[optind][0] != '-')) {
- delay = argv[optind++];
+ 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);
- return STATUS_ERROR_EXIT;
- }
+ 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);
+ return STATUS_ERROR_EXIT;
+ }
}
flags->highlightChanges = true;
break;
@@ -259,31 +281,40 @@ static CommandLineStatus parseArguments(const char* program, int argc, char** ar
}
}
}
+
+ if (optind < argc) {
+ fprintf(stderr, "Error: unsupported non-option ARGV-elements:");
+ while (optind < argc)
+ fprintf(stderr, " %s", argv[optind++]);
+ fprintf(stderr, "\n");
+ return STATUS_ERROR_EXIT;
+ }
+
return STATUS_OK;
}
-static void CommandLine_delay(ProcessList* pl, unsigned long millisec) {
+static void CommandLine_delay(Machine* host, 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);
+ Platform_gettime_realtime(&host->realtime, &host->realtimeMs);
}
static void setCommFilter(State* state, char** commFilter) {
- ProcessList* pl = state->pl;
+ Table* table = state->host->activeTable;
IncSet* inc = state->mainPanel->inc;
IncSet_setFilter(inc, *commFilter);
- pl->incFilter = IncSet_filter(inc);
+ table->incFilter = IncSet_filter(inc);
free(*commFilter);
*commFilter = NULL;
}
-int CommandLine_run(const char* name, int argc, char** argv) {
+int CommandLine_run(int argc, char** argv) {
/* initialize locale */
const char* lc_ctype;
@@ -295,7 +326,7 @@ int CommandLine_run(const char* name, int argc, char** argv) {
CommandLineStatus status = STATUS_OK;
CommandLineSettings flags = { 0 };
- if ((status = parseArguments(name, argc, argv, &flags)) != STATUS_OK)
+ if ((status = parseArguments(argc, argv, &flags)) != STATUS_OK)
return status != STATUS_OK_EXIT ? 1 : 0;
if (flags.readonly)
@@ -304,21 +335,17 @@ int CommandLine_run(const char* name, int argc, char** argv) {
if (!Platform_init())
return 1;
- 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;
+ Hashtable* dc = DynamicColumns_new();
+ Hashtable* ds = DynamicScreens_new();
- Header* header = Header_new(pl, settings, 2);
+ Machine* host = Machine_new(ut, flags.userId);
+ ProcessTable* pt = ProcessTable_new(host, flags.pidMatchList);
+ Settings* settings = Settings_new(host->activeCPUs, dm, dc, ds);
+ Machine_populateTablesFromSettings(host, settings, &pt->super);
+ Header* header = Header_new(host, 2);
Header_populateFromSettings(header);
if (flags.delay != -1)
@@ -344,36 +371,38 @@ int CommandLine_run(const char* name, int argc, char** argv) {
ScreenSettings_setSortKey(settings->ss, flags.sortKey);
}
- CRT_init(settings, flags.allowUnicode);
+ host->iterationsRemaining = flags.iterationsRemaining;
+ CRT_init(settings, flags.allowUnicode, flags.iterationsRemaining != -1);
MainPanel* panel = MainPanel_new();
- ProcessList_setPanel(pl, (Panel*) panel);
+ Machine_setTablesPanel(host, (Panel*) panel);
MainPanel_updateLabels(panel, settings->ss->treeView, flags.commFilter);
State state = {
- .settings = settings,
- .ut = ut,
- .pl = pl,
+ .host = host,
.mainPanel = panel,
.header = header,
- .pauseProcessUpdate = false,
- .hideProcessSelection = false,
+ .pauseUpdate = false,
+ .hideSelection = false,
+ .hideMeters = false,
};
MainPanel_setState(panel, &state);
if (flags.commFilter)
setCommFilter(&state, &(flags.commFilter));
- ScreenManager* scr = ScreenManager_new(header, settings, &state, true);
+ ScreenManager* scr = ScreenManager_new(header, host, &state, true);
ScreenManager_add(scr, (Panel*) panel, -1);
- ProcessList_scan(pl, false);
- CommandLine_delay(pl, 75);
- ProcessList_scan(pl, false);
+ Machine_scan(host);
+ Machine_scanTables(host);
+ CommandLine_delay(host, 75);
+ Machine_scan(host);
+ Machine_scanTables(host);
if (settings->ss->allBranchesCollapsed)
- ProcessList_collapseAllBranches(pl);
+ Table_collapseAllBranches(&pt->super);
ScreenManager_run(scr, NULL, NULL, NULL);
@@ -382,13 +411,17 @@ int CommandLine_run(const char* name, int argc, char** argv) {
CRT_done();
if (settings->changed) {
+#ifndef NDEBUG
+ if (!String_eq(settings->initialFilename, settings->filename))
+ fprintf(stderr, "Configuration %s was resolved to %s\n", settings->initialFilename, settings->filename);
+#endif /* NDEBUG */
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);
+ Machine_delete(host);
ScreenManager_delete(scr);
MetersPanel_cleanup();
@@ -404,6 +437,7 @@ int CommandLine_run(const char* name, int argc, char** argv) {
Settings_delete(settings);
DynamicColumns_delete(dc);
DynamicMeters_delete(dm);
+ DynamicScreens_delete(ds);
return 0;
}
diff --git a/CommandLine.h b/CommandLine.h
index fbdede84..18395005 100644
--- a/CommandLine.h
+++ b/CommandLine.h
@@ -14,6 +14,8 @@ typedef enum {
STATUS_OK_EXIT
} CommandLineStatus;
-int CommandLine_run(const char* name, int argc, char** argv);
+extern const char* program;
+
+int CommandLine_run(int argc, char** argv);
#endif
diff --git a/CommandScreen.c b/CommandScreen.c
index 6a87d137..465e4c2f 100644
--- a/CommandScreen.c
+++ b/CommandScreen.c
@@ -1,3 +1,11 @@
+/*
+htop - CommandScreen.c
+(C) 2017,2020 ryenus
+(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 "CommandScreen.h"
@@ -46,7 +54,7 @@ static void CommandScreen_scan(InfoScreen* this) {
}
static void CommandScreen_draw(InfoScreen* this) {
- InfoScreen_drawTitled(this, "Command of process %d - %s", this->process->pid, Process_getCommand(this->process));
+ InfoScreen_drawTitled(this, "Command of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process));
}
const InfoScreenClass CommandScreen_class = {
diff --git a/CommandScreen.h b/CommandScreen.h
index e56982b2..2eef5e55 100644
--- a/CommandScreen.h
+++ b/CommandScreen.h
@@ -1,5 +1,12 @@
#ifndef HEADER_CommandScreen
#define HEADER_CommandScreen
+/*
+htop - CommandScreen.h
+(C) 2017,2020 ryenus
+(C) 2020,2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
#include "InfoScreen.h"
#include "Object.h"
diff --git a/Compat.c b/Compat.c
index 8c881381..6df0b081 100644
--- a/Compat.c
+++ b/Compat.c
@@ -11,6 +11,7 @@ in the source distribution for its full text.
#include <errno.h>
#include <fcntl.h> // IWYU pragma: keep
+#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h> // IWYU pragma: keep
@@ -18,6 +19,12 @@ in the source distribution for its full text.
#include "XUtils.h" // IWYU pragma: keep
+/* GNU/Hurd does not have PATH_MAX in limits.h */
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+
+
int Compat_faccessat(int dirfd,
const char* pathname,
int mode,
@@ -43,11 +50,11 @@ int Compat_faccessat(int dirfd,
}
// Fallback to stat(2)/lstat(2) depending on flags
- struct stat statinfo;
+ struct stat sb;
if (flags) {
- ret = lstat(pathname, &statinfo);
+ ret = lstat(pathname, &sb);
} else {
- ret = stat(pathname, &statinfo);
+ ret = stat(pathname, &sb);
}
return ret;
@@ -117,3 +124,33 @@ ssize_t Compat_readlinkat(int dirfd,
#endif
}
+
+ssize_t Compat_readlink(openat_arg_t dirfd,
+ const char* pathname,
+ char* buf,
+ size_t bufsize) {
+
+#ifdef HAVE_OPENAT
+
+ char fdPath[32];
+ xSnprintf(fdPath, sizeof(fdPath), "/proc/self/fd/%d", dirfd);
+
+ char dirPath[PATH_MAX + 1];
+ ssize_t r = readlink(fdPath, dirPath, sizeof(dirPath) - 1);
+ if (r < 0)
+ return r;
+
+ dirPath[r] = '\0';
+
+ char linkPath[PATH_MAX + 1];
+ xSnprintf(linkPath, sizeof(linkPath), "%s/%s", dirPath, pathname);
+
+#else
+
+ char linkPath[PATH_MAX + 1];
+ xSnprintf(linkPath, sizeof(linkPath), "%s/%s", dirfd, pathname);
+
+#endif /* HAVE_OPENAT */
+
+ return readlink(linkPath, buf, bufsize);
+}
diff --git a/Compat.h b/Compat.h
index 2b5e2052..02b2fcb2 100644
--- a/Compat.h
+++ b/Compat.h
@@ -7,10 +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> // IWYU pragma: keep
#include <fcntl.h>
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
#include <unistd.h>
#include <sys/stat.h> // IWYU pragma: keep
@@ -56,4 +55,30 @@ ssize_t Compat_readlinkat(int dirfd,
char* buf,
size_t bufsize);
+ssize_t Compat_readlink(openat_arg_t dirfd,
+ const char* pathname,
+ char* buf,
+ size_t bufsize);
+
+/*
+ * static_assert() hack for pre-C11
+ * TODO: drop after moving to -std=c11 or newer
+ */
+
+/* C11 guarantees _Static_assert is a keyword */
+#if (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) < 201112L
+# if !defined(_Static_assert)
+# define _Static_assert(expr, msg) \
+ extern int (*__Static_assert_function (void)) \
+ [!!sizeof (struct { int __error_if_negative: (expr) ? 2 : -1; })]
+# endif
+#endif
+
+/* C23 guarantees static_assert is a keyword or a macro */
+#if (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) < 202311L
+# if !defined(static_assert)
+# define static_assert(expr, msg) _Static_assert(expr, msg)
+# endif
+#endif
+
#endif /* HEADER_Compat */
diff --git a/DateMeter.c b/DateMeter.c
index 96285963..0bdb30a8 100644
--- a/DateMeter.c
+++ b/DateMeter.c
@@ -13,8 +13,8 @@ in the source distribution for its full text.
#include <sys/time.h>
#include "CRT.h"
+#include "Machine.h"
#include "Object.h"
-#include "ProcessList.h"
static const int DateMeter_attributes[] = {
@@ -22,10 +22,10 @@ static const int DateMeter_attributes[] = {
};
static void DateMeter_updateValues(Meter* this) {
- const ProcessList* pl = this->pl;
+ const Machine* host = this->host;
struct tm result;
- const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
+ const struct tm* lt = localtime_r(&host->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)) {
diff --git a/DateTimeMeter.c b/DateTimeMeter.c
index 1044ff39..dcd85ea6 100644
--- a/DateTimeMeter.c
+++ b/DateTimeMeter.c
@@ -13,8 +13,8 @@ in the source distribution for its full text.
#include <sys/time.h>
#include "CRT.h"
+#include "Machine.h"
#include "Object.h"
-#include "ProcessList.h"
static const int DateTimeMeter_attributes[] = {
@@ -22,10 +22,10 @@ static const int DateTimeMeter_attributes[] = {
};
static void DateTimeMeter_updateValues(Meter* this) {
- const ProcessList* pl = this->pl;
+ const Machine* host = this->host;
struct tm result;
- const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
+ const struct tm* lt = localtime_r(&host->realtime.tv_sec, &result);
int year = lt->tm_year + 1900;
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
this->total = 366;
diff --git a/DiskIOMeter.c b/DiskIOMeter.c
index 12c87de6..8d658de8 100644
--- a/DiskIOMeter.c
+++ b/DiskIOMeter.c
@@ -5,18 +5,19 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "DiskIOMeter.h"
#include <stdbool.h>
-#include <stdio.h>
#include "CRT.h"
+#include "Machine.h"
#include "Macros.h"
-#include "Meter.h"
#include "Object.h"
#include "Platform.h"
-#include "ProcessList.h"
#include "RichString.h"
+#include "Row.h"
#include "XUtils.h"
@@ -27,25 +28,22 @@ static const int DiskIOMeter_attributes[] = {
};
static MeterRateStatus status = RATESTATUS_INIT;
-static uint32_t cached_read_diff;
-static uint32_t cached_write_diff;
+static char cached_read_diff_str[6];
+static char cached_write_diff_str[6];
static double cached_utilisation_diff;
static void DiskIOMeter_updateValues(Meter* this) {
- const ProcessList* pl = this->pl;
+ const Machine* host = this->host;
static uint64_t cached_last_update;
- uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update;
+ uint64_t passedTimeInMs = host->realtimeMs - cached_last_update;
+ bool hasNewData = false;
+ DiskIOData data;
/* update only every 500ms to have a sane span for rate calculation */
if (passedTimeInMs > 500) {
- static uint64_t cached_read_total;
- static uint64_t cached_write_total;
- static uint64_t cached_msTimeSpend_total;
- uint64_t diff;
-
- DiskIOData data;
- if (!Platform_getDiskIO(&data)) {
+ hasNewData = Platform_getDiskIO(&data);
+ if (!hasNewData) {
status = RATESTATUS_NODATA;
} else if (cached_last_update == 0) {
status = RATESTATUS_INIT;
@@ -55,40 +53,55 @@ static void DiskIOMeter_updateValues(Meter* this) {
status = RATESTATUS_DATA;
}
- cached_last_update = pl->realtimeMs;
+ cached_last_update = host->realtimeMs;
+ }
- if (status == RATESTATUS_NODATA) {
- xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
- return;
- }
+ if (hasNewData) {
+ static uint64_t cached_read_total;
+ static uint64_t cached_write_total;
+ static uint64_t cached_msTimeSpend_total;
- if (data.totalBytesRead > cached_read_total) {
- 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;
+ if (status != RATESTATUS_INIT) {
+ uint64_t diff;
+
+ if (data.totalBytesRead > cached_read_total) {
+ diff = data.totalBytesRead - cached_read_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ diff /= ONE_K; /* convert to KiB/s */
+ } else {
+ diff = 0;
+ }
+ Meter_humanUnit(cached_read_diff_str, diff, sizeof(cached_read_diff_str));
+
+ if (data.totalBytesWritten > cached_write_total) {
+ diff = data.totalBytesWritten - cached_write_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ diff /= ONE_K; /* convert to KiB/s */
+ } else {
+ diff = 0;
+ }
+ Meter_humanUnit(cached_write_diff_str, diff, sizeof(cached_write_diff_str));
+
+ if (data.totalMsTimeSpend > cached_msTimeSpend_total) {
+ diff = data.totalMsTimeSpend - cached_msTimeSpend_total;
+ cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs;
+ cached_utilisation_diff = MINIMUM(cached_utilisation_diff, 100.0);
+ } else {
+ cached_utilisation_diff = 0.0;
+ }
}
- cached_read_total = data.totalBytesRead;
- if (data.totalBytesWritten > cached_write_total) {
- 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_read_total = data.totalBytesRead;
cached_write_total = data.totalBytesWritten;
-
- if (data.totalMsTimeSpend > cached_msTimeSpend_total) {
- diff = data.totalMsTimeSpend - cached_msTimeSpend_total;
- cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs;
- } else {
- cached_utilisation_diff = 0.0;
- }
cached_msTimeSpend_total = data.totalMsTimeSpend;
}
+ this->values[0] = cached_utilisation_diff;
+
+ if (status == RATESTATUS_NODATA) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
+ return;
+ }
if (status == RATESTATUS_INIT) {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init");
return;
@@ -98,44 +111,37 @@ static void DiskIOMeter_updateValues(Meter* this) {
return;
}
- this->values[0] = cached_utilisation_diff;
- this->total = MAXIMUM(this->values[0], 100.0); /* fix total after (initial) spike */
-
- char bufferRead[12], bufferWrite[12];
- Meter_humanUnit(bufferRead, cached_read_diff, sizeof(bufferRead));
- Meter_humanUnit(bufferWrite, cached_write_diff, sizeof(bufferWrite));
- snprintf(this->txtBuffer, sizeof(this->txtBuffer), "%sB %sB %.1f%%", bufferRead, bufferWrite, cached_utilisation_diff);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "r:%siB/s w:%siB/s %.1f%%", cached_read_diff_str, cached_write_diff_str, cached_utilisation_diff);
}
static void DiskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
switch (status) {
- case RATESTATUS_NODATA:
- RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
- return;
- case RATESTATUS_INIT:
- RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
- return;
- case RATESTATUS_STALE:
- RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
- return;
- case RATESTATUS_DATA:
- break;
+ case RATESTATUS_NODATA:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
+ return;
+ case RATESTATUS_INIT:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
+ return;
+ case RATESTATUS_STALE:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
+ return;
+ case RATESTATUS_DATA:
+ break;
}
char buffer[16];
- int len;
int color = cached_utilisation_diff > 40.0 ? METER_VALUE_NOTICE : METER_VALUE;
- len = xSnprintf(buffer, sizeof(buffer), "%.1f%%", cached_utilisation_diff);
+ int 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));
- RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], cached_read_diff_str);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], "iB/s");
RichString_appendAscii(out, CRT_colors[METER_TEXT], " write: ");
- Meter_humanUnit(buffer, cached_write_diff, sizeof(buffer));
- RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], cached_write_diff_str);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s");
}
const MeterClass DiskIOMeter_class = {
diff --git a/DiskIOMeter.h b/DiskIOMeter.h
index 3b03e321..5ac9c484 100644
--- a/DiskIOMeter.h
+++ b/DiskIOMeter.h
@@ -7,6 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include <stdint.h>
+
#include "Meter.h"
diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c
index fc23cb65..66793e16 100644
--- a/DisplayOptionsPanel.c
+++ b/DisplayOptionsPanel.c
@@ -11,6 +11,7 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdlib.h>
+#include <string.h>
#include "CRT.h"
#include "FunctionBar.h"
@@ -37,37 +38,37 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {
OptionItem* selected = (OptionItem*) Panel_getSelected(super);
switch (ch) {
- case '\n':
- case '\r':
- case KEY_ENTER:
- case KEY_MOUSE:
- case KEY_RECLICK:
- case ' ':
- switch (OptionItem_kind(selected)) {
- case OPTION_ITEM_TEXT:
+ case '\n':
+ case '\r':
+ case KEY_ENTER:
+ case KEY_MOUSE:
+ case KEY_RECLICK:
+ case ' ':
+ switch (OptionItem_kind(selected)) {
+ case OPTION_ITEM_TEXT:
+ break;
+ case OPTION_ITEM_CHECK:
+ CheckItem_toggle((CheckItem*)selected);
+ result = HANDLED;
+ break;
+ case OPTION_ITEM_NUMBER:
+ NumberItem_toggle((NumberItem*)selected);
+ result = HANDLED;
+ break;
+ }
break;
- case OPTION_ITEM_CHECK:
- CheckItem_toggle((CheckItem*)selected);
- result = HANDLED;
+ case '-':
+ if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) {
+ NumberItem_decrease((NumberItem*)selected);
+ result = HANDLED;
+ }
break;
- case OPTION_ITEM_NUMBER:
- NumberItem_toggle((NumberItem*)selected);
- result = HANDLED;
+ case '+':
+ if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) {
+ NumberItem_increase((NumberItem*)selected);
+ result = HANDLED;
+ }
break;
- }
- break;
- case '-':
- if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) {
- NumberItem_decrease((NumberItem*)selected);
- result = HANDLED;
- }
- break;
- case '+':
- if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) {
- NumberItem_increase((NumberItem*)selected);
- result = HANDLED;
- }
- break;
}
if (result == HANDLED) {
@@ -80,6 +81,7 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {
Header_draw(header);
ScreenManager_resize(this->scr);
}
+
return result;
}
@@ -104,7 +106,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
#define TABMSG "For current screen tab: \0"
char tabheader[sizeof(TABMSG) + SCREEN_NAME_LEN + 1] = TABMSG;
- strncat(tabheader, settings->ss->name, SCREEN_NAME_LEN);
+ strncat(tabheader, settings->ss->heading, SCREEN_NAME_LEN);
Panel_add(super, (Object*) TextItem_new(tabheader));
#undef TABMSG
@@ -116,11 +118,13 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
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)));
+ Panel_add(super, (Object*) CheckItem_newByRef("Hide processes running in containers", &(settings->hideRunningInContainer)));
Panel_add(super, (Object*) CheckItem_newByRef("Display threads in a different color", &(settings->highlightThreads)));
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 (red) / libraries (yellow)", &(settings->highlightDeletedExe)));
+ Panel_add(super, (Object*) CheckItem_newByRef("Shadow distribution path prefixes", &(settings->shadowDistPathPrefix)));
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)));
diff --git a/DynamicColumn.c b/DynamicColumn.c
index bd038df4..5f021740 100644
--- a/DynamicColumn.c
+++ b/DynamicColumn.c
@@ -12,6 +12,7 @@ in the source distribution for its full text.
#include "DynamicColumn.h"
#include <stddef.h>
+#include <stdlib.h>
#include "Platform.h"
#include "RichString.h"
@@ -19,7 +20,10 @@ in the source distribution for its full text.
Hashtable* DynamicColumns_new(void) {
- return Platform_dynamicColumns();
+ Hashtable* dynamics = Platform_dynamicColumns();
+ if (!dynamics)
+ dynamics = Hashtable_new(0, true);
+ return dynamics;
}
void DynamicColumns_delete(Hashtable* dynamics) {
@@ -29,8 +33,14 @@ void DynamicColumns_delete(Hashtable* dynamics) {
}
}
-const char* DynamicColumn_init(unsigned int key) {
- return Platform_dynamicColumnInit(key);
+const char* DynamicColumn_name(unsigned int key) {
+ return Platform_dynamicColumnName(key);
+}
+
+void DynamicColumn_done(DynamicColumn* this) {
+ free(this->heading);
+ free(this->caption);
+ free(this->description);
}
typedef struct {
diff --git a/DynamicColumn.h b/DynamicColumn.h
index 4760e6ea..bdce82d2 100644
--- a/DynamicColumn.h
+++ b/DynamicColumn.h
@@ -1,29 +1,40 @@
#ifndef HEADER_DynamicColumn
#define HEADER_DynamicColumn
+/*
+htop - DynamicColumn.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
#include <stdbool.h>
#include "Hashtable.h"
#include "Process.h"
#include "RichString.h"
+#include "Table.h"
-#define DYNAMIC_MAX_COLUMN_WIDTH 28
+#define DYNAMIC_MAX_COLUMN_WIDTH 64
#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 */
+ 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 */
+ bool enabled; /* false == ignore this column (until enabled) */
+ Table* table; /* pointer to DynamicScreen or ProcessTable */
} DynamicColumn;
Hashtable* DynamicColumns_new(void);
void DynamicColumns_delete(Hashtable* dynamics);
-const char* DynamicColumn_init(unsigned int key);
+const char* DynamicColumn_name(unsigned int key);
+
+void DynamicColumn_done(DynamicColumn* this);
const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key);
diff --git a/DynamicMeter.c b/DynamicMeter.c
index a8cd76cc..15d853bd 100644
--- a/DynamicMeter.c
+++ b/DynamicMeter.c
@@ -15,10 +15,11 @@ in the source distribution for its full text.
#include <string.h>
#include "CRT.h"
+#include "Machine.h"
#include "Object.h"
#include "Platform.h"
-#include "ProcessList.h"
#include "RichString.h"
+#include "Settings.h"
#include "XUtils.h"
@@ -88,16 +89,16 @@ static void DynamicMeter_display(const Object* cast, RichString* out) {
}
static const char* DynamicMeter_getCaption(const Meter* this) {
- const ProcessList* pl = this->pl;
- const DynamicMeter* meter = Hashtable_get(pl->dynamicMeters, this->param);
+ const Settings* settings = this->host->settings;
+ const DynamicMeter* meter = Hashtable_get(settings->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);
+ const Settings* settings = this->host->settings;
+ const DynamicMeter* meter = Hashtable_get(settings->dynamicMeters, this->param);
if (meter) {
const char* uiName = meter->caption;
if (uiName) {
diff --git a/DynamicMeter.h b/DynamicMeter.h
index 3ef0176a..2bc3cba1 100644
--- a/DynamicMeter.h
+++ b/DynamicMeter.h
@@ -1,5 +1,12 @@
#ifndef HEADER_DynamicMeter
#define HEADER_DynamicMeter
+/*
+htop - DynamicMeter.h
+(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 <stdbool.h>
diff --git a/DynamicScreen.c b/DynamicScreen.c
new file mode 100644
index 00000000..9e3d5e49
--- /dev/null
+++ b/DynamicScreen.c
@@ -0,0 +1,68 @@
+/*
+htop - DynamicScreen.c
+(C) 2022 Sohaib Mohammed
+(C) 2022-2023 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 "DynamicScreen.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "Hashtable.h"
+#include "Platform.h"
+#include "XUtils.h"
+
+
+Hashtable* DynamicScreens_new(void) {
+ return Platform_dynamicScreens();
+}
+
+void DynamicScreens_delete(Hashtable* screens) {
+ if (screens) {
+ Platform_dynamicScreensDone(screens);
+ Hashtable_delete(screens);
+ }
+}
+
+void DynamicScreen_done(DynamicScreen* this) {
+ free(this->caption);
+ free(this->fields);
+ free(this->heading);
+ free(this->sortKey);
+ free(this->columnKeys);
+}
+
+typedef struct {
+ ht_key_t key;
+ const char* name;
+ bool found;
+} DynamicIterator;
+
+static void DynamicScreen_compare(ht_key_t key, void* value, void* data) {
+ const DynamicScreen* screen = (const DynamicScreen*)value;
+ DynamicIterator* iter = (DynamicIterator*)data;
+ if (String_eq(iter->name, screen->name)) {
+ iter->found = true;
+ iter->key = key;
+ }
+}
+
+bool DynamicScreen_search(Hashtable* screens, const char* name, ht_key_t* key) {
+ DynamicIterator iter = { .key = 0, .name = name, .found = false };
+ if (screens)
+ Hashtable_foreach(screens, DynamicScreen_compare, &iter);
+ if (key)
+ *key = iter.key;
+ return iter.found;
+}
+
+const char* DynamicScreen_lookup(Hashtable* screens, ht_key_t key) {
+ const DynamicScreen* screen = Hashtable_get(screens, key);
+ return screen ? screen->name : NULL;
+}
diff --git a/DynamicScreen.h b/DynamicScreen.h
new file mode 100644
index 00000000..00aa5e17
--- /dev/null
+++ b/DynamicScreen.h
@@ -0,0 +1,38 @@
+#ifndef HEADER_DynamicScreen
+#define HEADER_DynamicScreen
+/*
+htop - DynamicColumn.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "Hashtable.h"
+#include "Panel.h"
+
+
+typedef struct DynamicScreen_ {
+ char name[32]; /* unique name cannot contain any spaces */
+ char* heading; /* user-settable more readable name */
+ char* caption; /* explanatory text for screen */
+ char* fields;
+ char* sortKey;
+ char* columnKeys;
+ int direction;
+} DynamicScreen;
+
+Hashtable* DynamicScreens_new(void);
+
+void DynamicScreens_delete(Hashtable* screens);
+
+void DynamicScreen_done(DynamicScreen* this);
+
+void DynamicScreens_addAvailableColumns(Panel* availableColumns, char* screen);
+
+const char* DynamicScreen_lookup(Hashtable* screens, unsigned int key);
+
+bool DynamicScreen_search(Hashtable* screens, const char* name, unsigned int* key);
+
+#endif
diff --git a/EnvScreen.c b/EnvScreen.c
index 0fcee83a..4a36b318 100644
--- a/EnvScreen.c
+++ b/EnvScreen.c
@@ -1,3 +1,12 @@
+/*
+htop - EnvScreen.c
+(C) 2015,2016 Michael Klein
+(C) 2016,2017 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 "EnvScreen.h"
@@ -24,7 +33,7 @@ void EnvScreen_delete(Object* this) {
}
static void EnvScreen_draw(InfoScreen* this) {
- InfoScreen_drawTitled(this, "Environment of process %d - %s", this->process->pid, Process_getCommand(this->process));
+ InfoScreen_drawTitled(this, "Environment of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process));
}
static void EnvScreen_scan(InfoScreen* this) {
@@ -33,7 +42,7 @@ static void EnvScreen_scan(InfoScreen* this) {
Panel_prune(panel);
- char* env = Platform_getProcessEnv(this->process->pid);
+ char* env = Platform_getProcessEnv(Process_getPid(this->process));
if (env) {
for (const char* p = env; *p; p = strrchr(p, 0) + 1)
InfoScreen_addLine(this, p);
diff --git a/EnvScreen.h b/EnvScreen.h
index 4d44c81c..118c271c 100644
--- a/EnvScreen.h
+++ b/EnvScreen.h
@@ -1,5 +1,13 @@
#ifndef HEADER_EnvScreen
#define HEADER_EnvScreen
+/*
+htop - EnvScreen.h
+(C) 2015,2016 Michael Klein
+(C) 2016,2017 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 "InfoScreen.h"
#include "Object.h"
diff --git a/FileDescriptorMeter.c b/FileDescriptorMeter.c
new file mode 100644
index 00000000..cf1ec933
--- /dev/null
+++ b/FileDescriptorMeter.c
@@ -0,0 +1,118 @@
+/*
+htop - FileDescriptorMeter.c
+(C) 2022 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 "FileDescriptorMeter.h"
+
+#include <math.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Meter.h"
+#include "Object.h"
+#include "Platform.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+
+#define FD_EFFECTIVE_UNLIMITED(x) (!isgreaterequal((double)(1<<30), (x)))
+
+static const int FileDescriptorMeter_attributes[] = {
+ FILE_DESCRIPTOR_USED,
+ FILE_DESCRIPTOR_MAX
+};
+
+static void FileDescriptorMeter_updateValues(Meter* this) {
+ this->values[0] = 0;
+ this->values[1] = 1;
+
+ Platform_getFileDescriptors(&this->values[0], &this->values[1]);
+
+ /* only print bar for first value */
+ this->curItems = 1;
+
+ /* Use maximum value for scaling of bar mode
+ *
+ * As the plain total value can be very large compared to
+ * the actually used value, this is capped in the following way:
+ *
+ * 1. If the maximum value is below (or equal to) 1<<16, use it directly
+ * 2. If the maximum value is above, use powers of 2 starting at 1<<16 and
+ * double it until it's larger than 16 times the used file handles
+ * (capped at the maximum number of files)
+ * 3. If the maximum is effectively unlimited (AKA > 1<<30),
+ * Do the same as for 2, but cap at 1<<30.
+ */
+ if (this->values[1] <= 1 << 16) {
+ this->total = this->values[1];
+ } else {
+ if (this->total < 16 * this->values[0]) {
+ for (this->total = 1 << 16; this->total < 16 * this->values[0]; this->total *= 2) {
+ if (this->total >= 1 << 30) {
+ break;
+ }
+ }
+ }
+
+ if (this->total > this->values[1]) {
+ this->total = this->values[1];
+ }
+
+ if (this->total > 1 << 30) {
+ this->total = 1 << 30;
+ }
+ }
+
+ if (!isNonnegative(this->values[0])) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "unknown/unknown");
+ } else if (FD_EFFECTIVE_UNLIMITED(this->values[1])) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.0lf/unlimited", this->values[0]);
+ } else {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.0lf/%.0lf", this->values[0], this->values[1]);
+ }
+}
+
+static void FileDescriptorMeter_display(const Object* cast, RichString* out) {
+ const Meter* this = (const Meter*)cast;
+ char buffer[50];
+ int len;
+
+ if (!isNonnegative(this->values[0])) {
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "unknown");
+ return;
+ }
+
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "used: ");
+ len = xSnprintf(buffer, sizeof(buffer), "%.0lf", this->values[0]);
+ RichString_appendnAscii(out, CRT_colors[FILE_DESCRIPTOR_USED], buffer, len);
+
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " max: ");
+ if (FD_EFFECTIVE_UNLIMITED(this->values[1])) {
+ RichString_appendAscii(out, CRT_colors[FILE_DESCRIPTOR_MAX], "unlimited");
+ } else {
+ len = xSnprintf(buffer, sizeof(buffer), "%.0lf", this->values[1]);
+ RichString_appendnAscii(out, CRT_colors[FILE_DESCRIPTOR_MAX], buffer, len);
+ }
+}
+
+const MeterClass FileDescriptorMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = FileDescriptorMeter_display,
+ },
+ .updateValues = FileDescriptorMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 2,
+ .total = 65536.0,
+ .attributes = FileDescriptorMeter_attributes,
+ .name = "FileDescriptors",
+ .uiName = "File Descriptors",
+ .caption = "FDs: ",
+ .description = "Number of allocated/available file descriptors"
+};
diff --git a/FileDescriptorMeter.h b/FileDescriptorMeter.h
new file mode 100644
index 00000000..e1b4f5f8
--- /dev/null
+++ b/FileDescriptorMeter.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_FileDescriptorMeter
+#define HEADER_FileDescriptorMeter
+/*
+htop - FileDescriptorMeter.h
+(C) 2022 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 FileDescriptorMeter_class;
+
+#endif
diff --git a/FunctionBar.c b/FunctionBar.c
index 08500373..af4142e9 100644
--- a/FunctionBar.c
+++ b/FunctionBar.c
@@ -30,25 +30,25 @@ static const int FunctionBar_EnterEscEvents[] = {13, 27};
static int currentLen = 0;
FunctionBar* FunctionBar_newEnterEsc(const char* enter, const char* esc) {
- const char* functions[] = {enter, esc, NULL};
+ const char* functions[FUNCTIONBAR_MAXEVENTS + 1] = {enter, esc, NULL};
return FunctionBar_new(functions, FunctionBar_EnterEscKeys, FunctionBar_EnterEscEvents);
}
FunctionBar* FunctionBar_new(const char* const* functions, const char* const* keys, const int* events) {
FunctionBar* this = xCalloc(1, sizeof(FunctionBar));
- this->functions = xCalloc(16, sizeof(char*));
+ this->functions = xCalloc(FUNCTIONBAR_MAXEVENTS + 1, sizeof(char*));
if (!functions) {
functions = FunctionBar_FLabels;
}
- for (int i = 0; i < 15 && functions[i]; i++) {
+ for (int i = 0; i < FUNCTIONBAR_MAXEVENTS && functions[i]; i++) {
this->functions[i] = xStrdup(functions[i]);
}
if (keys && events) {
this->staticData = false;
- this->keys.keys = xCalloc(15, sizeof(char*));
- this->events = xCalloc(15, sizeof(int));
+ this->keys.keys = xCalloc(FUNCTIONBAR_MAXEVENTS, sizeof(char*));
+ this->events = xCalloc(FUNCTIONBAR_MAXEVENTS, sizeof(int));
int i = 0;
- while (i < 15 && functions[i]) {
+ while (i < FUNCTIONBAR_MAXEVENTS && functions[i]) {
this->keys.keys[i] = xStrdup(keys[i]);
this->events[i] = events[i];
i++;
@@ -64,7 +64,7 @@ FunctionBar* FunctionBar_new(const char* const* functions, const char* const* ke
}
void FunctionBar_delete(FunctionBar* this) {
- for (int i = 0; i < 15 && this->functions[i]; i++) {
+ for (int i = 0; i < FUNCTIONBAR_MAXEVENTS && this->functions[i]; i++) {
free(this->functions[i]);
}
free(this->functions);
diff --git a/FunctionBar.h b/FunctionBar.h
index f01a5ef5..06d9c6c8 100644
--- a/FunctionBar.h
+++ b/FunctionBar.h
@@ -21,6 +21,8 @@ typedef struct FunctionBar_ {
bool staticData;
} FunctionBar;
+#define FUNCTIONBAR_MAXEVENTS 15
+
FunctionBar* FunctionBar_newEnterEsc(const char* enter, const char* esc);
FunctionBar* FunctionBar_new(const char* const* functions, const char* const* keys, const int* events);
diff --git a/Hashtable.c b/Hashtable.c
index 2756b232..041f25eb 100644
--- a/Hashtable.c
+++ b/Hashtable.c
@@ -109,13 +109,15 @@ static size_t nextPrime(size_t n) {
}
Hashtable* Hashtable_new(size_t size, bool owner) {
- Hashtable* this;
-
- this = xMalloc(sizeof(Hashtable));
- this->items = 0;
- this->size = size ? nextPrime(size) : 13;
- this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem));
- this->owner = owner;
+ size = size ? nextPrime(size) : 13;
+
+ Hashtable* this = xMalloc(sizeof(Hashtable));
+ *this = (Hashtable) {
+ .items = 0,
+ .size = size,
+ .buckets = xCalloc(size, sizeof(HashtableItem)),
+ .owner = owner,
+ };
assert(Hashtable_isConsistent(this));
return this;
diff --git a/Header.c b/Header.c
index 8dff89b8..8a9eae34 100644
--- a/Header.c
+++ b/Header.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 "Header.h"
#include <assert.h>
@@ -22,15 +24,15 @@ in the source distribution for its full text.
#include "Object.h"
#include "Platform.h"
#include "ProvideCurses.h"
+#include "Settings.h"
#include "XUtils.h"
-Header* Header_new(ProcessList* pl, Settings* settings, HeaderLayout hLayout) {
+Header* Header_new(Machine* host, HeaderLayout hLayout) {
Header* this = xCalloc(1, sizeof(Header));
this->columns = xMallocArray(HeaderLayout_getColumns(hLayout), sizeof(Vector*));
- this->settings = settings;
- this->pl = pl;
this->headerLayout = hLayout;
+ this->host = host;
Header_forEachColumn(this, i) {
this->columns[i] = Vector_new(Class(Meter), true, DEFAULT_SIZE);
@@ -84,15 +86,15 @@ static void Header_addMeterByName(Header* this, const char* name, MeterModeId mo
unsigned int param = 0;
size_t nameLen;
if (paren) {
- int ok = sscanf(paren, "(%10u)", &param); // CPUMeter
- if (!ok) {
+ if (sscanf(paren, "(%10u)", &param) != 1) { // not CPUMeter
char dynamic[32] = {0};
- if (sscanf(paren, "(%30s)", dynamic)) { // DynamicMeter
+ if (sscanf(paren, "(%30s)", dynamic) == 1) { // DynamicMeter
char* end;
if ((end = strrchr(dynamic, ')')) == NULL)
return; // htoprc parse failure
*end = '\0';
- if (!DynamicMeter_search(this->pl->dynamicMeters, dynamic, &param))
+ const Settings* settings = this->host->settings;
+ if (!DynamicMeter_search(settings->dynamicMeters, dynamic, &param))
return; // name lookup failure
} else {
param = 0;
@@ -105,7 +107,7 @@ static void Header_addMeterByName(Header* this, const char* name, MeterModeId mo
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);
+ Meter* meter = Meter_new(this->host, param, *type);
if (mode != 0) {
Meter_setMode(meter, mode);
}
@@ -116,10 +118,11 @@ static void Header_addMeterByName(Header* this, const char* name, MeterModeId mo
}
void Header_populateFromSettings(Header* this) {
- Header_setLayout(this, this->settings->hLayout);
+ const Settings* settings = this->host->settings;
+ Header_setLayout(this, settings->hLayout);
Header_forEachColumn(this, col) {
- const MeterColumnSetting* colSettings = &this->settings->hColumns[col];
+ const MeterColumnSetting* colSettings = &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);
@@ -130,10 +133,11 @@ void Header_populateFromSettings(Header* this) {
}
void Header_writeBackToSettings(const Header* this) {
- Settings_setHeaderLayout(this->settings, this->headerLayout);
+ Settings* settings = this->host->settings;
+ Settings_setHeaderLayout(settings, this->headerLayout);
Header_forEachColumn(this, col) {
- MeterColumnSetting* colSettings = &this->settings->hColumns[col];
+ MeterColumnSetting* colSettings = &settings->hColumns[col];
if (colSettings->names) {
for (size_t j = 0; j < colSettings->len; j++)
@@ -145,15 +149,15 @@ void Header_writeBackToSettings(const Header* this) {
const Vector* vec = this->columns[col];
int len = Vector_size(vec);
- colSettings->names = len ? xCalloc(len + 1, sizeof(char*)) : NULL;
- colSettings->modes = len ? xCalloc(len, sizeof(int)) : NULL;
+ colSettings->names = len ? xCalloc(len + 1, sizeof(*colSettings->names)) : NULL;
+ colSettings->modes = len ? xCalloc(len, sizeof(*colSettings->modes)) : NULL;
colSettings->len = len;
for (int i = 0; i < len; i++) {
const Meter* meter = (Meter*) Vector_get(vec, i);
char* name;
if (meter->param && As_Meter(meter) == &DynamicMeter_class) {
- const char* dynamic = DynamicMeter_lookup(this->pl->dynamicMeters, meter->param);
+ const char* dynamic = DynamicMeter_lookup(settings->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);
@@ -171,7 +175,7 @@ Meter* Header_addMeterByClass(Header* this, const MeterClass* type, unsigned int
Vector* meters = this->columns[column];
- Meter* meter = Meter_new(this->pl, param, type);
+ Meter* meter = Meter_new(this->host, param, type);
Vector_add(meters, meter);
return meter;
}
@@ -273,7 +277,8 @@ static int calcColumnWidthCount(const Header* this, const Meter* curMeter, const
}
int Header_calculateHeight(Header* this) {
- const int pad = this->settings->headerMargin ? 2 : 0;
+ const Settings* settings = this->host->settings;
+ const int pad = settings->headerMargin ? 2 : 0;
int maxHeight = pad;
Header_forEachColumn(this, col) {
@@ -286,10 +291,19 @@ int Header_calculateHeight(Header* this) {
}
maxHeight = MAXIMUM(maxHeight, height);
}
- if (this->settings->screenTabs) {
+
+ if (maxHeight == pad) {
+ maxHeight = 0;
+ this->pad = 0;
+ } else {
+ this->pad = pad;
+ }
+
+ if (settings->screenTabs) {
maxHeight++;
}
+
this->height = maxHeight;
- this->pad = pad;
+
return maxHeight;
}
diff --git a/Header.h b/Header.h
index 954d434c..2cc78ab7 100644
--- a/Header.h
+++ b/Header.h
@@ -7,17 +7,17 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include <stddef.h>
+
#include "HeaderLayout.h"
+#include "Machine.h"
#include "Meter.h"
-#include "ProcessList.h"
-#include "Settings.h"
#include "Vector.h"
typedef struct Header_ {
Vector** columns;
- Settings* settings;
- ProcessList* pl;
+ Machine* host;
HeaderLayout headerLayout;
int pad;
int height;
@@ -25,7 +25,7 @@ typedef struct Header_ {
#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, HeaderLayout hLayout);
+Header* Header_new(Machine* host, HeaderLayout hLayout);
void Header_delete(Header* this);
diff --git a/HeaderLayout.h b/HeaderLayout.h
index 1cf7bf7b..c8d51c84 100644
--- a/HeaderLayout.h
+++ b/HeaderLayout.h
@@ -7,8 +7,6 @@ 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>
@@ -19,6 +17,7 @@ in the source distribution for its full text.
typedef enum HeaderLayout_ {
HF_INVALID = -1,
+ HF_ONE_100,
HF_TWO_50_50,
HF_TWO_33_67,
HF_TWO_67_33,
@@ -26,6 +25,9 @@ typedef enum HeaderLayout_ {
HF_THREE_25_25_50,
HF_THREE_25_50_25,
HF_THREE_50_25_25,
+ HF_THREE_40_30_30,
+ HF_THREE_30_40_30,
+ HF_THREE_30_30_40,
HF_THREE_40_20_40,
HF_FOUR_25_25_25_25,
LAST_HEADER_LAYOUT
@@ -37,6 +39,7 @@ static const struct {
const char* name;
const char* description;
} HeaderLayout_layouts[LAST_HEADER_LAYOUT] = {
+ [HF_ONE_100] = { 1, { 100, 0, 0, 0 }, "one_100", "1 column - full width", },
[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", },
@@ -44,6 +47,9 @@ static const struct {
[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_30_30] = { 3, { 40, 30, 30, 0 }, "three_40_30_30", "3 columns - 40/30/30", },
+ [HF_THREE_30_40_30] = { 3, { 30, 40, 30, 0 }, "three_30_40_30", "3 columns - 30/40/30", },
+ [HF_THREE_30_30_40] = { 3, { 30, 30, 40, 0 }, "three_30_30_40", "3 columns - 30/30/40", },
[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", },
};
diff --git a/HeaderOptionsPanel.c b/HeaderOptionsPanel.c
index 25d1ddbb..7b5c81be 100644
--- a/HeaderOptionsPanel.c
+++ b/HeaderOptionsPanel.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 "HeaderOptionsPanel.h"
#include <assert.h>
@@ -33,30 +35,30 @@ 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;
- this->settings->lastUpdate++;
-
- ScreenManager_resize(this->scr);
-
- result = HANDLED;
+ case 0x0a:
+ case 0x0d:
+ case KEY_ENTER:
+ case KEY_MOUSE:
+ case KEY_RECLICK:
+ case ' ': {
+ int 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;
+ this->settings->lastUpdate++;
+
+ ScreenManager_resize(this->scr);
+
+ result = HANDLED;
+ }
}
return result;
diff --git a/IncSet.h b/IncSet.h
index 15b5d5d2..a84407e5 100644
--- a/IncSet.h
+++ b/IncSet.h
@@ -15,7 +15,7 @@ in the source distribution for its full text.
#include "Vector.h"
-#define INCMODE_MAX 40
+#define INCMODE_MAX 128
typedef enum {
INC_SEARCH = 0,
diff --git a/InfoScreen.c b/InfoScreen.c
index 105d9c34..c602cd46 100644
--- a/InfoScreen.c
+++ b/InfoScreen.c
@@ -1,3 +1,11 @@
+/*
+htop - InfoScreen.c
+(C) 2016 Hisham H. Muhammad
+(C) 2020,2022 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
#include "config.h" // IWYU pragma: keep
#include "InfoScreen.h"
@@ -130,48 +138,48 @@ void InfoScreen_run(InfoScreen* this) {
}
switch (ch) {
- case ERR:
- continue;
- case KEY_F(3):
- case '/':
- IncSet_activate(this->inc, INC_SEARCH, panel);
- break;
- case KEY_F(4):
- case '\\':
- IncSet_activate(this->inc, INC_FILTER, panel);
- break;
- case KEY_F(5):
- clear();
- if (As_InfoScreen(this)->scan) {
- Vector_prune(this->lines);
- InfoScreen_scan(this);
- }
+ case ERR:
+ continue;
+ case KEY_F(3):
+ case '/':
+ IncSet_activate(this->inc, INC_SEARCH, panel);
+ break;
+ case KEY_F(4):
+ case '\\':
+ IncSet_activate(this->inc, INC_FILTER, panel);
+ break;
+ case KEY_F(5):
+ clear();
+ if (As_InfoScreen(this)->scan) {
+ Vector_prune(this->lines);
+ InfoScreen_scan(this);
+ }
- InfoScreen_draw(this);
- break;
- case '\014': // Ctrl+L
- clear();
- InfoScreen_draw(this);
- break;
- case 27:
- case 'q':
- case KEY_F(10):
- looping = false;
- break;
- case KEY_RESIZE:
- Panel_resize(panel, COLS, LINES - 2);
- if (As_InfoScreen(this)->scan) {
- Vector_prune(this->lines);
- InfoScreen_scan(this);
- }
+ InfoScreen_draw(this);
+ break;
+ case '\014': // Ctrl+L
+ clear();
+ InfoScreen_draw(this);
+ break;
+ case 27:
+ case 'q':
+ case KEY_F(10):
+ looping = false;
+ break;
+ case KEY_RESIZE:
+ Panel_resize(panel, COLS, LINES - 2);
+ if (As_InfoScreen(this)->scan) {
+ Vector_prune(this->lines);
+ InfoScreen_scan(this);
+ }
- InfoScreen_draw(this);
- break;
- default:
- if (As_InfoScreen(this)->onKey && InfoScreen_onKey(this, ch)) {
- continue;
- }
- Panel_onKey(panel, ch);
+ InfoScreen_draw(this);
+ break;
+ default:
+ if (As_InfoScreen(this)->onKey && InfoScreen_onKey(this, ch)) {
+ continue;
+ }
+ Panel_onKey(panel, ch);
}
}
}
diff --git a/InfoScreen.h b/InfoScreen.h
index d7497bed..469c2707 100644
--- a/InfoScreen.h
+++ b/InfoScreen.h
@@ -1,5 +1,12 @@
#ifndef HEADER_InfoScreen
#define HEADER_InfoScreen
+/*
+htop - InfoScreen.h
+(C) 2016 Hisham H. Muhammad
+(C) 2020,2022 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
#include <stdbool.h>
diff --git a/ListItem.h b/ListItem.h
index b2c3e061..5efe8743 100644
--- a/ListItem.h
+++ b/ListItem.h
@@ -10,6 +10,7 @@ in the source distribution for its full text.
#include <stdbool.h>
#include "Object.h"
+#include "RichString.h"
typedef struct ListItem_ {
diff --git a/LoadAverageMeter.c b/LoadAverageMeter.c
index 3fe3d900..30c58bb9 100644
--- a/LoadAverageMeter.c
+++ b/LoadAverageMeter.c
@@ -5,12 +5,14 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "LoadAverageMeter.h"
#include "CRT.h"
+#include "Machine.h"
#include "Object.h"
#include "Platform.h"
-#include "ProcessList.h"
#include "RichString.h"
#include "XUtils.h"
@@ -47,12 +49,12 @@ static void LoadAverageMeter_updateValues(Meter* this) {
if (this->values[0] < 1.0) {
this->curAttributes = OK_attributes;
this->total = 1.0;
- } else if (this->values[0] < this->pl->activeCPUs) {
+ } else if (this->values[0] < this->host->activeCPUs) {
this->curAttributes = Medium_attributes;
- this->total = this->pl->activeCPUs;
+ this->total = this->host->activeCPUs;
} else {
this->curAttributes = High_attributes;
- this->total = 2 * this->pl->activeCPUs;
+ this->total = 2 * this->host->activeCPUs;
}
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.2f/%.2f/%.2f", this->values[0], this->values[1], this->values[2]);
@@ -79,12 +81,12 @@ static void LoadMeter_updateValues(Meter* this) {
if (this->values[0] < 1.0) {
this->curAttributes = OK_attributes;
this->total = 1.0;
- } else if (this->values[0] < this->pl->activeCPUs) {
+ } else if (this->values[0] < this->host->activeCPUs) {
this->curAttributes = Medium_attributes;
- this->total = this->pl->activeCPUs;
+ this->total = this->host->activeCPUs;
} else {
this->curAttributes = High_attributes;
- this->total = 2 * this->pl->activeCPUs;
+ this->total = 2 * this->host->activeCPUs;
}
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.2f", this->values[0]);
diff --git a/Machine.c b/Machine.c
new file mode 100644
index 00000000..2c66571e
--- /dev/null
+++ b/Machine.c
@@ -0,0 +1,129 @@
+/*
+htop - Machine.c
+(C) 2023 Red Hat, Inc.
+(C) 2004,2005 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "Machine.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "Object.h"
+#include "Platform.h"
+#include "Row.h"
+#include "XUtils.h"
+
+
+void Machine_init(Machine* this, UsersTable* usersTable, uid_t userId) {
+ this->usersTable = usersTable;
+ this->userId = userId;
+
+ this->htopUserId = getuid();
+
+ // discover fixed column width limits
+ Row_setPidColumnWidth(Platform_getMaxPid());
+
+ // always maintain valid realtime timestamps
+ Platform_gettime_realtime(&this->realtime, &this->realtimeMs);
+
+#ifdef HAVE_LIBHWLOC
+ this->topologyOk = false;
+ if (hwloc_topology_init(&this->topology) == 0) {
+ this->topologyOk =
+ #if HWLOC_API_VERSION < 0x00020000
+ /* try to ignore the top-level machine object type */
+ 0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_MACHINE) &&
+ /* ignore caches, which don't add structure */
+ 0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_CORE) &&
+ 0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_CACHE) &&
+ 0 == hwloc_topology_set_flags(this->topology, HWLOC_TOPOLOGY_FLAG_WHOLE_SYSTEM) &&
+ #else
+ 0 == hwloc_topology_set_all_types_filter(this->topology, HWLOC_TYPE_FILTER_KEEP_STRUCTURE) &&
+ #endif
+ 0 == hwloc_topology_load(this->topology);
+ }
+#endif
+}
+
+void Machine_done(Machine* this) {
+#ifdef HAVE_LIBHWLOC
+ if (this->topologyOk) {
+ hwloc_topology_destroy(this->topology);
+ }
+#endif
+ Object_delete(this->processTable);
+ free(this->tables);
+}
+
+static void Machine_addTable(Machine* this, Table* table) {
+ /* check that this table has not been seen previously */
+ for (size_t i = 0; i < this->tableCount; i++)
+ if (this->tables[i] == table)
+ return;
+
+ size_t nmemb = this->tableCount + 1;
+ Table** tables = xReallocArray(this->tables, nmemb, sizeof(Table*));
+ tables[nmemb - 1] = table;
+ this->tables = tables;
+ this->tableCount++;
+}
+
+void Machine_populateTablesFromSettings(Machine* this, Settings* settings, Table* processTable) {
+ this->settings = settings;
+ this->processTable = processTable;
+
+ for (size_t i = 0; i < settings->nScreens; i++) {
+ ScreenSettings* ss = settings->screens[i];
+ Table* table = ss->table;
+ if (!table)
+ table = ss->table = processTable;
+ if (i == 0)
+ this->activeTable = table;
+
+ Machine_addTable(this, table);
+ }
+}
+
+void Machine_setTablesPanel(Machine* this, Panel* panel) {
+ for (size_t i = 0; i < this->tableCount; i++) {
+ Table_setPanel(this->tables[i], panel);
+ }
+}
+
+void Machine_scanTables(Machine* this) {
+ // set scan timestamp
+ static bool firstScanDone = false;
+
+ if (firstScanDone) {
+ this->prevMonotonicMs = this->monotonicMs;
+ Platform_gettime_monotonic(&this->monotonicMs);
+ } else {
+ this->prevMonotonicMs = 0;
+ this->monotonicMs = 1;
+ firstScanDone = true;
+ }
+ assert(this->monotonicMs > this->prevMonotonicMs);
+
+ this->maxUserId = 0;
+ Row_resetFieldWidths();
+
+ for (size_t i = 0; i < this->tableCount; i++) {
+ Table* table = this->tables[i];
+
+ // pre-processing of each row
+ Table_scanPrepare(table);
+
+ // scan values for this table
+ Table_scanIterate(table);
+
+ // post-process after scanning
+ Table_scanCleanup(table);
+ }
+
+ Row_setUidColumnWidth(this->maxUserId);
+}
diff --git a/Machine.h b/Machine.h
new file mode 100644
index 00000000..247f7336
--- /dev/null
+++ b/Machine.h
@@ -0,0 +1,97 @@
+#ifndef HEADER_Machine
+#define HEADER_Machine
+/*
+htop - Machine.h
+(C) 2023 Red Hat, Inc.
+(C) 2004,2005 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "Panel.h"
+#include "Settings.h"
+#include "Table.h"
+#include "UsersTable.h"
+
+#ifdef HAVE_LIBHWLOC
+#include <hwloc.h>
+#endif
+
+
+#ifndef MAX_NAME
+#define MAX_NAME 128
+#endif
+
+#ifndef MAX_READ
+#define MAX_READ 2048
+#endif
+
+typedef unsigned long long int memory_t;
+#define MEMORY_MAX ULLONG_MAX
+
+typedef struct Machine_ {
+ struct Settings_* settings;
+
+ struct timeval realtime; /* time of the current sample */
+ uint64_t realtimeMs; /* current time in milliseconds */
+ uint64_t monotonicMs; /* same, but from monotonic clock */
+ uint64_t prevMonotonicMs; /* time in milliseconds from monotonic clock of previous scan */
+
+ int64_t iterationsRemaining;
+
+ #ifdef HAVE_LIBHWLOC
+ hwloc_topology_t topology;
+ bool topologyOk;
+ #endif
+
+ memory_t totalMem;
+ memory_t usedMem;
+ memory_t buffersMem;
+ memory_t cachedMem;
+ memory_t sharedMem;
+ memory_t availableMem;
+
+ memory_t totalSwap;
+ memory_t usedSwap;
+ memory_t cachedSwap;
+
+ unsigned int activeCPUs;
+ unsigned int existingCPUs;
+
+ UsersTable* usersTable;
+ uid_t htopUserId;
+ uid_t maxUserId; /* recently observed */
+ uid_t userId; /* selected row user ID */
+
+ size_t tableCount;
+ Table **tables;
+ Table *activeTable;
+ Table *processTable;
+} Machine;
+
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId);
+
+void Machine_init(Machine* this, UsersTable* usersTable, uid_t userId);
+
+void Machine_delete(Machine* this);
+
+void Machine_done(Machine* this);
+
+bool Machine_isCPUonline(const Machine* this, unsigned int id);
+
+void Machine_populateTablesFromSettings(Machine* this, Settings* settings, Table* processTable);
+
+void Machine_setTablesPanel(Machine* this, Panel* panel);
+
+void Machine_scan(Machine* this);
+
+void Machine_scanTables(Machine* this);
+
+#endif
diff --git a/Macros.h b/Macros.h
index 5e8891a4..051bedbc 100644
--- a/Macros.h
+++ b/Macros.h
@@ -1,7 +1,17 @@
#ifndef HEADER_Macros
#define HEADER_Macros
+/*
+htop - Macros.h
+(C) 2020-2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
#include <assert.h> // IWYU pragma: keep
+#include <math.h>
+#include <stdbool.h>
+#include <string.h> // IWYU pragma: keep
+
#ifndef MINIMUM
#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
@@ -34,7 +44,6 @@
#ifdef __GNUC__ // defined by GCC and Clang
#define ATTR_FORMAT(type, index, check) __attribute__((format (type, index, check)))
-#define ATTR_NONNULL __attribute__((nonnull))
#define ATTR_NORETURN __attribute__((noreturn))
#define ATTR_UNUSED __attribute__((unused))
#define ATTR_MALLOC __attribute__((malloc))
@@ -42,13 +51,24 @@
#else /* __GNUC__ */
#define ATTR_FORMAT(type, index, check)
-#define ATTR_NONNULL
#define ATTR_NORETURN
#define ATTR_UNUSED
#define ATTR_MALLOC
#endif /* __GNUC__ */
+#ifdef HAVE_ATTR_NONNULL
+
+#define ATTR_NONNULL __attribute__((nonnull))
+#define ATTR_NONNULL_N(...) __attribute__((nonnull(__VA_ARGS__)))
+
+#else
+
+#define ATTR_NONNULL
+#define ATTR_NONNULL_N(...)
+
+#endif /* HAVE_ATTR_NONNULL */
+
#ifdef HAVE_ATTR_ALLOC_SIZE
#define ATTR_ALLOC_SIZE1(a) __attribute__((alloc_size (a)))
@@ -61,6 +81,27 @@
#endif /* HAVE_ATTR_ALLOC_SIZE */
+#ifdef HAVE_ATTR_ACCESS
+
+#define ATTR_ACCESS2(mode, ref) __attribute__((access (mode, ref)))
+#define ATTR_ACCESS3(mode, ref, size) __attribute__((access (mode, ref, size)))
+
+#else
+
+#define ATTR_ACCESS2(mode, ref)
+#define ATTR_ACCESS3(mode, ref, size)
+
+#endif /* HAVE_ATTR_ACCESS */
+
+#define ATTR_ACCESS2_R(ref) ATTR_ACCESS2(read_only, ref)
+#define ATTR_ACCESS3_R(ref, size) ATTR_ACCESS3(read_only, ref, size)
+
+#define ATTR_ACCESS2_RW(ref) ATTR_ACCESS2(read_write, ref)
+#define ATTR_ACCESS3_RW(ref, size) ATTR_ACCESS3(read_write, ref, size)
+
+#define ATTR_ACCESS2_W(ref) ATTR_ACCESS2(write_only, ref)
+#define ATTR_ACCESS3_W(ref, size) ATTR_ACCESS3(write_only, ref, size)
+
// ignore casts discarding const specifier, e.g.
// const char [] -> char * / void *
// const char *[2]' -> char *const *
@@ -77,6 +118,25 @@
#define IGNORE_WCASTQUAL_END
#endif
+/* Cheaper function for checking NaNs. Unlike the standard isnan(), this may
+ throw an FP exception on a "signaling NaN".
+ (ISO/IEC TS 18661-1 and the C23 standard stated that isnan() throws no
+ exceptions even with a "signaling NaN") */
+static inline bool isNaN(double x) {
+ return !isgreaterequal(x, x);
+}
+
+/* Checks if x >= 0.0 but returns false if x is NaN. Because IEEE 754 considers
+ -0.0 == 0.0, this function treats both zeros as nonnegative. */
+static inline bool isNonnegative(double x) {
+ return isgreaterequal(x, 0.0);
+}
+
+/* Checks if x > 0.0 but returns false if x is NaN. */
+static inline bool isPositive(double x) {
+ return isgreater(x, 0.0);
+}
+
/* 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;
diff --git a/MainPanel.c b/MainPanel.c
index 44915dfd..47b2f92f 100644
--- a/MainPanel.c
+++ b/MainPanel.c
@@ -6,18 +6,23 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "MainPanel.h"
#include <ctype.h>
#include <stdlib.h>
+#include <sys/types.h>
#include "CRT.h"
#include "FunctionBar.h"
+#include "Machine.h"
#include "Platform.h"
-#include "Process.h"
-#include "ProcessList.h"
#include "ProvideCurses.h"
+#include "Row.h"
+#include "RowField.h"
#include "Settings.h"
+#include "Table.h"
#include "XUtils.h"
@@ -30,33 +35,32 @@ void MainPanel_updateLabels(MainPanel* this, bool list, bool filter) {
FunctionBar_setLabel(bar, KEY_F(4), filter ? "FILTER" : "Filter");
}
-static void MainPanel_pidSearch(MainPanel* this, int ch) {
+static void MainPanel_idSearch(MainPanel* this, int ch) {
Panel* super = (Panel*) this;
- pid_t pid = ch - 48 + this->pidSearch;
+ pid_t id = ch - 48 + this->idSearch;
for (int i = 0; i < Panel_size(super); i++) {
- const Process* p = (const Process*) Panel_get(super, i);
- if (p && p->pid == pid) {
+ const Row* row = (const Row*) Panel_get(super, i);
+ if (row && row->id == id) {
Panel_setSelected(super, i);
break;
}
}
- this->pidSearch = pid * 10;
- if (this->pidSearch > 10000000) {
- this->pidSearch = 0;
+ this->idSearch = id * 10;
+ if (this->idSearch > 10000000) {
+ this->idSearch = 0;
}
}
static const char* MainPanel_getValue(Panel* this, int i) {
- const Process* p = (const Process*) Panel_get(this, i);
- return Process_getCommand(p);
+ Row* row = (Row*) Panel_get(this, i);
+ return Row_sortKeyString(row);
}
static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
MainPanel* this = (MainPanel*) super;
-
- HandlerResult result = IGNORED;
-
+ Machine* host = this->state->host;
Htop_Reaction reaction = HTOP_OK;
+ HandlerResult result = IGNORED;
/* Let supervising ScreenManager handle resize */
if (ch == KEY_RESIZE)
@@ -66,20 +70,19 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
bool needReset = ch != ERR;
#ifdef HAVE_GETMOUSE
/* except mouse events while mouse support is disabled */
- if (!(ch != KEY_MOUSE || this->state->settings->enableMouse))
+ if (!(ch != KEY_MOUSE || host->settings->enableMouse))
needReset = false;
#endif
if (needReset)
- this->state->hideProcessSelection = false;
+ this->state->hideSelection = false;
- Settings* settings = this->state->settings;
+ Settings* settings = host->settings;
ScreenSettings* ss = settings->ss;
if (EVENT_IS_HEADER_CLICK(ch)) {
int x = EVENT_HEADER_CLICK_GET_X(ch);
- const ProcessList* pl = this->state->pl;
int hx = super->scrollH + x + 1;
- ProcessField field = ProcessList_keyAt(pl, hx);
+ RowField field = RowField_keyAt(settings, hx);
if (ss->treeView && ss->treeViewAlwaysByPID) {
ss->treeView = false;
ss->direction = 1;
@@ -89,16 +92,16 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
} else {
reaction |= Action_setSortKey(settings, field);
}
- reaction |= HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_SAVE_SETTINGS;
+ reaction |= HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR | HTOP_SAVE_SETTINGS;
result = HANDLED;
} else if (EVENT_IS_SCREEN_TAB_CLICK(ch)) {
int x = EVENT_SCREEN_TAB_GET_X(ch);
- reaction |= Action_setScreenTab(settings, x);
+ reaction |= Action_setScreenTab(this->state, x);
result = HANDLED;
} else if (ch != ERR && this->inc->active) {
bool filterChanged = IncSet_handleKey(this->inc, ch, super, MainPanel_getValue, NULL);
if (filterChanged) {
- this->state->pl->incFilter = IncSet_filter(this->inc);
+ host->activeTable->incFilter = IncSet_filter(this->inc);
reaction = HTOP_REFRESH | HTOP_REDRAW_BAR;
}
if (this->inc->found) {
@@ -107,72 +110,69 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
}
result = HANDLED;
} else if (ch == 27) {
- this->state->hideProcessSelection = true;
+ this->state->hideSelection = true;
return HANDLED;
} else if (ch != ERR && ch > 0 && ch < KEY_MAX && this->keys[ch]) {
reaction |= (this->keys[ch])(this->state);
result = HANDLED;
} else if (0 < ch && ch < 255 && isdigit((unsigned char)ch)) {
- MainPanel_pidSearch(this, ch);
+ MainPanel_idSearch(this, ch);
} else {
if (ch != ERR) {
- this->pidSearch = 0;
+ this->idSearch = 0;
} else {
reaction |= HTOP_KEEP_FOLLOWING;
}
}
- if (reaction & HTOP_REDRAW_BAR) {
- MainPanel_updateLabels(this, settings->ss->treeView, this->state->pl->incFilter);
+ if ((reaction & HTOP_REDRAW_BAR) == HTOP_REDRAW_BAR) {
+ MainPanel_updateLabels(this, settings->ss->treeView, host->activeTable->incFilter);
}
- if (reaction & HTOP_RESIZE) {
+ if ((reaction & HTOP_RESIZE) == HTOP_RESIZE) {
result |= RESIZE;
}
- if (reaction & HTOP_UPDATE_PANELHDR) {
+ if ((reaction & HTOP_UPDATE_PANELHDR) == HTOP_UPDATE_PANELHDR) {
result |= REDRAW;
}
- if (reaction & HTOP_REFRESH) {
+ if ((reaction & HTOP_REFRESH) == HTOP_REFRESH) {
result |= REFRESH;
}
- if (reaction & HTOP_RECALCULATE) {
+ if ((reaction & HTOP_RECALCULATE) == HTOP_RECALCULATE) {
result |= RESCAN;
}
- if (reaction & HTOP_SAVE_SETTINGS) {
- this->state->settings->changed = true;
+ if ((reaction & HTOP_SAVE_SETTINGS) == HTOP_SAVE_SETTINGS) {
+ host->settings->changed = true;
}
- if (reaction & HTOP_QUIT) {
+ if ((reaction & HTOP_QUIT) == HTOP_QUIT) {
return BREAK_LOOP;
}
- if (!(reaction & HTOP_KEEP_FOLLOWING)) {
- this->state->pl->following = -1;
+ if ((reaction & HTOP_KEEP_FOLLOWING) != HTOP_KEEP_FOLLOWING) {
+ host->activeTable->following = -1;
Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
}
return result;
}
-int MainPanel_selectedPid(MainPanel* this) {
- const Process* p = (const Process*) Panel_getSelected((Panel*)this);
- if (p) {
- return p->pid;
- }
- return -1;
+int MainPanel_selectedRow(MainPanel* this) {
+ const Row* row = (const Row*) Panel_getSelected((Panel*)this);
+ return row ? row->id : -1;
}
-bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged) {
+bool MainPanel_foreachRow(MainPanel* this, MainPanel_foreachRowFn fn, Arg arg, bool* wasAnyTagged) {
Panel* super = (Panel*) this;
bool ok = true;
bool anyTagged = false;
for (int i = 0; i < Panel_size(super); i++) {
- Process* p = (Process*) Panel_get(super, i);
- if (p->tag) {
- ok = fn(p, arg) && ok;
+ Row* row = (Row*) Panel_get(super, i);
+ if (row->tag) {
+ ok &= fn(row, arg);
anyTagged = true;
}
}
if (!anyTagged) {
- Process* p = (Process*) Panel_getSelected(super);
- if (p) {
- ok &= fn(p, arg);
+ Row* row = (Row*) Panel_getSelected(super);
+ if (row) {
+ ok &= fn(row, arg);
}
}
@@ -190,14 +190,15 @@ static void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) {
return;
IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
- if (this->state->pauseProcessUpdate) {
+ if (this->state->pauseUpdate) {
FunctionBar_append("PAUSED", CRT_colors[PAUSED]);
}
}
static void MainPanel_printHeader(Panel* super) {
MainPanel* this = (MainPanel*) super;
- ProcessList_printHeader(this->state->pl, &super->header);
+ Machine* host = this->state->host;
+ Table_printHeader(host->settings, &super->header);
}
const PanelClass MainPanel_class = {
@@ -210,11 +211,14 @@ const PanelClass MainPanel_class = {
.printHeader = MainPanel_printHeader
};
-MainPanel* MainPanel_new() {
+MainPanel* MainPanel_new(void) {
MainPanel* this = AllocThis(MainPanel);
- Panel_init((Panel*) this, 1, 1, 1, 1, Class(Process), false, FunctionBar_new(Settings_isReadonly() ? MainFunctions_ro : MainFunctions, NULL, NULL));
+ this->processBar = FunctionBar_new(MainFunctions, NULL, NULL);
+ this->readonlyBar = FunctionBar_new(MainFunctions_ro, NULL, NULL);
+ FunctionBar* activeBar = Settings_isReadonly() ? this->readonlyBar : this->processBar;
+ Panel_init((Panel*) this, 1, 1, 1, 1, Class(Row), false, activeBar);
this->keys = xCalloc(KEY_MAX, sizeof(Htop_Action));
- this->inc = IncSet_new(MainPanel_getFunctionBar(this));
+ this->inc = IncSet_new(activeBar);
Action_setBindings(this->keys);
Platform_setBindings(this->keys);
@@ -226,9 +230,16 @@ void MainPanel_setState(MainPanel* this, State* state) {
this->state = state;
}
+void MainPanel_setFunctionBar(MainPanel* this, bool readonly) {
+ this->super.defaultBar = readonly ? this->readonlyBar : this->processBar;
+ this->inc->defaultBar = this->super.defaultBar;
+}
+
void MainPanel_delete(Object* object) {
Panel* super = (Panel*) object;
MainPanel* this = (MainPanel*) object;
+ MainPanel_setFunctionBar(this, false);
+ FunctionBar_delete(this->readonlyBar);
Panel_done(super);
IncSet_delete(this->inc);
free(this->keys);
diff --git a/MainPanel.h b/MainPanel.h
index bd22acd0..105b46de 100644
--- a/MainPanel.h
+++ b/MainPanel.h
@@ -8,16 +8,14 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <stdbool.h>
-#include <sys/types.h>
#include "Action.h"
+#include "FunctionBar.h"
#include "IncSet.h"
#include "Object.h"
#include "Panel.h"
-#include "Process.h"
+#include "Row.h"
typedef struct MainPanel_ {
@@ -25,19 +23,21 @@ typedef struct MainPanel_ {
State* state;
IncSet* inc;
Htop_Action* keys;
- pid_t pidSearch;
+ FunctionBar* processBar; /* function bar with process-specific actions */
+ FunctionBar* readonlyBar; /* function bar without process actions (ro) */
+ unsigned int idSearch;
} MainPanel;
-typedef bool(*MainPanel_ForeachProcessFn)(Process*, Arg);
+typedef bool(*MainPanel_foreachRowFn)(Row*, Arg);
#define MainPanel_getFunctionBar(this_) (((Panel*)(this_))->defaultBar)
// update the Label Keys in the MainPanel bar, list: list / tree mode, filter: filter (inc) active / inactive
void MainPanel_updateLabels(MainPanel* this, bool list, bool filter);
-int MainPanel_selectedPid(MainPanel* this);
+int MainPanel_selectedRow(MainPanel* this);
-bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged);
+bool MainPanel_foreachRow(MainPanel* this, MainPanel_foreachRowFn fn, Arg arg, bool* wasAnyTagged);
extern const PanelClass MainPanel_class;
@@ -45,6 +45,8 @@ MainPanel* MainPanel_new(void);
void MainPanel_setState(MainPanel* this, State* state);
+void MainPanel_setFunctionBar(MainPanel* this, bool readonly);
+
void MainPanel_delete(Object* object);
#endif
diff --git a/Makefile.am b/Makefile.am
index 8af1864a..2580252b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,7 +24,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 += -D_XOPEN_SOURCE_EXTENDED -DSYSCONFDIR="\"$(sysconfdir)\"" -I"$(top_srcdir)/$(my_htop_platform)"
AM_LDFLAGS =
myhtopsources = \
@@ -49,7 +49,9 @@ myhtopsources = \
DisplayOptionsPanel.c \
DynamicColumn.c \
DynamicMeter.c \
+ DynamicScreen.c \
EnvScreen.c \
+ FileDescriptorMeter.c \
FunctionBar.c \
Hashtable.c \
Header.c \
@@ -59,6 +61,7 @@ myhtopsources = \
InfoScreen.c \
ListItem.c \
LoadAverageMeter.c \
+ Machine.c \
MainPanel.c \
MemoryMeter.c \
MemorySwapMeter.c \
@@ -70,15 +73,19 @@ myhtopsources = \
OptionItem.c \
Panel.c \
Process.c \
- ProcessList.c \
ProcessLocksScreen.c \
+ ProcessTable.c \
+ Row.c \
RichString.c \
+ Scheduling.c \
ScreenManager.c \
ScreensPanel.c \
+ ScreenTabsPanel.c \
Settings.c \
SignalsPanel.c \
SwapMeter.c \
SysArchMeter.c \
+ Table.c \
TasksMeter.c \
TraceScreen.c \
UptimeMeter.c \
@@ -108,7 +115,9 @@ myhtopheaders = \
DisplayOptionsPanel.h \
DynamicColumn.h \
DynamicMeter.h \
+ DynamicScreen.h \
EnvScreen.h \
+ FileDescriptorMeter.h \
FunctionBar.h \
Hashtable.h \
Header.h \
@@ -119,11 +128,13 @@ myhtopheaders = \
InfoScreen.h \
ListItem.h \
LoadAverageMeter.h \
+ Machine.h \
Macros.h \
MainPanel.h \
MemoryMeter.h \
MemorySwapMeter.h \
Meter.h \
+ MeterMode.h \
MetersPanel.h \
NetworkIOMeter.h \
Object.h \
@@ -131,16 +142,22 @@ myhtopheaders = \
OptionItem.h \
Panel.h \
Process.h \
- ProcessList.h \
ProcessLocksScreen.h \
+ ProcessTable.h \
ProvideCurses.h \
+ ProvideTerm.h \
RichString.h \
+ Row.h \
+ RowField.h \
+ Scheduling.h \
ScreenManager.h \
ScreensPanel.h \
+ ScreenTabsPanel.h \
Settings.h \
SignalsPanel.h \
SwapMeter.h \
SysArchMeter.h \
+ Table.h \
TasksMeter.h \
TraceScreen.h \
UptimeMeter.h \
@@ -156,12 +173,15 @@ linux_platform_headers = \
generic/hostname.h \
generic/uname.h \
linux/CGroupUtils.h \
+ linux/GPU.h \
+ linux/GPUMeter.h \
linux/HugePageMeter.h \
linux/IOPriority.h \
linux/IOPriorityPanel.h \
linux/LibSensors.h \
+ linux/LinuxMachine.h \
linux/LinuxProcess.h \
- linux/LinuxProcessList.h \
+ linux/LinuxProcessTable.h \
linux/Platform.h \
linux/PressureStallMeter.h \
linux/ProcessField.h \
@@ -169,6 +189,7 @@ linux_platform_headers = \
linux/SystemdMeter.h \
linux/ZramMeter.h \
linux/ZramStats.h \
+ linux/ZswapStats.h \
zfs/ZfsArcMeter.h \
zfs/ZfsArcStats.h \
zfs/ZfsCompressedArcMeter.h
@@ -178,11 +199,14 @@ linux_platform_sources = \
generic/hostname.c \
generic/uname.c \
linux/CGroupUtils.c \
+ linux/GPU.c \
+ linux/GPUMeter.c \
linux/HugePageMeter.c \
linux/IOPriorityPanel.c \
linux/LibSensors.c \
+ linux/LinuxMachine.c \
linux/LinuxProcess.c \
- linux/LinuxProcessList.c \
+ linux/LinuxProcessTable.c \
linux/Platform.c \
linux/PressureStallMeter.c \
linux/SELinuxMeter.c \
@@ -191,6 +215,11 @@ linux_platform_sources = \
zfs/ZfsArcMeter.c \
zfs/ZfsCompressedArcMeter.c
+if HAVE_DELAYACCT
+linux_platform_headers += linux/LibNl.h
+linux_platform_sources += linux/LibNl.c
+endif
+
if HTOP_LINUX
AM_LDFLAGS += -rdynamic
myhtopplatheaders = $(linux_platform_headers)
@@ -201,10 +230,12 @@ endif
# -------
freebsd_platform_headers = \
- freebsd/FreeBSDProcessList.h \
+ freebsd/FreeBSDMachine.h \
+ freebsd/FreeBSDProcessTable.h \
freebsd/FreeBSDProcess.h \
freebsd/Platform.h \
freebsd/ProcessField.h \
+ generic/fdstat_sysctl.h \
generic/gettime.h \
generic/hostname.h \
generic/openzfs_sysctl.h \
@@ -215,8 +246,10 @@ freebsd_platform_headers = \
freebsd_platform_sources = \
freebsd/Platform.c \
- freebsd/FreeBSDProcessList.c \
+ freebsd/FreeBSDMachine.c \
+ freebsd/FreeBSDProcessTable.c \
freebsd/FreeBSDProcess.c \
+ generic/fdstat_sysctl.c \
generic/gettime.c \
generic/hostname.c \
generic/openzfs_sysctl.c \
@@ -233,18 +266,22 @@ endif
# ------------
dragonflybsd_platform_headers = \
- dragonflybsd/DragonFlyBSDProcessList.h \
+ dragonflybsd/DragonFlyBSDMachine.h \
+ dragonflybsd/DragonFlyBSDProcessTable.h \
dragonflybsd/DragonFlyBSDProcess.h \
dragonflybsd/Platform.h \
dragonflybsd/ProcessField.h \
+ generic/fdstat_sysctl.h \
generic/gettime.h \
generic/hostname.h \
generic/uname.h
dragonflybsd_platform_sources = \
- dragonflybsd/DragonFlyBSDProcessList.c \
+ dragonflybsd/DragonFlyBSDMachine.c \
+ dragonflybsd/DragonFlyBSDProcessTable.c \
dragonflybsd/DragonFlyBSDProcess.c \
dragonflybsd/Platform.c \
+ generic/fdstat_sysctl.c \
generic/gettime.c \
generic/hostname.c \
generic/uname.c
@@ -258,21 +295,25 @@ endif
# -------
netbsd_platform_headers = \
+ generic/fdstat_sysctl.h \
generic/gettime.h \
generic/hostname.h \
generic/uname.h \
netbsd/Platform.h \
netbsd/ProcessField.h \
+ netbsd/NetBSDMachine.h \
netbsd/NetBSDProcess.h \
- netbsd/NetBSDProcessList.h
+ netbsd/NetBSDProcessTable.h
netbsd_platform_sources = \
+ generic/fdstat_sysctl.c \
generic/gettime.c \
generic/hostname.c \
generic/uname.c \
netbsd/Platform.c \
+ netbsd/NetBSDMachine.c \
netbsd/NetBSDProcess.c \
- netbsd/NetBSDProcessList.c
+ netbsd/NetBSDProcessTable.c
if HTOP_NETBSD
myhtopplatheaders = $(netbsd_platform_headers)
@@ -286,7 +327,8 @@ openbsd_platform_headers = \
generic/gettime.h \
generic/hostname.h \
generic/uname.h \
- openbsd/OpenBSDProcessList.h \
+ openbsd/OpenBSDMachine.h \
+ openbsd/OpenBSDProcessTable.h \
openbsd/OpenBSDProcess.h \
openbsd/Platform.h \
openbsd/ProcessField.h
@@ -295,7 +337,8 @@ openbsd_platform_sources = \
generic/gettime.c \
generic/hostname.c \
generic/uname.c \
- openbsd/OpenBSDProcessList.c \
+ openbsd/OpenBSDMachine.c \
+ openbsd/OpenBSDProcessTable.c \
openbsd/OpenBSDProcess.c \
openbsd/Platform.c
@@ -308,11 +351,13 @@ endif
# ------
darwin_platform_headers = \
+ darwin/DarwinMachine.h \
darwin/DarwinProcess.h \
- darwin/DarwinProcessList.h \
+ darwin/DarwinProcessTable.h \
darwin/Platform.h \
darwin/PlatformHelpers.h \
darwin/ProcessField.h \
+ generic/fdstat_sysctl.h \
generic/gettime.h \
generic/hostname.h \
generic/openzfs_sysctl.h \
@@ -324,8 +369,10 @@ darwin_platform_headers = \
darwin_platform_sources = \
darwin/Platform.c \
darwin/PlatformHelpers.c \
+ darwin/DarwinMachine.c \
darwin/DarwinProcess.c \
- darwin/DarwinProcessList.c \
+ darwin/DarwinProcessTable.c \
+ generic/fdstat_sysctl.c \
generic/gettime.c \
generic/hostname.c \
generic/openzfs_sysctl.c \
@@ -348,8 +395,9 @@ solaris_platform_headers = \
generic/uname.h \
solaris/ProcessField.h \
solaris/Platform.h \
+ solaris/SolarisMachine.h \
solaris/SolarisProcess.h \
- solaris/SolarisProcessList.h \
+ solaris/SolarisProcessTable.h \
zfs/ZfsArcMeter.h \
zfs/ZfsArcStats.h \
zfs/ZfsCompressedArcMeter.h
@@ -359,8 +407,9 @@ solaris_platform_sources = \
generic/hostname.c \
generic/uname.c \
solaris/Platform.c \
+ solaris/SolarisMachine.c \
solaris/SolarisProcess.c \
- solaris/SolarisProcessList.c \
+ solaris/SolarisProcessTable.c \
zfs/ZfsArcMeter.c \
zfs/ZfsCompressedArcMeter.c
@@ -373,29 +422,39 @@ endif
# --------------------------
pcp_platform_headers = \
+ linux/CGroupUtils.h \
linux/PressureStallMeter.h \
linux/ZramMeter.h \
linux/ZramStats.h \
+ pcp/Instance.h \
+ pcp/InDomTable.h \
+ pcp/Metric.h \
+ pcp/Platform.h \
+ pcp/ProcessField.h \
pcp/PCPDynamicColumn.h \
pcp/PCPDynamicMeter.h \
- pcp/PCPMetric.h \
+ pcp/PCPDynamicScreen.h \
+ pcp/PCPMachine.h \
pcp/PCPProcess.h \
- pcp/PCPProcessList.h \
- pcp/Platform.h \
- pcp/ProcessField.h \
+ pcp/PCPProcessTable.h \
zfs/ZfsArcMeter.h \
zfs/ZfsArcStats.h \
zfs/ZfsCompressedArcMeter.h
pcp_platform_sources = \
+ linux/CGroupUtils.c \
linux/PressureStallMeter.c \
linux/ZramMeter.c \
+ pcp/Instance.c \
+ pcp/InDomTable.c \
+ pcp/Metric.c \
+ pcp/Platform.c \
pcp/PCPDynamicColumn.c \
pcp/PCPDynamicMeter.c \
- pcp/PCPMetric.c \
+ pcp/PCPDynamicScreen.c \
+ pcp/PCPMachine.c \
pcp/PCPProcess.c \
- pcp/PCPProcessList.c \
- pcp/Platform.c \
+ pcp/PCPProcessTable.c \
zfs/ZfsArcMeter.c \
zfs/ZfsCompressedArcMeter.c
@@ -412,14 +471,16 @@ unsupported_platform_headers = \
generic/gettime.h \
unsupported/Platform.h \
unsupported/ProcessField.h \
+ unsupported/UnsupportedMachine.h \
unsupported/UnsupportedProcess.h \
- unsupported/UnsupportedProcessList.h
+ unsupported/UnsupportedProcessTable.h
unsupported_platform_sources = \
generic/gettime.c \
unsupported/Platform.c \
+ unsupported/UnsupportedMachine.c \
unsupported/UnsupportedProcess.c \
- unsupported/UnsupportedProcessList.c
+ unsupported/UnsupportedProcessTable.c
if HTOP_UNSUPPORTED
myhtopplatsources = $(unsupported_platform_sources)
diff --git a/MemoryMeter.c b/MemoryMeter.c
index 89e242ca..573b8983 100644
--- a/MemoryMeter.c
+++ b/MemoryMeter.c
@@ -5,12 +5,16 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "MemoryMeter.h"
+#include <assert.h>
#include <math.h>
#include <stddef.h>
#include "CRT.h"
+#include "Macros.h"
#include "Object.h"
#include "Platform.h"
#include "RichString.h"
@@ -18,8 +22,9 @@ in the source distribution for its full text.
static const int MemoryMeter_attributes[] = {
MEMORY_USED,
- MEMORY_BUFFERS,
MEMORY_SHARED,
+ MEMORY_COMPRESSED,
+ MEMORY_BUFFERS,
MEMORY_CACHE
};
@@ -28,15 +33,25 @@ static void MemoryMeter_updateValues(Meter* this) {
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;
+ /* shared, compressed and available memory are not supported on all platforms */
+ this->values[MEMORY_METER_SHARED] = NAN;
+ this->values[MEMORY_METER_COMPRESSED] = NAN;
+ this->values[MEMORY_METER_AVAILABLE] = NAN;
Platform_setMemoryValues(this);
/* Do not print available memory in bar mode */
- this->curItems = 4;
-
- written = Meter_humanUnit(buffer, this->values[0], size);
+ static_assert(MEMORY_METER_AVAILABLE + 1 == MEMORY_METER_ITEMCOUNT,
+ "MEMORY_METER_AVAILABLE is not the last item in MemoryMeterValues");
+ this->curItems = MEMORY_METER_AVAILABLE;
+
+ /* we actually want to show "used + shared + compressed" */
+ double used = this->values[MEMORY_METER_USED];
+ if (isPositive(this->values[MEMORY_METER_SHARED]))
+ used += this->values[MEMORY_METER_SHARED];
+ if (isPositive(this->values[MEMORY_METER_COMPRESSED]))
+ used += this->values[MEMORY_METER_COMPRESSED];
+
+ written = Meter_humanUnit(buffer, used, size);
METER_BUFFER_CHECK(buffer, size, written);
METER_BUFFER_APPEND_CHR(buffer, size, '/');
@@ -52,28 +67,35 @@ static void MemoryMeter_display(const Object* cast, RichString* out) {
Meter_humanUnit(buffer, this->total, sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
- Meter_humanUnit(buffer, this->values[0], sizeof(buffer));
+ Meter_humanUnit(buffer, this->values[MEMORY_METER_USED], 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);
-
/* shared memory is not supported on all platforms */
- if (!isnan(this->values[2])) {
- Meter_humanUnit(buffer, this->values[2], sizeof(buffer));
+ if (isNonnegative(this->values[MEMORY_METER_SHARED])) {
+ Meter_humanUnit(buffer, this->values[MEMORY_METER_SHARED], 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));
+ /* compressed memory is not supported on all platforms */
+ if (isNonnegative(this->values[MEMORY_METER_COMPRESSED])) {
+ Meter_humanUnit(buffer, this->values[MEMORY_METER_COMPRESSED], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " compressed:");
+ RichString_appendAscii(out, CRT_colors[MEMORY_COMPRESSED], buffer);
+ }
+
+ Meter_humanUnit(buffer, this->values[MEMORY_METER_BUFFERS], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " buffers:");
+ RichString_appendAscii(out, CRT_colors[MEMORY_BUFFERS_TEXT], buffer);
+
+ Meter_humanUnit(buffer, this->values[MEMORY_METER_CACHE], 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));
+ if (isNonnegative(this->values[MEMORY_METER_AVAILABLE])) {
+ Meter_humanUnit(buffer, this->values[MEMORY_METER_AVAILABLE], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " available:");
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
}
@@ -87,7 +109,7 @@ const MeterClass MemoryMeter_class = {
},
.updateValues = MemoryMeter_updateValues,
.defaultMode = BAR_METERMODE,
- .maxItems = 5,
+ .maxItems = MEMORY_METER_ITEMCOUNT,
.total = 100.0,
.attributes = MemoryMeter_attributes,
.name = "Memory",
diff --git a/MemoryMeter.h b/MemoryMeter.h
index d23021e9..e39b3bd3 100644
--- a/MemoryMeter.h
+++ b/MemoryMeter.h
@@ -9,6 +9,15 @@ in the source distribution for its full text.
#include "Meter.h"
+typedef enum {
+ MEMORY_METER_USED = 0,
+ MEMORY_METER_SHARED = 1,
+ MEMORY_METER_COMPRESSED = 2,
+ MEMORY_METER_BUFFERS = 3,
+ MEMORY_METER_CACHE = 4,
+ MEMORY_METER_AVAILABLE = 5,
+ MEMORY_METER_ITEMCOUNT = 6, // number of entries in this enum
+} MemoryMeterValues;
extern const MeterClass MemoryMeter_class;
diff --git a/MemorySwapMeter.c b/MemorySwapMeter.c
index 46b2a6b7..e1e5b104 100644
--- a/MemorySwapMeter.c
+++ b/MemorySwapMeter.c
@@ -5,9 +5,12 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "MemorySwapMeter.h"
#include <assert.h>
+#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
@@ -47,28 +50,23 @@ static void MemorySwapMeter_init(Meter* this) {
MemorySwapMeterData* data = this->meterData;
if (!data) {
- data = this->meterData = xMalloc(sizeof(MemorySwapMeterData));
- data->memoryMeter = NULL;
- data->swapMeter = NULL;
+ data = this->meterData = xCalloc(1, sizeof(MemorySwapMeterData));
}
if (!data->memoryMeter)
- data->memoryMeter = Meter_new(this->pl, 0, (const MeterClass*) Class(MemoryMeter));
+ data->memoryMeter = Meter_new(this->host, 0, (const MeterClass*) Class(MemoryMeter));
if (!data->swapMeter)
- data->swapMeter = Meter_new(this->pl, 0, (const MeterClass*) Class(SwapMeter));
+ data->swapMeter = Meter_new(this->host, 0, (const MeterClass*) Class(SwapMeter));
- if (Meter_initFn(data->memoryMeter))
+ if (Meter_initFn(data->memoryMeter)) {
Meter_init(data->memoryMeter);
- if (Meter_initFn(data->swapMeter))
+ }
+ 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) {
+static void MemorySwapMeter_updateMode(Meter* this, MeterModeId mode) {
MemorySwapMeterData* data = this->meterData;
this->mode = mode;
@@ -76,7 +74,7 @@ static void MemorySwapMeter_updateMode(Meter* this, int 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);
+ this->h = MAXIMUM(data->memoryMeter->h, data->swapMeter->h);
}
static void MemorySwapMeter_done(Meter* this) {
@@ -94,7 +92,7 @@ const MeterClass MemorySwapMeter_class = {
.delete = Meter_delete,
},
.updateValues = MemorySwapMeter_updateValues,
- .defaultMode = CUSTOM_METERMODE,
+ .defaultMode = BAR_METERMODE,
.isMultiColumn = true,
.name = "MemorySwap",
.uiName = "Memory & Swap",
diff --git a/Meter.c b/Meter.c
index 164c4d38..3dbdfcc6 100644
--- a/Meter.c
+++ b/Meter.c
@@ -13,88 +13,26 @@ in the source distribution for its full text.
#include <math.h>
#include <stdlib.h>
#include <string.h>
-#include <stdio.h>
#include "CRT.h"
#include "Macros.h"
#include "Object.h"
#include "ProvideCurses.h"
#include "RichString.h"
+#include "Row.h"
#include "Settings.h"
#include "XUtils.h"
#define GRAPH_HEIGHT 4 /* Unit: rows (lines) */
-const MeterClass Meter_class = {
- .super = {
- .extends = Class(Object)
- }
-};
-
-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;
- this->param = param;
- this->pl = pl;
- this->curItems = type->maxItems;
- this->curAttributes = NULL;
- this->values = type->maxItems ? xCalloc(type->maxItems, sizeof(double)) : NULL;
- this->total = type->total;
- this->caption = xStrdup(type->caption);
- if (Meter_initFn(this)) {
- Meter_init(this);
- }
- Meter_setMode(this, type->defaultMode);
- return this;
-}
-
-int Meter_humanUnit(char* buffer, unsigned long int value, size_t size) {
- const char* prefix = "KMGTPEZY";
- unsigned long int powi = 1;
- unsigned int powj = 1, precision = 2;
-
- for (;;) {
- if (value / 1024 < powi)
- break;
-
- if (prefix[1] == '\0')
- break;
-
- powi *= 1024;
- ++prefix;
- }
-
- if (*prefix == 'K')
- precision = 0;
-
- for (; precision > 0; precision--) {
- powj *= 10;
- if (value / powi < powj)
- break;
- }
-
- return snprintf(buffer, size, "%.*f%c", precision, (double) value / powi, *prefix);
-}
+typedef struct MeterMode_ {
+ Meter_Draw draw;
+ const char* uiName;
+ int h;
+} MeterMode;
-void Meter_delete(Object* cast) {
- if (!cast)
- return;
-
- Meter* this = (Meter*) cast;
- if (Meter_doneFn(this)) {
- Meter_done(this);
- }
- free(this->drawData);
- free(this->caption);
- free(this->values);
- free(this);
-}
-
-void Meter_setCaption(Meter* this, const char* caption) {
- free_and_xStrdup(&this->caption, caption);
-}
+/* Meter drawing modes */
static inline void Meter_displayBuffer(const Meter* this, RichString* out) {
if (Object_displayFn(this)) {
@@ -104,52 +42,6 @@ static inline void Meter_displayBuffer(const Meter* this, RichString* out) {
}
}
-void Meter_setMode(Meter* this, int modeIndex) {
- if (modeIndex > 0 && modeIndex == this->mode) {
- return;
- }
-
- if (!modeIndex) {
- modeIndex = 1;
- }
-
- assert(modeIndex < LAST_METERMODE);
- if (Meter_defaultMode(this) == CUSTOM_METERMODE) {
- this->draw = Meter_drawFn(this);
- if (Meter_updateModeFn(this)) {
- Meter_updateMode(this, modeIndex);
- }
- } else {
- assert(modeIndex >= 1);
- free(this->drawData);
- this->drawData = NULL;
-
- const MeterMode* mode = Meter_modes[modeIndex];
- this->draw = mode->draw;
- this->h = mode->h;
- }
- this->mode = modeIndex;
-}
-
-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 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", name, mode);
- ListItem* li = ListItem_new(buffer, 0);
- li->moving = moving;
- return li;
-}
-
/* ---------- TextMeterMode ---------- */
static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
@@ -175,12 +67,15 @@ 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) {
+ // Draw the caption
const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
int captionLen = 3;
mvaddnstr(y, x, caption, captionLen);
x += captionLen;
w -= captionLen;
+
+ // Draw the bar borders
attrset(CRT_colors[BAR_BORDER]);
mvaddch(y, x, '[');
w--;
@@ -190,14 +85,16 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
x++;
- if (w < 1)
+ if (w < 1) {
return;
+ }
// 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, 0, ' ', w);
RichString_appendWide(&bar, 0, this->txtBuffer);
+
int startPos = RichString_sizeVal(bar) - w;
if (startPos > w) {
// Text is too large for bar
@@ -214,6 +111,7 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
// If still too large, print the start not the end
startPos = MINIMUM(startPos, w);
}
+
assert(startPos >= 0);
assert(startPos <= w);
assert(startPos + w <= RichString_sizeVal(bar));
@@ -224,8 +122,8 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
int offset = 0;
for (uint8_t i = 0; i < this->curItems; i++) {
double value = this->values[i];
- value = CLAMP(value, 0.0, this->total);
- if (value > 0) {
+ if (isPositive(value) && this->total > 0.0) {
+ value = MINIMUM(value, this->total);
blockSizes[i] = ceil((value / this->total) * w);
} else {
blockSizes[i] = 0;
@@ -236,6 +134,7 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
for (int j = offset; j < nextOffset; j++)
if (RichString_getCharVal(bar, startPos + j) == ' ') {
if (CRT_colorScheme == COLORSCHEME_MONOCHROME) {
+ assert(i < strlen(BarMeterMode_characters));
RichString_setChar(&bar, startPos + j, BarMeterMode_characters[i]);
} else {
RichString_setChar(&bar, startPos + j, '|');
@@ -287,13 +186,46 @@ static const char* const GraphMeterMode_dotsAscii[] = {
};
static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
- const ProcessList* pl = this->pl;
+ const char* caption = Meter_getCaption(this);
+ attrset(CRT_colors[METER_TEXT]);
+ const int captionLen = 3;
+ mvaddnstr(y, x, caption, captionLen);
+ x += captionLen;
+ w -= captionLen;
+
+ GraphData* data = &this->drawData;
+ assert(data->nValues / 2 <= INT_MAX);
+ if (w > (int)(data->nValues / 2) && MAX_METER_GRAPHDATA_VALUES > data->nValues) {
+ size_t oldNValues = data->nValues;
+ data->nValues = MAXIMUM(oldNValues + oldNValues / 2, (size_t)w * 2);
+ data->nValues = MINIMUM(data->nValues, MAX_METER_GRAPHDATA_VALUES);
+ data->values = xReallocArray(data->values, data->nValues, sizeof(*data->values));
+ memmove(data->values + (data->nValues - oldNValues), data->values, oldNValues * sizeof(*data->values));
+ memset(data->values, 0, (data->nValues - oldNValues) * sizeof(*data->values));
+ }
+
+ const size_t nValues = data->nValues;
+ if (nValues < 1)
+ return;
+
+ const Machine* host = this->host;
+ if (!timercmp(&host->realtime, &(data->time), <)) {
+ int globalDelay = host->settings->delay;
+ struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay % 10) * 100000L };
+ timeradd(&host->realtime, &delay, &(data->time));
+
+ memmove(&data->values[0], &data->values[1], (nValues - 1) * sizeof(*data->values));
+
+ data->values[nValues - 1] = sumPositiveValues(this->values, this->curItems);
+ }
+
+ if (w <= 0)
+ return;
- if (!this->drawData) {
- this->drawData = xCalloc(1, sizeof(GraphData));
+ if ((size_t)w > nValues / 2) {
+ x += w - nValues / 2;
+ w = nValues / 2;
}
- GraphData* data = this->drawData;
- const int nValues = METER_GRAPHDATA_SIZE;
const char* const* GraphMeterMode_dots;
int GraphMeterMode_pixPerRow;
@@ -308,38 +240,12 @@ 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, caption, captionLen);
- x += captionLen;
- w -= captionLen;
-
- if (!timercmp(&pl->realtime, &(data->time), <)) {
- int globalDelay = this->pl->settings->delay;
- 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];
-
- double value = 0.0;
- for (uint8_t i = 0; i < this->curItems; i++)
- value += this->values[i];
- data->values[nValues - 1] = value;
- }
-
- int i = nValues - (w * 2), k = 0;
- if (i < 0) {
- k = -i / 2;
- i = 0;
- }
- for (; i < nValues - 1; i += 2, k++) {
+ size_t i = nValues - (size_t)w * 2;
+ for (int col = 0; i < nValues - 1; i += 2, col++) {
int pix = GraphMeterMode_pixPerRow * GRAPH_HEIGHT;
- 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);
+ double total = MAXIMUM(this->total, 1);
+ int v1 = CLAMP((int) lround(data->values[i] / total * pix), 1, pix);
+ int v2 = CLAMP((int) lround(data->values[i + 1] / total * pix), 1, pix);
int colorIdx = GRAPH_1;
for (int line = 0; line < GRAPH_HEIGHT; line++) {
@@ -347,7 +253,7 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
int line2 = CLAMP(v2 - (GraphMeterMode_pixPerRow * (GRAPH_HEIGHT - 1 - line)), 0, GraphMeterMode_pixPerRow);
attrset(CRT_colors[colorIdx]);
- mvaddstr(y + line, x + k, GraphMeterMode_dots[line1 * (GraphMeterMode_pixPerRow + 1) + line2]);
+ mvaddstr(y + line, x + col, GraphMeterMode_dots[line1 * (GraphMeterMode_pixPerRow + 1) + line2]);
colorIdx = GRAPH_2;
}
}
@@ -367,7 +273,7 @@ static const char* const LEDMeterMode_digitsAscii[] = {
static const char* const LEDMeterMode_digitsUtf8[] = {
"┌──┐", " ┐ ", "╶──┐", "╶──┐", "╷ ╷", "┌──╴", "┌──╴", "╶──┐", "┌──┐", "┌──┐",
"│ │", " │ ", "┌──┘", " ──┤", "└──┤", "└──┐", "├──┐", " │", "├──┤", "└──┤",
- "└──┘", " ╵ ", "└──╴", "╶──┘", " ╵", "╶──┘", "└──┘", " ╵", "└──┘", " ──┘"
+ "└──┘", " ╵ ", "└──╴", "╶──┘", " ╵", "╶──┘", "└──┘", " ╵", "└──┘", "╶──┘"
};
#endif
@@ -424,38 +330,159 @@ static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
RichString_delete(&out);
}
-static MeterMode BarMeterMode = {
- .uiName = "Bar",
- .h = 1,
- .draw = BarMeterMode_draw,
+static const MeterMode Meter_modes[] = {
+ [0] = {
+ .uiName = NULL,
+ .h = 0,
+ .draw = NULL,
+ },
+ [BAR_METERMODE] = {
+ .uiName = "Bar",
+ .h = 1,
+ .draw = BarMeterMode_draw,
+ },
+ [TEXT_METERMODE] = {
+ .uiName = "Text",
+ .h = 1,
+ .draw = TextMeterMode_draw,
+ },
+ [GRAPH_METERMODE] = {
+ .uiName = "Graph",
+ .h = GRAPH_HEIGHT,
+ .draw = GraphMeterMode_draw,
+ },
+ [LED_METERMODE] = {
+ .uiName = "LED",
+ .h = 3,
+ .draw = LEDMeterMode_draw,
+ },
};
-static MeterMode TextMeterMode = {
- .uiName = "Text",
- .h = 1,
- .draw = TextMeterMode_draw,
-};
+/* Meter class and methods */
-static MeterMode GraphMeterMode = {
- .uiName = "Graph",
- .h = GRAPH_HEIGHT,
- .draw = GraphMeterMode_draw,
+const MeterClass Meter_class = {
+ .super = {
+ .extends = Class(Object)
+ }
};
-static MeterMode LEDMeterMode = {
- .uiName = "LED",
- .h = 3,
- .draw = LEDMeterMode_draw,
-};
+Meter* Meter_new(const Machine* host, unsigned int param, const MeterClass* type) {
+ Meter* this = xCalloc(1, sizeof(Meter));
+ Object_setClass(this, type);
+ this->h = 1;
+ this->param = param;
+ this->host = host;
+ this->curItems = type->maxItems;
+ this->curAttributes = NULL;
+ this->values = type->maxItems ? xCalloc(type->maxItems, sizeof(double)) : NULL;
+ this->total = type->total;
+ this->caption = xStrdup(type->caption);
+ if (Meter_initFn(this)) {
+ Meter_init(this);
+ }
+ Meter_setMode(this, type->defaultMode);
+ return this;
+}
-const MeterMode* const Meter_modes[] = {
- NULL,
- &BarMeterMode,
- &TextMeterMode,
- &GraphMeterMode,
- &LEDMeterMode,
- NULL
-};
+/* Converts 'value' in kibibytes into a human readable string.
+ Example output strings: "0K", "1023K", "98.7M" and "1.23G" */
+int Meter_humanUnit(char* buffer, double value, size_t size) {
+ size_t i = 0;
+
+ assert(value >= 0.0);
+ while (value >= ONE_K) {
+ if (i >= ARRAYSIZE(unitPrefixes) - 1) {
+ if (value > 9999.0) {
+ return xSnprintf(buffer, size, "inf");
+ }
+ break;
+ }
+
+ value /= ONE_K;
+ ++i;
+ }
+
+ int precision = 0;
+
+ if (i > 0) {
+ // Fraction digits for mebibytes and above
+ precision = value <= 99.9 ? (value <= 9.99 ? 2 : 1) : 0;
+
+ // Round up if 'value' is in range (99.9, 100) or (9.99, 10)
+ if (precision < 2) {
+ double limit = precision == 1 ? 10.0 : 100.0;
+ if (value < limit) {
+ value = limit;
+ }
+ }
+ }
+
+ return xSnprintf(buffer, size, "%.*f%c", precision, value, unitPrefixes[i]);
+}
+
+void Meter_delete(Object* cast) {
+ if (!cast)
+ return;
+
+ Meter* this = (Meter*) cast;
+ if (Meter_doneFn(this)) {
+ Meter_done(this);
+ }
+ free(this->drawData.values);
+ free(this->caption);
+ free(this->values);
+ free(this);
+}
+
+void Meter_setCaption(Meter* this, const char* caption) {
+ free_and_xStrdup(&this->caption, caption);
+}
+
+void Meter_setMode(Meter* this, MeterModeId modeIndex) {
+ if (modeIndex > 0 && modeIndex == this->mode) {
+ return;
+ }
+
+ if (modeIndex == 0) {
+ modeIndex = 1;
+ }
+
+ assert(modeIndex < LAST_METERMODE);
+ if (Meter_updateModeFn(this)) {
+ assert(Meter_drawFn(this));
+ this->draw = Meter_drawFn(this);
+ Meter_updateMode(this, modeIndex);
+ } else {
+ assert(modeIndex >= 1);
+ free(this->drawData.values);
+ this->drawData.values = NULL;
+ this->drawData.nValues = 0;
+
+ const MeterMode* mode = &Meter_modes[modeIndex];
+ this->draw = mode->draw;
+ this->h = mode->h;
+ }
+ this->mode = modeIndex;
+}
+
+ListItem* Meter_toListItem(const Meter* this, bool moving) {
+ char mode[20];
+ if (this->mode > 0) {
+ xSnprintf(mode, sizeof(mode), " [%s]", Meter_modes[this->mode].uiName);
+ } else {
+ mode[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", name, mode);
+ ListItem* li = ListItem_new(buffer, 0);
+ li->moving = moving;
+ return li;
+}
/* Blank meter */
diff --git a/Meter.h b/Meter.h
index bd7604a0..0d93759f 100644
--- a/Meter.h
+++ b/Meter.h
@@ -7,20 +7,19 @@ Released under the GNU GPLv2+, see the COPYING file
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>
#include "ListItem.h"
+#include "Machine.h"
+#include "MeterMode.h"
#include "Object.h"
-#include "ProcessList.h"
#define METER_TXTBUFFER_LEN 256
-#define METER_GRAPHDATA_SIZE 256
+#define MAX_METER_GRAPHDATA_VALUES 32768
#define METER_BUFFER_CHECK(buffer, size, written) \
do { \
@@ -50,7 +49,7 @@ typedef struct Meter_ Meter;
typedef void(*Meter_Init)(Meter*);
typedef void(*Meter_Done)(Meter*);
-typedef void(*Meter_UpdateMode)(Meter*, int);
+typedef void(*Meter_UpdateMode)(Meter*, MeterModeId);
typedef void(*Meter_UpdateValues)(Meter*);
typedef void(*Meter_Draw)(Meter*, int, int, int);
typedef const char* (*Meter_GetCaption)(const Meter*);
@@ -65,7 +64,7 @@ typedef struct MeterClass_ {
const Meter_Draw draw;
const Meter_GetCaption getCaption;
const Meter_GetUiName getUiName;
- const int defaultMode;
+ const MeterModeId defaultMode;
const double total;
const int* const attributes;
const char* const name; /* internal name of the meter, must not contain any space */
@@ -89,7 +88,6 @@ typedef struct MeterClass_ {
#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
@@ -97,20 +95,21 @@ typedef struct MeterClass_ {
typedef struct GraphData_ {
struct timeval time;
- double values[METER_GRAPHDATA_SIZE];
+ size_t nValues;
+ double* values;
} GraphData;
struct Meter_ {
Object super;
Meter_Draw draw;
+ const Machine* host;
char* caption;
- int mode;
+ MeterModeId mode;
unsigned int param;
- GraphData* drawData;
+ 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];
@@ -119,21 +118,6 @@ struct Meter_ {
void* meterData;
};
-typedef struct MeterMode_ {
- Meter_Draw draw;
- const char* uiName;
- int h;
-} MeterMode;
-
-typedef enum {
- CUSTOM_METERMODE = 0,
- BAR_METERMODE,
- TEXT_METERMODE,
- GRAPH_METERMODE,
- LED_METERMODE,
- LAST_METERMODE
-} MeterModeId;
-
typedef enum {
RATESTATUS_DATA,
RATESTATUS_INIT,
@@ -143,20 +127,20 @@ typedef enum {
extern const MeterClass Meter_class;
-Meter* Meter_new(const ProcessList* pl, unsigned int param, const MeterClass* type);
+Meter* Meter_new(const Machine* host, unsigned int param, const MeterClass* type);
-int Meter_humanUnit(char* buffer, unsigned long int value, size_t size);
+/* Converts 'value' in kibibytes into a human readable string.
+ Example output strings: "0K", "1023K", "98.7M" and "1.23G" */
+int Meter_humanUnit(char* buffer, double value, size_t size);
void Meter_delete(Object* cast);
void Meter_setCaption(Meter* this, const char* caption);
-void Meter_setMode(Meter* this, int modeIndex);
+void Meter_setMode(Meter* this, MeterModeId modeIndex);
ListItem* Meter_toListItem(const Meter* this, bool moving);
-extern const MeterMode* const Meter_modes[];
-
extern const MeterClass BlankMeter_class;
#endif
diff --git a/MeterMode.h b/MeterMode.h
new file mode 100644
index 00000000..fc5b7785
--- /dev/null
+++ b/MeterMode.h
@@ -0,0 +1,22 @@
+#ifndef HEADER_MeterMode
+#define HEADER_MeterMode
+/*
+htop - MeterMode.h
+(C) 2024 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+enum MeterModeId_ {
+ /* Meter mode 0 is reserved */
+ BAR_METERMODE = 1,
+ TEXT_METERMODE,
+ GRAPH_METERMODE,
+ LED_METERMODE,
+ LAST_METERMODE
+};
+
+typedef unsigned int MeterModeId;
+
+#endif
diff --git a/MetersPanel.c b/MetersPanel.c
index 97da6309..2b8049ca 100644
--- a/MetersPanel.c
+++ b/MetersPanel.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 "MetersPanel.h"
#include <stdlib.h>
@@ -33,7 +35,7 @@ static const char* const MetersMovingKeys[] = {"Space", "Enter", "Up", "Dn", "<-
static const int MetersMovingEvents[] = {' ', 13, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, ERR, KEY_DC, KEY_F(10)};
static FunctionBar* Meters_movingBar = NULL;
-void MetersPanel_cleanup() {
+void MetersPanel_cleanup(void) {
if (Meters_movingBar) {
FunctionBar_delete(Meters_movingBar);
Meters_movingBar = NULL;
@@ -91,65 +93,52 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {
HandlerResult result = IGNORED;
bool sideMove = false;
- switch(ch) {
+ switch (ch) {
case 0x0a:
case 0x0d:
case KEY_ENTER:
- {
if (!Vector_size(this->meters))
break;
MetersPanel_setMoving(this, !(this->moving));
result = HANDLED;
break;
- }
case ' ':
case KEY_F(4):
- case 't':
- {
+ case 't': {
if (!Vector_size(this->meters))
break;
Meter* meter = (Meter*) Vector_get(this->meters, selected);
- int mode = meter->mode + 1;
- if (mode == LAST_METERMODE) mode = 1;
+ MeterModeId mode = meter->mode + 1;
+ if (mode == LAST_METERMODE)
+ mode = 1;
Meter_setMode(meter, mode);
Panel_set(super, selected, (Object*) Meter_toListItem(meter, this->moving));
result = HANDLED;
break;
}
case KEY_UP:
- {
- if (!this->moving) {
+ if (!this->moving)
break;
- }
- }
/* else fallthrough */
case KEY_F(7):
case '[':
case '-':
- {
Vector_moveUp(this->meters, selected);
Panel_moveSelectedUp(super);
result = HANDLED;
break;
- }
case KEY_DOWN:
- {
- if (!this->moving) {
+ if (!this->moving)
break;
- }
- }
/* else fallthrough */
case KEY_F(8):
case ']':
case '+':
- {
Vector_moveDown(this->meters, selected);
Panel_moveSelectedDown(super);
result = HANDLED;
break;
- }
case KEY_RIGHT:
- {
sideMove = moveToNeighbor(this, this->rightNeighbor, selected);
if (this->moving && !sideMove) {
// lock user here until it exits positioning-mode
@@ -158,18 +147,14 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {
// if user is free, don't set HANDLED;
// let ScreenManager handle focus.
break;
- }
case KEY_LEFT:
- {
sideMove = moveToNeighbor(this, this->leftNeighbor, selected);
if (this->moving && !sideMove) {
result = HANDLED;
}
break;
- }
case KEY_F(9):
case KEY_DC:
- {
if (!Vector_size(this->meters))
break;
if (selected < Vector_size(this->meters)) {
@@ -179,8 +164,8 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {
MetersPanel_setMoving(this, false);
result = HANDLED;
break;
- }
}
+
if (result == HANDLED || sideMove) {
Header* header = this->scr->header;
this->settings->changed = true;
@@ -188,6 +173,7 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {
Header_calculateHeight(header);
ScreenManager_resize(this->scr);
}
+
return result;
}
diff --git a/NetworkIOMeter.c b/NetworkIOMeter.c
index dd91b75e..6bc2d082 100644
--- a/NetworkIOMeter.c
+++ b/NetworkIOMeter.c
@@ -1,16 +1,24 @@
+/*
+htop - NetworkIOMeter.c
+(C) 2020-2023 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 "NetworkIOMeter.h"
#include <stdbool.h>
-#include <stdint.h>
#include "CRT.h"
+#include "Machine.h"
#include "Macros.h"
#include "Meter.h"
#include "Object.h"
#include "Platform.h"
-#include "Process.h"
-#include "ProcessList.h"
#include "RichString.h"
+#include "Row.h"
#include "XUtils.h"
@@ -20,27 +28,25 @@ static const int NetworkIOMeter_attributes[] = {
};
static MeterRateStatus status = RATESTATUS_INIT;
-static uint32_t cached_rxb_diff;
+static double cached_rxb_diff;
+static char cached_rxb_diff_str[6];
static uint32_t cached_rxp_diff;
-static uint32_t cached_txb_diff;
+static double cached_txb_diff;
+static char cached_txb_diff_str[6];
static uint32_t cached_txp_diff;
static void NetworkIOMeter_updateValues(Meter* this) {
- const ProcessList* pl = this->pl;
- static uint64_t cached_last_update = 0;
+ const Machine* host = this->host;
- uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update;
+ static uint64_t cached_last_update = 0;
+ uint64_t passedTimeInMs = host->realtimeMs - cached_last_update;
+ bool hasNewData = false;
+ NetworkIOData data;
/* update only every 500ms to have a sane span for rate calculation */
if (passedTimeInMs > 500) {
- static uint64_t cached_rxb_total;
- static uint64_t cached_rxp_total;
- static uint64_t cached_txb_total;
- static uint64_t cached_txp_total;
- uint64_t diff;
-
- NetworkIOData data;
- if (!Platform_getNetworkIO(&data)) {
+ hasNewData = Platform_getNetworkIO(&data);
+ if (!hasNewData) {
status = RATESTATUS_NODATA;
} else if (cached_last_update == 0) {
status = RATESTATUS_INIT;
@@ -50,50 +56,69 @@ static void NetworkIOMeter_updateValues(Meter* this) {
status = RATESTATUS_DATA;
}
- cached_last_update = pl->realtimeMs;
+ cached_last_update = host->realtimeMs;
+ }
- if (status == RATESTATUS_NODATA) {
- xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
- return;
- }
+ if (hasNewData) {
+ static uint64_t cached_rxb_total;
+ static uint64_t cached_rxp_total;
+ static uint64_t cached_txb_total;
+ static uint64_t cached_txp_total;
- 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;
+ if (status != RATESTATUS_INIT) {
+ uint64_t diff;
+
+ if (data.bytesReceived > cached_rxb_total) {
+ diff = data.bytesReceived - cached_rxb_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ cached_rxb_diff = diff;
+ } else {
+ cached_rxb_diff = 0;
+ }
+ Meter_humanUnit(cached_rxb_diff_str, cached_rxb_diff / ONE_K, sizeof(cached_rxb_diff_str));
+
+ if (data.packetsReceived > cached_rxp_total) {
+ diff = data.packetsReceived - cached_rxp_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to pkts/s */
+ cached_rxp_diff = (uint32_t)diff;
+ } else {
+ cached_rxp_diff = 0;
+ }
+
+ if (data.bytesTransmitted > cached_txb_total) {
+ diff = data.bytesTransmitted - cached_txb_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ cached_txb_diff = diff;
+ } else {
+ cached_txb_diff = 0;
+ }
+ Meter_humanUnit(cached_txb_diff_str, cached_txb_diff / ONE_K, sizeof(cached_txb_diff_str));
+
+ if (data.packetsTransmitted > cached_txp_total) {
+ diff = data.packetsTransmitted - cached_txp_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to pkts/s */
+ cached_txp_diff = (uint32_t)diff;
+ } else {
+ cached_txp_diff = 0;
+ }
}
- cached_rxb_total = data.bytesReceived;
- if (data.packetsReceived > cached_rxp_total) {
- diff = data.packetsReceived - cached_rxp_total;
- cached_rxp_diff = (uint32_t)diff;
- } else {
- cached_rxp_diff = 0;
- }
+ cached_rxb_total = data.bytesReceived;
cached_rxp_total = data.packetsReceived;
-
- 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 = data.bytesTransmitted;
-
- 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 = 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;
+ }
+
+ if (status == RATESTATUS_NODATA) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
+ return;
+ }
if (status == RATESTATUS_INIT) {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init");
return;
@@ -103,47 +128,36 @@ static void NetworkIOMeter_updateValues(Meter* this) {
return;
}
- 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(this->txtBuffer, sizeof(this->txtBuffer), "rx:%siB/s tx:%siB/s", bufferBytesReceived, bufferBytesTransmitted);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "rx:%siB/s tx:%siB/s %u/%upkts/s",
+ cached_rxb_diff_str, cached_txb_diff_str, cached_rxp_diff, cached_txp_diff);
}
static void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
switch (status) {
- case RATESTATUS_NODATA:
- RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
- return;
- case RATESTATUS_INIT:
- RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
- return;
- case RATESTATUS_STALE:
- RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
- return;
- case RATESTATUS_DATA:
- break;
+ case RATESTATUS_NODATA:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
+ return;
+ case RATESTATUS_INIT:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
+ return;
+ case RATESTATUS_STALE:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
+ return;
+ case RATESTATUS_DATA:
+ break;
}
char buffer[64];
- int len;
RichString_writeAscii(out, CRT_colors[METER_TEXT], "rx: ");
- Meter_humanUnit(buffer, cached_rxb_diff, sizeof(buffer));
- RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], cached_rxb_diff_str);
RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], "iB/s");
RichString_appendAscii(out, CRT_colors[METER_TEXT], " tx: ");
- Meter_humanUnit(buffer, cached_txb_diff, sizeof(buffer));
- RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], cached_txb_diff_str);
RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s");
- len = xSnprintf(buffer, sizeof(buffer), " (%u/%u packets) ", cached_rxp_diff, cached_txp_diff);
+ int len = xSnprintf(buffer, sizeof(buffer), " (%u/%u pkts/s) ", cached_rxp_diff, cached_txp_diff);
RichString_appendnAscii(out, CRT_colors[METER_TEXT], buffer, len);
}
diff --git a/NetworkIOMeter.h b/NetworkIOMeter.h
index 18c23ce2..355b815b 100644
--- a/NetworkIOMeter.h
+++ b/NetworkIOMeter.h
@@ -1,5 +1,13 @@
#ifndef HEADER_NetworkIOMeter
#define HEADER_NetworkIOMeter
+/*
+htop - NetworkIOMeter.h
+(C) 2020-2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdint.h>
#include "Meter.h"
diff --git a/Object.c b/Object.c
index f0091480..ae4d33f2 100644
--- a/Object.c
+++ b/Object.c
@@ -6,6 +6,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 "Object.h"
#include <stddef.h>
diff --git a/Object.h b/Object.h
index d5b3bb39..4d7d653e 100644
--- a/Object.h
+++ b/Object.h
@@ -8,8 +8,6 @@ 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>
diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c
index 4256575c..d04cce76 100644
--- a/OpenFilesScreen.c
+++ b/OpenFilesScreen.c
@@ -12,9 +12,9 @@ in the source distribution for its full text.
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -27,13 +27,17 @@ in the source distribution for its full text.
#include "XUtils.h"
+// cf. getIndexForType; must be larger than the maximum value returned.
+#define LSOF_DATACOL_COUNT 8
+
typedef struct OpenFiles_Data_ {
- char* data[8];
+ char* data[LSOF_DATACOL_COUNT];
} OpenFiles_Data;
typedef struct OpenFiles_ProcessData_ {
OpenFiles_Data data;
int error;
+ int cols[LSOF_DATACOL_COUNT];
struct OpenFiles_FileData_* files;
} OpenFiles_ProcessData;
@@ -44,22 +48,22 @@ typedef struct OpenFiles_FileData_ {
static size_t getIndexForType(char type) {
switch (type) {
- case 'f':
- return 0;
- case 'a':
- return 1;
- case 'D':
- return 2;
- case 'i':
- return 3;
- case 'n':
- return 4;
- case 's':
- return 5;
- case 't':
- return 6;
- case 'o':
- return 7;
+ case 'f':
+ return 0;
+ case 'a':
+ return 1;
+ case 'D':
+ return 2;
+ case 'i':
+ return 3;
+ case 'n':
+ return 4;
+ case 's':
+ return 5;
+ case 't':
+ return 6;
+ case 'o':
+ return 7;
}
/* should never reach here */
@@ -72,12 +76,12 @@ static const char* getDataForType(const OpenFiles_Data* data, char type) {
}
OpenFilesScreen* OpenFilesScreen_new(const Process* process) {
- OpenFilesScreen* this = xMalloc(sizeof(OpenFilesScreen));
+ OpenFilesScreen* this = xCalloc(1, sizeof(OpenFilesScreen));
Object_setClass(this, Class(OpenFilesScreen));
if (Process_isThread(process)) {
- this->pid = process->tgid;
+ this->pid = Process_getThreadGroup(process);
} else {
- this->pid = process->pid;
+ this->pid = Process_getPid(process);
}
return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE OFFSET NODE NAME");
}
@@ -92,6 +96,9 @@ static void OpenFilesScreen_draw(InfoScreen* this) {
static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
OpenFiles_ProcessData* pdata = xCalloc(1, sizeof(OpenFiles_ProcessData));
+ pdata->cols[getIndexForType('s')] = 8;
+ pdata->cols[getIndexForType('o')] = 8;
+ pdata->cols[getIndexForType('i')] = 8;
int fdpair[2] = {0, 0};
if (pipe(fdpair) == -1) {
@@ -122,7 +129,7 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
xSnprintf(buffer, sizeof(buffer), "%d", pid);
// Use of NULL in variadic functions must have a pointer cast.
// The NULL constant is not required by standard to have a pointer type.
- execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char *)NULL);
+ execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char*)NULL);
exit(127);
}
close(fdpair[1]);
@@ -131,65 +138,73 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
OpenFiles_FileData* fdata = NULL;
bool lsofIncludesFileSize = false;
- FILE* fd = fdopen(fdpair[0], "r");
- if (!fd) {
+ FILE* fp = fdopen(fdpair[0], "r");
+ if (!fp) {
pdata->error = 1;
return pdata;
}
for (;;) {
- char* line = String_readLine(fd);
+ char* line = String_readLine(fp);
if (!line) {
break;
}
unsigned char cmd = line[0];
switch (cmd) {
- case 'f': /* file descriptor */
- {
- OpenFiles_FileData* nextFile = xCalloc(1, sizeof(OpenFiles_FileData));
- if (fdata == NULL) {
- pdata->files = nextFile;
- } else {
- fdata->next = nextFile;
- }
- fdata = nextFile;
- item = &(fdata->data);
- } /* FALLTHRU */
- case 'a': /* file access mode */
- case 'D': /* file's major/minor device number */
- case 'i': /* file's inode number */
- case 'n': /* file name, comment, Internet address */
- case 's': /* file's size */
- case 't': /* file's type */
- {
- size_t index = getIndexForType(cmd);
- free_and_xStrdup(&item->data[index], line + 1);
- break;
- }
- case 'o': /* file's offset */
- {
- size_t index = getIndexForType(cmd);
- if (String_startsWith(line + 1, "0t")) {
- free_and_xStrdup(&item->data[index], line + 3);
- } else {
+ case 'f': /* file descriptor */
+ {
+ OpenFiles_FileData* nextFile = xCalloc(1, sizeof(OpenFiles_FileData));
+ if (fdata == NULL) {
+ pdata->files = nextFile;
+ } else {
+ fdata->next = nextFile;
+ }
+ fdata = nextFile;
+ item = &(fdata->data);
+ } /* FALLTHRU */
+ case 'a': /* file access mode */
+ case 'D': /* file's major/minor device number */
+ case 'i': /* file's inode number */
+ case 'n': /* file name, comment, Internet address */
+ case 's': /* file's size */
+ case 't': /* file's type */
+ {
+ size_t index = getIndexForType(cmd);
free_and_xStrdup(&item->data[index], line + 1);
+ size_t dlen = strlen(item->data[index]);
+ if (dlen > (size_t)pdata->cols[index]) {
+ pdata->cols[index] = (int)CLAMP(dlen, 0, INT16_MAX);
+ }
+ break;
}
- break;
- }
- case 'c': /* process command name */
- case 'd': /* file's device character code */
- case 'g': /* process group ID */
- case 'G': /* file flags */
- case 'k': /* link count */
- case 'l': /* file's lock status */
- case 'L': /* process login name */
- case 'p': /* process ID */
- case 'P': /* protocol name */
- case 'R': /* parent process ID */
- case 'T': /* TCP/TPI information, identified by prefixes */
- case 'u': /* process user ID */
- /* ignore */
- break;
+ case 'o': /* file's offset */
+ {
+ size_t index = getIndexForType(cmd);
+ if (String_startsWith(line + 1, "0t")) {
+ free_and_xStrdup(&item->data[index], line + 3);
+ } else {
+ free_and_xStrdup(&item->data[index], line + 1);
+ }
+ size_t dlen = strlen(item->data[index]);
+ if (dlen > (size_t)pdata->cols[index]) {
+ pdata->cols[index] = (int)CLAMP(dlen, 0, INT16_MAX);
+ }
+ break;
+ }
+ case 'c': /* process command name */
+ case 'd': /* file's device character code */
+ case 'g': /* process group ID */
+ case 'G': /* file flags */
+ case 'k': /* link count */
+ case 'l': /* file's lock status */
+ case 'L': /* process login name */
+ case 'p': /* process ID */
+ case 'P': /* protocol name */
+ case 'R': /* parent process ID */
+ case 'T': /* TCP/TPI information, identified by prefixes */
+ case 'u': /* process user ID */
+ /* ignore */
+ break;
}
if (cmd == 's')
@@ -197,7 +212,7 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
free(line);
}
- fclose(fd);
+ fclose(fp);
int wstatus;
while (waitpid(child, &wstatus, 0) == -1)
@@ -223,10 +238,10 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
item = &fdata->data;
const char* filename = getDataForType(item, 'n');
- struct stat st;
- if (stat(filename, &st) == 0) {
+ struct stat sb;
+ if (stat(filename, &sb) == 0) {
char fileSizeBuf[21]; /* 20 (long long) + 1 (NULL) */
- xSnprintf(fileSizeBuf, sizeof(fileSizeBuf), "%"PRIu64, (uint64_t)st.st_size); /* st.st_size is long long on macOS, long on linux */
+ xSnprintf(fileSizeBuf, sizeof(fileSizeBuf), "%"PRIu64, (uint64_t)sb.st_size); /* sb.st_size is long long on macOS, long on linux */
free_and_xStrdup(&item->data[fileSizeIndex], fileSizeBuf);
}
}
@@ -239,32 +254,44 @@ static void OpenFiles_Data_clear(OpenFiles_Data* data) {
free(data->data[i]);
}
-static void OpenFilesScreen_scan(InfoScreen* this) {
- Panel* panel = this->display;
+static void OpenFilesScreen_scan(InfoScreen* super) {
+ Panel* panel = super->display;
int idx = Panel_getSelectedIndex(panel);
Panel_prune(panel);
- OpenFiles_ProcessData* pdata = OpenFilesScreen_getProcessData(((OpenFilesScreen*)this)->pid);
+ OpenFiles_ProcessData* pdata = OpenFilesScreen_getProcessData(((OpenFilesScreen*)super)->pid);
if (pdata->error == 127) {
- InfoScreen_addLine(this, "Could not execute 'lsof'. Please make sure it is available in your $PATH.");
+ InfoScreen_addLine(super, "Could not execute 'lsof'. Please make sure it is available in your $PATH.");
} else if (pdata->error == 1) {
- InfoScreen_addLine(this, "Failed listing open files.");
+ InfoScreen_addLine(super, "Failed listing open files.");
} else {
+ char hdrbuf[128] = {0};
+ snprintf(hdrbuf, sizeof(hdrbuf), "%5.5s %-7.7s %-4.4s %6.6s %*s %*s %*s %s",
+ "FD", "TYPE", "MODE", "DEVICE",
+ pdata->cols[getIndexForType('s')], "SIZE",
+ pdata->cols[getIndexForType('o')], "OFFSET",
+ pdata->cols[getIndexForType('i')], "NODE",
+ "NAME"
+ );
+ Panel_setHeader(panel, hdrbuf);
+
OpenFiles_FileData* fdata = pdata->files;
while (fdata) {
OpenFiles_Data* data = &fdata->data;
- size_t lenN = strlen(getDataForType(data, 'n'));
- size_t sizeEntry = 5 + 7 + 4 + 10 + 10 + 10 + 10 + lenN + 8 /*spaces*/ + 1 /*null*/;
- char entry[sizeEntry];
- xSnprintf(entry, sizeof(entry), "%5.5s %-7.7s %-4.4s %-10.10s %10.10s %10.10s %10.10s %s",
+ char* entry = NULL;
+ xAsprintf(&entry, "%5.5s %-7.7s %-4.4s %6.6s %*s %*s %*s %s",
getDataForType(data, 'f'),
getDataForType(data, 't'),
getDataForType(data, 'a'),
getDataForType(data, 'D'),
+ pdata->cols[getIndexForType('s')],
getDataForType(data, 's'),
+ pdata->cols[getIndexForType('o')],
getDataForType(data, 'o'),
+ pdata->cols[getIndexForType('i')],
getDataForType(data, 'i'),
getDataForType(data, 'n'));
- InfoScreen_addLine(this, entry);
+ InfoScreen_addLine(super, entry);
+ free(entry);
OpenFiles_Data_clear(data);
OpenFiles_FileData* old = fdata;
fdata = fdata->next;
@@ -273,7 +300,7 @@ static void OpenFilesScreen_scan(InfoScreen* this) {
OpenFiles_Data_clear(&pdata->data);
}
free(pdata);
- Vector_insertionSort(this->lines);
+ Vector_insertionSort(super->lines);
Vector_insertionSort(panel->items);
Panel_setSelected(panel, idx);
}
diff --git a/OptionItem.c b/OptionItem.c
index 962c0a9c..728113b9 100644
--- a/OptionItem.c
+++ b/OptionItem.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 "OptionItem.h"
#include <assert.h>
diff --git a/Panel.c b/Panel.c
index 4ea03f66..4784a658 100644
--- a/Panel.c
+++ b/Panel.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 "Panel.h"
#include <assert.h>
@@ -271,8 +273,8 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
int upTo = MINIMUM(first + h, size);
int selectionColor = focus
- ? CRT_colors[this->selectionColorId]
- : CRT_colors[PANEL_SELECTION_UNFOCUS];
+ ? CRT_colors[this->selectionColorId]
+ : CRT_colors[PANEL_SELECTION_UNFOCUS];
if (this->needsRedraw || force_redraw) {
int line = 0;
@@ -357,72 +359,74 @@ bool Panel_onKey(Panel* this, int key) {
} while (0)
switch (key) {
- case KEY_DOWN:
- case KEY_CTRL('N'):
- #ifdef KEY_C_DOWN
- case KEY_C_DOWN:
- #endif
- this->selected++;
- break;
-
- case KEY_UP:
- case KEY_CTRL('P'):
- #ifdef KEY_C_UP
- case KEY_C_UP:
- #endif
- this->selected--;
- break;
+ case KEY_DOWN:
+ case KEY_CTRL('N'):
+ #ifdef KEY_C_DOWN
+ case KEY_C_DOWN:
+ #endif
+ this->selected++;
+ break;
+
+ case KEY_UP:
+ case KEY_CTRL('P'):
+ #ifdef KEY_C_UP
+ case KEY_C_UP:
+ #endif
+ this->selected--;
+ break;
+
+ case KEY_LEFT:
+ case KEY_CTRL('B'):
+ if (this->scrollH > 0) {
+ this->scrollH -= MAXIMUM(CRT_scrollHAmount, 0);
+ this->needsRedraw = true;
+ }
+ break;
- case KEY_LEFT:
- case KEY_CTRL('B'):
- if (this->scrollH > 0) {
- this->scrollH -= MAXIMUM(CRT_scrollHAmount, 0);
+ case KEY_RIGHT:
+ case KEY_CTRL('F'):
+ this->scrollH += CRT_scrollHAmount;
this->needsRedraw = true;
- }
- break;
+ break;
- case KEY_RIGHT:
- case KEY_CTRL('F'):
- this->scrollH += CRT_scrollHAmount;
- this->needsRedraw = true;
- break;
+ case KEY_PPAGE:
+ PANEL_SCROLL(-(this->h - Panel_headerHeight(this)));
+ break;
- case KEY_PPAGE:
- PANEL_SCROLL(-(this->h - Panel_headerHeight(this)));
- break;
+ case KEY_NPAGE:
+ PANEL_SCROLL(+(this->h - Panel_headerHeight(this)));
+ break;
- case KEY_NPAGE:
- PANEL_SCROLL(+(this->h - Panel_headerHeight(this)));
- break;
+ case KEY_WHEELUP:
+ PANEL_SCROLL(-CRT_scrollWheelVAmount);
+ break;
- case KEY_WHEELUP:
- PANEL_SCROLL(-CRT_scrollWheelVAmount);
- break;
+ case KEY_WHEELDOWN:
+ PANEL_SCROLL(+CRT_scrollWheelVAmount);
+ break;
- case KEY_WHEELDOWN:
- PANEL_SCROLL(+CRT_scrollWheelVAmount);
- break;
+ case KEY_HOME:
+ this->selected = 0;
+ break;
- case KEY_HOME:
- this->selected = 0;
- break;
+ case KEY_END:
+ this->selected = size - 1;
+ break;
- case KEY_END:
- this->selected = size - 1;
- break;
+ case KEY_CTRL('A'):
+ case '^':
+ this->scrollH = 0;
+ this->needsRedraw = true;
+ break;
- case KEY_CTRL('A'):
- case '^':
- this->scrollH = 0;
- this->needsRedraw = true;
- break;
- case KEY_CTRL('E'):
- case '$':
- this->scrollH = MAXIMUM(this->selectedLen - this->w, 0);
- this->needsRedraw = true;
- break;
- default:
- return false;
+ case KEY_CTRL('E'):
+ case '$':
+ this->scrollH = MAXIMUM(this->selectedLen - this->w, 0);
+ this->needsRedraw = true;
+ break;
+
+ default:
+ return false;
}
#undef PANEL_SCROLL
@@ -443,6 +447,9 @@ bool Panel_onKey(Panel* this, int key) {
HandlerResult Panel_selectByTyping(Panel* this, int ch) {
int size = Panel_size(this);
+ if (ch == '#')
+ return IGNORED;
+
if (!this->eventHandlerState)
this->eventHandlerState = xCalloc(100, sizeof(char));
char* buffer = this->eventHandlerState;
@@ -468,7 +475,8 @@ HandlerResult Panel_selectByTyping(Panel* this, int ch) {
len = strlen(buffer);
for (int i = 0; i < size; i++) {
const char* cur = ((ListItem*) Panel_get(this, i))->value;
- while (*cur == ' ') cur++;
+ while (*cur == ' ')
+ cur++;
if (strncasecmp(cur, buffer, len) == 0) {
Panel_setSelected(this, i);
return HANDLED;
diff --git a/Panel.h b/Panel.h
index 33d532e8..286e4464 100644
--- a/Panel.h
+++ b/Panel.h
@@ -7,8 +7,6 @@ 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>
diff --git a/Process.c b/Process.c
index 2a4c809d..527fc201 100644
--- a/Process.c
+++ b/Process.c
@@ -11,7 +11,6 @@ in the source distribution for its full text.
#include "Process.h"
#include <assert.h>
-#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdbool.h>
@@ -19,16 +18,18 @@ in the source distribution for its full text.
#include <stdlib.h>
#include <string.h>
#include <time.h>
-#include <unistd.h>
#include <sys/resource.h>
#include "CRT.h"
+#include "Hashtable.h"
+#include "Machine.h"
#include "Macros.h"
-#include "Platform.h"
-#include "ProcessList.h"
+#include "ProcessTable.h"
#include "DynamicColumn.h"
#include "RichString.h"
+#include "Scheduling.h"
#include "Settings.h"
+#include "Table.h"
#include "XUtils.h"
#if defined(MAJOR_IN_MKDEV)
@@ -39,242 +40,15 @@ in the source distribution for its full text.
/* 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 = PROCESS_MIN_PID_DIGITS;
-int Process_uidDigits = PROCESS_MIN_UID_DIGITS;
-
-void Process_setupColumnWidths() {
- int maxPid = Platform_getMaxPid();
- if (maxPid == -1)
- return;
-
- if (maxPid < (int)pow(10, PROCESS_MIN_PID_DIGITS)) {
- Process_pidDigits = PROCESS_MIN_PID_DIGITS;
- return;
- }
-
- Process_pidDigits = (int)log10(maxPid) + 1;
- assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS);
-}
-
-void Process_setUidColumnWidth(uid_t maxUid) {
- if (maxUid < (uid_t)pow(10, PROCESS_MIN_UID_DIGITS)) {
- Process_uidDigits = PROCESS_MIN_UID_DIGITS;
- return;
- }
-
- Process_uidDigits = (int)log10(maxUid) + 1;
- assert(Process_uidDigits <= PROCESS_MAX_UID_DIGITS);
-}
-
-void Process_printBytes(RichString* str, unsigned long long number, bool coloring) {
- char buffer[16];
- int len;
-
- 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 (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);
- RichString_appendnAscii(str, processMegabytesColor, buffer, len);
- number %= 1000;
- len = xSnprintf(buffer, sizeof(buffer), "%03llu ", number);
- RichString_appendnAscii(str, processColor, buffer, len);
- } else if (number < 1000 * ONE_K) {
- //3 digit MB
- number /= ONE_K;
- len = xSnprintf(buffer, sizeof(buffer), "%4lluM ", number);
- RichString_appendnAscii(str, processMegabytesColor, buffer, len);
- } else if (number < 10000 * ONE_K) {
- //1 digit GB, 3 digit MB
- number /= ONE_K;
- len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000);
- RichString_appendnAscii(str, processGigabytesColor, buffer, len);
- number %= 1000;
- len = xSnprintf(buffer, sizeof(buffer), "%03lluM ", number);
- RichString_appendnAscii(str, processMegabytesColor, buffer, len);
- } else if (number < 100000 * ONE_K) {
- //2 digit GB, 1 digit MB
- number /= 100 * ONE_K;
- len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10);
- RichString_appendnAscii(str, processGigabytesColor, buffer, len);
- number %= 10;
- len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number);
- RichString_appendnAscii(str, processMegabytesColor, buffer, len);
- RichString_appendAscii(str, processGigabytesColor, "G ");
- } else if (number < 1000 * ONE_M) {
- //3 digit GB
- number /= ONE_M;
- len = xSnprintf(buffer, sizeof(buffer), "%4lluG ", number);
- RichString_appendnAscii(str, processGigabytesColor, buffer, len);
- } else if (number < 10000ULL * ONE_M) {
- //1 digit TB, 3 digit GB
- number /= ONE_M;
- 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 PB and above
- len = xSnprintf(buffer, sizeof(buffer), "%4.1lfP ", (double)number / ONE_T);
- RichString_appendnAscii(str, largeNumberColor, buffer, len);
- }
-}
-
-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 = 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 = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
-
- if (number == ULLONG_MAX) {
- RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A ");
- } else if (number >= 100000LL * ONE_DECIMAL_T) {
- xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G);
- RichString_appendnAscii(str, largeNumberColor, buffer, 12);
- } 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);
- } 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);
- } 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);
- }
-}
-
-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);
-
- 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 (years >= 10000000) {
- RichString_appendnAscii(str, yearColor, "eternity ", 9);
- } else if (years >= 1000) {
- len = xSnprintf(buffer, sizeof(buffer), "%7dy ", years);
- RichString_appendnAscii(str, yearColor, buffer, len);
- } else if (daysLeft >= 100) {
- len = xSnprintf(buffer, sizeof(buffer), "%3dy", years);
- RichString_appendnAscii(str, yearColor, buffer, len);
- len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft);
- 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 {
- 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);
- }
- } 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);
- }
-}
-
void Process_fillStarttimeBuffer(Process* this) {
struct tm date;
+ time_t now = this->super.host->realtime.tv_sec;
(void) localtime_r(&this->starttime_ctime, &date);
- strftime(this->starttime_show, sizeof(this->starttime_show) - 1, (this->starttime_ctime > (time(NULL) - 86400)) ? "%R " : "%b%d ", &date);
+
+ strftime(this->starttime_show,
+ sizeof(this->starttime_show) - 1,
+ (this->starttime_ctime > now - 86400) ? "%R " : (this->starttime_ctime > now - 364 * 86400) ? "%b%d " : " %Y ",
+ &date);
}
/*
@@ -286,7 +60,7 @@ void Process_fillStarttimeBuffer(Process* this) {
*
* 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.
+ * implicit that only up to (TASK_COMM_LEN - 1) could be comm.
*/
#define TASK_COMM_LEN 16
@@ -404,15 +178,15 @@ static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr
* Process_writeCommand() for coloring. The merged Command string is also
* returned by Process_getCommand() for searching, sorting and filtering.
*/
-void Process_makeCommandStr(Process* this) {
+void Process_makeCommandStr(Process* this, const Settings* settings) {
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;
+ bool shadowDistPathPrefix = settings->shadowDistPathPrefix;
uint64_t settingsStamp = settings->lastUpdate;
@@ -432,7 +206,7 @@ void Process_makeCommandStr(Process* this) {
mc->lastUpdate = settingsStamp;
- /* The field separtor "│" has been chosen such that it will not match any
+ /* The field separator "│" 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);
@@ -472,6 +246,56 @@ void Process_makeCommandStr(Process* this) {
str = stpcpy(str, SEPARATOR); \
} while (0)
+ #define CHECK_AND_MARK(str_, prefix_) \
+ if (String_startsWith(str_, prefix_)) { \
+ WRITE_HIGHLIGHT(0, strlen(prefix_), CRT_colors[PROCESS_SHADOW], CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR); \
+ break; \
+ } else (void)0
+
+ #define CHECK_AND_MARK_DIST_PATH_PREFIXES(str_) \
+ do { \
+ if ((str_)[0] != '/') { \
+ break; \
+ } \
+ switch ((str_)[1]) { \
+ case 'b': \
+ CHECK_AND_MARK(str_, "/bin/"); \
+ break; \
+ case 'l': \
+ CHECK_AND_MARK(str_, "/lib/"); \
+ CHECK_AND_MARK(str_, "/lib32/"); \
+ CHECK_AND_MARK(str_, "/lib64/"); \
+ CHECK_AND_MARK(str_, "/libx32/"); \
+ break; \
+ case 's': \
+ CHECK_AND_MARK(str_, "/sbin/"); \
+ break; \
+ case 'u': \
+ if (String_startsWith(str_, "/usr/")) { \
+ switch ((str_)[5]) { \
+ case 'b': \
+ CHECK_AND_MARK(str_, "/usr/bin/"); \
+ break; \
+ case 'l': \
+ CHECK_AND_MARK(str_, "/usr/libexec/"); \
+ CHECK_AND_MARK(str_, "/usr/lib/"); \
+ CHECK_AND_MARK(str_, "/usr/lib32/"); \
+ CHECK_AND_MARK(str_, "/usr/lib64/"); \
+ CHECK_AND_MARK(str_, "/usr/libx32/"); \
+ \
+ CHECK_AND_MARK(str_, "/usr/local/bin/"); \
+ CHECK_AND_MARK(str_, "/usr/local/lib/"); \
+ CHECK_AND_MARK(str_, "/usr/local/sbin/"); \
+ break; \
+ case 's': \
+ CHECK_AND_MARK(str_, "/usr/sbin/"); \
+ break; \
+ } \
+ } \
+ break; \
+ } \
+ } 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];
@@ -503,13 +327,16 @@ void Process_makeCommandStr(Process* this) {
WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
str = stpcpy(str, procComm);
- if(!showMergedCommand)
+ if (!showMergedCommand)
return;
WRITE_SEPARATOR;
}
}
+ if (shadowDistPathPrefix && showProgramPath)
+ CHECK_AND_MARK_DIST_PATH_PREFIXES(cmdline);
+
if (cmdlineBasenameEnd > cmdlineBasenameStart)
WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
@@ -537,6 +364,8 @@ void Process_makeCommandStr(Process* this) {
/* Start with copying exe */
if (showProgramPath) {
+ if (shadowDistPathPrefix)
+ CHECK_AND_MARK_DIST_PATH_PREFIXES(procExe);
if (haveCommInExe)
WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
@@ -594,6 +423,9 @@ void Process_makeCommandStr(Process* this) {
WRITE_SEPARATOR;
}
+ if (shadowDistPathPrefix)
+ CHECK_AND_MARK_DIST_PATH_PREFIXES(cmdline);
+
if (!haveCommInExe && haveCommInCmdline && !haveCommField && (!Process_isUserlandThread(this) || showThreadNames))
WRITE_HIGHLIGHT(commStart, commEnd - commStart, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
@@ -601,6 +433,8 @@ void Process_makeCommandStr(Process* this) {
if (*cmdline)
(void)stpcpyWithNewlineConversion(str, cmdline);
+ #undef CHECK_AND_MARK_DIST_PATH_PREFIXES
+ #undef CHECK_AND_MARK
#undef WRITE_SEPARATOR
#undef WRITE_HIGHLIGHT
}
@@ -609,18 +443,20 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin
(void)baseAttr;
const ProcessMergedCommand* mc = &this->mergedCommand;
+ const char* mergedCommand = mc->str;
int strStart = RichString_size(str);
- const bool highlightBaseName = this->settings->highlightBaseName;
+ const Settings* settings = this->super.host->settings;
+ const bool highlightBaseName = settings->highlightBaseName;
const bool highlightSeparator = true;
- const bool highlightDeleted = this->settings->highlightDeletedExe;
+ const bool highlightDeleted = settings->highlightDeletedExe;
- if (!this->mergedCommand.str) {
+ if (!mergedCommand) {
int len = 0;
const char* cmdline = this->cmdline;
- if (highlightBaseName || !this->settings->showProgramPath) {
+ if (highlightBaseName || !settings->showProgramPath) {
int basename = 0;
for (int i = 0; i < this->cmdlineBasenameEnd; i++) {
if (cmdline[i] == '/') {
@@ -631,7 +467,7 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin
}
}
if (len == 0) {
- if (this->settings->showProgramPath) {
+ if (settings->showProgramPath) {
strStart += basename;
} else {
cmdline += basename;
@@ -642,14 +478,14 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin
RichString_appendWide(str, attr, cmdline);
- if (this->settings->highlightBaseName) {
+ if (settings->highlightBaseName) {
RichString_setAttrn(str, baseAttr, strStart, len);
}
return;
}
- RichString_appendWide(str, attr, this->mergedCommand.str);
+ RichString_appendWide(str, attr, mergedCommand);
for (size_t i = 0, hlCount = CLAMP(mc->highlightCount, 0, ARRAYSIZE(mc->highlights)); i < hlCount; i++) {
const ProcessCmdlineHighlight* hl = &mc->highlights[i];
@@ -669,74 +505,11 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin
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];
- processMegabytesColor = CRT_colors[PROCESS];
- }
-
- if (isnan(rate)) {
- 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, sizeof(buffer), "%7.2f B/s ", rate);
- RichString_appendnAscii(str, processColor, buffer, len);
- } else if (rate < ONE_M) {
- 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, sizeof(buffer), "%7.2f M/s ", rate / ONE_M);
- RichString_appendnAscii(str, processMegabytesColor, buffer, len);
- } else if (rate < ONE_T) {
- 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, 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 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, uint8_t width, int* attr) {
- if (val >= 0) {
- if (val < 0.05F)
- *attr = CRT_colors[PROCESS_SHADOW];
- else if (val >= 99.9F)
- *attr = CRT_colors[PROCESS_MEGABYTES];
-
- int precision = 1;
-
- // Display "val" as "100" for columns like "MEM%".
- if (width == 4 && val > 99.9F) {
- precision = 0;
- val = 100.0F;
- }
+ if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR)
+ if (!highlightDeleted)
+ continue;
- xSnprintf(buffer, n, "%*.*f ", width, precision, val);
- } else {
- *attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
+ RichString_setAttrn(str, hl->attr, strStart + hl->offset, hl->length);
}
}
@@ -762,39 +535,41 @@ static inline char processStateChar(ProcessState state) {
}
}
-void Process_writeField(const Process* this, RichString* str, ProcessField field) {
- char buffer[256];
- size_t n = sizeof(buffer);
+static void Process_rowWriteField(const Row* super, RichString* str, RowField field) {
+ const Process* this = (const Process*) super;
+ assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
+ Process_writeField(this, str, field);
+}
+
+void Process_writeField(const Process* this, RichString* str, RowField field) {
+ const Row* super = (const Row*) &this->super;
+ const Machine* host = super->host;
+ const Settings* settings = host->settings;
+
+ bool coloring = settings->highlightMegabytes;
+ char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
- bool coloring = this->settings->highlightMegabytes;
+ size_t n = sizeof(buffer) - 1;
switch (field) {
case COMM: {
int baseattr = CRT_colors[PROCESS_BASENAME];
- if (this->settings->highlightThreads && Process_isThread(this)) {
+ if (settings->highlightThreads && Process_isThread(this)) {
attr = CRT_colors[PROCESS_THREAD];
baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
}
- const ScreenSettings* ss = this->settings->ss;
- if (!ss->treeView || this->indent == 0) {
+ const ScreenSettings* ss = settings->ss;
+ if (!ss->treeView || super->indent == 0) {
Process_writeCommand(this, attr, baseattr, str);
return;
}
char* buf = buffer;
- int maxIndent = 0;
- bool lastItem = (this->indent < 0);
- int indent = (this->indent < 0 ? -this->indent : this->indent);
+ const bool lastItem = (super->indent < 0);
- for (int i = 0; i < 32; i++) {
- if (indent & (1U << i)) {
- maxIndent = i + 1;
- }
- }
-
- for (int i = 0; i < maxIndent - 1; i++) {
+ for (uint32_t indent = (super->indent < 0 ? -super->indent : super->indent); indent > 1; indent >>= 1) {
int written, ret;
- if (indent & (1 << i)) {
+ if (indent & 1U) {
ret = xSnprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]);
} else {
ret = xSnprintf(buf, n, " ");
@@ -809,7 +584,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
}
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] );
+ xSnprintf(buf, n, "%s%s ", draw, super->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;
@@ -824,14 +599,14 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
procComm = Process_isKernelThread(this) ? kthreadID : "N/A";
}
- Process_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1);
+ Row_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 (settings->highlightDeletedExe) {
if (this->procExeDeleted)
attr = CRT_colors[FAILED_READ];
else if (this->usesDeletedLib)
@@ -843,7 +618,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
procExe = Process_isKernelThread(this) ? kthreadID : "N/A";
}
- Process_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1);
+ Row_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1);
return;
}
case CWD: {
@@ -857,27 +632,27 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
} else {
cwd = this->procCwd;
}
- Process_printLeftAlignedField(str, attr, cwd, 25);
+ Row_printLeftAlignedField(str, attr, cwd, 25);
return;
}
case ELAPSED: {
- const uint64_t rt = this->processList->realtimeMs;
+ const uint64_t rt = host->realtimeMs;
const uint64_t st = this->starttime_ctime * 1000;
const uint64_t dt =
rt < st ? 0 :
rt - st;
- Process_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring);
+ Row_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring);
return;
}
- case MAJFLT: Process_printCount(str, this->majflt, coloring); return;
- case MINFLT: Process_printCount(str, this->minflt, coloring); return;
- case M_RESIDENT: Process_printKBytes(str, this->m_resident, coloring); return;
- case M_VIRT: Process_printKBytes(str, this->m_virt, coloring); return;
+ case MAJFLT: Row_printCount(str, this->majflt, coloring); return;
+ case MINFLT: Row_printCount(str, this->minflt, coloring); return;
+ case M_RESIDENT: Row_printKBytes(str, this->m_resident, coloring); return;
+ case M_VIRT: Row_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]
- : CRT_colors[PROCESS_SHADOW];
+ : this->nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
+ : CRT_colors[PROCESS_SHADOW];
break;
case NLWP:
if (this->nlwp == 1)
@@ -885,61 +660,70 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
xSnprintf(buffer, n, "%4ld ", this->nlwp);
break;
- case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break;
+ case PERCENT_CPU: Row_printPercentage(this->percent_cpu, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); break;
case PERCENT_NORM_CPU: {
- float cpuPercentage = this->percent_cpu / this->processList->activeCPUs;
- Process_printPercentage(cpuPercentage, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr);
+ float cpuPercentage = this->percent_cpu / host->activeCPUs;
+ Row_printPercentage(cpuPercentage, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr);
break;
}
- case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, 4, &attr); break;
+ case PERCENT_MEM: Row_printPercentage(this->percent_mem, buffer, n, 4, &attr); break;
case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break;
- case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pid); break;
- case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->ppid); break;
+ case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getPid(this)); break;
+ case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getParent(this)); break;
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 PROCESSOR: xSnprintf(buffer, n, "%3d ", Settings_cpuId(settings, this->processor)); break;
+ case SCHEDULERPOLICY: {
+ const char* schedPolStr = "N/A";
+#ifdef SCHEDULER_SUPPORT
+ if (this->scheduling_policy >= 0)
+ schedPolStr = Scheduling_formatPolicy(this->scheduling_policy);
+#endif
+ xSnprintf(buffer, n, "%-5s ", schedPolStr);
+ break;
+ }
case SESSION: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->session); break;
case STARTTIME: xSnprintf(buffer, n, "%s", this->starttime_show); break;
case STATE:
xSnprintf(buffer, n, "%c ", processStateChar(this->state));
switch (this->state) {
- case RUNNABLE:
- case RUNNING:
- case TRACED:
- attr = CRT_colors[PROCESS_RUN_STATE];
- break;
-
- case BLOCKED:
- case DEFUNCT:
- case STOPPED:
- case UNINTERRUPTIBLE_WAIT:
- case ZOMBIE:
- attr = CRT_colors[PROCESS_D_STATE];
- break;
-
- case QUEUED:
- case WAITING:
- case IDLE:
- case SLEEPING:
- attr = CRT_colors[PROCESS_SHADOW];
- break;
-
- case UNKNOWN:
- case PAGING:
- break;
+ case RUNNABLE:
+ case RUNNING:
+ case TRACED:
+ attr = CRT_colors[PROCESS_RUN_STATE];
+ break;
+
+ case BLOCKED:
+ case DEFUNCT:
+ case STOPPED:
+ case UNINTERRUPTIBLE_WAIT:
+ case ZOMBIE:
+ attr = CRT_colors[PROCESS_D_STATE];
+ break;
+
+ case QUEUED:
+ case WAITING:
+ case IDLE:
+ case SLEEPING:
+ attr = CRT_colors[PROCESS_SHADOW];
+ break;
+
+ case UNKNOWN:
+ case PAGING:
+ break;
}
break;
case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break;
- case TIME: Process_printTime(str, this->time, coloring); return;
+ case TIME: Row_printTime(str, this->time, coloring); return;
case TGID:
- if (this->tgid == this->pid)
+ if (Process_getThreadGroup(this) == Process_getPid(this))
attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tgid);
+ xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getThreadGroup(this));
break;
case TPGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tpgid); break;
case TTY:
@@ -952,11 +736,13 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
}
break;
case USER:
- if (Process_getuid != this->st_uid)
+ if (this->elevated_priv == TRI_ON)
+ attr = CRT_colors[PROCESS_PRIV];
+ else if (host->htopUserId != this->st_uid)
attr = CRT_colors[PROCESS_SHADOW];
if (this->user) {
- Process_printLeftAlignedField(str, attr, this->user, 10);
+ Row_printLeftAlignedField(str, attr, this->user, 10);
return;
}
@@ -969,36 +755,12 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
xSnprintf(buffer, n, "- ");
break;
}
- RichString_appendAscii(str, attr, buffer);
-}
-
-void Process_display(const Object* cast, RichString* out) {
- const Process* this = (const Process*) cast;
- const ProcessField* fields = this->settings->ss->fields;
- for (int i = 0; fields[i]; i++)
- As_Process(this)->writeField(this, out, fields[i]);
-
- if (this->settings->shadowOtherUsers && this->st_uid != Process_getuid) {
- RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]);
- }
-
- if (this->tag == true) {
- RichString_setAttr(out, CRT_colors[PROCESS_TAG]);
- }
- if (this->settings->highlightChanges) {
- if (Process_isTomb(this)) {
- out->highlightAttr = CRT_colors[PROCESS_TOMB];
- } else if (Process_isNew(this)) {
- out->highlightAttr = CRT_colors[PROCESS_NEW];
- }
- }
-
- assert(RichString_size(out) > 0);
+ RichString_appendAscii(str, attr, buffer);
}
void Process_done(Process* this) {
- assert (this != NULL);
+ assert(this != NULL);
free(this->cmdline);
free(this->procComm);
free(this->procExe);
@@ -1011,80 +773,122 @@ void Process_done(Process* this) {
* happens on what is displayed - whether comm, full path, basename, etc.. So
* this follows Process_writeField(COMM) and Process_writeCommand */
const char* Process_getCommand(const Process* this) {
- if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !this->mergedCommand.str) {
+ const Settings* settings = this->super.host->settings;
+
+ if ((Process_isUserlandThread(this) && settings->showThreadNames) || !this->mergedCommand.str) {
return this->cmdline;
}
return this->mergedCommand.str;
}
-const ProcessClass Process_class = {
- .super = {
- .extends = Class(Object),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
- },
- .writeField = Process_writeField,
-};
+static const char* Process_getSortKey(const Process* this) {
+ return Process_getCommand(this);
+}
-void Process_init(Process* this, const Settings* settings) {
- this->settings = settings;
- this->tag = false;
- this->showChildren = true;
- this->show = true;
- this->updated = false;
- this->cmdlineBasenameEnd = -1;
- this->st_uid = (uid_t)-1;
+const char* Process_rowGetSortKey(Row* super) {
+ const Process* this = (const Process*) super;
+ assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
+ return Process_getSortKey(this);
+}
- if (Process_getuid == (uid_t)-1) {
- Process_getuid = getuid();
- }
+/* Test whether display must highlight this row (if the htop UID matches) */
+static bool Process_isHighlighted(const Process* this) {
+ const Machine* host = this->super.host;
+ const Settings* settings = host->settings;
+ return settings->shadowOtherUsers && this->st_uid != host->htopUserId;
}
-void Process_toggleTag(Process* this) {
- this->tag = !this->tag;
+bool Process_rowIsHighlighted(const Row* super) {
+ const Process* this = (const Process*) super;
+ assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
+ return Process_isHighlighted(this);
}
-bool Process_isNew(const Process* this) {
- assert(this->processList);
- if (this->processList->monotonicMs >= this->seenStampMs) {
- return this->processList->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)this->processList->settings->highlightDelaySecs;
- }
+/* Test whether display must follow parent process (if this thread is hidden) */
+static bool Process_isVisible(const Process* p, const Settings* settings) {
+ if (settings->hideUserlandThreads)
+ return !Process_isThread(p);
+ return true;
+}
+
+bool Process_rowIsVisible(const Row* super, const Table* table) {
+ const Process* this = (const Process*) super;
+ assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
+ return Process_isVisible(this, table->host->settings);
+}
+
+/* Test whether display must filter out this process (various mechanisms) */
+static bool Process_matchesFilter(const Process* this, const Table* table) {
+ const Machine* host = table->host;
+ if (host->userId != (uid_t) -1 && this->st_uid != host->userId)
+ return true;
+
+ const char* incFilter = table->incFilter;
+ if (incFilter && !String_contains_i(Process_getCommand(this), incFilter, true))
+ return true;
+
+ const ProcessTable* pt = (const ProcessTable*) host->activeTable;
+ assert(Object_isA((const Object*) pt, (const ObjectClass*) &ProcessTable_class));
+ if (pt->pidMatchList && !Hashtable_get(pt->pidMatchList, Process_getThreadGroup(this)))
+ return true;
+
return false;
}
-bool Process_isTomb(const Process* this) {
- return this->tombStampMs > 0;
+bool Process_rowMatchesFilter(const Row* super, const Table* table) {
+ const Process* this = (const Process*) super;
+ assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
+ return Process_matchesFilter(this, table);
}
-bool Process_setPriority(Process* this, int priority) {
+void Process_init(Process* this, const Machine* host) {
+ Row_init(&this->super, host);
+
+ this->cmdlineBasenameEnd = -1;
+ this->st_uid = (uid_t)-1;
+}
+
+static bool Process_setPriority(Process* this, int priority) {
if (Settings_isReadonly())
return false;
- int old_prio = getpriority(PRIO_PROCESS, this->pid);
- int err = setpriority(PRIO_PROCESS, this->pid, priority);
+ int old_prio = getpriority(PRIO_PROCESS, Process_getPid(this));
+ int err = setpriority(PRIO_PROCESS, Process_getPid(this), priority);
- if (err == 0 && old_prio != getpriority(PRIO_PROCESS, this->pid)) {
+ if (err == 0 && old_prio != getpriority(PRIO_PROCESS, Process_getPid(this))) {
this->nice = priority;
}
return (err == 0);
}
-bool Process_changePriorityBy(Process* this, Arg delta) {
+bool Process_rowSetPriority(Row* super, int priority) {
+ Process* this = (Process*) super;
+ assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
+ return Process_setPriority(this, priority);
+}
+
+bool Process_rowChangePriorityBy(Row* super, Arg delta) {
+ Process* this = (Process*) super;
+ assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
return Process_setPriority(this, this->nice + delta.i);
}
-bool Process_sendSignal(Process* this, Arg sgn) {
- return kill(this->pid, sgn.i) == 0;
+static bool Process_sendSignal(Process* this, Arg sgn) {
+ return kill(Process_getPid(this), sgn.i) == 0;
+}
+
+bool Process_rowSendSignal(Row* super, Arg sgn) {
+ Process* this = (Process*) super;
+ assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
+ return Process_sendSignal(this, sgn);
}
int Process_compare(const void* v1, const void* v2) {
const Process* p1 = (const Process*)v1;
const Process* p2 = (const Process*)v2;
- const Settings* settings = p1->settings;
- const ScreenSettings* ss = settings->ss;
+ const ScreenSettings* ss = p1->super.host->settings->ss;
ProcessField key = ScreenSettings_getActiveSortKey(ss);
@@ -1092,18 +896,30 @@ int Process_compare(const void* v1, const void* v2) {
// Implement tie-breaker (needed to make tree mode more stable)
if (!result)
- return SPACESHIP_NUMBER(p1->pid, p2->pid);
+ return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;
}
+int Process_compareByParent(const Row* r1, const Row* r2) {
+ int result = SPACESHIP_NUMBER(
+ r1->isRoot ? 0 : Row_getGroupOrParent(r1),
+ r2->isRoot ? 0 : Row_getGroupOrParent(r2)
+ );
+
+ if (result != 0)
+ return result;
+
+ return Process_compare(r1, r2);
+}
+
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) {
int r;
switch (key) {
case PERCENT_CPU:
case PERCENT_NORM_CPU:
- return SPACESHIP_NUMBER(p1->percent_cpu, p2->percent_cpu);
+ return compareRealNumbers(p1->percent_cpu, p2->percent_cpu);
case PERCENT_MEM:
return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
case COMM:
@@ -1122,7 +938,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
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);
+ return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
case MAJFLT:
return SPACESHIP_NUMBER(p1->majflt, p2->majflt);
case MINFLT:
@@ -1138,18 +954,20 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
case PGRP:
return SPACESHIP_NUMBER(p1->pgrp, p2->pgrp);
case PID:
- return SPACESHIP_NUMBER(p1->pid, p2->pid);
+ return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
case PPID:
- return SPACESHIP_NUMBER(p1->ppid, p2->ppid);
+ return SPACESHIP_NUMBER(Process_getParent(p1), Process_getParent(p2));
case PRIORITY:
return SPACESHIP_NUMBER(p1->priority, p2->priority);
case PROCESSOR:
return SPACESHIP_NUMBER(p1->processor, p2->processor);
+ case SCHEDULERPOLICY:
+ return SPACESHIP_NUMBER(p1->scheduling_policy, p2->scheduling_policy);
case SESSION:
return SPACESHIP_NUMBER(p1->session, p2->session);
case STARTTIME:
r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
- return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid);
+ return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
case STATE:
return SPACESHIP_NUMBER(p1->state, p2->state);
case ST_UID:
@@ -1157,7 +975,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
case TIME:
return SPACESHIP_NUMBER(p1->time, p2->time);
case TGID:
- return SPACESHIP_NUMBER(p1->tgid, p2->tgid);
+ return SPACESHIP_NUMBER(Process_getThreadGroup(p1), Process_getThreadGroup(p2));
case TPGID:
return SPACESHIP_NUMBER(p1->tpgid, p2->tpgid);
case TTY:
@@ -1168,7 +986,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
default:
CRT_debug("Process_compareByKey_Base() called with key %d", key);
assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */
- return SPACESHIP_NUMBER(p1->pid, p2->pid);
+ return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
}
}
@@ -1220,8 +1038,14 @@ void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart
free(this->cmdline);
this->cmdline = cmdline ? xStrdup(cmdline) : NULL;
- this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);
- this->cmdlineBasenameEnd = basenameEnd;
+ if (Process_isKernelThread(this)) {
+ /* kernel threads have no basename */
+ this->cmdlineBasenameStart = 0;
+ this->cmdlineBasenameEnd = 0;
+ } else {
+ this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);
+ this->cmdlineBasenameEnd = basenameEnd;
+ }
this->mergedCommand.lastUpdate = 0;
}
@@ -1246,36 +1070,33 @@ void Process_updateExe(Process* this, const char* exe) {
this->mergedCommand.lastUpdate = 0;
}
-uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 };
-
-void Process_resetFieldWidths() {
- for (size_t i = 0; i < LAST_PROCESSFIELD; i++) {
- if (!Process_fields[i].autoWidth)
- continue;
-
- size_t len = strlen(Process_fields[i].title);
- assert(len <= UINT8_MAX);
- Process_fieldWidths[i] = (uint8_t)len;
- }
-}
-
-void Process_updateFieldWidth(ProcessField key, size_t width) {
- if (width > UINT8_MAX)
- Process_fieldWidths[key] = UINT8_MAX;
- else if (width > Process_fieldWidths[key])
- Process_fieldWidths[key] = (uint8_t)width;
-}
-
void Process_updateCPUFieldWidths(float percentage) {
- if (percentage < 99.9F) {
- Process_updateFieldWidth(PERCENT_CPU, 4);
- Process_updateFieldWidth(PERCENT_NORM_CPU, 4);
+ if (!isgreaterequal(percentage, 99.9F)) {
+ Row_updateFieldWidth(PERCENT_CPU, 4);
+ Row_updateFieldWidth(PERCENT_NORM_CPU, 4);
return;
}
// Add additional two characters, one for "." and another for precision.
uint8_t width = ceil(log10(percentage + 0.1)) + 2;
- Process_updateFieldWidth(PERCENT_CPU, width);
- Process_updateFieldWidth(PERCENT_NORM_CPU, width);
+ Row_updateFieldWidth(PERCENT_CPU, width);
+ Row_updateFieldWidth(PERCENT_NORM_CPU, width);
}
+
+const ProcessClass Process_class = {
+ .super = {
+ .super = {
+ .extends = Class(Row),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .sortKeyString = Process_rowGetSortKey,
+ .compareByParent = Process_compareByParent,
+ .writeField = Process_rowWriteField
+ },
+};
diff --git a/Process.h b/Process.h
index a1ca50fb..8c7ae76e 100644
--- a/Process.h
+++ b/Process.h
@@ -13,52 +13,23 @@ in the source distribution for its full text.
#include <sys/types.h>
#include "Object.h"
-#include "ProcessField.h"
#include "RichString.h"
+#include "Row.h"
+#include "RowField.h"
#define PROCESS_FLAG_IO 0x00000001
#define PROCESS_FLAG_CWD 0x00000002
+#define PROCESS_FLAG_SCHEDPOL 0x00000004
#define DEFAULT_HIGHLIGHT_SECS 5
-typedef enum ProcessField_ {
- NULL_PROCESSFIELD = 0,
- PID = 1,
- COMM = 2,
- STATE = 3,
- PPID = 4,
- PGRP = 5,
- SESSION = 6,
- TTY = 7,
- TPGID = 8,
- MINFLT = 10,
- MAJFLT = 12,
- PRIORITY = 18,
- NICE = 19,
- STARTTIME = 21,
- PROCESSOR = 38,
- M_VIRT = 39,
- M_RESIDENT = 40,
- ST_UID = 46,
- PERCENT_CPU = 47,
- PERCENT_MEM = 48,
- USER = 49,
- TIME = 50,
- 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;
+typedef enum Tristate_ {
+ TRI_INITIAL = 0,
+ TRI_OFF = -1,
+ TRI_ON = 1,
+} Tristate;
+
/* Core process states (shared by platforms)
* NOTE: The enum has an ordering that is important!
@@ -80,7 +51,8 @@ typedef enum ProcessState_ {
SLEEPING
} ProcessState;
-struct Settings_;
+struct Machine_; // IWYU pragma: keep
+struct Settings_; // IWYU pragma: keep
/* Holds information about regions of the cmdline that should be
* highlighted (e.g. program basename, delimiter, comm). */
@@ -104,20 +76,7 @@ typedef struct 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;
-
- /* Process identifier */
- pid_t pid;
-
- /* Parent process identifier */
- pid_t ppid;
-
- /* Thread group identifier */
- pid_t tgid;
+ Row super;
/* Process group identifier */
int pgrp;
@@ -134,6 +93,9 @@ typedef struct Process_ {
/* This is a userland thread / LWP */
bool isUserlandThread;
+ /* This process is running inside a container */
+ Tristate isRunningInContainer;
+
/* Controlling terminal identifier of the process */
unsigned long int tty_nr;
@@ -146,6 +108,13 @@ typedef struct Process_ {
/* User name */
const char* user;
+ /* Non root owned process with elevated privileges
+ * Linux:
+ * - from file capabilities
+ * - inherited from the ambient set
+ */
+ Tristate elevated_priv;
+
/* Process runtime (in hundredth of a second) */
unsigned long long int time;
@@ -218,35 +187,8 @@ typedef struct Process_ {
/* Process state enum field (platform dependent) */
ProcessState 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_depth;
-
- /* Has no known parent process */
- bool isRoot;
+ /* Current scheduling policy */
+ int scheduling_policy;
/*
* Internal state for merged Command display
@@ -275,41 +217,62 @@ typedef struct ProcessFieldData_ {
/* Whether the column width is dynamically adjusted (the minimum width is determined by the title length) */
bool autoWidth;
+
+ /* Whether the title of a column with dynamically adjusted width is right aligned (default is left aligned) */
+ bool autoTitleRightAlign;
} ProcessFieldData;
+#define LAST_PROCESSFIELD LAST_RESERVED_FIELD
+typedef int32_t ProcessField; /* see ReservedField list in RowField.h */
+
// Implemented in platform-specific code:
void Process_writeField(const Process* this, RichString* str, ProcessField field);
int Process_compare(const void* v1, const void* v2);
+int Process_compareByParent(const Row* r1, const Row* r2);
void Process_delete(Object* cast);
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-extern uint8_t Process_fieldWidths[LAST_PROCESSFIELD];
-#define PROCESS_MIN_PID_DIGITS 5
-#define PROCESS_MAX_PID_DIGITS 19
-#define PROCESS_MIN_UID_DIGITS 5
-#define PROCESS_MAX_UID_DIGITS 20
-extern int Process_pidDigits;
-extern int Process_uidDigits;
-
-typedef Process* (*Process_New)(const struct Settings_*);
-typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField);
+#define Process_pidDigits Row_pidDigits
+#define Process_uidDigits Row_uidDigits
+
+typedef Process* (*Process_New)(const struct Machine_*);
typedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField);
typedef struct ProcessClass_ {
- const ObjectClass super;
- const Process_WriteField writeField;
+ const RowClass super;
const Process_CompareByKey compareByKey;
} ProcessClass;
-#define As_Process(this_) ((const ProcessClass*)((this_)->super.klass))
+#define As_Process(this_) ((const ProcessClass*)((this_)->super.super.klass))
+
+#define Process_compareByKey(p1_, p2_, key_) (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_))
-#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) {
- return this->tgid == this->pid ? this->ppid : this->tgid;
+static inline void Process_setPid(Process* this, pid_t pid) {
+ this->super.id = pid;
}
-static inline bool Process_isChildOf(const Process* this, pid_t pid) {
- return pid == Process_getParentPid(this);
+static inline pid_t Process_getPid(const Process* this) {
+ return (pid_t)this->super.id;
+}
+
+static inline void Process_setThreadGroup(Process* this, pid_t pid) {
+ this->super.group = pid;
+}
+
+static inline pid_t Process_getThreadGroup(const Process* this) {
+ return (pid_t)this->super.group;
+}
+
+static inline void Process_setParent(Process* this, pid_t pid) {
+ this->super.parent = pid;
+}
+
+static inline pid_t Process_getParent(const Process* this) {
+ return (pid_t)this->super.parent;
+}
+
+static inline pid_t Process_getGroupOrParent(const Process* this) {
+ return Row_getGroupOrParent(&this->super);
}
static inline bool Process_isKernelThread(const Process* this) {
@@ -328,69 +291,32 @@ static inline bool Process_isThread(const Process* this) {
#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);
-
-/* Sets the size of the UID column based on the passed UID */
-void Process_setUidColumnWidth(uid_t maxUid);
-
-/* Takes number in bytes (base 1024). Prints 6 columns. */
-void Process_printBytes(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);
-
-/* Takes number as count (base 1000). Prints 12 columns. */
-void Process_printCount(RichString* str, unsigned long long number, bool coloring);
-
-/* Takes time in hundredths of a seconds. Prints 9 columns. */
-void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring);
-
-/* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */
-void Process_printRate(RichString* str, double rate, bool coloring);
+#define CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR 0x00000010
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, uint8_t width, int* attr);
-
-void Process_display(const Object* cast, RichString* out);
-
void Process_done(Process* this);
extern const ProcessClass Process_class;
-void Process_init(Process* this, const struct Settings_* settings);
+void Process_init(Process* this, const struct Machine_* host);
+
+const char* Process_rowGetSortKey(Row* super);
-void Process_toggleTag(Process* this);
+bool Process_rowSetPriority(Row* super, int priority);
-bool Process_isNew(const Process* this);
+bool Process_rowChangePriorityBy(Row* super, Arg delta);
-bool Process_isTomb(const Process* this);
+bool Process_rowSendSignal(Row* super, Arg sgn);
-bool Process_setPriority(Process* this, int priority);
+bool Process_rowIsHighlighted(const Row* super);
-bool Process_changePriorityBy(Process* this, Arg delta);
+bool Process_rowIsVisible(const Row* super, const struct Table_* table);
-bool Process_sendSignal(Process* this, Arg sgn);
+bool Process_rowMatchesFilter(const Row* super, const struct Table_* table);
static inline int Process_pidEqualCompare(const void* v1, const void* v2) {
- const pid_t p1 = ((const Process*)v1)->pid;
- const pid_t p2 = ((const Process*)v2)->pid;
- return p1 != p2; /* return zero when equal */
+ return Row_idEqualCompare(v1, v2);
}
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key);
@@ -403,12 +329,10 @@ void Process_updateExe(Process* this, const char* exe);
/* This function constructs the string that is displayed by
* Process_writeCommand and also returned by Process_getCommand */
-void Process_makeCommandStr(Process* this);
+void Process_makeCommandStr(Process* this, const struct Settings_ *settings);
void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str);
-void Process_resetFieldWidths(void);
-void Process_updateFieldWidth(ProcessField key, size_t width);
void Process_updateCPUFieldWidths(float percentage);
#endif
diff --git a/ProcessList.c b/ProcessList.c
deleted file mode 100644
index bbaddd86..00000000
--- a/ProcessList.c
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
-htop - ProcessList.c
-(C) 2004,2005 Hisham H. Muhammad
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "ProcessList.h"
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.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* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
- this->processes = Vector_new(klass, true, DEFAULT_SIZE);
- this->displayList = Vector_new(klass, false, DEFAULT_SIZE);
-
- this->processTable = Hashtable_new(200, false);
- this->needsSort = true;
-
- this->usersTable = usersTable;
- this->pidMatchList = pidMatchList;
- this->dynamicMeters = dynamicMeters;
- this->dynamicColumns = dynamicColumns;
-
- this->userId = userId;
-
- // set later by platform-specific code
- this->activeCPUs = 0;
- this->existingCPUs = 0;
- this->monotonicMs = 0;
-
- // always maintain valid realtime timestamps
- Platform_gettime_realtime(&this->realtime, &this->realtimeMs);
-
-#ifdef HAVE_LIBHWLOC
- this->topologyOk = false;
- if (hwloc_topology_init(&this->topology) == 0) {
- this->topologyOk =
- #if HWLOC_API_VERSION < 0x00020000
- /* try to ignore the top-level machine object type */
- 0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_MACHINE) &&
- /* ignore caches, which don't add structure */
- 0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_CORE) &&
- 0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_CACHE) &&
- 0 == hwloc_topology_set_flags(this->topology, HWLOC_TOPOLOGY_FLAG_WHOLE_SYSTEM) &&
- #else
- 0 == hwloc_topology_set_all_types_filter(this->topology, HWLOC_TYPE_FILTER_KEEP_STRUCTURE) &&
- #endif
- 0 == hwloc_topology_load(this->topology);
- }
-#endif
-
- this->following = -1;
-
- return this;
-}
-
-void ProcessList_done(ProcessList* this) {
-#ifdef HAVE_LIBHWLOC
- if (this->topologyOk) {
- hwloc_topology_destroy(this->topology);
- }
-#endif
-
- Hashtable_delete(this->processTable);
-
- Vector_delete(this->displayList);
- Vector_delete(this->processes);
-}
-
-void ProcessList_setPanel(ProcessList* this, Panel* panel) {
- this->panel = panel;
-}
-
-static const char* alignedDynamicColumnTitle(const ProcessList* this, int key, char* titleBuffer, size_t titleBufferSize) {
- const DynamicColumn* column = Hashtable_get(this->dynamicColumns, key);
- if (column == NULL)
- return "- ";
- int width = column->width;
- if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
- width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
- xSnprintf(titleBuffer, titleBufferSize, "%*s", width, column->heading);
- return titleBuffer;
-}
-
-static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessField field) {
- static char titleBuffer[UINT8_MAX + sizeof(" ")];
- assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" "));
- assert(sizeof(titleBuffer) >= PROCESS_MAX_PID_DIGITS + sizeof(" "));
- assert(sizeof(titleBuffer) >= PROCESS_MAX_UID_DIGITS + sizeof(" "));
-
- if (field >= LAST_PROCESSFIELD)
- return alignedDynamicColumnTitle(this, field, titleBuffer, sizeof(titleBuffer));
-
- const char* title = Process_fields[field].title;
- if (!title)
- return "- ";
-
- if (Process_fields[field].pidColumn) {
- xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title);
- return titleBuffer;
- }
-
- if (field == ST_UID) {
- xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_uidDigits, title);
- return titleBuffer;
- }
-
- if (Process_fields[field].autoWidth) {
- if (field == PERCENT_CPU)
- xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_fieldWidths[field], title);
- else
- xSnprintf(titleBuffer, sizeof(titleBuffer), "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title);
- return titleBuffer;
- }
-
- return title;
-}
-
-void ProcessList_printHeader(const ProcessList* this, RichString* header) {
- RichString_rewind(header, RichString_size(header));
-
- const Settings* settings = this->settings;
- const ScreenSettings* ss = settings->ss;
- const ProcessField* fields = ss->fields;
-
- ProcessField key = ScreenSettings_getActiveSortKey(ss);
-
- for (int i = 0; fields[i]; i++) {
- int color;
- if (ss->treeView && ss->treeViewAlwaysByPID) {
- color = CRT_colors[PANEL_HEADER_FOCUS];
- } else if (key == fields[i]) {
- color = CRT_colors[PANEL_SELECTION_FOCUS];
- } else {
- color = CRT_colors[PANEL_HEADER_FOCUS];
- }
-
- RichString_appendWide(header, color, alignedProcessFieldTitle(this, fields[i]));
- if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') {
- bool ascending = ScreenSettings_getActiveDirection(ss) == 1;
- RichString_rewind(header, 1); // rewind to override space
- RichString_appendnWide(header,
- CRT_colors[PANEL_SELECTION_FOCUS],
- CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC],
- 1);
- }
- if (COMM == fields[i] && settings->showMergedCommand) {
- RichString_appendAscii(header, color, "(merged)");
- }
- }
-}
-
-void ProcessList_add(ProcessList* this, Process* p) {
- assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) == -1);
- assert(Hashtable_get(this->processTable, p->pid) == NULL);
- p->processList = this;
-
- // highlighting processes found in first scan by first scan marked "far in the past"
- p->seenStampMs = this->monotonicMs;
-
- Vector_add(this->processes, p);
- Hashtable_put(this->processTable, p->pid, p);
-
- assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) != -1);
- assert(Hashtable_get(this->processTable, p->pid) != NULL);
- assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable)));
-}
-
-// ProcessList_removeIndex removes Process p from the list's map and soft deletes
-// it from its vector. Vector_compact *must* be called once the caller is done
-// removing items.
-// Should only be called from ProcessList_scan to avoid breaking dying process highlighting.
-static void ProcessList_removeIndex(ProcessList* this, const Process* p, int idx) {
- pid_t pid = p->pid;
-
- assert(p == (Process*)Vector_get(this->processes, idx));
- assert(Hashtable_get(this->processTable, pid) != NULL);
-
- Hashtable_remove(this->processTable, pid);
- Vector_softRemove(this->processes, idx);
-
- if (this->following != -1 && this->following == pid) {
- this->following = -1;
- Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS);
- }
-
- assert(Hashtable_get(this->processTable, pid) == NULL);
- assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable)));
-}
-
-static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, bool show) {
- // On OpenBSD the kernel thread 'swapper' has pid 0.
- // Do not treat it as root of any tree.
- if (pid == 0)
- return;
-
- // The vector is sorted by parent PID, find the start of the range by bisection
- int vsize = Vector_size(this->processes);
- int l = 0;
- int r = vsize;
- while (l < r) {
- int c = (l + r) / 2;
- Process* process = (Process*)Vector_get(this->processes, c);
- pid_t ppid = process->isRoot ? 0 : Process_getParentPid(process);
- if (ppid < pid) {
- l = c + 1;
- } else {
- r = c;
- }
- }
- // Find the end to know the last line for indent handling purposes
- int lastShown = r;
- while (r < vsize) {
- Process* process = (Process*)Vector_get(this->processes, r);
- if (!Process_isChildOf(process, pid))
- break;
- if (process->show)
- lastShown = r;
- r++;
- }
-
- for (int i = l; i < r; i++) {
- Process* process = (Process*)Vector_get(this->processes, i);
-
- if (!show) {
- process->show = false;
- }
-
- Vector_add(this->displayList, process);
-
- int nextIndent = indent | (1 << level);
- ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < lastShown) ? nextIndent : indent, process->show && process->showChildren);
- if (i == lastShown) {
- process->indent = -nextIndent;
- } else {
- process->indent = nextIndent;
- }
-
- process->tree_depth = level + 1;
- }
-}
-
-static int compareProcessByKnownParentThenNatural(const void* v1, const void* v2) {
- const Process* p1 = (const Process*)v1;
- const Process* p2 = (const Process*)v2;
-
- int result = SPACESHIP_NUMBER(
- p1->isRoot ? 0 : Process_getParentPid(p1),
- p2->isRoot ? 0 : Process_getParentPid(p2)
- );
-
- if (result != 0)
- return result;
-
- return Process_compare(v1, v2);
-}
-
-// Builds a sorted tree from scratch, without relying on previously gathered information
-static void ProcessList_buildTree(ProcessList* this) {
- Vector_prune(this->displayList);
-
- // Mark root processes
- int vsize = Vector_size(this->processes);
- for (int i = 0; i < vsize; i++) {
- Process* process = (Process*)Vector_get(this->processes, i);
- pid_t ppid = Process_getParentPid(process);
- process->isRoot = false;
-
- // If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0)
- // on Mac OS X 10.11.6) regard this process as root.
- if (process->pid == ppid) {
- process->isRoot = true;
- continue;
- }
-
- // On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2)
- // use a ppid of 0. As that PID can't exist, we can skip searching for it.
- if (!ppid) {
- process->isRoot = true;
- continue;
- }
-
- // We don't know about its parent for whatever reason
- if (ProcessList_findProcess(this, ppid) == NULL)
- process->isRoot = true;
- }
-
- // Sort by known parent PID (roots first), then PID
- Vector_quickSortCustomCompare(this->processes, compareProcessByKnownParentThenNatural);
-
- // Find all processes whose parent is not visible
- for (int i = 0; i < vsize; i++) {
- Process* process = (Process*)Vector_get(this->processes, i);
-
- // If parent not found, then construct the tree with this node as root
- if (process->isRoot) {
- process = (Process*)Vector_get(this->processes, i);
- process->indent = 0;
- process->tree_depth = 0;
- Vector_add(this->displayList, process);
- ProcessList_buildTreeBranch(this, process->pid, 0, 0, process->showChildren);
- continue;
- }
- }
-
- this->needsSort = false;
-
- // Check consistency of the built structures
- assert(Vector_size(this->displayList) == vsize); (void)vsize;
-}
-
-void ProcessList_updateDisplayList(ProcessList* this) {
- if (this->settings->ss->treeView) {
- if (this->needsSort)
- ProcessList_buildTree(this);
- } else {
- if (this->needsSort)
- Vector_insertionSort(this->processes);
- Vector_prune(this->displayList);
- int size = Vector_size(this->processes);
- for (int i = 0; i < size; i++)
- Vector_add(this->displayList, Vector_get(this->processes, i));
- }
- this->needsSort = false;
-}
-
-ProcessField ProcessList_keyAt(const ProcessList* this, int at) {
- int x = 0;
- const ProcessField* fields = this->settings->ss->fields;
- ProcessField field;
- for (int i = 0; (field = fields[i]); i++) {
- int len = strlen(alignedProcessFieldTitle(this, field));
- if (at >= x && at <= x + len) {
- return field;
- }
- x += len;
- }
- return COMM;
-}
-
-void ProcessList_expandTree(ProcessList* this) {
- int size = Vector_size(this->processes);
- for (int i = 0; i < size; i++) {
- Process* process = (Process*) Vector_get(this->processes, i);
- process->showChildren = true;
- }
-}
-
-// Called on collapse-all toggle and on startup, possibly in non-tree mode
-void ProcessList_collapseAllBranches(ProcessList* this) {
- ProcessList_buildTree(this); // Update `tree_depth` fields of the processes
- this->needsSort = true; // ProcessList is sorted by parent now, force new sort
- int size = Vector_size(this->processes);
- for (int i = 0; i < size; i++) {
- Process* process = (Process*) Vector_get(this->processes, i);
- // 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) {
- ProcessList_updateDisplayList(this);
-
- const char* incFilter = this->incFilter;
-
- const int currPos = Panel_getSelectedIndex(this->panel);
- const int currScrollV = this->panel->scrollV;
- const int currSize = Panel_size(this->panel);
-
- Panel_prune(this->panel);
-
- /* 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->displayList);
- int idx = 0;
- bool foundFollowed = false;
-
- for (int i = 0; i < processCount; i++) {
- Process* p = (Process*) Vector_get(this->displayList, i);
-
- if ( (!p->show)
- || (this->userId != (uid_t) -1 && (p->st_uid != this->userId))
- || (incFilter && !(String_contains_i(Process_getCommand(p), incFilter, true)))
- || (this->pidMatchList && !Hashtable_get(this->pidMatchList, p->tgid)) )
- continue;
-
- Panel_set(this->panel, idx, (Object*)p);
-
- 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 != NULL;
- if (proc) {
- assert(Vector_indexOf(this->processes, proc, Process_pidEqualCompare) != -1);
- assert(proc->pid == pid);
- } else {
- proc = constructor(this->settings);
- assert(proc->cmdline == NULL);
- proc->pid = pid;
- }
- return proc;
-}
-
-void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- ProcessList_goThroughEntries(this, true);
- return;
- }
-
- // mark all process as "dirty"
- for (int i = 0; i < Vector_size(this->processes); i++) {
- Process* p = (Process*) Vector_get(this->processes, i);
- p->updated = false;
- p->wasShown = p->show;
- p->show = true;
- }
-
- this->totalTasks = 0;
- this->userlandThreads = 0;
- this->kernelThreads = 0;
- this->runningTasks = 0;
-
- Process_resetFieldWidths();
-
- // set scan timestamp
- static bool firstScanDone = false;
- if (firstScanDone) {
- Platform_gettime_monotonic(&this->monotonicMs);
- } else {
- this->monotonicMs = 0;
- firstScanDone = true;
- }
-
- ProcessList_goThroughEntries(this, false);
-
- uid_t maxUid = 0;
- for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
- Process* p = (Process*) Vector_get(this->processes, i);
- Process_makeCommandStr(p);
-
- // keep track of the highest UID for column scaling
- if (p->st_uid > maxUid)
- maxUid = p->st_uid;
-
- if (p->tombStampMs > 0) {
- // remove tombed process
- if (this->monotonicMs >= p->tombStampMs) {
- ProcessList_removeIndex(this, p, i);
- }
- } else if (p->updated == false) {
- // process no longer exists
- if (this->settings->highlightChanges && p->wasShown) {
- // mark tombed
- p->tombStampMs = this->monotonicMs + 1000 * this->settings->highlightDelaySecs;
- } else {
- // immediately remove
- ProcessList_removeIndex(this, p, i);
- }
- }
- }
-
- // Compact the processes vector in case of any deletions
- Vector_compact(this->processes);
-
- // Set UID column width based on max UID.
- Process_setUidColumnWidth(maxUid);
-}
diff --git a/ProcessList.h b/ProcessList.h
deleted file mode 100644
index 419dea89..00000000
--- a/ProcessList.h
+++ /dev/null
@@ -1,127 +0,0 @@
-#ifndef HEADER_ProcessList
-#define HEADER_ProcessList
-/*
-htop - ProcessList.h
-(C) 2004,2005 Hisham H. Muhammad
-Released under the GNU GPLv2+, see the COPYING file
-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"
-#include "Object.h"
-#include "Panel.h"
-#include "Process.h"
-#include "RichString.h"
-#include "Settings.h"
-#include "UsersTable.h"
-#include "Vector.h"
-
-#ifdef HAVE_LIBHWLOC
-#include <hwloc.h>
-#endif
-
-
-#ifndef MAX_NAME
-#define MAX_NAME 128
-#endif
-
-#ifndef MAX_READ
-#define MAX_READ 2048
-#endif
-
-typedef unsigned long long int memory_t;
-#define MEMORY_MAX ULLONG_MAX
-
-typedef struct ProcessList_ {
- const Settings* settings;
-
- Vector* processes; /* all known processes; sort order can vary and differ from display order */
- Vector* displayList; /* process tree flattened in display order (borrowed);
- updated in ProcessList_updateDisplayList when rebuilding panel */
- Hashtable* processTable; /* fast known process lookup by PID */
- UsersTable* usersTable;
-
- bool needsSort;
-
- 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;
- const char* incFilter;
- Hashtable* pidMatchList;
-
- #ifdef HAVE_LIBHWLOC
- hwloc_topology_t topology;
- bool topologyOk;
- #endif
-
- unsigned int totalTasks;
- unsigned int runningTasks;
- unsigned int userlandThreads;
- unsigned int kernelThreads;
-
- memory_t totalMem;
- memory_t usedMem;
- memory_t buffersMem;
- memory_t cachedMem;
- memory_t sharedMem;
- memory_t availableMem;
-
- memory_t totalSwap;
- memory_t usedSwap;
- memory_t cachedSwap;
-
- unsigned int activeCPUs;
- unsigned int existingCPUs;
-} ProcessList;
-
-/* 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* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
-
-void ProcessList_done(ProcessList* this);
-
-void ProcessList_setPanel(ProcessList* this, Panel* panel);
-
-void ProcessList_printHeader(const ProcessList* this, RichString* header);
-
-void ProcessList_add(ProcessList* this, Process* p);
-
-void ProcessList_updateDisplayList(ProcessList* this);
-
-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/ProcessLocksScreen.c b/ProcessLocksScreen.c
index b842d2b9..36a37f92 100644
--- a/ProcessLocksScreen.c
+++ b/ProcessLocksScreen.c
@@ -24,10 +24,11 @@ ProcessLocksScreen* ProcessLocksScreen_new(const Process* process) {
ProcessLocksScreen* this = xMalloc(sizeof(ProcessLocksScreen));
Object_setClass(this, Class(ProcessLocksScreen));
if (Process_isThread(process))
- this->pid = process->tgid;
+ this->pid = Process_getThreadGroup(process);
else
- this->pid = process->pid;
- return (ProcessLocksScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " ID TYPE EXCLUSION READ/WRITE DEVICE:INODE START END FILENAME");
+ this->pid = Process_getPid(process);
+
+ return (ProcessLocksScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE EXCLUSION READ/WRITE DEVICE NODE START END FILENAME");
}
void ProcessLocksScreen_delete(Object* this) {
@@ -64,18 +65,18 @@ static void ProcessLocksScreen_scan(InfoScreen* this) {
char entry[512];
if (ULLONG_MAX == data->end) {
- xSnprintf(entry, sizeof(entry), "%10d %-10s %-10s %-10s %02x:%02x:%020"PRIu64" %20"PRIu64" %20s %s",
- data->id,
+ xSnprintf(entry, sizeof(entry), "%5d %-10s %-10s %-10s %#6"PRIx64" %10"PRIu64" %19"PRIu64" %19s %s",
+ data->fd,
data->locktype, data->exclusive, data->readwrite,
- data->dev[0], data->dev[1], data->inode,
+ (uint64_t) data->dev, data->inode,
data->start, "<END OF FILE>",
data->filename ? data->filename : "<N/A>"
);
} else {
- xSnprintf(entry, sizeof(entry), "%10d %-10s %-10s %-10s %02x:%02x:%020"PRIu64" %20"PRIu64" %20"PRIu64" %s",
- data->id,
+ xSnprintf(entry, sizeof(entry), "%5d %-10s %-10s %-10s %#6"PRIx64" %10"PRIu64" %19"PRIu64" %19"PRIu64" %s",
+ data->fd,
data->locktype, data->exclusive, data->readwrite,
- data->dev[0], data->dev[1], data->inode,
+ (uint64_t) data->dev, data->inode,
data->start, data->end,
data->filename ? data->filename : "<N/A>"
);
diff --git a/ProcessLocksScreen.h b/ProcessLocksScreen.h
index cf34de4f..417df7b2 100644
--- a/ProcessLocksScreen.h
+++ b/ProcessLocksScreen.h
@@ -26,8 +26,8 @@ typedef struct FileLocks_Data_ {
char* exclusive;
char* readwrite;
char* filename;
- int id;
- unsigned int dev[2];
+ int fd;
+ dev_t dev;
uint64_t inode;
uint64_t start;
uint64_t end;
diff --git a/ProcessTable.c b/ProcessTable.c
new file mode 100644
index 00000000..ac6fc705
--- /dev/null
+++ b/ProcessTable.c
@@ -0,0 +1,92 @@
+/*
+htop - ProcessTable.c
+(C) 2004,2005 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "ProcessTable.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "Hashtable.h"
+#include "Row.h"
+#include "Settings.h"
+#include "Vector.h"
+
+
+void ProcessTable_init(ProcessTable* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList) {
+ Table_init(&this->super, klass, host);
+
+ this->pidMatchList = pidMatchList;
+}
+
+void ProcessTable_done(ProcessTable* this) {
+ Table_done(&this->super);
+}
+
+Process* ProcessTable_getProcess(ProcessTable* this, pid_t pid, bool* preExisting, Process_New constructor) {
+ const Table* table = &this->super;
+ Process* proc = (Process*) Hashtable_get(table->table, pid);
+ *preExisting = proc != NULL;
+ if (proc) {
+ assert(Vector_indexOf(table->rows, proc, Row_idEqualCompare) != -1);
+ assert(Process_getPid(proc) == pid);
+ } else {
+ proc = constructor(table->host);
+ assert(proc->cmdline == NULL);
+ Process_setPid(proc, pid);
+ }
+ return proc;
+}
+
+static void ProcessTable_prepareEntries(Table* super) {
+ ProcessTable* this = (ProcessTable*) super;
+ this->totalTasks = 0;
+ this->userlandThreads = 0;
+ this->kernelThreads = 0;
+ this->runningTasks = 0;
+
+ Table_prepareEntries(super);
+}
+
+static void ProcessTable_iterateEntries(Table* super) {
+ ProcessTable* this = (ProcessTable*) super;
+ // calling into platform-specific code
+ ProcessTable_goThroughEntries(this);
+}
+
+static void ProcessTable_cleanupEntries(Table* super) {
+ Machine* host = super->host;
+ const Settings* settings = host->settings;
+
+ // Finish process table update, culling any exit'd processes
+ for (int i = Vector_size(super->rows) - 1; i >= 0; i--) {
+ Process* p = (Process*) Vector_get(super->rows, i);
+
+ // tidy up Process state after refreshing the ProcessTable table
+ Process_makeCommandStr(p, settings);
+
+ // keep track of the highest UID for column scaling
+ if (p->st_uid > host->maxUserId)
+ host->maxUserId = p->st_uid;
+
+ Table_cleanupRow(super, (Row*) p, i);
+ }
+
+ // compact the table in case of deletions
+ Table_compact(super);
+}
+
+const TableClass ProcessTable_class = {
+ .super = {
+ .extends = Class(Table),
+ .delete = ProcessTable_delete,
+ },
+ .prepare = ProcessTable_prepareEntries,
+ .iterate = ProcessTable_iterateEntries,
+ .cleanup = ProcessTable_cleanupEntries,
+};
diff --git a/ProcessTable.h b/ProcessTable.h
new file mode 100644
index 00000000..96a517a0
--- /dev/null
+++ b/ProcessTable.h
@@ -0,0 +1,52 @@
+#ifndef HEADER_ProcessTable
+#define HEADER_ProcessTable
+/*
+htop - ProcessTable.h
+(C) 2004,2005 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "Hashtable.h"
+#include "Machine.h"
+#include "Object.h"
+#include "Process.h"
+#include "Table.h"
+
+
+typedef struct ProcessTable_ {
+ Table super;
+
+ Hashtable* pidMatchList;
+
+ unsigned int totalTasks;
+ unsigned int runningTasks;
+ unsigned int userlandThreads;
+ unsigned int kernelThreads;
+} ProcessTable;
+
+/* Implemented by platforms */
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList);
+void ProcessTable_delete(Object* cast);
+void ProcessTable_goThroughEntries(ProcessTable* this);
+
+void ProcessTable_init(ProcessTable* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList);
+
+void ProcessTable_done(ProcessTable* this);
+
+extern const TableClass ProcessTable_class;
+
+static inline void ProcessTable_add(ProcessTable* this, Process* process) {
+ Table_add(&this->super, &process->super);
+}
+
+Process* ProcessTable_getProcess(ProcessTable* this, pid_t pid, bool* preExisting, Process_New constructor);
+
+static inline Process* ProcessTable_findProcess(ProcessTable* this, pid_t pid) {
+ return (Process*) Table_findRow(&this->super, pid);
+}
+
+#endif
diff --git a/ProvideCurses.h b/ProvideCurses.h
index 06602ff0..7ae99e62 100644
--- a/ProvideCurses.h
+++ b/ProvideCurses.h
@@ -1,14 +1,14 @@
#ifndef HEADER_ProvideCurses
#define HEADER_ProvideCurses
/*
-htop - RichString.h
+htop - ProvideCurses.h
(C) 2004,2011 Hisham H. Muhammad
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h"
+#include "config.h" // IWYU pragma: keep
// IWYU pragma: begin_exports
diff --git a/ProvideTerm.h b/ProvideTerm.h
new file mode 100644
index 00000000..a8910f1a
--- /dev/null
+++ b/ProvideTerm.h
@@ -0,0 +1,24 @@
+#ifndef HEADER_ProvideTerm
+#define HEADER_ProvideTerm
+/*
+htop - ProvideTerm.h
+(C) 2023 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
+
+// IWYU pragma: begin_exports
+
+#if defined(HAVE_NCURSESW_TERM_H)
+#include <ncursesw/term.h>
+#elif defined(HAVE_NCURSES_TERM_H)
+#include <ncurses/term.h>
+#elif defined(HAVE_TERM_H)
+#include <term.h>
+#endif
+
+// IWYU pragma: end_exports
+
+#endif // HEADER_ProvideTerm
diff --git a/README b/README
index 55372b43..42061c01 100644..120000
--- a/README
+++ b/README
@@ -1,187 +1 @@
-# [![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://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)
-
-## Introduction
-
-`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 libncurses(w).
-
-`htop` is written in C.
-
-For more information and details visit [htop.dev](https://htop.dev).
-
-## Build instructions
-
-### Prerequisite
-List of build-time dependencies:
- * standard GNU autotools-based C toolchain
- - 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` (v2.21 or later)
-* `libnl-3`
-
-Install these and other required packages for C development from your package manager.
-
-**Debian/Ubuntu**
-~~~ shell
-sudo apt install libncursesw5-dev autotools-dev autoconf build-essential
-~~~
-
-**Fedora/RHEL**
-~~~ shell
-sudo dnf install ncurses-devel automake autoconf gcc
-~~~
-
-**Archlinux/Manjaro**
-~~~ shell
-sudo pacman -S ncurses automake autoconf gcc
-~~~
-
-**macOS**
-~~~ shell
-brew install ncurses automake autoconf gcc
-~~~
-
-### Compile from source:
-To compile from source, download from the Git repository (`git clone` or downloads from [GitHub releases](https://github.com/htop-dev/htop/releases/)), then run:
-~~~ shell
-./autogen.sh && ./configure && make
-~~~
-
-### Install
-To install on the local system run `make install`. By default `make install` installs into `/usr/local`. To change this path use `./configure --prefix=/some/path`.
-
-### Build Options
-
-`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.
-
-## 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`.
-
-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 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](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.
-
-## License
-
-GNU General Public License, version 2 (GPL-2.0) or, at your option, any later version.
+README.md \ No newline at end of file
diff --git a/README.md b/README.md
index 100b9382..40887254 120000..100644
--- a/README.md
+++ b/README.md
@@ -1 +1,189 @@
-README \ No newline at end of file
+# [![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://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)
+
+## Introduction
+
+`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 libncurses(w).
+
+`htop` is written in C.
+
+For more information and details visit [htop.dev](https://htop.dev).
+
+## Build instructions
+
+### Prerequisite
+List of build-time dependencies:
+ * standard GNU autotools-based C toolchain
+ - C99 compliant compiler
+ - `autoconf`
+ - `automake`
+ - `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` (v2.21 or later)
+* `libnl-3` and `libnl-genl-3`
+
+Install these and other required packages for C development from your package manager.
+
+**Debian/Ubuntu**
+~~~ shell
+sudo apt install libncursesw5-dev autotools-dev autoconf automake build-essential
+~~~
+
+**Fedora/RHEL**
+~~~ shell
+sudo dnf install ncurses-devel automake autoconf gcc
+~~~
+
+**Archlinux/Manjaro**
+~~~ shell
+sudo pacman -S ncurses automake autoconf gcc
+~~~
+
+**macOS**
+~~~ shell
+brew install ncurses automake autoconf gcc
+~~~
+
+### Compile from source:
+To compile from source, download from the Git repository (`git clone` or downloads from [GitHub releases](https://github.com/htop-dev/htop/releases/)), then run:
+~~~ shell
+./autogen.sh && ./configure && make
+~~~
+
+### Install
+To install on the local system run `make install`. By default `make install` installs into `/usr/local`. To change this path use `./configure --prefix=/some/path`.
+
+### Build Options
+
+`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: *libnl-3-dev*(build-time) and *libnl-genl-3-dev*(build-time), at runtime *libnl-3* and *libnl-genl-3* are loaded via `dlopen(3)` if available and requested
+ - 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.
+* `libnl-3` and `libnl-genl-3`, if `htop` was configured with `--enable-delayacct` and delay accounting process fields are active.
+
+`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.
+
+## 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`.
+
+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 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](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.
+
+## License
+
+GNU General Public License, version 2 (GPL-2.0) or, at your option, any later version.
diff --git a/RichString.c b/RichString.c
index 3dac0832..556674b0 100644
--- a/RichString.c
+++ b/RichString.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 "RichString.h"
#include <ctype.h>
@@ -18,18 +20,26 @@ in the source distribution for its full text.
#define charBytes(n) (sizeof(CharType) * (n))
static void RichString_extendLen(RichString* this, int len) {
- if (this->chlen <= RICHSTRING_MAXLEN) {
+ if (this->chptr == this->chstr) {
+ // String is in internal buffer
if (len > RICHSTRING_MAXLEN) {
+ // Copy from internal buffer to allocated string
this->chptr = xMalloc(charBytes(len + 1));
memcpy(this->chptr, this->chstr, charBytes(this->chlen));
+ } else {
+ // Still fits in internal buffer, do nothing
+ assert(this->chlen <= RICHSTRING_MAXLEN);
}
} else {
- if (len <= RICHSTRING_MAXLEN) {
+ // String is managed externally
+ if (len > RICHSTRING_MAXLEN) {
+ // Just reallocate the buffer accordingly
+ this->chptr = xRealloc(this->chptr, charBytes(len + 1));
+ } else {
+ // Move string into internal buffer and free resources
memcpy(this->chstr, this->chptr, charBytes(len));
free(this->chptr);
this->chptr = this->chstr;
- } else {
- this->chptr = xRealloc(this->chptr, charBytes(len + 1));
}
}
@@ -52,24 +62,57 @@ void RichString_rewind(RichString* this, int count) {
#ifdef HAVE_LIBNCURSESW
+static size_t mbstowcs_nonfatal(wchar_t* dest, const char* src, size_t n) {
+ size_t written = 0;
+ mbstate_t ps = { 0 };
+ bool broken = false;
+
+ while (n > 0) {
+ size_t ret = mbrtowc(dest, src, n, &ps);
+ if (ret == (size_t)-1 || ret == (size_t)-2) {
+ if (!broken) {
+ broken = true;
+ *dest++ = L'\xFFFD';
+ written++;
+ }
+ src++;
+ n--;
+ continue;
+ }
+
+ broken = false;
+
+ if (ret == 0) {
+ break;
+ }
+
+ dest++;
+ written++;
+ src += ret;
+ n -= ret;
+ }
+
+ return written;
+}
+
static inline int RichString_writeFromWide(RichString* this, int attrs, const char* data_c, int from, int len) {
- wchar_t data[len + 1];
- len = mbstowcs(data, data_c, len);
+ wchar_t data[len];
+ len = mbstowcs_nonfatal(data, data_c, len);
if (len <= 0)
return 0;
int newLen = from + len;
RichString_setLen(this, newLen);
for (int i = from, j = 0; i < newLen; i++, j++) {
- this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (iswprint(data[j]) ? data[j] : '?') } };
+ this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (iswprint(data[j]) ? data[j] : L'\xFFFD') } };
}
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);
+ wchar_t data[len];
+ len = mbstowcs_nonfatal(data, data_c, len);
if (len <= 0)
return 0;
@@ -79,7 +122,7 @@ int RichString_appendnWideColumns(RichString* this, int attrs, const char* data_
int columnsWritten = 0;
int pos = from;
for (int j = 0; j < len; j++) {
- wchar_t c = iswprint(data[j]) ? data[j] : '?';
+ wchar_t c = iswprint(data[j]) ? data[j] : L'\xFFFD';
int cwidth = wcwidth(c);
if (cwidth > *columns)
break;
@@ -101,7 +144,7 @@ static inline int RichString_writeFromAscii(RichString* this, int attrs, const c
int newLen = from + len;
RichString_setLen(this, newLen);
for (int i = from, j = 0; i < newLen; i++, j++) {
- this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (isprint(data[j]) ? data[j] : '?') } };
+ this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (isprint(data[j]) ? data[j] : L'\xFFFD') } };
}
return len;
diff --git a/RichString.h b/RichString.h
index cbcbe48b..7783378b 100644
--- a/RichString.h
+++ b/RichString.h
@@ -7,8 +7,7 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h"
-
+#include "Macros.h"
#include "ProvideCurses.h"
@@ -18,7 +17,7 @@ in the source distribution for its full text.
#define RichString_begin(this) RichString this; RichString_beginAllocated(this)
#define RichString_beginAllocated(this) \
do { \
- (this).chlen = 0, \
+ (this).chlen = 0; \
(this).chptr = (this).chstr; \
RichString_setChar(&(this), 0, 0); \
(this).highlightAttr = 0; \
@@ -63,6 +62,7 @@ void RichString_appendChr(RichString* this, int attrs, char c, int count);
int RichString_appendWide(RichString* this, int attrs, const char* data);
+ATTR_ACCESS3_R(3, 4)
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. */
@@ -72,6 +72,7 @@ int RichString_writeWide(RichString* this, int attrs, const char* data);
int RichString_appendAscii(RichString* this, int attrs, const char* data);
+ATTR_ACCESS3_R(3, 4)
int RichString_appendnAscii(RichString* this, int attrs, const char* data, int len);
int RichString_writeAscii(RichString* this, int attrs, const char* data);
diff --git a/Row.c b/Row.c
new file mode 100644
index 00000000..d795787e
--- /dev/null
+++ b/Row.c
@@ -0,0 +1,560 @@
+/*
+htop - Row.c
+(C) 2004-2015 Hisham H. Muhammad
+(C) 2020-2023 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 "Row.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "CRT.h"
+#include "DynamicColumn.h"
+#include "Hashtable.h"
+#include "Machine.h"
+#include "Macros.h"
+#include "Process.h"
+#include "RichString.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+
+int Row_pidDigits = ROW_MIN_PID_DIGITS;
+int Row_uidDigits = ROW_MIN_UID_DIGITS;
+
+void Row_init(Row* this, const Machine* host) {
+ this->host = host;
+ this->tag = false;
+ this->showChildren = true;
+ this->show = true;
+ this->wasShown = false;
+ this->updated = false;
+}
+
+void Row_done(Row* this) {
+ assert(this != NULL);
+ (void) this;
+}
+
+static inline bool Row_isNew(const Row* this) {
+ const Machine* host = this->host;
+ if (host->monotonicMs < this->seenStampMs)
+ return false;
+
+ const Settings* settings = host->settings;
+ return host->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)settings->highlightDelaySecs;
+}
+
+static inline bool Row_isTomb(const Row* this) {
+ return this->tombStampMs > 0;
+}
+
+void Row_display(const Object* cast, RichString* out) {
+ const Row* this = (const Row*) cast;
+ const Settings* settings = this->host->settings;
+ const RowField* fields = settings->ss->fields;
+
+ for (int i = 0; fields[i]; i++)
+ As_Row(this)->writeField(this, out, fields[i]);
+
+ if (Row_isHighlighted(this))
+ RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]);
+
+ if (this->tag == true)
+ RichString_setAttr(out, CRT_colors[PROCESS_TAG]);
+
+ if (settings->highlightChanges) {
+ if (Row_isTomb(this))
+ out->highlightAttr = CRT_colors[PROCESS_TOMB];
+ else if (Row_isNew(this))
+ out->highlightAttr = CRT_colors[PROCESS_NEW];
+ }
+
+ assert(RichString_size(out) > 0);
+}
+
+void Row_setPidColumnWidth(pid_t maxPid) {
+ if (maxPid < (int)pow(10, ROW_MIN_PID_DIGITS)) {
+ Row_pidDigits = ROW_MIN_PID_DIGITS;
+ return;
+ }
+
+ Row_pidDigits = (int)log10(maxPid) + 1;
+ assert(Row_pidDigits <= ROW_MAX_PID_DIGITS);
+}
+
+void Row_setUidColumnWidth(uid_t maxUid) {
+ if (maxUid < (uid_t)pow(10, ROW_MIN_UID_DIGITS)) {
+ Row_uidDigits = ROW_MIN_UID_DIGITS;
+ return;
+ }
+
+ Row_uidDigits = (int)log10(maxUid) + 1;
+ assert(Row_uidDigits <= ROW_MAX_UID_DIGITS);
+}
+
+uint8_t Row_fieldWidths[LAST_PROCESSFIELD] = { 0 };
+
+void Row_resetFieldWidths(void) {
+ for (size_t i = 0; i < LAST_PROCESSFIELD; i++) {
+ if (!Process_fields[i].autoWidth)
+ continue;
+
+ size_t len = strlen(Process_fields[i].title);
+ assert(len <= UINT8_MAX);
+ Row_fieldWidths[i] = (uint8_t)len;
+ }
+}
+
+void Row_updateFieldWidth(RowField key, size_t width) {
+ if (width > UINT8_MAX)
+ Row_fieldWidths[key] = UINT8_MAX;
+ else if (width > Row_fieldWidths[key])
+ Row_fieldWidths[key] = (uint8_t)width;
+}
+
+// helper function to fill an aligned title string for a dynamic column
+static const char* alignedTitleDynamicColumn(const Settings* settings, int key, char* titleBuffer, size_t titleBufferSize) {
+ const DynamicColumn* column = Hashtable_get(settings->dynamicColumns, key);
+ if (column == NULL)
+ return "- ";
+
+ int width = column->width;
+ if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
+ width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
+
+ xSnprintf(titleBuffer, titleBufferSize, "%*s ", width, column->heading);
+ return titleBuffer;
+}
+
+// helper function to fill an aligned title string for a process field
+static const char* alignedTitleProcessField(ProcessField field, char* titleBuffer, size_t titleBufferSize) {
+ const char* title = Process_fields[field].title;
+ if (!title)
+ return "- ";
+
+ if (Process_fields[field].pidColumn) {
+ xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_pidDigits, title);
+ return titleBuffer;
+ }
+
+ if (field == ST_UID) {
+ xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_uidDigits, title);
+ return titleBuffer;
+ }
+
+ if (Process_fields[field].autoWidth) {
+ if (Process_fields[field].autoTitleRightAlign)
+ xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_fieldWidths[field], title);
+ else
+ xSnprintf(titleBuffer, titleBufferSize, "%-*.*s ", Row_fieldWidths[field], Row_fieldWidths[field], title);
+ return titleBuffer;
+ }
+
+ return title;
+}
+
+// helper function to create an aligned title string for a given field
+const char* RowField_alignedTitle(const Settings* settings, RowField field) {
+ static char titleBuffer[UINT8_MAX + sizeof(" ")];
+ assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" "));
+ assert(sizeof(titleBuffer) >= ROW_MAX_PID_DIGITS + sizeof(" "));
+ assert(sizeof(titleBuffer) >= ROW_MAX_UID_DIGITS + sizeof(" "));
+
+ if (field < LAST_PROCESSFIELD)
+ return alignedTitleProcessField((ProcessField)field, titleBuffer, sizeof(titleBuffer));
+ return alignedTitleDynamicColumn(settings, field, titleBuffer, sizeof(titleBuffer));
+}
+
+RowField RowField_keyAt(const Settings* settings, int at) {
+ const RowField* fields = (const RowField*) settings->ss->fields;
+ RowField field;
+ int x = 0;
+ for (int i = 0; (field = fields[i]); i++) {
+ int len = strlen(RowField_alignedTitle(settings, field));
+ if (at >= x && at <= x + len) {
+ return field;
+ }
+ x += len;
+ }
+ return COMM;
+}
+
+void Row_printKBytes(RichString* str, unsigned long long number, bool coloring) {
+ char buffer[16];
+ int len;
+
+ int color = CRT_colors[PROCESS];
+ int nextUnitColor = CRT_colors[PROCESS];
+
+ const int colors[4] = {
+ [0] = CRT_colors[PROCESS],
+ [1] = CRT_colors[PROCESS_MEGABYTES],
+ [2] = CRT_colors[PROCESS_GIGABYTES],
+ [3] = CRT_colors[LARGE_NUMBER]
+ };
+
+ if (number == ULLONG_MAX)
+ goto invalidNumber;
+
+ if (coloring) {
+ color = colors[0];
+ nextUnitColor = colors[1];
+ }
+
+ if (number < 1000) {
+ // Plain number, no markings
+ len = xSnprintf(buffer, sizeof(buffer), "%5u ", (unsigned int)number);
+ RichString_appendnAscii(str, color, buffer, len);
+ return;
+ }
+
+ if (number < 100000) {
+ // 2 digits for M, 3 digits for K
+ len = xSnprintf(buffer, sizeof(buffer), "%2u", (unsigned int)(number / 1000));
+ RichString_appendnAscii(str, nextUnitColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%03u ", (unsigned int)(number % 1000));
+ RichString_appendnAscii(str, color, buffer, len);
+ return;
+ }
+
+ // 100000 KiB (97.6 MiB) or greater. A unit prefix would be added.
+ const size_t maxUnitIndex = (sizeof(number) * CHAR_BIT - 1) / 10 + 1;
+ const bool canOverflow = maxUnitIndex >= ARRAYSIZE(unitPrefixes);
+
+ size_t i = 1;
+ int prevUnitColor;
+ // Convert KiB to (1/100) of MiB
+ unsigned long long hundredths = (number / 256) * 25 + (number % 256) * 25 / 256;
+ while (true) {
+ if (canOverflow && i >= ARRAYSIZE(unitPrefixes))
+ goto invalidNumber;
+
+ prevUnitColor = color;
+ color = nextUnitColor;
+
+ if (coloring && i + 1 < ARRAYSIZE(colors))
+ nextUnitColor = colors[i + 1];
+
+ if (hundredths < 1000000)
+ break;
+
+ hundredths /= ONE_K;
+ i++;
+ }
+
+ number = hundredths / 100;
+ hundredths %= 100;
+ if (number < 100) {
+ if (number < 10) {
+ // 1 digit + decimal point + 2 digits
+ // "9.76G", "9.99G", "9.76T", "9.99T", etc.
+ len = xSnprintf(buffer, sizeof(buffer), "%1u", (unsigned int)number);
+ RichString_appendnAscii(str, color, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), ".%02u", (unsigned int)hundredths);
+ } else {
+ // 2 digits + decimal point + 1 digit
+ // "97.6M", "99.9M", "10.0G", "99.9G", etc.
+ len = xSnprintf(buffer, sizeof(buffer), "%2u", (unsigned int)number);
+ RichString_appendnAscii(str, color, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), ".%1u", (unsigned int)hundredths / 10);
+ }
+ RichString_appendnAscii(str, prevUnitColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%c ", unitPrefixes[i]);
+ } else if (number < 1000) {
+ // 3 digits
+ // "100M", "999M", "100G", "999G", etc.
+ len = xSnprintf(buffer, sizeof(buffer), "%4u%c ", (unsigned int)number, unitPrefixes[i]);
+ } else {
+ // 1 digit + 3 digits
+ // "1000M", "9999M", "1000G", "9999G", etc.
+ assert(number < 10000);
+
+ len = xSnprintf(buffer, sizeof(buffer), "%1u", (unsigned int)number / 1000);
+ RichString_appendnAscii(str, nextUnitColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%03u%c ", (unsigned int)number % 1000, unitPrefixes[i]);
+ }
+ RichString_appendnAscii(str, color, buffer, len);
+ return;
+
+invalidNumber:
+ if (coloring)
+ color = CRT_colors[PROCESS_SHADOW];
+
+ RichString_appendAscii(str, color, " N/A ");
+}
+
+void Row_printBytes(RichString* str, unsigned long long number, bool coloring) {
+ if (number == ULLONG_MAX)
+ Row_printKBytes(str, ULLONG_MAX, coloring);
+ else
+ Row_printKBytes(str, number / ONE_K, coloring);
+}
+
+void Row_printCount(RichString* str, unsigned long long number, bool coloring) {
+ char buffer[13];
+
+ int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
+ int megabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
+ int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
+ int baseColor = CRT_colors[PROCESS];
+
+ if (number == ULLONG_MAX) {
+ RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A ");
+ } else if (number >= 100000LL * ONE_DECIMAL_T) {
+ xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G);
+ RichString_appendnAscii(str, largeNumberColor, buffer, 12);
+ } 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, megabytesColor, 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, megabytesColor, buffer + 5, 3);
+ RichString_appendnAscii(str, baseColor, buffer + 8, 4);
+ } else {
+ xSnprintf(buffer, sizeof(buffer), "%11llu ", number);
+ RichString_appendnAscii(str, largeNumberColor, buffer, 2);
+ RichString_appendnAscii(str, megabytesColor, buffer + 2, 3);
+ RichString_appendnAscii(str, baseColor, buffer + 5, 3);
+ RichString_appendnAscii(str, shadowColor, buffer + 8, 4);
+ }
+}
+
+void Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) {
+ char buffer[10];
+ int len;
+
+ if (totalHundredths == 0) {
+ int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
+
+ RichString_appendAscii(str, shadowColor, " 0:00.00 ");
+ return;
+ }
+
+ 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 baseColor = CRT_colors[PROCESS];
+
+ unsigned long long totalSeconds = totalHundredths / 100;
+ unsigned long long totalMinutes = totalSeconds / 60;
+ unsigned long long totalHours = totalMinutes / 60;
+ unsigned int seconds = totalSeconds % 60;
+ unsigned int minutes = totalMinutes % 60;
+
+ if (totalMinutes < 60) {
+ unsigned int hundredths = totalHundredths % 100;
+ len = xSnprintf(buffer, sizeof(buffer), "%2u:%02u.%02u ", (unsigned int)totalMinutes, seconds, hundredths);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ return;
+ }
+ if (totalHours < 24) {
+ len = xSnprintf(buffer, sizeof(buffer), "%2uh", (unsigned int)totalHours);
+ RichString_appendnAscii(str, hourColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%02u:%02u ", minutes, seconds);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ return;
+ }
+
+ unsigned long long totalDays = totalHours / 24;
+ unsigned int hours = totalHours % 24;
+ if (totalDays < 10) {
+ len = xSnprintf(buffer, sizeof(buffer), "%1ud", (unsigned int)totalDays);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%02uh", hours);
+ RichString_appendnAscii(str, hourColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%02um ", minutes);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ return;
+ }
+ if (totalDays < /* Ignore leap years */365) {
+ len = xSnprintf(buffer, sizeof(buffer), "%4ud", (unsigned int)totalDays);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%02uh ", hours);
+ RichString_appendnAscii(str, hourColor, buffer, len);
+ return;
+ }
+
+ unsigned long long years = totalDays / 365;
+ unsigned int days = totalDays % 365;
+ if (years < 1000) {
+ len = xSnprintf(buffer, sizeof(buffer), "%3uy", (unsigned int)years);
+ RichString_appendnAscii(str, yearColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%03ud ", days);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ } else if (years < 10000000) {
+ len = xSnprintf(buffer, sizeof(buffer), "%7luy ", (unsigned long)years);
+ RichString_appendnAscii(str, yearColor, buffer, len);
+ } else {
+ RichString_appendAscii(str, yearColor, "eternity ");
+ }
+}
+
+void Row_printNanoseconds(RichString* str, unsigned long long totalNanoseconds, bool coloring) {
+ if (totalNanoseconds == 0) {
+ int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
+
+ RichString_appendAscii(str, shadowColor, " 0ns ");
+ return;
+ }
+
+ char buffer[10];
+ int len;
+ int baseColor = CRT_colors[PROCESS];
+
+ if (totalNanoseconds < 1000000) {
+ len = xSnprintf(buffer, sizeof(buffer), "%6luns ", (unsigned long)totalNanoseconds);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ return;
+ }
+
+ unsigned long long totalMicroseconds = totalNanoseconds / 1000;
+ if (totalMicroseconds < 1000000) {
+ len = xSnprintf(buffer, sizeof(buffer), ".%06lus ", (unsigned long)totalMicroseconds);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ return;
+ }
+
+ unsigned long long totalSeconds = totalMicroseconds / 1000000;
+ unsigned long microseconds = totalMicroseconds % 1000000;
+ if (totalSeconds < 60) {
+ int width = 5;
+ unsigned long fraction = microseconds / 10;
+ if (totalSeconds >= 10) {
+ width--;
+ fraction /= 10;
+ }
+ len = xSnprintf(buffer, sizeof(buffer), "%u.%0*lus ", (unsigned int)totalSeconds, width, fraction);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ return;
+ }
+
+ if (totalSeconds < 600) {
+ unsigned int minutes = totalSeconds / 60;
+ unsigned int seconds = totalSeconds % 60;
+ unsigned int milliseconds = microseconds / 1000;
+ len = xSnprintf(buffer, sizeof(buffer), "%u:%02u.%03u ", minutes, seconds, milliseconds);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ return;
+ }
+
+ unsigned long long totalHundredths = totalMicroseconds / 1000 / 10;
+ Row_printTime(str, totalHundredths, coloring);
+}
+
+void Row_printRate(RichString* str, double rate, bool coloring) {
+ char buffer[16];
+
+ int largeNumberColor = CRT_colors[LARGE_NUMBER];
+ int megabytesColor = CRT_colors[PROCESS_MEGABYTES];
+ int shadowColor = CRT_colors[PROCESS_SHADOW];
+ int baseColor = CRT_colors[PROCESS];
+
+ if (!coloring) {
+ largeNumberColor = CRT_colors[PROCESS];
+ megabytesColor = CRT_colors[PROCESS];
+ }
+
+ if (!isNonnegative(rate)) {
+ 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, sizeof(buffer), "%7.2f B/s ", rate);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ } else if (rate < ONE_M) {
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K);
+ RichString_appendnAscii(str, baseColor, buffer, len);
+ } else if (rate < ONE_G) {
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M);
+ RichString_appendnAscii(str, megabytesColor, buffer, len);
+ } else if (rate < ONE_T) {
+ 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, sizeof(buffer), "%7.2f P/s ", rate / ONE_P);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
+ }
+}
+
+void Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) {
+ int columns = width;
+ RichString_appendnWideColumns(str, attr, content, strlen(content), &columns);
+ RichString_appendChr(str, attr, ' ', width + 1 - columns);
+}
+
+int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) {
+ assert(n >= 6 && width >= 4 && "Invalid width in Row_printPercentage()");
+ // truncate in favour of abort in xSnprintf()
+ width = (uint8_t)CLAMP(width, 4, n - 2);
+ assert(width < n - 1 && "Insufficient space to print column");
+
+ if (isNonnegative(val)) {
+ if (val < 0.05F)
+ *attr = CRT_colors[PROCESS_SHADOW];
+ else if (val >= 99.9F)
+ *attr = CRT_colors[PROCESS_MEGABYTES];
+
+ int precision = 1;
+
+ // Display "val" as "100" for columns like "MEM%".
+ if (width == 4 && val > 99.9F) {
+ precision = 0;
+ val = 100.0F;
+ }
+
+ return xSnprintf(buffer, n, "%*.*f ", width, precision, val);
+ }
+
+ *attr = CRT_colors[PROCESS_SHADOW];
+ return xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
+}
+
+void Row_toggleTag(Row* this) {
+ this->tag = !this->tag;
+}
+
+int Row_compare(const void* v1, const void* v2) {
+ const Row* r1 = (const Row*)v1;
+ const Row* r2 = (const Row*)v2;
+
+ return SPACESHIP_NUMBER(r1->id, r2->id);
+}
+
+int Row_compareByParent_Base(const void* v1, const void* v2) {
+ const Row* r1 = (const Row*)v1;
+ const Row* r2 = (const Row*)v2;
+
+ int result = SPACESHIP_NUMBER(
+ r1->isRoot ? 0 : Row_getGroupOrParent(r1),
+ r2->isRoot ? 0 : Row_getGroupOrParent(r2)
+ );
+
+ if (result != 0)
+ return result;
+
+ return Row_compare(v1, v2);
+}
+
+const RowClass Row_class = {
+ .super = {
+ .extends = Class(Object),
+ .compare = Row_compare
+ },
+};
diff --git a/Row.h b/Row.h
new file mode 100644
index 00000000..6d882909
--- /dev/null
+++ b/Row.h
@@ -0,0 +1,184 @@
+#ifndef HEADER_Row
+#define HEADER_Row
+/*
+htop - Row.h
+(C) 2004-2015 Hisham H. Muhammad
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "Object.h"
+#include "RichString.h"
+#include "RowField.h"
+
+
+extern uint8_t Row_fieldWidths[LAST_RESERVED_FIELD];
+#define ROW_MIN_PID_DIGITS 5
+#define ROW_MAX_PID_DIGITS 19
+#define ROW_MIN_UID_DIGITS 5
+#define ROW_MAX_UID_DIGITS 20
+extern int Row_pidDigits;
+extern int Row_uidDigits;
+
+struct Machine_; // IWYU pragma: keep
+struct Settings_; // IWYU pragma: keep
+struct Table_; // IWYU pragma: keep
+
+/* Class representing entities (such as processes) that can be
+ * represented in a tabular form in the lower half of the htop
+ * display. */
+
+typedef struct Row_ {
+ /* Super object for emulated OOP */
+ Object super;
+
+ /* Pointer to quasi-global data */
+ const struct Machine_* host;
+
+ int id;
+ int group;
+ int parent;
+
+ /* Has no known parent */
+ bool isRoot;
+
+ /* Whether the row was tagged by the user */
+ bool tag;
+
+ /* Whether to display this row */
+ bool show;
+
+ /* Whether this row was shown last cycle */
+ bool wasShown;
+
+ /* Whether to show children of this row in tree-mode */
+ bool showChildren;
+
+ /* Whether the row was updated during the last scan */
+ bool updated;
+
+ /*
+ * Internal state for tree-mode.
+ */
+ int32_t indent;
+ unsigned int tree_depth;
+
+ /*
+ * Internal time counts for showing new and exited processes.
+ */
+ uint64_t seenStampMs;
+ uint64_t tombStampMs;
+} Row;
+
+typedef Row* (*Row_New)(const struct Machine_*);
+typedef void (*Row_WriteField)(const Row*, RichString*, RowField);
+typedef bool (*Row_IsHighlighted)(const Row*);
+typedef bool (*Row_IsVisible)(const Row*, const struct Table_*);
+typedef bool (*Row_MatchesFilter)(const Row*, const struct Table_*);
+typedef const char* (*Row_SortKeyString)(Row*);
+typedef int (*Row_CompareByParent)(const Row*, const Row*);
+
+int Row_compare(const void* v1, const void* v2);
+
+typedef struct RowClass_ {
+ const ObjectClass super;
+ const Row_IsHighlighted isHighlighted;
+ const Row_IsVisible isVisible;
+ const Row_WriteField writeField;
+ const Row_MatchesFilter matchesFilter;
+ const Row_SortKeyString sortKeyString;
+ const Row_CompareByParent compareByParent;
+} RowClass;
+
+#define As_Row(this_) ((const RowClass*)((this_)->super.klass))
+
+#define Row_isHighlighted(r_) (As_Row(r_)->isHighlighted ? (As_Row(r_)->isHighlighted(r_)) : false)
+#define Row_isVisible(r_, t_) (As_Row(r_)->isVisible ? (As_Row(r_)->isVisible(r_, t_)) : true)
+#define Row_matchesFilter(r_, t_) (As_Row(r_)->matchesFilter ? (As_Row(r_)->matchesFilter(r_, t_)) : false)
+#define Row_sortKeyString(r_) (As_Row(r_)->sortKeyString ? (As_Row(r_)->sortKeyString(r_)) : "")
+#define Row_compareByParent(r1_, r2_) (As_Row(r1_)->compareByParent ? (As_Row(r1_)->compareByParent(r1_, r2_)) : Row_compareByParent_Base(r1_, r2_))
+
+#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)
+
+extern const RowClass Row_class;
+
+void Row_init(Row* this, const struct Machine_* host);
+
+void Row_done(Row* this);
+
+void Row_display(const Object* cast, RichString* out);
+
+void Row_toggleTag(Row* this);
+
+void Row_resetFieldWidths(void);
+
+void Row_updateFieldWidth(RowField key, size_t width);
+
+void Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width);
+
+const char* RowField_alignedTitle(const struct Settings_* settings, RowField field);
+
+RowField RowField_keyAt(const struct Settings_* settings, int at);
+
+/* Sets the size of the PID column based on the passed PID */
+void Row_setPidColumnWidth(pid_t maxPid);
+
+/* Sets the size of the UID column based on the passed UID */
+void Row_setUidColumnWidth(uid_t maxUid);
+
+/* Takes number in kibibytes (base 1024). Prints 6 columns. */
+void Row_printKBytes(RichString* str, unsigned long long number, bool coloring);
+
+/* Takes number in bytes (base 1024). Prints 6 columns. */
+void Row_printBytes(RichString* str, unsigned long long number, bool coloring);
+
+/* Takes number as count (base 1000). Prints 12 columns. */
+void Row_printCount(RichString* str, unsigned long long number, bool coloring);
+
+/* Takes time in hundredths of a seconds. Prints 9 columns. */
+void Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring);
+
+/* Takes time in nanoseconds. Prints 9 columns. */
+void Row_printNanoseconds(RichString* str, unsigned long long totalNanoseconds, bool coloring);
+
+/* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */
+void Row_printRate(RichString* str, double rate, bool coloring);
+
+int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr);
+
+void Row_display(const Object* cast, RichString* out);
+
+static inline int Row_idEqualCompare(const void* v1, const void* v2) {
+ const int p1 = ((const Row*)v1)->id;
+ const int p2 = ((const Row*)v2)->id;
+ return p1 != p2; /* return zero when equal */
+}
+
+/* Routines used primarily with the tree view */
+static inline int Row_getGroupOrParent(const Row* this) {
+ return this->group == this->id ? this->parent : this->group;
+}
+
+static inline bool Row_isChildOf(const Row* this, int id) {
+ return id == Row_getGroupOrParent(this);
+}
+
+int Row_compareByParent_Base(const void* v1, const void* v2);
+
+#endif
diff --git a/RowField.h b/RowField.h
new file mode 100644
index 00000000..1e01ea3e
--- /dev/null
+++ b/RowField.h
@@ -0,0 +1,56 @@
+#ifndef HEADER_RowField
+#define HEADER_RowField
+/*
+htop - RowField.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "ProcessField.h" // platform-specific fields reserved for processes
+
+
+typedef enum ReservedFields_ {
+ NULL_FIELD = 0,
+ PID = 1,
+ COMM = 2,
+ STATE = 3,
+ PPID = 4,
+ PGRP = 5,
+ SESSION = 6,
+ TTY = 7,
+ TPGID = 8,
+ MINFLT = 10,
+ MAJFLT = 12,
+ PRIORITY = 18,
+ NICE = 19,
+ STARTTIME = 21,
+ PROCESSOR = 38,
+ M_VIRT = 39,
+ M_RESIDENT = 40,
+ ST_UID = 46,
+ PERCENT_CPU = 47,
+ PERCENT_MEM = 48,
+ USER = 49,
+ TIME = 50,
+ NLWP = 51,
+ TGID = 52,
+ PERCENT_NORM_CPU = 53,
+ ELAPSED = 54,
+ SCHEDULERPOLICY = 55,
+ 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_RESERVED_FIELD
+} ReservedFields;
+
+/* Follow ReservedField entries with dynamic fields defined at runtime */
+#define ROW_DYNAMIC_FIELDS LAST_RESERVED_FIELD
+typedef int32_t RowField;
+
+#endif
diff --git a/Scheduling.c b/Scheduling.c
new file mode 100644
index 00000000..406caf7d
--- /dev/null
+++ b/Scheduling.c
@@ -0,0 +1,165 @@
+/*
+htop - Scheduling.c
+(C) 2023 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 "Scheduling.h"
+
+#ifdef SCHEDULER_SUPPORT
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "FunctionBar.h"
+#include "ListItem.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Panel.h"
+#include "XUtils.h"
+
+
+static const SchedulingPolicy policies[] = {
+ [SCHED_OTHER] = { "Other", SCHED_OTHER, false },
+#ifdef SCHED_BATCH
+ [SCHED_BATCH] = { "Batch", SCHED_BATCH, false },
+#endif
+#ifdef SCHED_IDLE
+ [SCHED_IDLE] = { "Idle", SCHED_IDLE, false },
+#endif
+ [SCHED_FIFO] = { "FiFo", SCHED_FIFO, true },
+ [SCHED_RR] = { "RoundRobin", SCHED_RR, true },
+};
+
+#ifdef SCHED_RESET_ON_FORK
+static bool reset_on_fork = false;
+#endif
+
+
+Panel* Scheduling_newPolicyPanel(int preSelectedPolicy) {
+ Panel* this = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Select ", "Cancel "));
+ Panel_setHeader(this, "New policy:");
+
+#ifdef SCHED_RESET_ON_FORK
+ Panel_add(this, (Object*) ListItem_new(reset_on_fork ? "Reset on fork: on" : "Reset on fork: off", -1));
+#endif
+
+ for (unsigned i = 0; i < ARRAYSIZE(policies); i++) {
+ if (!policies[i].name)
+ continue;
+
+ Panel_add(this, (Object*) ListItem_new(policies[i].name, policies[i].id));
+ if (policies[i].id == preSelectedPolicy)
+ Panel_setSelected(this, i);
+ }
+
+ return this;
+}
+
+void Scheduling_togglePolicyPanelResetOnFork(Panel* schedPanel) {
+#ifdef SCHED_RESET_ON_FORK
+ reset_on_fork = !reset_on_fork;
+
+ ListItem* item = (ListItem*) Panel_get(schedPanel, 0);
+
+ free_and_xStrdup(&item->value, reset_on_fork ? "Reset on fork: on" : "Reset on fork: off");
+#else
+ (void)schedPanel;
+#endif
+}
+
+Panel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority) {
+ if (policy < 0 || (unsigned)policy >= ARRAYSIZE(policies) || policies[policy].name == NULL)
+ return NULL;
+
+ if (!policies[policy].prioritySupport)
+ return NULL;
+
+ int min = sched_get_priority_min(policy);
+ if (min < 0)
+ return NULL;
+ int max = sched_get_priority_max(policy);
+ if (max < 0 )
+ return NULL;
+
+ Panel* this = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Select ", "Cancel "));
+ Panel_setHeader(this, "Priority:");
+
+ for (int i = min; i <= max; i++) {
+ char buf[16];
+ xSnprintf(buf, sizeof(buf), "%d", i);
+ Panel_add(this, (Object*) ListItem_new(buf, i));
+ if (i == preSelectedPriority)
+ Panel_setSelected(this, i);
+ }
+
+ return this;
+}
+
+static bool Scheduling_setPolicy(Process* p, Arg arg) {
+ const SchedulingArg* sarg = arg.v;
+ int policy = sarg->policy;
+
+ assert(policy >= 0);
+ assert((unsigned)policy < ARRAYSIZE(policies));
+ assert(policies[policy].name);
+
+ const struct sched_param param = { .sched_priority = policies[policy].prioritySupport ? sarg->priority : 0 };
+
+ #ifdef SCHED_RESET_ON_FORK
+ if (reset_on_fork)
+ policy &= SCHED_RESET_ON_FORK;
+ #endif
+
+ int r = sched_setscheduler(Process_getPid(p), policy, &param);
+
+ /* POSIX says on success the previous scheduling policy should be returned,
+ * but Linux always returns 0. */
+ return r != -1;
+}
+
+bool Scheduling_rowSetPolicy(Row* row, Arg arg) {
+ Process* p = (Process*) row;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+ return Scheduling_setPolicy(p, arg);
+}
+
+const char* Scheduling_formatPolicy(int policy) {
+#ifdef SCHED_RESET_ON_FORK
+ policy = policy & ~SCHED_RESET_ON_FORK;
+#endif
+
+ switch (policy) {
+ case SCHED_OTHER:
+ return "OTHER";
+ case SCHED_FIFO:
+ return "FIFO";
+ case SCHED_RR:
+ return "RR";
+#ifdef SCHED_BATCH
+ case SCHED_BATCH:
+ return "BATCH";
+#endif
+#ifdef SCHED_IDLE
+ case SCHED_IDLE:
+ return "IDLE";
+#endif
+#ifdef SCHED_DEADLINE
+ case SCHED_DEADLINE:
+ return "EDF";
+#endif
+ default:
+ return "???";
+ }
+}
+
+/*
+ * Gather scheduling policy (thread-specific data)
+ */
+void Scheduling_readProcessPolicy(Process* proc) {
+ proc->scheduling_policy = sched_getscheduler(Process_getPid(proc));
+}
+#endif /* SCHEDULER_SUPPORT */
diff --git a/Scheduling.h b/Scheduling.h
new file mode 100644
index 00000000..610503cf
--- /dev/null
+++ b/Scheduling.h
@@ -0,0 +1,50 @@
+#ifndef HEADER_Scheduling
+#define HEADER_Scheduling
+/*
+htop - Scheduling.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <sched.h>
+#include <stdbool.h>
+
+#include "Object.h"
+#include "Panel.h"
+#include "Process.h"
+#include "Row.h"
+
+
+#if defined(HAVE_SCHED_SETSCHEDULER) && defined(HAVE_SCHED_GETSCHEDULER)
+#define SCHEDULER_SUPPORT
+
+typedef struct {
+ const char* name;
+ int id;
+ bool prioritySupport;
+} SchedulingPolicy;
+
+#define SCHEDULINGPANEL_INITSELECTEDPOLICY SCHED_OTHER
+#define SCHEDULINGPANEL_INITSELECTEDPRIORITY 50
+
+Panel* Scheduling_newPolicyPanel(int preSelectedPolicy);
+void Scheduling_togglePolicyPanelResetOnFork(Panel* schedPanel);
+
+Panel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority);
+
+
+typedef struct {
+ int policy;
+ int priority;
+} SchedulingArg;
+
+bool Scheduling_rowSetPolicy(Row* proc, Arg arg);
+
+const char* Scheduling_formatPolicy(int policy);
+
+void Scheduling_readProcessPolicy(Process* proc);
+
+#endif
+
+#endif /* HEADER_Scheduling */
diff --git a/ScreenManager.c b/ScreenManager.c
index e4b04bd3..6f3b0598 100644
--- a/ScreenManager.c
+++ b/ScreenManager.c
@@ -12,19 +12,23 @@ in the source distribution for its full text.
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/time.h>
#include "CRT.h"
#include "FunctionBar.h"
+#include "Machine.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
-#include "ProcessList.h"
+#include "Process.h"
#include "ProvideCurses.h"
+#include "Settings.h"
+#include "Table.h"
#include "XUtils.h"
-ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const State* state, bool owner) {
+ScreenManager* ScreenManager_new(Header* header, Machine* host, State* state, bool owner) {
ScreenManager* this;
this = xMalloc(sizeof(ScreenManager));
this->x1 = 0;
@@ -34,7 +38,7 @@ ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const
this->panels = Vector_new(Class(Panel), owner, DEFAULT_SIZE);
this->panelCount = 0;
this->header = header;
- this->settings = settings;
+ this->host = host;
this->state = state;
this->allowFocusChange = true;
return this;
@@ -53,18 +57,28 @@ void ScreenManager_add(ScreenManager* this, Panel* item, int size) {
ScreenManager_insert(this, item, size, Vector_size(this->panels));
}
+static int header_height(const ScreenManager* this) {
+ if (this->state->hideMeters)
+ return 0;
+
+ if (this->header)
+ return this->header->height;
+
+ return 0;
+}
+
void ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx) {
int lastX = 0;
if (idx > 0) {
const Panel* last = (const Panel*) Vector_get(this->panels, idx - 1);
lastX = last->x + last->w + 1;
}
- int height = LINES - this->y1 - (this->header ? this->header->height : 0) + this->y2;
+ int height = LINES - this->y1 - header_height(this) + this->y2;
if (size <= 0) {
size = COLS - this->x1 + this->x2 - lastX;
}
Panel_resize(item, size, height);
- Panel_move(item, lastX, this->y1 + (this->header ? this->header->height : 0));
+ Panel_move(item, lastX, this->y1 + header_height(this));
if (idx < this->panelCount) {
for (int i = idx + 1; i <= this->panelCount; i++) {
Panel* p = (Panel*) Vector_get(this->panels, i);
@@ -91,7 +105,7 @@ Panel* ScreenManager_remove(ScreenManager* this, int idx) {
}
void ScreenManager_resize(ScreenManager* this) {
- int y1_header = this->y1 + (this->header ? this->header->height : 0);
+ int y1_header = this->y1 + header_height(this);
int panels = this->panelCount;
int lastX = 0;
for (int i = 0; i < panels - 1; i++) {
@@ -105,13 +119,13 @@ void ScreenManager_resize(ScreenManager* this) {
Panel_move(panel, lastX, y1_header);
}
-static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut, bool *force_redraw) {
- ProcessList* pl = this->header->pl;
+static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut, bool* force_redraw) {
+ Machine* host = this->host;
- Platform_gettime_realtime(&pl->realtime, &pl->realtimeMs);
- double newTime = ((double)pl->realtime.tv_sec * 10) + ((double)pl->realtime.tv_usec / 100000);
+ Platform_gettime_realtime(&host->realtime, &host->realtimeMs);
+ double newTime = ((double)host->realtime.tv_sec * 10) + ((double)host->realtime.tv_usec / 100000);
- *timedOut = (newTime - *oldTime > this->settings->delay);
+ *timedOut = (newTime - *oldTime > host->settings->delay);
*rescan |= *timedOut;
if (newTime < *oldTime) {
@@ -121,12 +135,15 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi
if (*rescan) {
*oldTime = newTime;
int oldUidDigits = Process_uidDigits;
- if (!this->state->pauseProcessUpdate && (*sortTimeout == 0 || this->settings->ss->treeView)) {
- pl->needsSort = true;
+ if (!this->state->pauseUpdate && (*sortTimeout == 0 || host->settings->ss->treeView)) {
+ host->activeTable->needsSort = true;
*sortTimeout = 1;
}
- // scan processes first - some header values are calculated there
- ProcessList_scan(pl, this->state->pauseProcessUpdate);
+ // sample current values for system metrics and processes if not paused
+ Machine_scan(host);
+ if (!this->state->pauseUpdate)
+ Machine_scanTables(host);
+
// always update header, especially to avoid gaps in graph meters
Header_updateData(this->header);
// force redraw if the number of UID digits was changed
@@ -136,13 +153,14 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi
*redraw = true;
}
if (*redraw) {
- ProcessList_rebuildPanel(pl);
- Header_draw(this->header);
+ Table_rebuildPanel(host->activeTable);
+ if (!this->state->hideMeters)
+ Header_draw(this->header);
}
*rescan = false;
}
-static inline bool drawTab(int* y, int* x, int l, const char* name, bool cur) {
+static inline bool drawTab(const int* y, int* x, int l, const char* name, bool cur) {
attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);
mvaddch(*y, *x, '[');
(*x)++;
@@ -164,8 +182,9 @@ static inline bool drawTab(int* y, int* x, int l, const char* name, bool cur) {
}
static void ScreenManager_drawScreenTabs(ScreenManager* this) {
- ScreenSettings** screens = this->settings->screens;
- int cur = this->settings->ssIndex;
+ Settings* settings = this->host->settings;
+ ScreenSettings** screens = settings->screens;
+ int cur = settings->ssIndex;
int l = COLS;
Panel* panel = (Panel*) Vector_get(this->panels, 0);
int y = panel->y - 1;
@@ -177,7 +196,7 @@ static void ScreenManager_drawScreenTabs(ScreenManager* this) {
}
for (int s = 0; screens[s]; s++) {
- bool ok = drawTab(&y, &x, l, screens[s]->name, s == cur);
+ bool ok = drawTab(&y, &x, l, screens[s]->heading, s == cur);
if (!ok) {
break;
}
@@ -186,7 +205,8 @@ static void ScreenManager_drawScreenTabs(ScreenManager* this) {
}
static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_redraw) {
- if (this->settings->screenTabs) {
+ Settings* settings = this->host->settings;
+ if (settings->screenTabs) {
ScreenManager_drawScreenTabs(this);
}
const int nPanels = this->panelCount;
@@ -195,7 +215,7 @@ static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_
Panel_draw(panel,
force_redraw,
i == focus,
- panel != (Panel*)this->state->mainPanel || !this->state->hideProcessSelection,
+ panel != (Panel*)this->state->mainPanel || !this->state->hideSelection,
State_hideFunctionBar(this->state));
mvvline(panel->y, panel->x + panel->w, ' ', panel->h + (State_hideFunctionBar(this->state) ? 1 : 0));
}
@@ -206,6 +226,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, con
int focus = 0;
Panel* panelFocus = (Panel*) Vector_get(this->panels, focus);
+ Settings* settings = this->host->settings;
double oldTime = 0.0;
@@ -229,6 +250,12 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, con
if (redraw || force_redraw) {
ScreenManager_drawPanels(this, focus, force_redraw);
force_redraw = false;
+ if (this->host->iterationsRemaining != -1) {
+ if (!--this->host->iterationsRemaining) {
+ quit = true;
+ continue;
+ }
+ }
}
int prevCh = ch;
@@ -236,7 +263,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, con
HandlerResult result = IGNORED;
#ifdef HAVE_GETMOUSE
- if (ch == KEY_MOUSE && this->settings->enableMouse) {
+ if (ch == KEY_MOUSE && settings->enableMouse) {
ch = ERR;
MEVENT mevent;
int ok = getmouse(&mevent);
@@ -251,7 +278,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, con
if (mevent.y == panel->y) {
ch = EVENT_HEADER_CLICK(mevent.x - panel->x);
break;
- } else if (this->settings->screenTabs && mevent.y == panel->y - 1) {
+ } else if (settings->screenTabs && mevent.y == panel->y - 1) {
ch = EVENT_SCREEN_TAB_CLICK(mevent.x);
break;
} else if (mevent.y > panel->y && mevent.y <= panel->y + panel->h) {
@@ -294,12 +321,14 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, con
redraw = false;
continue;
}
+
switch (ch) {
case KEY_ALT('H'): ch = KEY_LEFT; break;
case KEY_ALT('J'): ch = KEY_DOWN; break;
case KEY_ALT('K'): ch = KEY_UP; break;
case KEY_ALT('L'): ch = KEY_RIGHT; break;
}
+
redraw = true;
if (Panel_eventHandlerFn(panelFocus)) {
result = Panel_eventHandler(panelFocus, ch);
@@ -334,6 +363,9 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, con
ScreenManager_resize(this);
continue;
}
+ case KEY_FOCUS_IN:
+ case KEY_FOCUS_OUT:
+ break;
case KEY_LEFT:
case KEY_CTRL('B'):
if (this->panelCount < 2) {
@@ -376,6 +408,11 @@ tryRight:
}
break;
+ case '#':
+ this->state->hideMeters = !this->state->hideMeters;
+ ScreenManager_resize(this);
+ force_redraw = true;
+ break;
case 27:
case 'q':
case KEY_F(10):
diff --git a/ScreenManager.h b/ScreenManager.h
index 978b524b..37821bb4 100644
--- a/ScreenManager.h
+++ b/ScreenManager.h
@@ -11,8 +11,8 @@ in the source distribution for its full text.
#include "Action.h"
#include "Header.h"
+#include "Machine.h"
#include "Panel.h"
-#include "Settings.h"
#include "Vector.h"
@@ -25,12 +25,12 @@ typedef struct ScreenManager_ {
const char* name;
int panelCount;
Header* header;
- const Settings* settings;
- const State* state;
+ Machine* host;
+ State* state;
bool allowFocusChange;
} ScreenManager;
-ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const State* state, bool owner);
+ScreenManager* ScreenManager_new(Header* header, Machine* host, State* state, bool owner);
void ScreenManager_delete(ScreenManager* this);
diff --git a/ScreenTabsPanel.c b/ScreenTabsPanel.c
new file mode 100644
index 00000000..e48e5fb8
--- /dev/null
+++ b/ScreenTabsPanel.c
@@ -0,0 +1,374 @@
+/*
+htop - ScreenTabsPanel.c
+(C) 2023 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 "ScreenTabsPanel.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "CRT.h"
+#include "FunctionBar.h"
+#include "Hashtable.h"
+#include "Macros.h"
+#include "ProvideCurses.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+
+static HandlerResult ScreenNamesPanel_eventHandlerNormal(Panel* super, int ch);
+
+ObjectClass ScreenTabListItem_class = {
+ .extends = Class(ListItem),
+ .display = ListItem_display,
+ .delete = ListItem_delete,
+ .compare = ListItem_compare
+};
+
+static void ScreenNamesPanel_fill(ScreenNamesPanel* this, DynamicScreen* ds) {
+ const Settings* settings = this->settings;
+ Panel* super = (Panel*) this;
+ Panel_prune(super);
+
+ for (unsigned int i = 0; i < settings->nScreens; i++) {
+ const ScreenSettings* ss = settings->screens[i];
+
+ if (ds == NULL) {
+ if (ss->dynamic != NULL)
+ continue;
+ /* built-in (processes, not dynamic) - e.g. Main or I/O */
+ } else {
+ if (ss->dynamic == NULL)
+ continue;
+ if (!String_eq(ds->name, ss->dynamic))
+ continue;
+ /* matching dynamic screen found, add it into the Panel */
+ }
+ Panel_add(super, (Object*) ListItem_new(ss->heading, i));
+ }
+
+ this->ds = ds;
+}
+
+static void ScreenTabsPanel_delete(Object* object) {
+ Panel* super = (Panel*) object;
+ ScreenTabsPanel* this = (ScreenTabsPanel*) object;
+
+ Panel_done(super);
+ free(this);
+}
+
+static HandlerResult ScreenTabsPanel_eventHandler(Panel* super, int ch) {
+ ScreenTabsPanel* const this = (ScreenTabsPanel* const) super;
+
+ HandlerResult result = IGNORED;
+
+ int selected = Panel_getSelectedIndex(super);
+ switch (ch) {
+ case EVENT_SET_SELECTED:
+ result = HANDLED;
+ break;
+ case KEY_F(5):
+ case KEY_CTRL('N'):
+ /* pass onto the Names panel for creating new screen */
+ return ScreenNamesPanel_eventHandlerNormal(&this->names->super, ch);
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_NPAGE:
+ case KEY_PPAGE:
+ case KEY_HOME:
+ case KEY_END: {
+ int previous = selected;
+ Panel_onKey(super, ch);
+ selected = Panel_getSelectedIndex(super);
+ if (previous != selected)
+ result = HANDLED;
+ break;
+ }
+ default:
+ if (ch < 255 && isalpha(ch))
+ result = Panel_selectByTyping(super, ch);
+ if (result == BREAK_LOOP)
+ result = IGNORED;
+ break;
+ }
+
+ if (result == HANDLED) {
+ ScreenTabListItem* focus = (ScreenTabListItem*) Panel_getSelected(super);
+ if (focus) {
+ ScreenNamesPanel_fill(this->names, focus->ds);
+ }
+ }
+
+ return result;
+}
+
+PanelClass ScreenTabsPanel_class = {
+ .super = {
+ .extends = Class(Panel),
+ .delete = ScreenTabsPanel_delete,
+ },
+ .eventHandler = ScreenTabsPanel_eventHandler
+};
+
+static ScreenTabListItem* ScreenTabListItem_new(const char* value, DynamicScreen* ds) {
+ ScreenTabListItem* this = AllocThis(ScreenTabListItem);
+ ListItem_init((ListItem*)this, value, 0);
+ this->ds = ds;
+ return this;
+}
+
+static void addDynamicScreen(ATTR_UNUSED ht_key_t key, void* value, void* userdata) {
+ DynamicScreen* screen = (DynamicScreen*) value;
+ Panel* super = (Panel*) userdata;
+ const char* name = screen->heading ? screen->heading : screen->name;
+
+ Panel_add(super, (Object*) ScreenTabListItem_new(name, screen));
+}
+
+static const char* const ScreenTabsFunctions[] = {" ", " ", " ", " ", "New ", " ", " ", " ", " ", "Done ", NULL};
+
+ScreenTabsPanel* ScreenTabsPanel_new(Settings* settings) {
+ ScreenTabsPanel* this = AllocThis(ScreenTabsPanel);
+ Panel* super = (Panel*) this;
+ FunctionBar* fuBar = FunctionBar_new(ScreenTabsFunctions, NULL, NULL);
+ Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
+
+ this->settings = settings;
+ this->names = ScreenNamesPanel_new(settings);
+ super->cursorOn = false;
+ this->cursor = 0;
+ Panel_setHeader(super, "Screen tabs");
+
+ assert(settings->dynamicScreens != NULL);
+ Panel_add(super, (Object*) ScreenTabListItem_new("Processes", NULL));
+ Hashtable_foreach(settings->dynamicScreens, addDynamicScreen, super);
+
+ return this;
+}
+
+// -------------
+
+ObjectClass ScreenNameListItem_class = {
+ .extends = Class(ListItem),
+ .display = ListItem_display,
+ .delete = ListItem_delete,
+ .compare = ListItem_compare
+};
+
+ScreenNameListItem* ScreenNameListItem_new(const char* value, ScreenSettings* ss) {
+ ScreenNameListItem* this = AllocThis(ScreenNameListItem);
+ ListItem_init((ListItem*)this, value, 0);
+ this->ss = ss;
+ return this;
+}
+
+static const char* const ScreenNamesFunctions[] = {" ", " ", " ", " ", "New ", " ", " ", " ", " ", "Done ", NULL};
+
+static void ScreenNamesPanel_delete(Object* object) {
+ Panel* super = (Panel*) object;
+ ScreenNamesPanel* this = (ScreenNamesPanel*) object;
+
+ /* do not delete screen settings still in use */
+ int n = Panel_size(super);
+ for (int i = 0; i < n; i++) {
+ ScreenNameListItem* item = (ScreenNameListItem*) Panel_get(super, i);
+ item->ss = NULL;
+ }
+
+ /* during renaming the ListItem's value points to our static buffer */
+ if (this->renamingItem)
+ this->renamingItem->value = this->saved;
+
+ Panel_done(super);
+ free(this);
+}
+
+static void renameScreenSettings(ScreenNamesPanel* this, const ListItem* item) {
+ const ScreenNameListItem* nameItem = (const ScreenNameListItem*) item;
+
+ ScreenSettings* ss = nameItem->ss;
+ free_and_xStrdup(&ss->heading, item->value);
+
+ Settings* settings = this->settings;
+ settings->changed = true;
+ settings->lastUpdate++;
+}
+
+static HandlerResult ScreenNamesPanel_eventHandlerRenaming(Panel* super, int ch) {
+ ScreenNamesPanel* const this = (ScreenNamesPanel*) super;
+
+ if (ch >= 32 && ch < 127 && ch != '=') {
+ if (this->cursor < SCREEN_NAME_LEN - 1) {
+ this->buffer[this->cursor] = (char)ch;
+ this->cursor++;
+ super->selectedLen = strlen(this->buffer);
+ Panel_setCursorToSelection(super);
+ }
+
+ return HANDLED;
+ }
+
+ switch (ch) {
+ case 127:
+ case KEY_BACKSPACE:
+ if (this->cursor > 0) {
+ this->cursor--;
+ this->buffer[this->cursor] = '\0';
+ super->selectedLen = strlen(this->buffer);
+ Panel_setCursorToSelection(super);
+ }
+ break;
+ case '\n':
+ case '\r':
+ case KEY_ENTER: {
+ ListItem* item = (ListItem*) Panel_getSelected(super);
+ if (!item)
+ break;
+ assert(item == this->renamingItem);
+ free(this->saved);
+ item->value = xStrdup(this->buffer);
+ this->renamingItem = NULL;
+ super->cursorOn = false;
+ Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
+ renameScreenSettings(this, item);
+ break;
+ }
+ case 27: { // Esc
+ ListItem* item = (ListItem*) Panel_getSelected(super);
+ if (!item)
+ break;
+ assert(item == this->renamingItem);
+ item->value = this->saved;
+ this->renamingItem = NULL;
+ super->cursorOn = false;
+ Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
+ break;
+ }
+ }
+
+ return HANDLED;
+}
+
+static void startRenaming(Panel* super) {
+ ScreenNamesPanel* const this = (ScreenNamesPanel*) super;
+
+ ListItem* item = (ListItem*) Panel_getSelected(super);
+ if (item == NULL)
+ return;
+
+ this->renamingItem = item;
+ super->cursorOn = true;
+ char* name = item->value;
+ this->saved = name;
+ strncpy(this->buffer, name, SCREEN_NAME_LEN);
+ this->buffer[SCREEN_NAME_LEN] = '\0';
+ this->cursor = strlen(this->buffer);
+ item->value = this->buffer;
+ Panel_setSelectionColor(super, PANEL_EDIT);
+ super->selectedLen = strlen(this->buffer);
+ Panel_setCursorToSelection(super);
+}
+
+static void addNewScreen(Panel* super, DynamicScreen* ds) {
+ ScreenNamesPanel* const this = (ScreenNamesPanel*) super;
+ const char* name = "New";
+ ScreenSettings* ss = (ds != NULL) ? Settings_newDynamicScreen(this->settings, name, ds, NULL) : Settings_newScreen(this->settings, &(const ScreenDefaults) { .name = name, .columns = "PID Command", .sortKey = "PID" });
+ ScreenNameListItem* item = ScreenNameListItem_new(name, ss);
+ int idx = Panel_getSelectedIndex(super);
+ Panel_insert(super, idx + 1, (Object*) item);
+ Panel_setSelected(super, idx + 1);
+}
+
+static HandlerResult ScreenNamesPanel_eventHandlerNormal(Panel* super, int ch) {
+ ScreenNamesPanel* const this = (ScreenNamesPanel*) super;
+ ScreenNameListItem* oldFocus = (ScreenNameListItem*) Panel_getSelected(super);
+ HandlerResult result = IGNORED;
+
+ switch (ch) {
+ case '\n':
+ case '\r':
+ case KEY_ENTER:
+ case KEY_MOUSE:
+ case KEY_RECLICK:
+ Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
+ result = HANDLED;
+ break;
+ case EVENT_SET_SELECTED:
+ result = HANDLED;
+ break;
+ case KEY_NPAGE:
+ case KEY_PPAGE:
+ case KEY_HOME:
+ case KEY_END:
+ Panel_onKey(super, ch);
+ break;
+ case KEY_F(5):
+ case KEY_CTRL('N'):
+ addNewScreen(super, this->ds);
+ startRenaming(super);
+ result = HANDLED;
+ break;
+ default:
+ if (ch < 255 && isalpha(ch))
+ result = Panel_selectByTyping(super, ch);
+ if (result == BREAK_LOOP)
+ result = IGNORED;
+ break;
+ }
+
+ ScreenNameListItem* newFocus = (ScreenNameListItem*) Panel_getSelected(super);
+ if (newFocus && oldFocus != newFocus)
+ result = HANDLED;
+
+ return result;
+}
+
+static HandlerResult ScreenNamesPanel_eventHandler(Panel* super, int ch) {
+ ScreenNamesPanel* const this = (ScreenNamesPanel*) super;
+
+ if (!this->renamingItem)
+ return ScreenNamesPanel_eventHandlerNormal(super, ch);
+ return ScreenNamesPanel_eventHandlerRenaming(super, ch);
+}
+
+PanelClass ScreenNamesPanel_class = {
+ .super = {
+ .extends = Class(Panel),
+ .delete = ScreenNamesPanel_delete
+ },
+ .eventHandler = ScreenNamesPanel_eventHandler
+};
+
+ScreenNamesPanel* ScreenNamesPanel_new(Settings* settings) {
+ ScreenNamesPanel* this = AllocThis(ScreenNamesPanel);
+ Panel* super = (Panel*) this;
+ FunctionBar* fuBar = FunctionBar_new(ScreenNamesFunctions, NULL, NULL);
+ Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
+
+ this->settings = settings;
+ this->renamingItem = NULL;
+ memset(this->buffer, 0, sizeof(this->buffer));
+ this->ds = NULL;
+ this->saved = NULL;
+ this->cursor = 0;
+ super->cursorOn = false;
+ Panel_setHeader(super, "Screens");
+
+ for (unsigned int i = 0; i < settings->nScreens; i++) {
+ ScreenSettings* ss = settings->screens[i];
+ /* initially show only for Processes tabs (selected) */
+ if (ss->dynamic)
+ continue;
+ Panel_add(super, (Object*) ScreenNameListItem_new(ss->heading, ss));
+ }
+ return this;
+}
diff --git a/ScreenTabsPanel.h b/ScreenTabsPanel.h
new file mode 100644
index 00000000..fe1a313c
--- /dev/null
+++ b/ScreenTabsPanel.h
@@ -0,0 +1,61 @@
+#ifndef HEADER_ScreenTabsPanel
+#define HEADER_ScreenTabsPanel
+/*
+htop - ScreenTabsPanel.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "DynamicScreen.h"
+#include "ListItem.h"
+#include "Object.h"
+#include "Panel.h"
+#include "ScreensPanel.h"
+#include "ScreenManager.h"
+#include "Settings.h"
+
+
+typedef struct ScreenNamesPanel_ {
+ Panel super;
+
+ ScreenManager* scr;
+ Settings* settings;
+ char buffer[SCREEN_NAME_LEN + 1];
+ DynamicScreen* ds;
+ char* saved;
+ int cursor;
+ ListItem* renamingItem;
+} ScreenNamesPanel;
+
+typedef struct ScreenNameListItem_ {
+ ListItem super;
+ ScreenSettings* ss;
+} ScreenNameListItem;
+
+typedef struct ScreenTabsPanel_ {
+ Panel super;
+
+ ScreenManager* scr;
+ Settings* settings;
+ ScreenNamesPanel* names;
+ int cursor;
+} ScreenTabsPanel;
+
+typedef struct ScreenTabListItem_ {
+ ListItem super;
+ DynamicScreen* ds;
+} ScreenTabListItem;
+
+
+ScreenTabsPanel* ScreenTabsPanel_new(Settings* settings);
+
+extern ObjectClass ScreenNameListItem_class;
+
+ScreenNameListItem* ScreenNameListItem_new(const char* value, ScreenSettings* ss);
+
+extern PanelClass ScreenNamesPanel_class;
+
+ScreenNamesPanel* ScreenNamesPanel_new(Settings* settings);
+
+#endif
diff --git a/ScreensPanel.c b/ScreensPanel.c
index 785c3878..4138066b 100644
--- a/ScreensPanel.c
+++ b/ScreensPanel.c
@@ -1,17 +1,21 @@
/*
htop - ScreensPanel.c
(C) 2004-2011 Hisham H. Muhammad
-(C) 2020-2022 htop dev team
+(C) 2020-2023 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 "ScreensPanel.h"
-#include <stdlib.h>
+#include <assert.h>
#include <ctype.h>
+#include <stdlib.h>
#include <string.h>
+#include "AvailableColumnsPanel.h"
#include "CRT.h"
#include "FunctionBar.h"
#include "Hashtable.h"
@@ -43,10 +47,10 @@ ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) {
}
static const char* const ScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL};
+static const char* const DynamicFunctions[] = {" ", "Rename", " ", " ", " ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL};
static void ScreensPanel_delete(Object* object) {
Panel* super = (Panel*) object;
- ScreensPanel* this = (ScreensPanel*) object;
/* do not delete screen settings still in use */
int n = Panel_size(super);
@@ -55,8 +59,7 @@ static void ScreensPanel_delete(Object* object) {
item->ss = NULL;
}
- Panel_done(super);
- free(this);
+ Panel_delete(object);
}
static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) {
@@ -69,47 +72,48 @@ static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) {
super->selectedLen = strlen(this->buffer);
Panel_setCursorToSelection(super);
}
- } else {
- switch(ch) {
- case 127:
- case KEY_BACKSPACE:
- {
- if (this->cursor > 0) {
- this->cursor--;
- this->buffer[this->cursor] = '\0';
- super->selectedLen = strlen(this->buffer);
- Panel_setCursorToSelection(super);
- }
- break;
+
+ return HANDLED;
+ }
+
+ switch (ch) {
+ case 127:
+ case KEY_BACKSPACE:
+ if (this->cursor > 0) {
+ this->cursor--;
+ this->buffer[this->cursor] = '\0';
+ super->selectedLen = strlen(this->buffer);
+ Panel_setCursorToSelection(super);
}
- case '\n':
- case '\r':
- case KEY_ENTER:
- {
- ListItem* item = (ListItem*) Panel_getSelected(super);
- if (!item)
- break;
- free(this->saved);
- item->value = xStrdup(this->buffer);
- this->renaming = false;
- super->cursorOn = false;
- Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
- ScreensPanel_update(super);
+ break;
+ case '\n':
+ case '\r':
+ case KEY_ENTER: {
+ ListItem* item = (ListItem*) Panel_getSelected(super);
+ if (!item)
break;
- }
- case 27: // Esc
- {
- ListItem* item = (ListItem*) Panel_getSelected(super);
- if (!item)
- break;
- item->value = this->saved;
- this->renaming = false;
- super->cursorOn = false;
- Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
+ assert(item == this->renamingItem);
+ free(this->saved);
+ item->value = xStrdup(this->buffer);
+ this->renamingItem = NULL;
+ super->cursorOn = false;
+ Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
+ ScreensPanel_update(super);
+ break;
+ }
+ case 27: { // Esc
+ ListItem* item = (ListItem*) Panel_getSelected(super);
+ if (!item)
break;
- }
+ assert(item == this->renamingItem);
+ item->value = this->saved;
+ this->renamingItem = NULL;
+ super->cursorOn = false;
+ Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
+ break;
}
}
+
return HANDLED;
}
@@ -119,7 +123,7 @@ static void startRenaming(Panel* super) {
ListItem* item = (ListItem*) Panel_getSelected(super);
if (item == NULL)
return;
- this->renaming = true;
+ this->renamingItem = item;
super->cursorOn = true;
char* name = item->value;
this->saved = name;
@@ -132,7 +136,7 @@ static void startRenaming(Panel* super) {
Panel_setCursorToSelection(super);
}
-static void rebuildSettingsArray(Panel* super) {
+static void rebuildSettingsArray(Panel* super, int selected) {
ScreensPanel* const this = (ScreensPanel*) super;
int n = Panel_size(super);
@@ -144,13 +148,20 @@ static void rebuildSettingsArray(Panel* super) {
this->settings->screens[i] = item->ss;
}
this->settings->nScreens = n;
+ /* ensure selection is in valid range */
+ if (selected > n - 1)
+ selected = n - 1;
+ else if (selected < 0)
+ selected = 0;
+ this->settings->ssIndex = selected;
+ this->settings->ss = this->settings->screens[selected];
}
static void addNewScreen(Panel* super) {
ScreensPanel* const this = (ScreensPanel*) super;
const char* name = "New";
- ScreenSettings* ss = Settings_newScreen(this->settings, &(const ScreenDefaults){ .name = name, .columns = "PID Command", .sortKey = "PID" });
+ ScreenSettings* ss = Settings_newScreen(this->settings, &(const ScreenDefaults) { .name = name, .columns = "PID Command", .sortKey = "PID" });
ScreenListItem* item = ScreenListItem_new(name, ss);
int idx = Panel_getSelectedIndex(super);
Panel_insert(super, idx + 1, (Object*) item);
@@ -164,14 +175,14 @@ static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) {
ScreenListItem* oldFocus = (ScreenListItem*) Panel_getSelected(super);
bool shouldRebuildArray = false;
HandlerResult result = IGNORED;
- switch(ch) {
+
+ switch (ch) {
case '\n':
case '\r':
case KEY_ENTER:
case KEY_MOUSE:
- case KEY_RECLICK:
- {
- this->moving = !(this->moving);
+ case KEY_RECLICK: {
+ this->moving = !this->moving;
Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS);
ListItem* item = (ListItem*) Panel_getSelected(super);
if (item)
@@ -185,95 +196,84 @@ static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) {
case KEY_NPAGE:
case KEY_PPAGE:
case KEY_HOME:
- case KEY_END: {
+ case KEY_END:
Panel_onKey(super, ch);
break;
- }
case KEY_F(2):
case KEY_CTRL('R'):
- {
startRenaming(super);
result = HANDLED;
break;
- }
case KEY_F(5):
case KEY_CTRL('N'):
- {
+ if (this->settings->dynamicScreens)
+ break;
addNewScreen(super);
startRenaming(super);
shouldRebuildArray = true;
result = HANDLED;
break;
- }
case KEY_UP:
- {
if (!this->moving) {
Panel_onKey(super, ch);
break;
}
- /* else fallthrough */
- } /* FALLTHRU */
+ /* FALLTHRU */
case KEY_F(7):
case '[':
case '-':
- {
Panel_moveSelectedUp(super);
shouldRebuildArray = true;
result = HANDLED;
break;
- }
case KEY_DOWN:
- {
if (!this->moving) {
Panel_onKey(super, ch);
break;
}
- /* else fallthrough */
- } /* FALLTHRU */
+ /* FALLTHRU */
case KEY_F(8):
case ']':
case '+':
- {
Panel_moveSelectedDown(super);
shouldRebuildArray = true;
result = HANDLED;
break;
- }
case KEY_F(9):
//case KEY_DC:
- {
- if (Panel_size(super) > 1) {
+ if (Panel_size(super) > 1)
Panel_remove(super, selected);
- }
shouldRebuildArray = true;
result = HANDLED;
break;
- }
default:
- {
if (ch < 255 && isalpha(ch))
result = Panel_selectByTyping(super, ch);
if (result == BREAK_LOOP)
result = IGNORED;
break;
- }
}
+
ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super);
if (newFocus && oldFocus != newFocus) {
- ColumnsPanel_fill(this->columns, newFocus->ss, this->settings->dynamicColumns);
+ Hashtable* dynamicColumns = this->settings->dynamicColumns;
+ ColumnsPanel_fill(this->columns, newFocus->ss, dynamicColumns);
+ AvailableColumnsPanel_fill(this->availableColumns, newFocus->ss->dynamic, dynamicColumns);
result = HANDLED;
}
+
if (shouldRebuildArray)
- rebuildSettingsArray(super);
+ rebuildSettingsArray(super, selected);
if (result == HANDLED)
ScreensPanel_update(super);
+
return result;
}
static HandlerResult ScreensPanel_eventHandler(Panel* super, int ch) {
ScreensPanel* const this = (ScreensPanel*) super;
- if (this->renaming) {
+ if (this->renamingItem) {
return ScreensPanel_eventHandlerRenaming(super, ch);
} else {
return ScreensPanel_eventHandlerNormal(super, ch);
@@ -292,20 +292,21 @@ ScreensPanel* ScreensPanel_new(Settings* settings) {
ScreensPanel* this = AllocThis(ScreensPanel);
Panel* super = (Panel*) this;
Hashtable* columns = settings->dynamicColumns;
- FunctionBar* fuBar = FunctionBar_new(ScreensFunctions, NULL, NULL);
+ FunctionBar* fuBar = FunctionBar_new(settings->dynamicScreens ? DynamicFunctions : ScreensFunctions, NULL, NULL);
Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
this->settings = settings;
this->columns = ColumnsPanel_new(settings->screens[0], columns, &(settings->changed));
+ this->availableColumns = AvailableColumnsPanel_new((Panel*) this->columns, columns);
this->moving = false;
- this->renaming = false;
+ this->renamingItem = NULL;
super->cursorOn = false;
this->cursor = 0;
Panel_setHeader(super, "Screens");
for (unsigned int i = 0; i < settings->nScreens; i++) {
ScreenSettings* ss = settings->screens[i];
- char* name = ss->name;
+ char* name = ss->heading;
Panel_add(super, (Object*) ScreenListItem_new(name, ss));
}
return this;
@@ -320,9 +321,8 @@ void ScreensPanel_update(Panel* super) {
for (int i = 0; i < size; i++) {
ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
ScreenSettings* ss = item->ss;
- free(ss->name);
+ free_and_xStrdup(&ss->heading, ((ListItem*) item)->value);
this->settings->screens[i] = ss;
- ss->name = xStrdup(((ListItem*) item)->value);
}
this->settings->screens[size] = NULL;
}
diff --git a/ScreensPanel.h b/ScreensPanel.h
index 1f82395a..0be0b824 100644
--- a/ScreensPanel.h
+++ b/ScreensPanel.h
@@ -10,7 +10,9 @@ in the source distribution for its full text.
#include <stdbool.h>
+#include "AvailableColumnsPanel.h"
#include "ColumnsPanel.h"
+#include "DynamicScreen.h"
#include "ListItem.h"
#include "Object.h"
#include "Panel.h"
@@ -27,15 +29,17 @@ typedef struct ScreensPanel_ {
ScreenManager* scr;
Settings* settings;
ColumnsPanel* columns;
+ AvailableColumnsPanel* availableColumns;
char buffer[SCREEN_NAME_LEN + 1];
char* saved;
int cursor;
bool moving;
- bool renaming;
+ ListItem* renamingItem;
} ScreensPanel;
typedef struct ScreenListItem_ {
ListItem super;
+ DynamicScreen* ds;
ScreenSettings* ss;
} ScreenListItem;
@@ -44,8 +48,6 @@ extern ObjectClass ScreenListItem_class;
ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss);
-extern PanelClass ScreensPanel_class;
-
ScreensPanel* ScreensPanel_new(Settings* settings);
void ScreensPanel_update(Panel* super);
diff --git a/Settings.c b/Settings.c
index 7d6fca47..07b7e6a2 100644
--- a/Settings.c
+++ b/Settings.c
@@ -5,11 +5,15 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "Settings.h"
#include <ctype.h>
#include <errno.h>
+#include <fcntl.h>
#include <limits.h>
+#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -18,9 +22,12 @@ in the source distribution for its full text.
#include "CRT.h"
#include "DynamicColumn.h"
+#include "DynamicScreen.h"
#include "Macros.h"
#include "Meter.h"
#include "Platform.h"
+#include "Process.h"
+#include "Table.h"
#include "XUtils.h"
@@ -55,19 +62,20 @@ static char** readQuotedList(char* line) {
return list;
}
-static void writeQuotedList(FILE* fd, char** list) {
+static void writeQuotedList(FILE* fp, char** list) {
const char* sep = "";
for (int i = 0; list[i]; i++) {
- fprintf(fd, "%s\"%s\"", sep, list[i]);
+ fprintf(fp, "%s\"%s\"", sep, list[i]);
sep = " ";
}
- fprintf(fd, "\n");
+ fprintf(fp, "\n");
}
*/
void Settings_delete(Settings* this) {
free(this->filename);
+ free(this->initialFilename);
for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
String_freeArray(this->hColumns[i].names);
free(this->hColumns[i].modes);
@@ -100,9 +108,9 @@ static void Settings_readMeterModes(Settings* this, const char* line, unsigned i
}
column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);
this->hColumns[column].len = len;
- int* modes = len ? xCalloc(len, sizeof(int)) : NULL;
+ MeterModeId* modes = len ? xCalloc(len, sizeof(MeterModeId)) : NULL;
for (int i = 0; i < len; i++) {
- modes[i] = atoi(ids[i]);
+ modes[i] = (MeterModeId) atoi(ids[i]);
}
String_freeArray(ids);
this->hColumns[column].modes = modes;
@@ -115,7 +123,7 @@ static bool Settings_validateMeters(Settings* this) {
for (size_t column = 0; column < colCount; column++) {
char** names = this->hColumns[column].names;
- const int* modes = this->hColumns[column].modes;
+ const MeterModeId* modes = this->hColumns[column].modes;
const size_t len = this->hColumns[column].len;
if (!len)
@@ -155,8 +163,8 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount)
this->hLayout = HF_TWO_50_50;
this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
for (size_t i = 0; i < 2; i++) {
- this->hColumns[i].names = xCalloc(sizes[i] + 1, sizeof(char*));
- this->hColumns[i].modes = xCalloc(sizes[i], sizeof(int));
+ this->hColumns[i].names = xCalloc(sizes[i] + 1, sizeof(*this->hColumns[0].names));
+ this->hColumns[i].modes = xCalloc(sizes[i], sizeof(*this->hColumns[0].modes));
this->hColumns[i].len = sizes[i];
}
@@ -202,13 +210,25 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount)
this->hColumns[1].modes[r++] = TEXT_METERMODE;
}
-static const char* toFieldName(Hashtable* columns, int id) {
- if (id < 0)
+static const char* toFieldName(Hashtable* columns, int id, bool* enabled) {
+ if (id < 0) {
+ if (enabled)
+ *enabled = false;
return NULL;
- if (id >= LAST_PROCESSFIELD) {
+ }
+ if (id >= ROW_DYNAMIC_FIELDS) {
const DynamicColumn* column = DynamicColumn_lookup(columns, id);
+ if (!column) {
+ if (enabled)
+ *enabled = false;
+ return NULL;
+ }
+ if (enabled)
+ *enabled = column->enabled;
return column->name;
}
+ if (enabled)
+ *enabled = true;
return Process_fields[id].name;
}
@@ -216,13 +236,13 @@ static int toFieldIndex(Hashtable* columns, const char* str) {
if (isdigit(str[0])) {
// This "+1" is for compatibility with the older enum format.
int id = atoi(str) + 1;
- if (toFieldName(columns, id)) {
+ if (toFieldName(columns, id, NULL)) {
return id;
}
} else {
// Dynamically-defined columns are always stored by-name.
char dynamic[32] = {0};
- if (sscanf(str, "Dynamic(%30s)", dynamic)) {
+ if (sscanf(str, "Dynamic(%30s)", dynamic) == 1) {
char* end;
if ((end = strrchr(dynamic, ')')) != NULL) {
bool success;
@@ -236,7 +256,7 @@ static int toFieldIndex(Hashtable* columns, const char* str) {
}
// Fallback to iterative scan of table of fields by-name.
for (int p = 1; p < LAST_PROCESSFIELD; p++) {
- const char* pName = toFieldName(columns, p);
+ const char* pName = toFieldName(columns, p, NULL);
if (pName && strcmp(pName, str) == 0)
return p;
}
@@ -261,42 +281,64 @@ static void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, co
}
int id = toFieldIndex(columns, ids[i]);
if (id >= 0)
- ss->fields[j] = id;
+ ss->fields[j++] = id;
if (id > 0 && id < LAST_PROCESSFIELD)
ss->flags |= Process_fields[id].flags;
- j++;
}
String_freeArray(ids);
}
+static ScreenSettings* Settings_initScreenSettings(ScreenSettings* ss, Settings* this, const char* columns) {
+ ScreenSettings_readFields(ss, this->dynamicColumns, columns);
+ this->screens[this->nScreens] = ss;
+ this->nScreens++;
+ this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1));
+ this->screens[this->nScreens] = NULL;
+ return ss;
+}
+
ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults) {
int sortKey = defaults->sortKey ? toFieldIndex(this->dynamicColumns, defaults->sortKey) : PID;
+ int treeSortKey = defaults->treeSortKey ? toFieldIndex(this->dynamicColumns, defaults->treeSortKey) : PID;
int sortDesc = (sortKey >= 0 && sortKey < LAST_PROCESSFIELD) ? Process_fields[sortKey].defaultSortDesc : 1;
ScreenSettings* ss = xMalloc(sizeof(ScreenSettings));
*ss = (ScreenSettings) {
- .name = xStrdup(defaults->name),
+ .heading = xStrdup(defaults->name),
+ .dynamic = NULL,
+ .table = NULL,
.fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)),
.flags = 0,
.direction = sortDesc ? -1 : 1,
.treeDirection = 1,
.sortKey = sortKey,
- .treeSortKey = PID,
+ .treeSortKey = treeSortKey,
.treeView = false,
.treeViewAlwaysByPID = false,
.allBranchesCollapsed = false,
};
+ return Settings_initScreenSettings(ss, this, defaults->columns);
+}
- ScreenSettings_readFields(ss, this->dynamicColumns, defaults->columns);
- this->screens[this->nScreens] = ss;
- this->nScreens++;
- this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1));
- this->screens[this->nScreens] = NULL;
- return ss;
+ScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const DynamicScreen* screen, Table* table) {
+ int sortKey = toFieldIndex(this->dynamicColumns, screen->columnKeys);
+
+ ScreenSettings* ss = xMalloc(sizeof(ScreenSettings));
+ *ss = (ScreenSettings) {
+ .heading = xStrdup(tab),
+ .dynamic = xStrdup(screen->name),
+ .table = table,
+ .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)),
+ .direction = screen->direction,
+ .treeDirection = 1,
+ .sortKey = sortKey,
+ };
+ return Settings_initScreenSettings(ss, this, screen->columnKeys);
}
void ScreenSettings_delete(ScreenSettings* this) {
- free(this->name);
+ free(this->heading);
+ free(this->dynamic);
free(this->fields);
free(this);
}
@@ -308,19 +350,54 @@ static ScreenSettings* Settings_defaultScreens(Settings* this) {
const ScreenDefaults* defaults = &Platform_defaultScreens[i];
Settings_newScreen(this, defaults);
}
+ Platform_defaultDynamicScreens(this);
return this->screens[0];
}
-static bool Settings_read(Settings* this, const char* fileName, unsigned int initialCpuCount) {
- FILE* fd = fopen(fileName, "r");
- if (!fd)
+static bool Settings_read(Settings* this, const char* fileName, unsigned int initialCpuCount, bool checkWritability) {
+ int fd = -1;
+ const char* fopen_mode = "r+";
+ if (checkWritability) {
+ do {
+ fd = open(fileName, O_RDWR | O_NOCTTY | O_NOFOLLOW);
+ } while (fd < 0 && errno == EINTR);
+
+ if (fd < 0) {
+ this->writeConfig = (errno == ENOENT);
+ if (errno != EACCES && errno != EPERM && errno != EROFS) {
+ return false;
+ }
+ } else {
+ // Check if this is a regular file
+ struct stat sb;
+ int err = fstat(fd, &sb);
+ this->writeConfig = !err && S_ISREG(sb.st_mode);
+ }
+ }
+
+ // If opening for read & write is not needed or fails, open for read only.
+ // There is no risk of following symlink in this case.
+ if (fd < 0) {
+ fopen_mode = "r";
+ do {
+ fd = open(fileName, O_RDONLY | O_NOCTTY);
+ } while (fd < 0 && errno == EINTR);
+ }
+
+ if (fd < 0)
return false;
+ FILE* fp = fdopen(fd, fopen_mode);
+ if (!fp) {
+ close(fd);
+ return false;
+ }
+
ScreenSettings* screen = NULL;
bool didReadMeters = false;
bool didReadAny = false;
for (;;) {
- char* line = String_readLine(fd);
+ char* line = String_readLine(fp);
if (!line) {
break;
}
@@ -340,7 +417,7 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
fprintf(stderr, " version v%d, but this %s binary only supports up to version v%d.\n", this->config_version, PACKAGE, CONFIG_READER_MIN_VERSION);
fprintf(stderr, " The configuration file will be downgraded to v%d when %s exits.\n", CONFIG_READER_MIN_VERSION, PACKAGE);
String_freeArray(option);
- fclose(fd);
+ fclose(fp);
return false;
}
} else if (String_eq(option[0], "fields") && this->config_version <= 2) {
@@ -381,6 +458,8 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
this->hideKernelThreads = atoi(option[1]);
} else if (String_eq(option[0], "hide_userland_threads")) {
this->hideUserlandThreads = atoi(option[1]);
+ } else if (String_eq(option[0], "hide_running_in_container")) {
+ this->hideRunningInContainer = atoi(option[1]);
} else if (String_eq(option[0], "shadow_other_users")) {
this->shadowOtherUsers = atoi(option[1]);
} else if (String_eq(option[0], "show_thread_names")) {
@@ -391,6 +470,8 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
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], "shadow_distribution_path_prefix")) {
+ this->shadowDistPathPrefix = atoi(option[1]);
} else if (String_eq(option[0], "highlight_megabytes")) {
this->highlightMegabytes = atoi(option[1]);
} else if (String_eq(option[0], "highlight_threads")) {
@@ -475,13 +556,17 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
this->topologyAffinity = !!atoi(option[1]);
#endif
} else if (strncmp(option[0], "screen:", 7) == 0) {
- screen = Settings_newScreen(this, &(const ScreenDefaults){ .name = option[0] + 7, .columns = option[1] });
+ screen = Settings_newScreen(this, &(const ScreenDefaults) { .name = option[0] + 7, .columns = option[1] });
} else if (String_eq(option[0], ".sort_key")) {
- if (screen)
- screen->sortKey = toFieldIndex(this->dynamicColumns, option[1]);
+ if (screen) {
+ int key = toFieldIndex(this->dynamicColumns, option[1]);
+ screen->sortKey = key > 0 ? key : PID;
+ }
} else if (String_eq(option[0], ".tree_sort_key")) {
- if (screen)
- screen->treeSortKey = toFieldIndex(this->dynamicColumns, option[1]);
+ if (screen) {
+ int key = toFieldIndex(this->dynamicColumns, option[1]);
+ screen->treeSortKey = key > 0 ? key : PID;
+ }
} else if (String_eq(option[0], ".sort_direction")) {
if (screen)
screen->direction = atoi(option[1]);
@@ -497,10 +582,15 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
} else if (String_eq(option[0], ".all_branches_collapsed")) {
if (screen)
screen->allBranchesCollapsed = atoi(option[1]);
+ } else if (String_eq(option[0], ".dynamic")) {
+ if (screen) {
+ free_and_xStrdup(&screen->dynamic, option[1]);
+ Platform_addDynamicScreen(screen);
+ }
}
String_freeArray(option);
}
- fclose(fd);
+ fclose(fp);
if (!didReadMeters || !Settings_validateMeters(this))
Settings_defaultMeters(this, initialCpuCount);
if (!this->nScreens)
@@ -508,78 +598,126 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
return didReadAny;
}
-static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, bool byName, char separator) {
+typedef ATTR_FORMAT(printf, 2, 3) int (*OutputFunc)(FILE*, const char*,...);
+
+static void writeFields(OutputFunc of, FILE* fp,
+ const ProcessField* fields, Hashtable* columns,
+ bool byName, char separator) {
const char* sep = "";
for (unsigned int i = 0; fields[i]; i++) {
if (fields[i] < LAST_PROCESSFIELD && byName) {
- const char* pName = toFieldName(columns, fields[i]);
- fprintf(fd, "%s%s", sep, pName);
+ const char* pName = toFieldName(columns, fields[i], NULL);
+ of(fp, "%s%s", sep, pName);
} else if (fields[i] >= LAST_PROCESSFIELD && byName) {
- const char* pName = toFieldName(columns, fields[i]);
- fprintf(fd, " Dynamic(%s)", pName);
+ bool enabled;
+ const char* pName = toFieldName(columns, fields[i], &enabled);
+ if (enabled)
+ of(fp, "%sDynamic(%s)", sep, pName);
} else {
// This "-1" is for compatibility with the older enum format.
- fprintf(fd, "%s%d", sep, (int) fields[i] - 1);
+ of(fp, "%s%d", sep, (int) fields[i] - 1);
}
sep = " ";
}
- fputc(separator, fd);
+ of(fp, "%c", separator);
}
-static void writeList(FILE* fd, char** list, int len, char separator) {
+static void writeList(OutputFunc of, FILE* fp,
+ char** list, int len, char separator) {
const char* sep = "";
for (int i = 0; i < len; i++) {
- fprintf(fd, "%s%s", sep, list[i]);
+ of(fp, "%s%s", sep, list[i]);
sep = " ";
}
- fputc(separator, fd);
+ of(fp, "%c", separator);
}
-static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) {
- writeList(fd, this->hColumns[column].names, this->hColumns[column].len, separator);
+static void writeMeters(const Settings* this, OutputFunc of,
+ FILE* fp, char separator, unsigned int column) {
+ if (this->hColumns[column].len) {
+ writeList(of, fp, this->hColumns[column].names, this->hColumns[column].len, separator);
+ } else {
+ of(fp, "!%c", separator);
+ }
}
-static void writeMeterModes(const Settings* this, FILE* fd, char separator, unsigned int column) {
- const char* sep = "";
- for (size_t i = 0; i < this->hColumns[column].len; i++) {
- fprintf(fd, "%s%d", sep, this->hColumns[column].modes[i]);
- sep = " ";
+static void writeMeterModes(const Settings* this, OutputFunc of,
+ FILE* fp, char separator, unsigned int column) {
+ if (this->hColumns[column].len) {
+ const char* sep = "";
+ for (size_t i = 0; i < this->hColumns[column].len; i++) {
+ of(fp, "%s%u", sep, this->hColumns[column].modes[i]);
+ sep = " ";
+ }
+ } else {
+ of(fp, "!");
}
- fputc(separator, fd);
+
+ of(fp, "%c", separator);
+}
+
+ATTR_FORMAT(printf, 2, 3)
+static int signal_safe_fprintf(FILE* stream, const char* fmt, ...) {
+ char buf[2048];
+
+ va_list vl;
+ va_start(vl, fmt);
+ int n = vsnprintf(buf, sizeof(buf), fmt, vl);
+ va_end(vl);
+
+ if (n <= 0)
+ return n;
+
+ return full_write_str(fileno(stream), buf);
}
int Settings_write(const Settings* this, bool onCrash) {
- FILE* fd;
+ FILE* fp;
char separator;
+ char* tmpFilename = NULL;
+ OutputFunc of;
if (onCrash) {
- fd = stderr;
+ fp = stderr;
separator = ';';
+ of = signal_safe_fprintf;
+ } else if (!this->writeConfig) {
+ return 0;
} else {
- fd = fopen(this->filename, "w");
- if (fd == NULL)
+ /* create tempfile with mode 0600 */
+ mode_t cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ xAsprintf(&tmpFilename, "%s.tmp.XXXXXX", this->filename);
+ int fdtmp = mkstemp(tmpFilename);
+ umask(cur_umask);
+ if (fdtmp == -1)
+ return -errno;
+ fp = fdopen(fdtmp, "w");
+ if (!fp)
return -errno;
separator = '\n';
+ of = fprintf;
}
#define printSettingInteger(setting_, value_) \
- fprintf(fd, setting_ "=%d%c", (int) (value_), separator)
+ of(fp, setting_ "=%d%c", (int) (value_), separator)
#define printSettingString(setting_, value_) \
- fprintf(fd, setting_ "=%s%c", value_, separator)
+ of(fp, setting_ "=%s%c", value_, separator)
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");
+ of(fp, "# Beware! This file is rewritten by htop when settings are changed in the interface.\n");
+ of(fp, "# The parser is also very primitive, and not human-friendly.\n");
}
printSettingString("htop_version", VERSION);
printSettingInteger("config_reader_min_version", CONFIG_READER_MIN_VERSION);
- fprintf(fd, "fields="); writeFields(fd, this->screens[0]->fields, this->dynamicColumns, false, separator);
+ of(fp, "fields="); writeFields(of, fp, this->screens[0]->fields, this->dynamicColumns, false, separator);
printSettingInteger("hide_kernel_threads", this->hideKernelThreads);
printSettingInteger("hide_userland_threads", this->hideUserlandThreads);
+ printSettingInteger("hide_running_in_container", this->hideRunningInContainer);
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("shadow_distribution_path_prefix", this->shadowDistPathPrefix);
printSettingInteger("highlight_megabytes", this->highlightMegabytes);
printSettingInteger("highlight_threads", this->highlightThreads);
printSettingInteger("highlight_changes", this->highlightChanges);
@@ -611,10 +749,10 @@ int Settings_write(const Settings* this, bool onCrash) {
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);
+ of(fp, "column_meters_%u=", i);
+ writeMeters(this, of, fp, separator, i);
+ of(fp, "column_meter_modes_%u=", i);
+ writeMeterModes(this, of, fp, separator, i);
}
// Legacy compatibility with older versions of htop
@@ -629,12 +767,23 @@ int Settings_write(const Settings* this, bool onCrash) {
for (unsigned int i = 0; i < this->nScreens; i++) {
ScreenSettings* ss = this->screens[i];
- fprintf(fd, "screen:%s=", ss->name);
- writeFields(fd, ss->fields, this->dynamicColumns, true, separator);
- printSettingString(".sort_key", toFieldName(this->dynamicColumns, ss->sortKey));
- printSettingString(".tree_sort_key", toFieldName(this->dynamicColumns, ss->treeSortKey));
+ const char* sortKey = toFieldName(this->dynamicColumns, ss->sortKey, NULL);
+ const char* treeSortKey = toFieldName(this->dynamicColumns, ss->treeSortKey, NULL);
+
+ of(fp, "screen:%s=", ss->heading);
+ writeFields(of, fp, ss->fields, this->dynamicColumns, true, separator);
+ if (ss->dynamic) {
+ printSettingString(".dynamic", ss->dynamic);
+ if (ss->sortKey && ss->sortKey != PID)
+ of(fp, "%s=Dynamic(%s)%c", ".sort_key", sortKey, separator);
+ if (ss->treeSortKey && ss->treeSortKey != PID)
+ of(fp, "%s=Dynamic(%s)%c", ".tree_sort_key", treeSortKey, separator);
+ } else {
+ printSettingString(".sort_key", sortKey);
+ printSettingString(".tree_sort_key", treeSortKey);
+ printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID);
+ }
printSettingInteger(".tree_view", ss->treeView);
- printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID);
printSettingInteger(".sort_direction", ss->direction);
printSettingInteger(".tree_sort_direction", ss->treeDirection);
printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed);
@@ -648,19 +797,28 @@ int Settings_write(const Settings* this, bool onCrash) {
int r = 0;
- if (ferror(fd) != 0)
+ if (ferror(fp) != 0)
r = (errno != 0) ? -errno : -EBADF;
- if (fclose(fd) != 0)
+ if (fclose(fp) != 0)
r = r ? r : -errno;
+ if (r == 0)
+ r = (rename(tmpFilename, this->filename) == -1) ? -errno : 0;
+
+ free(tmpFilename);
+
return r;
}
-Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns) {
+Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens) {
Settings* this = xCalloc(1, sizeof(Settings));
+ this->writeConfig = true;
+
+ this->dynamicScreens = dynamicScreens;
this->dynamicColumns = dynamicColumns;
+ this->dynamicMeters = dynamicMeters;
this->hLayout = HF_TWO_50_50;
this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
@@ -668,8 +826,10 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
this->showThreadNames = false;
this->hideKernelThreads = true;
this->hideUserlandThreads = false;
+ this->hideRunningInContainer = false;
this->highlightBaseName = false;
this->highlightDeletedExe = true;
+ this->shadowDistPathPrefix = false;
this->highlightMegabytes = true;
this->detailedCPUTime = false;
this->countCPUsFromOne = false;
@@ -693,66 +853,65 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
this->topologyAffinity = false;
#endif
- this->screens = xCalloc(Platform_numberOfDefaultScreens * sizeof(ScreenSettings*), 1);
+ this->screens = xCalloc(Platform_numberOfDefaultScreens, sizeof(ScreenSettings*));
this->nScreens = 0;
char* legacyDotfile = NULL;
const char* rcfile = getenv("HTOPRC");
if (rcfile) {
- this->filename = xStrdup(rcfile);
+ this->initialFilename = xStrdup(rcfile);
} else {
const char* home = getenv("HOME");
- if (!home)
- home = "";
-
+ if (!home || home[0] != '/') {
+ const struct passwd* pw = getpwuid(getuid());
+ home = (pw && pw->pw_dir && pw->pw_dir[0] == '/') ? pw->pw_dir : "";
+ }
const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
char* configDir = NULL;
char* htopDir = NULL;
- if (xdgConfigHome) {
- this->filename = String_cat(xdgConfigHome, "/htop/htoprc");
+ if (xdgConfigHome && xdgConfigHome[0] == '/') {
+ this->initialFilename = String_cat(xdgConfigHome, "/htop/htoprc");
configDir = xStrdup(xdgConfigHome);
htopDir = String_cat(xdgConfigHome, "/htop");
} else {
- this->filename = String_cat(home, "/.config/htop/htoprc");
+ this->initialFilename = String_cat(home, "/.config/htop/htoprc");
configDir = String_cat(home, "/.config");
htopDir = String_cat(home, "/.config/htop");
}
- legacyDotfile = String_cat(home, "/.htoprc");
(void) mkdir(configDir, 0700);
(void) mkdir(htopDir, 0700);
free(htopDir);
free(configDir);
- struct stat st;
- int err = lstat(legacyDotfile, &st);
- if (err || S_ISLNK(st.st_mode)) {
- free(legacyDotfile);
- legacyDotfile = NULL;
- }
+
+ legacyDotfile = String_cat(home, "/.htoprc");
}
+
+ this->filename = xMalloc(PATH_MAX);
+ if (!realpath(this->initialFilename, this->filename))
+ free_and_xStrdup(&this->filename, this->initialFilename);
+
this->colorScheme = 0;
#ifdef HAVE_GETMOUSE
this->enableMouse = true;
#endif
this->changed = false;
this->delay = DEFAULT_DELAY;
- bool ok = false;
- if (legacyDotfile) {
- ok = Settings_read(this, legacyDotfile, initialCpuCount);
- if (ok) {
+
+ bool ok = Settings_read(this, this->filename, initialCpuCount, /*checkWritability*/true);
+ if (!ok && legacyDotfile) {
+ ok = Settings_read(this, legacyDotfile, initialCpuCount, this->writeConfig);
+ if (ok && this->writeConfig) {
// Transition to new location and delete old configuration file
if (Settings_write(this, false) == 0) {
unlink(legacyDotfile);
}
}
- free(legacyDotfile);
- }
- if (!ok) {
- ok = Settings_read(this, this->filename, initialCpuCount);
}
if (!ok) {
this->screenTabs = true;
this->changed = true;
- ok = Settings_read(this, SYSCONFDIR "/htoprc", initialCpuCount);
+
+ ok = Settings_read(this, SYSCONFDIR "/htoprc", initialCpuCount, /*checkWritability*/false);
}
if (!ok) {
Settings_defaultMeters(this, initialCpuCount);
@@ -764,6 +923,8 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
this->lastUpdate = 1;
+ free(legacyDotfile);
+
return this;
}
diff --git a/Settings.h b/Settings.h
index facd3f2a..9d9e0dba 100644
--- a/Settings.h
+++ b/Settings.h
@@ -7,40 +7,47 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include "Hashtable.h"
#include "HeaderLayout.h"
-#include "Process.h"
+#include "MeterMode.h"
+#include "Row.h"
+#include "RowField.h"
#define DEFAULT_DELAY 15
#define CONFIG_READER_MIN_VERSION 3
+struct DynamicScreen_; // IWYU pragma: keep
+struct Table_; // IWYU pragma: keep
+
typedef struct {
const char* name;
const char* columns;
const char* sortKey;
+ const char* treeSortKey;
} ScreenDefaults;
typedef struct {
size_t len;
char** names;
- int* modes;
+ MeterModeId* modes;
} MeterColumnSetting;
-typedef struct {
- char* name;
- ProcessField* fields;
+typedef struct ScreenSettings_ {
+ char* heading; /* user-editable screen name (pretty) */
+ char* dynamic; /* from DynamicScreen config (fixed) */
+ struct Table_* table;
+ RowField* fields;
uint32_t flags;
int direction;
int treeDirection;
- ProcessField sortKey;
- ProcessField treeSortKey;
+ RowField sortKey;
+ RowField treeSortKey;
bool treeView;
bool treeViewAlwaysByPID;
bool allBranchesCollapsed;
@@ -48,10 +55,14 @@ typedef struct {
typedef struct Settings_ {
char* filename;
+ char* initialFilename;
+ bool writeConfig; /* whether to write the current settings on exit */
int config_version;
HeaderLayout hLayout;
MeterColumnSetting* hColumns;
- Hashtable* dynamicColumns;
+ Hashtable* dynamicColumns; /* runtime-discovered columns */
+ Hashtable* dynamicMeters; /* runtime-discovered meters */
+ Hashtable* dynamicScreens; /* runtime-discovered screens */
ScreenSettings** screens;
unsigned int nScreens;
@@ -73,9 +84,11 @@ typedef struct Settings_ {
bool shadowOtherUsers;
bool showThreadNames;
bool hideKernelThreads;
+ bool hideRunningInContainer;
bool hideUserlandThreads;
bool highlightBaseName;
bool highlightDeletedExe;
+ bool shadowDistPathPrefix;
bool highlightMegabytes;
bool highlightThreads;
bool highlightChanges;
@@ -101,9 +114,9 @@ typedef struct Settings_ {
#define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu))
-static inline ProcessField ScreenSettings_getActiveSortKey(const ScreenSettings* this) {
+static inline RowField ScreenSettings_getActiveSortKey(const ScreenSettings* this) {
return (this->treeView)
- ? (this->treeViewAlwaysByPID ? PID : this->treeSortKey)
+ ? (this->treeViewAlwaysByPID ? 1 : this->treeSortKey)
: this->sortKey;
}
@@ -115,15 +128,17 @@ void Settings_delete(Settings* this);
int Settings_write(const Settings* this, bool onCrash);
-Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns);
+Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens);
ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults);
+ScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const struct DynamicScreen_* screen, struct Table_* table);
+
void ScreenSettings_delete(ScreenSettings* this);
void ScreenSettings_invertSortOrder(ScreenSettings* this);
-void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey);
+void ScreenSettings_setSortKey(ScreenSettings* this, RowField sortKey);
void Settings_enableReadonly(void);
diff --git a/SignalsPanel.c b/SignalsPanel.c
index 7e21ce93..28d307e2 100644
--- a/SignalsPanel.c
+++ b/SignalsPanel.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 "SignalsPanel.h"
// the above contains #include <signal.h> so do not add that here again (breaks Solaris build)
diff --git a/SignalsPanel.h b/SignalsPanel.h
index 529043a1..20fb4a68 100644
--- a/SignalsPanel.h
+++ b/SignalsPanel.h
@@ -7,14 +7,12 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
+#include "Panel.h"
#ifndef HTOP_SOLARIS
#include <signal.h>
#endif
-#include "Panel.h"
-
typedef struct SignalItem_ {
const char* name;
diff --git a/SwapMeter.c b/SwapMeter.c
index 081967fe..1055a6e7 100644
--- a/SwapMeter.c
+++ b/SwapMeter.c
@@ -13,6 +13,7 @@ in the source distribution for its full text.
#include <stddef.h>
#include "CRT.h"
+#include "Macros.h"
#include "Object.h"
#include "Platform.h"
#include "RichString.h"
@@ -20,7 +21,8 @@ in the source distribution for its full text.
static const int SwapMeter_attributes[] = {
SWAP,
- SWAP_CACHE
+ SWAP_CACHE,
+ SWAP_FRONTSWAP,
};
static void SwapMeter_updateValues(Meter* this) {
@@ -28,10 +30,11 @@ static void SwapMeter_updateValues(Meter* this) {
size_t size = sizeof(this->txtBuffer);
int written;
- this->values[1] = NAN; /* 'cached' not present on all platforms */
+ this->values[SWAP_METER_CACHE] = NAN; /* 'cached' not present on all platforms */
+ this->values[SWAP_METER_FRONTSWAP] = NAN; /* 'frontswap' not present on all platforms */
Platform_setSwapValues(this);
- written = Meter_humanUnit(buffer, this->values[0], size);
+ written = Meter_humanUnit(buffer, this->values[SWAP_METER_USED], size);
METER_BUFFER_CHECK(buffer, size, written);
METER_BUFFER_APPEND_CHR(buffer, size, '/');
@@ -45,15 +48,21 @@ static void SwapMeter_display(const Object* cast, RichString* out) {
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));
+ Meter_humanUnit(buffer, this->values[SWAP_METER_USED], 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));
+ if (isNonnegative(this->values[SWAP_METER_CACHE])) {
+ Meter_humanUnit(buffer, this->values[SWAP_METER_CACHE], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " cache:");
RichString_appendAscii(out, CRT_colors[SWAP_CACHE], buffer);
}
+
+ if (isNonnegative(this->values[SWAP_METER_FRONTSWAP])) {
+ Meter_humanUnit(buffer, this->values[SWAP_METER_FRONTSWAP], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " frontswap:");
+ RichString_appendAscii(out, CRT_colors[SWAP_FRONTSWAP], buffer);
+ }
}
const MeterClass SwapMeter_class = {
@@ -64,7 +73,7 @@ const MeterClass SwapMeter_class = {
},
.updateValues = SwapMeter_updateValues,
.defaultMode = BAR_METERMODE,
- .maxItems = 2,
+ .maxItems = SWAP_METER_ITEMCOUNT,
.total = 100.0,
.attributes = SwapMeter_attributes,
.name = "Swap",
diff --git a/SwapMeter.h b/SwapMeter.h
index 485485a1..94b8dc85 100644
--- a/SwapMeter.h
+++ b/SwapMeter.h
@@ -9,6 +9,12 @@ in the source distribution for its full text.
#include "Meter.h"
+typedef enum {
+ SWAP_METER_USED = 0,
+ SWAP_METER_CACHE = 1,
+ SWAP_METER_FRONTSWAP = 2,
+ SWAP_METER_ITEMCOUNT = 3, // number of entries in this enum
+} SwapMeterValues;
extern const MeterClass SwapMeter_class;
diff --git a/TESTPLAN b/TESTPLAN
index 88fe039d..b6ddfa6a 100644
--- a/TESTPLAN
+++ b/TESTPLAN
@@ -5,7 +5,7 @@ Main screen:
Mouse click header - nothing happens.
- Mouse click on ProcessList title bar - exit Tree view, update FunctionBar, title bar updates, sort by clicked field.
+ Mouse click on ProcessTable title bar - exit Tree view, update FunctionBar, title bar updates, sort by clicked field.
*** FAILING: wrong FB update depending on mode; does not change sort in wip branch
click on same entry - invert sort.
click on another entry - sort another field.
diff --git a/Table.c b/Table.c
new file mode 100644
index 00000000..79bd4324
--- /dev/null
+++ b/Table.c
@@ -0,0 +1,371 @@
+/*
+htop - Table.c
+(C) 2004,2005 Hisham H. Muhammad
+(C) 2023 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 "Table.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "CRT.h"
+#include "Hashtable.h"
+#include "Machine.h"
+#include "Macros.h"
+#include "Panel.h"
+#include "RowField.h"
+#include "Vector.h"
+
+
+Table* Table_init(Table* this, const ObjectClass* klass, Machine* host) {
+ this->rows = Vector_new(klass, true, DEFAULT_SIZE);
+ this->displayList = Vector_new(klass, false, DEFAULT_SIZE);
+ this->table = Hashtable_new(200, false);
+ this->needsSort = true;
+ this->following = -1;
+ this->host = host;
+ return this;
+}
+
+void Table_done(Table* this) {
+ Hashtable_delete(this->table);
+ Vector_delete(this->displayList);
+ Vector_delete(this->rows);
+}
+
+static void Table_delete(Object* cast) {
+ Table* this = (Table*) cast;
+ Table_done(this);
+ free(this);
+}
+
+void Table_setPanel(Table* this, Panel* panel) {
+ this->panel = panel;
+}
+
+void Table_add(Table* this, Row* row) {
+ assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) == -1);
+ assert(Hashtable_get(this->table, row->id) == NULL);
+
+ // highlighting row found in first scan by first scan marked "far in the past"
+ row->seenStampMs = this->host->monotonicMs;
+
+ Vector_add(this->rows, row);
+ Hashtable_put(this->table, row->id, row);
+
+ assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) != -1);
+ assert(Hashtable_get(this->table, row->id) != NULL);
+ assert(Vector_countEquals(this->rows, Hashtable_count(this->table)));
+}
+
+// Table_removeIndex removes a given row from the lists map and soft deletes
+// it from its vector. Vector_compact *must* be called once the caller is done
+// removing items.
+// Note: for processes should only be called from ProcessTable_iterate to avoid
+// breaking dying process highlighting.
+void Table_removeIndex(Table* this, const Row* row, int idx) {
+ int rowid = row->id;
+
+ assert(row == (Row*)Vector_get(this->rows, idx));
+ assert(Hashtable_get(this->table, rowid) != NULL);
+
+ Hashtable_remove(this->table, rowid);
+ Vector_softRemove(this->rows, idx);
+
+ if (this->following != -1 && this->following == rowid) {
+ this->following = -1;
+ Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS);
+ }
+
+ assert(Hashtable_get(this->table, rowid) == NULL);
+ assert(Vector_countEquals(this->rows, Hashtable_count(this->table)));
+}
+
+static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, int32_t indent, bool show) {
+ // Do not treat zero as root of any tree.
+ // (e.g. on OpenBSD the kernel thread 'swapper' has pid 0.)
+ if (rowid == 0)
+ return;
+
+ // The vector is sorted by parent, find the start of the range by bisection
+ int vsize = Vector_size(this->rows);
+ int l = 0;
+ int r = vsize;
+ while (l < r) {
+ int c = (l + r) / 2;
+ Row* row = (Row*)Vector_get(this->rows, c);
+ int parent = row->isRoot ? 0 : Row_getGroupOrParent(row);
+ if (parent < rowid) {
+ l = c + 1;
+ } else {
+ r = c;
+ }
+ }
+ // Find the end to know the last line for indent handling purposes
+ int lastShown = r;
+ while (r < vsize) {
+ Row* row = (Row*)Vector_get(this->rows, r);
+ if (!Row_isChildOf(row, rowid))
+ break;
+ if (row->show)
+ lastShown = r;
+ r++;
+ }
+
+ for (int i = l; i < r; i++) {
+ Row* row = (Row*)Vector_get(this->rows, i);
+
+ if (!show)
+ row->show = false;
+
+ Vector_add(this->displayList, row);
+
+ int32_t nextIndent = indent | ((int32_t)1 << MINIMUM(level, sizeof(row->indent) * 8 - 2));
+ Table_buildTreeBranch(this, row->id, level + 1, (i < lastShown) ? nextIndent : indent, row->show && row->showChildren);
+ if (i == lastShown)
+ row->indent = -nextIndent;
+ else
+ row->indent = nextIndent;
+
+ row->tree_depth = level + 1;
+ }
+}
+
+static int compareRowByKnownParentThenNatural(const void* v1, const void* v2) {
+ return Row_compareByParent((const Row*) v1, (const Row*) v2);
+}
+
+// Builds a sorted tree from scratch, without relying on previously gathered information
+static void Table_buildTree(Table* this) {
+ Vector_prune(this->displayList);
+
+ // Mark root processes
+ int vsize = Vector_size(this->rows);
+ for (int i = 0; i < vsize; i++) {
+ Row* row = (Row*) Vector_get(this->rows, i);
+ int parent = Row_getGroupOrParent(row);
+ row->isRoot = false;
+
+ if (row->id == parent) {
+ row->isRoot = true;
+ continue;
+ }
+
+ if (!parent) {
+ row->isRoot = true;
+ continue;
+ }
+
+ // We don't know about its parent for whatever reason
+ if (Table_findRow(this, parent) == NULL)
+ row->isRoot = true;
+ }
+
+ // Sort by known parent (roots first), then row ID
+ Vector_quickSortCustomCompare(this->rows, compareRowByKnownParentThenNatural);
+
+ // Find all processes whose parent is not visible
+ for (int i = 0; i < vsize; i++) {
+ Row* row = (Row*)Vector_get(this->rows, i);
+
+ // If parent not found, then construct the tree with this node as root
+ if (row->isRoot) {
+ row = (Row*)Vector_get(this->rows, i);
+ row->indent = 0;
+ row->tree_depth = 0;
+ Vector_add(this->displayList, row);
+ Table_buildTreeBranch(this, row->id, 0, 0, row->showChildren);
+ continue;
+ }
+ }
+
+ this->needsSort = false;
+
+ // Check consistency of the built structures
+ assert(Vector_size(this->displayList) == vsize); (void)vsize;
+}
+
+void Table_updateDisplayList(Table* this) {
+ const Settings* settings = this->host->settings;
+
+ if (settings->ss->treeView) {
+ if (this->needsSort)
+ Table_buildTree(this);
+ } else {
+ if (this->needsSort)
+ Vector_insertionSort(this->rows);
+ Vector_prune(this->displayList);
+ int size = Vector_size(this->rows);
+ for (int i = 0; i < size; i++)
+ Vector_add(this->displayList, Vector_get(this->rows, i));
+ }
+ this->needsSort = false;
+}
+
+void Table_expandTree(Table* this) {
+ int size = Vector_size(this->rows);
+ for (int i = 0; i < size; i++) {
+ Row* row = (Row*) Vector_get(this->rows, i);
+ row->showChildren = true;
+ }
+}
+
+// Called on collapse-all toggle and on startup, possibly in non-tree mode
+void Table_collapseAllBranches(Table* this) {
+ Table_buildTree(this); // Update `tree_depth` fields of the rows
+ this->needsSort = true; // Table is sorted by parent now, force new sort
+ int size = Vector_size(this->rows);
+ for (int i = 0; i < size; i++) {
+ Row* row = (Row*) Vector_get(this->rows, i);
+ // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1
+ if (row->tree_depth > 0 && row->id > 1)
+ row->showChildren = false;
+ }
+}
+
+void Table_rebuildPanel(Table* this) {
+ Table_updateDisplayList(this);
+
+ const int currPos = Panel_getSelectedIndex(this->panel);
+ const int currScrollV = this->panel->scrollV;
+ const int currSize = Panel_size(this->panel);
+
+ Panel_prune(this->panel);
+
+ /* Follow main group row instead if following a row that is occluded (hidden) */
+ if (this->following != -1) {
+ const Row* followed = (const Row*) Hashtable_get(this->table, this->following);
+ if (followed != NULL
+ && Hashtable_get(this->table, followed->group)
+ && Row_isVisible(followed, this) == false ) {
+ this->following = followed->group;
+ }
+ }
+
+ const int rowCount = Vector_size(this->displayList);
+ bool foundFollowed = false;
+ int idx = 0;
+
+ for (int i = 0; i < rowCount; i++) {
+ Row* row = (Row*) Vector_get(this->displayList, i);
+
+ if ( !row->show || (Row_matchesFilter(row, this) == true) )
+ continue;
+
+ Panel_set(this->panel, idx, (Object*)row);
+
+ if (this->following != -1 && row->id == this->following) {
+ foundFollowed = true;
+ Panel_setSelected(this->panel, idx);
+ /* Keep scroll position relative to followed row */
+ this->panel->scrollV = idx - (currPos - currScrollV);
+ }
+ idx++;
+ }
+
+ if (this->following != -1 && !foundFollowed) {
+ /* Reset if current followed row 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;
+ }
+}
+
+void Table_printHeader(const Settings* settings, RichString* header) {
+ RichString_rewind(header, RichString_size(header));
+
+ const ScreenSettings* ss = settings->ss;
+ const RowField* fields = ss->fields;
+
+ RowField key = ScreenSettings_getActiveSortKey(ss);
+
+ for (int i = 0; fields[i]; i++) {
+ int color;
+ if (ss->treeView && ss->treeViewAlwaysByPID) {
+ color = CRT_colors[PANEL_HEADER_FOCUS];
+ } else if (key == fields[i]) {
+ color = CRT_colors[PANEL_SELECTION_FOCUS];
+ } else {
+ color = CRT_colors[PANEL_HEADER_FOCUS];
+ }
+
+ RichString_appendWide(header, color, RowField_alignedTitle(settings, fields[i]));
+ if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') {
+ bool ascending = ScreenSettings_getActiveDirection(ss) == 1;
+ RichString_rewind(header, 1); // rewind to override space
+ RichString_appendWide(header,
+ CRT_colors[PANEL_SELECTION_FOCUS],
+ CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC]);
+ }
+ if (COMM == fields[i] && settings->showMergedCommand) {
+ RichString_appendAscii(header, color, "(merged)");
+ }
+ }
+}
+
+// set flags on an existing rows before refreshing table
+void Table_prepareEntries(Table* this) {
+ for (int i = 0; i < Vector_size(this->rows); i++) {
+ Row* row = (struct Row_*) Vector_get(this->rows, i);
+ row->updated = false;
+ row->wasShown = row->show;
+ row->show = true;
+ }
+}
+
+// tidy up Row state after refreshing the table
+void Table_cleanupRow(Table* table, Row* row, int idx) {
+ Machine* host = table->host;
+ const Settings* settings = host->settings;
+
+ if (row->tombStampMs > 0) {
+ // remove tombed process
+ if (host->monotonicMs >= row->tombStampMs) {
+ Table_removeIndex(table, row, idx);
+ }
+ } else if (row->updated == false) {
+ // process no longer exists
+ if (settings->highlightChanges && row->wasShown) {
+ // mark tombed
+ row->tombStampMs = host->monotonicMs + 1000 * settings->highlightDelaySecs;
+ } else {
+ // immediately remove
+ Table_removeIndex(table, row, idx);
+ }
+ }
+}
+
+void Table_cleanupEntries(Table* this) {
+ // Finish process table update, culling any removed rows
+ for (int i = Vector_size(this->rows) - 1; i >= 0; i--) {
+ Row* row = (Row*) Vector_get(this->rows, i);
+ Table_cleanupRow(this, row, i);
+ }
+
+ // compact the table in case of any earlier row removals
+ Table_compact(this);
+}
+
+const TableClass Table_class = {
+ .super = {
+ .extends = Class(Object),
+ .delete = Table_delete,
+ },
+ .prepare = Table_prepareEntries,
+ .cleanup = Table_cleanupEntries,
+};
diff --git a/Table.h b/Table.h
new file mode 100644
index 00000000..fa85fd66
--- /dev/null
+++ b/Table.h
@@ -0,0 +1,95 @@
+#ifndef HEADER_Table
+#define HEADER_Table
+/*
+htop - Table.h
+(C) 2004,2005 Hisham H. Muhammad
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "Hashtable.h"
+#include "Object.h"
+#include "RichString.h"
+#include "Settings.h"
+#include "Vector.h"
+
+
+struct Machine_; // IWYU pragma: keep
+struct Panel_; // IWYU pragma: keep
+struct Row_; // IWYU pragma: keep
+
+typedef struct Table_ {
+ /* Super object for emulated OOP */
+ Object super;
+
+ Vector* rows; /* all known; sort order can vary and differ from display order */
+ Vector* displayList; /* row tree flattened in display order (borrowed);
+ updated in Table_updateDisplayList when rebuilding panel */
+ Hashtable* table; /* fast known row lookup by identifier */
+
+ struct Machine_* host;
+ const char* incFilter;
+ bool needsSort;
+ int following; /* -1 or row being visually tracked in the user interface */
+
+ struct Panel_* panel;
+} Table;
+
+typedef Table* (*Table_New)(const struct Machine_*);
+typedef void (*Table_ScanPrepare)(Table* this);
+typedef void (*Table_ScanIterate)(Table* this);
+typedef void (*Table_ScanCleanup)(Table* this);
+
+typedef struct TableClass_ {
+ const ObjectClass super;
+ const Table_ScanPrepare prepare;
+ const Table_ScanIterate iterate;
+ const Table_ScanCleanup cleanup;
+} TableClass;
+
+#define As_Table(this_) ((const TableClass*)((this_)->super.klass))
+
+#define Table_scanPrepare(t_) (As_Table(t_)->prepare ? (As_Table(t_)->prepare(t_)) : Table_prepareEntries(t_))
+#define Table_scanIterate(t_) (As_Table(t_)->iterate(t_)) /* mandatory; must have a custom iterate method */
+#define Table_scanCleanup(t_) (As_Table(t_)->cleanup ? (As_Table(t_)->cleanup(t_)) : Table_cleanupEntries(t_))
+
+Table* Table_init(Table* this, const ObjectClass* klass, struct Machine_* host);
+
+void Table_done(Table* this);
+
+extern const TableClass Table_class;
+
+void Table_setPanel(Table* this, struct Panel_* panel);
+
+void Table_printHeader(const Settings* settings, RichString* header);
+
+void Table_add(Table* this, struct Row_* row);
+
+void Table_removeIndex(Table* this, const struct Row_* row, int idx);
+
+void Table_updateDisplayList(Table* this);
+
+void Table_expandTree(Table* this);
+
+void Table_collapseAllBranches(Table* this);
+
+void Table_rebuildPanel(Table* this);
+
+static inline struct Row_* Table_findRow(Table* this, int id) {
+ return (struct Row_*) Hashtable_get(this->table, id);
+}
+
+void Table_prepareEntries(Table* this);
+
+void Table_cleanupEntries(Table* this);
+
+void Table_cleanupRow(Table* this, Row* row, int idx);
+
+static inline void Table_compact(Table* this) {
+ Vector_compact(this->rows);
+}
+
+#endif
diff --git a/TasksMeter.c b/TasksMeter.c
index 64c9837c..aa41e631 100644
--- a/TasksMeter.c
+++ b/TasksMeter.c
@@ -5,12 +5,15 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "TasksMeter.h"
#include "CRT.h"
+#include "Machine.h"
#include "Macros.h"
#include "Object.h"
-#include "ProcessList.h"
+#include "ProcessTable.h"
#include "RichString.h"
#include "Settings.h"
#include "XUtils.h"
@@ -24,19 +27,21 @@ static const int TasksMeter_attributes[] = {
};
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->activeCPUs);
- this->total = pl->totalTasks;
+ const Machine* host = this->host;
+ const ProcessTable* pt = (const ProcessTable*) host->processTable;
+
+ this->values[0] = pt->kernelThreads;
+ this->values[1] = pt->userlandThreads;
+ this->values[2] = pt->totalTasks - pt->kernelThreads - pt->userlandThreads;
+ this->values[3] = MINIMUM(pt->runningTasks, host->activeCPUs);
+ this->total = pt->totalTasks;
- xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%u/%u", MINIMUM(pl->runningTasks, pl->activeCPUs), pl->totalTasks);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%u/%u", MINIMUM(pt->runningTasks, host->activeCPUs), pt->totalTasks);
}
static void TasksMeter_display(const Object* cast, RichString* out) {
const Meter* this = (const Meter*)cast;
- const Settings* settings = this->pl->settings;
+ const Settings* settings = this->host->settings;
char buffer[20];
int len;
diff --git a/TraceScreen.c b/TraceScreen.c
index 90400b43..d421d0eb 100644
--- a/TraceScreen.c
+++ b/TraceScreen.c
@@ -62,7 +62,7 @@ void TraceScreen_delete(Object* cast) {
}
static void TraceScreen_draw(InfoScreen* this) {
- InfoScreen_drawTitled(this, "Trace of process %d - %s", this->process->pid, Process_getCommand(this->process));
+ InfoScreen_drawTitled(this, "Trace of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process));
}
bool TraceScreen_forkTracer(TraceScreen* this) {
@@ -89,26 +89,38 @@ bool TraceScreen_forkTracer(TraceScreen* this) {
close(fdpair[1]);
char buffer[32] = {0};
- xSnprintf(buffer, sizeof(buffer), "%d", this->super.process->pid);
- // Use of NULL in variadic functions must have a pointer cast.
- // The NULL constant is not required by standard to have a pointer type.
- execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (char *)NULL);
-
- // Should never reach here, unless execlp fails ...
- const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH.";
- (void)! write(STDERR_FILENO, message, strlen(message));
+ xSnprintf(buffer, sizeof(buffer), "%d", Process_getPid(this->super.process));
+
+ #if defined(HTOP_FREEBSD) || defined(HTOP_OPENBSD) || defined(HTOP_NETBSD) || defined(HTOP_DRAGONFLYBSD) || defined(HTOP_SOLARIS)
+ // Use of NULL in variadic functions must have a pointer cast.
+ // The NULL constant is not required by standard to have a pointer type.
+ execlp("truss", "truss", "-s", "512", "-p", buffer, (void*)NULL);
+
+ // Should never reach here, unless execlp fails ...
+ const char* message = "Could not execute 'truss'. Please make sure it is available in your $PATH.";
+ (void)! write(STDERR_FILENO, message, strlen(message));
+ #elif defined(HTOP_LINUX)
+ execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (void*)NULL);
+
+ // Should never reach here, unless execlp fails ...
+ const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH.";
+ (void)! write(STDERR_FILENO, message, strlen(message));
+ #else // HTOP_DARWIN, HTOP_PCP == HTOP_UNSUPPORTED
+ const char* message = "Tracing unavailable on not supported system.";
+ (void)! write(STDERR_FILENO, message, strlen(message));
+ #endif
exit(127);
}
- FILE* fd = fdopen(fdpair[0], "r");
- if (!fd)
+ FILE* fp = fdopen(fdpair[0], "r");
+ if (!fp)
goto err;
close(fdpair[1]);
this->child = child;
- this->strace = fd;
+ this->strace = fp;
return true;
err:
@@ -164,20 +176,22 @@ static void TraceScreen_updateTrace(InfoScreen* super) {
static bool TraceScreen_onKey(InfoScreen* super, int ch) {
TraceScreen* this = (TraceScreen*) super;
- switch(ch) {
+
+ switch (ch) {
case 'f':
case KEY_F(8):
this->follow = !(this->follow);
if (this->follow)
- Panel_setSelected(super->display, Panel_size(super->display)-1);
+ Panel_setSelected(super->display, Panel_size(super->display) - 1);
return true;
case 't':
case KEY_F(9):
this->tracing = !this->tracing;
- FunctionBar_setLabel(super->display->defaultBar, KEY_F(9), this->tracing?"Stop Tracing ":"Resume Tracing ");
+ FunctionBar_setLabel(super->display->defaultBar, KEY_F(9), this->tracing ? "Stop Tracing " : "Resume Tracing ");
InfoScreen_draw(this);
return true;
}
+
this->follow = false;
return false;
}
diff --git a/UptimeMeter.c b/UptimeMeter.c
index d4b3175e..622deda3 100644
--- a/UptimeMeter.c
+++ b/UptimeMeter.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 "UptimeMeter.h"
#include "CRT.h"
diff --git a/UsersTable.c b/UsersTable.c
index 8c4a0ed0..6586a4b9 100644
--- a/UsersTable.c
+++ b/UsersTable.c
@@ -17,7 +17,7 @@ in the source distribution for its full text.
#include "XUtils.h"
-UsersTable* UsersTable_new() {
+UsersTable* UsersTable_new(void) {
UsersTable* this;
this = xMalloc(sizeof(UsersTable));
this->users = Hashtable_new(10, true);
diff --git a/Vector.c b/Vector.c
index eddbc9a6..aeb19391 100644
--- a/Vector.c
+++ b/Vector.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 "Vector.h"
#include <assert.h>
@@ -23,14 +25,16 @@ Vector* Vector_new(const ObjectClass* type, bool owner, int size) {
assert(size > 0);
this = xMalloc(sizeof(Vector));
- this->growthRate = size;
- this->array = (Object**) xCalloc(size, sizeof(Object*));
- this->arraySize = size;
- this->items = 0;
- this->type = type;
- this->owner = owner;
- this->dirty_index = -1;
- this->dirty_count = 0;
+ *this = (Vector) {
+ .growthRate = size,
+ .array = xCalloc(size, sizeof(Object*)),
+ .arraySize = size,
+ .items = 0,
+ .type = type,
+ .owner = owner,
+ .dirty_index = -1,
+ .dirty_count = 0,
+ };
return this;
}
@@ -62,14 +66,6 @@ static bool Vector_isConsistent(const Vector* this) {
assert(this->items <= this->arraySize);
assert(!Vector_isDirty(this));
- if (this->owner) {
- for (int i = 0; i < this->items; i++) {
- if (!this->array[i]) {
- return false;
- }
- }
- }
-
return true;
}
@@ -103,13 +99,13 @@ void Vector_prune(Vector* this) {
for (int i = 0; i < this->items; i++) {
if (this->array[i]) {
Object_delete(this->array[i]);
- this->array[i] = NULL;
}
}
}
this->items = 0;
this->dirty_index = -1;
this->dirty_count = 0;
+ memset(this->array, '\0', this->arraySize * sizeof(Object*));
}
//static int comparisons = 0;
@@ -200,15 +196,13 @@ void Vector_insertionSort(Vector* this) {
assert(Vector_isConsistent(this));
}
-static void Vector_checkArraySize(Vector* this) {
- assert(Vector_isConsistent(this));
- if (this->items >= this->arraySize) {
- //int i;
- //i = this->arraySize;
- this->arraySize = this->items + this->growthRate;
- this->array = (Object**) xRealloc(this->array, sizeof(Object*) * this->arraySize);
- //for (; i < this->arraySize; i++)
- // this->array[i] = NULL;
+static void Vector_resizeIfNecessary(Vector* this, int newSize) {
+ assert(newSize >= 0);
+ if (newSize > this->arraySize) {
+ assert(Vector_isConsistent(this));
+ int oldSize = this->arraySize;
+ this->arraySize = newSize + this->growthRate;
+ this->array = (Object**)xReallocArrayZero(this->array, oldSize, this->arraySize, sizeof(Object*));
}
assert(Vector_isConsistent(this));
}
@@ -223,7 +217,7 @@ void Vector_insert(Vector* this, int idx, void* data_) {
idx = this->items;
}
- Vector_checkArraySize(this);
+ Vector_resizeIfNecessary(this, this->items + 1);
//assert(this->array[this->items] == NULL);
if (idx < this->items) {
memmove(&this->array[idx + 1], &this->array[idx], (this->items - idx) * sizeof(this->array[0]));
@@ -242,7 +236,7 @@ Object* Vector_take(Vector* this, int idx) {
if (idx < this->items) {
memmove(&this->array[idx], &this->array[idx + 1], (this->items - idx) * sizeof(this->array[0]));
}
- //this->array[this->items] = NULL;
+ this->array[this->items] = NULL;
assert(Vector_isConsistent(this));
return removed;
}
@@ -290,16 +284,19 @@ void Vector_compact(Vector* this) {
int idx = this->dirty_index;
- /* one deletion: use memmove, which should be faster */
+ // one deletion: use memmove, which should be faster
if (this->dirty_count == 1) {
memmove(&this->array[idx], &this->array[idx + 1], (this->items - idx - 1) * sizeof(this->array[0]));
+ this->array[this->items - 1] = NULL;
} else {
- /* multiple deletions */
+ // multiple deletions
for (int i = idx + 1; i < size; i++) {
if (this->array[i]) {
this->array[idx++] = this->array[i];
}
}
+ // idx is now at the end of the vector and on the first index which should be set to NULL
+ memset(&this->array[idx], '\0', (size - idx) * sizeof(this->array[0]));
}
this->items -= this->dirty_count;
@@ -339,14 +336,15 @@ void Vector_set(Vector* this, int idx, void* data_) {
assert(Object_isA(data, this->type));
assert(Vector_isConsistent(this));
- Vector_checkArraySize(this);
+ Vector_resizeIfNecessary(this, idx + 1);
if (idx >= this->items) {
this->items = idx + 1;
} else {
if (this->owner) {
Object* removed = this->array[idx];
- assert (removed != NULL);
- Object_delete(removed);
+ if (removed != NULL) {
+ Object_delete(removed);
+ }
}
}
this->array[idx] = data;
@@ -396,11 +394,11 @@ int Vector_indexOf(const Vector* this, const void* search_, Object_Compare compa
void Vector_splice(Vector* this, Vector* from) {
assert(Vector_isConsistent(this));
assert(Vector_isConsistent(from));
- assert(!(this->owner && from->owner));
+ assert(!this->owner);
int olditems = this->items;
+ Vector_resizeIfNecessary(this, this->items + from->items);
this->items += from->items;
- Vector_checkArraySize(this);
for (int j = 0; j < from->items; j++) {
this->array[olditems + j] = from->array[j];
}
diff --git a/XUtils.c b/XUtils.c
index 2012b6c6..ec6cce4a 100644
--- a/XUtils.c
+++ b/XUtils.c
@@ -12,6 +12,7 @@ in the source distribution for its full text.
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
+#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
@@ -19,9 +20,10 @@ in the source distribution for its full text.
#include <unistd.h>
#include "CRT.h"
+#include "Macros.h"
-void fail() {
+void fail(void) {
CRT_done();
abort();
@@ -61,9 +63,16 @@ void* xCalloc(size_t nmemb, size_t size) {
void* xRealloc(void* ptr, size_t size) {
assert(size > 0);
- void* data = realloc(ptr, size); // deepcode ignore MemoryLeakOnRealloc: this goes to fail()
+ void* data = size ? realloc(ptr, size) : NULL; // deepcode ignore MemoryLeakOnRealloc: this goes to fail()
if (!data) {
- free(ptr);
+ /* free'ing ptr here causes an indirect memory leak if pointers
+ * are held as part of an potential array referenced in ptr.
+ * In GCC 14 -fanalyzer recognizes this leak, but fails to
+ * ignore it given that this path ends in a noreturn function.
+ * Thus to avoid this confusing diagnostic we opt to leave
+ * that pointer alone instead.
+ */
+ // free(ptr);
fail();
}
return data;
@@ -104,9 +113,9 @@ inline bool String_contains_i(const char* s1, const char* s2, bool multi) {
String_freeArray(needles);
return true;
}
- }
- String_freeArray(needles);
- return false;
+ }
+ String_freeArray(needles);
+ return false;
} else {
return strcasestr(s1, s2) != NULL;
}
@@ -177,13 +186,13 @@ void String_freeArray(char** s) {
free(s);
}
-char* String_readLine(FILE* fd) {
+char* String_readLine(FILE* fp) {
const size_t step = 1024;
size_t bufSize = step;
char* buffer = xMalloc(step + 1);
char* at = buffer;
for (;;) {
- const char* ok = fgets(at, step + 1, fd);
+ const char* ok = fgets(at, step + 1, fp);
if (!ok) {
free(buffer);
return NULL;
@@ -193,7 +202,7 @@ char* String_readLine(FILE* fd) {
*newLine = '\0';
return buffer;
} else {
- if (feof(fd)) {
+ if (feof(fp)) {
return buffer;
}
}
@@ -265,6 +274,7 @@ char* xStrndup(const char* str, size_t len) {
return data;
}
+ATTR_ACCESS3_W(2, 3)
static ssize_t readfd_internal(int fd, void* buffer, size_t count) {
if (!count) {
close(fd);
@@ -281,10 +291,13 @@ static ssize_t readfd_internal(int fd, void* buffer, size_t count) {
continue;
close(fd);
+ *((char*)buffer) = '\0';
return -errno;
}
if (res > 0) {
+ assert((size_t)res <= count);
+
buffer = ((char*)buffer) + res;
count -= (size_t)res;
alreadyRead += res;
@@ -313,3 +326,48 @@ ssize_t xReadfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size
return readfd_internal(fd, buffer, count);
}
+
+ssize_t full_write(int fd, const void* buf, size_t count) {
+ ssize_t written = 0;
+
+ while (count > 0) {
+ ssize_t r = write(fd, buf, count);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return r;
+ }
+
+ if (r == 0)
+ break;
+
+ written += r;
+ buf = (const unsigned char*)buf + r;
+ count -= (size_t)r;
+ }
+
+ return written;
+}
+
+/* Compares floating point values for ordering data entries. In this function,
+ NaN is considered "less than" any other floating point value (regardless of
+ sign), and two NaNs are considered "equal" regardless of payload. */
+int compareRealNumbers(double a, double b) {
+ int result = isgreater(a, b) - isgreater(b, a);
+ if (result)
+ return result;
+ return !isNaN(a) - !isNaN(b);
+}
+
+/* Computes the sum of all positive floating point values in an array.
+ NaN values in the array are skipped. The returned sum will always be
+ nonnegative. */
+double sumPositiveValues(const double* array, size_t count) {
+ double sum = 0.0;
+ for (size_t i = 0; i < count; i++) {
+ if (isPositive(array[i]))
+ sum += array[i];
+ }
+ return sum;
+}
diff --git a/XUtils.h b/XUtils.h
index 2522a71f..093f5005 100644
--- a/XUtils.h
+++ b/XUtils.h
@@ -3,13 +3,19 @@
/*
htop - StringUtils.h
(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2023 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
+// IWYU pragma: no_include "config.h"
+#ifndef PACKAGE
+// strchrnul() needs _GNU_SOURCE defined, see PR #1337 for details
+#error "Must have #include \"config.h\" line at the top of the file that includes these XUtils helper functions"
+#endif
#include <stdbool.h>
+#include <stddef.h> // IWYU pragma: keep
#include <stdio.h>
#include <stdlib.h> // IWYU pragma: keep
#include <string.h> // IWYU pragma: keep
@@ -36,41 +42,96 @@ void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size)
* String_startsWith gives better performance if strlen(match) can be computed
* at compile time (e.g. when they are immutable string literals). :)
*/
+ATTR_NONNULL
static inline bool String_startsWith(const char* s, const char* match) {
return strncmp(s, match, strlen(match)) == 0;
}
bool String_contains_i(const char* s1, const char* s2, bool multi);
+ATTR_NONNULL
static inline bool String_eq(const char* s1, const char* s2) {
return strcmp(s1, s2) == 0;
}
+static inline bool String_eq_nullable(const char* s1, const char* s2) {
+ if (s1 == s2)
+ return true;
+
+ if (s1 && s2)
+ return String_eq(s1, s2);
+
+ return false;
+}
+
+ATTR_NONNULL
char* String_cat(const char* s1, const char* s2) ATTR_MALLOC;
+ATTR_NONNULL
char* String_trim(const char* in) ATTR_MALLOC;
+ATTR_NONNULL_N(1)
char** String_split(const char* s, char sep, size_t* n);
void String_freeArray(char** s);
-char* String_readLine(FILE* fd) ATTR_MALLOC;
+ATTR_NONNULL
+char* String_readLine(FILE* fp) ATTR_MALLOC;
+
+ATTR_NONNULL
+static inline char* String_strchrnul(const char* s, int c) {
+#ifdef HAVE_STRCHRNUL
+ return strchrnul(s, c);
+#else
+ char* result = strchr(s, c);
+ if (result)
+ return result;
+ return strchr(s, '\0');
+#endif
+}
/* Always null-terminates dest. Caller must pass a strictly positive size. */
+ATTR_ACCESS3_W(1, 3)
+ATTR_ACCESS3_R(2, 3)
size_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size);
ATTR_FORMAT(printf, 2, 3)
+ATTR_NONNULL_N(1, 2)
int xAsprintf(char** strp, const char* fmt, ...);
ATTR_FORMAT(printf, 3, 4)
+ATTR_ACCESS3_W(1, 2)
int xSnprintf(char* buf, size_t len, const char* fmt, ...);
char* xStrdup(const char* str) ATTR_NONNULL ATTR_MALLOC;
void free_and_xStrdup(char** ptr, const char* str);
+ATTR_ACCESS3_R(1, 2)
char* xStrndup(const char* str, size_t len) ATTR_NONNULL ATTR_MALLOC;
+ATTR_ACCESS3_W(2, 3)
ssize_t xReadfile(const char* pathname, void* buffer, size_t count);
+ATTR_ACCESS3_W(3, 4)
ssize_t xReadfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size_t count);
+ATTR_ACCESS3_R(2, 3)
+ssize_t full_write(int fd, const void* buf, size_t count);
+
+static inline ssize_t full_write_str(int fd, const char* str) {
+ return full_write(fd, str, strlen(str));
+}
+
+/* Compares floating point values for ordering data entries. In this function,
+ NaN is considered "less than" any other floating point value (regardless of
+ sign), and two NaNs are considered "equal" regardless of payload. */
+int compareRealNumbers(double a, double b);
+
+/* Computes the sum of all positive floating point values in an array.
+ NaN values in the array are skipped. The returned sum will always be
+ nonnegative. */
+double sumPositiveValues(const double* array, size_t count);
+
+/* IEC unit prefixes */
+static const char unitPrefixes[] = { 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q' };
+
#endif
diff --git a/configure.ac b/configure.ac
index 7a52ab5e..81836e8e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,11 +2,19 @@
# Process this file with autoconf to produce a configure script.
# ----------------------------------------------------------------------
+# Build version string.
+# ----------------------------------------------------------------------
+
+m4_define([htop_git_version],
+ m4_normalize(m4_esyscmd([git describe --abbrev=7 --dirty --always --tags 2> /dev/null || echo ''])))
+m4_define([htop_release_version], [3.4.0-dev])
+
+# ----------------------------------------------------------------------
# Autoconf initialization.
# ----------------------------------------------------------------------
AC_PREREQ([2.69])
-AC_INIT([htop], [3.2.1], [htop@groups.io], [], [https://htop.dev/])
+AC_INIT([htop], [m4_join([-],m4_defn([htop_release_version]),m4_defn([htop_git_version]))], [htop@groups.io], [], [https://htop.dev/])
AC_CONFIG_SRCDIR([htop.c])
AC_CONFIG_AUX_DIR([build-aux])
@@ -60,6 +68,9 @@ esac
# Enable extensions, required by hwloc scripts
AC_USE_SYSTEM_EXTENSIONS
+# Activate some more of the missing global defines
+AC_SYS_LARGEFILE
+
# ----------------------------------------------------------------------
@@ -71,6 +82,7 @@ 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.])])
+AM_CFLAGS="-std=c99 -pedantic"
# ----------------------------------------------------------------------
@@ -149,7 +161,7 @@ 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],
- [Define to 1 if `major', `minor', and `makedev' are declared in <sys/sysmacros.h>.])])
+ [Define to 1 if 'major', 'minor', and 'makedev' are declared in <sys/sysmacros.h>.])])
fi])
# Optional Section
@@ -167,7 +179,12 @@ fi
# Checks for typedefs, structures, and compiler characteristics.
# ----------------------------------------------------------------------
+AC_TYPE_MBSTATE_T
+AC_TYPE_MODE_T
+AC_TYPE_OFF_T
AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
AC_TYPE_UID_T
AC_TYPE_UINT8_T
AC_TYPE_UINT16_T
@@ -188,22 +205,73 @@ AC_COMPILE_IFELSE([
AC_MSG_RESULT(no))
CFLAGS="$old_CFLAGS"
+AC_MSG_CHECKING(for access)
+old_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -Wno-error -Werror=attributes"
+AC_COMPILE_IFELSE([
+ AC_LANG_SOURCE(
+ [
+ __attribute__((access(read_only, 1, 2))) extern int foo(const char* str, unsigned len);
+ ],[]
+ )],
+ AC_DEFINE([HAVE_ATTR_ACCESS], 1, [The access attribute is supported.])
+ AC_MSG_RESULT(yes),
+ AC_MSG_RESULT(no))
+CFLAGS="$old_CFLAGS"
+
+AC_MSG_CHECKING(for nonnull)
+old_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -Wno-error -Werror=attributes"
+AC_COMPILE_IFELSE([
+ AC_LANG_SOURCE(
+ [[
+ /* Attribute supported in GCC 3.3 or later */
+ __attribute__((nonnull)) int my_strcmp(const char* a, const char* b);
+ __attribute__((nonnull(1))) long my_strtol(const char* str, char** endptr, int base);
+ ]]
+ )],
+ AC_DEFINE([HAVE_ATTR_NONNULL], 1, [The nonnull attribute is supported.])
+ AC_MSG_RESULT(yes),
+ AC_MSG_RESULT(no))
+CFLAGS="$old_CFLAGS"
+
AC_MSG_CHECKING(for NaN support)
-AC_RUN_IFELSE([
+dnl Note: AC_RUN_IFELSE does not try compiling the program at all when
+dnl $cross_compiling is 'yes'.
+AC_LINK_IFELSE([
AC_LANG_PROGRAM(
[[
- #include <math.h>
+#include <math.h>
]],
[[
- double x = NAN; return !isnan(x);
+ double x = NAN;
+ /* Both should evaluate to false -> 0 (exit success) */
+ return isgreater(x, x) || isgreaterequal(x, x);
]]
)],
- [AC_MSG_RESULT(yes)],
- [
+ [flag_finite_math_only=unknown
+ if test "$cross_compiling" = yes; then
+ AC_COMPILE_IFELSE([
+ AC_LANG_SOURCE([[
+/* __FINITE_MATH_ONLY__ is documented in Clang. */
+#ifdef __FINITE_MATH_ONLY__
+#error "should not enable -ffinite-math-only"
+#endif
+ ]])],
+ AC_MSG_RESULT([assume yes (cross compiling)]),
+ flag_finite_math_only=yes)
+ elif ./conftest$EXEEXT >&AS_MESSAGE_LOG_FD; then
+ flag_finite_math_only=no
+ AC_MSG_RESULT(yes)
+ else
+ flag_finite_math_only=yes
+ fi
+ if test "$flag_finite_math_only" = yes; then
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_MSG_WARN([runtime behavior with NaN is not compliant - some functionality might break; consider using '-fno-finite-math-only'])
+ fi],
+ [AC_MSG_RESULT(no)
+ AC_MSG_ERROR([can not find required macros: NAN, isgreater() and isgreaterequal()])])
# ----------------------------------------------------------------------
@@ -212,10 +280,11 @@ AC_RUN_IFELSE([
# Checks for generic library functions.
# ----------------------------------------------------------------------
-AC_CHECK_LIB([m], [ceil], [], [AC_MSG_ERROR([can not find required function ceil()])])
+AC_SEARCH_LIBS([ceil], [m], [], [AC_MSG_ERROR([can not find required function ceil()])])
if test "$my_htop_platform" = dragonflybsd; then
AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
+ AC_SEARCH_LIBS([getdevs], [devstat], [], [AC_MSG_ERROR([can not find required function getdevs()])])
fi
if test "$my_htop_platform" = freebsd; then
@@ -260,14 +329,23 @@ AC_CHECK_FUNCS([ \
memfd_create\
openat \
readlinkat \
+ sched_getscheduler \
+ sched_setscheduler \
+ strchrnul \
])
if test "$my_htop_platform" = darwin; then
AC_CHECK_FUNCS([mach_timebase_info])
+ AC_CHECK_DECLS([IOMainPort], [], [], [[#include <IOKit/IOKitLib.h>]])
+ AC_CHECK_DECLS([IOMasterPort], [], [], [[#include <IOKit/IOKitLib.h>]])
+fi
+
+if test "$my_htop_platform" = pcp; then
+ AC_CHECK_FUNCS([pmLookupDescs])
fi
if test "$my_htop_platform" = linux && test "x$enable_static" = xyes; then
- AC_CHECK_LIB([systemd], [sd_bus_open_system])
+ AC_CHECK_LIB([systemd], [sd_bus_open_system], [], [], [-lcap])
fi
# ----------------------------------------------------------------------
@@ -289,15 +367,15 @@ m4_define([HTOP_CHECK_SCRIPT],
htop_config_script_cflags=$([$4] --cflags 2> /dev/null)
fi
htop_script_success=no
- htop_save_CFLAGS="$CFLAGS"
+ htop_save_CFLAGS="$AM_CFLAGS"
if test ! "x$htop_config_script_libs" = x; then
- CFLAGS="$htop_config_script_cflags $CFLAGS"
+ AM_CFLAGS="$AM_CFLAGS $htop_config_script_cflags"
AC_CHECK_LIB([$1], [$2], [
AC_DEFINE([$3], 1, [The library is present.])
LIBS="$htop_config_script_libs $LIBS "
htop_script_success=yes
], [
- CFLAGS="$htop_save_CFLAGS"
+ AM_CFLAGS="$htop_save_CFLAGS"
], [
$htop_config_script_libs
])
@@ -338,6 +416,11 @@ if test "x$enable_unicode" = xyes; then
[AC_CHECK_HEADERS([ncurses.h], [],
[AC_MSG_ERROR([can not find required ncurses header file])])])])])
+ AC_CHECK_HEADERS([ncursesw/term.h], [],
+ [AC_CHECK_HEADERS([ncurses/term.h], [],
+ [AC_CHECK_HEADERS([term.h], [],
+ [AC_MSG_ERROR([can not find required term 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])
@@ -353,13 +436,18 @@ else
AC_CHECK_HEADERS([curses.h], [],
[AC_CHECK_HEADERS([ncurses/curses.h], [],
[AC_CHECK_HEADERS([ncurses/ncurses.h], [],
- [AC_CHECK_HEADERS([ncurses.h] ,[],
+ [AC_CHECK_HEADERS([ncurses.h], [],
[AC_MSG_ERROR([can not find required ncurses header file])])])])])
+ AC_CHECK_HEADERS([ncurses/term.h], [],
+ [AC_CHECK_HEADERS([term.h], [],
+ [AC_MSG_ERROR([can not find required term 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 "$enable_static" = yes; then
AC_SEARCH_LIBS([Gpm_GetEvent], [gpm])
fi
@@ -396,7 +484,7 @@ if test "x$enable_affinity" = xcheck; then
AC_MSG_RESULT([yes])],
[enable_affinity=no
AC_MSG_RESULT([no])],
- [AC_MSG_RESULT([yes (assumed while cross compiling)])])
+ [AC_MSG_RESULT([assume yes (cross compiling)])])
fi
fi
if test "x$enable_affinity" = xyes; then
@@ -433,7 +521,7 @@ case "$enable_unwind" in
yes)
AC_CHECK_LIB([unwind], [backtrace], [], [AC_MSG_ERROR([can not find required library libunwind])])
AC_CHECK_HEADERS([libunwind.h], [], [
- CFLAGS="$CFLAGS -I/usr/include/libunwind"
+ AM_CFLAGS="$AM_CFLAGS -I/usr/include/libunwind"
AC_CHECK_HEADERS([libunwind/libunwind.h], [], [AC_MSG_ERROR([can not find required header file libunwind.h])])
])
;;
@@ -459,7 +547,9 @@ case "$enable_hwloc" in
m4_ifdef([PKG_PROG_PKG_CONFIG], [
PKG_PROG_PKG_CONFIG()
PKG_CHECK_MODULES(HWLOC, hwloc, [
- CFLAGS="$CFLAGS $HWLOC_CFLAGS" LIBS="$LIBS $HWLOC_LIBS"
+ AM_CFLAGS="$AM_CFLAGS $HWLOC_CFLAGS"
+ LIBS="$LIBS $HWLOC_LIBS"
+ AC_DEFINE([HAVE_LIBHWLOC], [1], [Define to 1 if you have the 'hwloc' library (-lhwloc).])
], [
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])])
@@ -576,40 +666,27 @@ case "$enable_delayacct" in
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])
- ])
+ old_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -I/usr/include/libnl3"
+ AC_CHECK_HEADERS([netlink/attr.h netlink/handlers.h netlink/msg.h], [enable_delayacct=yes], [enable_delayacct=no])
+ CFLAGS="$old_CFLAGS"
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.])
- ])
+ old_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -I/usr/include/libnl3"
+ AC_CHECK_HEADERS([netlink/attr.h netlink/handlers.h netlink/msg.h], [], [AC_MSG_ERROR([can not find required header files netlink/attr.h, netlink/handlers.h, netlink/msg.h])])
+ CFLAGS="$old_CFLAGS"
;;
*)
AC_MSG_ERROR([bad value '$enable_delayacct' for --enable-delayacct])
;;
esac
+if test "$enable_delayacct" = yes; then
+ AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
+ AM_CFLAGS="$AM_CFLAGS -I/usr/include/libnl3"
+fi
+AM_CONDITIONAL([HAVE_DELAYACCT], [test "$enable_delayacct" = yes])
AC_ARG_ENABLE([sensors],
@@ -648,7 +725,7 @@ fi
# Checks for compiler warnings.
# ----------------------------------------------------------------------
-AM_CFLAGS="\
+AM_CFLAGS="$AM_CFLAGS\
-Wall\
-Wcast-align\
-Wcast-qual\
@@ -666,11 +743,6 @@ 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],
[
@@ -708,6 +780,22 @@ AC_ARG_ENABLE([debug],
[enable_debug=no])
if test "x$enable_debug" != xyes; then
AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG"
+
+ AC_COMPILE_IFELSE([
+ AC_LANG_SOURCE([[
+#ifdef __SUPPORT_SNAN__
+#error "signaling NaN support not recommended"
+#endif
+ ]])],
+ :,
+ [warning_msg="signaling NaN support is enabled; not recommended for htop"
+ case "$CC" in
+ *gcc*)
+ warning_msg="$warning_msg (use '-fno-signaling-nans' compiler flag to disable)"
+ ;;
+ esac
+ AC_MSG_WARN([$warning_msg])
+ ])
else
AM_CPPFLAGS="$AM_CPPFLAGS -ggdb3"
fi
@@ -723,7 +811,7 @@ AC_SUBST([AM_CPPFLAGS])
# We're done, let's go!
# ----------------------------------------------------------------------
-AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2022 htop dev team."], [Copyright message.])
+AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2024 htop dev team."], [Copyright message.])
AM_CONDITIONAL([HTOP_LINUX], [test "$my_htop_platform" = linux])
AM_CONDITIONAL([HTOP_FREEBSD], [test "$my_htop_platform" = freebsd])
diff --git a/darwin/DarwinMachine.c b/darwin/DarwinMachine.c
new file mode 100644
index 00000000..582d4968
--- /dev/null
+++ b/darwin/DarwinMachine.c
@@ -0,0 +1,119 @@
+/*
+htop - DarwinMachine.c
+(C) 2014 Hisham H. Muhammad
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "darwin/DarwinMachine.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/sysctl.h>
+
+#include "CRT.h"
+#include "Machine.h"
+#include "darwin/Platform.h"
+#include "darwin/PlatformHelpers.h"
+#include "generic/openzfs_sysctl.h"
+#include "zfs/ZfsArcStats.h"
+
+
+static void DarwinMachine_getHostInfo(host_basic_info_data_t* p) {
+ mach_msg_type_number_t info_size = HOST_BASIC_INFO_COUNT;
+
+ if (0 != host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)p, &info_size)) {
+ CRT_fatalError("Unable to retrieve host info");
+ }
+}
+
+static void DarwinMachine_freeCPULoadInfo(processor_cpu_load_info_t* p) {
+ if (!p)
+ return;
+
+ if (!*p)
+ return;
+
+ if (0 != munmap(*p, vm_page_size)) {
+ CRT_fatalError("Unable to free old CPU load information");
+ }
+
+ *p = NULL;
+}
+
+static unsigned DarwinMachine_allocateCPULoadInfo(processor_cpu_load_info_t* p) {
+ mach_msg_type_number_t info_size = sizeof(processor_cpu_load_info_t);
+ unsigned cpu_count;
+
+ // TODO Improving the accuracy of the load counts would help a lot.
+ if (0 != host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, (processor_info_array_t*)p, &info_size)) {
+ CRT_fatalError("Unable to retrieve CPU info");
+ }
+
+ return cpu_count;
+}
+
+static void DarwinMachine_getVMStats(vm_statistics_t p) {
+ mach_msg_type_number_t info_size = HOST_VM_INFO_COUNT;
+
+ if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)p, &info_size) != 0) {
+ CRT_fatalError("Unable to retrieve VM statistics");
+ }
+}
+
+void Machine_scan(Machine* super) {
+ DarwinMachine* host = (DarwinMachine*) super;
+
+ /* Update the global data (CPU times and VM stats) */
+ DarwinMachine_freeCPULoadInfo(&host->prev_load);
+ host->prev_load = host->curr_load;
+ DarwinMachine_allocateCPULoadInfo(&host->curr_load);
+ DarwinMachine_getVMStats(&host->vm_stats);
+ openzfs_sysctl_updateArcStats(&host->zfs);
+}
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ DarwinMachine* this = xCalloc(1, sizeof(DarwinMachine));
+ Machine* super = &this->super;
+
+ Machine_init(super, usersTable, userId);
+
+ /* Initialize the CPU information */
+ super->activeCPUs = DarwinMachine_allocateCPULoadInfo(&this->prev_load);
+ super->existingCPUs = super->activeCPUs;
+ DarwinMachine_getHostInfo(&this->host_info);
+ DarwinMachine_allocateCPULoadInfo(&this->curr_load);
+
+ /* Initialize the VM statistics */
+ DarwinMachine_getVMStats(&this->vm_stats);
+
+ /* Initialize the ZFS kstats, if zfs.kext loaded */
+ openzfs_sysctl_init(&this->zfs);
+ openzfs_sysctl_updateArcStats(&this->zfs);
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ DarwinMachine* this = (DarwinMachine*) super;
+
+ DarwinMachine_freeCPULoadInfo(&this->prev_load);
+
+ Machine_done(super);
+ free(this);
+}
+
+bool Machine_isCPUonline(const Machine* host, unsigned int id) {
+ assert(id < host->existingCPUs);
+
+ // TODO: support offline CPUs and hot swapping
+ (void) host; (void) id;
+
+ return true;
+}
diff --git a/darwin/DarwinMachine.h b/darwin/DarwinMachine.h
new file mode 100644
index 00000000..3135b589
--- /dev/null
+++ b/darwin/DarwinMachine.h
@@ -0,0 +1,28 @@
+#ifndef HEADER_DarwinMachine
+#define HEADER_DarwinMachine
+/*
+htop - DarwinMachine.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <mach/mach_host.h>
+#include <sys/sysctl.h>
+
+#include "Machine.h"
+#include "zfs/ZfsArcStats.h"
+
+
+typedef struct DarwinMachine_ {
+ Machine super;
+
+ host_basic_info_data_t host_info;
+ vm_statistics_data_t vm_stats;
+ processor_cpu_load_info_t prev_load;
+ processor_cpu_load_info_t curr_load;
+
+ ZfsArcStats zfs;
+} DarwinMachine;
+
+#endif
diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c
index d0204ddc..717e467f 100644
--- a/darwin/DarwinProcess.c
+++ b/darwin/DarwinProcess.c
@@ -16,6 +16,7 @@ in the source distribution for its full text.
#include "CRT.h"
#include "Process.h"
+#include "darwin/DarwinMachine.h"
#include "darwin/Platform.h"
@@ -39,7 +40,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@@ -51,17 +52,17 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, },
};
-Process* DarwinProcess_new(const Settings* settings) {
+Process* DarwinProcess_new(const Machine* host) {
DarwinProcess* this = xCalloc(1, sizeof(DarwinProcess));
Object_setClass(this, Class(DarwinProcess));
- Process_init(&this->super, settings);
+ Process_init(&this->super, host);
this->utime = 0;
this->stime = 0;
this->taskAccess = true;
this->translated = false;
- return &this->super;
+ return (Process*)this;
}
void Process_delete(Object* cast) {
@@ -71,18 +72,21 @@ void Process_delete(Object* cast) {
free(this);
}
-static void DarwinProcess_writeField(const Process* this, RichString* str, ProcessField field) {
- const DarwinProcess* dp = (const DarwinProcess*) this;
+static void DarwinProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const DarwinProcess* dp = (const DarwinProcess*) super;
+
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
- int n = sizeof(buffer) - 1;
+ size_t n = sizeof(buffer) - 1;
+
switch (field) {
// add Platform-specific fields here
case TRANSLATED: xSnprintf(buffer, n, "%c ", dp->translated ? 'T' : 'N'); break;
default:
- Process_writeField(this, str, field);
+ Process_writeField(&dp->super, str, field);
return;
}
+
RichString_appendWide(str, attr, buffer);
}
@@ -282,7 +286,7 @@ static char* DarwinProcess_getDevname(dev_t dev) {
return NULL;
}
char buf[sizeof("/dev/") + MAXNAMLEN];
- char *name = devname_r(dev, S_IFCHR, buf, MAXNAMLEN);
+ char* name = devname_r(dev, S_IFCHR, buf, MAXNAMLEN);
if (name) {
return xStrdup(name);
}
@@ -291,13 +295,14 @@ static char* DarwinProcess_getDevname(dev_t dev) {
void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) {
DarwinProcess* dp = (DarwinProcess*)proc;
+ const Settings* settings = proc->super.host->settings;
const struct extern_proc* ep = &ps->kp_proc;
/* UNSET HERE :
*
* processor
- * user (set at ProcessList level)
+ * user (set at ProcessTable level)
* nlwp
* percent_cpu
* percent_mem
@@ -310,12 +315,12 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
/* First, the "immutable" parts */
if (!exists) {
/* Set the PID/PGID/etc. */
- proc->pid = ep->p_pid;
- proc->ppid = ps->kp_eproc.e_ppid;
+ Process_setPid(proc, ep->p_pid);
+ Process_setThreadGroup(proc, ep->p_pid);
+ Process_setParent(proc, ps->kp_eproc.e_ppid);
proc->pgrp = ps->kp_eproc.e_pgid;
proc->session = 0; /* TODO Get the session id */
proc->tpgid = ps->kp_eproc.e_tpgid;
- proc->tgid = proc->pid;
proc->isKernelThread = false;
proc->isUserlandThread = false;
dp->translated = ps->kp_proc.p_flag & P_TRANSLATED;
@@ -328,7 +333,7 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
DarwinProcess_updateExe(ep->p_pid, proc);
DarwinProcess_updateCmdLine(ps, proc);
- if (proc->settings->ss->flags & PROCESS_FLAG_CWD) {
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
DarwinProcess_updateCwd(ep->p_pid, proc);
}
}
@@ -341,7 +346,7 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
* To mitigate this we only fetch TTY information if the TTY
* field is enabled in the settings.
*/
- if (proc->settings->ss->flags & PROCESS_FLAG_TTY) {
+ if (settings->ss->flags & PROCESS_FLAG_TTY) {
proc->tty_name = DarwinProcess_getDevname(proc->tty_nr);
if (!proc->tty_name) {
/* devname failed: prevent us from calling it again */
@@ -357,13 +362,15 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
proc->state = (ep->p_stat == SZOMB) ? ZOMBIE : UNKNOWN;
/* Make sure the updated flag is set */
- proc->updated = true;
+ proc->super.updated = true;
}
-void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS) {
+void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable* dpt, double timeIntervalNS) {
struct proc_taskinfo pti;
- if (sizeof(pti) == proc_pidinfo(proc->super.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) {
+ if (sizeof(pti) == proc_pidinfo(Process_getPid(&proc->super), PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) {
+ const DarwinMachine* dhost = (const DarwinMachine*) proc->super.super.host;
+
uint64_t total_existing_time_ns = proc->stime + proc->utime;
uint64_t user_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_user);
@@ -385,16 +392,32 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList*
proc->super.m_resident = pti.pti_resident_size / ONE_K;
proc->super.majflt = pti.pti_faults;
proc->super.percent_mem = (double)pti.pti_resident_size * 100.0
- / (double)dpl->host_info.max_mem;
+ / (double)dhost->host_info.max_mem;
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;*/
- dpl->super.totalTasks += pti.pti_threadnum;
- dpl->super.runningTasks += pti.pti_numrunning;
+ dpt->super.kernelThreads += 0; /*pti.pti_threads_system;*/
+ dpt->super.userlandThreads += pti.pti_threadnum; /*pti.pti_threads_user;*/
+ dpt->super.totalTasks += pti.pti_threadnum;
+ dpt->super.runningTasks += pti.pti_numrunning;
+ }
+}
+
+static ProcessState stateToChar(int run_state) {
+ switch (run_state) {
+ case TH_STATE_RUNNING:
+ return RUNNING;
+ case TH_STATE_STOPPED:
+ return STOPPED;
+ case TH_STATE_WAITING:
+ return WAITING;
+ case TH_STATE_UNINTERRUPTIBLE:
+ return UNINTERRUPTIBLE_WAIT;
+ case TH_STATE_HALTED:
+ return BLOCKED;
}
+ return UNKNOWN;
}
/*
@@ -402,7 +425,7 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList*
* Based on: http://stackoverflow.com/questions/6788274/ios-mac-cpu-usage-for-thread
* and https://github.com/max-horvath/htop-osx/blob/e86692e869e30b0bc7264b3675d2a4014866ef46/ProcessList.c
*/
-void DarwinProcess_scanThreads(DarwinProcess* dp) {
+void DarwinProcess_scanThreads(DarwinProcess* dp, DarwinProcessTable* dpt) {
Process* proc = (Process*) dp;
kern_return_t ret;
@@ -414,65 +437,122 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) {
return;
}
- task_t port;
- ret = task_for_pid(mach_task_self(), proc->pid, &port);
+ pid_t pid = Process_getPid(proc);
+
+ task_t task;
+ ret = task_for_pid(mach_task_self(), pid, &task);
if (ret != KERN_SUCCESS) {
+ // TODO: workaround for modern MacOS limits on task_for_pid()
+ if (ret != KERN_FAILURE)
+ CRT_debug("task_for_pid(%d) failed: %s", pid, mach_error_string(ret));
dp->taskAccess = false;
return;
}
- task_info_data_t tinfo;
- mach_msg_type_number_t task_info_count = TASK_INFO_MAX;
- ret = task_info(port, TASK_BASIC_INFO, (task_info_t) tinfo, &task_info_count);
- if (ret != KERN_SUCCESS) {
- dp->taskAccess = false;
- return;
+ {
+ task_info_data_t tinfo;
+ mach_msg_type_number_t task_info_count = TASK_INFO_MAX;
+ ret = task_info(task, TASK_BASIC_INFO, (task_info_t) &tinfo, &task_info_count);
+ if (ret != KERN_SUCCESS) {
+ CRT_debug("task_info(%d) failed: %s", pid, mach_error_string(ret));
+ dp->taskAccess = false;
+ mach_port_deallocate(mach_task_self(), task);
+ return;
+ }
}
thread_array_t thread_list;
mach_msg_type_number_t thread_count;
- ret = task_threads(port, &thread_list, &thread_count);
+ ret = task_threads(task, &thread_list, &thread_count);
if (ret != KERN_SUCCESS) {
+ CRT_debug("task_threads(%d) failed: %s", pid, mach_error_string(ret));
dp->taskAccess = false;
- mach_port_deallocate(mach_task_self(), port);
+ mach_port_deallocate(mach_task_self(), task);
return;
}
+ const bool hideUserlandThreads = dpt->super.super.host->settings->hideUserlandThreads;
+
integer_t run_state = 999;
- for (unsigned int i = 0; i < thread_count; i++) {
- thread_info_data_t thinfo;
- mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT;
- ret = thread_info(thread_list[i], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count);
- if (ret == KERN_SUCCESS) {
- thread_basic_info_t basic_info_th = (thread_basic_info_t) thinfo;
- if (basic_info_th->run_state < run_state) {
- run_state = basic_info_th->run_state;
- }
- mach_port_deallocate(mach_task_self(), thread_list[i]);
+ for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
+
+ thread_identifier_info_data_t identifer_info;
+ mach_msg_type_number_t identifer_info_count = THREAD_IDENTIFIER_INFO_COUNT;
+ ret = thread_info(thread_list[i], THREAD_IDENTIFIER_INFO, (thread_info_t) &identifer_info, &identifer_info_count);
+ if (ret != KERN_SUCCESS) {
+ CRT_debug("thread_info(%d:%d) for identifier failed: %s", pid, i, mach_error_string(ret));
+ continue;
+ }
+
+ uint64_t tid = identifer_info.thread_id;
+
+ bool preExisting;
+ Process *tprocess = ProcessTable_getProcess(&dpt->super, tid, &preExisting, DarwinProcess_new);
+ tprocess->super.updated = true;
+ dpt->super.totalTasks++;
+
+ if (hideUserlandThreads) {
+ tprocess->super.show = false;
+ continue;
+ }
+
+ assert(Process_getPid(tprocess) == tid);
+ Process_setParent(tprocess, pid);
+ Process_setThreadGroup(tprocess, pid);
+ tprocess->super.show = true;
+ tprocess->isUserlandThread = true;
+ tprocess->st_uid = proc->st_uid;
+ tprocess->user = proc->user;
+
+ thread_extended_info_data_t extended_info;
+ mach_msg_type_number_t extended_info_count = THREAD_EXTENDED_INFO_COUNT;
+ ret = thread_info(thread_list[i], THREAD_EXTENDED_INFO, (thread_info_t) &extended_info, &extended_info_count);
+ if (ret != KERN_SUCCESS) {
+ CRT_debug("thread_info(%d:%d) for extended failed: %s", pid, i, mach_error_string(ret));
+ continue;
}
+
+ DarwinProcess* tdproc = (DarwinProcess*)tprocess;
+ tdproc->super.state = stateToChar(extended_info.pth_run_state);
+ tdproc->super.percent_cpu = extended_info.pth_cpu_usage / 10.0;
+ tdproc->stime = extended_info.pth_system_time;
+ tdproc->utime = extended_info.pth_user_time;
+ tdproc->super.time = (extended_info.pth_system_time + extended_info.pth_user_time) / 10000000;
+ tdproc->super.priority = extended_info.pth_curpri;
+
+ if (extended_info.pth_run_state < run_state)
+ run_state = extended_info.pth_run_state;
+
+ // TODO: depend on setting
+ const char* name = extended_info.pth_name[0] != '\0' ? extended_info.pth_name : proc->procComm;
+ Process_updateCmdline(tprocess, name, 0, strlen(name));
+
+ if (!preExisting)
+ ProcessTable_add(&dpt->super, tprocess);
}
+
vm_deallocate(mach_task_self(), (vm_address_t) thread_list, sizeof(thread_port_array_t) * thread_count);
- mach_port_deallocate(mach_task_self(), port);
+ mach_port_deallocate(mach_task_self(), task);
- /* Taken from: https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/thread_info.h#L129 */
- switch (run_state) {
- case TH_STATE_RUNNING: proc->state = RUNNING; break;
- case TH_STATE_STOPPED: proc->state = STOPPED; break;
- case TH_STATE_WAITING: proc->state = WAITING; break;
- case TH_STATE_UNINTERRUPTIBLE: proc->state = UNINTERRUPTIBLE_WAIT; break;
- case TH_STATE_HALTED: proc->state = BLOCKED; break;
- default: proc->state = UNKNOWN;
- }
+ if (run_state != 999)
+ proc->state = stateToChar(run_state);
}
const ProcessClass DarwinProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = DarwinProcess_rowWriteField
},
- .writeField = DarwinProcess_writeField,
- .compareByKey = DarwinProcess_compareByKey,
+ .compareByKey = DarwinProcess_compareByKey
};
diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h
index bd179746..a5d66b49 100644
--- a/darwin/DarwinProcess.h
+++ b/darwin/DarwinProcess.h
@@ -9,8 +9,8 @@ in the source distribution for its full text.
#include <sys/sysctl.h>
-#include "Settings.h"
-#include "darwin/DarwinProcessList.h"
+#include "Machine.h"
+#include "darwin/DarwinProcessTable.h"
#define PROCESS_FLAG_TTY 0x00000100
@@ -28,19 +28,19 @@ extern const ProcessClass DarwinProcess_class;
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-Process* DarwinProcess_new(const Settings* settings);
+Process* DarwinProcess_new(const Machine* settings);
void Process_delete(Object* cast);
void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists);
-void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS);
+void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable* dpt, double timeIntervalNS);
/*
* Scan threads for process state information.
* Based on: http://stackoverflow.com/questions/6788274/ios-mac-cpu-usage-for-thread
* and https://github.com/max-horvath/htop-osx/blob/e86692e869e30b0bc7264b3675d2a4014866ef46/ProcessList.c
*/
-void DarwinProcess_scanThreads(DarwinProcess* dp);
+void DarwinProcess_scanThreads(DarwinProcess* dp, DarwinProcessTable* dpt);
#endif
diff --git a/darwin/DarwinProcessList.c b/darwin/DarwinProcessList.c
deleted file mode 100644
index bd7821b8..00000000
--- a/darwin/DarwinProcessList.c
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
-htop - DarwinProcessList.c
-(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "darwin/DarwinProcessList.h"
-
-#include <errno.h>
-#include <libproc.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <utmpx.h>
-#include <sys/mman.h>
-#include <sys/sysctl.h>
-
-#include "CRT.h"
-#include "ProcessList.h"
-#include "darwin/DarwinProcess.h"
-#include "darwin/Platform.h"
-#include "darwin/PlatformHelpers.h"
-#include "generic/openzfs_sysctl.h"
-#include "zfs/ZfsArcStats.h"
-
-
-static void ProcessList_getHostInfo(host_basic_info_data_t* p) {
- mach_msg_type_number_t info_size = HOST_BASIC_INFO_COUNT;
-
- if (0 != host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)p, &info_size)) {
- CRT_fatalError("Unable to retrieve host info");
- }
-}
-
-static void ProcessList_freeCPULoadInfo(processor_cpu_load_info_t* p) {
- if (NULL != p && NULL != *p) {
- if (0 != munmap(*p, vm_page_size)) {
- CRT_fatalError("Unable to free old CPU load information");
- }
- *p = NULL;
- }
-}
-
-static unsigned ProcessList_allocateCPULoadInfo(processor_cpu_load_info_t* p) {
- mach_msg_type_number_t info_size = sizeof(processor_cpu_load_info_t);
- unsigned cpu_count;
-
- // TODO Improving the accuracy of the load counts woule help a lot.
- if (0 != host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, (processor_info_array_t*)p, &info_size)) {
- CRT_fatalError("Unable to retrieve CPU info");
- }
-
- return cpu_count;
-}
-
-static void ProcessList_getVMStats(vm_statistics_t p) {
- mach_msg_type_number_t info_size = HOST_VM_INFO_COUNT;
-
- if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)p, &info_size) != 0) {
- CRT_fatalError("Unable to retrieve VM statistics");
- }
-}
-
-static struct kinfo_proc* ProcessList_getKInfoProcs(size_t* count) {
- int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
- struct kinfo_proc* processes = NULL;
-
- for (int retry = 3; retry > 0; retry--) {
- size_t size = 0;
- if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) {
- CRT_fatalError("Unable to get size of kproc_infos");
- }
-
- processes = xRealloc(processes, size);
-
- if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) {
- *count = size / sizeof(struct kinfo_proc);
- return processes;
- }
-
- if (errno != ENOMEM)
- break;
- }
-
- CRT_fatalError("Unable to get kinfo_procs");
-}
-
-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, dynamicMeters, dynamicColumns, pidMatchList, userId);
-
- /* Initialize the CPU information */
- 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);
-
- /* Initialize the VM statistics */
- ProcessList_getVMStats(&this->vm_stats);
-
- /* Initialize the ZFS kstats, if zfs.kext loaded */
- openzfs_sysctl_init(&this->zfs);
- openzfs_sysctl_updateArcStats(&this->zfs);
-
- this->super.kernelThreads = 0;
- this->super.userlandThreads = 0;
- this->super.totalTasks = 0;
- this->super.runningTasks = 0;
-
- return &this->super;
-}
-
-void ProcessList_delete(ProcessList* this) {
- ProcessList_done(this);
- free(this);
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
- DarwinProcessList* dpl = (DarwinProcessList*)super;
- bool preExisting = true;
- struct kinfo_proc* ps;
- size_t count;
- DarwinProcess* proc;
-
- /* Update the global data (CPU times and VM stats) */
- ProcessList_freeCPULoadInfo(&dpl->prev_load);
- dpl->prev_load = dpl->curr_load;
- ProcessList_allocateCPULoadInfo(&dpl->curr_load);
- ProcessList_getVMStats(&dpl->vm_stats);
- openzfs_sysctl_updateArcStats(&dpl->zfs);
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
- /* Get the time difference */
- dpl->global_diff = 0;
- 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_ns = Platform_schedulerTicksToNanoseconds(dpl->global_diff) / (double) dpl->super.activeCPUs;
-
- /* Clear the thread counts */
- super->kernelThreads = 0;
- super->userlandThreads = 0;
- super->totalTasks = 0;
- super->runningTasks = 0;
-
- /* We use kinfo_procs for initial data since :
- *
- * 1) They always succeed.
- * 2) The contain the basic information.
- *
- * We attempt to fill-in additional information with libproc.
- */
- ps = ProcessList_getKInfoProcs(&count);
-
- for (size_t i = 0; i < count; ++i) {
- 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_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 = !Platform_KernelVersionIsBetween((KernelVersion) {17, 0, 0}, (KernelVersion) {17, 5, 0});
-
- if (isScanThreadSupported) {
- DarwinProcess_scanThreads(proc);
- }
-
- super->totalTasks += 1;
-
- if (!preExisting) {
- 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
deleted file mode 100644
index 393e6567..00000000
--- a/darwin/DarwinProcessList.h
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef HEADER_DarwinProcessList
-#define HEADER_DarwinProcessList
-/*
-htop - DarwinProcessList.h
-(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include <mach/mach_host.h>
-#include <sys/sysctl.h>
-
-#include "ProcessList.h"
-#include "zfs/ZfsArcStats.h"
-
-
-typedef struct DarwinProcessList_ {
- ProcessList super;
-
- host_basic_info_data_t host_info;
- vm_statistics_data_t vm_stats;
- processor_cpu_load_info_t prev_load;
- processor_cpu_load_info_t curr_load;
- uint64_t kernel_threads;
- uint64_t user_threads;
- uint64_t global_diff;
-
- ZfsArcStats zfs;
-} DarwinProcessList;
-
-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/DarwinProcessTable.c b/darwin/DarwinProcessTable.c
new file mode 100644
index 00000000..a08c2669
--- /dev/null
+++ b/darwin/DarwinProcessTable.c
@@ -0,0 +1,126 @@
+/*
+htop - DarwinProcessTable.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "darwin/DarwinProcessTable.h"
+
+#include <errno.h>
+#include <libproc.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <utmpx.h>
+#include <sys/mman.h>
+#include <sys/sysctl.h>
+
+#include "CRT.h"
+#include "ProcessTable.h"
+#include "darwin/DarwinMachine.h"
+#include "darwin/DarwinProcess.h"
+#include "darwin/Platform.h"
+#include "darwin/PlatformHelpers.h"
+#include "generic/openzfs_sysctl.h"
+#include "zfs/ZfsArcStats.h"
+
+
+static struct kinfo_proc* ProcessTable_getKInfoProcs(size_t* count) {
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
+ struct kinfo_proc* processes = NULL;
+
+ for (unsigned int retry = 0; retry < 4; retry++) {
+ size_t size = 0;
+ if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) {
+ CRT_fatalError("Unable to get size of kproc_infos");
+ }
+
+ size += 16 * retry * retry * sizeof(struct kinfo_proc);
+ processes = xRealloc(processes, size);
+
+ if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) {
+ *count = size / sizeof(struct kinfo_proc);
+ return processes;
+ }
+
+ if (errno != ENOMEM)
+ break;
+ }
+
+ CRT_fatalError("Unable to get kinfo_procs");
+}
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ DarwinProcessTable* this = xCalloc(1, sizeof(DarwinProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(DarwinProcess), host, pidMatchList);
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ DarwinProcessTable* this = (DarwinProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ free(this);
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ const Machine* host = super->super.host;
+ const DarwinMachine* dhost = (const DarwinMachine*) host;
+ DarwinProcessTable* dpt = (DarwinProcessTable*) super;
+ bool preExisting = true;
+ struct kinfo_proc* ps;
+ size_t count;
+ DarwinProcess* proc;
+
+ /* Get the time difference */
+ dpt->global_diff = 0;
+ for (unsigned int i = 0; i < host->existingCPUs; ++i) {
+ for (size_t j = 0; j < CPU_STATE_MAX; ++j) {
+ dpt->global_diff += dhost->curr_load[i].cpu_ticks[j] - dhost->prev_load[i].cpu_ticks[j];
+ }
+ }
+
+ const double time_interval_ns = Platform_schedulerTicksToNanoseconds(dpt->global_diff) / (double) host->activeCPUs;
+
+ /* We use kinfo_procs for initial data since :
+ *
+ * 1) They always succeed.
+ * 2) They contain the basic information.
+ *
+ * We attempt to fill-in additional information with libproc.
+ */
+ ps = ProcessTable_getKInfoProcs(&count);
+
+ for (size_t i = 0; i < count; ++i) {
+ proc = (DarwinProcess*)ProcessTable_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new);
+
+ DarwinProcess_setFromKInfoProc(&proc->super, &ps[i], preExisting);
+ DarwinProcess_setFromLibprocPidinfo(proc, dpt, 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(host->usersTable, proc->super.st_uid);
+ }
+
+ // Disabled for High Sierra due to bug in macOS High Sierra
+ bool isScanThreadSupported = !Platform_KernelVersionIsBetween((KernelVersion) {17, 0, 0}, (KernelVersion) {17, 5, 0});
+
+ if (isScanThreadSupported) {
+ DarwinProcess_scanThreads(proc, dpt);
+ }
+
+ super->totalTasks += 1;
+
+ if (!preExisting) {
+ ProcessTable_add(super, &proc->super);
+ }
+ }
+
+ free(ps);
+}
diff --git a/darwin/DarwinProcessTable.h b/darwin/DarwinProcessTable.h
new file mode 100644
index 00000000..7467bfd9
--- /dev/null
+++ b/darwin/DarwinProcessTable.h
@@ -0,0 +1,22 @@
+#ifndef HEADER_DarwinProcessTable
+#define HEADER_DarwinProcessTable
+/*
+htop - DarwinProcessTable.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <mach/mach_host.h>
+#include <sys/sysctl.h>
+
+#include "ProcessTable.h"
+
+
+typedef struct DarwinProcessTable_ {
+ ProcessTable super;
+
+ uint64_t global_diff;
+} DarwinProcessTable;
+
+#endif
diff --git a/darwin/Platform.c b/darwin/Platform.c
index 4b34d885..ba31a59f 100644
--- a/darwin/Platform.c
+++ b/darwin/Platform.c
@@ -14,16 +14,30 @@ in the source distribution for its full text.
#include <math.h>
#include <stdlib.h>
#include <unistd.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <sys/socket.h>
+#include <mach/port.h>
+
+#include <CoreFoundation/CFBase.h>
+#include <CoreFoundation/CFDictionary.h>
+#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CoreFoundation.h>
+
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOTypes.h>
#include <IOKit/ps/IOPowerSources.h>
#include <IOKit/ps/IOPSKeys.h>
+#include <IOKit/storage/IOBlockStorageDriver.h>
#include "ClockMeter.h"
#include "CPUMeter.h"
#include "CRT.h"
#include "DateMeter.h"
#include "DateTimeMeter.h"
+#include "FileDescriptorMeter.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
@@ -34,8 +48,9 @@ in the source distribution for its full text.
#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
-#include "darwin/DarwinProcessList.h"
+#include "darwin/DarwinMachine.h"
#include "darwin/PlatformHelpers.h"
+#include "generic/fdstat_sysctl.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsCompressedArcMeter.h"
@@ -126,6 +141,9 @@ const MeterClass* const Platform_meterTypes[] = {
&RightCPUs8Meter_class,
&ZfsArcMeter_class,
&ZfsCompressedArcMeter_class,
+ &DiskIOMeter_class,
+ &NetworkIOMeter_class,
+ &FileDescriptorMeter_class,
&BlankMeter_class,
NULL
};
@@ -134,6 +152,9 @@ static double Platform_nanosecondsPerMachTick = 1.0;
static double Platform_nanosecondsPerSchedulerTick = -1;
+static bool iokit_available = false;
+static mach_port_t iokit_port; // the mach port used to initiate communication with IOKit
+
bool Platform_init(void) {
Platform_nanosecondsPerMachTick = Platform_calculateNanosecondsPerMachTick();
@@ -148,6 +169,17 @@ bool Platform_init(void) {
const double nanos_per_sec = 1e9;
Platform_nanosecondsPerSchedulerTick = nanos_per_sec / scheduler_ticks_per_sec;
+ // Since macOS 12.0, IOMasterPort is deprecated, and one should use IOMainPort instead
+ #if defined(HAVE_DECL_IOMAINPORT) && HAVE_DECL_IOMAINPORT
+ if (!IOMainPort(bootstrap_port, &iokit_port)) {
+ iokit_available = true;
+ }
+ #elif defined(HAVE_DECL_IOMASTERPORT) && HAVE_DECL_IOMASTERPORT
+ if (!IOMasterPort(bootstrap_port, &iokit_port)) {
+ iokit_available = true;
+ }
+ #endif
+
return true;
}
@@ -172,7 +204,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
struct timeval bootTime, currTime;
int mib[2] = { CTL_KERN, KERN_BOOTTIME };
size_t size = sizeof(bootTime);
@@ -200,19 +232,19 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
}
}
-int Platform_getMaxPid() {
+pid_t Platform_getMaxPid(void) {
/* http://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/proc_internal.hh */
return 99999;
}
static double Platform_setCPUAverageValues(Meter* mtr) {
- const ProcessList* dpl = mtr->pl;
- unsigned int activeCPUs = dpl->activeCPUs;
+ const Machine* host = mtr->host;
+ unsigned int activeCPUs = host->activeCPUs;
double sumNice = 0.0;
double sumNormal = 0.0;
double sumKernel = 0.0;
double sumPercent = 0.0;
- for (unsigned int i = 1; i <= dpl->existingCPUs; i++) {
+ for (unsigned int i = 1; i <= host->existingCPUs; i++) {
sumPercent += Platform_setCPUValues(mtr, i);
sumNice += mtr->values[CPU_METER_NICE];
sumNormal += mtr->values[CPU_METER_NORMAL];
@@ -230,9 +262,9 @@ double Platform_setCPUValues(Meter* mtr, unsigned int cpu) {
return Platform_setCPUAverageValues(mtr);
}
- const DarwinProcessList* dpl = (const DarwinProcessList*)mtr->pl;
- const processor_cpu_load_info_t prev = &dpl->prev_load[cpu - 1];
- const processor_cpu_load_info_t curr = &dpl->curr_load[cpu - 1];
+ const DarwinMachine* dhost = (const DarwinMachine*) mtr->host;
+ const processor_cpu_load_info_t prev = &dhost->prev_load[cpu - 1];
+ const processor_cpu_load_info_t curr = &dhost->curr_load[cpu - 1];
double total = 0;
/* Take the sums */
@@ -240,12 +272,18 @@ double Platform_setCPUValues(Meter* mtr, unsigned int cpu) {
total += (double)curr->cpu_ticks[i] - (double)prev->cpu_ticks[i];
}
- mtr->values[CPU_METER_NICE]
- = ((double)curr->cpu_ticks[CPU_STATE_NICE] - (double)prev->cpu_ticks[CPU_STATE_NICE]) * 100.0 / total;
- mtr->values[CPU_METER_NORMAL]
- = ((double)curr->cpu_ticks[CPU_STATE_USER] - (double)prev->cpu_ticks[CPU_STATE_USER]) * 100.0 / total;
- mtr->values[CPU_METER_KERNEL]
- = ((double)curr->cpu_ticks[CPU_STATE_SYSTEM] - (double)prev->cpu_ticks[CPU_STATE_SYSTEM]) * 100.0 / total;
+ if (total > 1e-6) {
+ mtr->values[CPU_METER_NICE]
+ = ((double)curr->cpu_ticks[CPU_STATE_NICE] - (double)prev->cpu_ticks[CPU_STATE_NICE]) * 100.0 / total;
+ mtr->values[CPU_METER_NORMAL]
+ = ((double)curr->cpu_ticks[CPU_STATE_USER] - (double)prev->cpu_ticks[CPU_STATE_USER]) * 100.0 / total;
+ mtr->values[CPU_METER_KERNEL]
+ = ((double)curr->cpu_ticks[CPU_STATE_SYSTEM] - (double)prev->cpu_ticks[CPU_STATE_SYSTEM]) * 100.0 / total;
+ } else {
+ mtr->values[CPU_METER_NICE] = 0.0;
+ mtr->values[CPU_METER_NORMAL] = 0.0;
+ mtr->values[CPU_METER_KERNEL] = 0.0;
+ }
mtr->curItems = 3;
@@ -259,16 +297,17 @@ double Platform_setCPUValues(Meter* mtr, unsigned int cpu) {
}
void Platform_setMemoryValues(Meter* mtr) {
- const DarwinProcessList* dpl = (const DarwinProcessList*)mtr->pl;
- const struct vm_statistics* vm = &dpl->vm_stats;
+ const DarwinMachine* dhost = (const DarwinMachine*) mtr->host;
+ const struct vm_statistics* vm = &dhost->vm_stats;
double page_K = (double)vm_page_size / (double)1024;
- 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] = "shared memory, like tmpfs and shm"
- mtr->values[3] = (double)vm->inactive_count * page_K;
- // mtr->values[4] = "available memory"
+ mtr->total = dhost->host_info.max_mem / 1024;
+ mtr->values[MEMORY_METER_USED] = (double)(vm->active_count + vm->wire_count) * page_K;
+ // mtr->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ // mtr->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux"
+ mtr->values[MEMORY_METER_BUFFERS] = (double)vm->purgeable_count * page_K;
+ mtr->values[MEMORY_METER_CACHE] = (double)vm->inactive_count * page_K;
+ // mtr->values[MEMORY_METER_AVAILABLE] = "available memory"
}
void Platform_setSwapValues(Meter* mtr) {
@@ -278,19 +317,21 @@ void Platform_setSwapValues(Meter* mtr) {
sysctl(mib, 2, &swapused, &swlen, NULL, 0);
mtr->total = swapused.xsu_total / 1024;
- mtr->values[0] = swapused.xsu_used / 1024;
+ mtr->values[SWAP_METER_USED] = swapused.xsu_used / 1024;
+ // mtr->values[SWAP_METER_CACHE] = "pages that are both in swap and RAM, like SwapCached on linux"
+ // mtr->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux"
}
void Platform_setZfsArcValues(Meter* this) {
- const DarwinProcessList* dpl = (const DarwinProcessList*) this->pl;
+ const DarwinMachine* dhost = (const DarwinMachine*) this->host;
- ZfsArcMeter_readStats(this, &(dpl->zfs));
+ ZfsArcMeter_readStats(this, &dhost->zfs);
}
void Platform_setZfsCompressedArcValues(Meter* this) {
- const DarwinProcessList* dpl = (const DarwinProcessList*) this->pl;
+ const DarwinMachine* dhost = (const DarwinMachine*) this->host;
- ZfsCompressedArcMeter_readStats(this, &(dpl->zfs));
+ ZfsCompressedArcMeter_readStats(this, &dhost->zfs);
}
char* Platform_getProcessEnv(pid_t pid) {
@@ -344,27 +385,158 @@ char* Platform_getProcessEnv(pid_t pid) {
return env;
}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
(void)pid;
- (void)inode;
return NULL;
}
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+void Platform_getFileDescriptors(double* used, double* max) {
+ Generic_getFileDescriptors_sysctl(used, max);
}
bool Platform_getDiskIO(DiskIOData* data) {
- // TODO
- (void)data;
- return false;
+ if (!iokit_available)
+ return false;
+
+ io_iterator_t drive_list;
+
+ /* Get the list of all drives */
+ if (IOServiceGetMatchingServices(iokit_port, IOServiceMatching("IOBlockStorageDriver"), &drive_list))
+ return false;
+
+ unsigned long long int read_sum = 0, write_sum = 0, timeSpend_sum = 0;
+
+ io_registry_entry_t drive;
+ while ((drive = IOIteratorNext(drive_list)) != 0) {
+ CFMutableDictionaryRef properties_tmp = NULL;
+
+ /* Get the properties of this drive */
+ if (IORegistryEntryCreateCFProperties(drive, &properties_tmp, kCFAllocatorDefault, 0)) {
+ IOObjectRelease(drive);
+ IOObjectRelease(drive_list);
+ return false;
+ }
+
+ if (!properties_tmp) {
+ IOObjectRelease(drive);
+ continue;
+ }
+
+ CFDictionaryRef properties = properties_tmp;
+
+ /* Get the statistics of this drive */
+ CFDictionaryRef statistics = (CFDictionaryRef) CFDictionaryGetValue(properties, CFSTR(kIOBlockStorageDriverStatisticsKey));
+
+ if (!statistics) {
+ CFRelease(properties);
+ IOObjectRelease(drive);
+ continue;
+ }
+
+ CFNumberRef number;
+ unsigned long long int value;
+
+ /* Get bytes read */
+ number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey));
+ if (number != 0) {
+ CFNumberGetValue(number, kCFNumberSInt64Type, &value);
+ read_sum += value;
+ }
+
+ /* Get bytes written */
+ number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey));
+ if (number != 0) {
+ CFNumberGetValue(number, kCFNumberSInt64Type, &value);
+ write_sum += value;
+ }
+
+ /* Get total read time (in ns) */
+ number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey));
+ if (number != 0) {
+ CFNumberGetValue(number, kCFNumberSInt64Type, &value);
+ timeSpend_sum += value;
+ }
+
+ /* Get total write time (in ns) */
+ number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey));
+ if (number != 0) {
+ CFNumberGetValue(number, kCFNumberSInt64Type, &value);
+ timeSpend_sum += value;
+ }
+
+ CFRelease(properties);
+ IOObjectRelease(drive);
+ }
+
+ data->totalBytesRead = read_sum;
+ data->totalBytesWritten = write_sum;
+ data->totalMsTimeSpend = timeSpend_sum / 1e6; /* Convert from ns to ms */
+
+ if (drive_list)
+ IOObjectRelease(drive_list);
+
+ return true;
}
+/* Caution: Given that interfaces are dynamic, and it is not possible to get statistics on interfaces that no longer exist,
+ if some interface disappears between the time of two samples, the values of the second sample may be lower than those of
+ the first one. */
bool Platform_getNetworkIO(NetworkIOData* data) {
- // TODO
- (void)data;
- return false;
+ int mib[6] = {CTL_NET,
+ PF_ROUTE, /* routing messages */
+ 0, /* protocol number, currently always 0 */
+ 0, /* select all address families */
+ NET_RT_IFLIST2, /* interface list with addresses */
+ 0};
+
+ for (size_t retry = 0; retry < 4; retry++) {
+ size_t len = 0;
+
+ /* Determine len */
+ if (sysctl(mib, ARRAYSIZE(mib), NULL, &len, NULL, 0) < 0 || len == 0)
+ return false;
+
+ len += 16 * retry * retry * sizeof(struct if_msghdr2);
+ char *buf = xMalloc(len);
+
+ if (sysctl(mib, ARRAYSIZE(mib), buf, &len, NULL, 0) < 0) {
+ free(buf);
+ if (errno == ENOMEM && retry < 3)
+ continue;
+ else
+ return false;
+ }
+
+ uint64_t bytesReceived_sum = 0, packetsReceived_sum = 0, bytesTransmitted_sum = 0, packetsTransmitted_sum = 0;
+
+ for (char *next = buf; next < buf + len;) {
+ void *tmp = (void*) next;
+ struct if_msghdr *ifm = (struct if_msghdr*) tmp;
+
+ next += ifm->ifm_msglen;
+
+ if (ifm->ifm_type != RTM_IFINFO2)
+ continue;
+
+ struct if_msghdr2 *ifm2 = (struct if_msghdr2*) ifm;
+
+ if (ifm2->ifm_data.ifi_type != IFT_LOOP) { /* do not count loopback traffic */
+ bytesReceived_sum += ifm2->ifm_data.ifi_ibytes;
+ packetsReceived_sum += ifm2->ifm_data.ifi_ipackets;
+ bytesTransmitted_sum += ifm2->ifm_data.ifi_obytes;
+ packetsTransmitted_sum += ifm2->ifm_data.ifi_opackets;
+ }
+ }
+
+ data->bytesReceived = bytesReceived_sum;
+ data->packetsReceived = packetsReceived_sum;
+ data->bytesTransmitted = bytesTransmitted_sum;
+ data->packetsTransmitted = packetsTransmitted_sum;
+
+ free(buf);
+ }
+
+ return true;
}
void Platform_getBattery(double* percent, ACPresence* isOnAC) {
diff --git a/darwin/Platform.h b/darwin/Platform.h
index 4f8e7c9d..f67db8ff 100644
--- a/darwin/Platform.h
+++ b/darwin/Platform.h
@@ -54,7 +54,7 @@ int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* mtr, unsigned int cpu);
@@ -68,10 +68,10 @@ void Platform_setZfsCompressedArcValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+void Platform_getFileDescriptors(double* used, double* max);
+
bool Platform_getDiskIO(DiskIOData* data);
bool Platform_getNetworkIO(NetworkIOData* data);
@@ -100,7 +100,9 @@ static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec)
void Platform_gettime_monotonic(uint64_t* msec);
-static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
@@ -110,12 +112,30 @@ 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 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 const char* Platform_dynamicColumnName(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;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
-static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
#endif
diff --git a/darwin/PlatformHelpers.c b/darwin/PlatformHelpers.c
index bde90685..a4ea82be 100644
--- a/darwin/PlatformHelpers.c
+++ b/darwin/PlatformHelpers.c
@@ -58,7 +58,7 @@ bool Platform_KernelVersionIsBetween(KernelVersion lowerBound, KernelVersion upp
&& Platform_CompareKernelVersion(upperBound) < 0;
}
-void Platform_getCPUBrandString(char *cpuBrandString, size_t cpuBrandStringSize) {
+void Platform_getCPUBrandString(char* cpuBrandString, size_t cpuBrandStringSize) {
if (sysctlbyname("machdep.cpu.brand_string", cpuBrandString, &cpuBrandStringSize, NULL, 0) == -1) {
fprintf(stderr,
"WARN: Unable to determine the CPU brand string.\n"
@@ -69,12 +69,13 @@ void Platform_getCPUBrandString(char *cpuBrandString, size_t cpuBrandStringSize)
}
// Adapted from https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment
-bool Platform_isRunningTranslated() {
+bool Platform_isRunningTranslated(void) {
int ret = 0;
size_t size = sizeof(ret);
errno = 0;
if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
- if (errno == ENOENT) return false;
+ if (errno == ENOENT)
+ return false;
fprintf(stderr,
"WARN: Could not determine if this process was running in a translation environment like Rosetta 2.\n"
@@ -86,7 +87,7 @@ bool Platform_isRunningTranslated() {
return ret;
}
-double Platform_calculateNanosecondsPerMachTick() {
+double Platform_calculateNanosecondsPerMachTick(void) {
// Check if we can determine the timebase used on this system.
// If the API is unavailable assume we get our timebase in nanoseconds.
#ifndef HAVE_MACH_TIMEBASE_INFO
@@ -102,9 +103,8 @@ double Platform_calculateNanosecondsPerMachTick() {
* the "Apple M1" chip specifically when running under Rosetta 2.
*/
- size_t cpuBrandStringSize = 1024;
- char cpuBrandString[cpuBrandStringSize];
- Platform_getCPUBrandString(cpuBrandString, cpuBrandStringSize);
+ char cpuBrandString[1024] = "";
+ Platform_getCPUBrandString(cpuBrandString, sizeof(cpuBrandString));
bool isRunningUnderRosetta2 = Platform_isRunningTranslated();
diff --git a/darwin/PlatformHelpers.h b/darwin/PlatformHelpers.h
index 07f3fe23..45aea1a7 100644
--- a/darwin/PlatformHelpers.h
+++ b/darwin/PlatformHelpers.h
@@ -31,7 +31,7 @@ bool Platform_KernelVersionIsBetween(KernelVersion lowerBound, KernelVersion upp
double Platform_calculateNanosecondsPerMachTick(void);
-void Platform_getCPUBrandString(char *cpuBrandString, size_t cpuBrandStringSize);
+void Platform_getCPUBrandString(char* cpuBrandString, size_t cpuBrandStringSize);
bool Platform_isRunningTranslated(void);
diff --git a/docs/images/screenshot.png b/docs/images/screenshot.png
index 0ff3cfea..85cb9dda 100644
--- a/docs/images/screenshot.png
+++ b/docs/images/screenshot.png
Binary files differ
diff --git a/docs/styleguide.md b/docs/styleguide.md
index 977ee38b..92e69f5a 100644
--- a/docs/styleguide.md
+++ b/docs/styleguide.md
@@ -8,12 +8,12 @@ Names are important to convey what all those things inside the project are for.
Filenames for source code traditionally used camel-case naming with the first letter written in uppercase.
The file extension is always lowercase.
-The only exception here is `htop.c` which is the main entrance point into the code.
+The only exceptions here are `htop.c` and `pcp-htop.c`, which contain the main entrance points into the code.
Folders for e.g. platform-specific code or complex features spawning multiple files are written in lowercase, e.g. `linux`, `freebsd`, `zfs`.
Inside files, the naming somewhat depends on the context.
-For functions names should include a camel-case prefix before the actual name, separated by an underscore.
+Function names should include a camel-case prefix before the actual name, separated by an underscore.
While this prefix usually coincides with the module name, this is not required, yet strongly advised.
One important exception to this rule are the memory management and the string utility functions from `XUtils.h`.
@@ -65,7 +65,7 @@ The list of includes should be the first thing in the file, after the copyright
The include list should be in the following order, with each group separated by one blank line:
1. `include "config.h" // IWYU pragma: keep` if the global configuration
- from automake&autoconfigure or any of the feature guards for C library headers
+ from `automake`&`autoconfigure` or any of the feature guards for C library headers
(like `__GNU_SOURCE`) are required, optional otherwise. Beware of the IWYU comment.
2. Accompanying module header file (for C source files only, missing inside headers)
3. List of used system headers (non-conditional includes)
@@ -75,6 +75,11 @@ The include list should be in the following order, with each group separated by
The list of headers should be sorted with includes from subdirectories following after files inside their parent directory.
Thus `unistd.h` sorts before `sys/time.h`.
+When `XUtils.h` is used by the module itself or any of its included headers, the C source file must include `config.h` in the manner noted above.
+Failure to do so will cause a compilation error (sanity check inside `XUtils.h`) or may result in other, hard-to-debug compilation issues.
+The include for `config.h` is only ever placed in the C source file and never in any header file.
+For further details see PR #1337 in our issue tracker.
+
Symbol Exports
--------------
@@ -97,6 +102,8 @@ Allocation functions assert the amount of memory requested is non-zero.
Trying to allocate 0 bytes of memory is an error.
Please use the explicit value `NULL` in this case and handle it in your code accordingly.
+If the allocated block of memory is intended to hold an array of values, you should use the alternate functions `xReallocArray` and `xReallocArrayZero` instead.
+
Working with Strings
--------------------
@@ -169,7 +176,7 @@ if (fd >= 0)
While the existing code base isn't fully consistent with this code style yet it is strongly recommended that new code follows these rules.
Adapting surrounding code near places you need to touch is encouraged.
-Try to separate such changes into a single, clean-up only commit to reduce noise while reviewing your changes.
+Try to split off such changes into a separate, clean-up only commit to reduce noise while reviewing your changes.
When writing your code consistency with the surrounding codebase is favoured.
@@ -230,6 +237,8 @@ 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.
+The commit comment should be descriptive (`Updated Foo.c` is not), explain what the changes are and describe why they were made.
+While in trivial cases a short subject suffices, more complex changes might warrant a longer description and explanation of the rationale behind the changes.
A PR should not contain any merge commits.
To follow the upstream branch of your PR rebase your work instead.
@@ -239,5 +248,7 @@ Instead squash those changes in the appropriate commit that introduced that mist
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.
+If you open a PR you need to follow up to resolve any comments/change requests.
+Otherwise it may be closed without merging.
Rebase early. Rebase often.
diff --git a/dragonflybsd/DragonFlyBSDMachine.c b/dragonflybsd/DragonFlyBSDMachine.c
new file mode 100644
index 00000000..d71778fa
--- /dev/null
+++ b/dragonflybsd/DragonFlyBSDMachine.c
@@ -0,0 +1,349 @@
+/*
+htop - DragonFlyBSDMachine.c
+(C) 2014 Hisham H. Muhammad
+(C) 2017 Diederik de Groot
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "dragonflybsd/DragonFlyBSDMachine.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 <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];
+
+static int MIB_vm_stats_vm_v_wire_count[4];
+static int MIB_vm_stats_vm_v_active_count[4];
+static int MIB_vm_stats_vm_v_cache_count[4];
+static int MIB_vm_stats_vm_v_inactive_count[4];
+static int MIB_vm_stats_vm_v_free_count[4];
+
+static int MIB_vfs_bufspace[2];
+
+static int MIB_kern_cp_time[2];
+static int MIB_kern_cp_times[2];
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ size_t len;
+ char errbuf[_POSIX2_LINE_MAX];
+ DragonFlyBSDMachine* this = xCalloc(1, sizeof(DragonFlyBSDMachine));
+ Machine* super = &this->super;
+
+ Machine_init(super, usersTable, userId);
+
+ // physical memory in system: hw.physmem
+ // physical page size: hw.pagesize
+ // usable pagesize : vm.stats.vm.v_page_size
+ len = 2; sysctlnametomib("hw.physmem", MIB_hw_physmem, &len);
+
+ len = sizeof(this->pageSize);
+ if (sysctlbyname("vm.stats.vm.v_page_size", &this->pageSize, &len, NULL, 0) == -1)
+ CRT_fatalError("Cannot get pagesize by sysctl");
+ this->pageSizeKb = this->pageSize / ONE_K;
+
+ // usable page count vm.stats.vm.v_page_count
+ // actually usable memory : vm.stats.vm.v_page_count * vm.stats.vm.v_page_size
+ len = 4; sysctlnametomib("vm.stats.vm.v_page_count", MIB_vm_stats_vm_v_page_count, &len);
+
+ len = 4; sysctlnametomib("vm.stats.vm.v_wire_count", MIB_vm_stats_vm_v_wire_count, &len);
+ len = 4; sysctlnametomib("vm.stats.vm.v_active_count", MIB_vm_stats_vm_v_active_count, &len);
+ len = 4; sysctlnametomib("vm.stats.vm.v_cache_count", MIB_vm_stats_vm_v_cache_count, &len);
+ len = 4; sysctlnametomib("vm.stats.vm.v_inactive_count", MIB_vm_stats_vm_v_inactive_count, &len);
+ len = 4; sysctlnametomib("vm.stats.vm.v_free_count", MIB_vm_stats_vm_v_free_count, &len);
+
+ len = 2; sysctlnametomib("vfs.bufspace", MIB_vfs_bufspace, &len);
+
+ int cpus = 1;
+ len = sizeof(cpus);
+ if (sysctlbyname("hw.ncpu", &cpus, &len, NULL, 0) != 0) {
+ cpus = 1;
+ }
+
+ size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
+ len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len);
+ this->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
+ this->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
+ len = sizeof_cp_time_array;
+
+ // fetch initial single (or average) CPU clicks from kernel
+ sysctl(MIB_kern_cp_time, 2, this->cp_time_o, &len, NULL, 0);
+
+ // on smp box, fetch rest of initial CPU's clicks
+ if (cpus > 1) {
+ len = 2; sysctlnametomib("kern.cp_times", MIB_kern_cp_times, &len);
+ this->cp_times_o = xCalloc(cpus, sizeof_cp_time_array);
+ this->cp_times_n = xCalloc(cpus, sizeof_cp_time_array);
+ len = cpus * sizeof_cp_time_array;
+ sysctl(MIB_kern_cp_times, 2, this->cp_times_o, &len, NULL, 0);
+ }
+
+ super->existingCPUs = MAXIMUM(cpus, 1);
+ // TODO: support offline CPUs and hot swapping
+ super->activeCPUs = super->existingCPUs;
+
+ if (cpus == 1 ) {
+ this->cpus = xRealloc(this->cpus, sizeof(CPUData));
+ } else {
+ // on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)
+ this->cpus = xRealloc(this->cpus, (super->existingCPUs + 1) * sizeof(CPUData));
+ }
+
+ len = sizeof(this->kernelFScale);
+ if (sysctlbyname("kern.fscale", &this->kernelFScale, &len, NULL, 0) == -1 || this->kernelFScale <= 0) {
+ //sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed
+ this->kernelFScale = 2048;
+ }
+
+ this->kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf);
+ if (this->kd == NULL) {
+ CRT_fatalError("kvm_openfiles() failed");
+ }
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ DragonFlyBSDMachine* this = (DragonFlyBSDMachine*) super;
+
+ Machine_done(super);
+
+ if (this->kd) {
+ kvm_close(this->kd);
+ }
+
+ if (this->jails) {
+ Hashtable_delete(this->jails);
+ }
+
+ free(this->cp_time_o);
+ free(this->cp_time_n);
+ free(this->cp_times_o);
+ free(this->cp_times_n);
+ free(this->cpus);
+
+ free(this);
+}
+
+static void DragonFlyBSDMachine_scanCPUTime(Machine* super) {
+ const DragonFlyBSDMachine* this = (DragonFlyBSDMachine*) super;
+
+ unsigned int cpus = super->existingCPUs; // actual CPU count
+ unsigned int maxcpu = cpus; // max iteration (in case we have average + smp)
+ int cp_times_offset;
+
+ assert(cpus > 0);
+
+ size_t sizeof_cp_time_array;
+
+ unsigned long* cp_time_n; // old clicks state
+ unsigned long* cp_time_o; // current clicks state
+
+ unsigned long cp_time_d[CPUSTATES];
+ double cp_time_p[CPUSTATES];
+
+ // get averages or single CPU clicks
+ sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
+ sysctl(MIB_kern_cp_time, 2, this->cp_time_n, &sizeof_cp_time_array, NULL, 0);
+
+ // get rest of CPUs
+ if (cpus > 1) {
+ // on smp systems DragonFlyBSD kernel concats all CPU states into one long array in
+ // kern.cp_times sysctl OID
+ // we store averages in dfpl->cpus[0], and actual cores after that
+ maxcpu = cpus + 1;
+ sizeof_cp_time_array = cpus * sizeof(unsigned long) * CPUSTATES;
+ sysctl(MIB_kern_cp_times, 2, this->cp_times_n, &sizeof_cp_time_array, NULL, 0);
+ }
+
+ for (unsigned int i = 0; i < maxcpu; i++) {
+ if (cpus == 1) {
+ // single CPU box
+ cp_time_n = this->cp_time_n;
+ cp_time_o = this->cp_time_o;
+ } else {
+ if (i == 0 ) {
+ // average
+ cp_time_n = this->cp_time_n;
+ cp_time_o = this->cp_time_o;
+ } else {
+ // specific smp cores
+ cp_times_offset = i - 1;
+ cp_time_n = this->cp_times_n + (cp_times_offset * CPUSTATES);
+ cp_time_o = this->cp_times_o + (cp_times_offset * CPUSTATES);
+ }
+ }
+
+ // diff old vs new
+ unsigned long long total_o = 0;
+ unsigned long long total_n = 0;
+ unsigned long long total_d = 0;
+ for (int s = 0; s < CPUSTATES; s++) {
+ cp_time_d[s] = cp_time_n[s] - cp_time_o[s];
+ total_o += cp_time_o[s];
+ total_n += cp_time_n[s];
+ }
+
+ // totals
+ total_d = total_n - total_o;
+ if (total_d < 1 ) {
+ total_d = 1;
+ }
+
+ // save current state as old and calc percentages
+ for (int s = 0; s < CPUSTATES; ++s) {
+ cp_time_o[s] = cp_time_n[s];
+ cp_time_p[s] = ((double)cp_time_d[s]) / ((double)total_d) * 100;
+ }
+
+ CPUData* cpuData = &(this->cpus[i]);
+ cpuData->userPercent = cp_time_p[CP_USER];
+ cpuData->nicePercent = cp_time_p[CP_NICE];
+ 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];
+ }
+}
+
+static void DragonFlyBSDMachine_scanMemoryInfo(Machine* super) {
+ DragonFlyBSDMachine* this = (DragonFlyBSDMachine*) super;
+
+ // @etosan:
+ // memory counter relationships seem to be these:
+ // total = active + wired + inactive + cache + free
+ // htop_used (unavail to anybody) = active + wired
+ // htop_cache (for cache meter) = buffers + cache
+ // user_free (avail to procs) = buffers + inactive + cache + free
+ size_t len = sizeof(super->totalMem);
+
+ //disabled for now, as it is always smaller than phycal amount of memory...
+ //...to avoid "where is my memory?" questions
+ //sysctl(MIB_vm_stats_vm_v_page_count, 4, &(this->totalMem), &len, NULL, 0);
+ //this->totalMem *= pageSizeKb;
+ sysctl(MIB_hw_physmem, 2, &(super->totalMem), &len, NULL, 0);
+ super->totalMem /= 1024;
+
+ sysctl(MIB_vm_stats_vm_v_active_count, 4, &(this->memActive), &len, NULL, 0);
+ this->memActive *= this->pageSizeKb;
+
+ sysctl(MIB_vm_stats_vm_v_wire_count, 4, &(this->memWire), &len, NULL, 0);
+ this->memWire *= this->pageSizeKb;
+
+ sysctl(MIB_vfs_bufspace, 2, &(super->buffersMem), &len, NULL, 0);
+ super->buffersMem /= 1024;
+
+ sysctl(MIB_vm_stats_vm_v_cache_count, 4, &(super->cachedMem), &len, NULL, 0);
+ super->cachedMem *= this->pageSizeKb;
+ super->usedMem = this->memActive + this->memWire;
+
+ struct kvm_swap swap[16];
+ int nswap = kvm_getswapinfo(this->kd, swap, ARRAYSIZE(swap), 0);
+ super->totalSwap = 0;
+ super->usedSwap = 0;
+ for (int i = 0; i < nswap; i++) {
+ super->totalSwap += swap[i].ksw_total;
+ super->usedSwap += swap[i].ksw_used;
+ }
+ super->totalSwap *= this->pageSizeKb;
+ super->usedSwap *= this->pageSizeKb;
+}
+
+static void DragonFlyBSDMachine_scanJails(DragonFlyBSDMachine* this) {
+ size_t len;
+ char* jails; /* Jail list */
+ char* curpos;
+ char* nextpos;
+
+ if (sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1) {
+ CRT_fatalError("initial sysctlbyname / jail.list failed");
+ }
+
+retry:
+ if (len == 0)
+ return;
+
+ jails = xMalloc(len);
+
+ if (sysctlbyname("jail.list", jails, &len, NULL, 0) == -1) {
+ if (errno == ENOMEM) {
+ free(jails);
+ goto retry;
+ }
+ CRT_fatalError("sysctlbyname / jail.list failed");
+ }
+
+ if (this->jails) {
+ Hashtable_delete(this->jails);
+ }
+
+ this->jails = Hashtable_new(20, true);
+ curpos = jails;
+ while (curpos) {
+ int jailid;
+ char* str_hostname;
+
+ nextpos = strchr(curpos, '\n');
+ if (nextpos) {
+ *nextpos++ = 0;
+ }
+
+ jailid = atoi(strtok(curpos, " "));
+ str_hostname = strtok(NULL, " ");
+
+ char* jname = (char*) (Hashtable_get(this->jails, jailid));
+ if (jname == NULL) {
+ jname = xStrdup(str_hostname);
+ Hashtable_put(this->jails, jailid, jname);
+ }
+
+ curpos = nextpos;
+ }
+
+ free(jails);
+}
+
+char* DragonFlyBSDMachine_readJailName(const DragonFlyBSDMachine* host, int jailid) {
+ char* hostname;
+ char* jname;
+
+ if (jailid != 0 && host->jails && (hostname = (char*)Hashtable_get(host->jails, jailid))) {
+ jname = xStrdup(hostname);
+ } else {
+ jname = xStrdup("-");
+ }
+
+ return jname;
+}
+
+void Machine_scan(Machine* super) {
+ DragonFlyBSDMachine* this = (DragonFlyBSDMachine*) super;
+
+ DragonFlyBSDMachine_scanMemoryInfo(super);
+ DragonFlyBSDMachine_scanCPUTime(super);
+ DragonFlyBSDMachine_scanJails(this);
+}
+
+bool Machine_isCPUonline(const Machine* host, unsigned int id) {
+ assert(id < host->existingCPUs);
+ (void)host; (void)id;
+
+ // TODO: Support detecting online / offline CPUs.
+ return true;
+}
diff --git a/dragonflybsd/DragonFlyBSDProcessList.h b/dragonflybsd/DragonFlyBSDMachine.h
index c1bf2d19..31996a0e 100644
--- a/dragonflybsd/DragonFlyBSDProcessList.h
+++ b/dragonflybsd/DragonFlyBSDMachine.h
@@ -1,7 +1,7 @@
-#ifndef HEADER_DragonFlyBSDProcessList
-#define HEADER_DragonFlyBSDProcessList
+#ifndef HEADER_DragonFlyBSDMachine
+#define HEADER_DragonFlyBSDMachine
/*
-htop - DragonFlyBSDProcessList.h
+htop - DragonFlyBSDMachine.h
(C) 2014 Hisham H. Muhammad
(C) 2017 Diederik de Groot
Released under the GNU GPLv2+, see the COPYING file
@@ -18,11 +18,10 @@ in the source distribution for its full text.
#include <sys/uio.h>
#include "Hashtable.h"
-#include "ProcessList.h"
+#include "Machine.h"
+#include "ProcessTable.h"
#include "UsersTable.h"
-#include "dragonflybsd/DragonFlyBSDProcess.h"
-
typedef struct CPUData_ {
double userPercent;
@@ -33,10 +32,16 @@ typedef struct CPUData_ {
double systemAllPercent;
} CPUData;
-typedef struct DragonFlyBSDProcessList_ {
- ProcessList super;
+typedef struct DragonFlyBSDMachine_ {
+ Machine super;
kvm_t* kd;
+ Hashtable* jails;
+
+ int pageSize;
+ int pageSizeKb;
+ int kernelFScale;
+
unsigned long long int memWire;
unsigned long long int memActive;
unsigned long long int memInactive;
@@ -49,16 +54,8 @@ typedef struct DragonFlyBSDProcessList_ {
unsigned long* cp_times_o;
unsigned long* cp_times_n;
+} DragonFlyBSDMachine;
- Hashtable* jails;
-} DragonFlyBSDProcessList;
-
-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);
+char* DragonFlyBSDMachine_readJailName(const DragonFlyBSDMachine* host, int jailid);
#endif
diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c
index 7ff92451..002378a7 100644
--- a/dragonflybsd/DragonFlyBSDProcess.c
+++ b/dragonflybsd/DragonFlyBSDProcess.c
@@ -38,7 +38,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@@ -52,11 +52,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[JAIL] = { .name = "JAIL", .title = "JAIL ", .description = "Jail prison name", .flags = 0, },
};
-Process* DragonFlyBSDProcess_new(const Settings* settings) {
+Process* DragonFlyBSDProcess_new(const Machine* host) {
DragonFlyBSDProcess* this = xCalloc(1, sizeof(DragonFlyBSDProcess));
Object_setClass(this, Class(DragonFlyBSDProcess));
- Process_init(&this->super, settings);
- return &this->super;
+ Process_init(&this->super, host);
+ return (Process*)this;
}
void Process_delete(Object* cast) {
@@ -66,20 +66,24 @@ void Process_delete(Object* cast) {
free(this);
}
-static void DragonFlyBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
- const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) this;
+static void DragonFlyBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const Process* this = (const Process*) super;
+ const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) super;
+
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
size_t n = sizeof(buffer) - 1;
+
switch (field) {
// add Platform-specific fields here
- case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_isKernelThread(this) ? -1 : this->pid); break;
+ case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_isKernelThread(this) ? -1 : Process_getPid(this)); break;
case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break;
- case JAIL: Process_printLeftAlignedField(str, attr, fp->jname, 11); return;
+ case JAIL: Row_printLeftAlignedField(str, attr, fp->jname, 11); return;
default:
- Process_writeField(this, str, field);
+ Process_writeField(&fp->super, str, field);
return;
}
+
RichString_appendWide(str, attr, buffer);
}
@@ -100,11 +104,18 @@ static int DragonFlyBSDProcess_compareByKey(const Process* v1, const Process* v2
const ProcessClass DragonFlyBSDProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = DragonFlyBSDProcess_rowWriteField
},
- .writeField = DragonFlyBSDProcess_writeField,
.compareByKey = DragonFlyBSDProcess_compareByKey
};
diff --git a/dragonflybsd/DragonFlyBSDProcess.h b/dragonflybsd/DragonFlyBSDProcess.h
index e0a77ef1..92747b1a 100644
--- a/dragonflybsd/DragonFlyBSDProcess.h
+++ b/dragonflybsd/DragonFlyBSDProcess.h
@@ -12,7 +12,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "Process.h"
-#include "Settings.h"
+#include "Machine.h"
typedef struct DragonFlyBSDProcess_ {
@@ -25,7 +25,7 @@ extern const ProcessClass DragonFlyBSDProcess_class;
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-Process* DragonFlyBSDProcess_new(const Settings* settings);
+Process* DragonFlyBSDProcess_new(const Machine* host);
void Process_delete(Object* cast);
diff --git a/dragonflybsd/DragonFlyBSDProcessList.c b/dragonflybsd/DragonFlyBSDProcessList.c
deleted file mode 100644
index 0d0e1a4b..00000000
--- a/dragonflybsd/DragonFlyBSDProcessList.c
+++ /dev/null
@@ -1,615 +0,0 @@
-/*
-htop - DragonFlyBSDProcessList.c
-(C) 2014 Hisham H. Muhammad
-(C) 2017 Diederik de Groot
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "dragonflybsd/DragonFlyBSDProcessList.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 <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];
-static int pageSize;
-static int pageSizeKb;
-
-static int MIB_vm_stats_vm_v_wire_count[4];
-static int MIB_vm_stats_vm_v_active_count[4];
-static int MIB_vm_stats_vm_v_cache_count[4];
-static int MIB_vm_stats_vm_v_inactive_count[4];
-static int MIB_vm_stats_vm_v_free_count[4];
-
-static int MIB_vfs_bufspace[2];
-
-static int MIB_kern_cp_time[2];
-static int MIB_kern_cp_times[2];
-static int kernelFScale;
-
-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, dynamicMeters, dynamicColumns, pidMatchList, userId);
-
- // physical memory in system: hw.physmem
- // physical page size: hw.pagesize
- // usable pagesize : vm.stats.vm.v_page_size
- len = 2; sysctlnametomib("hw.physmem", MIB_hw_physmem, &len);
-
- len = sizeof(pageSize);
- if (sysctlbyname("vm.stats.vm.v_page_size", &pageSize, &len, NULL, 0) == -1)
- CRT_fatalError("Cannot get pagesize by sysctl");
- pageSizeKb = pageSize / ONE_K;
-
- // usable page count vm.stats.vm.v_page_count
- // actually usable memory : vm.stats.vm.v_page_count * vm.stats.vm.v_page_size
- len = 4; sysctlnametomib("vm.stats.vm.v_page_count", MIB_vm_stats_vm_v_page_count, &len);
-
- len = 4; sysctlnametomib("vm.stats.vm.v_wire_count", MIB_vm_stats_vm_v_wire_count, &len);
- len = 4; sysctlnametomib("vm.stats.vm.v_active_count", MIB_vm_stats_vm_v_active_count, &len);
- len = 4; sysctlnametomib("vm.stats.vm.v_cache_count", MIB_vm_stats_vm_v_cache_count, &len);
- len = 4; sysctlnametomib("vm.stats.vm.v_inactive_count", MIB_vm_stats_vm_v_inactive_count, &len);
- len = 4; sysctlnametomib("vm.stats.vm.v_free_count", MIB_vm_stats_vm_v_free_count, &len);
-
- len = 2; sysctlnametomib("vfs.bufspace", MIB_vfs_bufspace, &len);
-
- int cpus = 1;
- len = sizeof(cpus);
- if (sysctlbyname("hw.ncpu", &cpus, &len, NULL, 0) != 0) {
- cpus = 1;
- }
-
- size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
- len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len);
- dfpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
- dfpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
- len = sizeof_cp_time_array;
-
- // fetch initial single (or average) CPU clicks from kernel
- sysctl(MIB_kern_cp_time, 2, dfpl->cp_time_o, &len, NULL, 0);
-
- // on smp box, fetch rest of initial CPU's clicks
- if (cpus > 1) {
- len = 2; sysctlnametomib("kern.cp_times", MIB_kern_cp_times, &len);
- dfpl->cp_times_o = xCalloc(cpus, sizeof_cp_time_array);
- dfpl->cp_times_n = xCalloc(cpus, sizeof_cp_time_array);
- len = cpus * sizeof_cp_time_array;
- sysctl(MIB_kern_cp_times, 2, dfpl->cp_times_o, &len, NULL, 0);
- }
-
- 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->existingCPUs + 1) * sizeof(CPUData));
- }
-
- len = sizeof(kernelFScale);
- if (sysctlbyname("kern.fscale", &kernelFScale, &len, NULL, 0) == -1) {
- //sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed
- kernelFScale = 2048;
- }
-
- dfpl->kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf);
- if (dfpl->kd == NULL) {
- CRT_fatalError("kvm_openfiles() failed");
- }
-
- return pl;
-}
-
-void ProcessList_delete(ProcessList* this) {
- const DragonFlyBSDProcessList* dfpl = (DragonFlyBSDProcessList*) this;
- if (dfpl->kd) {
- kvm_close(dfpl->kd);
- }
-
- if (dfpl->jails) {
- Hashtable_delete(dfpl->jails);
- }
- free(dfpl->cp_time_o);
- free(dfpl->cp_time_n);
- free(dfpl->cp_times_o);
- free(dfpl->cp_times_n);
- free(dfpl->cpus);
-
- ProcessList_done(this);
- free(this);
-}
-
-static inline void DragonFlyBSDProcessList_scanCPUTime(ProcessList* pl) {
- const DragonFlyBSDProcessList* dfpl = (DragonFlyBSDProcessList*) pl;
-
- 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);
-
- size_t sizeof_cp_time_array;
-
- unsigned long* cp_time_n; // old clicks state
- unsigned long* cp_time_o; // current clicks state
-
- unsigned long cp_time_d[CPUSTATES];
- double cp_time_p[CPUSTATES];
-
- // get averages or single CPU clicks
- sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
- sysctl(MIB_kern_cp_time, 2, dfpl->cp_time_n, &sizeof_cp_time_array, NULL, 0);
-
- // get rest of CPUs
- if (cpus > 1) {
- // on smp systems DragonFlyBSD kernel concats all CPU states into one long array in
- // kern.cp_times sysctl OID
- // we store averages in dfpl->cpus[0], and actual cores after that
- maxcpu = cpus + 1;
- sizeof_cp_time_array = cpus * sizeof(unsigned long) * CPUSTATES;
- sysctl(MIB_kern_cp_times, 2, dfpl->cp_times_n, &sizeof_cp_time_array, NULL, 0);
- }
-
- for (unsigned int i = 0; i < maxcpu; i++) {
- if (cpus == 1) {
- // single CPU box
- cp_time_n = dfpl->cp_time_n;
- cp_time_o = dfpl->cp_time_o;
- } else {
- if (i == 0 ) {
- // average
- cp_time_n = dfpl->cp_time_n;
- cp_time_o = dfpl->cp_time_o;
- } else {
- // specific smp cores
- cp_times_offset = i - 1;
- cp_time_n = dfpl->cp_times_n + (cp_times_offset * CPUSTATES);
- cp_time_o = dfpl->cp_times_o + (cp_times_offset * CPUSTATES);
- }
- }
-
- // diff old vs new
- unsigned long long total_o = 0;
- unsigned long long total_n = 0;
- unsigned long long total_d = 0;
- for (int s = 0; s < CPUSTATES; s++) {
- cp_time_d[s] = cp_time_n[s] - cp_time_o[s];
- total_o += cp_time_o[s];
- total_n += cp_time_n[s];
- }
-
- // totals
- total_d = total_n - total_o;
- if (total_d < 1 ) {
- total_d = 1;
- }
-
- // save current state as old and calc percentages
- for (int s = 0; s < CPUSTATES; ++s) {
- cp_time_o[s] = cp_time_n[s];
- cp_time_p[s] = ((double)cp_time_d[s]) / ((double)total_d) * 100;
- }
-
- CPUData* cpuData = &(dfpl->cpus[i]);
- cpuData->userPercent = cp_time_p[CP_USER];
- cpuData->nicePercent = cp_time_p[CP_NICE];
- 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];
- }
-}
-
-static inline void DragonFlyBSDProcessList_scanMemoryInfo(ProcessList* pl) {
- DragonFlyBSDProcessList* dfpl = (DragonFlyBSDProcessList*) pl;
-
- // @etosan:
- // memory counter relationships seem to be these:
- // total = active + wired + inactive + cache + free
- // htop_used (unavail to anybody) = active + wired
- // htop_cache (for cache meter) = buffers + cache
- // user_free (avail to procs) = buffers + inactive + cache + free
- size_t len = sizeof(pl->totalMem);
-
- //disabled for now, as it is always smaller than phycal amount of memory...
- //...to avoid "where is my memory?" questions
- //sysctl(MIB_vm_stats_vm_v_page_count, 4, &(pl->totalMem), &len, NULL, 0);
- //pl->totalMem *= pageSizeKb;
- sysctl(MIB_hw_physmem, 2, &(pl->totalMem), &len, NULL, 0);
- pl->totalMem /= 1024;
-
- sysctl(MIB_vm_stats_vm_v_active_count, 4, &(dfpl->memActive), &len, NULL, 0);
- dfpl->memActive *= pageSizeKb;
-
- sysctl(MIB_vm_stats_vm_v_wire_count, 4, &(dfpl->memWire), &len, NULL, 0);
- dfpl->memWire *= pageSizeKb;
-
- sysctl(MIB_vfs_bufspace, 2, &(pl->buffersMem), &len, NULL, 0);
- pl->buffersMem /= 1024;
-
- sysctl(MIB_vm_stats_vm_v_cache_count, 4, &(pl->cachedMem), &len, NULL, 0);
- pl->cachedMem *= pageSizeKb;
- pl->usedMem = dfpl->memActive + dfpl->memWire;
-
- struct kvm_swap swap[16];
- int nswap = kvm_getswapinfo(dfpl->kd, swap, ARRAYSIZE(swap), 0);
- pl->totalSwap = 0;
- pl->usedSwap = 0;
- for (int i = 0; i < nswap; i++) {
- pl->totalSwap += swap[i].ksw_total;
- pl->usedSwap += swap[i].ksw_used;
- }
- pl->totalSwap *= pageSizeKb;
- pl->usedSwap *= pageSizeKb;
-}
-
-//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 || !argv[0]) {
- Process_updateCmdline(proc, kproc->kp_comm, 0, strlen(kproc->kp_comm));
- return;
- }
-
- size_t len = 0;
- for (int i = 0; argv[i]; i++) {
- len += strlen(argv[i]) + 1;
- }
-
- char* cmdline = xMalloc(len);
-
- char* at = cmdline;
- int end = 0;
- for (int i = 0; argv[i]; i++) {
- at = stpcpy(at, argv[i]);
- if (end == 0) {
- end = at - cmdline;
- }
- *at++ = ' ';
- }
- at--;
- *at = '\0';
-
- Process_updateCmdline(proc, cmdline, 0, end);
-
- free(cmdline);
-}
-
-static inline void DragonFlyBSDProcessList_scanJails(DragonFlyBSDProcessList* dfpl) {
- size_t len;
- char* jls; /* Jail list */
- char* curpos;
- char* nextpos;
-
- if (sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1) {
- CRT_fatalError("initial sysctlbyname / jail.list failed");
- }
-
-retry:
- if (len == 0)
- return;
-
- jls = xMalloc(len);
-
- if (sysctlbyname("jail.list", jls, &len, NULL, 0) == -1) {
- if (errno == ENOMEM) {
- free(jls);
- goto retry;
- }
- CRT_fatalError("sysctlbyname / jail.list failed");
- }
-
- 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;
- }
-
- jailid = atoi(strtok(curpos, " "));
- str_hostname = strtok(NULL, " ");
-
- char* jname = (char*) (Hashtable_get(dfpl->jails, jailid));
- if (jname == NULL) {
- jname = xStrdup(str_hostname);
- Hashtable_put(dfpl->jails, jailid, jname);
- }
-
- curpos = nextpos;
- }
-
- free(jls);
-}
-
-static char* DragonFlyBSDProcessList_readJailName(DragonFlyBSDProcessList* dfpl, int jailid) {
- char* hostname;
- char* jname;
-
- if (jailid != 0 && dfpl->jails && (hostname = (char*)Hashtable_get(dfpl->jails, jailid))) {
- jname = xStrdup(hostname);
- } else {
- jname = xStrdup("-");
- }
-
- return jname;
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
- DragonFlyBSDProcessList* dfpl = (DragonFlyBSDProcessList*) super;
- const Settings* settings = super->settings;
- bool hideKernelThreads = settings->hideKernelThreads;
- bool hideUserlandThreads = settings->hideUserlandThreads;
-
- DragonFlyBSDProcessList_scanMemoryInfo(super);
- DragonFlyBSDProcessList_scanCPUTime(super);
- DragonFlyBSDProcessList_scanJails(dfpl);
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
- int count = 0;
-
- 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++) {
- const struct kinfo_proc* kproc = &kprocs[i];
- bool preExisting = false;
- bool ATTR_UNUSED isIdleProcess = false;
-
- // note: dragonflybsd kernel processes all have the same pid, so we misuse the kernel thread address to give them a unique identifier
- Process* proc = ProcessList_getProcess(super, kproc->kp_ktaddr ? (pid_t)kproc->kp_ktaddr : kproc->kp_pid, &preExisting, DragonFlyBSDProcess_new);
- DragonFlyBSDProcess* dfp = (DragonFlyBSDProcess*) 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;
- proc->isKernelThread = true;
- } else {
- proc->pid = kproc->kp_pid; // process ID
- proc->isKernelThread = false;
- }
- 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->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->ss->flags & PROCESS_FLAG_CWD) {
- DragonFlyBSDProcessList_updateCwd(kproc, proc);
- }
-
- ProcessList_add(super, proc);
-
- dfp->jname = DragonFlyBSDProcessList_readJailName(dfpl, kproc->kp_jailid);
- } else {
- proc->processor = kproc->kp_lwp.kl_cpuid;
- if (dfp->jid != kproc->kp_jailid) { // process can enter jail anytime
- dfp->jid = kproc->kp_jailid;
- free(dfp->jname);
- dfp->jname = DragonFlyBSDProcessList_readJailName(dfpl, kproc->kp_jailid);
- }
- // if there are reapers in the system, process can get reparented anytime
- proc->ppid = kproc->kp_ppid;
- if (proc->st_uid != kproc->kp_uid) { // some processes change users (eg. to lower privs)
- proc->st_uid = kproc->kp_uid;
- proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
- }
- if (settings->updateProcessNames) {
- DragonFlyBSDProcessList_updateProcessName(dfpl->kd, kproc, proc);
- }
- }
-
- proc->m_virt = kproc->kp_vm_map_size / ONE_K;
- proc->m_resident = kproc->kp_vm_rssize * pageSizeKb;
- proc->nlwp = kproc->kp_nthreads; // number of lwp thread
- proc->time = (kproc->kp_swtime + 5000) / 10000;
-
- proc->percent_cpu = 100.0 * ((double)kproc->kp_lwp.kl_pctcpu / (double)kernelFScale);
- proc->percent_mem = 100.0 * proc->m_resident / (double)(super->totalMem);
- Process_updateCPUFieldWidths(proc->percent_cpu);
-
- if (proc->percent_cpu > 0.1) {
- // system idle process should own all CPU time left regardless of CPU count
- if (String_eq("idle", kproc->kp_comm)) {
- isIdleProcess = true;
- }
- }
-
- if (kproc->kp_lwp.kl_pid != -1)
- proc->priority = kproc->kp_lwp.kl_prio;
- else
- 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;
- }
-
- // would be nice if we could store multiple states in proc->state (as enum) and have writeField render them
- /* Taken from: https://github.com/DragonFlyBSD/DragonFlyBSD/blob/c163a4d7ee9c6857ee4e04a3a2cbb50c3de29da1/sys/sys/proc_common.h */
- switch (kproc->kp_stat) {
- case SIDL: proc->state = IDLE; isIdleProcess = true; break;
- case SACTIVE:
- switch (kproc->kp_lwp.kl_stat) {
- case LSSLEEP:
- if (kproc->kp_lwp.kl_flags & LWP_SINTR) // interruptible wait short/long
- if (kproc->kp_lwp.kl_slptime >= MAXSLP) {
- proc->state = IDLE;
- isIdleProcess = true;
- } else {
- proc->state = SLEEPING;
- }
- else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) // interruptible lwkt wait
- proc->state = SLEEPING;
- else if (kproc->kp_paddr) // uninterruptible wait
- proc->state = UNINTERRUPTIBLE_WAIT;
- else // uninterruptible lwkt wait
- proc->state = UNINTERRUPTIBLE_WAIT;
- break;
- case LSRUN:
- if (kproc->kp_lwp.kl_stat == LSRUN) {
- if (!(kproc->kp_lwp.kl_tdflags & (TDF_RUNNING | TDF_RUNQ)))
- proc->state = QUEUED;
- else
- proc->state = RUNNING;
- }
- break;
- case LSSTOP:
- proc->state = STOPPED;
- break;
- default:
- proc->state = PAGING;
- break;
- }
- break;
- case SSTOP: proc->state = STOPPED; break;
- case SZOMB: proc->state = ZOMBIE; break;
- case SCORE: proc->state = BLOCKED; break;
- default: proc->state = UNKNOWN;
- }
-
- if (kproc->kp_flags & P_SWAPPEDOUT)
- proc->state = SLEEPING;
- if (kproc->kp_flags & P_TRACED)
- proc->state = TRACED;
- if (kproc->kp_flags & P_JAILED)
- proc->state = TRACED;
-
- if (Process_isKernelThread(proc))
- super->kernelThreads++;
-
- super->totalTasks++;
-
- if (proc->state == RUNNING)
- 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/DragonFlyBSDProcessTable.c b/dragonflybsd/DragonFlyBSDProcessTable.c
new file mode 100644
index 00000000..b286219d
--- /dev/null
+++ b/dragonflybsd/DragonFlyBSDProcessTable.c
@@ -0,0 +1,322 @@
+/*
+htop - DragonFlyBSDProcessTable.c
+(C) 2014 Hisham H. Muhammad
+(C) 2017 Diederik de Groot
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "dragonflybsd/DragonFlyBSDProcessTable.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 <sys/param.h>
+
+#include "CRT.h"
+#include "Macros.h"
+
+#include "dragonflybsd/DragonFlyBSDMachine.h"
+#include "dragonflybsd/DragonFlyBSDProcess.h"
+
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ DragonFlyBSDProcessTable* this = xCalloc(1, sizeof(DragonFlyBSDProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = (ProcessTable*) this;
+ ProcessTable_init(super, Class(DragonFlyBSDProcess), host, pidMatchList);
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ DragonFlyBSDProcessTable* this = (DragonFlyBSDProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ free(this);
+}
+
+//static void DragonFlyBSDProcessTable_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 DragonFlyBSDProcessTable_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 DragonFlyBSDProcessTable_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 DragonFlyBSDProcessTable_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 || !argv[0]) {
+ Process_updateCmdline(proc, kproc->kp_comm, 0, strlen(kproc->kp_comm));
+ return;
+ }
+
+ size_t len = 0;
+ for (int i = 0; argv[i]; i++) {
+ len += strlen(argv[i]) + 1;
+ }
+
+ char* cmdline = xMalloc(len);
+
+ char* at = cmdline;
+ int end = 0;
+ for (int i = 0; argv[i]; i++) {
+ at = stpcpy(at, argv[i]);
+ if (end == 0) {
+ end = at - cmdline;
+ }
+ *at++ = ' ';
+ }
+ at--;
+ *at = '\0';
+
+ Process_updateCmdline(proc, cmdline, 0, end);
+
+ free(cmdline);
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ const Machine* host = super->super.host;
+ const DragonFlyBSDMachine* dhost = (const DragonFlyBSDMachine*) host;
+ const Settings* settings = host->settings;
+
+ bool hideKernelThreads = settings->hideKernelThreads;
+ bool hideUserlandThreads = settings->hideUserlandThreads;
+
+ int count = 0;
+
+ const struct kinfo_proc* kprocs = kvm_getprocs(dhost->kd, KERN_PROC_ALL | (!hideUserlandThreads ? KERN_PROC_FLAG_LWP : 0), 0, &count);
+
+ for (int i = 0; i < count; i++) {
+ const struct kinfo_proc* kproc = &kprocs[i];
+ bool preExisting = false;
+ bool ATTR_UNUSED isIdleProcess = false;
+
+ // note: dragonflybsd kernel processes all have the same pid, so we misuse the kernel thread address to give them a unique identifier
+ Process* proc = ProcessTable_getProcess(super, kproc->kp_ktaddr ? (pid_t)kproc->kp_ktaddr : kproc->kp_pid, &preExisting, DragonFlyBSDProcess_new);
+ DragonFlyBSDProcess* dfp = (DragonFlyBSDProcess*) 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
+ Process_setPid(proc, (pid_t)kproc->kp_ktaddr);
+ proc->isKernelThread = true;
+ } else {
+ Process_setPid(proc, kproc->kp_pid); // process ID
+ proc->isKernelThread = false;
+ }
+ proc->isUserlandThread = kproc->kp_nthreads > 1;
+ Process_setParent(proc, kproc->kp_ppid); // parent process id
+ proc->tpgid = kproc->kp_tpgid; // tty process group id
+ //Process_setThreadGroup(proc, kproc->kp_lwp.kl_tid); // thread group id
+ Process_setThreadGroup(proc, kproc->kp_pid);
+ proc->pgrp = kproc->kp_pgid; // process group id
+ proc->session = kproc->kp_sid;
+ 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(host->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);
+ }
+
+ DragonFlyBSDProcessTable_updateExe(kproc, proc);
+ DragonFlyBSDProcessTable_updateProcessName(dhost->kd, kproc, proc);
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ DragonFlyBSDProcessTable_updateCwd(kproc, proc);
+ }
+
+ ProcessTable_add(super, proc);
+
+ dfp->jname = DragonFlyBSDMachine_readJailName(dhost, kproc->kp_jailid);
+ } else {
+ proc->processor = kproc->kp_lwp.kl_cpuid;
+ if (dfp->jid != kproc->kp_jailid) { // process can enter jail anytime
+ dfp->jid = kproc->kp_jailid;
+ free(dfp->jname);
+ dfp->jname = DragonFlyBSDMachine_readJailName(dhost, kproc->kp_jailid);
+ }
+ // if there are reapers in the system, process can get reparented anytime
+ Process_setParent(proc, kproc->kp_ppid);
+ if (proc->st_uid != kproc->kp_uid) { // some processes change users (eg. to lower privs)
+ proc->st_uid = kproc->kp_uid;
+ proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);
+ }
+ if (settings->updateProcessNames) {
+ DragonFlyBSDProcessTable_updateProcessName(dhost->kd, kproc, proc);
+ }
+ }
+
+ proc->m_virt = kproc->kp_vm_map_size / ONE_K;
+ proc->m_resident = kproc->kp_vm_rssize * dhost->pageSizeKb;
+ proc->nlwp = kproc->kp_nthreads; // number of lwp thread
+ proc->time = (kproc->kp_lwp.kl_uticks + kproc->kp_lwp.kl_sticks + kproc->kp_lwp.kl_iticks) / 10000;
+
+ proc->percent_cpu = 100.0 * ((double)kproc->kp_lwp.kl_pctcpu / (double)dhost->kernelFScale);
+ proc->percent_mem = 100.0 * proc->m_resident / (double)(super->super.host->totalMem);
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ if (proc->percent_cpu > 0.1) {
+ // system idle process should own all CPU time left regardless of CPU count
+ if (String_eq("idle", kproc->kp_comm)) {
+ isIdleProcess = true;
+ }
+ }
+
+ if (kproc->kp_lwp.kl_pid != -1)
+ proc->priority = kproc->kp_lwp.kl_prio;
+ else
+ 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;
+ }
+
+ // would be nice if we could store multiple states in proc->state (as enum) and have writeField render them
+ /* Taken from: https://github.com/DragonFlyBSD/DragonFlyBSD/blob/c163a4d7ee9c6857ee4e04a3a2cbb50c3de29da1/sys/sys/proc_common.h */
+ switch (kproc->kp_stat) {
+ case SIDL:
+ proc->state = IDLE;
+ isIdleProcess = true;
+ break;
+ case SACTIVE:
+ switch (kproc->kp_lwp.kl_stat) {
+ case LSSLEEP:
+ if (kproc->kp_lwp.kl_flags & LWP_SINTR) { // interruptible wait short/long
+ if (kproc->kp_lwp.kl_slptime >= MAXSLP) {
+ proc->state = IDLE;
+ isIdleProcess = true;
+ } else {
+ proc->state = SLEEPING;
+ }
+ } else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) { // interruptible lwkt wait
+ proc->state = SLEEPING;
+ } else if (kproc->kp_paddr) { // uninterruptible wait
+ proc->state = UNINTERRUPTIBLE_WAIT;
+ } else { // uninterruptible lwkt wait
+ proc->state = UNINTERRUPTIBLE_WAIT;
+ }
+ break;
+ case LSRUN:
+ if (kproc->kp_lwp.kl_stat == LSRUN) {
+ if (!(kproc->kp_lwp.kl_tdflags & (TDF_RUNNING | TDF_RUNQ))) {
+ proc->state = QUEUED;
+ } else {
+ proc->state = RUNNING;
+ }
+ }
+ break;
+ case LSSTOP:
+ proc->state = STOPPED;
+ break;
+ default:
+ proc->state = PAGING;
+ break;
+ }
+ break;
+ case SSTOP:
+ proc->state = STOPPED;
+ break;
+ case SZOMB:
+ proc->state = ZOMBIE;
+ break;
+ case SCORE:
+ proc->state = BLOCKED;
+ break;
+ default:
+ proc->state = UNKNOWN;
+ }
+
+ if (kproc->kp_flags & P_SWAPPEDOUT)
+ proc->state = SLEEPING;
+ if (kproc->kp_flags & P_TRACED)
+ proc->state = TRACED;
+ if (kproc->kp_flags & P_JAILED)
+ proc->state = TRACED;
+
+ if (Process_isKernelThread(proc))
+ super->kernelThreads++;
+
+ super->totalTasks++;
+
+ if (proc->state == RUNNING)
+ super->runningTasks++;
+
+ proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+ proc->super.updated = true;
+ }
+}
diff --git a/dragonflybsd/DragonFlyBSDProcessTable.h b/dragonflybsd/DragonFlyBSDProcessTable.h
new file mode 100644
index 00000000..e8ff1af4
--- /dev/null
+++ b/dragonflybsd/DragonFlyBSDProcessTable.h
@@ -0,0 +1,21 @@
+#ifndef HEADER_DragonFlyBSDProcessTable
+#define HEADER_DragonFlyBSDProcessTable
+/*
+htop - DragonFlyBSDProcessTable.h
+(C) 2014 Hisham H. Muhammad
+(C) 2017 Diederik de Groot
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <sys/param.h>
+
+#include "ProcessTable.h"
+
+
+typedef struct DragonFlyBSDProcessTable_ {
+ ProcessTable super;
+} DragonFlyBSDProcessTable;
+
+#endif
diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c
index ea5f4fa4..f3412ef9 100644
--- a/dragonflybsd/Platform.c
+++ b/dragonflybsd/Platform.c
@@ -6,11 +6,17 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "dragonflybsd/Platform.h"
+#include <devstat.h>
+#include <errno.h>
+#include <ifaddrs.h>
#include <math.h>
#include <time.h>
#include <sys/resource.h>
+#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -20,17 +26,23 @@ in the source distribution for its full text.
#include "CPUMeter.h"
#include "DateMeter.h"
#include "DateTimeMeter.h"
+#include "FileDescriptorMeter.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
+#include "Macros.h"
#include "MemoryMeter.h"
#include "MemorySwapMeter.h"
-#include "ProcessList.h"
+#include "ProcessTable.h"
#include "SwapMeter.h"
#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
+#include "XUtils.h"
+#include "dragonflybsd/DragonFlyBSDMachine.h"
#include "dragonflybsd/DragonFlyBSDProcess.h"
-#include "dragonflybsd/DragonFlyBSDProcessList.h"
+#include "dragonflybsd/DragonFlyBSDProcessTable.h"
+#include "generic/fdstat_sysctl.h"
+
const ScreenDefaults Platform_defaultScreens[] = {
{
@@ -108,6 +120,9 @@ const MeterClass* const Platform_meterTypes[] = {
&RightCPUs4Meter_class,
&LeftCPUs8Meter_class,
&RightCPUs8Meter_class,
+ &DiskIOMeter_class,
+ &NetworkIOMeter_class,
+ &FileDescriptorMeter_class,
&BlankMeter_class,
NULL
};
@@ -126,7 +141,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
struct timeval bootTime, currTime;
int mib[2] = { CTL_KERN, KERN_BOOTTIME };
size_t size = sizeof(bootTime);
@@ -157,7 +172,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;
}
-int Platform_getMaxPid() {
+pid_t Platform_getMaxPid(void) {
int maxPid;
size_t size = sizeof(maxPid);
int err = sysctlbyname("kern.pid_max", &maxPid, &size, NULL, 0);
@@ -168,15 +183,16 @@ int Platform_getMaxPid() {
}
double Platform_setCPUValues(Meter* this, unsigned int cpu) {
- const DragonFlyBSDProcessList* fpl = (const DragonFlyBSDProcessList*) this->pl;
- unsigned int cpus = this->pl->activeCPUs;
+ const Machine* host = this->host;
+ const DragonFlyBSDMachine* dhost = (const DragonFlyBSDMachine*) host;
+ unsigned int cpus = host->activeCPUs;
const CPUData* cpuData;
if (cpus == 1) {
// single CPU box has everything in fpl->cpus[0]
- cpuData = &(fpl->cpus[0]);
+ cpuData = &(dhost->cpus[0]);
} else {
- cpuData = &(fpl->cpus[cpu]);
+ cpuData = &(dhost->cpus[cpu]);
}
double percent;
@@ -184,18 +200,17 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
v[CPU_METER_NICE] = cpuData->nicePercent;
v[CPU_METER_NORMAL] = cpuData->userPercent;
- if (this->pl->settings->detailedCPUTime) {
+ if (host->settings->detailedCPUTime) {
v[CPU_METER_KERNEL] = cpuData->systemPercent;
v[CPU_METER_IRQ] = cpuData->irqPercent;
this->curItems = 4;
- percent = v[0] + v[1] + v[2] + v[3];
} else {
- v[2] = cpuData->systemAllPercent;
+ v[CPU_METER_KERNEL] = cpuData->systemAllPercent;
this->curItems = 3;
- percent = v[0] + v[1] + v[2];
}
- percent = isnan(percent) ? 0.0 : CLAMP(percent, 0.0, 100.0);
+ percent = sumPositiveValues(v, this->curItems);
+ percent = MINIMUM(percent, 100.0);
v[CPU_METER_FREQUENCY] = NAN;
v[CPU_METER_TEMPERATURE] = NAN;
@@ -204,22 +219,23 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
}
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] = "shared memory, like tmpfs and shm"
- this->values[3] = pl->cachedMem;
- // this->values[4] = "available memory"
+ const Machine* host = this->host;
+
+ this->total = host->totalMem;
+ this->values[MEMORY_METER_USED] = host->usedMem;
+ // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux"
+ this->values[MEMORY_METER_BUFFERS] = host->buffersMem;
+ this->values[MEMORY_METER_CACHE] = host->cachedMem;
+ // this->values[MEMORY_METER_AVAILABLE] = "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;
+ const Machine* host = this->host;
+ this->total = host->totalSwap;
+ this->values[SWAP_METER_USED] = host->usedSwap;
+ // mtr->values[SWAP_METER_CACHE] = "pages that are both in swap and RAM, like SwapCached on linux"
+ // mtr->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux"
}
char* Platform_getProcessEnv(pid_t pid) {
@@ -228,27 +244,97 @@ char* Platform_getProcessEnv(pid_t pid) {
return NULL;
}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
(void)pid;
- (void)inode;
return NULL;
}
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+void Platform_getFileDescriptors(double* used, double* max) {
+ Generic_getFileDescriptors_sysctl(used, max);
}
bool Platform_getDiskIO(DiskIOData* data) {
- // TODO
- (void)data;
- return false;
+ struct statinfo dev_stats = { 0 };
+ struct device_selection* dev_sel = NULL;
+ int n_selected, n_selections;
+ long sel_gen;
+
+ dev_stats.dinfo = xCalloc(1, sizeof(struct devinfo));
+
+ int ret = getdevs(&dev_stats);
+ if (ret < 0) {
+ CRT_debug("getdevs() failed [%d]: %s", ret, strerror(errno));
+ free(dev_stats.dinfo);
+ return false;
+ }
+
+ ret = selectdevs(&dev_sel, &n_selected, &n_selections, &sel_gen,
+ dev_stats.dinfo->generation, dev_stats.dinfo->devices, dev_stats.dinfo->numdevs,
+ NULL, 0, NULL, 0, DS_SELECT_ONLY, dev_stats.dinfo->numdevs, 1);
+ if (ret < 0) {
+ CRT_debug("selectdevs() failed [%d]: %s", ret, strerror(errno));
+ free(dev_stats.dinfo);
+ return false;
+ }
+
+ uint64_t bytesReadSum = 0;
+ uint64_t bytesWriteSum = 0;
+ uint64_t busyMsTimeSum = 0;
+
+ for (int i = 0; i < dev_stats.dinfo->numdevs; i++) {
+ const struct devstat* device = &dev_stats.dinfo->devices[dev_sel[i].position];
+
+ switch (device->device_type & DEVSTAT_TYPE_MASK) {
+ case DEVSTAT_TYPE_DIRECT:
+ case DEVSTAT_TYPE_SEQUENTIAL:
+ case DEVSTAT_TYPE_WORM:
+ case DEVSTAT_TYPE_CDROM:
+ case DEVSTAT_TYPE_OPTICAL:
+ case DEVSTAT_TYPE_CHANGER:
+ case DEVSTAT_TYPE_STORARRAY:
+ case DEVSTAT_TYPE_FLOPPY:
+ break;
+ default:
+ continue;
+ }
+
+ bytesReadSum += device->bytes_read;
+ bytesWriteSum += device->bytes_written;
+ busyMsTimeSum += (device->busy_time.tv_sec * 1000 + device->busy_time.tv_usec / 1000);
+ }
+
+ data->totalBytesRead = bytesReadSum;
+ data->totalBytesWritten = bytesWriteSum;
+ data->totalMsTimeSpend = busyMsTimeSum;
+
+ free(dev_stats.dinfo);
+ return true;
}
bool Platform_getNetworkIO(NetworkIOData* data) {
- // TODO
- (void)data;
- return false;
+ struct ifaddrs* ifaddrs = NULL;
+
+ if (getifaddrs(&ifaddrs) != 0)
+ return false;
+
+ for (const struct ifaddrs* ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
+ if (!ifa->ifa_addr)
+ continue;
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+ if (ifa->ifa_flags & IFF_LOOPBACK)
+ continue;
+
+ const struct if_data* ifd = (const struct if_data*)ifa->ifa_data;
+
+ data->bytesReceived += ifd->ifi_ibytes;
+ data->packetsReceived += ifd->ifi_ipackets;
+ data->bytesTransmitted += ifd->ifi_obytes;
+ data->packetsTransmitted += ifd->ifi_opackets;
+ }
+
+ freeifaddrs(ifaddrs);
+ return true;
}
void Platform_getBattery(double* percent, ACPresence* isOnAC) {
diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h
index ec140ad0..606b004c 100644
--- a/dragonflybsd/Platform.h
+++ b/dragonflybsd/Platform.h
@@ -49,7 +49,7 @@ int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, unsigned int cpu);
@@ -59,10 +59,10 @@ void Platform_setSwapValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+void Platform_getFileDescriptors(double* used, double* max);
+
bool Platform_getDiskIO(DiskIOData* data);
bool Platform_getNetworkIO(NetworkIOData* data);
@@ -93,7 +93,9 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) {
Generic_gettime_monotonic(msec);
}
-static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
@@ -103,12 +105,30 @@ 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 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 const char* Platform_dynamicColumnName(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;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
-static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
#endif
diff --git a/freebsd/FreeBSDMachine.c b/freebsd/FreeBSDMachine.c
new file mode 100644
index 00000000..d781414b
--- /dev/null
+++ b/freebsd/FreeBSDMachine.c
@@ -0,0 +1,393 @@
+/*
+htop - FreeBSDMachine.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "freebsd/FreeBSDMachine.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/_iovec.h>
+#include <sys/errno.h>
+#include <sys/param.h> // needs to be included before <sys/jail.h> for MAXPATHLEN
+#include <sys/jail.h>
+#include <sys/priority.h>
+#include <sys/proc.h>
+#include <sys/resource.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/user.h>
+#include <sys/vmmeter.h>
+
+#include "CRT.h"
+#include "Compat.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Scheduling.h"
+#include "Settings.h"
+#include "XUtils.h"
+#include "generic/openzfs_sysctl.h"
+#include "zfs/ZfsArcStats.h"
+
+
+static int MIB_hw_physmem[2];
+static int MIB_vm_stats_vm_v_page_count[4];
+
+static int MIB_vm_stats_vm_v_wire_count[4];
+static int MIB_vm_stats_vm_v_active_count[4];
+static int MIB_vm_stats_vm_v_cache_count[4];
+static int MIB_vm_stats_vm_v_inactive_count[4];
+static int MIB_vm_stats_vm_v_free_count[4];
+static int MIB_vm_vmtotal[2];
+
+static int MIB_vfs_bufspace[2];
+
+static int MIB_kern_cp_time[2];
+static int MIB_kern_cp_times[2];
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ FreeBSDMachine* this = xCalloc(1, sizeof(FreeBSDMachine));
+ Machine* super = &this->super;
+ char errbuf[_POSIX2_LINE_MAX];
+ size_t len;
+
+ Machine_init(super, usersTable, userId);
+
+ // physical memory in system: hw.physmem
+ // physical page size: hw.pagesize
+ // usable pagesize : vm.stats.vm.v_page_size
+ len = 2; sysctlnametomib("hw.physmem", MIB_hw_physmem, &len);
+
+ len = sizeof(this->pageSize);
+ if (sysctlbyname("vm.stats.vm.v_page_size", &this->pageSize, &len, NULL, 0) == -1)
+ CRT_fatalError("Cannot get pagesize by sysctl");
+ this->pageSizeKb = this->pageSize / ONE_K;
+
+ // usable page count vm.stats.vm.v_page_count
+ // actually usable memory : vm.stats.vm.v_page_count * vm.stats.vm.v_page_size
+ len = 4; sysctlnametomib("vm.stats.vm.v_page_count", MIB_vm_stats_vm_v_page_count, &len);
+
+ len = 4; sysctlnametomib("vm.stats.vm.v_wire_count", MIB_vm_stats_vm_v_wire_count, &len);
+ len = 4; sysctlnametomib("vm.stats.vm.v_active_count", MIB_vm_stats_vm_v_active_count, &len);
+ len = 4; sysctlnametomib("vm.stats.vm.v_cache_count", MIB_vm_stats_vm_v_cache_count, &len);
+ len = 4; sysctlnametomib("vm.stats.vm.v_inactive_count", MIB_vm_stats_vm_v_inactive_count, &len);
+ len = 4; sysctlnametomib("vm.stats.vm.v_free_count", MIB_vm_stats_vm_v_free_count, &len);
+ len = 2; sysctlnametomib("vm.vmtotal", MIB_vm_vmtotal, &len);
+
+ len = 2; sysctlnametomib("vfs.bufspace", MIB_vfs_bufspace, &len);
+
+ openzfs_sysctl_init(&this->zfs);
+ openzfs_sysctl_updateArcStats(&this->zfs);
+
+ int smp = 0;
+ len = sizeof(smp);
+
+ if (sysctlbyname("kern.smp.active", &smp, &len, NULL, 0) != 0 || len != sizeof(smp)) {
+ smp = 0;
+ }
+
+ int cpus = 1;
+ len = sizeof(cpus);
+
+ if (smp) {
+ int err = sysctlbyname("kern.smp.cpus", &cpus, &len, NULL, 0);
+ if (err) {
+ cpus = 1;
+ }
+ } else {
+ cpus = 1;
+ }
+
+ size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
+ len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len);
+ this->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
+ this->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
+ len = sizeof_cp_time_array;
+
+ // fetch initial single (or average) CPU clicks from kernel
+ sysctl(MIB_kern_cp_time, 2, this->cp_time_o, &len, NULL, 0);
+
+ // on smp box, fetch rest of initial CPU's clicks
+ if (cpus > 1) {
+ len = 2; sysctlnametomib("kern.cp_times", MIB_kern_cp_times, &len);
+ this->cp_times_o = xCalloc(cpus, sizeof_cp_time_array);
+ this->cp_times_n = xCalloc(cpus, sizeof_cp_time_array);
+ len = cpus * sizeof_cp_time_array;
+ sysctl(MIB_kern_cp_times, 2, this->cp_times_o, &len, NULL, 0);
+ }
+
+ super->existingCPUs = MAXIMUM(cpus, 1);
+ // TODO: support offline CPUs and hot swapping
+ super->activeCPUs = super->existingCPUs;
+
+ if (cpus == 1 ) {
+ this->cpus = xRealloc(this->cpus, sizeof(CPUData));
+ } else {
+ // on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)
+ this->cpus = xRealloc(this->cpus, (super->existingCPUs + 1) * sizeof(CPUData));
+ }
+
+ len = sizeof(this->kernelFScale);
+ if (sysctlbyname("kern.fscale", &this->kernelFScale, &len, NULL, 0) == -1 || this->kernelFScale <= 0) {
+ //sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed
+ this->kernelFScale = 2048;
+ }
+
+ this->kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf);
+ if (this->kd == NULL) {
+ CRT_fatalError("kvm_openfiles() failed");
+ }
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ FreeBSDMachine* this = (FreeBSDMachine*) super;
+
+ Machine_done(super);
+
+ if (this->kd) {
+ kvm_close(this->kd);
+ }
+
+ free(this->cp_time_o);
+ free(this->cp_time_n);
+ free(this->cp_times_o);
+ free(this->cp_times_n);
+ free(this->cpus);
+
+ free(this);
+}
+
+static inline void FreeBSDMachine_scanCPU(Machine* super) {
+ const FreeBSDMachine* this = (FreeBSDMachine*) super;
+
+ unsigned int cpus = super->existingCPUs; // actual CPU count
+ unsigned int maxcpu = cpus; // max iteration (in case we have average + smp)
+ int cp_times_offset;
+
+ assert(cpus > 0);
+
+ size_t sizeof_cp_time_array;
+
+ unsigned long* cp_time_n; // old clicks state
+ unsigned long* cp_time_o; // current clicks state
+
+ unsigned long cp_time_d[CPUSTATES];
+ double cp_time_p[CPUSTATES];
+
+ // get averages or single CPU clicks
+ sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
+ sysctl(MIB_kern_cp_time, 2, this->cp_time_n, &sizeof_cp_time_array, NULL, 0);
+
+ // get rest of CPUs
+ if (cpus > 1) {
+ // on smp systems FreeBSD kernel concats all CPU states into one long array in
+ // kern.cp_times sysctl OID
+ // we store averages in this->cpus[0], and actual cores after that
+ maxcpu = cpus + 1;
+ sizeof_cp_time_array = cpus * sizeof(unsigned long) * CPUSTATES;
+ sysctl(MIB_kern_cp_times, 2, this->cp_times_n, &sizeof_cp_time_array, NULL, 0);
+ }
+
+ for (unsigned int i = 0; i < maxcpu; i++) {
+ if (cpus == 1) {
+ // single CPU box
+ cp_time_n = this->cp_time_n;
+ cp_time_o = this->cp_time_o;
+ } else {
+ if (i == 0 ) {
+ // average
+ cp_time_n = this->cp_time_n;
+ cp_time_o = this->cp_time_o;
+ } else {
+ // specific smp cores
+ cp_times_offset = i - 1;
+ cp_time_n = this->cp_times_n + (cp_times_offset * CPUSTATES);
+ cp_time_o = this->cp_times_o + (cp_times_offset * CPUSTATES);
+ }
+ }
+
+ // diff old vs new
+ unsigned long long total_o = 0;
+ unsigned long long total_n = 0;
+ unsigned long long total_d = 0;
+ for (int s = 0; s < CPUSTATES; s++) {
+ cp_time_d[s] = cp_time_n[s] - cp_time_o[s];
+ total_o += cp_time_o[s];
+ total_n += cp_time_n[s];
+ }
+
+ // totals
+ total_d = total_n - total_o;
+ if (total_d < 1 ) {
+ total_d = 1;
+ }
+
+ // save current state as old and calc percentages
+ for (int s = 0; s < CPUSTATES; ++s) {
+ cp_time_o[s] = cp_time_n[s];
+ cp_time_p[s] = ((double)cp_time_d[s]) / ((double)total_d) * 100;
+ }
+
+ CPUData* cpuData = &(this->cpus[i]);
+ cpuData->userPercent = cp_time_p[CP_USER];
+ cpuData->nicePercent = cp_time_p[CP_NICE];
+ 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
+ //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 (super->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 (super->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 (super->settings->showCPUTemperature) {
+ double maxTemp = -HUGE_VAL;
+ for (unsigned int i = 1; i < maxcpu; i++) {
+ if (isgreater(this->cpus[i].temperature, maxTemp)) {
+ maxTemp = this->cpus[i].temperature;
+ this->cpus[0].temperature = maxTemp;
+ }
+ }
+ }
+
+ if (super->settings->showCPUFrequency) {
+ const double coreZeroFreq = this->cpus[1].frequency;
+ double freqSum = coreZeroFreq;
+ if (isNonnegative(coreZeroFreq)) {
+ for (unsigned int i = 2; i < maxcpu; i++) {
+ if (!isNonnegative(this->cpus[i].frequency))
+ this->cpus[i].frequency = coreZeroFreq;
+
+ freqSum += this->cpus[i].frequency;
+ }
+
+ this->cpus[0].frequency = freqSum / (maxcpu - 1);
+ }
+ }
+ }
+}
+
+static void FreeBSDMachine_scanMemoryInfo(Machine* super) {
+ FreeBSDMachine* this = (FreeBSDMachine*) super;
+
+ // @etosan:
+ // memory counter relationships seem to be these:
+ // total = active + wired + inactive + cache + free
+ // htop_used (unavail to anybody) = active + wired
+ // htop_cache (for cache meter) = buffers + cache
+ // user_free (avail to procs) = buffers + inactive + cache + free
+ //
+ // with ZFS ARC situation becomes bit muddled, as ARC behaves like "user_free"
+ // and belongs into cache, but is reported as wired by kernel
+ //
+ // htop_used = active + (wired - arc)
+ // htop_cache = buffers + cache + arc
+ u_long totalMem;
+ u_int memActive, memWire, cachedMem;
+ long buffersMem;
+ size_t len;
+ struct vmtotal vmtotal;
+
+ //disabled for now, as it is always smaller than phycal amount of memory...
+ //...to avoid "where is my memory?" questions
+ //sysctl(MIB_vm_stats_vm_v_page_count, 4, &(super->totalMem), &len, NULL, 0);
+ //super->totalMem *= this->pageSizeKb;
+ len = sizeof(totalMem);
+ sysctl(MIB_hw_physmem, 2, &(totalMem), &len, NULL, 0);
+ totalMem /= 1024;
+ super->totalMem = totalMem;
+
+ len = sizeof(memActive);
+ sysctl(MIB_vm_stats_vm_v_active_count, 4, &(memActive), &len, NULL, 0);
+ memActive *= this->pageSizeKb;
+ this->memActive = memActive;
+
+ len = sizeof(memWire);
+ sysctl(MIB_vm_stats_vm_v_wire_count, 4, &(memWire), &len, NULL, 0);
+ memWire *= this->pageSizeKb;
+ this->memWire = memWire;
+
+ len = sizeof(buffersMem);
+ sysctl(MIB_vfs_bufspace, 2, &(buffersMem), &len, NULL, 0);
+ buffersMem /= 1024;
+ super->buffersMem = buffersMem;
+
+ len = sizeof(cachedMem);
+ sysctl(MIB_vm_stats_vm_v_cache_count, 4, &(cachedMem), &len, NULL, 0);
+ cachedMem *= this->pageSizeKb;
+ super->cachedMem = cachedMem;
+
+ len = sizeof(vmtotal);
+ sysctl(MIB_vm_vmtotal, 2, &(vmtotal), &len, NULL, 0);
+ super->sharedMem = vmtotal.t_rmshr * this->pageSizeKb;
+
+ super->usedMem = this->memActive + this->memWire;
+
+ struct kvm_swap swap[16];
+ int nswap = kvm_getswapinfo(this->kd, swap, ARRAYSIZE(swap), 0);
+ super->totalSwap = 0;
+ super->usedSwap = 0;
+ for (int i = 0; i < nswap; i++) {
+ super->totalSwap += swap[i].ksw_total;
+ super->usedSwap += swap[i].ksw_used;
+ }
+ super->totalSwap *= this->pageSizeKb;
+ super->usedSwap *= this->pageSizeKb;
+}
+
+void Machine_scan(Machine* super) {
+ FreeBSDMachine* this = (FreeBSDMachine*) super;
+
+ openzfs_sysctl_updateArcStats(&this->zfs);
+ FreeBSDMachine_scanMemoryInfo(super);
+ FreeBSDMachine_scanCPU(super);
+}
+
+bool Machine_isCPUonline(const Machine* host, unsigned int id) {
+ assert(id < host->existingCPUs);
+
+ // TODO: support offline CPUs and hot swapping
+ (void) host; (void) id;
+
+ return true;
+}
diff --git a/freebsd/FreeBSDProcessList.h b/freebsd/FreeBSDMachine.h
index adc70e4f..f34b568e 100644
--- a/freebsd/FreeBSDProcessList.h
+++ b/freebsd/FreeBSDMachine.h
@@ -1,7 +1,7 @@
-#ifndef HEADER_FreeBSDProcessList
-#define HEADER_FreeBSDProcessList
+#ifndef HEADER_FreeBSDMachine
+#define HEADER_FreeBSDMachine
/*
-htop - FreeBSDProcessList.h
+htop - FreeBSDMachine.h
(C) 2014 Hisham H. Muhammad
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
@@ -12,7 +12,7 @@ in the source distribution for its full text.
#include <sys/types.h>
#include "Hashtable.h"
-#include "ProcessList.h"
+#include "Machine.h"
#include "UsersTable.h"
#include "zfs/ZfsArcStats.h"
@@ -28,10 +28,14 @@ typedef struct CPUData_ {
double temperature;
} CPUData;
-typedef struct FreeBSDProcessList_ {
- ProcessList super;
+typedef struct FreeBSDMachine_ {
+ Machine super;
kvm_t* kd;
+ int pageSize;
+ int pageSizeKb;
+ int kernelFScale;
+
unsigned long long int memWire;
unsigned long long int memActive;
@@ -45,14 +49,6 @@ typedef struct FreeBSDProcessList_ {
unsigned long* cp_times_o;
unsigned long* cp_times_n;
-} FreeBSDProcessList;
-
-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);
+} FreeBSDMachine;
#endif
diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c
index 4eccfe7f..662943c9 100644
--- a/freebsd/FreeBSDProcess.c
+++ b/freebsd/FreeBSDProcess.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 "freebsd/FreeBSDProcess.h"
#include <stdlib.h>
@@ -13,6 +15,7 @@ in the source distribution for its full text.
#include "Macros.h"
#include "Process.h"
#include "RichString.h"
+#include "Scheduling.h"
#include "XUtils.h"
@@ -37,7 +40,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@@ -47,16 +50,19 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[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, },
+#ifdef SCHEDULER_SUPPORT
+ [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, },
+#endif
[JID] = { .name = "JID", .title = "JID", .description = "Jail prison ID", .flags = 0, .pidColumn = true, },
[JAIL] = { .name = "JAIL", .title = "JAIL ", .description = "Jail prison name", .flags = 0, },
[EMULATION] = { .name = "EMULATION", .title = "EMULATION ", .description = "System call emulation environment (ABI)", .flags = 0, },
};
-Process* FreeBSDProcess_new(const Settings* settings) {
+Process* FreeBSDProcess_new(const Machine* machine) {
FreeBSDProcess* this = xCalloc(1, sizeof(FreeBSDProcess));
Object_setClass(this, Class(FreeBSDProcess));
- Process_init(&this->super, settings);
- return &this->super;
+ Process_init(&this->super, machine);
+ return (Process*)this;
}
void Process_delete(Object* cast) {
@@ -67,25 +73,27 @@ void Process_delete(Object* cast) {
free(this);
}
-static void FreeBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
- const FreeBSDProcess* fp = (const FreeBSDProcess*) this;
- char buffer[256];
- size_t n = sizeof(buffer);
+static void FreeBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const FreeBSDProcess* fp = (const FreeBSDProcess*) super;
+
+ char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
+ size_t 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);
+ Row_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11);
return;
case EMULATION:
- Process_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16);
+ Row_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16);
return;
default:
- Process_writeField(this, str, field);
+ Process_writeField(&fp->super, str, field);
return;
}
+
RichString_appendWide(str, attr, buffer);
}
@@ -108,11 +116,18 @@ static int FreeBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro
const ProcessClass FreeBSDProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = FreeBSDProcess_rowWriteField
},
- .writeField = FreeBSDProcess_writeField,
.compareByKey = FreeBSDProcess_compareByKey
};
diff --git a/freebsd/FreeBSDProcess.h b/freebsd/FreeBSDProcess.h
index 654f9cfa..012cfa27 100644
--- a/freebsd/FreeBSDProcess.h
+++ b/freebsd/FreeBSDProcess.h
@@ -11,7 +11,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "Process.h"
-#include "Settings.h"
+#include "Machine.h"
typedef struct FreeBSDProcess_ {
@@ -25,7 +25,7 @@ extern const ProcessClass FreeBSDProcess_class;
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-Process* FreeBSDProcess_new(const Settings* settings);
+Process* FreeBSDProcess_new(const Machine* host);
void Process_delete(Object* cast);
diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessList.c
deleted file mode 100644
index f58f3387..00000000
--- a/freebsd/FreeBSDProcessList.c
+++ /dev/null
@@ -1,618 +0,0 @@
-/*
-htop - FreeBSDProcessList.c
-(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "config.h" // IWYU pragma: keep
-
-#include "freebsd/FreeBSDProcessList.h"
-
-#include <assert.h>
-#include <limits.h>
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/_iovec.h>
-#include <sys/errno.h>
-#include <sys/param.h> // needs to be included before <sys/jail.h> for MAXPATHLEN
-#include <sys/jail.h>
-#include <sys/priority.h>
-#include <sys/proc.h>
-#include <sys/resource.h>
-#include <sys/sysctl.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/user.h>
-
-#include "CRT.h"
-#include "Compat.h"
-#include "FreeBSDProcess.h"
-#include "Macros.h"
-#include "Object.h"
-#include "Process.h"
-#include "ProcessList.h"
-#include "Settings.h"
-#include "XUtils.h"
-#include "generic/openzfs_sysctl.h"
-#include "zfs/ZfsArcStats.h"
-
-
-static int MIB_hw_physmem[2];
-static int MIB_vm_stats_vm_v_page_count[4];
-static int pageSize;
-static int pageSizeKb;
-
-static int MIB_vm_stats_vm_v_wire_count[4];
-static int MIB_vm_stats_vm_v_active_count[4];
-static int MIB_vm_stats_vm_v_cache_count[4];
-static int MIB_vm_stats_vm_v_inactive_count[4];
-static int MIB_vm_stats_vm_v_free_count[4];
-
-static int MIB_vfs_bufspace[2];
-
-static int MIB_kern_cp_time[2];
-static int MIB_kern_cp_times[2];
-static int kernelFScale;
-
-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, dynamicMeters, DynamicColumns, pidMatchList, userId);
-
- // physical memory in system: hw.physmem
- // physical page size: hw.pagesize
- // usable pagesize : vm.stats.vm.v_page_size
- len = 2; sysctlnametomib("hw.physmem", MIB_hw_physmem, &len);
-
- len = sizeof(pageSize);
- if (sysctlbyname("vm.stats.vm.v_page_size", &pageSize, &len, NULL, 0) == -1)
- CRT_fatalError("Cannot get pagesize by sysctl");
- pageSizeKb = pageSize / ONE_K;
-
- // usable page count vm.stats.vm.v_page_count
- // actually usable memory : vm.stats.vm.v_page_count * vm.stats.vm.v_page_size
- len = 4; sysctlnametomib("vm.stats.vm.v_page_count", MIB_vm_stats_vm_v_page_count, &len);
-
- len = 4; sysctlnametomib("vm.stats.vm.v_wire_count", MIB_vm_stats_vm_v_wire_count, &len);
- len = 4; sysctlnametomib("vm.stats.vm.v_active_count", MIB_vm_stats_vm_v_active_count, &len);
- len = 4; sysctlnametomib("vm.stats.vm.v_cache_count", MIB_vm_stats_vm_v_cache_count, &len);
- len = 4; sysctlnametomib("vm.stats.vm.v_inactive_count", MIB_vm_stats_vm_v_inactive_count, &len);
- len = 4; sysctlnametomib("vm.stats.vm.v_free_count", MIB_vm_stats_vm_v_free_count, &len);
-
- len = 2; sysctlnametomib("vfs.bufspace", MIB_vfs_bufspace, &len);
-
- openzfs_sysctl_init(&fpl->zfs);
- openzfs_sysctl_updateArcStats(&fpl->zfs);
-
- int smp = 0;
- len = sizeof(smp);
-
- if (sysctlbyname("kern.smp.active", &smp, &len, NULL, 0) != 0 || len != sizeof(smp)) {
- smp = 0;
- }
-
- int cpus = 1;
- len = sizeof(cpus);
-
- if (smp) {
- int err = sysctlbyname("kern.smp.cpus", &cpus, &len, NULL, 0);
- if (err) {
- cpus = 1;
- }
- } else {
- cpus = 1;
- }
-
- size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
- len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len);
- fpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
- fpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
- len = sizeof_cp_time_array;
-
- // fetch initial single (or average) CPU clicks from kernel
- sysctl(MIB_kern_cp_time, 2, fpl->cp_time_o, &len, NULL, 0);
-
- // on smp box, fetch rest of initial CPU's clicks
- if (cpus > 1) {
- len = 2; sysctlnametomib("kern.cp_times", MIB_kern_cp_times, &len);
- fpl->cp_times_o = xCalloc(cpus, sizeof_cp_time_array);
- fpl->cp_times_n = xCalloc(cpus, sizeof_cp_time_array);
- len = cpus * sizeof_cp_time_array;
- sysctl(MIB_kern_cp_times, 2, fpl->cp_times_o, &len, NULL, 0);
- }
-
- 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->existingCPUs + 1) * sizeof(CPUData));
- }
-
-
- len = sizeof(kernelFScale);
- if (sysctlbyname("kern.fscale", &kernelFScale, &len, NULL, 0) == -1) {
- //sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed
- kernelFScale = 2048;
- }
-
- fpl->kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf);
- if (fpl->kd == NULL) {
- CRT_fatalError("kvm_openfiles() failed");
- }
-
- return pl;
-}
-
-void ProcessList_delete(ProcessList* this) {
- const FreeBSDProcessList* fpl = (FreeBSDProcessList*) this;
-
- if (fpl->kd) {
- kvm_close(fpl->kd);
- }
-
- free(fpl->cp_time_o);
- free(fpl->cp_time_n);
- free(fpl->cp_times_o);
- free(fpl->cp_times_n);
- free(fpl->cpus);
-
- ProcessList_done(this);
- free(this);
-}
-
-static inline void FreeBSDProcessList_scanCPU(ProcessList* pl) {
- const FreeBSDProcessList* fpl = (FreeBSDProcessList*) pl;
-
- 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);
-
- size_t sizeof_cp_time_array;
-
- unsigned long* cp_time_n; // old clicks state
- unsigned long* cp_time_o; // current clicks state
-
- unsigned long cp_time_d[CPUSTATES];
- double cp_time_p[CPUSTATES];
-
- // get averages or single CPU clicks
- sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
- sysctl(MIB_kern_cp_time, 2, fpl->cp_time_n, &sizeof_cp_time_array, NULL, 0);
-
- // get rest of CPUs
- if (cpus > 1) {
- // on smp systems FreeBSD kernel concats all CPU states into one long array in
- // kern.cp_times sysctl OID
- // we store averages in fpl->cpus[0], and actual cores after that
- maxcpu = cpus + 1;
- sizeof_cp_time_array = cpus * sizeof(unsigned long) * CPUSTATES;
- sysctl(MIB_kern_cp_times, 2, fpl->cp_times_n, &sizeof_cp_time_array, NULL, 0);
- }
-
- for (unsigned int i = 0; i < maxcpu; i++) {
- if (cpus == 1) {
- // single CPU box
- cp_time_n = fpl->cp_time_n;
- cp_time_o = fpl->cp_time_o;
- } else {
- if (i == 0 ) {
- // average
- cp_time_n = fpl->cp_time_n;
- cp_time_o = fpl->cp_time_o;
- } else {
- // specific smp cores
- cp_times_offset = i - 1;
- cp_time_n = fpl->cp_times_n + (cp_times_offset * CPUSTATES);
- cp_time_o = fpl->cp_times_o + (cp_times_offset * CPUSTATES);
- }
- }
-
- // diff old vs new
- unsigned long long total_o = 0;
- unsigned long long total_n = 0;
- unsigned long long total_d = 0;
- for (int s = 0; s < CPUSTATES; s++) {
- cp_time_d[s] = cp_time_n[s] - cp_time_o[s];
- total_o += cp_time_o[s];
- total_n += cp_time_n[s];
- }
-
- // totals
- total_d = total_n - total_o;
- if (total_d < 1 ) {
- total_d = 1;
- }
-
- // save current state as old and calc percentages
- for (int s = 0; s < CPUSTATES; ++s) {
- cp_time_o[s] = cp_time_n[s];
- cp_time_p[s] = ((double)cp_time_d[s]) / ((double)total_d) * 100;
- }
-
- CPUData* cpuData = &(fpl->cpus[i]);
- cpuData->userPercent = cp_time_p[CP_USER];
- cpuData->nicePercent = cp_time_p[CP_NICE];
- 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
- //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);
- }
- }
- }
-}
-
-static inline void FreeBSDProcessList_scanMemoryInfo(ProcessList* pl) {
- FreeBSDProcessList* fpl = (FreeBSDProcessList*) pl;
-
- // @etosan:
- // memory counter relationships seem to be these:
- // total = active + wired + inactive + cache + free
- // htop_used (unavail to anybody) = active + wired
- // htop_cache (for cache meter) = buffers + cache
- // user_free (avail to procs) = buffers + inactive + cache + free
- //
- // with ZFS ARC situation becomes bit muddled, as ARC behaves like "user_free"
- // and belongs into cache, but is reported as wired by kernel
- //
- // htop_used = active + (wired - arc)
- // htop_cache = buffers + cache + arc
- u_long totalMem;
- u_int memActive, memWire, cachedMem;
- long buffersMem;
- size_t len;
-
- //disabled for now, as it is always smaller than phycal amount of memory...
- //...to avoid "where is my memory?" questions
- //sysctl(MIB_vm_stats_vm_v_page_count, 4, &(pl->totalMem), &len, NULL, 0);
- //pl->totalMem *= pageSizeKb;
- len = sizeof(totalMem);
- sysctl(MIB_hw_physmem, 2, &(totalMem), &len, NULL, 0);
- totalMem /= 1024;
- pl->totalMem = totalMem;
-
- len = sizeof(memActive);
- sysctl(MIB_vm_stats_vm_v_active_count, 4, &(memActive), &len, NULL, 0);
- memActive *= pageSizeKb;
- fpl->memActive = memActive;
-
- len = sizeof(memWire);
- sysctl(MIB_vm_stats_vm_v_wire_count, 4, &(memWire), &len, NULL, 0);
- memWire *= pageSizeKb;
- fpl->memWire = memWire;
-
- len = sizeof(buffersMem);
- sysctl(MIB_vfs_bufspace, 2, &(buffersMem), &len, NULL, 0);
- buffersMem /= 1024;
- pl->buffersMem = buffersMem;
-
- len = sizeof(cachedMem);
- sysctl(MIB_vm_stats_vm_v_cache_count, 4, &(cachedMem), &len, NULL, 0);
- cachedMem *= pageSizeKb;
- pl->cachedMem = cachedMem;
-
- if (fpl->zfs.enabled) {
- fpl->memWire -= fpl->zfs.size;
- pl->cachedMem += fpl->zfs.size;
- }
-
- pl->usedMem = fpl->memActive + fpl->memWire;
-
- struct kvm_swap swap[16];
- int nswap = kvm_getswapinfo(fpl->kd, swap, ARRAYSIZE(swap), 0);
- pl->totalSwap = 0;
- pl->usedSwap = 0;
- for (int i = 0; i < nswap; i++) {
- pl->totalSwap += swap[i].ksw_total;
- pl->usedSwap += swap[i].ksw_used;
- }
- pl->totalSwap *= pageSizeKb;
- pl->usedSwap *= pageSizeKb;
-}
-
-static void FreeBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) {
- if (Process_isKernelThread(proc)) {
- Process_updateExe(proc, NULL);
- return;
- }
-
- 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;
- }
-
- Process_updateExe(proc, buffer);
-}
-
-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;
- }
-
- /* 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 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 || !argv[0]) {
- Process_updateCmdline(proc, kproc->ki_comm, 0, strlen(kproc->ki_comm));
- return;
- }
-
- size_t len = 0;
- for (int i = 0; argv[i]; i++) {
- len += strlen(argv[i]) + 1;
- }
-
- char* cmdline = xMalloc(len);
- char* at = cmdline;
- int end = 0;
- for (int i = 0; argv[i]; i++) {
- at = stpcpy(at, argv[i]);
- if (end == 0) {
- end = at - cmdline;
- }
- *at++ = ' ';
- }
- at--;
- *at = '\0';
-
- Process_updateCmdline(proc, cmdline, 0, end);
-
- free(cmdline);
-}
-
-static char* FreeBSDProcessList_readJailName(const struct kinfo_proc* kproc) {
- if (kproc->ki_jid == 0)
- return xStrdup("-");
-
- char jnamebuf[MAXHOSTNAMELEN] = {0};
- struct iovec jiov[4];
-
-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);
-IGNORE_WCASTQUAL_END
-
- int jid = jail_get(jiov, 4, 0);
- if (jid == kproc->ki_jid)
- return xStrdup(jnamebuf);
-
- return NULL;
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
- FreeBSDProcessList* fpl = (FreeBSDProcessList*) super;
- const Settings* settings = super->settings;
- bool hideKernelThreads = settings->hideKernelThreads;
- bool hideUserlandThreads = settings->hideUserlandThreads;
-
- openzfs_sysctl_updateArcStats(&fpl->zfs);
- FreeBSDProcessList_scanMemoryInfo(super);
- FreeBSDProcessList_scanCPU(super);
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
- int count = 0;
- const struct kinfo_proc* kprocs = kvm_getprocs(fpl->kd, KERN_PROC_PROC, 0, &count);
-
- for (int i = 0; i < count; i++) {
- const struct kinfo_proc* kproc = &kprocs[i];
- bool preExisting = false;
- Process* proc = ProcessList_getProcess(super, kproc->ki_pid, &preExisting, FreeBSDProcess_new);
- FreeBSDProcess* fp = (FreeBSDProcess*) proc;
-
- if (!preExisting) {
- fp->jid = kproc->ki_jid;
- proc->pid = kproc->ki_pid;
- 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;
- proc->session = kproc->ki_sid;
- proc->pgrp = kproc->ki_pgid;
- proc->st_uid = kproc->ki_uid;
- proc->starttime_ctime = kproc->ki_start.tv_sec;
- if (proc->starttime_ctime < 0) {
- proc->starttime_ctime = super->realtimeMs / 1000;
- }
- Process_fillStarttimeBuffer(proc);
- proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
- ProcessList_add(super, proc);
-
- FreeBSDProcessList_updateExe(kproc, proc);
- FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
-
- if (settings->ss->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
- fp->jid = kproc->ki_jid;
- free(fp->jname);
- fp->jname = FreeBSDProcessList_readJailName(kproc);
- }
- // if there are reapers in the system, process can get reparented anytime
- proc->ppid = kproc->ki_ppid;
- if (proc->st_uid != kproc->ki_uid) {
- // some processes change users (eg. to lower privs)
- proc->st_uid = kproc->ki_uid;
- proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
- }
- if (settings->updateProcessNames) {
- FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
- }
- }
-
- free_and_xStrdup(&fp->emul, kproc->ki_emul);
-
- // from FreeBSD source /src/usr.bin/top/machine.c
- proc->m_virt = kproc->ki_size / ONE_K;
- proc->m_resident = kproc->ki_rssize * pageSizeKb;
- proc->nlwp = kproc->ki_numthreads;
- proc->time = (kproc->ki_runtime + 5000) / 10000;
-
- proc->percent_cpu = 100.0 * ((double)kproc->ki_pctcpu / (double)kernelFScale);
- proc->percent_mem = 100.0 * proc->m_resident / (double)(super->totalMem);
- Process_updateCPUFieldWidths(proc->percent_cpu);
-
- if (kproc->ki_stat == SRUN && kproc->ki_oncpu != NOCPU) {
- proc->processor = kproc->ki_oncpu;
- } else {
- proc->processor = kproc->ki_lastcpu;
- }
-
- proc->majflt = kproc->ki_cow;
-
- proc->priority = kproc->ki_pri.pri_level - PZERO;
-
- 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;
- } else if (PRI_IS_REALTIME(kproc->ki_pri.pri_class)) {
- proc->nice = PRIO_MIN - 1 - (PRI_MAX_REALTIME - kproc->ki_pri.pri_level);
- } else {
- proc->nice = PRIO_MAX + 1 + kproc->ki_pri.pri_level - PRI_MIN_IDLE;
- }
-
- /* Taken from: https://github.com/freebsd/freebsd-src/blob/1ad2d87778970582854082bcedd2df0394fd4933/sys/sys/proc.h#L851 */
- switch (kproc->ki_stat) {
- case SIDL: proc->state = IDLE; break;
- case SRUN: proc->state = RUNNING; break;
- case SSLEEP: proc->state = SLEEPING; break;
- case SSTOP: proc->state = STOPPED; break;
- case SZOMB: proc->state = ZOMBIE; break;
- case SWAIT: proc->state = WAITING; break;
- case SLOCK: proc->state = BLOCKED; break;
- default: proc->state = UNKNOWN;
- }
-
- if (Process_isKernelThread(proc))
- super->kernelThreads++;
-
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
- super->totalTasks++;
- if (proc->state == RUNNING)
- 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/FreeBSDProcessTable.c b/freebsd/FreeBSDProcessTable.c
new file mode 100644
index 00000000..9e18b8ab
--- /dev/null
+++ b/freebsd/FreeBSDProcessTable.c
@@ -0,0 +1,288 @@
+/*
+htop - FreeBSDProcessTable.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "freebsd/FreeBSDProcessTable.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/_iovec.h>
+#include <sys/errno.h>
+#include <sys/param.h> // needs to be included before <sys/jail.h> for MAXPATHLEN
+#include <sys/jail.h>
+#include <sys/priority.h>
+#include <sys/proc.h>
+#include <sys/resource.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/user.h>
+#include <sys/vmmeter.h>
+
+#include "CRT.h"
+#include "Compat.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Process.h"
+#include "ProcessTable.h"
+#include "Scheduling.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+#include "freebsd/FreeBSDMachine.h"
+#include "freebsd/FreeBSDProcess.h"
+
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ FreeBSDProcessTable* this = xCalloc(1, sizeof(FreeBSDProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(FreeBSDProcess), host, pidMatchList);
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ FreeBSDProcessTable* this = (FreeBSDProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ free(this);
+}
+
+static void FreeBSDProcessTable_updateExe(const struct kinfo_proc* kproc, Process* proc) {
+ if (Process_isKernelThread(proc)) {
+ Process_updateExe(proc, NULL);
+ return;
+ }
+
+ 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;
+ }
+
+ Process_updateExe(proc, buffer);
+}
+
+static void FreeBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
+#ifdef KERN_PROC_CWD
+ 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;
+ }
+
+ /* Kernel threads return an empty buffer */
+ if (buffer[0] == '\0') {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ free_and_xStrdup(&proc->procCwd, buffer);
+#else
+ proc->procCwd = NULL;
+#endif
+}
+
+static void FreeBSDProcessTable_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 || !argv[0]) {
+ Process_updateCmdline(proc, kproc->ki_comm, 0, strlen(kproc->ki_comm));
+ return;
+ }
+
+ size_t len = 0;
+ for (int i = 0; argv[i]; i++) {
+ len += strlen(argv[i]) + 1;
+ }
+
+ char* cmdline = xMalloc(len);
+ char* at = cmdline;
+ int end = 0;
+ for (int i = 0; argv[i]; i++) {
+ at = stpcpy(at, argv[i]);
+ if (end == 0) {
+ end = at - cmdline;
+ }
+ *at++ = ' ';
+ }
+ at--;
+ *at = '\0';
+
+ Process_updateCmdline(proc, cmdline, 0, end);
+
+ free(cmdline);
+}
+
+static char* FreeBSDProcessTable_readJailName(const struct kinfo_proc* kproc) {
+ if (kproc->ki_jid == 0)
+ return xStrdup("-");
+
+ char jnamebuf[MAXHOSTNAMELEN] = {0};
+ struct iovec jiov[4];
+
+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);
+IGNORE_WCASTQUAL_END
+
+ int jid = jail_get(jiov, 4, 0);
+ if (jid == kproc->ki_jid)
+ return xStrdup(jnamebuf);
+
+ return NULL;
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ const Machine* host = super->super.host;
+ const FreeBSDMachine* fhost = (const FreeBSDMachine*) host;
+ const Settings* settings = host->settings;
+ bool hideKernelThreads = settings->hideKernelThreads;
+ bool hideUserlandThreads = settings->hideUserlandThreads;
+
+ int count = 0;
+ const struct kinfo_proc* kprocs = kvm_getprocs(fhost->kd, KERN_PROC_PROC, 0, &count);
+
+ for (int i = 0; i < count; i++) {
+ const struct kinfo_proc* kproc = &kprocs[i];
+ bool preExisting = false;
+ Process* proc = ProcessTable_getProcess(super, kproc->ki_pid, &preExisting, FreeBSDProcess_new);
+ FreeBSDProcess* fp = (FreeBSDProcess*) proc;
+
+ if (!preExisting) {
+ fp->jid = kproc->ki_jid;
+ Process_setPid(proc, kproc->ki_pid);
+ Process_setThreadGroup(proc, kproc->ki_pid);
+ Process_setParent(proc, kproc->ki_ppid);
+ proc->isKernelThread = kproc->ki_pid != 1 && (kproc->ki_flag & P_SYSTEM);
+ proc->isUserlandThread = false;
+ proc->tpgid = kproc->ki_tpgid;
+ proc->session = kproc->ki_sid;
+ proc->pgrp = kproc->ki_pgid;
+ proc->st_uid = kproc->ki_uid;
+ proc->starttime_ctime = kproc->ki_start.tv_sec;
+ if (proc->starttime_ctime < 0) {
+ proc->starttime_ctime = host->realtimeMs / 1000;
+ }
+ Process_fillStarttimeBuffer(proc);
+ proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);
+ ProcessTable_add(super, proc);
+
+ FreeBSDProcessTable_updateExe(kproc, proc);
+ FreeBSDProcessTable_updateProcessName(fhost->kd, kproc, proc);
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ FreeBSDProcessTable_updateCwd(kproc, proc);
+ }
+
+ fp->jname = FreeBSDProcessTable_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
+ fp->jid = kproc->ki_jid;
+ free(fp->jname);
+ fp->jname = FreeBSDProcessTable_readJailName(kproc);
+ }
+ // if there are reapers in the system, process can get reparented anytime
+ Process_setParent(proc, kproc->ki_ppid);
+ if (proc->st_uid != kproc->ki_uid) {
+ // some processes change users (eg. to lower privs)
+ proc->st_uid = kproc->ki_uid;
+ proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);
+ }
+ if (settings->updateProcessNames) {
+ FreeBSDProcessTable_updateProcessName(fhost->kd, kproc, proc);
+ }
+ }
+
+ free_and_xStrdup(&fp->emul, kproc->ki_emul);
+
+ // from FreeBSD source /src/usr.bin/top/machine.c
+ proc->m_virt = kproc->ki_size / ONE_K;
+ proc->m_resident = kproc->ki_rssize * fhost->pageSizeKb;
+ proc->nlwp = kproc->ki_numthreads;
+ proc->time = (kproc->ki_runtime + 5000) / 10000;
+
+ proc->percent_cpu = 100.0 * ((double)kproc->ki_pctcpu / (double)fhost->kernelFScale);
+ proc->percent_mem = 100.0 * proc->m_resident / (double)(host->totalMem);
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ 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 (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;
+ } else if (PRI_IS_REALTIME(kproc->ki_pri.pri_class)) {
+ proc->nice = PRIO_MIN - 1 - (PRI_MAX_REALTIME - kproc->ki_pri.pri_level);
+ } else {
+ proc->nice = PRIO_MAX + 1 + kproc->ki_pri.pri_level - PRI_MIN_IDLE;
+ }
+
+ /* Taken from: https://github.com/freebsd/freebsd-src/blob/1ad2d87778970582854082bcedd2df0394fd4933/sys/sys/proc.h#L851 */
+ switch (kproc->ki_stat) {
+ case SIDL: proc->state = IDLE; break;
+ case SRUN: proc->state = RUNNING; break;
+ case SSLEEP: proc->state = SLEEPING; break;
+ case SSTOP: proc->state = STOPPED; break;
+ case SZOMB: proc->state = ZOMBIE; break;
+ case SWAIT: proc->state = WAITING; break;
+ case SLOCK: proc->state = BLOCKED; break;
+ default: proc->state = UNKNOWN;
+ }
+
+ if (Process_isKernelThread(proc))
+ super->kernelThreads++;
+
+#ifdef SCHEDULER_SUPPORT
+ if (settings->ss->flags & PROCESS_FLAG_SCHEDPOL)
+ Scheduling_readProcessPolicy(proc);
+#endif
+
+ proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+
+ super->totalTasks++;
+ if (proc->state == RUNNING)
+ super->runningTasks++;
+ proc->super.updated = true;
+ }
+}
diff --git a/freebsd/FreeBSDProcessTable.h b/freebsd/FreeBSDProcessTable.h
new file mode 100644
index 00000000..23a6ab22
--- /dev/null
+++ b/freebsd/FreeBSDProcessTable.h
@@ -0,0 +1,21 @@
+#ifndef HEADER_FreeBSDProcessTable
+#define HEADER_FreeBSDProcessTable
+/*
+htop - FreeBSDProcessTable.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "Hashtable.h"
+#include "ProcessTable.h"
+#include "UsersTable.h"
+
+typedef struct FreeBSDProcessTable_ {
+ ProcessTable super;
+} FreeBSDProcessTable;
+
+#endif
diff --git a/freebsd/Platform.c b/freebsd/Platform.c
index 1a731b71..9be7195e 100644
--- a/freebsd/Platform.c
+++ b/freebsd/Platform.c
@@ -31,22 +31,23 @@ in the source distribution for its full text.
#include "DateMeter.h"
#include "DateTimeMeter.h"
#include "DiskIOMeter.h"
+#include "FileDescriptorMeter.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
+#include "Machine.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 "freebsd/FreeBSDMachine.h"
+#include "generic/fdstat_sysctl.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsCompressedArcMeter.h"
@@ -130,6 +131,7 @@ const MeterClass* const Platform_meterTypes[] = {
&ZfsArcMeter_class,
&ZfsCompressedArcMeter_class,
&DiskIOMeter_class,
+ &FileDescriptorMeter_class,
&NetworkIOMeter_class,
NULL
};
@@ -148,7 +150,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
struct timeval bootTime, currTime;
const int mib[2] = { CTL_KERN, KERN_BOOTTIME };
size_t size = sizeof(bootTime);
@@ -179,7 +181,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;
}
-int Platform_getMaxPid() {
+pid_t Platform_getMaxPid(void) {
int maxPid;
size_t size = sizeof(maxPid);
int err = sysctlbyname("kern.pid_max", &maxPid, &size, NULL, 0);
@@ -190,31 +192,27 @@ int Platform_getMaxPid() {
}
double Platform_setCPUValues(Meter* this, unsigned int cpu) {
- const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) this->pl;
- unsigned int cpus = this->pl->activeCPUs;
- const CPUData* cpuData;
+ const Machine* host = this->host;
+ const FreeBSDMachine* fhost = (const FreeBSDMachine*) host;
+ unsigned int cpus = host->activeCPUs;
- if (cpus == 1) {
- // single CPU box has everything in fpl->cpus[0]
- cpuData = &(fpl->cpus[0]);
- } else {
- cpuData = &(fpl->cpus[cpu]);
- }
+ // single CPU box has everything in fhost->cpus[0]
+ const CPUData* cpuData = cpus == 1 ? &fhost->cpus[0] : &fhost->cpus[cpu];
double percent;
double* v = this->values;
v[CPU_METER_NICE] = cpuData->nicePercent;
v[CPU_METER_NORMAL] = cpuData->userPercent;
- if (this->pl->settings->detailedCPUTime) {
+ if (host->settings->detailedCPUTime) {
v[CPU_METER_KERNEL] = cpuData->systemPercent;
v[CPU_METER_IRQ] = cpuData->irqPercent;
this->curItems = 4;
- percent = v[0] + v[1] + v[2] + v[3];
+ percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ];
} else {
- v[2] = cpuData->systemAllPercent;
+ v[CPU_METER_NORMAL] = cpuData->systemAllPercent;
this->curItems = 3;
- percent = v[0] + v[1] + v[2];
+ percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL];
}
percent = CLAMP(percent, 0.0, 100.0);
@@ -226,44 +224,47 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
}
void Platform_setMemoryValues(Meter* this) {
- const ProcessList* pl = this->pl;
- const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) pl;
-
- this->total = pl->totalMem;
- this->values[0] = pl->usedMem;
- this->values[1] = pl->buffersMem;
- // this->values[2] = "shared memory, like tmpfs and shm"
- this->values[3] = pl->cachedMem;
- // this->values[4] = "available memory"
-
- if (fpl->zfs.enabled) {
+ const Machine* host = this->host;
+ const FreeBSDMachine* fhost = (const FreeBSDMachine*) host;
+
+ this->total = host->totalMem;
+ this->values[MEMORY_METER_USED] = host->usedMem;
+ this->values[MEMORY_METER_SHARED] = host->sharedMem;
+ // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux"
+ this->values[MEMORY_METER_BUFFERS] = host->buffersMem;
+ this->values[MEMORY_METER_CACHE] = host->cachedMem;
+ // this->values[MEMORY_METER_AVAILABLE] = "available memory"
+
+ if (fhost->zfs.enabled) {
// ZFS does not shrink below the value of zfs_arc_min.
unsigned long long int shrinkableSize = 0;
- if (fpl->zfs.size > fpl->zfs.min)
- shrinkableSize = fpl->zfs.size - fpl->zfs.min;
- this->values[0] -= shrinkableSize;
- this->values[3] += shrinkableSize;
- // this->values[4] += shrinkableSize;
+ if (fhost->zfs.size > fhost->zfs.min)
+ shrinkableSize = fhost->zfs.size - fhost->zfs.min;
+ this->values[MEMORY_METER_USED] -= shrinkableSize;
+ this->values[MEMORY_METER_CACHE] += shrinkableSize;
+ // this->values[MEMORY_METER_AVAILABLE] += shrinkableSize;
}
}
void Platform_setSwapValues(Meter* this) {
- const ProcessList* pl = this->pl;
- this->total = pl->totalSwap;
- this->values[0] = pl->usedSwap;
- this->values[1] = NAN;
+ const Machine* host = this->host;
+
+ this->total = host->totalSwap;
+ this->values[SWAP_METER_USED] = host->usedSwap;
+ // this->values[SWAP_METER_CACHE] = "pages that are both in swap and RAM, like SwapCached on linux"
+ // this->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux"
}
void Platform_setZfsArcValues(Meter* this) {
- const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) this->pl;
+ const FreeBSDMachine* fhost = (const FreeBSDMachine*) this->host;
- ZfsArcMeter_readStats(this, &(fpl->zfs));
+ ZfsArcMeter_readStats(this, &fhost->zfs);
}
void Platform_setZfsCompressedArcValues(Meter* this) {
- const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) this->pl;
+ const FreeBSDMachine* fhost = (const FreeBSDMachine*) this->host;
- ZfsCompressedArcMeter_readStats(this, &(fpl->zfs));
+ ZfsCompressedArcMeter_readStats(this, &fhost->zfs);
}
char* Platform_getProcessEnv(pid_t pid) {
@@ -287,15 +288,13 @@ char* Platform_getProcessEnv(pid_t pid) {
return env;
}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
(void)pid;
- (void)inode;
return NULL;
}
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+void Platform_getFileDescriptors(double* used, double* max) {
+ Generic_getFileDescriptors_sysctl(used, max);
}
bool Platform_getDiskIO(DiskIOData* data) {
diff --git a/freebsd/Platform.h b/freebsd/Platform.h
index c0292d9a..c358d85d 100644
--- a/freebsd/Platform.h
+++ b/freebsd/Platform.h
@@ -45,7 +45,7 @@ int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, unsigned int cpu);
@@ -59,10 +59,10 @@ void Platform_setZfsCompressedArcValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+void Platform_getFileDescriptors(double* used, double* max);
+
bool Platform_getDiskIO(DiskIOData* data);
bool Platform_getNetworkIO(NetworkIOData* data);
@@ -93,7 +93,9 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) {
Generic_gettime_monotonic(msec);
}
-static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
@@ -103,12 +105,30 @@ 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 Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
-static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; }
+static inline const char* Platform_dynamicColumnName(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; }
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
+
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
#endif
diff --git a/generic/fdstat_sysctl.c b/generic/fdstat_sysctl.c
new file mode 100644
index 00000000..353605a6
--- /dev/null
+++ b/generic/fdstat_sysctl.c
@@ -0,0 +1,80 @@
+/*
+htop - generic/fdstat_sysctl.c
+(C) 2022-2023 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/fdstat_sysctl.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <sys/types.h> // Shitty FreeBSD upstream headers
+#include <sys/sysctl.h>
+
+
+static void Generic_getFileDescriptors_sysctl_internal(
+ const char* sysctlname_maxfiles,
+ const char* sysctlname_numfiles,
+ size_t size_header,
+ size_t size_entry,
+ double* used,
+ double* max
+) {
+ *used = NAN;
+ *max = 65536;
+
+ int max_fd, open_fd;
+ size_t len;
+
+ len = sizeof(max_fd);
+ if (sysctlname_maxfiles && sysctlbyname(sysctlname_maxfiles, &max_fd, &len, NULL, 0) == 0) {
+ if (max_fd) {
+ *max = max_fd;
+ } else {
+ *max = NAN;
+ }
+ }
+
+ len = sizeof(open_fd);
+ if (sysctlname_numfiles && sysctlbyname(sysctlname_numfiles, &open_fd, &len, NULL, 0) == 0) {
+ *used = open_fd;
+ return;
+ }
+
+ // If no sysctl arc available, try to guess from the file table size at kern.file
+ // The size per entry differs per OS, thus skip if we don't know:
+ if (!size_entry)
+ return;
+
+ len = 0;
+ if (sysctlbyname("kern.file", NULL, &len, NULL, 0) < 0)
+ return;
+
+ if (len < size_header)
+ return;
+
+ *used = (len - size_header) / size_entry;
+}
+
+void Generic_getFileDescriptors_sysctl(double* used, double* max) {
+#if defined(HTOP_DARWIN)
+ Generic_getFileDescriptors_sysctl_internal(
+ "kern.maxfiles", "kern.num_files", 0, 0, used, max);
+#elif defined(HTOP_DRAGONFLYBSD)
+ Generic_getFileDescriptors_sysctl_internal(
+ "kern.maxfiles", "kern.openfiles", 0, 0, used, max);
+#elif defined(HTOP_FREEBSD)
+ Generic_getFileDescriptors_sysctl_internal(
+ "kern.maxfiles", "kern.openfiles", 0, 0, used, max);
+#elif defined(HTOP_NETBSD)
+ Generic_getFileDescriptors_sysctl_internal(
+ "kern.maxfiles", NULL, 0, sizeof(struct kinfo_file), used, max);
+#else
+#error Unknown platform: Please implement proper way to query open/max file information
+#endif
+}
diff --git a/generic/fdstat_sysctl.h b/generic/fdstat_sysctl.h
new file mode 100644
index 00000000..107fcabe
--- /dev/null
+++ b/generic/fdstat_sysctl.h
@@ -0,0 +1,13 @@
+#ifndef HEADER_fdstat_sysctl
+#define HEADER_fdstat_sysctl
+/*
+htop - generic/fdstat_sysctl.h
+(C) 2022-2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+void Generic_getFileDescriptors_sysctl(double* used, double* max);
+
+#endif
diff --git a/generic/gettime.c b/generic/gettime.c
index b7c4885e..209f5232 100644
--- a/generic/gettime.c
+++ b/generic/gettime.c
@@ -4,6 +4,7 @@ htop - generic/gettime.c
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"
diff --git a/generic/hostname.c b/generic/hostname.c
index 69a41468..e3275828 100644
--- a/generic/hostname.c
+++ b/generic/hostname.c
@@ -4,6 +4,7 @@ htop - generic/hostname.c
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"
diff --git a/generic/uname.c b/generic/uname.c
index 2a734dc1..d8293b3a 100644
--- a/generic/uname.c
+++ b/generic/uname.c
@@ -4,6 +4,7 @@ htop - generic/uname.c
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"
@@ -25,8 +26,8 @@ in the source distribution for its full text.
#endif
static void parseOSRelease(char* buffer, size_t bufferLen) {
- FILE* stream = fopen(OSRELEASEFILE, "r");
- if (!stream) {
+ FILE* fp = fopen(OSRELEASEFILE, "r");
+ if (!fp) {
xSnprintf(buffer, bufferLen, "No OS Release");
return;
}
@@ -34,14 +35,14 @@ static void parseOSRelease(char* buffer, size_t bufferLen) {
char name[64] = {'\0'};
char version[64] = {'\0'};
char lineBuffer[256];
- while (fgets(lineBuffer, sizeof(lineBuffer), stream)) {
+ while (fgets(lineBuffer, sizeof(lineBuffer), fp)) {
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);
+ fclose(fp);
return;
}
if (String_startsWith(lineBuffer, "NAME=\"")) {
@@ -61,7 +62,7 @@ static void parseOSRelease(char* buffer, size_t bufferLen) {
continue;
}
}
- fclose(stream);
+ fclose(fp);
snprintf(buffer, bufferLen, "%s%s%s", name[0] ? name : "", name[0] && version[0] ? " " : "", version);
}
diff --git a/htop.1.in b/htop.1.in
index c81c8192..ad7a4ba3 100644
--- a/htop.1.in
+++ b/htop.1.in
@@ -1,11 +1,11 @@
-.TH "HTOP" "1" "2022" "@PACKAGE_STRING@" "User Commands"
+.TH "HTOP" "1" "2024" "@PACKAGE_STRING@" "User Commands"
.SH "NAME"
htop, pcp-htop \- interactive process viewer
.SH "SYNOPSIS"
.B htop
.RB [ \-dCFhpustvH ]
.br
-.B pcp\ htop
+.B pcp-htop
.RB [ \-dCFhpustvH ]
.RB [ \-\-host/-h\ host ]
.SH "DESCRIPTION"
@@ -258,6 +258,10 @@ Hide user threads: on systems that represent them differently than ordinary
processes (such as recent NPTL-based systems), this can hide threads from
userspace processes in the process list. (This is a toggle key.)
.TP
+.B O
+Hide containerized processes: prevent processes running in a container
+from being displayed in the process list. (This is a toggle key.)
+.TP
.B p
Show full paths to running programs, where applicable. (This is a toggle key.)
.TP
@@ -327,7 +331,8 @@ The process ID.
.TP
.B STATE (S)
The state of the process:
- \fBS\fR for sleeping (idle)
+ \fBS\fR for sleeping
+ \fBI\fR for idle (longer inactivity than sleeping on platforms that distinguish)
\fBR\fR for running
\fBD\fR for disk sleep (uninterruptible)
\fBZ\fR for zombie (waiting for parent to read its exit status)
@@ -448,6 +453,11 @@ resident memory size, see M_RESIDENT above).
.B USER
The username of the process owner, or the user ID if the name can't be
determined.
+
+On Linux the username is highlighted if the process has elevated privileges,
+i.e. if it has been started from binaries with file capabilities set or
+retained Linux capabilities, via the ambient set, after switching from the
+root user.
.TP
.B TIME (TIME+)
The time, measured in clock ticks that the process has spent in user and system
@@ -592,7 +602,7 @@ By default
.B htop
reads its configuration from the XDG-compliant path
.IR ~/.config/htop/htoprc .
-The configuration file is overwritten by
+The configuration file is overwritten upon clean exit by
.BR htop 's
in-program Setup configuration, so it should not be hand-edited.
If no user configuration exists
@@ -634,15 +644,24 @@ can also be displayed by
if the
.BR pmdaopenmetrics (1)
component is configured.
+.LP
+The configuration for both
+.B htop
+and
+.B pcp-htop
+is only saved when a clean exit is performed. Sending any signal will cause
+.I all configuration changes to be lost.
.SH "MEMORY SIZES"
Memory sizes in
.B htop
are displayed in a human-readable form.
-Sizes are printed in powers of 1024. (e.g., 1023M = 1072693248 Bytes)
+Sizes are printed in powers of 1024 using binary IEC units.
+If no suffix is shown the units are implicitly K as in KiB (kibibyte, 1 KiB = 1024 bytes).
.LP
The decision to use this convention was made in order to conserve screen
space and make memory size representations consistent throughout
-.BR htop .
+.B htop
+as allocations are granular to full memory pages (4 KiB for most platforms).
.SH "SEE ALSO"
.BR proc (5),
.BR top (1),
@@ -668,7 +687,7 @@ communities, and forms part of the Performance Co-Pilot suite of tools.
.SH "COPYRIGHT"
Copyright \(co 2004-2019 Hisham Muhammad.
.br
-Copyright \(co 2020-2022 htop dev team.
+Copyright \(co 2020-2024 htop dev team.
.LP
License GPLv2+: GNU General Public License version 2 or, at your option, any later version.
.LP
diff --git a/htop.c b/htop.c
index 6b9ea48a..71557938 100644
--- a/htop.c
+++ b/htop.c
@@ -11,6 +11,8 @@ in the source distribution for its full text.
#include "CommandLine.h"
+const char* program = PACKAGE;
+
int main(int argc, char** argv) {
- return CommandLine_run(PACKAGE, argc, argv);
+ return CommandLine_run(argc, argv);
}
diff --git a/htop.png b/htop.png
index 90020ad0..7809d227 100644
--- a/htop.png
+++ b/htop.png
Binary files differ
diff --git a/iwyu/htop.imp b/iwyu/htop.imp
index 1416d743..e2fc72b5 100644
--- a/iwyu/htop.imp
+++ b/iwyu/htop.imp
@@ -4,6 +4,13 @@
{ include: ["<ncurses/curses.h>", "private", "\"ProvideCurses.h\"", "public"] },
{ include: ["<ncurses/ncurses.h>", "private", "\"ProvideCurses.h\"", "public"] },
+ { include: ["<term.h>", "private", "\"ProvideTerm.h\"", "public"] },
+ { include: ["<ncurses/term.h>", "private", "\"ProvideTerm.h\"", "public"] },
+ { include: ["<ncursesw/term.h>", "private", "\"ProvideTerm.h\"", "public"] },
+
+ { include: ["<libunwind-x86_64.h>", "private", "<libunwind.h>", "public"] },
+ { include: ["\"ibunwind-x86_64.h\"", "private", "<libunwind.h>", "public"] },
+
{ include: ["<bits/types/struct_tm.h>", "private", "<time.h>", "public"] },
{ include: ["<bits/getopt_core.h>", "private", "<unistd.h>", "public"] },
@@ -21,4 +28,6 @@
{ include: ["<linux/capability.h>", "private", "<sys/capability.h>", "public"] },
{ include: ["<bits/mman-shared.h>", "private", "<sys/mman.h>", "public"] },
+
+ { include: ["<bits/types/struct_sched_param.h>", "private", "<sched.h>", "public"] },
]
diff --git a/iwyu/run_iwyu.sh b/iwyu/run_iwyu.sh
index 6139ebec..8456240a 100755
--- a/iwyu/run_iwyu.sh
+++ b/iwyu/run_iwyu.sh
@@ -10,5 +10,6 @@ IWYU=${IWYU:-iwyu}
cd "$SOURCEDIR" || exit
+./configure CC=clang CXX=clang++ --enable-silent-rules
make clean
make -k -s CC="$IWYU" CFLAGS="-Xiwyu --no_comments -Xiwyu --no_fwd_decl -Xiwyu --mapping_file='$SCRIPTDIR/htop.imp' $PKG_NL3"
diff --git a/linux/CGroupUtils.c b/linux/CGroupUtils.c
index 22cce91b..f352b8e2 100644
--- a/linux/CGroupUtils.c
+++ b/linux/CGroupUtils.c
@@ -1,17 +1,48 @@
/*
-htop - CGroupUtils.h
+htop - CGroupUtils.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 "linux/CGroupUtils.h"
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "Macros.h"
#include "XUtils.h"
+static const char* str_slice_suffix = ".slice";
+static const char* str_system_slice = "system.slice";
+static const char* str_user_slice = "user.slice";
+static const char* str_machine_slice = "machine.slice";
+static const char* str_user_slice_prefix = "/user-";
+static const char* str_system_slice_prefix = "/system-";
+
+static const char* str_lxc_monitor_legacy = "lxc.monitor";
+static const char* str_lxc_payload_legacy = "lxc.payload";
+static const char* str_lxc_monitor_prefix = "lxc.monitor.";
+static const char* str_lxc_payload_prefix = "lxc.payload.";
+
+static const char* str_nspawn_scope_prefix = "machine-";
+static const char* str_nspawn_monitor_label = "/supervisor";
+static const char* str_nspawn_payload_label = "/payload";
+
+static const char* str_snap_scope_prefix = "snap.";
+static const char* str_pod_scope_prefix = "libpod-";
+static const char* str_docker_scope_prefix = "docker-";
+
+static const char* str_service_suffix = ".service";
+static const char* str_scope_suffix = ".scope";
+
typedef struct StrBuf_state {
- char *buf;
+ char* buf;
size_t size;
size_t pos;
} StrBuf_state;
@@ -60,28 +91,7 @@ static bool Label_checkSuffix(const char* labelStart, size_t labelLen, const cha
return labelLen > strlen(expected) && String_startsWith(labelStart + labelLen - strlen(expected), expected);
}
-static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrBuf_putc_t w) {
- const char* str_slice_suffix = ".slice";
- const char* str_system_slice = "system.slice";
- const char* str_user_slice = "user.slice";
- const char* str_machine_slice = "machine.slice";
- const char* str_user_slice_prefix = "/user-";
- const char* str_system_slice_prefix = "/system-";
-
- const char* str_lxc_monitor_legacy = "lxc.monitor";
- const char* str_lxc_payload_legacy = "lxc.payload";
- const char* str_lxc_monitor_prefix = "lxc.monitor.";
- const char* str_lxc_payload_prefix = "lxc.payload.";
-
- const char* str_nspawn_scope_prefix = "machine-";
- const char* str_nspawn_monitor_label = "/supervisor";
- const char* str_nspawn_payload_label = "/payload";
-
- const char* str_snap_scope_prefix = "snap.";
-
- const char* str_service_suffix = ".service";
- const char* str_scope_suffix = ".scope";
-
+static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) {
while (*cgroup) {
if ('/' == *cgroup) {
while ('/' == *cgroup)
@@ -94,7 +104,7 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
}
const char* labelStart = cgroup;
- const char* nextSlash = strchrnul(labelStart, '/');
+ const char* nextSlash = String_strchrnul(labelStart, '/');
const size_t labelLen = nextSlash - labelStart;
if (Label_checkEqual(labelStart, labelLen, str_system_slice)) {
@@ -104,7 +114,7 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
return false;
if (String_startsWith(cgroup, str_system_slice_prefix)) {
- cgroup = strchrnul(cgroup + 1, '/');
+ cgroup = String_strchrnul(cgroup + 1, '/');
continue;
}
@@ -129,7 +139,7 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
if (!String_startsWith(cgroup, str_user_slice_prefix))
continue;
- const char* userSliceSlash = strchrnul(cgroup + strlen(str_user_slice_prefix), '/');
+ const char* userSliceSlash = String_strchrnul(cgroup + strlen(str_user_slice_prefix), '/');
const char* sliceSpec = userSliceSlash - strlen(str_slice_suffix);
if (!String_startsWith(sliceSpec, str_slice_suffix))
@@ -212,7 +222,7 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
while (*labelStart == '/')
labelStart++;
- nextSlash = strchrnul(labelStart, '/');
+ nextSlash = String_strchrnul(labelStart, '/');
if (nextSlash - labelStart > 0) {
if (!StrBuf_putsz(s, w, isMonitor ? "[LXC:" : "[lxc:"))
return false;
@@ -237,7 +247,7 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
if (String_startsWith(cgroup, "user@")) {
cgroup = nextSlash;
- while(*cgroup == '/')
+ while (*cgroup == '/')
cgroup++;
continue;
@@ -275,8 +285,8 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
cgroup += strlen(str_nspawn_payload_label);
continue;
- } else if(Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) {
- const char* nextDot = strchrnul(labelStart + strlen(str_snap_scope_prefix), '.');
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_snap_scope_prefix), '.');
if (!StrBuf_putsz(s, w, "!snap:"))
return false;
@@ -291,6 +301,40 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
cgroup = nextSlash;
continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "!pod:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix),
+ MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12)))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "!docker:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix),
+ MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12)))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
}
if (!w(s, '!'))
@@ -316,7 +360,7 @@ static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrB
return true;
}
-char* CGroup_filterName(const char *cgroup) {
+char* CGroup_filterName(const char* cgroup) {
StrBuf_state s = {
.buf = NULL,
.size = 0,
@@ -339,3 +383,150 @@ char* CGroup_filterName(const char *cgroup) {
s.buf[s.size] = '\0';
return s.buf;
}
+
+static bool CGroup_filterContainer_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) {
+ while (*cgroup) {
+ if ('/' == *cgroup) {
+ while ('/' == *cgroup)
+ cgroup++;
+
+ continue;
+ }
+
+ const char* labelStart = cgroup;
+ const char* nextSlash = String_strchrnul(labelStart, '/');
+ const size_t labelLen = nextSlash - labelStart;
+
+ if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) {
+ const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix);
+
+ if (!StrBuf_putsz(s, w, "/lxc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ // LXC legacy cgroup naming
+ if (Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) {
+ labelStart = nextSlash;
+ while (*labelStart == '/')
+ labelStart++;
+
+ nextSlash = String_strchrnul(labelStart, '/');
+ if (nextSlash - labelStart > 0) {
+ if (!StrBuf_putsz(s, w, "/lxc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart))
+ return false;
+
+ cgroup = nextSlash;
+ continue;
+ }
+
+ labelStart = cgroup;
+ nextSlash = labelStart + labelLen;
+ }
+
+ if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) {
+ const size_t scopeNameLen = labelLen - strlen(str_scope_suffix);
+
+ if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) {
+ const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix);
+
+ const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label);
+
+ if (!is_monitor) {
+ if (!StrBuf_putsz(s, w, "/snc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen))
+ return false;
+ }
+
+ cgroup = nextSlash;
+ if (String_startsWith(nextSlash, str_nspawn_monitor_label))
+ cgroup += strlen(str_nspawn_monitor_label);
+ else if (String_startsWith(nextSlash, str_nspawn_payload_label))
+ cgroup += strlen(str_nspawn_payload_label);
+
+ continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "/pod:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix),
+ MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12)))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "!docker:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix),
+ MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12)))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ cgroup = nextSlash;
+ }
+
+ return true;
+}
+
+char* CGroup_filterContainer(const char* cgroup) {
+ StrBuf_state s = {
+ .buf = NULL,
+ .size = 0,
+ .pos = 0,
+ };
+
+ if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_count)) {
+ return NULL;
+ }
+
+ if (!s.pos) {
+ return xStrdup("/");
+ }
+
+ s.buf = xCalloc(s.pos + 1, sizeof(char));
+ s.size = s.pos;
+ s.pos = 0;
+
+ if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_write)) {
+ free(s.buf);
+ return NULL;
+ }
+
+ s.buf[s.size] = '\0';
+ return s.buf;
+}
diff --git a/linux/CGroupUtils.h b/linux/CGroupUtils.h
index db2df7f4..972a15bb 100644
--- a/linux/CGroupUtils.h
+++ b/linux/CGroupUtils.h
@@ -7,10 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include <stdbool.h>
-#include <stddef.h>
-
-char* CGroup_filterName(const char *cgroup);
+char* CGroup_filterName(const char* cgroup);
+char* CGroup_filterContainer(const char* cgroup);
#endif /* HEADER_CGroupUtils */
diff --git a/linux/GPU.c b/linux/GPU.c
new file mode 100644
index 00000000..a4af9207
--- /dev/null
+++ b/linux/GPU.c
@@ -0,0 +1,246 @@
+/*
+htop - GPU.c
+(C) 2023 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 "linux/GPU.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include "XUtils.h"
+
+#include "linux/LinuxMachine.h"
+
+
+typedef unsigned long long int ClientID;
+#define INVALID_CLIENT_ID ((ClientID)-1)
+
+
+typedef struct ClientInfo_ {
+ char* pdev;
+ ClientID id;
+ struct ClientInfo_* next;
+} ClientInfo;
+
+enum section_state {
+ SECST_UNKNOWN,
+ SECST_DUPLICATE,
+ SECST_NEW,
+};
+
+static bool is_duplicate_client(const ClientInfo* parsed, ClientID id, const char* pdev) {
+ for (; parsed; parsed = parsed->next) {
+ if (id == parsed->id && String_eq_nullable(pdev, parsed->pdev)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void update_machine_gpu(LinuxProcessTable* lpt, unsigned long long int time, const char* engine, size_t engine_len) {
+ Machine* host = lpt->super.super.host;
+ LinuxMachine* lhost = (LinuxMachine*) host;
+ GPUEngineData** engineData = &lhost->gpuEngineData;
+
+ while (*engineData) {
+ if (strncmp((*engineData)->key, engine, engine_len) == 0 && (*engineData)->key[engine_len] == '\0')
+ break;
+
+ engineData = &((*engineData)->next);
+ }
+
+ if (!*engineData) {
+ GPUEngineData* newData = xMalloc(sizeof(*newData));
+ *newData = (GPUEngineData) {
+ .prevTime = 0,
+ .curTime = 0,
+ .key = xStrndup(engine, engine_len),
+ .next = NULL,
+ };
+
+ *engineData = newData;
+ }
+
+ (*engineData)->curTime += time;
+ lhost->curGpuTime += time;
+}
+
+/*
+ * Documentation reference:
+ * https://www.kernel.org/doc/html/latest/gpu/drm-usage-stats.html
+ */
+void GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t procFd) {
+ const Machine* host = lp->super.super.host;
+ int fdinfoFd = -1;
+ DIR* fdinfoDir = NULL;
+ ClientInfo* parsed_ids = NULL;
+ unsigned long long int new_gpu_time = 0;
+
+ /* check only if active in last check or last scan was more than 5s ago */
+ if (lp->gpu_activityMs != 0 && host->monotonicMs - lp->gpu_activityMs < 5000) {
+ lp->gpu_percent = 0.0F;
+ return;
+ }
+ lp->gpu_activityMs = host->monotonicMs;
+
+ fdinfoFd = Compat_openat(procFd, "fdinfo", O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC);
+ if (fdinfoFd == -1)
+ goto out;
+
+ fdinfoDir = fdopendir(fdinfoFd);
+ if (!fdinfoDir)
+ goto out;
+ fdinfoFd = -1;
+
+#ifndef HAVE_OPENAT
+ char fdinfoPathBuf[32];
+ xSnprintf(fdinfoPathBuf, sizeof(fdinfoPathBuf), PROCDIR "/%u/fdinfo", Process_getPid(&lp->super));
+#endif
+
+ while (true) {
+ char* pdev = NULL;
+ ClientID client_id = INVALID_CLIENT_ID;
+ enum section_state sstate = SECST_UNKNOWN;
+
+ const struct dirent* entry = readdir(fdinfoDir);
+ if (!entry)
+ break;
+ const char* ename = entry->d_name;
+
+ if (ename[0] == '.' && (ename[1] == '\0' || (ename[1] == '.' && ename[2] == '\0')))
+ continue;
+
+ char buffer[4096];
+#ifdef HAVE_OPENAT
+ ssize_t ret = xReadfileat(dirfd(fdinfoDir), ename, buffer, sizeof(buffer));
+#else
+ ssize_t ret = xReadfileat(fdinfoPathBuf, ename, buffer, sizeof(buffer));
+#endif
+ /* eventfd information can be huge */
+ if (ret <= 0 || (size_t)ret >= sizeof(buffer) - 1)
+ continue;
+
+ char* buf = buffer;
+ const char* line;
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ if (!String_startsWith(line, "drm-"))
+ continue;
+ line += strlen("drm-");
+
+ if (line[0] == 'c' && String_startsWith(line, "client-id:")) {
+ if (sstate == SECST_NEW) {
+ assert(client_id != INVALID_CLIENT_ID);
+
+ ClientInfo* new = xMalloc(sizeof(*new));
+ *new = (ClientInfo) {
+ .id = client_id,
+ .pdev = pdev,
+ .next = parsed_ids,
+ };
+ pdev = NULL;
+
+ parsed_ids = new;
+ }
+
+ sstate = SECST_UNKNOWN;
+
+ char *endptr;
+ errno = 0;
+ client_id = strtoull(line + strlen("client-id:"), &endptr, 10);
+ if (errno || *endptr != '\0')
+ client_id = INVALID_CLIENT_ID;
+ } else if (line[0] == 'p' && String_startsWith(line, "pdev:")) {
+ const char* p = line + strlen("pdev:");
+
+ while (isspace((unsigned char)*p))
+ p++;
+
+ assert(!pdev || String_eq(pdev, p));
+ if (!pdev)
+ pdev = xStrdup(p);
+ } else if (line[0] == 'e' && String_startsWith(line, "engine-")) {
+ if (sstate == SECST_DUPLICATE)
+ continue;
+
+ const char* engineStart = line + strlen("engine-");
+
+ if (String_startsWith(engineStart, "capacity-"))
+ continue;
+
+ const char* delim = strchr(line, ':');
+
+ char* endptr;
+ errno = 0;
+ unsigned long long int value = strtoull(delim + 1, &endptr, 10);
+ if (errno == 0 && String_startsWith(endptr, " ns")) {
+ if (sstate == SECST_UNKNOWN) {
+ if (client_id != INVALID_CLIENT_ID && !is_duplicate_client(parsed_ids, client_id, pdev))
+ sstate = SECST_NEW;
+ else
+ sstate = SECST_DUPLICATE;
+ }
+
+ if (sstate == SECST_NEW) {
+ new_gpu_time += value;
+ update_machine_gpu(lpt, value, engineStart, delim - engineStart);
+ }
+ }
+ }
+ } /* finished parsing lines */
+
+ if (sstate == SECST_NEW) {
+ assert(client_id != INVALID_CLIENT_ID);
+
+ ClientInfo* new = xMalloc(sizeof(*new));
+ *new = (ClientInfo) {
+ .id = client_id,
+ .pdev = pdev,
+ .next = parsed_ids,
+ };
+ pdev = NULL;
+
+ parsed_ids = new;
+ }
+
+ free(pdev);
+ } /* finished parsing fdinfo entries */
+
+ if (new_gpu_time > 0) {
+ unsigned long long int gputimeDelta;
+ uint64_t monotonicTimeDelta;
+
+ Row_updateFieldWidth(GPU_TIME, ceil(log10(new_gpu_time)));
+
+ gputimeDelta = saturatingSub(new_gpu_time, lp->gpu_time);
+ monotonicTimeDelta = host->monotonicMs - host->prevMonotonicMs;
+ lp->gpu_percent = 100.0F * gputimeDelta / (1000 * 1000) / monotonicTimeDelta;
+
+ lp->gpu_activityMs = 0;
+ } else
+ lp->gpu_percent = 0.0F;
+
+out:
+
+ lp->gpu_time = new_gpu_time;
+
+ while (parsed_ids) {
+ ClientInfo* next = parsed_ids->next;
+ free(parsed_ids->pdev);
+ free(parsed_ids);
+ parsed_ids = next;
+ }
+
+ if (fdinfoDir)
+ closedir(fdinfoDir);
+ if (fdinfoFd != -1)
+ close(fdinfoFd);
+}
diff --git a/linux/GPU.h b/linux/GPU.h
new file mode 100644
index 00000000..15fb5c75
--- /dev/null
+++ b/linux/GPU.h
@@ -0,0 +1,17 @@
+#ifndef HEADER_GPU
+#define HEADER_GPU
+/*
+htop - GPU.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Compat.h"
+#include "linux/LinuxProcess.h"
+#include "linux/LinuxProcessTable.h"
+
+
+void GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t procFd);
+
+#endif /* HEADER_GPU */
diff --git a/linux/GPUMeter.c b/linux/GPUMeter.c
new file mode 100644
index 00000000..fbd32ea8
--- /dev/null
+++ b/linux/GPUMeter.c
@@ -0,0 +1,179 @@
+/*
+htop - GPUMeter.c
+(C) 2023 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 "linux/GPUMeter.h"
+
+#include "CRT.h"
+#include "RichString.h"
+#include "linux/LinuxMachine.h"
+
+
+static size_t activeMeters;
+
+bool GPUMeter_active(void) {
+ return activeMeters > 0;
+}
+
+struct EngineData {
+ const char* key; /* owned by LinuxMachine */
+ unsigned long long int timeDiff;
+};
+
+static struct EngineData GPUMeter_engineData[4];
+static unsigned long long int prevResidueTime, curResidueTime;
+static double totalUsage;
+static unsigned long long int totalGPUTimeDiff;
+
+static const int GPUMeter_attributes[] = {
+ GPU_ENGINE_1,
+ GPU_ENGINE_2,
+ GPU_ENGINE_3,
+ GPU_ENGINE_4,
+ GPU_RESIDUE,
+};
+
+static int humanTimeUnit(char* buffer, size_t size, unsigned long long int value) {
+
+ if (value < 1000)
+ return xSnprintf(buffer, size, "%3lluns", value);
+
+ if (value < 10000)
+ return xSnprintf(buffer, size, "%1llu.%1lluus", value / 1000, (value % 1000) / 100);
+
+ value /= 1000;
+
+ if (value < 1000)
+ return xSnprintf(buffer, size, "%3lluus", value);
+
+ if (value < 10000)
+ return xSnprintf(buffer, size, "%1llu.%1llums", value / 1000, (value % 1000) / 100);
+
+ value /= 1000;
+
+ if (value < 1000)
+ return xSnprintf(buffer, size, "%3llums", value);
+
+ if (value < 10000)
+ return xSnprintf(buffer, size, "%1llu.%1llus", value / 1000, (value % 1000) / 100);
+
+ value /= 1000;
+
+ if (value < 600)
+ return xSnprintf(buffer, size, "%3llus", value);
+
+ value /= 60;
+
+ if (value < 600)
+ return xSnprintf(buffer, size, "%3llum", value);
+
+ value /= 60;
+
+ if (value < 96)
+ return xSnprintf(buffer, size, "%3lluh", value);
+
+ value /= 24;
+
+ return xSnprintf(buffer, size, "%3llud", value);
+}
+
+static void GPUMeter_updateValues(Meter* this) {
+ const Machine* host = this->host;
+ const LinuxMachine* lhost = (const LinuxMachine*) host;
+ const GPUEngineData* gpuEngineData;
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
+ int written;
+ unsigned int i;
+ uint64_t monotonictimeDelta;
+
+ assert(ARRAYSIZE(GPUMeter_engineData) + 1 == ARRAYSIZE(GPUMeter_attributes));
+
+ totalGPUTimeDiff = saturatingSub(lhost->curGpuTime, lhost->prevGpuTime);
+ monotonictimeDelta = host->monotonicMs - host->prevMonotonicMs;
+
+ prevResidueTime = curResidueTime;
+ curResidueTime = lhost->curGpuTime;
+
+ for (gpuEngineData = lhost->gpuEngineData, i = 0; gpuEngineData && i < ARRAYSIZE(GPUMeter_engineData); gpuEngineData = gpuEngineData->next, i++) {
+ GPUMeter_engineData[i].key = gpuEngineData->key;
+ GPUMeter_engineData[i].timeDiff = saturatingSub(gpuEngineData->curTime, gpuEngineData->prevTime);
+
+ curResidueTime = saturatingSub(curResidueTime, gpuEngineData->curTime);
+
+ this->values[i] = 100.0 * GPUMeter_engineData[i].timeDiff / (1000 * 1000) / monotonictimeDelta;
+ }
+
+ this->values[ARRAYSIZE(GPUMeter_engineData)] = 100.0 * saturatingSub(curResidueTime, prevResidueTime) / (1000 * 1000) / monotonictimeDelta;
+
+ totalUsage = 100.0 * totalGPUTimeDiff / (1000 * 1000) / monotonictimeDelta;
+ written = snprintf(buffer, size, "%.1f", totalUsage);
+ METER_BUFFER_CHECK(buffer, size, written);
+
+ METER_BUFFER_APPEND_CHR(buffer, size, '%');
+}
+
+static void GPUMeter_display(const Object* cast, RichString* out) {
+ char buffer[50];
+ int written;
+ const Meter* this = (const Meter*)cast;
+ unsigned int i;
+
+ RichString_writeAscii(out, CRT_colors[METER_TEXT], ":");
+ written = xSnprintf(buffer, sizeof(buffer), "%4.1f", totalUsage);
+ RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written);
+ RichString_appendnAscii(out, CRT_colors[METER_TEXT], "%(", 2);
+ written = humanTimeUnit(buffer, sizeof(buffer), totalGPUTimeDiff);
+ RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written);
+ RichString_appendnAscii(out, CRT_colors[METER_TEXT], ")", 1);
+
+ for (i = 0; i < ARRAYSIZE(GPUMeter_engineData); i++) {
+ if (!GPUMeter_engineData[i].key)
+ break;
+
+ RichString_appendnAscii(out, CRT_colors[METER_TEXT], " ", 1);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], GPUMeter_engineData[i].key);
+ RichString_appendnAscii(out, CRT_colors[METER_TEXT], ":", 1);
+ if (isNonnegative(this->values[i]))
+ written = xSnprintf(buffer, sizeof(buffer), "%4.1f", this->values[i]);
+ else
+ written = xSnprintf(buffer, sizeof(buffer), " N/A");
+ RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written);
+ RichString_appendnAscii(out, CRT_colors[METER_TEXT], "%(", 2);
+ written = humanTimeUnit(buffer, sizeof(buffer), GPUMeter_engineData[i].timeDiff);
+ RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written);
+ RichString_appendnAscii(out, CRT_colors[METER_TEXT], ")", 1);
+ }
+}
+
+static void GPUMeter_init(Meter* this ATTR_UNUSED) {
+ activeMeters++;
+}
+
+static void GPUMeter_done(Meter* this ATTR_UNUSED) {
+ assert(activeMeters > 0);
+ activeMeters--;
+}
+
+const MeterClass GPUMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = GPUMeter_display,
+ },
+ .init = GPUMeter_init,
+ .done = GPUMeter_done,
+ .updateValues = GPUMeter_updateValues,
+ .defaultMode = BAR_METERMODE,
+ .maxItems = ARRAYSIZE(GPUMeter_engineData) + 1,
+ .total = 100.0,
+ .attributes = GPUMeter_attributes,
+ .name = "GPU",
+ .uiName = "GPU usage",
+ .caption = "GPU"
+};
diff --git a/linux/GPUMeter.h b/linux/GPUMeter.h
new file mode 100644
index 00000000..a770ec77
--- /dev/null
+++ b/linux/GPUMeter.h
@@ -0,0 +1,19 @@
+#ifndef HEADER_GPUMeter
+#define HEADER_GPUMeter
+/*
+htop - GPUMeter.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "Meter.h"
+
+
+extern const MeterClass GPUMeter_class;
+
+bool GPUMeter_active(void);
+
+#endif /* HEADER_GPUMeter */
diff --git a/linux/HugePageMeter.c b/linux/HugePageMeter.c
index 1efde2f2..058ab4b6 100644
--- a/linux/HugePageMeter.c
+++ b/linux/HugePageMeter.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 "linux/HugePageMeter.h"
#include <assert.h>
@@ -12,11 +14,11 @@ in the source distribution for its full text.
#include <stddef.h>
#include "CRT.h"
+#include "Machine.h"
#include "Macros.h"
#include "Object.h"
-#include "ProcessList.h"
#include "RichString.h"
-#include "linux/LinuxProcessList.h"
+#include "linux/LinuxMachine.h"
static const char* HugePageMeter_active_labels[4] = { NULL, NULL, NULL, NULL };
@@ -43,8 +45,8 @@ static void HugePageMeter_updateValues(Meter* this) {
memory_t usedTotal = 0;
unsigned nextUsed = 0;
- const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl;
- this->total = lpl->totalHugePageMem;
+ const LinuxMachine* host = (const LinuxMachine*) this->host;
+ this->total = host->totalHugePageMem;
this->values[0] = 0;
HugePageMeter_active_labels[0] = " used:";
for (unsigned i = 1; i < ARRAYSIZE(HugePageMeter_active_labels); i++) {
@@ -52,7 +54,7 @@ static void HugePageMeter_updateValues(Meter* this) {
HugePageMeter_active_labels[i] = NULL;
}
for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {
- memory_t value = lpl->usedHugePageMem[i];
+ memory_t value = host->usedHugePageMem[i];
if (value != MEMORY_MAX) {
this->values[nextUsed] = value;
usedTotal += value;
@@ -80,7 +82,7 @@ static void HugePageMeter_display(const Object* cast, RichString* out) {
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
for (unsigned i = 0; i < ARRAYSIZE(HugePageMeter_active_labels); i++) {
- if (isnan(this->values[i])) {
+ if (!HugePageMeter_active_labels[i]) {
break;
}
RichString_appendAscii(out, CRT_colors[METER_TEXT], HugePageMeter_active_labels[i]);
diff --git a/linux/IOPriorityPanel.c b/linux/IOPriorityPanel.c
index 3e91bc4d..8d0ef7b5 100644
--- a/linux/IOPriorityPanel.c
+++ b/linux/IOPriorityPanel.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 "linux/IOPriorityPanel.h"
#include <stdbool.h>
diff --git a/linux/LibNl.c b/linux/LibNl.c
new file mode 100644
index 00000000..ba1359f2
--- /dev/null
+++ b/linux/LibNl.c
@@ -0,0 +1,239 @@
+/*
+htop - linux/LibNl.c
+(C) 2024 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
+
+#ifndef HAVE_DELAYACCT
+#error Compiling this file requires HAVE_DELAYACCT
+#endif
+
+#include "linux/LibNl.h"
+
+#include <dlfcn.h>
+
+#include <linux/netlink.h>
+#include <linux/taskstats.h>
+
+#include <netlink/attr.h>
+#include <netlink/handlers.h>
+#include <netlink/msg.h>
+
+
+static void* libnlHandle;
+static void* libnlGenlHandle;
+
+static void (*sym_nl_close)(struct nl_sock*);
+static int (*sym_nl_connect)(struct nl_sock*, int);
+static int (*sym_nl_recvmsgs_default)(struct nl_sock*);
+static int (*sym_nl_send_sync)(struct nl_sock*, struct nl_msg*);
+static struct nl_sock* (*sym_nl_socket_alloc)(void);
+static void (*sym_nl_socket_free)(struct nl_sock*);
+static int (*sym_nl_socket_modify_cb)(struct nl_sock*, enum nl_cb_type, enum nl_cb_kind, nl_recvmsg_msg_cb_t, void*);
+static void* (*sym_nla_data)(const struct nlattr*);
+static struct nlattr* (*sym_nla_next)(const struct nlattr*, int*);
+static int (*sym_nla_put_u32)(struct nl_msg*, int, uint32_t);
+static struct nl_msg* (*sym_nlmsg_alloc)(void);
+static struct nlmsghdr* (*sym_nlmsg_hdr)(struct nl_msg*);
+static void (*sym_nlmsg_free)(struct nl_msg*);
+
+static int (*sym_genl_ctrl_resolve)(struct nl_sock*, const char*);
+static int (*sym_genlmsg_parse)(struct nlmsghdr*, int, struct nlattr**, int, const struct nla_policy*);
+static void* (*sym_genlmsg_put)(struct nl_msg*, uint32_t, uint32_t, int, int, int, uint8_t, uint8_t);
+
+
+static void unload_libnl(void) {
+ sym_nl_close = NULL;
+ sym_nl_connect = NULL;
+ sym_nl_recvmsgs_default = NULL;
+ sym_nl_send_sync = NULL;
+ sym_nl_socket_alloc = NULL;
+ sym_nl_socket_free = NULL;
+ sym_nl_socket_modify_cb = NULL;
+ sym_nla_data = NULL;
+ sym_nla_next = NULL;
+ sym_nla_put_u32 = NULL;
+ sym_nlmsg_alloc = NULL;
+ sym_nlmsg_free = NULL;
+ sym_nlmsg_hdr = NULL;
+
+ sym_genl_ctrl_resolve = NULL;
+ sym_genlmsg_parse = NULL;
+ sym_genlmsg_put = NULL;
+
+ if (libnlGenlHandle) {
+ dlclose(libnlGenlHandle);
+ libnlGenlHandle = NULL;
+ }
+ if (libnlHandle) {
+ dlclose(libnlHandle);
+ libnlHandle = NULL;
+ }
+}
+
+static int load_libnl(void) {
+ if (libnlHandle && libnlGenlHandle)
+ return 0;
+
+ libnlHandle = dlopen("libnl-3.so", RTLD_LAZY);
+ if (!libnlHandle) {
+ libnlHandle = dlopen("libnl-3.so.200", RTLD_LAZY);
+ if (!libnlHandle) {
+ goto dlfailure;
+ }
+ }
+
+ libnlGenlHandle = dlopen("libnl-genl-3.so", RTLD_LAZY);
+ if (!libnlGenlHandle) {
+ libnlGenlHandle = dlopen("libnl-genl-3.so.200", RTLD_LAZY);
+ if (!libnlGenlHandle) {
+ goto dlfailure;
+ }
+ }
+
+ /* Clear any errors */
+ dlerror();
+
+ #define resolve(handle, symbolname) do { \
+ *(void **)(&sym_##symbolname) = dlsym(handle, #symbolname); \
+ if (!sym_##symbolname || dlerror() != NULL) { \
+ goto dlfailure; \
+ } \
+ } while(0)
+
+ resolve(libnlHandle, nl_close);
+ resolve(libnlHandle, nl_connect);
+ resolve(libnlHandle, nl_recvmsgs_default);
+ resolve(libnlHandle, nl_send_sync);
+ resolve(libnlHandle, nl_socket_alloc);
+ resolve(libnlHandle, nl_socket_free);
+ resolve(libnlHandle, nl_socket_modify_cb);
+ resolve(libnlHandle, nla_data);
+ resolve(libnlHandle, nla_next);
+ resolve(libnlHandle, nla_put_u32);
+ resolve(libnlHandle, nlmsg_alloc);
+ resolve(libnlHandle, nlmsg_free);
+ resolve(libnlHandle, nlmsg_hdr);
+
+ resolve(libnlGenlHandle, genl_ctrl_resolve);
+ resolve(libnlGenlHandle, genlmsg_parse);
+ resolve(libnlGenlHandle, genlmsg_put);
+
+ #undef resolve
+
+ return 0;
+
+dlfailure:
+ unload_libnl();
+ return -1;
+}
+
+static void initNetlinkSocket(LinuxProcessTable* this) {
+ if (load_libnl() < 0) {
+ return;
+ }
+
+ this->netlink_socket = sym_nl_socket_alloc();
+ if (this->netlink_socket == NULL) {
+ return;
+ }
+ if (sym_nl_connect(this->netlink_socket, NETLINK_GENERIC) < 0) {
+ return;
+ }
+ this->netlink_family = sym_genl_ctrl_resolve(this->netlink_socket, TASKSTATS_GENL_NAME);
+}
+
+void LibNl_destroyNetlinkSocket(LinuxProcessTable* this) {
+ if (this->netlink_socket) {
+ assert(libnlHandle);
+
+ sym_nl_close(this->netlink_socket);
+ sym_nl_socket_free(this->netlink_socket);
+ this->netlink_socket = NULL;
+ }
+
+ unload_libnl();
+}
+
+static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {
+ struct nlmsghdr* nlhdr;
+ struct nlattr* nlattrs[TASKSTATS_TYPE_MAX + 1];
+ const struct nlattr* nlattr;
+ struct taskstats stats;
+ int rem;
+ LinuxProcess* lp = (LinuxProcess*) linuxProcess;
+
+ nlhdr = sym_nlmsg_hdr(nlmsg);
+
+ if (sym_genlmsg_parse(nlhdr, 0, nlattrs, TASKSTATS_TYPE_MAX, NULL) < 0) {
+ return NL_SKIP;
+ }
+
+ if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) {
+ memcpy(&stats, sym_nla_data(sym_nla_next(sym_nla_data(nlattr), &rem)), sizeof(stats));
+ assert(Process_getPid(&lp->super) == (pid_t)stats.ac_pid);
+
+ // The xxx_delay_total values wrap around on overflow.
+ // (Linux Kernel "Documentation/accounting/taskstats-struct.rst")
+ unsigned long long int timeDelta = stats.ac_etime * 1000 - lp->delay_read_time;
+ #define DELTAPERC(x, y) (timeDelta ? MINIMUM((float)((x) - (y)) / timeDelta * 100.0F, 100.0F) : NAN)
+ lp->cpu_delay_percent = DELTAPERC(stats.cpu_delay_total, lp->cpu_delay_total);
+ lp->blkio_delay_percent = DELTAPERC(stats.blkio_delay_total, lp->blkio_delay_total);
+ lp->swapin_delay_percent = DELTAPERC(stats.swapin_delay_total, lp->swapin_delay_total);
+ #undef DELTAPERC
+
+ lp->swapin_delay_total = stats.swapin_delay_total;
+ lp->blkio_delay_total = stats.blkio_delay_total;
+ lp->cpu_delay_total = stats.cpu_delay_total;
+ lp->delay_read_time = stats.ac_etime * 1000;
+ }
+ return NL_OK;
+}
+
+/*
+ * Gather delay-accounting information (thread-specific data)
+ */
+void LibNl_readDelayAcctData(LinuxProcessTable* this, LinuxProcess* process) {
+ struct nl_msg* msg;
+
+ if (!this->netlink_socket) {
+ initNetlinkSocket(this);
+ if (!this->netlink_socket) {
+ goto delayacct_failure;
+ }
+ }
+
+ if (sym_nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) {
+ goto delayacct_failure;
+ }
+
+ if (! (msg = sym_nlmsg_alloc())) {
+ goto delayacct_failure;
+ }
+
+ if (! sym_genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, this->netlink_family, 0, NLM_F_REQUEST, TASKSTATS_CMD_GET, TASKSTATS_VERSION)) {
+ sym_nlmsg_free(msg);
+ }
+
+ if (sym_nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, Process_getPid(&process->super)) < 0) {
+ sym_nlmsg_free(msg);
+ }
+
+ if (sym_nl_send_sync(this->netlink_socket, msg) < 0) {
+ goto delayacct_failure;
+ }
+
+ if (sym_nl_recvmsgs_default(this->netlink_socket) < 0) {
+ goto delayacct_failure;
+ }
+
+ return;
+
+delayacct_failure:
+ process->swapin_delay_percent = NAN;
+ process->blkio_delay_percent = NAN;
+ process->cpu_delay_percent = NAN;
+}
diff --git a/linux/LibNl.h b/linux/LibNl.h
new file mode 100644
index 00000000..e89c3694
--- /dev/null
+++ b/linux/LibNl.h
@@ -0,0 +1,18 @@
+#ifndef HEADER_LibNl
+#define HEADER_LibNl
+/*
+htop - linux/LibNl.h
+(C) 2024 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "linux/LinuxProcess.h"
+#include "linux/LinuxProcessTable.h"
+
+
+void LibNl_destroyNetlinkSocket(LinuxProcessTable* this);
+
+void LibNl_readDelayAcctData(LinuxProcessTable* this, LinuxProcess* process);
+
+#endif /* HEADER_LibNl */
diff --git a/linux/LibSensors.c b/linux/LibSensors.c
index 9a27fe57..bfc5903b 100644
--- a/linux/LibSensors.c
+++ b/linux/LibSensors.c
@@ -1,6 +1,13 @@
-#include "linux/LibSensors.h"
+/*
+htop - linux/LibSensors.c
+(C) 2020-2023 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 "config.h"
+#include "linux/LibSensors.h"
#ifdef HAVE_SENSORS_SENSORS_H
@@ -9,14 +16,15 @@
#include <errno.h>
#include <limits.h>
#include <math.h>
-#include <stdlib.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+
#include <sensors/sensors.h>
#include "Macros.h"
#include "XUtils.h"
-#include "linux/LinuxProcessList.h"
+#include "linux/LinuxMachine.h"
#ifdef BUILD_STATIC
@@ -143,7 +151,8 @@ static int tempDriverPriority(const sensors_chip_name* chip) {
void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs) {
assert(existingCPUs > 0 && existingCPUs < 16384);
- double data[existingCPUs + 1];
+
+ double* data = xMallocArray(existingCPUs + 1, sizeof(double));
for (size_t i = 0; i < existingCPUs + 1; i++)
data[i] = NAN;
@@ -200,7 +209,7 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns
continue;
/* If already set, e.g. Ryzen reporting platform temperature for each die, use the bigger one */
- if (isnan(data[tempID])) {
+ if (isNaN(data[tempID])) {
data[tempID] = temp;
if (tempID > 0)
coreTempCount++;
@@ -220,8 +229,8 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns
}
/* Only package temperature - copy to all cores */
- if (coreTempCount == 0 && !isnan(data[0])) {
- for (unsigned int i = 1; i <= existingCPUs; i++)
+ if (coreTempCount == 0 && !isNaN(data[0])) {
+ for (size_t i = 1; i <= existingCPUs; i++)
data[i] = data[0];
/* No further adjustments */
@@ -229,23 +238,21 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns
}
/* No package temperature - set to max core temperature */
- if (isnan(data[0]) && coreTempCount != 0) {
- double maxTemp = NAN;
- for (unsigned int i = 1; i <= existingCPUs; i++) {
- if (isnan(data[i]))
- continue;
-
- maxTemp = MAXIMUM(maxTemp, data[i]);
+ if (coreTempCount > 0 && isNaN(data[0])) {
+ double maxTemp = -HUGE_VAL;
+ for (size_t i = 1; i <= existingCPUs; i++) {
+ if (isgreater(data[i], maxTemp)) {
+ maxTemp = data[i];
+ data[0] = data[i];
+ }
}
- data[0] = maxTemp;
-
/* Check for further adjustments */
}
/* Only temperature for core 0, maybe Ryzen - copy to all other cores */
- if (coreTempCount == 1 && !isnan(data[1])) {
- for (unsigned int i = 2; i <= existingCPUs; i++)
+ if (coreTempCount == 1 && !isNaN(data[1])) {
+ for (size_t i = 2; i <= existingCPUs; i++)
data[i] = data[1];
/* No further adjustments */
@@ -253,7 +260,7 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns
}
/* Half the temperatures, probably HT/SMT - copy to second half */
- const unsigned int delta = activeCPUs / 2;
+ const size_t delta = activeCPUs / 2;
if (coreTempCount == delta) {
memcpy(&data[delta + 1], &data[1], delta * sizeof(*data));
@@ -262,8 +269,10 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns
}
out:
- for (unsigned int i = 0; i <= existingCPUs; i++)
+ for (size_t i = 0; i <= existingCPUs; i++)
cpus[i].temperature = data[i];
+
+ free(data);
}
#endif /* HAVE_SENSORS_SENSORS_H */
diff --git a/linux/LibSensors.h b/linux/LibSensors.h
index aa899793..6f054489 100644
--- a/linux/LibSensors.h
+++ b/linux/LibSensors.h
@@ -1,7 +1,13 @@
#ifndef HEADER_LibSensors
#define HEADER_LibSensors
+/*
+htop - linux/LibSensors.h
+(C) 2020-2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
-#include "linux/LinuxProcessList.h"
+#include "linux/LinuxMachine.h"
int LibSensors_init(void);
diff --git a/linux/LinuxMachine.c b/linux/LinuxMachine.c
new file mode 100644
index 00000000..c636be15
--- /dev/null
+++ b/linux/LinuxMachine.c
@@ -0,0 +1,715 @@
+/*
+htop - LinuxMachine.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/LinuxMachine.h"
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "Compat.h"
+#include "CRT.h"
+#include "Macros.h"
+#include "ProcessTable.h"
+#include "Row.h"
+#include "Settings.h"
+#include "UsersTable.h"
+#include "XUtils.h"
+
+#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep
+
+#ifdef HAVE_SENSORS_SENSORS_H
+#include "LibSensors.h"
+#endif
+
+#ifndef O_PATH
+#define O_PATH 010000000 // declare for ancient glibc versions
+#endif
+
+/* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF
+ * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD
+ */
+static void LinuxMachine_updateCPUcount(LinuxMachine* this) {
+ unsigned int existing = 0, active = 0;
+ Machine* super = &this->super;
+
+ // Initialize the cpuData array before anything else.
+ if (!this->cpuData) {
+ this->cpuData = xCalloc(2, sizeof(CPUData));
+ this->cpuData[0].online = true; /* average is always "online" */
+ this->cpuData[1].online = true;
+ super->activeCPUs = 1;
+ super->existingCPUs = 1;
+ }
+
+ DIR* dir = opendir("/sys/devices/system/cpu");
+ if (!dir)
+ return;
+
+ unsigned int currExisting = super->existingCPUs;
+
+ const struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
+ continue;
+
+ if (!String_startsWith(entry->d_name, "cpu"))
+ continue;
+
+ char* endp;
+ unsigned long int id = strtoul(entry->d_name + 3, &endp, 10);
+ if (id == ULONG_MAX || endp == entry->d_name + 3 || *endp != '\0')
+ continue;
+
+#ifdef HAVE_OPENAT
+ int cpuDirFd = openat(dirfd(dir), entry->d_name, O_DIRECTORY | O_PATH | O_NOFOLLOW);
+ if (cpuDirFd < 0)
+ continue;
+#else
+ char cpuDirFd[4096];
+ xSnprintf(cpuDirFd, sizeof(cpuDirFd), "/sys/devices/system/cpu/%s", entry->d_name);
+#endif
+
+ existing++;
+
+ /* readdir() iterates with no specific order */
+ unsigned int max = MAXIMUM(existing, id + 1);
+ if (max > currExisting) {
+ this->cpuData = xReallocArrayZero(this->cpuData, currExisting ? (currExisting + 1) : 0, max + /* aggregate */ 1, sizeof(CPUData));
+ this->cpuData[0].online = true; /* average is always "online" */
+ currExisting = max;
+ }
+
+ char buffer[8];
+ ssize_t res = xReadfileat(cpuDirFd, "online", buffer, sizeof(buffer));
+ /* If the file "online" does not exist or on failure count as active */
+ if (res < 1 || buffer[0] != '0') {
+ active++;
+ this->cpuData[id + 1].online = true;
+ } else {
+ this->cpuData[id + 1].online = false;
+ }
+
+ Compat_openatArgClose(cpuDirFd);
+ }
+
+ closedir(dir);
+
+ // return if no CPU is found
+ if (existing < 1)
+ return;
+
+#ifdef HAVE_SENSORS_SENSORS_H
+ /* When started with offline CPUs, libsensors does not monitor those,
+ * even when they become online. */
+ if (super->existingCPUs != 0 && (active > super->activeCPUs || currExisting > super->existingCPUs))
+ LibSensors_reload();
+#endif
+
+ super->activeCPUs = active;
+ assert(existing == currExisting);
+ super->existingCPUs = currExisting;
+}
+
+static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) {
+ Machine* host = &this->super;
+ memory_t availableMem = 0;
+ memory_t freeMem = 0;
+ memory_t totalMem = 0;
+ memory_t buffersMem = 0;
+ memory_t cachedMem = 0;
+ memory_t sharedMem = 0;
+ memory_t swapTotalMem = 0;
+ memory_t swapCacheMem = 0;
+ memory_t swapFreeMem = 0;
+ memory_t sreclaimableMem = 0;
+ memory_t zswapCompMem = 0;
+ memory_t zswapOrigMem = 0;
+
+ FILE* file = fopen(PROCMEMINFOFILE, "r");
+ if (!file)
+ CRT_fatalError("Cannot open " PROCMEMINFOFILE);
+
+ char buffer[128];
+ while (fgets(buffer, sizeof(buffer), file)) {
+
+ #define tryRead(label, variable) \
+ if (String_startsWith(buffer, label)) { \
+ memory_t parsed_; \
+ if (sscanf(buffer + strlen(label), "%llu kB", &parsed_) == 1) { \
+ (variable) = parsed_; \
+ } \
+ break; \
+ } else (void) 0 /* Require a ";" after the macro use. */
+
+ switch (buffer[0]) {
+ case 'M':
+ tryRead("MemAvailable:", availableMem);
+ tryRead("MemFree:", freeMem);
+ tryRead("MemTotal:", totalMem);
+ break;
+ case 'B':
+ tryRead("Buffers:", buffersMem);
+ break;
+ case 'C':
+ tryRead("Cached:", cachedMem);
+ break;
+ case 'S':
+ switch (buffer[1]) {
+ case 'h':
+ tryRead("Shmem:", sharedMem);
+ break;
+ case 'w':
+ tryRead("SwapTotal:", swapTotalMem);
+ tryRead("SwapCached:", swapCacheMem);
+ tryRead("SwapFree:", swapFreeMem);
+ break;
+ case 'R':
+ tryRead("SReclaimable:", sreclaimableMem);
+ break;
+ }
+ break;
+ case 'Z':
+ tryRead("Zswap:", zswapCompMem);
+ tryRead("Zswapped:", zswapOrigMem);
+ break;
+ }
+
+ #undef tryRead
+ }
+
+ fclose(file);
+
+ /*
+ * Compute memory partition like procps(free)
+ * https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c
+ *
+ * Adjustments:
+ * - Shmem in part of Cached (see https://lore.kernel.org/patchwork/patch/648763/),
+ * do not show twice by subtracting from Cached and do not subtract twice from used.
+ */
+ host->totalMem = totalMem;
+ host->cachedMem = cachedMem + sreclaimableMem - sharedMem;
+ host->sharedMem = sharedMem;
+ const memory_t usedDiff = freeMem + cachedMem + sreclaimableMem + buffersMem;
+ host->usedMem = (totalMem >= usedDiff) ? totalMem - usedDiff : totalMem - freeMem;
+ host->buffersMem = buffersMem;
+ host->availableMem = availableMem != 0 ? MINIMUM(availableMem, totalMem) : freeMem;
+ host->totalSwap = swapTotalMem;
+ host->usedSwap = swapTotalMem - swapFreeMem - swapCacheMem;
+ host->cachedSwap = swapCacheMem;
+ this->zswap.usedZswapComp = zswapCompMem;
+ this->zswap.usedZswapOrig = zswapOrigMem;
+}
+
+static void LinuxMachine_scanHugePages(LinuxMachine* this) {
+ this->totalHugePageMem = 0;
+ for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {
+ this->usedHugePageMem[i] = MEMORY_MAX;
+ }
+
+ DIR* dir = opendir("/sys/kernel/mm/hugepages");
+ if (!dir)
+ return;
+
+ const struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ const char* name = entry->d_name;
+
+ /* Ignore all non-directories */
+ if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
+ continue;
+
+ if (!String_startsWith(name, "hugepages-"))
+ continue;
+
+ char* endptr;
+ unsigned long int hugePageSize = strtoul(name + strlen("hugepages-"), &endptr, 10);
+ if (!endptr || *endptr != 'k')
+ continue;
+
+ char content[64];
+ char hugePagePath[128];
+ ssize_t r;
+
+ xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/nr_hugepages", name);
+ r = xReadfile(hugePagePath, content, sizeof(content));
+ if (r <= 0)
+ continue;
+
+ memory_t total = strtoull(content, NULL, 10);
+ if (total == 0)
+ continue;
+
+ xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/free_hugepages", name);
+ r = xReadfile(hugePagePath, content, sizeof(content));
+ if (r <= 0)
+ continue;
+
+ memory_t free = strtoull(content, NULL, 10);
+
+ int shift = ffsl(hugePageSize) - 1 - (HTOP_HUGEPAGE_BASE_SHIFT - 10);
+ assert(shift >= 0 && shift < HTOP_HUGEPAGE_COUNT);
+
+ this->totalHugePageMem += total * hugePageSize;
+ this->usedHugePageMem[shift] = (total - free) * hugePageSize;
+ }
+
+ closedir(dir);
+}
+
+static void LinuxMachine_scanZramInfo(LinuxMachine* this) {
+ memory_t totalZram = 0;
+ memory_t usedZramComp = 0;
+ memory_t usedZramOrig = 0;
+
+ char mm_stat[34];
+ char disksize[34];
+
+ unsigned int i = 0;
+ for (;;) {
+ xSnprintf(mm_stat, sizeof(mm_stat), "/sys/block/zram%u/mm_stat", i);
+ xSnprintf(disksize, sizeof(disksize), "/sys/block/zram%u/disksize", i);
+ i++;
+ FILE* disksize_file = fopen(disksize, "r");
+ FILE* mm_stat_file = fopen(mm_stat, "r");
+ if (disksize_file == NULL || mm_stat_file == NULL) {
+ if (disksize_file) {
+ fclose(disksize_file);
+ }
+ if (mm_stat_file) {
+ fclose(mm_stat_file);
+ }
+ break;
+ }
+ memory_t size = 0;
+ memory_t orig_data_size = 0;
+ memory_t compr_data_size = 0;
+
+ if (1 != fscanf(disksize_file, "%llu\n", &size) ||
+ 2 != fscanf(mm_stat_file, " %llu %llu", &orig_data_size, &compr_data_size)) {
+ fclose(disksize_file);
+ fclose(mm_stat_file);
+ break;
+ }
+
+ totalZram += size;
+ usedZramComp += compr_data_size;
+ usedZramOrig += orig_data_size;
+
+ fclose(disksize_file);
+ fclose(mm_stat_file);
+ }
+
+ this->zram.totalZram = totalZram / 1024;
+ this->zram.usedZramComp = usedZramComp / 1024;
+ this->zram.usedZramOrig = usedZramOrig / 1024;
+ if (this->zram.usedZramComp > this->zram.usedZramOrig) {
+ this->zram.usedZramComp = this->zram.usedZramOrig;
+ }
+}
+
+static void LinuxMachine_scanZfsArcstats(LinuxMachine* this) {
+ memory_t dbufSize = 0;
+ memory_t dnodeSize = 0;
+ memory_t bonusSize = 0;
+
+ FILE* file = fopen(PROCARCSTATSFILE, "r");
+ if (file == NULL) {
+ this->zfs.enabled = 0;
+ return;
+ }
+ char buffer[128];
+ while (fgets(buffer, 128, file)) {
+ #define tryRead(label, variable) \
+ if (String_startsWith(buffer, label)) { \
+ sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
+ break; \
+ } else (void) 0 /* Require a ";" after the macro use. */
+ #define tryReadFlag(label, variable, flag) \
+ if (String_startsWith(buffer, label)) { \
+ (flag) = (1 == sscanf(buffer + strlen(label), " %*2u %32llu", variable)); \
+ break; \
+ } else (void) 0 /* Require a ";" after the macro use. */
+
+ switch (buffer[0]) {
+ case 'c':
+ tryRead("c_min", &this->zfs.min);
+ tryRead("c_max", &this->zfs.max);
+ tryReadFlag("compressed_size", &this->zfs.compressed, this->zfs.isCompressed);
+ break;
+ case 'u':
+ tryRead("uncompressed_size", &this->zfs.uncompressed);
+ break;
+ case 's':
+ tryRead("size", &this->zfs.size);
+ break;
+ case 'h':
+ tryRead("hdr_size", &this->zfs.header);
+ break;
+ case 'd':
+ tryRead("dbuf_size", &dbufSize);
+ tryRead("dnode_size", &dnodeSize);
+ break;
+ case 'b':
+ tryRead("bonus_size", &bonusSize);
+ break;
+ case 'a':
+ tryRead("anon_size", &this->zfs.anon);
+ break;
+ case 'm':
+ tryRead("mfu_size", &this->zfs.MFU);
+ tryRead("mru_size", &this->zfs.MRU);
+ break;
+ }
+
+ #undef tryRead
+ #undef tryReadFlag
+ }
+ fclose(file);
+
+ this->zfs.enabled = (this->zfs.size > 0 ? 1 : 0);
+ this->zfs.size /= 1024;
+ this->zfs.min /= 1024;
+ this->zfs.max /= 1024;
+ this->zfs.MFU /= 1024;
+ this->zfs.MRU /= 1024;
+ this->zfs.anon /= 1024;
+ this->zfs.header /= 1024;
+ this->zfs.other = (dbufSize + dnodeSize + bonusSize) / 1024;
+ if ( this->zfs.isCompressed ) {
+ this->zfs.compressed /= 1024;
+ this->zfs.uncompressed /= 1024;
+ }
+}
+
+static void LinuxMachine_scanCPUTime(LinuxMachine* this) {
+ const Machine* super = &this->super;
+
+ LinuxMachine_updateCPUcount(this);
+
+ FILE* file = fopen(PROCSTATFILE, "r");
+ if (!file)
+ CRT_fatalError("Cannot open " PROCSTATFILE);
+
+ // Add an extra phantom thread for a later loop
+ bool adjCpuIdProcessed[super->existingCPUs+2];
+ memset(adjCpuIdProcessed, 0, sizeof(adjCpuIdProcessed));
+
+ for (unsigned int i = 0; i <= super->existingCPUs; i++) {
+ char buffer[PROC_LINE_LENGTH + 1];
+ unsigned long long int usertime, nicetime, systemtime, idletime;
+ unsigned long long int ioWait = 0, irq = 0, softIrq = 0, steal = 0, guest = 0, guestnice = 0;
+
+ const char* ok = fgets(buffer, sizeof(buffer), file);
+ if (!ok)
+ break;
+
+ // cpu fields are sorted first
+ if (!String_startsWith(buffer, "cpu"))
+ break;
+
+ // Depending on your kernel version,
+ // 5, 7, 8 or 9 of these fields will be set.
+ // The rest will remain at zero.
+ unsigned int adjCpuId;
+ if (i == 0) {
+ (void) sscanf(buffer, "cpu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
+ adjCpuId = 0;
+ } else {
+ unsigned int cpuid;
+ (void) sscanf(buffer, "cpu%4u %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
+ adjCpuId = cpuid + 1;
+ }
+
+ if (adjCpuId > super->existingCPUs)
+ break;
+
+ // Guest time is already accounted in usertime
+ usertime -= guest;
+ nicetime -= guestnice;
+ // Fields existing on kernels >= 2.6
+ // (and RHEL's patched kernel 2.4...)
+ unsigned long long int idlealltime = idletime + ioWait;
+ unsigned long long int systemalltime = systemtime + irq + softIrq;
+ unsigned long long int virtalltime = guest + guestnice;
+ unsigned long long int totaltime = usertime + nicetime + systemalltime + idlealltime + steal + virtalltime;
+ CPUData* cpuData = &(this->cpuData[adjCpuId]);
+ // Since we do a subtraction (usertime - guest) and cputime64_to_clock_t()
+ // used in /proc/stat rounds down numbers, it can lead to a case where the
+ // integer overflow.
+ cpuData->userPeriod = saturatingSub(usertime, cpuData->userTime);
+ cpuData->nicePeriod = saturatingSub(nicetime, cpuData->niceTime);
+ cpuData->systemPeriod = saturatingSub(systemtime, cpuData->systemTime);
+ cpuData->systemAllPeriod = saturatingSub(systemalltime, cpuData->systemAllTime);
+ cpuData->idleAllPeriod = saturatingSub(idlealltime, cpuData->idleAllTime);
+ cpuData->idlePeriod = saturatingSub(idletime, cpuData->idleTime);
+ cpuData->ioWaitPeriod = saturatingSub(ioWait, cpuData->ioWaitTime);
+ cpuData->irqPeriod = saturatingSub(irq, cpuData->irqTime);
+ cpuData->softIrqPeriod = saturatingSub(softIrq, cpuData->softIrqTime);
+ cpuData->stealPeriod = saturatingSub(steal, cpuData->stealTime);
+ cpuData->guestPeriod = saturatingSub(virtalltime, cpuData->guestTime);
+ cpuData->totalPeriod = saturatingSub(totaltime, cpuData->totalTime);
+ cpuData->userTime = usertime;
+ cpuData->niceTime = nicetime;
+ cpuData->systemTime = systemtime;
+ cpuData->systemAllTime = systemalltime;
+ cpuData->idleAllTime = idlealltime;
+ cpuData->idleTime = idletime;
+ cpuData->ioWaitTime = ioWait;
+ cpuData->irqTime = irq;
+ cpuData->softIrqTime = softIrq;
+ cpuData->stealTime = steal;
+ cpuData->guestTime = virtalltime;
+ cpuData->totalTime = totaltime;
+
+ adjCpuIdProcessed[adjCpuId] = true;
+ }
+
+ // Set the extra phantom thread as checked to make sure to mark trailing offline threads correctly in the loop
+ adjCpuIdProcessed[super->existingCPUs+1] = true;
+ unsigned int lastAdjCpuIdProcessed = 0;
+ for (unsigned int i = 0; i <= super->existingCPUs+1; i++) {
+ if (adjCpuIdProcessed[i]) {
+ for (unsigned int j = lastAdjCpuIdProcessed+1; j < i; j++) {
+ // Skipped an ID, but /proc/stat is ordered => threads in between are offline
+ memset(&(this->cpuData[j]), '\0', sizeof(CPUData));
+ }
+ lastAdjCpuIdProcessed = i;
+ }
+ }
+
+ this->period = (double)this->cpuData[0].totalPeriod / super->activeCPUs;
+
+ char buffer[PROC_LINE_LENGTH + 1];
+ while (fgets(buffer, sizeof(buffer), file)) {
+ if (String_startsWith(buffer, "procs_running")) {
+ this->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10);
+ break;
+ }
+ }
+
+ fclose(file);
+}
+
+static int scanCPUFrequencyFromSysCPUFreq(LinuxMachine* this) {
+ const Machine* super = &this->super;
+ int numCPUsWithFrequency = 0;
+ unsigned long totalFrequency = 0;
+
+ /*
+ * On some AMD and Intel CPUs read()ing scaling_cur_freq is quite slow (> 1ms). This delay
+ * accumulates for every core. For details see issue#471.
+ * If the read on CPU 0 takes longer than 500us bail out and fall back to reading the
+ * frequencies from /proc/cpuinfo.
+ * Once the condition has been met, bail out early for the next couple of scans.
+ */
+ static int timeout = 0;
+
+ if (timeout > 0) {
+ timeout--;
+ return -1;
+ }
+
+ for (unsigned int i = 0; i < super->existingCPUs; ++i) {
+ if (!Machine_isCPUonline(super, i))
+ continue;
+
+ char pathBuffer[64];
+ xSnprintf(pathBuffer, sizeof(pathBuffer), "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i);
+
+ struct timespec start;
+ if (i == 0)
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ FILE* file = fopen(pathBuffer, "r");
+ if (!file)
+ return -errno;
+
+ unsigned long frequency;
+ if (fscanf(file, "%lu", &frequency) == 1) {
+ /* convert kHz to MHz */
+ frequency = frequency / 1000;
+ this->cpuData[i + 1].frequency = frequency;
+ numCPUsWithFrequency++;
+ totalFrequency += frequency;
+ }
+
+ fclose(file);
+
+ if (i == 0) {
+ struct timespec end;
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ const time_t timeTakenUs = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;
+ if (timeTakenUs > 500) {
+ timeout = 30;
+ return -1;
+ }
+ }
+ }
+
+ if (numCPUsWithFrequency > 0)
+ this->cpuData[0].frequency = (double)totalFrequency / numCPUsWithFrequency;
+
+ return 0;
+}
+
+static void scanCPUFrequencyFromCPUinfo(LinuxMachine* this) {
+ const Machine* super = &this->super;
+
+ FILE* file = fopen(PROCCPUINFOFILE, "r");
+ if (file == NULL)
+ return;
+
+ int numCPUsWithFrequency = 0;
+ double totalFrequency = 0;
+ int cpuid = -1;
+
+ while (!feof(file)) {
+ double frequency;
+ char buffer[PROC_LINE_LENGTH];
+
+ if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
+ break;
+
+ if (sscanf(buffer, "processor : %d", &cpuid) == 1) {
+ continue;
+ } else if (
+ (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
+ (sscanf(buffer, "clock : %lfMHz", &frequency) == 1)
+ ) {
+ if (cpuid < 0 || (unsigned int)cpuid > (super->existingCPUs - 1)) {
+ continue;
+ }
+
+ CPUData* cpuData = &(this->cpuData[cpuid + 1]);
+ /* do not override sysfs data */
+ if (!isNonnegative(cpuData->frequency)) {
+ cpuData->frequency = frequency;
+ }
+ numCPUsWithFrequency++;
+ totalFrequency += frequency;
+ } else if (buffer[0] == '\n') {
+ cpuid = -1;
+ }
+ }
+ fclose(file);
+
+ if (numCPUsWithFrequency > 0) {
+ this->cpuData[0].frequency = totalFrequency / numCPUsWithFrequency;
+ }
+}
+
+static void LinuxMachine_scanCPUFrequency(LinuxMachine* this) {
+ const Machine* super = &this->super;
+
+ for (unsigned int i = 0; i <= super->existingCPUs; i++)
+ this->cpuData[i].frequency = NAN;
+
+ if (scanCPUFrequencyFromSysCPUFreq(this) == 0)
+ return;
+
+ scanCPUFrequencyFromCPUinfo(this);
+}
+
+void Machine_scan(Machine* super) {
+ LinuxMachine* this = (LinuxMachine*) super;
+
+ LinuxMachine_scanMemoryInfo(this);
+ LinuxMachine_scanHugePages(this);
+ LinuxMachine_scanZfsArcstats(this);
+ LinuxMachine_scanZramInfo(this);
+ LinuxMachine_scanCPUTime(this);
+
+ const Settings* settings = super->settings;
+ if (settings->showCPUFrequency)
+ LinuxMachine_scanCPUFrequency(this);
+
+ #ifdef HAVE_SENSORS_SENSORS_H
+ if (settings->showCPUTemperature)
+ LibSensors_getCPUTemperatures(this->cpuData, super->existingCPUs, super->activeCPUs);
+ #endif
+}
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ LinuxMachine* this = xCalloc(1, sizeof(LinuxMachine));
+ Machine* super = &this->super;
+
+ Machine_init(super, usersTable, userId);
+
+ // Initialize page size
+ if ((this->pageSize = sysconf(_SC_PAGESIZE)) == -1)
+ CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)");
+ this->pageSizeKB = this->pageSize / ONE_K;
+
+ // Initialize clock ticks
+ if ((this->jiffies = sysconf(_SC_CLK_TCK)) == -1)
+ CRT_fatalError("Cannot get clock ticks by sysconf(_SC_CLK_TCK)");
+
+ // Read btime (the kernel boot time, as number of seconds since the epoch)
+ FILE* statfile = fopen(PROCSTATFILE, "r");
+ if (statfile == NULL)
+ CRT_fatalError("Cannot open " PROCSTATFILE);
+
+ this->boottime = -1;
+
+ while (true) {
+ char buffer[PROC_LINE_LENGTH + 1];
+ if (fgets(buffer, sizeof(buffer), statfile) == NULL)
+ break;
+ if (String_startsWith(buffer, "btime ") == false)
+ continue;
+ if (sscanf(buffer, "btime %lld\n", &this->boottime) == 1)
+ break;
+ CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
+ }
+ fclose(statfile);
+
+ if (this->boottime == -1)
+ CRT_fatalError("No btime in " PROCSTATFILE);
+
+ // Initialize CPU count
+ LinuxMachine_updateCPUcount(this);
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ LinuxMachine* this = (LinuxMachine*) super;
+ GPUEngineData* gpuEngineData = this->gpuEngineData;
+
+ Machine_done(super);
+
+ while (gpuEngineData) {
+ GPUEngineData* next = gpuEngineData->next;
+ free(gpuEngineData->key);
+ free(gpuEngineData);
+ gpuEngineData = next;
+ }
+
+ free(this->cpuData);
+ free(this);
+}
+
+bool Machine_isCPUonline(const Machine* super, unsigned int id) {
+ const LinuxMachine* this = (const LinuxMachine*) super;
+
+ assert(id < super->existingCPUs);
+ return this->cpuData[id + 1].online;
+}
diff --git a/linux/LinuxProcessList.h b/linux/LinuxMachine.h
index 6c2f7dbe..398d1d77 100644
--- a/linux/LinuxProcessList.h
+++ b/linux/LinuxMachine.h
@@ -1,21 +1,17 @@
-#ifndef HEADER_LinuxProcessList
-#define HEADER_LinuxProcessList
+#ifndef HEADER_LinuxMachine
+#define HEADER_LinuxMachine
/*
-htop - LinuxProcessList.h
+htop - LinuxMachine.h
(C) 2014 Hisham H. Muhammad
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h"
-
#include <stdbool.h>
-#include <sys/types.h>
-#include "Hashtable.h"
-#include "ProcessList.h"
-#include "UsersTable.h"
-#include "ZramStats.h"
+#include "Machine.h"
+#include "linux/ZramStats.h"
+#include "linux/ZswapStats.h"
#include "zfs/ZfsArcStats.h"
#define HTOP_HUGEPAGE_BASE_SHIFT 16
@@ -57,35 +53,39 @@ typedef struct CPUData_ {
bool online;
} CPUData;
-typedef struct TtyDriver_ {
- char* path;
- unsigned int major;
- unsigned int minorFrom;
- unsigned int minorTo;
-} TtyDriver;
+typedef struct GPUEngineData_ {
+ unsigned long long int prevTime, curTime; /* absolute GPU time in nano seconds */
+ char* key; /* engine name */
+ struct GPUEngineData_* next;
+} GPUEngineData;
-typedef struct LinuxProcessList_ {
- ProcessList super;
+typedef struct LinuxMachine_ {
+ Machine super;
- CPUData* cpuData;
+ long jiffies;
+ int pageSize;
+ int pageSizeKB;
- TtyDriver* ttyDrivers;
- bool haveSmapsRollup;
- bool haveAutogroup;
+ /* see Linux kernel source for further detail, fs/proc/stat.c */
+ unsigned int runningTasks; /* procs_running from /proc/stat */
+ long long boottime; /* btime field from /proc/stat */
- #ifdef HAVE_DELAYACCT
- struct nl_sock* netlink_socket;
- int netlink_family;
- #endif
+ double period;
+
+ CPUData* cpuData;
memory_t totalHugePageMem;
memory_t usedHugePageMem[HTOP_HUGEPAGE_COUNT];
memory_t availableMem;
+ unsigned long long int prevGpuTime, curGpuTime; /* total absolute GPU time in nano seconds */
+ GPUEngineData* gpuEngineData;
+
ZfsArcStats zfs;
ZramStats zram;
-} LinuxProcessList;
+ ZswapStats zswap;
+} LinuxMachine;
#ifndef PROCDIR
#define PROCDIR "/proc"
@@ -115,12 +115,4 @@ typedef struct LinuxProcessList_ {
#define PROC_LINE_LENGTH 4096
#endif
-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);
-
#endif
diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c
index 92be3263..741fe19d 100644
--- a/linux/LinuxProcess.c
+++ b/linux/LinuxProcess.c
@@ -6,9 +6,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 "linux/LinuxProcess.h"
+#include <assert.h>
#include <math.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <syscall.h>
@@ -19,14 +23,14 @@ in the source distribution for its full text.
#include "Process.h"
#include "ProvideCurses.h"
#include "RichString.h"
+#include "RowField.h"
+#include "Scheduling.h"
+#include "Settings.h"
#include "XUtils.h"
#include "linux/IOPriority.h"
+#include "linux/LinuxMachine.h"
-/* semi-global */
-int pageSize;
-int pageSizeKB;
-
const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, },
[PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, },
@@ -53,11 +57,12 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, .defaultSortDesc = true, },
- [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, .defaultSortDesc = true, },
- [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, },
+ [M_PRIV] = { .name = "M_PRIV", .title = " PRIV ", .description = "The private memory size of the process - resident set size minus shared memory", .flags = 0, .defaultSortDesc = true, },
+ [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the .text segment of the process (CODE)", .flags = 0, .defaultSortDesc = true, },
+ [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the .data segment plus stack usage of the process (DATA)", .flags = 0, .defaultSortDesc = true, },
[M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@@ -78,11 +83,12 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[RBYTES] = { .name = "RBYTES", .title = " IO_R ", .description = "Bytes of read(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[WBYTES] = { .name = "WBYTES", .title = " IO_W ", .description = "Bytes of write(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[CNCLWB] = { .name = "CNCLWB", .title = " IO_C ", .description = "Bytes of cancelled write(2) I/O", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
- [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw)", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
[CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed)", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
+ [CONTAINER] = { .name = "CONTAINER", .title = "CONTAINER", .description = "Name of the container the process is in (guessed by heuristics)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
[OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, },
[IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, },
#ifdef HAVE_DELAYACCT
@@ -100,18 +106,25 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
[AUTOGROUP_ID] = { .name = "AUTOGROUP_ID", .title = "AGRP", .description = "The autogroup identifier of the process", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },
[AUTOGROUP_NICE] = { .name = "AUTOGROUP_NICE", .title = " ANI", .description = "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },
+ [ISCONTAINER] = { .name = "ISCONTAINER", .title = "CONT ", .description = "Whether the process is running inside a child container", .flags = PROCESS_FLAG_LINUX_CONTAINER, },
+#ifdef SCHEDULER_SUPPORT
+ [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, },
+#endif
+ [GPU_TIME] = { .name = "GPU_TIME", .title = "GPU_TIME ", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, },
+ [GPU_PERCENT] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, },
};
-Process* LinuxProcess_new(const Settings* settings) {
+Process* LinuxProcess_new(const Machine* host) {
LinuxProcess* this = xCalloc(1, sizeof(LinuxProcess));
Object_setClass(this, Class(LinuxProcess));
- Process_init(&this->super, settings);
- return &this->super;
+ Process_init(&this->super, host);
+ return (Process*)this;
}
void Process_delete(Object* cast) {
LinuxProcess* this = (LinuxProcess*) cast;
Process_done((Process*)cast);
+ free(this->container_short);
free(this->cgroup_short);
free(this->cgroup);
#ifdef HAVE_OPENVZ
@@ -142,22 +155,32 @@ static int LinuxProcess_effectiveIOPriority(const LinuxProcess* this) {
#define SYS_ioprio_set __NR_ioprio_set
#endif
-IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this) {
+/*
+ * Gather I/O scheduling class and priority (thread-specific data)
+ */
+IOPriority LinuxProcess_updateIOPriority(Process* p) {
IOPriority ioprio = 0;
// Other OSes masquerading as Linux (NetBSD?) don't have this syscall
#ifdef SYS_ioprio_get
- ioprio = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, this->super.pid);
+ ioprio = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, Process_getPid(p));
#endif
+ LinuxProcess* this = (LinuxProcess*) p;
this->ioPriority = ioprio;
return ioprio;
}
-bool LinuxProcess_setIOPriority(Process* this, Arg ioprio) {
+static bool LinuxProcess_setIOPriority(Process* p, Arg ioprio) {
// Other OSes masquerading as Linux (NetBSD?) don't have this syscall
#ifdef SYS_ioprio_set
- syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, this->pid, ioprio.i);
+ syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, Process_getPid(p), ioprio.i);
#endif
- return (LinuxProcess_updateIOPriority((LinuxProcess*)this) == ioprio.i);
+ return LinuxProcess_updateIOPriority(p) == ioprio.i;
+}
+
+bool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio) {
+ Process* p = (Process*) super;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+ return LinuxProcess_setIOPriority(p, ioprio);
}
bool LinuxProcess_isAutogroupEnabled(void) {
@@ -167,9 +190,10 @@ bool LinuxProcess_isAutogroupEnabled(void) {
return buf[0] == '1';
}
-bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) {
+static bool LinuxProcess_changeAutogroupPriorityBy(Process* p, Arg delta) {
char buffer[256];
- xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", this->pid);
+ pid_t pid = Process_getPid(p);
+ xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", pid);
FILE* file = fopen(buffer, "r+");
if (!file)
@@ -178,69 +202,81 @@ bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) {
long int identity;
int nice;
int ok = fscanf(file, "/autogroup-%ld nice %d", &identity, &nice);
- bool success;
- if (ok == 2) {
- rewind(file);
+ bool success = false;
+ if (ok == 2 && fseek(file, 0L, SEEK_SET) == 0) {
xSnprintf(buffer, sizeof(buffer), "%d", nice + delta.i);
success = fputs(buffer, file) > 0;
- } else {
- success = false;
}
fclose(file);
return success;
}
-static void LinuxProcess_writeField(const Process* this, RichString* str, ProcessField field) {
- const LinuxProcess* lp = (const LinuxProcess*) this;
- bool coloring = this->settings->highlightMegabytes;
+bool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta) {
+ Process* p = (Process*) super;
+ assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
+ return LinuxProcess_changeAutogroupPriorityBy(p, delta);
+}
+
+static double LinuxProcess_totalIORate(const LinuxProcess* lp) {
+ double totalRate = NAN;
+ if (isNonnegative(lp->io_rate_read_bps)) {
+ totalRate = lp->io_rate_read_bps;
+ if (isNonnegative(lp->io_rate_write_bps)) {
+ totalRate += lp->io_rate_write_bps;
+ }
+ } else if (isNonnegative(lp->io_rate_write_bps)) {
+ totalRate = lp->io_rate_write_bps;
+ }
+ return totalRate;
+}
+
+static void LinuxProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const Process* this = (const Process*) super;
+ const LinuxProcess* lp = (const LinuxProcess*) super;
+ const Machine* host = (const Machine*) super->host;
+ const LinuxMachine* lhost = (const LinuxMachine*) super->host;
+
+ bool coloring = host->settings->highlightMegabytes;
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
size_t n = sizeof(buffer) - 1;
+
switch (field) {
- case CMINFLT: Process_printCount(str, lp->cminflt, coloring); return;
- case CMAJFLT: Process_printCount(str, lp->cmajflt, coloring); return;
- case M_DRS: Process_printBytes(str, lp->m_drs * pageSize, coloring); return;
+ case CMINFLT: Row_printCount(str, lp->cminflt, coloring); return;
+ case CMAJFLT: Row_printCount(str, lp->cmajflt, coloring); return;
+ case GPU_PERCENT: Row_printPercentage(lp->gpu_percent, buffer, n, 5, &attr); break;
+ case GPU_TIME: Row_printNanoseconds(str, lp->gpu_time, coloring); return;
+ case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return;
case M_LRS:
if (lp->m_lrs) {
- Process_printBytes(str, lp->m_lrs * pageSize, coloring);
+ Row_printBytes(str, lp->m_lrs * lhost->pageSize, coloring);
return;
}
attr = CRT_colors[PROCESS_SHADOW];
xSnprintf(buffer, n, " N/A ");
break;
- case M_TRS: Process_printBytes(str, lp->m_trs * pageSize, coloring); return;
- case M_SHARE: Process_printBytes(str, lp->m_share * pageSize, coloring); return;
- case M_PSS: Process_printKBytes(str, lp->m_pss, coloring); return;
- case M_SWAP: Process_printKBytes(str, lp->m_swap, coloring); return;
- case M_PSSWP: Process_printKBytes(str, lp->m_psswp, coloring); return;
- case UTIME: Process_printTime(str, lp->utime, coloring); return;
- case STIME: Process_printTime(str, lp->stime, coloring); return;
- case CUTIME: Process_printTime(str, lp->cutime, coloring); return;
- case CSTIME: Process_printTime(str, lp->cstime, coloring); return;
- case RCHAR: Process_printBytes(str, lp->io_rchar, coloring); return;
- case WCHAR: Process_printBytes(str, lp->io_wchar, coloring); return;
- case SYSCR: Process_printCount(str, lp->io_syscr, coloring); return;
- case SYSCW: Process_printCount(str, lp->io_syscw, coloring); return;
- case RBYTES: Process_printBytes(str, lp->io_read_bytes, coloring); return;
- case WBYTES: Process_printBytes(str, lp->io_write_bytes, coloring); return;
- case CNCLWB: Process_printBytes(str, lp->io_cancelled_write_bytes, coloring); return;
- case IO_READ_RATE: Process_printRate(str, lp->io_rate_read_bps, coloring); return;
- case IO_WRITE_RATE: Process_printRate(str, lp->io_rate_write_bps, coloring); return;
- case IO_RATE: {
- double totalRate;
- if (!isnan(lp->io_rate_read_bps) && !isnan(lp->io_rate_write_bps))
- totalRate = lp->io_rate_read_bps + lp->io_rate_write_bps;
- else if (!isnan(lp->io_rate_read_bps))
- totalRate = lp->io_rate_read_bps;
- else if (!isnan(lp->io_rate_write_bps))
- totalRate = lp->io_rate_write_bps;
- else
- totalRate = NAN;
- Process_printRate(str, totalRate, coloring);
- return;
- }
+ case M_TRS: Row_printBytes(str, lp->m_trs * lhost->pageSize, coloring); return;
+ case M_SHARE: Row_printBytes(str, lp->m_share * lhost->pageSize, coloring); return;
+ case M_PRIV: Row_printKBytes(str, lp->m_priv, coloring); return;
+ case M_PSS: Row_printKBytes(str, lp->m_pss, coloring); return;
+ case M_SWAP: Row_printKBytes(str, lp->m_swap, coloring); return;
+ case M_PSSWP: Row_printKBytes(str, lp->m_psswp, coloring); return;
+ case UTIME: Row_printTime(str, lp->utime, coloring); return;
+ case STIME: Row_printTime(str, lp->stime, coloring); return;
+ case CUTIME: Row_printTime(str, lp->cutime, coloring); return;
+ case CSTIME: Row_printTime(str, lp->cstime, coloring); return;
+ case RCHAR: Row_printBytes(str, lp->io_rchar, coloring); return;
+ case WCHAR: Row_printBytes(str, lp->io_wchar, coloring); return;
+ case SYSCR: Row_printCount(str, lp->io_syscr, coloring); return;
+ case SYSCW: Row_printCount(str, lp->io_syscw, coloring); return;
+ case RBYTES: Row_printBytes(str, lp->io_read_bytes, coloring); return;
+ case WBYTES: Row_printBytes(str, lp->io_write_bytes, coloring); return;
+ case CNCLWB: Row_printBytes(str, lp->io_cancelled_write_bytes, coloring); return;
+ case IO_READ_RATE: Row_printRate(str, lp->io_rate_read_bps, coloring); return;
+ case IO_WRITE_RATE: Row_printRate(str, lp->io_rate_write_bps, coloring); return;
+ case IO_RATE: Row_printRate(str, LinuxProcess_totalIORate(lp), coloring); return;
#ifdef HAVE_OPENVZ
case CTID: xSnprintf(buffer, n, "%-8s ", lp->ctid ? lp->ctid : ""); break;
case VPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, lp->vpid); break;
@@ -248,8 +284,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
#ifdef HAVE_VSERVER
case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break;
#endif
- case CGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CGROUP], Process_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break;
- case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CCGROUP], Process_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break;
+ case CGROUP: xSnprintf(buffer, n, "%-*.*s ", Row_fieldWidths[CGROUP], Row_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break;
+ case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Row_fieldWidths[CCGROUP], Row_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break;
+ case CONTAINER: xSnprintf(buffer, n, "%-*.*s ", Row_fieldWidths[CONTAINER], Row_fieldWidths[CONTAINER], lp->container_short ? lp->container_short : "N/A"); break;
case OOM: xSnprintf(buffer, n, "%4u ", lp->oom); break;
case IO_PRIORITY: {
int klass = IOPriority_class(lp->ioPriority);
@@ -270,9 +307,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
break;
}
#ifdef HAVE_DELAYACCT
- case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break;
- case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break;
- case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break;
+ case PERCENT_CPU_DELAY: Row_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break;
+ case PERCENT_IO_DELAY: Row_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break;
+ case PERCENT_SWAP_DELAY: Row_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break;
#endif
case CTXT:
if (lp->ctxt_diff > 1000) {
@@ -280,7 +317,7 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
}
xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff);
break;
- case SECATTR: snprintf(buffer, n, "%-*.*s ", Process_fieldWidths[SECATTR], Process_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break;
+ case SECATTR: snprintf(buffer, n, "%-*.*s ", Row_fieldWidths[SECATTR], Row_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break;
case AUTOGROUP_ID:
if (lp->autogroup_id != -1) {
xSnprintf(buffer, n, "%4ld ", lp->autogroup_id);
@@ -293,25 +330,32 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
if (lp->autogroup_id != -1) {
xSnprintf(buffer, n, "%3d ", lp->autogroup_nice);
attr = lp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]
- : lp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
- : CRT_colors[PROCESS_SHADOW];
+ : lp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
+ : CRT_colors[PROCESS_SHADOW];
} else {
attr = CRT_colors[PROCESS_SHADOW];
xSnprintf(buffer, n, "N/A ");
}
break;
+ case ISCONTAINER:
+ switch (this->isRunningInContainer) {
+ case TRI_ON:
+ xSnprintf(buffer, n, "YES ");
+ break;
+ case TRI_OFF:
+ xSnprintf(buffer, n, "NO ");
+ break;
+ default:
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, "N/A ");
+ }
+ break;
default:
Process_writeField(this, str, field);
return;
}
- RichString_appendAscii(str, attr, buffer);
-}
-
-static double adjustNaN(double num) {
- if (isnan(num))
- return -0.0005;
- return num;
+ RichString_appendAscii(str, attr, buffer);
}
static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
@@ -327,6 +371,8 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce
return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs);
case M_SHARE:
return SPACESHIP_NUMBER(p1->m_share, p2->m_share);
+ case M_PRIV:
+ return SPACESHIP_NUMBER(p1->m_priv, p2->m_priv);
case M_PSS:
return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss);
case M_SWAP:
@@ -356,11 +402,11 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce
case CNCLWB:
return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes);
case IO_READ_RATE:
- return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps), adjustNaN(p2->io_rate_read_bps));
+ return compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps);
case IO_WRITE_RATE:
- return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_write_bps));
+ return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps);
case IO_RATE:
- return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps) + adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_read_bps) + adjustNaN(p2->io_rate_write_bps));
+ return compareRealNumbers(LinuxProcess_totalIORate(p1), LinuxProcess_totalIORate(p2));
#ifdef HAVE_OPENVZ
case CTID:
return SPACESHIP_NULLSTR(p1->ctid, p2->ctid);
@@ -375,15 +421,17 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce
return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);
case CCGROUP:
return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short);
+ case CONTAINER:
+ return SPACESHIP_NULLSTR(p1->container_short, p2->container_short);
case OOM:
return SPACESHIP_NUMBER(p1->oom, p2->oom);
#ifdef HAVE_DELAYACCT
case PERCENT_CPU_DELAY:
- return SPACESHIP_NUMBER(p1->cpu_delay_percent, p2->cpu_delay_percent);
+ return compareRealNumbers(p1->cpu_delay_percent, p2->cpu_delay_percent);
case PERCENT_IO_DELAY:
- return SPACESHIP_NUMBER(p1->blkio_delay_percent, p2->blkio_delay_percent);
+ return compareRealNumbers(p1->blkio_delay_percent, p2->blkio_delay_percent);
case PERCENT_SWAP_DELAY:
- return SPACESHIP_NUMBER(p1->swapin_delay_percent, p2->swapin_delay_percent);
+ return compareRealNumbers(p1->swapin_delay_percent, p2->swapin_delay_percent);
#endif
case IO_PRIORITY:
return SPACESHIP_NUMBER(LinuxProcess_effectiveIOPriority(p1), LinuxProcess_effectiveIOPriority(p2));
@@ -395,6 +443,17 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce
return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id);
case AUTOGROUP_NICE:
return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice);
+ case GPU_PERCENT: {
+ int r = compareRealNumbers(p1->gpu_percent, p2->gpu_percent);
+ if (r)
+ return r;
+
+ return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time);
+ }
+ case GPU_TIME:
+ return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time);
+ case ISCONTAINER:
+ return SPACESHIP_NUMBER(v1->isRunningInContainer, v2->isRunningInContainer);
default:
return Process_compareByKey_Base(v1, v2, key);
}
@@ -402,11 +461,18 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce
const ProcessClass LinuxProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = LinuxProcess_rowWriteField
},
- .writeField = LinuxProcess_writeField,
.compareByKey = LinuxProcess_compareByKey
};
diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h
index 3e5d3804..fafd7d00 100644
--- a/linux/LinuxProcess.h
+++ b/linux/LinuxProcess.h
@@ -8,15 +8,14 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <stdbool.h>
-#include <sys/types.h>
-#include "linux/IOPriority.h"
+#include "Machine.h"
#include "Object.h"
#include "Process.h"
-#include "Settings.h"
+#include "Row.h"
+
+#include "linux/IOPriority.h"
#define PROCESS_FLAG_LINUX_IOPRIO 0x00000100
@@ -30,6 +29,8 @@ in the source distribution for its full text.
#define PROCESS_FLAG_LINUX_LRS_FIX 0x00010000
#define PROCESS_FLAG_LINUX_DELAYACCT 0x00040000
#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000
+#define PROCESS_FLAG_LINUX_GPU 0x00100000
+#define PROCESS_FLAG_LINUX_CONTAINER 0x00200000
typedef struct LinuxProcess_ {
Process super;
@@ -41,6 +42,7 @@ typedef struct LinuxProcess_ {
unsigned long long int cutime;
unsigned long long int cstime;
long m_share;
+ long m_priv;
long m_pss;
long m_swap;
long m_psswp;
@@ -90,6 +92,7 @@ typedef struct LinuxProcess_ {
#endif
char* cgroup;
char* cgroup_short;
+ char* container_short;
unsigned int oom;
#ifdef HAVE_DELAYACCT
unsigned long long int delay_read_time;
@@ -105,6 +108,13 @@ typedef struct LinuxProcess_ {
char* secattr;
unsigned long long int last_mlrs_calctime;
+ /* Total GPU time used in nano seconds */
+ unsigned long long int gpu_time;
+ /* GPU utilization in percent */
+ float gpu_percent;
+ /* Activity of GPU: 0 if active, otherwise time of last scan in milliseconds */
+ uint64_t gpu_activityMs;
+
/* Autogroup scheduling (CFS) information */
long int autogroup_id;
int autogroup_nice;
@@ -118,17 +128,17 @@ extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
extern const ProcessClass LinuxProcess_class;
-Process* LinuxProcess_new(const Settings* settings);
+Process* LinuxProcess_new(const Machine* host);
void Process_delete(Object* cast);
-IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this);
+IOPriority LinuxProcess_updateIOPriority(Process* proc);
-bool LinuxProcess_setIOPriority(Process* this, Arg ioprio);
+bool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio);
bool LinuxProcess_isAutogroupEnabled(void);
-bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta);
+bool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta);
bool Process_isThread(const Process* this);
diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c
deleted file mode 100644
index 45b045c8..00000000
--- a/linux/LinuxProcessList.c
+++ /dev/null
@@ -1,2249 +0,0 @@
-/*
-htop - LinuxProcessList.c
-(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "config.h" // IWYU pragma: keep
-
-#include "linux/LinuxProcessList.h"
-
-#include <assert.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <math.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-#include <time.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#ifdef HAVE_DELAYACCT
-#include <linux/netlink.h>
-#include <linux/taskstats.h>
-#include <netlink/attr.h>
-#include <netlink/handlers.h>
-#include <netlink/msg.h>
-#include <netlink/netlink.h>
-#include <netlink/socket.h>
-#include <netlink/genl/genl.h>
-#include <netlink/genl/ctrl.h>
-#endif
-
-#include "Compat.h"
-#include "CRT.h"
-#include "Macros.h"
-#include "Object.h"
-#include "Process.h"
-#include "Settings.h"
-#include "XUtils.h"
-#include "linux/CGroupUtils.h"
-#include "linux/LinuxProcess.h"
-#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep
-
-#if defined(MAJOR_IN_MKDEV)
-#include <sys/mkdev.h>
-#elif defined(MAJOR_IN_SYSMACROS)
-#include <sys/sysmacros.h>
-#endif
-
-#ifdef HAVE_SENSORS_SENSORS_H
-#include "LibSensors.h"
-#endif
-
-#ifndef O_PATH
-#define O_PATH 010000000 // declare for ancient glibc versions
-#endif
-
-/* Not exposed yet. Defined at include/linux/sched.h */
-#ifndef PF_KTHREAD
-#define PF_KTHREAD 0x00200000
-#endif
-
-static long long btime = -1;
-
-static long jiffy;
-
-static FILE* fopenat(openat_arg_t openatArg, const char* pathname, const char* mode) {
- assert(String_eq(mode, "r")); /* only currently supported mode */
-
- int fd = Compat_openat(openatArg, pathname, O_RDONLY);
- if (fd < 0)
- return NULL;
-
- FILE* stream = fdopen(fd, mode);
- if (!stream)
- close(fd);
-
- return stream;
-}
-
-static int sortTtyDrivers(const void* va, const void* vb) {
- const TtyDriver* a = (const TtyDriver*) va;
- const TtyDriver* b = (const TtyDriver*) vb;
-
- int r = SPACESHIP_NUMBER(a->major, b->major);
- if (r)
- return r;
-
- return SPACESHIP_NUMBER(a->minorFrom, b->minorFrom);
-}
-
-static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) {
- TtyDriver* ttyDrivers;
-
- char buf[16384];
- ssize_t r = xReadfile(PROCTTYDRIVERSFILE, buf, sizeof(buf));
- if (r < 0)
- return;
-
- int numDrivers = 0;
- int allocd = 10;
- ttyDrivers = xMallocArray(allocd, sizeof(TtyDriver));
- char* at = buf;
- while (*at != '\0') {
- at = strchr(at, ' '); // skip first token
- while (*at == ' ') at++; // skip spaces
- const char* token = at; // mark beginning of path
- at = strchr(at, ' '); // find end of path
- *at = '\0'; at++; // clear and skip
- ttyDrivers[numDrivers].path = xStrdup(token); // save
- while (*at == ' ') at++; // skip spaces
- token = at; // mark beginning of major
- at = strchr(at, ' '); // find end of major
- *at = '\0'; at++; // clear and skip
- ttyDrivers[numDrivers].major = atoi(token); // save
- while (*at == ' ') at++; // skip spaces
- token = at; // mark beginning of minorFrom
- while (*at >= '0' && *at <= '9') at++; //find end of minorFrom
- if (*at == '-') { // if has range
- *at = '\0'; at++; // clear and skip
- ttyDrivers[numDrivers].minorFrom = atoi(token); // save
- token = at; // mark beginning of minorTo
- at = strchr(at, ' '); // find end of minorTo
- *at = '\0'; at++; // clear and skip
- ttyDrivers[numDrivers].minorTo = atoi(token); // save
- } else { // no range
- *at = '\0'; at++; // clear and skip
- ttyDrivers[numDrivers].minorFrom = atoi(token); // save
- ttyDrivers[numDrivers].minorTo = atoi(token); // save
- }
- at = strchr(at, '\n'); // go to end of line
- at++; // skip
- numDrivers++;
- if (numDrivers == allocd) {
- allocd += 10;
- ttyDrivers = xReallocArray(ttyDrivers, allocd, sizeof(TtyDriver));
- }
- }
- numDrivers++;
- ttyDrivers = xRealloc(ttyDrivers, sizeof(TtyDriver) * numDrivers);
- ttyDrivers[numDrivers - 1].path = NULL;
- qsort(ttyDrivers, numDrivers - 1, sizeof(TtyDriver), sortTtyDrivers);
- this->ttyDrivers = ttyDrivers;
-}
-
-#ifdef HAVE_DELAYACCT
-
-static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) {
- this->netlink_socket = nl_socket_alloc();
- if (this->netlink_socket == NULL) {
- return;
- }
- if (nl_connect(this->netlink_socket, NETLINK_GENERIC) < 0) {
- return;
- }
- this->netlink_family = genl_ctrl_resolve(this->netlink_socket, TASKSTATS_GENL_NAME);
-}
-
-#endif
-
-static unsigned int scanAvailableCPUsFromCPUinfo(LinuxProcessList* this) {
- FILE* file = fopen(PROCCPUINFOFILE, "r");
- if (file == NULL)
- return this->super.existingCPUs;
-
- unsigned int availableCPUs = 0;
-
- while (!feof(file)) {
- char buffer[PROC_LINE_LENGTH];
-
- if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
- break;
-
- if (String_startsWith(buffer, "processor"))
- availableCPUs++;
- }
-
- fclose(file);
-
- return availableCPUs ? availableCPUs : 1;
-}
-
-static void LinuxProcessList_updateCPUcount(ProcessList* super) {
- /* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF
- * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD
- */
-
- LinuxProcessList* this = (LinuxProcessList*) super;
- unsigned int existing = 0, active = 0;
-
- // Initialize the cpuData array before anything else.
- if (!this->cpuData) {
- this->cpuData = xCalloc(2, sizeof(CPUData));
- this->cpuData[0].online = true; /* average is always "online" */
- this->cpuData[1].online = true;
- super->activeCPUs = 1;
- super->existingCPUs = 1;
- }
-
- DIR* dir = opendir("/sys/devices/system/cpu");
- if (!dir)
- return;
-
- unsigned int currExisting = super->existingCPUs;
-
- const struct dirent* entry;
- while ((entry = readdir(dir)) != NULL) {
- if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
- continue;
-
- if (!String_startsWith(entry->d_name, "cpu"))
- continue;
-
- char *endp;
- unsigned long int id = strtoul(entry->d_name + 3, &endp, 10);
- if (id == ULONG_MAX || endp == entry->d_name + 3 || *endp != '\0')
- continue;
-
-#ifdef HAVE_OPENAT
- int cpuDirFd = openat(dirfd(dir), entry->d_name, O_DIRECTORY | O_PATH | O_NOFOLLOW);
- if (cpuDirFd < 0)
- continue;
-#else
- char cpuDirFd[4096];
- xSnprintf(cpuDirFd, sizeof(cpuDirFd), "/sys/devices/system/cpu/%s", entry->d_name);
-#endif
-
- existing++;
-
- /* readdir() iterates with no specific order */
- unsigned int max = MAXIMUM(existing, id + 1);
- if (max > currExisting) {
- this->cpuData = xReallocArrayZero(this->cpuData, currExisting ? (currExisting + 1) : 0, max + /* aggregate */ 1, sizeof(CPUData));
- this->cpuData[0].online = true; /* average is always "online" */
- currExisting = max;
- }
-
- char buffer[8];
- ssize_t res = xReadfileat(cpuDirFd, "online", buffer, sizeof(buffer));
- /* If the file "online" does not exist or on failure count as active */
- if (res < 1 || buffer[0] != '0') {
- active++;
- this->cpuData[id + 1].online = true;
- } else {
- this->cpuData[id + 1].online = false;
- }
-
- Compat_openatArgClose(cpuDirFd);
- }
-
- closedir(dir);
-
- // return if no CPU is found
- if (existing < 1)
- return;
-
- if (Running_containerized) {
- /* LXC munges /proc/cpuinfo but not the /sys/devices/system/cpu/ files,
- * so limit the visible CPUs to what the guest has been configured to see: */
- currExisting = active = scanAvailableCPUsFromCPUinfo(this);
- }
-
-#ifdef HAVE_SENSORS_SENSORS_H
- /* When started with offline CPUs, libsensors does not monitor those,
- * even when they become online. */
- if (super->existingCPUs != 0 && (active > super->activeCPUs || currExisting > super->existingCPUs))
- LibSensors_reload();
-#endif
-
- super->activeCPUs = active;
- assert(Running_containerized || (existing == currExisting));
- super->existingCPUs = currExisting;
-}
-
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
- LinuxProcessList* this = xCalloc(1, sizeof(LinuxProcessList));
- ProcessList* pl = &(this->super);
-
- ProcessList_init(pl, Class(LinuxProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
- LinuxProcessList_initTtyDrivers(this);
-
- // Initialize page size
- pageSize = sysconf(_SC_PAGESIZE);
- if (pageSize == -1)
- CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)");
- pageSizeKB = pageSize / ONE_K;
-
- // Initialize clock ticks
- jiffy = sysconf(_SC_CLK_TCK);
- if (jiffy == -1)
- CRT_fatalError("Cannot get clock ticks by sysconf(_SC_CLK_TCK)");
-
- // Test /proc/PID/smaps_rollup availability (faster to parse, Linux 4.14+)
- this->haveSmapsRollup = (access(PROCDIR "/self/smaps_rollup", R_OK) == 0);
-
- // Read btime (the kernel boot time, as number of seconds since the epoch)
- FILE* statfile = fopen(PROCSTATFILE, "r");
- if (statfile == NULL)
- CRT_fatalError("Cannot open " PROCSTATFILE);
- while (true) {
- char buffer[PROC_LINE_LENGTH + 1];
- if (fgets(buffer, sizeof(buffer), statfile) == NULL)
- break;
- if (String_startsWith(buffer, "btime ") == false)
- continue;
- if (sscanf(buffer, "btime %lld\n", &btime) == 1)
- break;
- CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
- }
-
- fclose(statfile);
-
- if (btime == -1)
- CRT_fatalError("No btime in " PROCSTATFILE);
-
- // Initialize CPU count
- LinuxProcessList_updateCPUcount(pl);
-
- return pl;
-}
-
-void ProcessList_delete(ProcessList* pl) {
- LinuxProcessList* this = (LinuxProcessList*) pl;
- ProcessList_done(pl);
- free(this->cpuData);
- if (this->ttyDrivers) {
- for (int i = 0; this->ttyDrivers[i].path; i++) {
- free(this->ttyDrivers[i].path);
- }
- free(this->ttyDrivers);
- }
- #ifdef HAVE_DELAYACCT
- if (this->netlink_socket) {
- nl_close(this->netlink_socket);
- nl_socket_free(this->netlink_socket);
- }
- #endif
- free(this);
-}
-
-static inline unsigned long long LinuxProcessList_adjustTime(unsigned long long t) {
- return t * 100 / jiffy;
-}
-
-/* Taken from: https://github.com/torvalds/linux/blob/64570fbc14f8d7cb3fe3995f20e26bc25ce4b2cc/fs/proc/array.c#L120 */
-static inline ProcessState LinuxProcessList_getProcessState(char state) {
- switch (state) {
- case 'S': return SLEEPING;
- case 'X': return DEFUNCT;
- case 'Z': return ZOMBIE;
- case 't': return TRACED;
- case 'T': return STOPPED;
- case 'D': return UNINTERRUPTIBLE_WAIT;
- case 'R': return RUNNING;
- case 'P': return BLOCKED;
- case 'I': return IDLE;
- default: return UNKNOWN;
- }
-}
-
-static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, char* command, size_t commLen) {
- LinuxProcess* lp = (LinuxProcess*) process;
-
- char buf[MAX_READ + 1];
- ssize_t r = xReadfileat(procFd, "stat", buf, sizeof(buf));
- if (r < 0)
- return false;
-
- /* (1) pid - %d */
- assert(process->pid == atoi(buf));
- char* location = strchr(buf, ' ');
- if (!location)
- return false;
-
- /* (2) comm - (%s) */
- location += 2;
- char* end = strrchr(location, ')');
- if (!end)
- return false;
-
- String_safeStrncpy(command, location, MINIMUM((size_t)(end - location + 1), commLen));
-
- location = end + 2;
-
- /* (3) state - %c */
- process->state = LinuxProcessList_getProcessState(location[0]);
- location += 2;
-
- /* (4) ppid - %d */
- process->ppid = strtol(location, &location, 10);
- location += 1;
-
- /* (5) pgrp - %d */
- process->pgrp = strtol(location, &location, 10);
- location += 1;
-
- /* (6) session - %d */
- process->session = strtol(location, &location, 10);
- location += 1;
-
- /* (7) tty_nr - %d */
- process->tty_nr = strtoul(location, &location, 10);
- location += 1;
-
- /* (8) tpgid - %d */
- process->tpgid = strtol(location, &location, 10);
- location += 1;
-
- /* (9) flags - %u */
- lp->flags = strtoul(location, &location, 10);
- location += 1;
-
- /* (10) minflt - %lu */
- process->minflt = strtoull(location, &location, 10);
- location += 1;
-
- /* (11) cminflt - %lu */
- lp->cminflt = strtoull(location, &location, 10);
- location += 1;
-
- /* (12) majflt - %lu */
- process->majflt = strtoull(location, &location, 10);
- location += 1;
-
- /* (13) cmajflt - %lu */
- lp->cmajflt = strtoull(location, &location, 10);
- location += 1;
-
- /* (14) utime - %lu */
- lp->utime = LinuxProcessList_adjustTime(strtoull(location, &location, 10));
- location += 1;
-
- /* (15) stime - %lu */
- lp->stime = LinuxProcessList_adjustTime(strtoull(location, &location, 10));
- location += 1;
-
- /* (16) cutime - %ld */
- lp->cutime = LinuxProcessList_adjustTime(strtoull(location, &location, 10));
- location += 1;
-
- /* (17) cstime - %ld */
- lp->cstime = LinuxProcessList_adjustTime(strtoull(location, &location, 10));
- location += 1;
-
- /* (18) priority - %ld */
- process->priority = strtol(location, &location, 10);
- location += 1;
-
- /* (19) nice - %ld */
- process->nice = strtol(location, &location, 10);
- location += 1;
-
- /* (20) num_threads - %ld */
- process->nlwp = strtol(location, &location, 10);
- location += 1;
-
- /* Skip (21) itrealvalue - %ld */
- location = strchr(location, ' ') + 1;
-
- /* (22) starttime - %llu */
- if (process->starttime_ctime == 0) {
- process->starttime_ctime = btime + LinuxProcessList_adjustTime(strtoll(location, &location, 10)) / 100;
- } else {
- location = strchr(location, ' ');
- }
- location += 1;
-
- /* Skip (23) - (38) */
- for (int i = 0; i < 16; i++) {
- location = strchr(location, ' ') + 1;
- }
-
- assert(location != NULL);
-
- /* (39) processor - %d */
- process->processor = strtol(location, &location, 10);
-
- /* Ignore further fields */
-
- process->time = lp->utime + lp->stime;
-
- return true;
-}
-
-
-static bool LinuxProcessList_updateUser(ProcessList* processList, Process* process, openat_arg_t procFd) {
- struct stat sstat;
-#ifdef HAVE_OPENAT
- int statok = fstat(procFd, &sstat);
-#else
- int statok = stat(procFd, &sstat);
-#endif
- if (statok == -1)
- return false;
-
- if (process->st_uid != sstat.st_uid) {
- process->st_uid = sstat.st_uid;
- process->user = UsersTable_getRef(processList->usersTable, sstat.st_uid);
- }
-
- return true;
-}
-
-static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t procFd, unsigned long long realtimeMs) {
- char buffer[1024];
- ssize_t r = xReadfileat(procFd, "io", buffer, sizeof(buffer));
- if (r < 0) {
- process->io_rate_read_bps = NAN;
- process->io_rate_write_bps = NAN;
- process->io_rchar = ULLONG_MAX;
- process->io_wchar = ULLONG_MAX;
- process->io_syscr = ULLONG_MAX;
- process->io_syscw = ULLONG_MAX;
- process->io_read_bytes = ULLONG_MAX;
- process->io_write_bytes = ULLONG_MAX;
- process->io_cancelled_write_bytes = ULLONG_MAX;
- process->io_last_scan_time_ms = realtimeMs;
- return;
- }
-
- unsigned long long last_read = process->io_read_bytes;
- unsigned long long last_write = process->io_write_bytes;
- unsigned long long time_delta = realtimeMs > process->io_last_scan_time_ms ? realtimeMs - process->io_last_scan_time_ms : 0;
-
- char* buf = buffer;
- const char* line;
- while ((line = strsep(&buf, "\n")) != NULL) {
- switch (line[0]) {
- case 'r':
- if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) {
- process->io_rchar = strtoull(line + 7, NULL, 10);
- } else if (String_startsWith(line + 1, "ead_bytes: ")) {
- process->io_read_bytes = strtoull(line + 12, NULL, 10);
- process->io_rate_read_bps = time_delta ? (process->io_read_bytes - last_read) * /*ms to s*/1000. / time_delta : NAN;
- }
- break;
- case 'w':
- if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) {
- process->io_wchar = strtoull(line + 7, NULL, 10);
- } else if (String_startsWith(line + 1, "rite_bytes: ")) {
- process->io_write_bytes = strtoull(line + 13, NULL, 10);
- process->io_rate_write_bps = time_delta ? (process->io_write_bytes - last_write) * /*ms to s*/1000. / time_delta : NAN;
- }
- break;
- case 's':
- if (line[4] == 'r' && String_startsWith(line + 1, "yscr: ")) {
- process->io_syscr = strtoull(line + 7, NULL, 10);
- } else if (String_startsWith(line + 1, "yscw: ")) {
- process->io_syscw = strtoull(line + 7, NULL, 10);
- }
- break;
- case 'c':
- if (String_startsWith(line + 1, "ancelled_write_bytes: ")) {
- process->io_cancelled_write_bytes = strtoull(line + 23, NULL, 10);
- }
- }
- }
-
- process->io_last_scan_time_ms = realtimeMs;
-}
-
-typedef struct LibraryData_ {
- uint64_t size;
- bool exec;
-} LibraryData;
-
-static inline uint64_t fast_strtoull_dec(char** str, int maxlen) {
- register uint64_t result = 0;
-
- if (!maxlen)
- --maxlen;
-
- while (maxlen-- && **str >= '0' && **str <= '9') {
- result *= 10;
- result += **str - '0';
- (*str)++;
- }
-
- return result;
-}
-
-static inline uint64_t fast_strtoull_hex(char** str, int maxlen) {
- register uint64_t result = 0;
- register int nibble, letter;
- const long valid_mask = 0x03FF007E;
-
- if (!maxlen)
- --maxlen;
-
- while (maxlen--) {
- nibble = (unsigned char)**str;
- if (!(valid_mask & (1 << (nibble & 0x1F))))
- break;
- if ((nibble < '0') || (nibble & ~0x20) > 'F')
- break;
- letter = (nibble & 0x40) ? 'A' - '9' - 1 : 0;
- nibble &=~0x20; // to upper
- nibble ^= 0x10; // switch letters and digits
- nibble -= letter;
- nibble &= 0x0f;
- result <<= 4;
- result += (uint64_t)nibble;
- (*str)++;
- }
-
- return result;
-}
-
-static void LinuxProcessList_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* value, void* data) {
- if (!data)
- return;
-
- if (!value)
- return;
-
- const LibraryData* v = (const LibraryData*)value;
- uint64_t* d = (uint64_t*)data;
- if (!v->exec)
- return;
-
- *d += v->size;
-}
-
-static void LinuxProcessList_readMaps(LinuxProcess* process, openat_arg_t procFd, bool calcSize, bool checkDeletedLib) {
- Process* proc = (Process*)process;
-
- proc->usesDeletedLib = false;
-
- FILE* mapsfile = fopenat(procFd, "maps", "r");
- if (!mapsfile)
- return;
-
- Hashtable* ht = NULL;
- if (calcSize)
- ht = Hashtable_new(64, true);
-
- char buffer[1024];
- while (fgets(buffer, sizeof(buffer), mapsfile)) {
- uint64_t map_start;
- uint64_t map_end;
- bool map_execute;
- unsigned int map_devmaj;
- unsigned int map_devmin;
- uint64_t map_inode;
-
- // Short circuit test: Look for a slash
- if (!strchr(buffer, '/'))
- continue;
-
- // Parse format: "%Lx-%Lx %4s %x %2x:%2x %Ld"
- char* readptr = buffer;
-
- map_start = fast_strtoull_hex(&readptr, 16);
- if ('-' != *readptr++)
- continue;
-
- map_end = fast_strtoull_hex(&readptr, 16);
- if (' ' != *readptr++)
- continue;
-
- map_execute = (readptr[2] == 'x');
- readptr += 4;
- if (' ' != *readptr++)
- continue;
-
- while(*readptr > ' ')
- readptr++; // Skip parsing this hex value
- if (' ' != *readptr++)
- continue;
-
- map_devmaj = fast_strtoull_hex(&readptr, 4);
- if (':' != *readptr++)
- continue;
-
- map_devmin = fast_strtoull_hex(&readptr, 4);
- if (' ' != *readptr++)
- continue;
-
- //Minor shortcut: Once we know there's no file for this region, we skip
- if (!map_devmaj && !map_devmin)
- continue;
-
- map_inode = fast_strtoull_dec(&readptr, 20);
- if (!map_inode)
- continue;
-
- if (calcSize) {
- LibraryData* libdata = Hashtable_get(ht, map_inode);
- if (!libdata) {
- libdata = xCalloc(1, sizeof(LibraryData));
- Hashtable_put(ht, map_inode, libdata);
- }
-
- libdata->size += map_end - map_start;
- libdata->exec |= map_execute;
- }
-
- if (checkDeletedLib && map_execute && !proc->usesDeletedLib) {
- while (*readptr == ' ')
- readptr++;
-
- if (*readptr != '/')
- continue;
-
- if (String_startsWith(readptr, "/memfd:"))
- continue;
-
- /* Virtualbox maps /dev/zero for memory allocation. That results in
- * false positive, so ignore. */
- if (String_eq(readptr, "/dev/zero (deleted)\n"))
- continue;
-
- if (strstr(readptr, " (deleted)\n")) {
- proc->usesDeletedLib = true;
- if (!calcSize)
- break;
- }
- }
- }
-
- fclose(mapsfile);
-
- if (calcSize) {
- uint64_t total_size = 0;
- Hashtable_foreach(ht, LinuxProcessList_calcLibSize_helper, &total_size);
-
- Hashtable_delete(ht);
-
- process->m_lrs = total_size / pageSize;
- }
-}
-
-static bool LinuxProcessList_readStatmFile(LinuxProcess* process, openat_arg_t procFd) {
- FILE* statmfile = fopenat(procFd, "statm", "r");
- if (!statmfile)
- return false;
-
- long int dummy, dummy2;
-
- int r = fscanf(statmfile, "%ld %ld %ld %ld %ld %ld %ld",
- &process->super.m_virt,
- &process->super.m_resident,
- &process->m_share,
- &process->m_trs,
- &dummy, /* unused since Linux 2.6; always 0 */
- &process->m_drs,
- &dummy2); /* unused since Linux 2.6; always 0 */
- fclose(statmfile);
-
- if (r == 7) {
- process->super.m_virt *= pageSizeKB;
- process->super.m_resident *= pageSizeKB;
- }
-
- return r == 7;
-}
-
-static bool LinuxProcessList_readSmapsFile(LinuxProcess* process, openat_arg_t procFd, bool haveSmapsRollup) {
- //http://elixir.free-electrons.com/linux/v4.10/source/fs/proc/task_mmu.c#L719
- //kernel will return data in chunks of size PAGE_SIZE or less.
- FILE* f = fopenat(procFd, haveSmapsRollup ? "smaps_rollup" : "smaps", "r");
- if (!f)
- return false;
-
- process->m_pss = 0;
- process->m_swap = 0;
- process->m_psswp = 0;
-
- char buffer[256];
- while (fgets(buffer, sizeof(buffer), f)) {
- if (!strchr(buffer, '\n')) {
- // Partial line, skip to end of this line
- while (fgets(buffer, sizeof(buffer), f)) {
- if (strchr(buffer, '\n')) {
- break;
- }
- }
- continue;
- }
-
- if (String_startsWith(buffer, "Pss:")) {
- process->m_pss += strtol(buffer + 4, NULL, 10);
- } else if (String_startsWith(buffer, "Swap:")) {
- process->m_swap += strtol(buffer + 5, NULL, 10);
- } else if (String_startsWith(buffer, "SwapPss:")) {
- process->m_psswp += strtol(buffer + 8, NULL, 10);
- }
- }
-
- fclose(f);
- return true;
-}
-
-#ifdef HAVE_OPENVZ
-
-static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) {
- if (access(PROCDIR "/vz", R_OK) != 0) {
- free(process->ctid);
- process->ctid = NULL;
- process->vpid = process->super.pid;
- return;
- }
-
- FILE* file = fopenat(procFd, "status", "r");
- if (!file) {
- free(process->ctid);
- process->ctid = NULL;
- process->vpid = process->super.pid;
- return;
- }
-
- bool foundEnvID = false;
- bool foundVPid = false;
- char linebuf[256];
- while (fgets(linebuf, sizeof(linebuf), file) != NULL) {
- if (strchr(linebuf, '\n') == NULL) {
- // Partial line, skip to end of this line
- while (fgets(linebuf, sizeof(linebuf), file) != NULL) {
- if (strchr(linebuf, '\n') != NULL) {
- break;
- }
- }
- continue;
- }
-
- char* name_value_sep = strchr(linebuf, ':');
- if (name_value_sep == NULL) {
- continue;
- }
-
- int field;
- if (0 == strncasecmp(linebuf, "envID", name_value_sep - linebuf)) {
- field = 1;
- } else if (0 == strncasecmp(linebuf, "VPid", name_value_sep - linebuf)) {
- field = 2;
- } else {
- continue;
- }
-
- do {
- name_value_sep++;
- } while (*name_value_sep != '\0' && *name_value_sep <= 32);
-
- char* value_end = name_value_sep;
-
- while(*value_end > 32) {
- value_end++;
- }
-
- if (name_value_sep == value_end) {
- continue;
- }
-
- *value_end = '\0';
-
- switch(field) {
- case 1:
- foundEnvID = true;
- if (!String_eq(name_value_sep, process->ctid ? process->ctid : ""))
- free_and_xStrdup(&process->ctid, name_value_sep);
- break;
- case 2:
- foundVPid = true;
- process->vpid = strtoul(name_value_sep, NULL, 0);
- break;
- default:
- //Sanity Check: Should never reach here, or the implementation is missing something!
- assert(false && "OpenVZ handling: Unimplemented case for field handling reached.");
- }
- }
-
- fclose(file);
-
- if (!foundEnvID) {
- free(process->ctid);
- process->ctid = NULL;
- }
-
- if (!foundVPid) {
- process->vpid = process->super.pid;
- }
-}
-
-#endif
-
-static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t procFd) {
- FILE* file = fopenat(procFd, "cgroup", "r");
- if (!file) {
- if (process->cgroup) {
- free(process->cgroup);
- process->cgroup = NULL;
- }
- if (process->cgroup_short) {
- free(process->cgroup_short);
- process->cgroup_short = NULL;
- }
- return;
- }
- char output[PROC_LINE_LENGTH + 1];
- output[0] = '\0';
- char* at = output;
- int left = PROC_LINE_LENGTH;
- while (!feof(file) && left > 0) {
- char buffer[PROC_LINE_LENGTH + 1];
- const char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
- if (!ok)
- break;
-
- char* group = buffer;
- for (size_t i = 0; i < 2; i++) {
- group = strchrnul(group, ':');
- if (!*group)
- break;
- group++;
- }
-
- char* eol = strchrnul(group, '\n');
- *eol = '\0';
-
- if (at != output) {
- *at = ';';
- at++;
- left--;
- }
- int wrote = snprintf(at, left, "%s", group);
- left -= wrote;
- }
- fclose(file);
-
- bool changed = !process->cgroup || !String_eq(process->cgroup, output);
-
- Process_updateFieldWidth(CGROUP, strlen(output));
- free_and_xStrdup(&process->cgroup, output);
-
- if (!changed) {
- if(process->cgroup_short) {
- Process_updateFieldWidth(CCGROUP, strlen(process->cgroup_short));
- } else {
- //CCGROUP is alias to normal CGROUP if shortening fails
- Process_updateFieldWidth(CCGROUP, strlen(process->cgroup));
- }
- return;
- }
-
- char* cgroup_short = CGroup_filterName(process->cgroup);
- if (cgroup_short) {
- Process_updateFieldWidth(CCGROUP, strlen(cgroup_short));
- free_and_xStrdup(&process->cgroup_short, cgroup_short);
- free(cgroup_short);
- } else {
- //CCGROUP is alias to normal CGROUP if shortening fails
- Process_updateFieldWidth(CCGROUP, strlen(process->cgroup));
- free(process->cgroup_short);
- process->cgroup_short = NULL;
- }
-}
-
-#ifdef HAVE_VSERVER
-
-static void LinuxProcessList_readVServerData(LinuxProcess* process, openat_arg_t procFd) {
- FILE* file = fopenat(procFd, "status", "r");
- if (!file)
- return;
-
- char buffer[PROC_LINE_LENGTH + 1];
- process->vxid = 0;
- while (fgets(buffer, PROC_LINE_LENGTH, file)) {
- if (String_startsWith(buffer, "VxID:")) {
- int vxid;
- int ok = sscanf(buffer, "VxID:\t%32d", &vxid);
- if (ok >= 1) {
- process->vxid = vxid;
- }
- }
- #if defined HAVE_ANCIENT_VSERVER
- else if (String_startsWith(buffer, "s_context:")) {
- int vxid;
- int ok = sscanf(buffer, "s_context:\t%32d", &vxid);
- if (ok >= 1) {
- process->vxid = vxid;
- }
- }
- #endif
- }
- fclose(file);
-}
-
-#endif
-
-static void LinuxProcessList_readOomData(LinuxProcess* process, openat_arg_t procFd) {
- FILE* file = fopenat(procFd, "oom_score", "r");
- if (!file)
- return;
-
- char buffer[PROC_LINE_LENGTH + 1];
- if (fgets(buffer, PROC_LINE_LENGTH, file)) {
- unsigned int oom;
- int ok = sscanf(buffer, "%u", &oom);
- if (ok >= 1) {
- process->oom = oom;
- }
- }
- fclose(file);
-}
-
-static void LinuxProcessList_readAutogroup(LinuxProcess* process, openat_arg_t procFd) {
- process->autogroup_id = -1;
-
- char autogroup[64]; // space for two numeric values and fixed length strings
- ssize_t amtRead = xReadfileat(procFd, "autogroup", autogroup, sizeof(autogroup));
- if (amtRead < 0)
- return;
-
- long int identity;
- int nice;
- int ok = sscanf(autogroup, "/autogroup-%ld nice %d", &identity, &nice);
- if (ok == 2) {
- process->autogroup_id = identity;
- process->autogroup_nice = nice;
- }
-}
-
-static void LinuxProcessList_readCtxtData(LinuxProcess* process, openat_arg_t procFd) {
- FILE* file = fopenat(procFd, "status", "r");
- if (!file)
- return;
-
- char buffer[PROC_LINE_LENGTH + 1];
- unsigned long ctxt = 0;
- while (fgets(buffer, PROC_LINE_LENGTH, file)) {
- if (String_startsWith(buffer, "voluntary_ctxt_switches:")) {
- unsigned long vctxt;
- int ok = sscanf(buffer, "voluntary_ctxt_switches:\t%lu", &vctxt);
- if (ok >= 1) {
- ctxt += vctxt;
- }
- } else if (String_startsWith(buffer, "nonvoluntary_ctxt_switches:")) {
- unsigned long nvctxt;
- int ok = sscanf(buffer, "nonvoluntary_ctxt_switches:\t%lu", &nvctxt);
- if (ok >= 1) {
- ctxt += nvctxt;
- }
- }
- }
- fclose(file);
- process->ctxt_diff = (ctxt > process->ctxt_total) ? (ctxt - process->ctxt_total) : 0;
- process->ctxt_total = ctxt;
-}
-
-static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t procFd) {
- FILE* file = fopenat(procFd, "attr/current", "r");
- if (!file) {
- free(process->secattr);
- process->secattr = NULL;
- return;
- }
-
- char buffer[PROC_LINE_LENGTH + 1];
- const char* res = fgets(buffer, sizeof(buffer), file);
- fclose(file);
- if (!res) {
- free(process->secattr);
- process->secattr = NULL;
- return;
- }
- char* newline = strchr(buffer, '\n');
- if (newline) {
- *newline = '\0';
- }
-
- Process_updateFieldWidth(SECATTR, strlen(buffer));
-
- if (process->secattr && String_eq(process->secattr, buffer)) {
- return;
- }
- free_and_xStrdup(&process->secattr, buffer);
-}
-
-static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd) {
- char pathBuffer[PATH_MAX + 1] = {0};
-
-#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)
- ssize_t r = readlinkat(procFd, "cwd", pathBuffer, sizeof(pathBuffer) - 1);
-#else
- char filename[MAX_NAME + 1];
- xSnprintf(filename, sizeof(filename), "%s/cwd", procFd);
- ssize_t r = readlink(filename, pathBuffer, sizeof(pathBuffer) - 1);
-#endif
-
- if (r < 0) {
- free(process->super.procCwd);
- process->super.procCwd = NULL;
- return;
- }
-
- pathBuffer[r] = '\0';
-
- if (process->super.procCwd && String_eq(process->super.procCwd, pathBuffer))
- return;
-
- free_and_xStrdup(&process->super.procCwd, pathBuffer);
-}
-
-#ifdef HAVE_DELAYACCT
-
-static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {
- struct nlmsghdr* nlhdr;
- struct nlattr* nlattrs[TASKSTATS_TYPE_MAX + 1];
- const struct nlattr* nlattr;
- struct taskstats stats;
- int rem;
- LinuxProcess* lp = (LinuxProcess*) linuxProcess;
-
- nlhdr = nlmsg_hdr(nlmsg);
-
- if (genlmsg_parse(nlhdr, 0, nlattrs, TASKSTATS_TYPE_MAX, NULL) < 0) {
- return NL_SKIP;
- }
-
- if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) {
- memcpy(&stats, nla_data(nla_next(nla_data(nlattr), &rem)), sizeof(stats));
- assert(lp->super.pid == (pid_t)stats.ac_pid);
-
- unsigned long long int timeDelta = stats.ac_etime * 1000 - lp->delay_read_time;
- #define BOUNDS(x) (isnan(x) ? 0.0 : ((x) > 100) ? 100.0 : (x))
- #define DELTAPERC(x,y) BOUNDS((float) ((x) - (y)) / timeDelta * 100)
- lp->cpu_delay_percent = DELTAPERC(stats.cpu_delay_total, lp->cpu_delay_total);
- lp->blkio_delay_percent = DELTAPERC(stats.blkio_delay_total, lp->blkio_delay_total);
- lp->swapin_delay_percent = DELTAPERC(stats.swapin_delay_total, lp->swapin_delay_total);
- #undef DELTAPERC
- #undef BOUNDS
-
- lp->swapin_delay_total = stats.swapin_delay_total;
- lp->blkio_delay_total = stats.blkio_delay_total;
- lp->cpu_delay_total = stats.cpu_delay_total;
- lp->delay_read_time = stats.ac_etime * 1000;
- }
- return NL_OK;
-}
-
-static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProcess* process) {
- struct nl_msg* msg;
-
- if (!this->netlink_socket) {
- LinuxProcessList_initNetlinkSocket(this);
- if (!this->netlink_socket) {
- goto delayacct_failure;
- }
- }
-
- if (nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) {
- goto delayacct_failure;
- }
-
- if (! (msg = nlmsg_alloc())) {
- goto delayacct_failure;
- }
-
- if (! genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, this->netlink_family, 0, NLM_F_REQUEST, TASKSTATS_CMD_GET, TASKSTATS_VERSION)) {
- nlmsg_free(msg);
- }
-
- if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, process->super.pid) < 0) {
- nlmsg_free(msg);
- }
-
- if (nl_send_sync(this->netlink_socket, msg) < 0) {
- goto delayacct_failure;
- }
-
- if (nl_recvmsgs_default(this->netlink_socket) < 0) {
- goto delayacct_failure;
- }
-
- return;
-
-delayacct_failure:
- process->swapin_delay_percent = NAN;
- process->blkio_delay_percent = NAN;
- process->cpu_delay_percent = NAN;
-}
-
-#endif
-
-static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t procFd) {
- char command[4096 + 1]; // max cmdline length on Linux
- ssize_t amtRead = xReadfileat(procFd, "cmdline", command, sizeof(command));
- if (amtRead <= 0)
- return false;
-
- int tokenEnd = 0;
- int tokenStart = 0;
- int lastChar = 0;
- bool argSepNUL = false;
- bool argSepSpace = false;
-
- for (int i = 0; i < amtRead; i++) {
- /* newline used as delimiter - when forming the mergedCommand, newline is
- * converted to space by Process_makeCommandStr */
- if (command[i] == '\0') {
- command[i] = '\n';
- } else {
- /* Record some information for the argument parsing heuristic below. */
- if (tokenEnd)
- argSepNUL = true;
- if (command[i] <= ' ')
- argSepSpace = true;
- }
-
- if (command[i] == '\n') {
- if (tokenEnd == 0) {
- tokenEnd = i;
- }
- } else {
- /* htop considers the next character after the last / that is before
- * basenameOffset, as the start of the basename in cmdline - see
- * Process_writeCommand */
- if (!tokenEnd && command[i] == '/') {
- tokenStart = i + 1;
- }
- lastChar = i;
- }
- }
-
- command[lastChar + 1] = '\0';
-
- if (!argSepNUL && argSepSpace) {
- /* Argument parsing heuristic.
- *
- * This heuristic is used for processes that rewrite their command line.
- * Normally the command line is split by using NUL bytes between each argument.
- * But some programs like chrome flatten this using spaces.
- *
- * This heuristic tries its best to undo this loss of information.
- * To achieve this, we treat every character <= 32 as argument separators
- * (i.e. all of ASCII control sequences and space).
- * We then search for the basename of the cmdline in the first argument we found that way.
- * As path names may contain we try to cross-validate if the path we got that way exists.
- */
-
- tokenStart = tokenEnd = 0;
-
- // From initial scan we know there's at least one space.
- // Check if that's part of a filename for an existing file.
- if (Compat_faccessat(AT_FDCWD, command, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
- // If we reach here the path does not exist.
- // Thus begin searching for the part of it that actually is.
-
- int tokenArg0Start = 0;
-
- for (int i = 0; i <= lastChar; i++) {
- /* Any ASCII control or space used as delimiter */
- char tmpCommandChar = command[i];
-
- if (command[i] <= ' ') {
- if (!tokenEnd) {
- command[i] = '\0';
-
- bool found = Compat_faccessat(AT_FDCWD, command, F_OK, AT_SYMLINK_NOFOLLOW) == 0;
-
- // Restore if this wasn't it
- command[i] = found ? '\n' : tmpCommandChar;
-
- if (found)
- tokenEnd = i;
- if (!tokenArg0Start)
- tokenArg0Start = tokenStart;
- } else {
- // Split on every further separator, regardless of path correctness
- command[i] = '\n';
- }
- } else if (!tokenEnd) {
- if (command[i] == '/' || (command[i] == '\\' && (!tokenStart || command[tokenStart - 1] == '\\'))) {
- tokenStart = i + 1;
- } else if (command[i] == ':' && (command[i + 1] != '/' && command[i + 1] != '\\')) {
- tokenEnd = i;
- }
- }
- }
-
- if (!tokenEnd) {
- tokenStart = tokenArg0Start;
-
- // No token delimiter found, forcibly split
- for (int i = 0; i <= lastChar; i++) {
- if (command[i] <= ' ') {
- command[i] = '\n';
- if (!tokenEnd) {
- tokenEnd = i;
- }
- }
- }
- }
- }
-
- /* Some command lines are hard to parse, like
- * file.so [kdeinit5] file local:/run/user/1000/klauncherdqbouY.1.slave-socket local:/run/user/1000/kded5TwsDAx.1.slave-socket
- * Reset if start is behind end.
- */
- if (tokenStart >= tokenEnd)
- tokenStart = tokenEnd = 0;
- }
-
- if (tokenEnd == 0) {
- tokenEnd = lastChar + 1;
- }
-
- Process_updateCmdline(process, command, tokenStart, tokenEnd);
-
- /* /proc/[pid]/comm could change, so should be updated */
- if ((amtRead = xReadfileat(procFd, "comm", command, sizeof(command))) > 0) {
- command[amtRead - 1] = '\0';
- Process_updateComm(process, command);
- } else {
- Process_updateComm(process, NULL);
- }
-
- char filename[MAX_NAME + 1];
-
- /* execve could change /proc/[pid]/exe, so procExe should be updated */
-#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)
- amtRead = readlinkat(procFd, "exe", filename, sizeof(filename) - 1);
-#else
- char path[4096];
- xSnprintf(path, sizeof(path), "%s/exe", procFd);
- amtRead = readlink(path, filename, sizeof(filename) - 1);
-#endif
- if (amtRead > 0) {
- filename[amtRead] = 0;
- if (!process->procExe ||
- (!process->procExeDeleted && !String_eq(filename, process->procExe)) ||
- process->procExeDeleted) {
-
- const char* deletedMarker = " (deleted)";
- const size_t markerLen = strlen(deletedMarker);
- const size_t filenameLen = strlen(filename);
-
- if (filenameLen > markerLen) {
- bool oldExeDeleted = process->procExeDeleted;
-
- process->procExeDeleted = String_eq(filename + filenameLen - markerLen, deletedMarker);
-
- if (process->procExeDeleted)
- filename[filenameLen - markerLen] = '\0';
-
- if (oldExeDeleted != process->procExeDeleted)
- process->mergedCommand.lastUpdate = 0;
- }
-
- Process_updateExe(process, filename);
- }
- } else if (process->procExe) {
- Process_updateExe(process, NULL);
- process->procExeDeleted = false;
- }
-
- return true;
-}
-
-static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned long int tty_nr) {
- unsigned int maj = major(tty_nr);
- unsigned int min = minor(tty_nr);
-
- int i = -1;
- for (;;) {
- i++;
- if ((!ttyDrivers[i].path) || maj < ttyDrivers[i].major) {
- break;
- }
- if (maj > ttyDrivers[i].major) {
- continue;
- }
- if (min < ttyDrivers[i].minorFrom) {
- break;
- }
- if (min > ttyDrivers[i].minorTo) {
- continue;
- }
- unsigned int idx = min - ttyDrivers[i].minorFrom;
- struct stat sstat;
- char* fullPath;
- for (;;) {
- xAsprintf(&fullPath, "%s/%d", ttyDrivers[i].path, idx);
- int err = stat(fullPath, &sstat);
- if (err == 0 && major(sstat.st_rdev) == maj && minor(sstat.st_rdev) == min) {
- return fullPath;
- }
- free(fullPath);
-
- xAsprintf(&fullPath, "%s%d", ttyDrivers[i].path, idx);
- err = stat(fullPath, &sstat);
- if (err == 0 && major(sstat.st_rdev) == maj && minor(sstat.st_rdev) == min) {
- return fullPath;
- }
- free(fullPath);
-
- if (idx == min) {
- break;
- }
-
- idx = min;
- }
- int err = stat(ttyDrivers[i].path, &sstat);
- if (err == 0 && tty_nr == sstat.st_rdev) {
- return xStrdup(ttyDrivers[i].path);
- }
- }
- char* out;
- xAsprintf(&out, "/dev/%u:%u", maj, min);
- return out;
-}
-
-static bool isOlderThan(const ProcessList* pl, const Process* proc, unsigned int seconds) {
- assert(pl->realtimeMs > 0);
-
- /* Starttime might not yet be parsed */
- if (proc->starttime_ctime <= 0)
- return false;
-
- uint64_t realtime = pl->realtimeMs / 1000;
-
- if (realtime < (uint64_t)proc->starttime_ctime)
- return false;
-
- return realtime - proc->starttime_ctime > seconds;
-}
-
-static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const char* dirname, const Process* parent, double period) {
- ProcessList* pl = (ProcessList*) this;
- const struct dirent* entry;
- const Settings* settings = pl->settings;
- const ScreenSettings* ss = settings->ss;
-
-#ifdef HAVE_OPENAT
- int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
- if (dirFd < 0)
- return false;
- DIR* dir = fdopendir(dirFd);
-#else
- char dirFd[4096];
- xSnprintf(dirFd, sizeof(dirFd), "%s/%s", parentFd, dirname);
- DIR* dir = opendir(dirFd);
-#endif
- if (!dir) {
- Compat_openatArgClose(dirFd);
- return false;
- }
-
- const unsigned int activeCPUs = pl->activeCPUs;
- const bool hideKernelThreads = settings->hideKernelThreads;
- const bool hideUserlandThreads = settings->hideUserlandThreads;
- while ((entry = readdir(dir)) != NULL) {
- const char* name = entry->d_name;
-
- // Ignore all non-directories
- if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) {
- continue;
- }
-
- // The RedHat kernel hides threads with a dot.
- // I believe this is non-standard.
- if (name[0] == '.') {
- name++;
- }
-
- // Just skip all non-number directories.
- if (name[0] < '0' || name[0] > '9') {
- continue;
- }
-
- // filename is a number: process directory
- int pid;
- {
- char* endptr;
- unsigned long parsedPid = strtoul(name, &endptr, 10);
- if (parsedPid == 0 || parsedPid == ULONG_MAX || *endptr != '\0')
- continue;
- pid = parsedPid;
- }
-
- // Skip task directory of main thread
- if (parent && pid == parent->pid)
- continue;
-
-#ifdef HAVE_OPENAT
- int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
- if (procFd < 0)
- continue;
-#else
- char procFd[4096];
- xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name);
-#endif
-
- bool preExisting;
- Process* proc = ProcessList_getProcess(pl, pid, &preExisting, LinuxProcess_new);
- LinuxProcess* lp = (LinuxProcess*) proc;
-
- proc->tgid = parent ? parent->pid : pid;
- proc->isUserlandThread = proc->pid != proc->tgid;
-
- LinuxProcessList_recurseProcTree(this, procFd, "task", proc, period);
-
- /*
- * These conditions will not trigger on first occurrence, cause we need to
- * add the process to the ProcessList and do all one time scans
- * (e.g. parsing the cmdline to detect a kernel thread)
- * But it will short-circuit subsequent scans.
- */
- if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {
- proc->updated = true;
- proc->show = false;
- pl->kernelThreads++;
- pl->totalTasks++;
- Compat_openatArgClose(procFd);
- continue;
- }
- if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
- proc->updated = true;
- proc->show = false;
- pl->userlandThreads++;
- pl->totalTasks++;
- Compat_openatArgClose(procFd);
- continue;
- }
-
- if (ss->flags & PROCESS_FLAG_IO)
- LinuxProcessList_readIoFile(lp, procFd, pl->realtimeMs);
-
- if (!LinuxProcessList_readStatmFile(lp, procFd))
- goto errorReadingProcess;
-
- {
- bool prev = proc->usesDeletedLib;
-
- if (!proc->isKernelThread && !proc->isUserlandThread &&
- ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(pl, proc, 10)))) {
-
- // Check if we really should recalculate the M_LRS value for this process
- uint64_t passedTimeInMs = pl->realtimeMs - lp->last_mlrs_calctime;
-
- uint64_t recheck = ((uint64_t)rand()) % 2048;
-
- if (passedTimeInMs > recheck) {
- lp->last_mlrs_calctime = pl->realtimeMs;
- LinuxProcessList_readMaps(lp, procFd, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe);
- }
- } else {
- /* Copy from process structure in threads and reset if setting got disabled */
- proc->usesDeletedLib = (proc->isUserlandThread && parent) ? parent->usesDeletedLib : false;
- lp->m_lrs = (proc->isUserlandThread && parent) ? ((const LinuxProcess*)parent)->m_lrs : 0;
- }
-
- if (prev != proc->usesDeletedLib)
- proc->mergedCommand.lastUpdate = 0;
- }
-
- if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
- if (!parent) {
- // Read smaps file of each process only every second pass to improve performance
- static int smaps_flag = 0;
- if ((pid & 1) == smaps_flag) {
- LinuxProcessList_readSmapsFile(lp, procFd, this->haveSmapsRollup);
- }
- if (pid == 1) {
- smaps_flag = !smaps_flag;
- }
- } else {
- lp->m_pss = ((const LinuxProcess*)parent)->m_pss;
- }
- }
-
- char statCommand[MAX_NAME + 1];
- unsigned long long int lasttimes = (lp->utime + lp->stime);
- unsigned long int tty_nr = proc->tty_nr;
- if (! LinuxProcessList_readStatFile(proc, procFd, statCommand, sizeof(statCommand)))
- goto errorReadingProcess;
-
- if (lp->flags & PF_KTHREAD) {
- proc->isKernelThread = true;
- }
-
- if (tty_nr != proc->tty_nr && this->ttyDrivers) {
- free(proc->tty_name);
- proc->tty_name = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
- }
-
- if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) {
- LinuxProcess_updateIOPriority(lp);
- }
-
- /* period might be 0 after system sleep */
- float percent_cpu = (period < 1E-6) ? 0.0F : ((lp->utime + lp->stime - lasttimes) / period * 100.0);
- proc->percent_cpu = CLAMP(percent_cpu, 0.0F, activeCPUs * 100.0F);
- proc->percent_mem = proc->m_resident / (double)(pl->totalMem) * 100.0;
- Process_updateCPUFieldWidths(proc->percent_cpu);
-
- if (! LinuxProcessList_updateUser(pl, proc, procFd))
- goto errorReadingProcess;
-
- if (!preExisting) {
-
- #ifdef HAVE_OPENVZ
- if (ss->flags & PROCESS_FLAG_LINUX_OPENVZ) {
- LinuxProcessList_readOpenVZData(lp, procFd);
- }
- #endif
-
- #ifdef HAVE_VSERVER
- if (ss->flags & PROCESS_FLAG_LINUX_VSERVER) {
- LinuxProcessList_readVServerData(lp, procFd);
- }
- #endif
-
- if (proc->isKernelThread) {
- Process_updateCmdline(proc, NULL, 0, 0);
- } else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) {
- Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
- }
-
- Process_fillStarttimeBuffer(proc);
-
- ProcessList_add(pl, proc);
- } else {
- if (settings->updateProcessNames && proc->state != ZOMBIE) {
- if (proc->isKernelThread) {
- Process_updateCmdline(proc, NULL, 0, 0);
- } else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) {
- Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
- }
- }
- }
-
- #ifdef HAVE_DELAYACCT
- if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) {
- LinuxProcessList_readDelayAcctData(this, lp);
- }
- #endif
-
- if (ss->flags & PROCESS_FLAG_LINUX_CGROUP) {
- LinuxProcessList_readCGroupFile(lp, procFd);
- }
-
- if (ss->flags & PROCESS_FLAG_LINUX_OOM) {
- LinuxProcessList_readOomData(lp, procFd);
- }
-
- if (ss->flags & PROCESS_FLAG_LINUX_CTXT) {
- LinuxProcessList_readCtxtData(lp, procFd);
- }
-
- if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) {
- LinuxProcessList_readSecattrData(lp, procFd);
- }
-
- if (ss->flags & PROCESS_FLAG_CWD) {
- LinuxProcessList_readCwd(lp, procFd);
- }
-
- if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {
- LinuxProcessList_readAutogroup(lp, procFd);
- }
-
- if (!proc->cmdline && statCommand[0] &&
- (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) {
- Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
- }
-
- if (Process_isKernelThread(proc)) {
- pl->kernelThreads++;
- } else if (Process_isUserlandThread(proc)) {
- pl->userlandThreads++;
- }
-
- /* Set at the end when we know if a new entry is a thread */
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
- pl->totalTasks++;
- /* runningTasks is set in LinuxProcessList_scanCPUTime() from /proc/stat */
- proc->updated = true;
- Compat_openatArgClose(procFd);
- continue;
-
- // Exception handler.
-
-errorReadingProcess:
- {
-#ifdef HAVE_OPENAT
- if (procFd >= 0)
- close(procFd);
-#endif
-
- if (preExisting) {
- /*
- * The only real reason for coming here (apart from Linux violating the /proc API)
- * would be the process going away with its /proc files disappearing (!HAVE_OPENAT).
- * However, we want to keep in the process list for now for the "highlight dying" mode.
- */
- } else {
- /* A really short-lived process that we don't have full info about */
- Process_delete((Object*)proc);
- }
- }
- }
- closedir(dir);
- return true;
-}
-
-static inline void LinuxProcessList_scanMemoryInfo(ProcessList* this) {
- memory_t availableMem = 0;
- memory_t freeMem = 0;
- memory_t totalMem = 0;
- memory_t buffersMem = 0;
- memory_t cachedMem = 0;
- memory_t sharedMem = 0;
- memory_t swapTotalMem = 0;
- memory_t swapCacheMem = 0;
- memory_t swapFreeMem = 0;
- memory_t sreclaimableMem = 0;
-
- FILE* file = fopen(PROCMEMINFOFILE, "r");
- if (!file)
- CRT_fatalError("Cannot open " PROCMEMINFOFILE);
-
- char buffer[128];
- while (fgets(buffer, sizeof(buffer), file)) {
-
- #define tryRead(label, variable) \
- if (String_startsWith(buffer, label)) { \
- memory_t parsed_; \
- if (sscanf(buffer + strlen(label), "%llu kB", &parsed_) == 1) { \
- (variable) = parsed_; \
- } \
- break; \
- } else (void) 0 /* Require a ";" after the macro use. */
-
- switch (buffer[0]) {
- case 'M':
- tryRead("MemAvailable:", availableMem);
- tryRead("MemFree:", freeMem);
- tryRead("MemTotal:", totalMem);
- break;
- case 'B':
- tryRead("Buffers:", buffersMem);
- break;
- case 'C':
- tryRead("Cached:", cachedMem);
- break;
- case 'S':
- switch (buffer[1]) {
- case 'h':
- tryRead("Shmem:", sharedMem);
- break;
- case 'w':
- tryRead("SwapTotal:", swapTotalMem);
- tryRead("SwapCached:", swapCacheMem);
- tryRead("SwapFree:", swapFreeMem);
- break;
- case 'R':
- tryRead("SReclaimable:", sreclaimableMem);
- break;
- }
- break;
- }
-
- #undef tryRead
- }
-
- fclose(file);
-
- /*
- * Compute memory partition like procps(free)
- * https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c
- *
- * Adjustments:
- * - Shmem in part of Cached (see https://lore.kernel.org/patchwork/patch/648763/),
- * do not show twice by subtracting from Cached and do not subtract twice from used.
- */
- this->totalMem = totalMem;
- this->cachedMem = cachedMem + sreclaimableMem - sharedMem;
- this->sharedMem = sharedMem;
- const memory_t usedDiff = freeMem + cachedMem + sreclaimableMem + buffersMem;
- this->usedMem = (totalMem >= usedDiff) ? totalMem - usedDiff : totalMem - freeMem;
- this->buffersMem = buffersMem;
- this->availableMem = availableMem != 0 ? MINIMUM(availableMem, totalMem) : freeMem;
- this->totalSwap = swapTotalMem;
- this->usedSwap = swapTotalMem - swapFreeMem - swapCacheMem;
- this->cachedSwap = swapCacheMem;
-}
-
-static void LinuxProcessList_scanHugePages(LinuxProcessList* this) {
- this->totalHugePageMem = 0;
- for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {
- this->usedHugePageMem[i] = MEMORY_MAX;
- }
-
- DIR* dir = opendir("/sys/kernel/mm/hugepages");
- if (!dir)
- return;
-
- const struct dirent* entry;
- while ((entry = readdir(dir)) != NULL) {
- const char* name = entry->d_name;
-
- /* Ignore all non-directories */
- if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
- continue;
-
- if (!String_startsWith(name, "hugepages-"))
- continue;
-
- char* endptr;
- unsigned long int hugePageSize = strtoul(name + strlen("hugepages-"), &endptr, 10);
- if (!endptr || *endptr != 'k')
- continue;
-
- char content[64];
- char hugePagePath[128];
- ssize_t r;
-
- xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/nr_hugepages", name);
- r = xReadfile(hugePagePath, content, sizeof(content));
- if (r <= 0)
- continue;
-
- memory_t total = strtoull(content, NULL, 10);
- if (total == 0)
- continue;
-
- xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/free_hugepages", name);
- r = xReadfile(hugePagePath, content, sizeof(content));
- if (r <= 0)
- continue;
-
- memory_t free = strtoull(content, NULL, 10);
-
- int shift = ffsl(hugePageSize) - 1 - (HTOP_HUGEPAGE_BASE_SHIFT - 10);
- assert(shift >= 0 && shift < HTOP_HUGEPAGE_COUNT);
-
- this->totalHugePageMem += total * hugePageSize;
- this->usedHugePageMem[shift] = (total - free) * hugePageSize;
- }
-
- closedir(dir);
-}
-
-static inline void LinuxProcessList_scanZramInfo(LinuxProcessList* this) {
- memory_t totalZram = 0;
- memory_t usedZramComp = 0;
- memory_t usedZramOrig = 0;
-
- char mm_stat[34];
- char disksize[34];
-
- unsigned int i = 0;
- for (;;) {
- xSnprintf(mm_stat, sizeof(mm_stat), "/sys/block/zram%u/mm_stat", i);
- xSnprintf(disksize, sizeof(disksize), "/sys/block/zram%u/disksize", i);
- i++;
- FILE* disksize_file = fopen(disksize, "r");
- FILE* mm_stat_file = fopen(mm_stat, "r");
- if (disksize_file == NULL || mm_stat_file == NULL) {
- if (disksize_file) {
- fclose(disksize_file);
- }
- if (mm_stat_file) {
- fclose(mm_stat_file);
- }
- break;
- }
- memory_t size = 0;
- memory_t orig_data_size = 0;
- memory_t compr_data_size = 0;
-
- if (!fscanf(disksize_file, "%llu\n", &size) ||
- !fscanf(mm_stat_file, " %llu %llu", &orig_data_size, &compr_data_size)) {
- fclose(disksize_file);
- fclose(mm_stat_file);
- break;
- }
-
- totalZram += size;
- usedZramComp += compr_data_size;
- usedZramOrig += orig_data_size;
-
- fclose(disksize_file);
- fclose(mm_stat_file);
- }
-
- this->zram.totalZram = totalZram / 1024;
- this->zram.usedZramComp = usedZramComp / 1024;
- this->zram.usedZramOrig = usedZramOrig / 1024;
-}
-
-static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
- memory_t dbufSize = 0;
- memory_t dnodeSize = 0;
- memory_t bonusSize = 0;
-
- FILE* file = fopen(PROCARCSTATSFILE, "r");
- if (file == NULL) {
- lpl->zfs.enabled = 0;
- return;
- }
- char buffer[128];
- while (fgets(buffer, 128, file)) {
- #define tryRead(label, variable) \
- if (String_startsWith(buffer, label)) { \
- sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
- break; \
- } else (void) 0 /* Require a ";" after the macro use. */
- #define tryReadFlag(label, variable, flag) \
- if (String_startsWith(buffer, label)) { \
- (flag) = sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
- break; \
- } else (void) 0 /* Require a ";" after the macro use. */
-
- switch (buffer[0]) {
- case 'c':
- tryRead("c_min", &lpl->zfs.min);
- tryRead("c_max", &lpl->zfs.max);
- tryReadFlag("compressed_size", &lpl->zfs.compressed, lpl->zfs.isCompressed);
- break;
- case 'u':
- tryRead("uncompressed_size", &lpl->zfs.uncompressed);
- break;
- case 's':
- tryRead("size", &lpl->zfs.size);
- break;
- case 'h':
- tryRead("hdr_size", &lpl->zfs.header);
- break;
- case 'd':
- tryRead("dbuf_size", &dbufSize);
- tryRead("dnode_size", &dnodeSize);
- break;
- case 'b':
- tryRead("bonus_size", &bonusSize);
- break;
- case 'a':
- tryRead("anon_size", &lpl->zfs.anon);
- break;
- case 'm':
- tryRead("mfu_size", &lpl->zfs.MFU);
- tryRead("mru_size", &lpl->zfs.MRU);
- break;
- }
- #undef tryRead
- #undef tryReadFlag
- }
- fclose(file);
-
- lpl->zfs.enabled = (lpl->zfs.size > 0 ? 1 : 0);
- lpl->zfs.size /= 1024;
- lpl->zfs.min /= 1024;
- lpl->zfs.max /= 1024;
- lpl->zfs.MFU /= 1024;
- lpl->zfs.MRU /= 1024;
- lpl->zfs.anon /= 1024;
- lpl->zfs.header /= 1024;
- lpl->zfs.other = (dbufSize + dnodeSize + bonusSize) / 1024;
- if ( lpl->zfs.isCompressed ) {
- lpl->zfs.compressed /= 1024;
- lpl->zfs.uncompressed /= 1024;
- }
-}
-
-static inline double LinuxProcessList_scanCPUTime(ProcessList* super) {
- LinuxProcessList* this = (LinuxProcessList*) super;
-
- LinuxProcessList_updateCPUcount(super);
-
- FILE* file = fopen(PROCSTATFILE, "r");
- if (!file)
- CRT_fatalError("Cannot open " PROCSTATFILE);
-
- unsigned int existingCPUs = super->existingCPUs;
- unsigned int lastAdjCpuId = 0;
-
- for (unsigned int i = 0; i <= existingCPUs; i++) {
- char buffer[PROC_LINE_LENGTH + 1];
- unsigned long long int usertime, nicetime, systemtime, idletime;
- unsigned long long int ioWait = 0, irq = 0, softIrq = 0, steal = 0, guest = 0, guestnice = 0;
-
- const char* ok = fgets(buffer, sizeof(buffer), file);
- if (!ok)
- break;
-
- // cpu fields are sorted first
- if (!String_startsWith(buffer, "cpu"))
- break;
-
- // Depending on your kernel version,
- // 5, 7, 8 or 9 of these fields will be set.
- // The rest will remain at zero.
- unsigned int adjCpuId;
- if (i == 0) {
- (void) sscanf(buffer, "cpu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
- adjCpuId = 0;
- } else {
- unsigned int cpuid;
- (void) sscanf(buffer, "cpu%4u %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
- adjCpuId = cpuid + 1;
- }
-
- if (adjCpuId > super->existingCPUs)
- break;
-
- for (unsigned int j = lastAdjCpuId + 1; j < adjCpuId; j++) {
- // Skipped an ID, but /proc/stat is ordered => got offline CPU
- memset(&(this->cpuData[j]), '\0', sizeof(CPUData));
- }
- lastAdjCpuId = adjCpuId;
-
- // Guest time is already accounted in usertime
- usertime -= guest;
- nicetime -= guestnice;
- // Fields existing on kernels >= 2.6
- // (and RHEL's patched kernel 2.4...)
- unsigned long long int idlealltime = idletime + ioWait;
- unsigned long long int systemalltime = systemtime + irq + softIrq;
- unsigned long long int virtalltime = guest + guestnice;
- unsigned long long int totaltime = usertime + nicetime + systemalltime + idlealltime + steal + virtalltime;
- CPUData* cpuData = &(this->cpuData[adjCpuId]);
- // Since we do a subtraction (usertime - guest) and cputime64_to_clock_t()
- // used in /proc/stat rounds down numbers, it can lead to a case where the
- // integer overflow.
- cpuData->userPeriod = saturatingSub(usertime, cpuData->userTime);
- cpuData->nicePeriod = saturatingSub(nicetime, cpuData->niceTime);
- cpuData->systemPeriod = saturatingSub(systemtime, cpuData->systemTime);
- cpuData->systemAllPeriod = saturatingSub(systemalltime, cpuData->systemAllTime);
- cpuData->idleAllPeriod = saturatingSub(idlealltime, cpuData->idleAllTime);
- cpuData->idlePeriod = saturatingSub(idletime, cpuData->idleTime);
- cpuData->ioWaitPeriod = saturatingSub(ioWait, cpuData->ioWaitTime);
- cpuData->irqPeriod = saturatingSub(irq, cpuData->irqTime);
- cpuData->softIrqPeriod = saturatingSub(softIrq, cpuData->softIrqTime);
- cpuData->stealPeriod = saturatingSub(steal, cpuData->stealTime);
- cpuData->guestPeriod = saturatingSub(virtalltime, cpuData->guestTime);
- cpuData->totalPeriod = saturatingSub(totaltime, cpuData->totalTime);
- cpuData->userTime = usertime;
- cpuData->niceTime = nicetime;
- cpuData->systemTime = systemtime;
- cpuData->systemAllTime = systemalltime;
- cpuData->idleAllTime = idlealltime;
- cpuData->idleTime = idletime;
- cpuData->ioWaitTime = ioWait;
- cpuData->irqTime = irq;
- cpuData->softIrqTime = softIrq;
- cpuData->stealTime = steal;
- cpuData->guestTime = virtalltime;
- cpuData->totalTime = totaltime;
- }
-
- double period = (double)this->cpuData[0].totalPeriod / super->activeCPUs;
-
- char buffer[PROC_LINE_LENGTH + 1];
- while (fgets(buffer, sizeof(buffer), file)) {
- if (String_startsWith(buffer, "procs_running")) {
- super->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10);
- break;
- }
- }
-
- fclose(file);
-
- return period;
-}
-
-static int scanCPUFrequencyFromSysCPUFreq(LinuxProcessList* this) {
- unsigned int existingCPUs = this->super.existingCPUs;
- int numCPUsWithFrequency = 0;
- unsigned long totalFrequency = 0;
-
- /*
- * On some AMD and Intel CPUs read()ing scaling_cur_freq is quite slow (> 1ms). This delay
- * accumulates for every core. For details see issue#471.
- * If the read on CPU 0 takes longer than 500us bail out and fall back to reading the
- * frequencies from /proc/cpuinfo.
- * Once the condition has been met, bail out early for the next couple of scans.
- */
- static int timeout = 0;
-
- if (timeout > 0) {
- timeout--;
- return -1;
- }
-
- for (unsigned int i = 0; i < existingCPUs; ++i) {
- if (!ProcessList_isCPUonline(&this->super, i))
- continue;
-
- char pathBuffer[64];
- xSnprintf(pathBuffer, sizeof(pathBuffer), "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i);
-
- struct timespec start;
- if (i == 0)
- clock_gettime(CLOCK_MONOTONIC, &start);
-
- FILE* file = fopen(pathBuffer, "r");
- if (!file)
- return -errno;
-
- unsigned long frequency;
- if (fscanf(file, "%lu", &frequency) == 1) {
- /* convert kHz to MHz */
- frequency = frequency / 1000;
- this->cpuData[i + 1].frequency = frequency;
- numCPUsWithFrequency++;
- totalFrequency += frequency;
- }
-
- fclose(file);
-
- if (i == 0) {
- struct timespec end;
- clock_gettime(CLOCK_MONOTONIC, &end);
- const time_t timeTakenUs = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;
- if (timeTakenUs > 500) {
- timeout = 30;
- return -1;
- }
- }
-
- }
-
- if (numCPUsWithFrequency > 0)
- this->cpuData[0].frequency = (double)totalFrequency / numCPUsWithFrequency;
-
- return 0;
-}
-
-static void scanCPUFrequencyFromCPUinfo(LinuxProcessList* this) {
- FILE* file = fopen(PROCCPUINFOFILE, "r");
- if (file == NULL)
- return;
-
- unsigned int existingCPUs = this->super.existingCPUs;
- int numCPUsWithFrequency = 0;
- double totalFrequency = 0;
- int cpuid = -1;
-
- while (!feof(file)) {
- double frequency;
- char buffer[PROC_LINE_LENGTH];
-
- if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
- break;
-
- if (sscanf(buffer, "processor : %d", &cpuid) == 1) {
- continue;
- } else if (
- (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
- (sscanf(buffer, "clock : %lfMHz", &frequency) == 1)
- ) {
- if (cpuid < 0 || (unsigned int)cpuid > (existingCPUs - 1)) {
- continue;
- }
-
- CPUData* cpuData = &(this->cpuData[cpuid + 1]);
- /* do not override sysfs data */
- if (isnan(cpuData->frequency)) {
- cpuData->frequency = frequency;
- }
- numCPUsWithFrequency++;
- totalFrequency += frequency;
- } else if (buffer[0] == '\n') {
- cpuid = -1;
- }
- }
- fclose(file);
-
- if (numCPUsWithFrequency > 0) {
- this->cpuData[0].frequency = totalFrequency / numCPUsWithFrequency;
- }
-}
-
-static void LinuxProcessList_scanCPUFrequency(LinuxProcessList* this) {
- unsigned int existingCPUs = this->super.existingCPUs;
-
- for (unsigned int i = 0; i <= existingCPUs; i++) {
- this->cpuData[i].frequency = NAN;
- }
-
- if (scanCPUFrequencyFromSysCPUFreq(this) == 0) {
- return;
- }
-
- scanCPUFrequencyFromCPUinfo(this);
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
- LinuxProcessList* this = (LinuxProcessList*) super;
- const Settings* settings = super->settings;
-
- LinuxProcessList_scanMemoryInfo(super);
- LinuxProcessList_scanHugePages(this);
- LinuxProcessList_scanZfsArcstats(this);
- LinuxProcessList_scanZramInfo(this);
-
- double period = LinuxProcessList_scanCPUTime(super);
-
- if (settings->showCPUFrequency) {
- LinuxProcessList_scanCPUFrequency(this);
- }
-
- #ifdef HAVE_SENSORS_SENSORS_H
- if (settings->showCPUTemperature)
- LibSensors_getCPUTemperatures(this->cpuData, this->super.existingCPUs, this->super.activeCPUs);
- #endif
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
- if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) {
- // Refer to sched(7) 'autogroup feature' section
- // The kernel feature can be enabled/disabled through procfs at
- // any time, so check for it at the start of each sample - only
- // read from per-process procfs files if it's globally enabled.
- this->haveAutogroup = LinuxProcess_isAutogroupEnabled();
- } else {
- this->haveAutogroup = false;
- }
-
- /* PROCDIR is an absolute path */
- assert(PROCDIR[0] == '/');
-#ifdef HAVE_OPENAT
- openat_arg_t rootFd = AT_FDCWD;
-#else
- openat_arg_t rootFd = "";
-#endif
-
- LinuxProcessList_recurseProcTree(this, rootFd, PROCDIR, NULL, period);
-}
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
- assert(id < super->existingCPUs);
-
- const LinuxProcessList* this = (const LinuxProcessList*) super;
- return this->cpuData[id + 1].online;
-}
diff --git a/linux/LinuxProcessTable.c b/linux/LinuxProcessTable.c
new file mode 100644
index 00000000..73f69106
--- /dev/null
+++ b/linux/LinuxProcessTable.c
@@ -0,0 +1,1800 @@
+/*
+htop - LinuxProcessTable.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/LinuxProcessTable.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syscall.h>
+#include <unistd.h>
+#include <linux/capability.h> // raw syscall, no libcap // IWYU pragma: keep // IWYU pragma: no_include <sys/capability.h>
+#include <sys/stat.h>
+
+#include "Compat.h"
+#include "Hashtable.h"
+#include "Machine.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Process.h"
+#include "Row.h"
+#include "RowField.h"
+#include "Scheduling.h"
+#include "Settings.h"
+#include "Table.h"
+#include "UsersTable.h"
+#include "XUtils.h"
+#include "linux/CGroupUtils.h"
+#include "linux/GPU.h"
+#include "linux/GPUMeter.h"
+#include "linux/LinuxMachine.h"
+#include "linux/LinuxProcess.h"
+#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep
+
+#ifdef HAVE_DELAYACCT
+#include "linux/LibNl.h"
+#endif
+
+#if defined(MAJOR_IN_MKDEV)
+#include <sys/mkdev.h>
+#elif defined(MAJOR_IN_SYSMACROS)
+#include <sys/sysmacros.h>
+#endif
+
+/* Not exposed yet. Defined at include/linux/sched.h */
+#ifndef PF_KTHREAD
+#define PF_KTHREAD 0x00200000
+#endif
+
+/* Inode number of the PID namespace of htop */
+static ino_t rootPidNs = (ino_t)-1;
+
+
+static FILE* fopenat(openat_arg_t openatArg, const char* pathname, const char* mode) {
+ assert(String_eq(mode, "r")); /* only currently supported mode */
+
+ int fd = Compat_openat(openatArg, pathname, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ FILE* fp = fdopen(fd, mode);
+ if (!fp)
+ close(fd);
+
+ return fp;
+}
+
+static inline uint64_t fast_strtoull_dec(char** str, int maxlen) {
+ register uint64_t result = 0;
+
+ if (!maxlen)
+ --maxlen;
+
+ while (maxlen-- && **str >= '0' && **str <= '9') {
+ result *= 10;
+ result += **str - '0';
+ (*str)++;
+ }
+
+ return result;
+}
+
+static inline uint64_t fast_strtoull_hex(char** str, int maxlen) {
+ register uint64_t result = 0;
+ register int nibble, letter;
+ const long valid_mask = 0x03FF007E;
+
+ if (!maxlen)
+ --maxlen;
+
+ while (maxlen--) {
+ nibble = (unsigned char)**str;
+ if (!(valid_mask & (1 << (nibble & 0x1F))))
+ break;
+ if ((nibble < '0') || (nibble & ~0x20) > 'F')
+ break;
+ letter = (nibble & 0x40) ? 'A' - '9' - 1 : 0;
+ nibble &=~0x20; // to upper
+ nibble ^= 0x10; // switch letters and digits
+ nibble -= letter;
+ nibble &= 0x0f;
+ result <<= 4;
+ result += (uint64_t)nibble;
+ (*str)++;
+ }
+
+ return result;
+}
+
+static int sortTtyDrivers(const void* va, const void* vb) {
+ const TtyDriver* a = (const TtyDriver*) va;
+ const TtyDriver* b = (const TtyDriver*) vb;
+
+ int r = SPACESHIP_NUMBER(a->major, b->major);
+ if (r)
+ return r;
+
+ return SPACESHIP_NUMBER(a->minorFrom, b->minorFrom);
+}
+
+static void LinuxProcessTable_initTtyDrivers(LinuxProcessTable* this) {
+ TtyDriver* ttyDrivers;
+
+ char buf[16384];
+ ssize_t r = xReadfile(PROCTTYDRIVERSFILE, buf, sizeof(buf));
+ if (r < 0)
+ return;
+
+ size_t numDrivers = 0;
+ size_t allocd = 10;
+ ttyDrivers = xMallocArray(allocd, sizeof(TtyDriver));
+ char* at = buf;
+ char* path = NULL;
+ while (at && *at != '\0') {
+ /*
+ * Format:
+ * [name] [node path] [major] [minor range] [type]
+ * serial /dev/ttyS 4 64-95 serial
+ */
+
+ at = strchr(at, ' '); // skip first token
+ if (!at)
+ goto finish; // bail out on truncation
+ while (*at == ' ') at++; // skip spaces
+
+ const char* token = at; // mark beginning of path
+ at = strchr(at, ' '); // find end of path
+ if (!at)
+ goto finish; // bail out on truncation
+ *at = '\0'; at++; // clear and skip
+ path = xStrdup(token); // save
+ while (*at == ' ') at++; // skip spaces
+
+ token = at; // mark beginning of major
+ at = strchr(at, ' '); // find end of major
+ if (!at)
+ goto finish; // bail out on truncation
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].major = atoi(token); // save
+ while (*at == ' ') at++; // skip spaces
+
+ token = at; // mark beginning of minorFrom
+ while (*at >= '0' && *at <= '9') at++; //find end of minorFrom
+ if (*at == '-') { // if has range
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].minorFrom = atoi(token); // save
+ token = at; // mark beginning of minorTo
+ at = strchr(at, ' '); // find end of minorTo
+ if (!at)
+ goto finish; // bail out on truncation
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].minorTo = atoi(token); // save
+ } else { // no range
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].minorFrom = atoi(token); // save
+ ttyDrivers[numDrivers].minorTo = atoi(token); // save
+ }
+
+ at = strchr(at, '\n'); // go to end of line
+ if (at)
+ at++; // skip
+ ttyDrivers[numDrivers].path = path;
+ path = NULL;
+ numDrivers++;
+ if (numDrivers == allocd) {
+ allocd += 10;
+ ttyDrivers = xReallocArray(ttyDrivers, allocd, sizeof(TtyDriver));
+ }
+ }
+finish:
+ free(path);
+
+ ttyDrivers = xRealloc(ttyDrivers, sizeof(TtyDriver) * (numDrivers + 1));
+ ttyDrivers[numDrivers].path = NULL;
+ qsort(ttyDrivers, numDrivers, sizeof(TtyDriver), sortTtyDrivers);
+ this->ttyDrivers = ttyDrivers;
+}
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ LinuxProcessTable* this = xCalloc(1, sizeof(LinuxProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(LinuxProcess), host, pidMatchList);
+
+ LinuxProcessTable_initTtyDrivers(this);
+
+ // Test /proc/PID/smaps_rollup availability (faster to parse, Linux 4.14+)
+ this->haveSmapsRollup = (access(PROCDIR "/self/smaps_rollup", R_OK) == 0);
+
+ // Read PID namespace inode number
+ {
+ struct stat sb;
+ int r = stat(PROCDIR "/self/ns/pid", &sb);
+ if (r == 0) {
+ rootPidNs = sb.st_ino;
+ } else {
+ rootPidNs = (ino_t)-1;
+ }
+ }
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ LinuxProcessTable* this = (LinuxProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ if (this->ttyDrivers) {
+ for (int i = 0; this->ttyDrivers[i].path; i++) {
+ free(this->ttyDrivers[i].path);
+ }
+ free(this->ttyDrivers);
+ }
+ #ifdef HAVE_DELAYACCT
+ LibNl_destroyNetlinkSocket(this);
+ #endif
+ free(this);
+}
+
+static inline unsigned long long LinuxProcessTable_adjustTime(const LinuxMachine* lhost, unsigned long long t) {
+ return t * 100 / lhost->jiffies;
+}
+
+/* Taken from: https://github.com/torvalds/linux/blob/64570fbc14f8d7cb3fe3995f20e26bc25ce4b2cc/fs/proc/array.c#L120 */
+static inline ProcessState LinuxProcessTable_getProcessState(char state) {
+ switch (state) {
+ case 'S': return SLEEPING;
+ case 'X': return DEFUNCT;
+ case 'Z': return ZOMBIE;
+ case 't': return TRACED;
+ case 'T': return STOPPED;
+ case 'D': return UNINTERRUPTIBLE_WAIT;
+ case 'R': return RUNNING;
+ case 'P': return BLOCKED;
+ case 'I': return IDLE;
+ default: return UNKNOWN;
+ }
+}
+
+/*
+ * Read /proc/<pid>/stat (thread-specific data)
+ */
+static bool LinuxProcessTable_readStatFile(LinuxProcess* lp, openat_arg_t procFd, const LinuxMachine* lhost, bool scanMainThread, char* command, size_t commLen) {
+ Process* process = &lp->super;
+
+ char buf[MAX_READ + 1];
+ char path[22] = "stat";
+ if (scanMainThread) {
+ xSnprintf(path, sizeof(path), "task/%"PRIi32"/stat", (int32_t)Process_getPid(process));
+ }
+ ssize_t r = xReadfileat(procFd, path, buf, sizeof(buf));
+ if (r < 0)
+ return false;
+
+ /* (1) pid - %d */
+ assert(Process_getPid(process) == atoi(buf));
+ char* location = strchr(buf, ' ');
+ if (!location)
+ return false;
+
+ /* (2) comm - (%s) */
+ location += 2;
+ char* end = strrchr(location, ')');
+ if (!end)
+ return false;
+
+ String_safeStrncpy(command, location, MINIMUM((size_t)(end - location + 1), commLen));
+
+ location = end + 2;
+
+ /* (3) state - %c */
+ process->state = LinuxProcessTable_getProcessState(location[0]);
+ location += 2;
+
+ /* (4) ppid - %d */
+ Process_setParent(process, strtol(location, &location, 10));
+ location += 1;
+
+ /* (5) pgrp - %d */
+ process->pgrp = strtol(location, &location, 10);
+ location += 1;
+
+ /* (6) session - %d */
+ process->session = strtol(location, &location, 10);
+ location += 1;
+
+ /* (7) tty_nr - %d */
+ process->tty_nr = strtoul(location, &location, 10);
+ location += 1;
+
+ /* (8) tpgid - %d */
+ process->tpgid = strtol(location, &location, 10);
+ location += 1;
+
+ /* (9) flags - %u */
+ lp->flags = strtoul(location, &location, 10);
+ location += 1;
+
+ /* (10) minflt - %lu */
+ process->minflt = strtoull(location, &location, 10);
+ location += 1;
+
+ /* (11) cminflt - %lu */
+ lp->cminflt = strtoull(location, &location, 10);
+ location += 1;
+
+ /* (12) majflt - %lu */
+ process->majflt = strtoull(location, &location, 10);
+ location += 1;
+
+ /* (13) cmajflt - %lu */
+ lp->cmajflt = strtoull(location, &location, 10);
+ location += 1;
+
+ /* (14) utime - %lu */
+ lp->utime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10));
+ location += 1;
+
+ /* (15) stime - %lu */
+ lp->stime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10));
+ location += 1;
+
+ /* (16) cutime - %ld */
+ lp->cutime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10));
+ location += 1;
+
+ /* (17) cstime - %ld */
+ lp->cstime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10));
+ location += 1;
+
+ /* (18) priority - %ld */
+ process->priority = strtol(location, &location, 10);
+ location += 1;
+
+ /* (19) nice - %ld */
+ process->nice = strtol(location, &location, 10);
+ location += 1;
+
+ /* (20) num_threads - %ld */
+ process->nlwp = strtol(location, &location, 10);
+ location += 1;
+
+ /* Skip (21) itrealvalue - %ld */
+ location = strchr(location, ' ') + 1;
+
+ /* (22) starttime - %llu */
+ if (process->starttime_ctime == 0) {
+ process->starttime_ctime = lhost->boottime + LinuxProcessTable_adjustTime(lhost, strtoll(location, &location, 10)) / 100;
+ } else {
+ location = strchr(location, ' ');
+ }
+ location += 1;
+
+ /* Skip (23) - (38) */
+ for (int i = 0; i < 16; i++) {
+ location = strchr(location, ' ') + 1;
+ }
+
+ assert(location != NULL);
+
+ /* (39) processor - %d */
+ process->processor = strtol(location, &location, 10);
+
+ /* Ignore further fields */
+
+ process->time = lp->utime + lp->stime;
+
+ return true;
+}
+
+/*
+ * Read /proc/<pid>/status (thread-specific data)
+ */
+static bool LinuxProcessTable_readStatusFile(Process* process, openat_arg_t procFd) {
+ LinuxProcess* lp = (LinuxProcess*) process;
+
+ unsigned long ctxt = 0;
+ process->isRunningInContainer = TRI_OFF;
+#ifdef HAVE_VSERVER
+ lp->vxid = 0;
+#endif
+
+ FILE* statusfile = fopenat(procFd, "status", "r");
+ if (!statusfile)
+ return false;
+
+ char buffer[PROC_LINE_LENGTH + 1] = {0};
+
+ while (fgets(buffer, sizeof(buffer), statusfile)) {
+
+ if (String_startsWith(buffer, "NSpid:")) {
+ const char* ptr = buffer;
+ int pid_ns_count = 0;
+ while (*ptr && *ptr != '\n' && !isdigit((unsigned char)*ptr))
+ ++ptr;
+
+ while (*ptr && *ptr != '\n') {
+ if (isdigit(*ptr))
+ pid_ns_count++;
+ while (isdigit((unsigned char)*ptr))
+ ++ptr;
+ while (*ptr && *ptr != '\n' && !isdigit((unsigned char)*ptr))
+ ++ptr;
+ }
+
+ if (pid_ns_count > 1)
+ process->isRunningInContainer = TRI_ON;
+
+ } else if (String_startsWith(buffer, "voluntary_ctxt_switches:")) {
+ unsigned long vctxt;
+ int ok = sscanf(buffer, "voluntary_ctxt_switches:\t%lu", &vctxt);
+ if (ok == 1) {
+ ctxt += vctxt;
+ }
+
+ } else if (String_startsWith(buffer, "nonvoluntary_ctxt_switches:")) {
+ unsigned long nvctxt;
+ int ok = sscanf(buffer, "nonvoluntary_ctxt_switches:\t%lu", &nvctxt);
+ if (ok == 1) {
+ ctxt += nvctxt;
+ }
+
+#ifdef HAVE_VSERVER
+ } else if (String_startsWith(buffer, "VxID:")) {
+ int vxid;
+ int ok = sscanf(buffer, "VxID:\t%32d", &vxid);
+ if (ok == 1) {
+ lp->vxid = vxid;
+ }
+#ifdef HAVE_ANCIENT_VSERVER
+ } else if (String_startsWith(buffer, "s_context:")) {
+ int vxid;
+ int ok = sscanf(buffer, "s_context:\t%32d", &vxid);
+ if (ok == 1) {
+ lp->vxid = vxid;
+ }
+#endif /* HAVE_ANCIENT_VSERVER */
+#endif /* HAVE_VSERVER */
+ }
+ }
+
+ fclose(statusfile);
+
+ lp->ctxt_diff = (ctxt > lp->ctxt_total) ? (ctxt - lp->ctxt_total) : 0;
+ lp->ctxt_total = ctxt;
+
+ return true;
+}
+
+/*
+ * Gather user of task (process-shared data)
+ */
+static bool LinuxProcessTable_updateUser(const Machine* host, Process* process, openat_arg_t procFd, const LinuxProcess* mainTask) {
+ if (mainTask) {
+ process->st_uid = mainTask->super.st_uid;
+ process->user = mainTask->super.user;
+ return true;
+ }
+
+ struct stat sb;
+#ifdef HAVE_OPENAT
+ int statok = fstat(procFd, &sb);
+#else
+ int statok = stat(procFd, &sb);
+#endif
+ if (statok == -1)
+ return false;
+
+ if (process->st_uid != sb.st_uid) {
+ process->st_uid = sb.st_uid;
+ process->user = UsersTable_getRef(host->usersTable, sb.st_uid);
+ }
+
+ return true;
+}
+
+/*
+ * Read /proc/<pid>/io (thread-specific data)
+ */
+static void LinuxProcessTable_readIoFile(LinuxProcess* lp, openat_arg_t procFd, bool scanMainThread) {
+ Process* process = &lp->super;
+ const Machine* host = process->super.host;
+ char path[20] = "io";
+ char buffer[1024];
+ if (scanMainThread) {
+ xSnprintf(path, sizeof(path), "task/%"PRIi32"/io", (int32_t)Process_getPid(process));
+ }
+ ssize_t r = xReadfileat(procFd, path, buffer, sizeof(buffer));
+ if (r < 0) {
+ lp->io_rate_read_bps = NAN;
+ lp->io_rate_write_bps = NAN;
+ lp->io_rchar = ULLONG_MAX;
+ lp->io_wchar = ULLONG_MAX;
+ lp->io_syscr = ULLONG_MAX;
+ lp->io_syscw = ULLONG_MAX;
+ lp->io_read_bytes = ULLONG_MAX;
+ lp->io_write_bytes = ULLONG_MAX;
+ lp->io_cancelled_write_bytes = ULLONG_MAX;
+ lp->io_last_scan_time_ms = host->realtimeMs;
+ return;
+ }
+
+ unsigned long long last_read = lp->io_read_bytes;
+ unsigned long long last_write = lp->io_write_bytes;
+ unsigned long long time_delta = saturatingSub(host->realtimeMs, lp->io_last_scan_time_ms);
+
+ // Note: Linux Kernel documentation states that /proc/<pid>/io may be racy
+ // on 32-bit machines. (Documentation/filesystems/proc.rst)
+
+ char* buf = buffer;
+ const char* line;
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ switch (line[0]) {
+ case 'r':
+ if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) {
+ lp->io_rchar = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "ead_bytes: ")) {
+ lp->io_read_bytes = strtoull(line + 12, NULL, 10);
+ lp->io_rate_read_bps = time_delta ? saturatingSub(lp->io_read_bytes, last_read) * /*ms to s*/1000. / time_delta : NAN;
+ }
+ break;
+ case 'w':
+ if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) {
+ lp->io_wchar = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "rite_bytes: ")) {
+ lp->io_write_bytes = strtoull(line + 13, NULL, 10);
+ lp->io_rate_write_bps = time_delta ? saturatingSub(lp->io_write_bytes, last_write) * /*ms to s*/1000. / time_delta : NAN;
+ }
+ break;
+ case 's':
+ if (line[4] == 'r' && String_startsWith(line + 1, "yscr: ")) {
+ lp->io_syscr = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "yscw: ")) {
+ lp->io_syscw = strtoull(line + 7, NULL, 10);
+ }
+ break;
+ case 'c':
+ if (String_startsWith(line + 1, "ancelled_write_bytes: ")) {
+ lp->io_cancelled_write_bytes = strtoull(line + 23, NULL, 10);
+ }
+ }
+ }
+
+ lp->io_last_scan_time_ms = host->realtimeMs;
+}
+
+typedef struct LibraryData_ {
+ uint64_t size;
+ bool exec;
+} LibraryData;
+
+static void LinuxProcessTable_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* value, void* data) {
+ if (!data)
+ return;
+
+ if (!value)
+ return;
+
+ const LibraryData* v = (const LibraryData*)value;
+ uint64_t* d = (uint64_t*)data;
+ if (!v->exec)
+ return;
+
+ *d += v->size;
+}
+
+/*
+ * Read /proc/<pid>/maps (process-shared data)
+ */
+static void LinuxProcessTable_readMaps(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host, bool calcSize, bool checkDeletedLib) {
+ Process* proc = (Process*)process;
+
+ proc->usesDeletedLib = false;
+
+ FILE* mapsfile = fopenat(procFd, "maps", "r");
+ if (!mapsfile)
+ return;
+
+ Hashtable* ht = NULL;
+ if (calcSize)
+ ht = Hashtable_new(64, true);
+
+ char buffer[1024];
+ while (fgets(buffer, sizeof(buffer), mapsfile)) {
+ uint64_t map_start;
+ uint64_t map_end;
+ bool map_execute;
+ unsigned int map_devmaj;
+ unsigned int map_devmin;
+ uint64_t map_inode;
+
+ // Short circuit test: Look for a slash
+ if (!strchr(buffer, '/'))
+ continue;
+
+ // Parse format: "%Lx-%Lx %4s %x %2x:%2x %Ld"
+ char* readptr = buffer;
+
+ map_start = fast_strtoull_hex(&readptr, 16);
+ if ('-' != *readptr++)
+ continue;
+
+ map_end = fast_strtoull_hex(&readptr, 16);
+ if (' ' != *readptr++)
+ continue;
+
+ if (!readptr[0] || !readptr[1] || !readptr[2] || !readptr[3])
+ continue;
+
+ map_execute = (readptr[2] == 'x');
+ readptr += 4;
+ if (' ' != *readptr++)
+ continue;
+
+ while (*readptr > ' ')
+ readptr++; // Skip parsing this hex value
+ if (' ' != *readptr++)
+ continue;
+
+ map_devmaj = fast_strtoull_hex(&readptr, 4);
+ if (':' != *readptr++)
+ continue;
+
+ map_devmin = fast_strtoull_hex(&readptr, 4);
+ if (' ' != *readptr++)
+ continue;
+
+ //Minor shortcut: Once we know there's no file for this region, we skip
+ if (!map_devmaj && !map_devmin)
+ continue;
+
+ map_inode = fast_strtoull_dec(&readptr, 20);
+ if (!map_inode)
+ continue;
+
+ if (calcSize) {
+ LibraryData* libdata = Hashtable_get(ht, map_inode);
+ if (!libdata) {
+ libdata = xCalloc(1, sizeof(LibraryData));
+ Hashtable_put(ht, map_inode, libdata);
+ }
+
+ libdata->size += map_end - map_start;
+ libdata->exec |= map_execute;
+ }
+
+ if (checkDeletedLib && map_execute && !proc->usesDeletedLib) {
+ while (*readptr == ' ')
+ readptr++;
+
+ if (*readptr != '/')
+ continue;
+
+ if (String_startsWith(readptr, "/memfd:"))
+ continue;
+
+ /* Virtualbox maps /dev/zero for memory allocation. That results in
+ * false positive, so ignore. */
+ if (String_eq(readptr, "/dev/zero (deleted)\n"))
+ continue;
+
+ if (strstr(readptr, " (deleted)\n")) {
+ proc->usesDeletedLib = true;
+ if (!calcSize)
+ break;
+ }
+ }
+ }
+
+ fclose(mapsfile);
+
+ if (calcSize) {
+ uint64_t total_size = 0;
+ Hashtable_foreach(ht, LinuxProcessTable_calcLibSize_helper, &total_size);
+
+ Hashtable_delete(ht);
+
+ process->m_lrs = total_size / host->pageSize;
+ }
+}
+
+/*
+ * Read /proc/<pid>/statm (process-shared data)
+ */
+static bool LinuxProcessTable_readStatmFile(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host, const LinuxProcess* mainTask) {
+ if (mainTask) {
+ process->super.m_virt = mainTask->super.m_virt;
+ process->super.m_resident = mainTask->super.m_resident;
+ return true;
+ }
+
+ char statmdata[128] = {0};
+
+ if (xReadfileat(procFd, "statm", statmdata, sizeof(statmdata)) < 1) {
+ return false;
+ }
+
+ long int dummy, dummy2;
+
+ int r = sscanf(statmdata, "%ld %ld %ld %ld %ld %ld %ld",
+ &process->super.m_virt,
+ &process->super.m_resident,
+ &process->m_share,
+ &process->m_trs,
+ &dummy, /* unused since Linux 2.6; always 0 */
+ &process->m_drs,
+ &dummy2); /* unused since Linux 2.6; always 0 */
+
+ if (r == 7) {
+ process->super.m_virt *= host->pageSizeKB;
+ process->super.m_resident *= host->pageSizeKB;
+
+ process->m_priv = process->super.m_resident - (process->m_share * host->pageSizeKB);
+ }
+
+ return r == 7;
+}
+
+/*
+ * Read /proc/<pid>/smaps (process-shared data)
+ */
+static bool LinuxProcessTable_readSmapsFile(LinuxProcess* process, openat_arg_t procFd, bool haveSmapsRollup) {
+ //http://elixir.free-electrons.com/linux/v4.10/source/fs/proc/task_mmu.c#L719
+ //kernel will return data in chunks of size PAGE_SIZE or less.
+ FILE* fp = fopenat(procFd, haveSmapsRollup ? "smaps_rollup" : "smaps", "r");
+ if (!fp)
+ return false;
+
+ process->m_pss = 0;
+ process->m_swap = 0;
+ process->m_psswp = 0;
+
+ char buffer[256];
+ while (fgets(buffer, sizeof(buffer), fp)) {
+ if (!strchr(buffer, '\n')) {
+ // Partial line, skip to end of this line
+ while (fgets(buffer, sizeof(buffer), fp)) {
+ if (strchr(buffer, '\n')) {
+ break;
+ }
+ }
+ continue;
+ }
+
+ if (String_startsWith(buffer, "Pss:")) {
+ process->m_pss += strtol(buffer + 4, NULL, 10);
+ } else if (String_startsWith(buffer, "Swap:")) {
+ process->m_swap += strtol(buffer + 5, NULL, 10);
+ } else if (String_startsWith(buffer, "SwapPss:")) {
+ process->m_psswp += strtol(buffer + 8, NULL, 10);
+ }
+ }
+
+ fclose(fp);
+ return true;
+}
+
+#ifdef HAVE_OPENVZ
+
+static void LinuxProcessTable_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) {
+ if (access(PROCDIR "/vz", R_OK) != 0) {
+ free(process->ctid);
+ process->ctid = NULL;
+ process->vpid = Process_getPid(&process->super);
+ return;
+ }
+
+ FILE* file = fopenat(procFd, "status", "r");
+ if (!file) {
+ free(process->ctid);
+ process->ctid = NULL;
+ process->vpid = Process_getPid(&process->super);
+ return;
+ }
+
+ bool foundEnvID = false;
+ bool foundVPid = false;
+ char linebuf[256];
+ while (fgets(linebuf, sizeof(linebuf), file) != NULL) {
+ if (strchr(linebuf, '\n') == NULL) {
+ // Partial line, skip to end of this line
+ while (fgets(linebuf, sizeof(linebuf), file) != NULL) {
+ if (strchr(linebuf, '\n') != NULL) {
+ break;
+ }
+ }
+ continue;
+ }
+
+ char* name_value_sep = strchr(linebuf, ':');
+ if (name_value_sep == NULL) {
+ continue;
+ }
+
+ int field;
+ if (0 == strncasecmp(linebuf, "envID", name_value_sep - linebuf)) {
+ field = 1;
+ } else if (0 == strncasecmp(linebuf, "VPid", name_value_sep - linebuf)) {
+ field = 2;
+ } else {
+ continue;
+ }
+
+ do {
+ name_value_sep++;
+ } while (*name_value_sep != '\0' && *name_value_sep <= 32);
+
+ char* value_end = name_value_sep;
+
+ while (*value_end > 32) {
+ value_end++;
+ }
+
+ if (name_value_sep == value_end) {
+ continue;
+ }
+
+ *value_end = '\0';
+
+ switch (field) {
+ case 1:
+ foundEnvID = true;
+ if (!String_eq(name_value_sep, process->ctid ? process->ctid : ""))
+ free_and_xStrdup(&process->ctid, name_value_sep);
+ break;
+ case 2:
+ foundVPid = true;
+ process->vpid = strtoul(name_value_sep, NULL, 0);
+ break;
+ default:
+ //Sanity Check: Should never reach here, or the implementation is missing something!
+ assert(false && "OpenVZ handling: Unimplemented case for field handling reached.");
+ }
+ }
+
+ fclose(file);
+
+ if (!foundEnvID) {
+ free(process->ctid);
+ process->ctid = NULL;
+ }
+
+ if (!foundVPid) {
+ process->vpid = Process_getPid(&process->super);
+ }
+}
+
+#endif /* HAVE_OPENVZ */
+
+/*
+ * Read /proc/<pid>/cgroup (thread-specific data)
+ */
+static void LinuxProcessTable_readCGroupFile(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "cgroup", "r");
+ if (!file) {
+ if (process->cgroup) {
+ free(process->cgroup);
+ process->cgroup = NULL;
+ }
+ if (process->cgroup_short) {
+ free(process->cgroup_short);
+ process->cgroup_short = NULL;
+ }
+ if (process->container_short) {
+ free(process->container_short);
+ process->container_short = NULL;
+ }
+ return;
+ }
+ char output[PROC_LINE_LENGTH + 1];
+ output[0] = '\0';
+ char* at = output;
+ int left = PROC_LINE_LENGTH;
+ while (!feof(file) && left > 0) {
+ char buffer[PROC_LINE_LENGTH + 1];
+ const char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
+ if (!ok)
+ break;
+
+ char* group = buffer;
+ for (size_t i = 0; i < 2; i++) {
+ group = String_strchrnul(group, ':');
+ if (!*group)
+ break;
+ group++;
+ }
+
+ char* eol = String_strchrnul(group, '\n');
+ *eol = '\0';
+
+ if (at != output) {
+ *at = ';';
+ at++;
+ left--;
+ }
+ int wrote = snprintf(at, left, "%s", group);
+ left -= wrote;
+ }
+ fclose(file);
+
+ bool changed = !process->cgroup || !String_eq(process->cgroup, output);
+
+ Row_updateFieldWidth(CGROUP, strlen(output));
+ free_and_xStrdup(&process->cgroup, output);
+
+ if (!changed) {
+ if (process->cgroup_short) {
+ Row_updateFieldWidth(CCGROUP, strlen(process->cgroup_short));
+ } else {
+ //CCGROUP is alias to normal CGROUP if shortening fails
+ Row_updateFieldWidth(CCGROUP, strlen(process->cgroup));
+ }
+ if (process->container_short) {
+ Row_updateFieldWidth(CONTAINER, strlen(process->container_short));
+ } else {
+ Row_updateFieldWidth(CONTAINER, strlen("N/A"));
+ }
+ return;
+ }
+
+ char* cgroup_short = CGroup_filterName(process->cgroup);
+ if (cgroup_short) {
+ Row_updateFieldWidth(CCGROUP, strlen(cgroup_short));
+ free_and_xStrdup(&process->cgroup_short, cgroup_short);
+ free(cgroup_short);
+ } else {
+ //CCGROUP is alias to normal CGROUP if shortening fails
+ Row_updateFieldWidth(CCGROUP, strlen(process->cgroup));
+ free(process->cgroup_short);
+ process->cgroup_short = NULL;
+ }
+
+ char* container_short = CGroup_filterContainer(process->cgroup);
+ if (container_short) {
+ Row_updateFieldWidth(CONTAINER, strlen(container_short));
+ free_and_xStrdup(&process->container_short, container_short);
+ free(container_short);
+ } else {
+ //CONTAINER is just "N/A" if shortening fails
+ Row_updateFieldWidth(CONTAINER, strlen("N/A"));
+ free(process->container_short);
+ process->container_short = NULL;
+ }
+}
+
+/*
+ * Read /proc/<pid>/oom_score (process-shared data)
+ */
+static void LinuxProcessTable_readOomData(LinuxProcess* process, openat_arg_t procFd, const LinuxProcess* mainTask) {
+ if (mainTask) {
+ process->oom = mainTask->oom;
+ return;
+ }
+
+ char buffer[PROC_LINE_LENGTH + 1] = {0};
+
+ ssize_t oomRead = xReadfileat(procFd, "oom_score", buffer, sizeof(buffer));
+ if (oomRead < 1) {
+ return;
+ }
+
+ char* oomPtr = buffer;
+ uint64_t oom = fast_strtoull_dec(&oomPtr, oomRead);
+ if (*oomPtr && *oomPtr != '\n' && *oomPtr != ' ') {
+ return;
+ }
+
+ if (oom > UINT_MAX) {
+ return;
+ }
+
+ process->oom = oom;
+}
+
+/*
+ * Read /proc/<pid>/autogroup (process-shared data)
+ */
+static void LinuxProcessTable_readAutogroup(LinuxProcess* process, openat_arg_t procFd, const LinuxProcess* mainTask) {
+ if (mainTask) {
+ process->autogroup_id = mainTask->autogroup_id;
+ return;
+ }
+
+ process->autogroup_id = -1;
+
+ char autogroup[64]; // space for two numeric values and fixed length strings
+ ssize_t amtRead = xReadfileat(procFd, "autogroup", autogroup, sizeof(autogroup));
+ if (amtRead < 0)
+ return;
+
+ long int identity;
+ int nice;
+ int ok = sscanf(autogroup, "/autogroup-%ld nice %d", &identity, &nice);
+ if (ok == 2) {
+ process->autogroup_id = identity;
+ process->autogroup_nice = nice;
+ }
+}
+
+/*
+ * Read /proc/<pid>/attr/current (process-shared data)
+ */
+static void LinuxProcessTable_readSecattrData(LinuxProcess* process, openat_arg_t procFd, const LinuxProcess* mainTask) {
+ if (mainTask) {
+ const char* mainSecAttr = mainTask->secattr;
+ if (mainSecAttr) {
+ free_and_xStrdup(&process->secattr, mainSecAttr);
+ } else {
+ free(process->secattr);
+ process->secattr = NULL;
+ }
+ return;
+ }
+
+ char buffer[PROC_LINE_LENGTH + 1] = {0};
+
+ ssize_t attrdata = xReadfileat(procFd, "attr/current", buffer, sizeof(buffer));
+ if (attrdata < 1) {
+ free(process->secattr);
+ process->secattr = NULL;
+ return;
+ }
+
+ char* newline = strchr(buffer, '\n');
+ if (newline) {
+ *newline = '\0';
+ }
+
+ Row_updateFieldWidth(SECATTR, strlen(buffer));
+
+ free_and_xStrdup(&process->secattr, buffer);
+}
+
+/*
+ * Read /proc/<pid>/cwd (process-shared data)
+ */
+static void LinuxProcessTable_readCwd(LinuxProcess* process, openat_arg_t procFd, const LinuxProcess* mainTask) {
+ if (mainTask) {
+ const char* mainCwd = mainTask->super.procCwd;
+ if (mainCwd) {
+ free_and_xStrdup(&process->super.procCwd, mainCwd);
+ } else {
+ free(process->super.procCwd);
+ process->super.procCwd = NULL;
+ }
+ return;
+ }
+
+ char pathBuffer[PATH_MAX + 1] = {0};
+
+#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)
+ ssize_t r = readlinkat(procFd, "cwd", pathBuffer, sizeof(pathBuffer) - 1);
+#else
+ ssize_t r = Compat_readlink(procFd, "cwd", pathBuffer, sizeof(pathBuffer) - 1);
+#endif
+
+ if (r < 0) {
+ free(process->super.procCwd);
+ process->super.procCwd = NULL;
+ return;
+ }
+
+ pathBuffer[r] = '\0';
+
+ free_and_xStrdup(&process->super.procCwd, pathBuffer);
+}
+
+/*
+ * Read /proc/<pid>/exe (process-shared data)
+ */
+static void LinuxProcessList_readExe(Process* process, openat_arg_t procFd, const LinuxProcess* mainTask) {
+ if (mainTask) {
+ Process_updateExe(process, mainTask->super.procExe);
+ process->procExeDeleted = mainTask->super.procExeDeleted;
+ return;
+ }
+
+ char filename[PATH_MAX + 1];
+
+#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)
+ ssize_t amtRead = readlinkat(procFd, "exe", filename, sizeof(filename) - 1);
+#else
+ ssize_t amtRead = Compat_readlink(procFd, "exe", filename, sizeof(filename) - 1);
+#endif
+ if (amtRead > 0) {
+ filename[amtRead] = 0;
+ if (!process->procExe ||
+ (!process->procExeDeleted && !String_eq(filename, process->procExe)) ||
+ process->procExeDeleted) {
+
+ const char* deletedMarker = " (deleted)";
+ const size_t markerLen = strlen(deletedMarker);
+ const size_t filenameLen = strlen(filename);
+
+ if (filenameLen > markerLen) {
+ bool oldExeDeleted = process->procExeDeleted;
+
+ process->procExeDeleted = String_eq(filename + filenameLen - markerLen, deletedMarker);
+
+ if (process->procExeDeleted)
+ filename[filenameLen - markerLen] = '\0';
+
+ if (oldExeDeleted != process->procExeDeleted)
+ process->mergedCommand.lastUpdate = 0;
+ }
+
+ Process_updateExe(process, filename);
+ }
+ } else if (process->procExe) {
+ Process_updateExe(process, NULL);
+ process->procExeDeleted = false;
+ }
+}
+
+/*
+ * Read /proc/<pid>/cmdline (process-shared data)
+ */
+static bool LinuxProcessTable_readCmdlineFile(Process* process, openat_arg_t procFd, const LinuxProcess* mainTask) {
+ LinuxProcessList_readExe(process, procFd, mainTask);
+
+ char command[4096 + 1]; // max cmdline length on Linux
+ ssize_t amtRead = xReadfileat(procFd, "cmdline", command, sizeof(command));
+ if (amtRead <= 0)
+ return false;
+
+ int tokenEnd = -1;
+ int tokenStart = -1;
+ int lastChar = 0;
+ bool argSepNUL = false;
+ bool argSepSpace = false;
+
+ for (int i = 0; i < amtRead; i++) {
+ // If this is true, there's a NUL byte in the middle of command
+ if (tokenEnd >= 0) {
+ argSepNUL = true;
+ }
+
+ const char argChar = command[i];
+
+ /* newline used as delimiter - when forming the mergedCommand, newline is
+ * converted to space by Process_makeCommandStr */
+ if (argChar == '\0') {
+ command[i] = '\n';
+
+ // Set tokenEnd to the NUL byte
+ if (tokenEnd < 0) {
+ tokenEnd = i;
+ }
+
+ continue;
+ }
+
+ /* Record some information for the argument parsing heuristic below. */
+ if (argChar <= ' ') {
+ argSepSpace = true;
+ }
+
+ /* Detect the last / before the end of the token as
+ * the start of the basename in cmdline, see Process_writeCommand */
+ if (argChar == '/' && tokenEnd < 0) {
+ tokenStart = i + 1;
+ }
+
+ lastChar = i;
+ }
+
+ command[lastChar + 1] = '\0';
+
+ if (!argSepNUL && argSepSpace) {
+ /* Argument parsing heuristic.
+ *
+ * This heuristic is used for processes that rewrite their command line.
+ * Normally the command line is split by using NUL bytes between each argument.
+ * But some programs like chrome flatten this using spaces.
+ *
+ * This heuristic tries its best to undo this loss of information.
+ * To achieve this, we treat every character <= 32 as argument separators
+ * (i.e. all of ASCII control sequences and space).
+ * We then search for the basename of the cmdline in the first argument we found that way.
+ * As path names may contain we try to cross-validate if the path we got that way exists.
+ */
+
+ tokenStart = -1;
+ tokenEnd = -1;
+
+ size_t exeLen = process->procExe ? strlen(process->procExe) : 0;
+
+ if (process->procExe && String_startsWith(command, process->procExe) &&
+ exeLen < (size_t)lastChar && command[exeLen] <= ' ') {
+ tokenStart = process->procExeBasenameOffset;
+ tokenEnd = exeLen;
+ }
+
+ // From initial scan we know there's at least one space.
+ // Check if that's part of a filename for an existing file.
+ else if (Compat_faccessat(AT_FDCWD, command, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
+ // If we reach here the path does not exist.
+ // Thus begin searching for the part of it that actually does.
+ int tokenArg0Start = -1;
+
+ for (int i = 0; i <= lastChar; i++) {
+ const char cmdChar = command[i];
+
+ /* Any ASCII control or space used as delimiter */
+ if (cmdChar <= ' ') {
+ if (tokenEnd >= 0) {
+ // Split on every further separator, regardless of path correctness
+ command[i] = '\n';
+ continue;
+ }
+
+ // Found our first argument
+ command[i] = '\0';
+
+ bool found = Compat_faccessat(AT_FDCWD, command, F_OK, AT_SYMLINK_NOFOLLOW) == 0;
+
+ // Restore if this wasn't it
+ command[i] = found ? '\n' : cmdChar;
+
+ if (found)
+ tokenEnd = i;
+ if (tokenArg0Start < 0)
+ tokenArg0Start = tokenStart < 0 ? 0 : tokenStart;
+
+ continue;
+ }
+
+ if (tokenEnd >= 0) {
+ continue;
+ }
+
+ if (cmdChar == '/') {
+ // Normal path separator
+ tokenStart = i + 1;
+ } else if (cmdChar == '\\' && (tokenStart < 1 || command[tokenStart - 1] == '\\')) {
+ // Windows Path separator (WINE)
+ tokenStart = i + 1;
+ } else if (cmdChar == ':' && (command[i + 1] != '/' && command[i + 1] != '\\')) {
+ // Colon not part of a Windows Path
+ tokenEnd = i;
+ } else if (tokenStart < 0) {
+ // Relative path
+ tokenStart = i;
+ }
+ }
+
+ if (tokenEnd < 0) {
+ tokenStart = tokenArg0Start;
+
+ // No token delimiter found, forcibly split
+ for (int i = 0; i <= lastChar; i++) {
+ if (command[i] <= ' ') {
+ command[i] = '\n';
+ if (tokenEnd < 0) {
+ tokenEnd = i;
+ }
+ }
+ }
+ }
+ }
+
+ /* Some command lines are hard to parse, like
+ * file.so [kdeinit5] file local:/run/user/1000/klauncherdqbouY.1.slave-socket local:/run/user/1000/kded5TwsDAx.1.slave-socket
+ * Reset if start is behind end.
+ */
+ if (tokenStart >= tokenEnd) {
+ tokenStart = -1;
+ tokenEnd = -1;
+ }
+ }
+
+ if (tokenStart < 0) {
+ tokenStart = 0;
+ }
+
+ if (tokenEnd < 0) {
+ tokenEnd = lastChar + 1;
+ }
+
+ Process_updateCmdline(process, command, tokenStart, tokenEnd);
+
+ return true;
+}
+
+/*
+ * Read /proc/<pid>/comm (thread-specific data)
+ */
+static void LinuxProcessList_readComm(Process* process, openat_arg_t procFd) {
+ char command[4096 + 1]; // max cmdline length on Linux
+ ssize_t amtRead = xReadfileat(procFd, "comm", command, sizeof(command));
+ if (amtRead > 0) {
+ command[amtRead - 1] = '\0';
+ Process_updateComm(process, command);
+ } else {
+ Process_updateComm(process, NULL);
+ }
+}
+
+static char* LinuxProcessTable_updateTtyDevice(TtyDriver* ttyDrivers, unsigned long int tty_nr) {
+ unsigned int maj = major(tty_nr);
+ unsigned int min = minor(tty_nr);
+
+ int i = -1;
+ for (;;) {
+ i++;
+ if ((!ttyDrivers[i].path) || maj < ttyDrivers[i].major) {
+ break;
+ }
+ if (maj > ttyDrivers[i].major) {
+ continue;
+ }
+ if (min < ttyDrivers[i].minorFrom) {
+ break;
+ }
+ if (min > ttyDrivers[i].minorTo) {
+ continue;
+ }
+ unsigned int idx = min - ttyDrivers[i].minorFrom;
+ struct stat sb;
+ char* fullPath;
+ for (;;) {
+ xAsprintf(&fullPath, "%s/%d", ttyDrivers[i].path, idx);
+ int err = stat(fullPath, &sb);
+ if (err == 0 && major(sb.st_rdev) == maj && minor(sb.st_rdev) == min) {
+ return fullPath;
+ }
+ free(fullPath);
+
+ xAsprintf(&fullPath, "%s%d", ttyDrivers[i].path, idx);
+ err = stat(fullPath, &sb);
+ if (err == 0 && major(sb.st_rdev) == maj && minor(sb.st_rdev) == min) {
+ return fullPath;
+ }
+ free(fullPath);
+
+ if (idx == min) {
+ break;
+ }
+
+ idx = min;
+ }
+ int err = stat(ttyDrivers[i].path, &sb);
+ if (err == 0 && tty_nr == sb.st_rdev) {
+ return xStrdup(ttyDrivers[i].path);
+ }
+ }
+ char* out;
+ xAsprintf(&out, "/dev/%u:%u", maj, min);
+ return out;
+}
+
+static bool isOlderThan(const Process* proc, unsigned int seconds) {
+ const Machine* host = proc->super.host;
+
+ assert(host->realtimeMs > 0);
+
+ /* Starttime might not yet be parsed */
+ if (proc->starttime_ctime <= 0)
+ return false;
+
+ uint64_t realtime = host->realtimeMs / 1000;
+
+ if (realtime < (uint64_t)proc->starttime_ctime)
+ return false;
+
+ return realtime - proc->starttime_ctime > seconds;
+}
+
+static bool LinuxProcessTable_recurseProcTree(LinuxProcessTable* this, openat_arg_t parentFd, const LinuxMachine* lhost, const char* dirname, const LinuxProcess* mainTask) {
+ ProcessTable* pt = (ProcessTable*) this;
+ const Machine* host = &lhost->super;
+ const Settings* settings = host->settings;
+ const ScreenSettings* ss = settings->ss;
+ const struct dirent* entry;
+
+ /* set runningTasks from /proc/stat (from Machine_scanCPUTime) */
+ pt->runningTasks = lhost->runningTasks;
+
+#ifdef HAVE_OPENAT
+ int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (dirFd < 0)
+ return false;
+ DIR* dir = fdopendir(dirFd);
+#else
+ char dirFd[4096];
+ xSnprintf(dirFd, sizeof(dirFd), "%s/%s", parentFd, dirname);
+ DIR* dir = opendir(dirFd);
+#endif
+ if (!dir) {
+ Compat_openatArgClose(dirFd);
+ return false;
+ }
+
+ const bool hideKernelThreads = settings->hideKernelThreads;
+ const bool hideUserlandThreads = settings->hideUserlandThreads;
+ const bool hideRunningInContainer = settings->hideRunningInContainer;
+ while ((entry = readdir(dir)) != NULL) {
+ const char* name = entry->d_name;
+
+ // Ignore all non-directories
+ if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) {
+ continue;
+ }
+
+ // The RedHat kernel hides threads with a dot.
+ // I believe this is non-standard.
+ if (name[0] == '.') {
+ name++;
+ }
+
+ // Just skip all non-number directories.
+ if (name[0] < '0' || name[0] > '9') {
+ continue;
+ }
+
+ // filename is a number: process directory
+ int pid;
+ {
+ char* endptr;
+ unsigned long parsedPid = strtoul(name, &endptr, 10);
+ if (parsedPid == 0 || parsedPid == ULONG_MAX || *endptr != '\0')
+ continue;
+ pid = parsedPid;
+ }
+
+ // Skip task directory of main thread
+ if (mainTask && pid == Process_getPid(&mainTask->super))
+ continue;
+
+#ifdef HAVE_OPENAT
+ int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (procFd < 0)
+ continue;
+#else
+ char procFd[4096];
+ xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name);
+#endif
+
+ bool preExisting;
+ Process* proc = ProcessTable_getProcess(pt, pid, &preExisting, LinuxProcess_new);
+ LinuxProcess* lp = (LinuxProcess*) proc;
+
+ Process_setThreadGroup(proc, mainTask ? Process_getPid(&mainTask->super) : pid);
+ proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc);
+ assert(proc->isUserlandThread == (mainTask != NULL));
+
+ LinuxProcessTable_recurseProcTree(this, procFd, lhost, "task", lp);
+
+ /*
+ * These conditions will not trigger on first occurrence, cause we need to
+ * add the process to the ProcessTable and do all one time scans
+ * (e.g. parsing the cmdline to detect a kernel thread)
+ * But it will short-circuit subsequent scans.
+ */
+ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ pt->kernelThreads++;
+ pt->totalTasks++;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
+ if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ pt->userlandThreads++;
+ pt->totalTasks++;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
+ if (preExisting && hideRunningInContainer && proc->isRunningInContainer == TRI_ON) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
+
+ const bool scanMainThread = !hideUserlandThreads && !Process_isKernelThread(proc) && !mainTask;
+
+ if (!LinuxProcessTable_readStatmFile(lp, procFd, lhost, mainTask))
+ goto errorReadingProcess;
+
+ {
+ bool prev = proc->usesDeletedLib;
+
+ if (!proc->isKernelThread && !proc->isUserlandThread &&
+ ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(proc, 10)))) {
+
+ // Check if we really should recalculate the M_LRS value for this process
+ uint64_t passedTimeInMs = host->realtimeMs - lp->last_mlrs_calctime;
+
+ uint64_t recheck = ((uint64_t)rand()) % 2048;
+
+ if (passedTimeInMs > recheck) {
+ lp->last_mlrs_calctime = host->realtimeMs;
+ LinuxProcessTable_readMaps(lp, procFd, lhost, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe);
+ }
+ } else {
+ /* Copy from process structure in threads and reset if setting got disabled */
+ proc->usesDeletedLib = (proc->isUserlandThread && mainTask) ? mainTask->super.usesDeletedLib : false;
+ lp->m_lrs = (proc->isUserlandThread && mainTask) ? mainTask->m_lrs : 0;
+ }
+
+ if (prev != proc->usesDeletedLib)
+ proc->mergedCommand.lastUpdate = 0;
+ }
+
+ char statCommand[MAX_NAME + 1];
+ unsigned long long int lasttimes = (lp->utime + lp->stime);
+ unsigned long int last_tty_nr = proc->tty_nr;
+ if (!LinuxProcessTable_readStatFile(lp, procFd, lhost, scanMainThread, statCommand, sizeof(statCommand)))
+ goto errorReadingProcess;
+
+ if (lp->flags & PF_KTHREAD) {
+ proc->isKernelThread = true;
+ }
+
+ if (last_tty_nr != proc->tty_nr && this->ttyDrivers) {
+ free(proc->tty_name);
+ proc->tty_name = LinuxProcessTable_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
+ }
+
+ proc->percent_cpu = NAN;
+ /* lhost->period might be 0 after system sleep */
+ if (lhost->period > 0.0) {
+ float percent_cpu = saturatingSub(lp->utime + lp->stime, lasttimes) / lhost->period * 100.0;
+ proc->percent_cpu = MINIMUM(percent_cpu, host->activeCPUs * 100.0F);
+ }
+ proc->percent_mem = proc->m_resident / (double)(host->totalMem) * 100.0;
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ if (!LinuxProcessTable_updateUser(host, proc, procFd, mainTask))
+ goto errorReadingProcess;
+
+ if (!preExisting) {
+
+ #ifdef HAVE_OPENVZ
+ if (ss->flags & PROCESS_FLAG_LINUX_OPENVZ) {
+ LinuxProcessTable_readOpenVZData(lp, procFd);
+ }
+ #endif
+
+ if (proc->isKernelThread) {
+ Process_updateCmdline(proc, NULL, 0, 0);
+ } else {
+ if (!LinuxProcessTable_readCmdlineFile(proc, procFd, mainTask)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+ LinuxProcessList_readComm(proc, procFd);
+ }
+
+ Process_fillStarttimeBuffer(proc);
+
+ ProcessTable_add(pt, proc);
+ } else {
+ if (settings->updateProcessNames && proc->state != ZOMBIE) {
+ if (proc->isKernelThread) {
+ Process_updateCmdline(proc, NULL, 0, 0);
+ } else {
+ if (!LinuxProcessTable_readCmdlineFile(proc, procFd, mainTask)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+ LinuxProcessList_readComm(proc, procFd);
+ }
+ }
+ }
+
+ /* Check if the process in inside a different PID namespace. */
+ if (proc->isRunningInContainer == TRI_INITIAL && rootPidNs != (ino_t)-1) {
+ struct stat sb;
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)
+ int res = fstatat(procFd, "ns/pid", &sb, 0);
+#else
+ char path[4096];
+ xSnprintf(path, sizeof(path), "%s/ns/pid", procFd);
+ int res = stat(path, &sb);
+#endif
+ if (res == 0) {
+ proc->isRunningInContainer = (sb.st_ino != rootPidNs) ? TRI_ON : TRI_OFF;
+ }
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_CTXT
+ || ((hideRunningInContainer || ss->flags & PROCESS_FLAG_LINUX_CONTAINER) && proc->isRunningInContainer == TRI_INITIAL)
+#ifdef HAVE_VSERVER
+ || ss->flags & PROCESS_FLAG_LINUX_VSERVER
+#endif
+ ) {
+ proc->isRunningInContainer = TRI_OFF;
+ if (!LinuxProcessTable_readStatusFile(proc, procFd))
+ goto errorReadingProcess;
+ }
+
+ /*
+ * Section gathering non-critical information that is independent from
+ * each other.
+ */
+
+ /* Gather permitted capabilities (thread-specific data) for non-root process. */
+ if (proc->st_uid != 0 && proc->elevated_priv != TRI_OFF) {
+ struct __user_cap_header_struct header = { .version = _LINUX_CAPABILITY_VERSION_3, .pid = Process_getPid(proc) };
+ struct __user_cap_data_struct data;
+
+ long res = syscall(SYS_capget, &header, &data);
+ if (res == 0) {
+ proc->elevated_priv = (data.permitted != 0) ? TRI_ON : TRI_OFF;
+ } else {
+ proc->elevated_priv = TRI_OFF;
+ }
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_CGROUP)
+ LinuxProcessTable_readCGroupFile(lp, procFd);
+
+ if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
+ if (!mainTask) {
+ // Read smaps file of each process only every second pass to improve performance
+ static int smaps_flag = 0;
+ if ((pid & 1) == smaps_flag) {
+ LinuxProcessTable_readSmapsFile(lp, procFd, this->haveSmapsRollup);
+ }
+ if (pid == 1) {
+ smaps_flag = !smaps_flag;
+ }
+ } else {
+ lp->m_pss = mainTask->m_pss;
+ lp->m_swap = mainTask->m_swap;
+ lp->m_psswp = mainTask->m_psswp;
+ }
+ }
+
+ if (ss->flags & PROCESS_FLAG_IO) {
+ LinuxProcessTable_readIoFile(lp, procFd, scanMainThread);
+ }
+
+ #ifdef HAVE_DELAYACCT
+ if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) {
+ LibNl_readDelayAcctData(this, lp);
+ }
+ #endif
+
+ if (ss->flags & PROCESS_FLAG_LINUX_OOM) {
+ LinuxProcessTable_readOomData(lp, procFd, mainTask);
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) {
+ LinuxProcess_updateIOPriority(proc);
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) {
+ LinuxProcessTable_readSecattrData(lp, procFd, mainTask);
+ }
+
+ if (ss->flags & PROCESS_FLAG_CWD) {
+ LinuxProcessTable_readCwd(lp, procFd, mainTask);
+ }
+
+ if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {
+ LinuxProcessTable_readAutogroup(lp, procFd, mainTask);
+ }
+
+ #ifdef SCHEDULER_SUPPORT
+ if (ss->flags & PROCESS_FLAG_SCHEDPOL) {
+ Scheduling_readProcessPolicy(proc);
+ }
+ #endif
+
+ if (ss->flags & PROCESS_FLAG_LINUX_GPU || GPUMeter_active()) {
+ if (mainTask) {
+ lp->gpu_time = mainTask->gpu_time;
+ } else {
+ GPU_readProcessData(this, lp, procFd);
+ }
+ }
+
+ /*
+ * Final section after all data has been gathered
+ */
+
+ if (!proc->cmdline && statCommand[0] &&
+ (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+
+ proc->super.updated = true;
+ Compat_openatArgClose(procFd);
+
+ if (hideRunningInContainer && proc->isRunningInContainer == TRI_ON) {
+ proc->super.show = false;
+ continue;
+ }
+
+ if (Process_isKernelThread(proc)) {
+ pt->kernelThreads++;
+ } else if (Process_isUserlandThread(proc)) {
+ pt->userlandThreads++;
+ }
+
+ /* Set at the end when we know if a new entry is a thread */
+ proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+
+ pt->totalTasks++;
+ /* runningTasks is set in Machine_scanCPUTime() from /proc/stat */
+ continue;
+
+ // Exception handler.
+
+errorReadingProcess:
+ {
+#ifdef HAVE_OPENAT
+ if (procFd >= 0)
+ close(procFd);
+#endif
+
+ if (preExisting) {
+ /*
+ * The only real reason for coming here (apart from Linux violating the /proc API)
+ * would be the process going away with its /proc files disappearing (!HAVE_OPENAT).
+ * However, we want to keep in the process list for now for the "highlight dying" mode.
+ */
+ } else {
+ /* A really short-lived process that we don't have full info about */
+ Process_delete((Object*)proc);
+ }
+ }
+ }
+ closedir(dir);
+ return true;
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ LinuxProcessTable* this = (LinuxProcessTable*) super;
+ Machine* host = super->super.host;
+ const Settings* settings = host->settings;
+ LinuxMachine* lhost = (LinuxMachine*) host;
+
+ if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) {
+ // Refer to sched(7) 'autogroup feature' section
+ // The kernel feature can be enabled/disabled through procfs at
+ // any time, so check for it at the start of each sample - only
+ // read from per-process procfs files if it's globally enabled.
+ this->haveAutogroup = LinuxProcess_isAutogroupEnabled();
+ } else {
+ this->haveAutogroup = false;
+ }
+
+ /* Shift GPU values */
+ {
+ lhost->prevGpuTime = lhost->curGpuTime;
+ lhost->curGpuTime = 0;
+
+ for (GPUEngineData* engine = lhost->gpuEngineData; engine; engine = engine->next) {
+ engine->prevTime = engine->curTime;
+ engine->curTime = 0;
+ }
+ }
+
+ /* PROCDIR is an absolute path */
+ assert(PROCDIR[0] == '/');
+#ifdef HAVE_OPENAT
+ openat_arg_t rootFd = AT_FDCWD;
+#else
+ openat_arg_t rootFd = "";
+#endif
+
+ LinuxProcessTable_recurseProcTree(this, rootFd, lhost, PROCDIR, NULL);
+}
diff --git a/linux/LinuxProcessTable.h b/linux/LinuxProcessTable.h
new file mode 100644
index 00000000..b87f9b0a
--- /dev/null
+++ b/linux/LinuxProcessTable.h
@@ -0,0 +1,35 @@
+#ifndef HEADER_LinuxProcessTable
+#define HEADER_LinuxProcessTable
+/*
+htop - LinuxProcessTable.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "ProcessTable.h"
+
+
+typedef struct TtyDriver_ {
+ char* path;
+ unsigned int major;
+ unsigned int minorFrom;
+ unsigned int minorTo;
+} TtyDriver;
+
+typedef struct LinuxProcessTable_ {
+ ProcessTable super;
+
+ TtyDriver* ttyDrivers;
+ bool haveSmapsRollup;
+ bool haveAutogroup;
+
+ #ifdef HAVE_DELAYACCT
+ struct nl_sock* netlink_socket;
+ int netlink_family;
+ #endif
+} LinuxProcessTable;
+
+#endif
diff --git a/linux/Platform.c b/linux/Platform.c
index 38b66e83..3316d1db 100644
--- a/linux/Platform.c
+++ b/linux/Platform.c
@@ -5,13 +5,13 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h"
+#include "config.h" // IWYU pragma: keep
#include "linux/Platform.h"
#include <assert.h>
-#include <ctype.h>
#include <dirent.h>
+#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <math.h>
@@ -21,6 +21,7 @@ in the source distribution for its full text.
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <sys/sysmacros.h>
#include "BatteryMeter.h"
#include "ClockMeter.h"
@@ -29,9 +30,11 @@ in the source distribution for its full text.
#include "DateMeter.h"
#include "DateTimeMeter.h"
#include "DiskIOMeter.h"
+#include "FileDescriptorMeter.h"
#include "HostnameMeter.h"
#include "HugePageMeter.h"
#include "LoadAverageMeter.h"
+#include "Machine.h"
#include "Macros.h"
#include "MainPanel.h"
#include "Meter.h"
@@ -41,28 +44,28 @@ in the source distribution for its full text.
#include "Object.h"
#include "Panel.h"
#include "PressureStallMeter.h"
-#include "ProcessList.h"
#include "ProvideCurses.h"
-#include "linux/SELinuxMeter.h"
#include "Settings.h"
#include "SwapMeter.h"
#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
#include "XUtils.h"
+#include "linux/GPUMeter.h"
#include "linux/IOPriority.h"
#include "linux/IOPriorityPanel.h"
+#include "linux/LinuxMachine.h"
#include "linux/LinuxProcess.h"
-#include "linux/LinuxProcessList.h"
+#include "linux/SELinuxMeter.h"
#include "linux/SystemdMeter.h"
#include "linux/ZramMeter.h"
#include "linux/ZramStats.h"
+#include "linux/ZswapStats.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsArcStats.h"
#include "zfs/ZfsCompressedArcMeter.h"
#ifdef HAVE_LIBCAP
-#include <errno.h>
#include <sys/capability.h>
#endif
@@ -161,7 +164,7 @@ static Htop_Reaction Platform_actionSetIOPriority(State* st) {
const void* set = Action_pickFromVector(st, ioprioPanel, 20, true);
if (set) {
IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel);
- bool ok = MainPanel_foreachProcess(st->mainPanel, LinuxProcess_setIOPriority, (Arg) { .i = ioprio2 }, NULL);
+ bool ok = MainPanel_foreachRow(st->mainPanel, LinuxProcess_rowSetIOPriority, (Arg) { .i = ioprio2 }, NULL);
if (!ok) {
beep();
}
@@ -176,7 +179,7 @@ static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) {
return false;
}
bool anyTagged;
- bool ok = MainPanel_foreachProcess(panel, LinuxProcess_changeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged);
+ bool ok = MainPanel_foreachRow(panel, LinuxProcess_rowChangeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged);
if (!ok)
beep();
return anyTagged;
@@ -238,6 +241,7 @@ const MeterClass* const Platform_meterTypes[] = {
&PressureStallCPUSomeMeter_class,
&PressureStallIOSomeMeter_class,
&PressureStallIOFullMeter_class,
+ &PressureStallIRQFullMeter_class,
&PressureStallMemorySomeMeter_class,
&PressureStallMemoryFullMeter_class,
&ZfsArcMeter_class,
@@ -247,59 +251,76 @@ const MeterClass* const Platform_meterTypes[] = {
&NetworkIOMeter_class,
&SELinuxMeter_class,
&SystemdMeter_class,
+ &SystemdUserMeter_class,
+ &FileDescriptorMeter_class,
+ &GPUMeter_class,
NULL
};
-int Platform_getUptime() {
+int Platform_getUptime(void) {
+ char uptimedata[64] = {0};
+
+ ssize_t uptimeread = xReadfile(PROCDIR "/uptime", uptimedata, sizeof(uptimedata));
+ if (uptimeread < 1) {
+ return 0;
+ }
+
double uptime = 0;
- FILE* fd = fopen(PROCDIR "/uptime", "r");
- if (fd) {
- int n = fscanf(fd, "%64lf", &uptime);
- fclose(fd);
- if (n <= 0) {
- return 0;
- }
+ double idle = 0;
+
+ int n = sscanf(uptimedata, "%lf %lf", &uptime, &idle);
+ if (n != 2) {
+ return 0;
}
+
return floor(uptime);
}
void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
- FILE* fd = fopen(PROCDIR "/loadavg", "r");
- if (!fd)
- goto err;
+ char loaddata[128] = {0};
+
+ *one = NAN;
+ *five = NAN;
+ *fifteen = NAN;
+
+ ssize_t loadread = xReadfile(PROCDIR "/loadavg", loaddata, sizeof(loaddata));
+ if (loadread < 1)
+ return;
- double scanOne, scanFive, scanFifteen;
- int r = fscanf(fd, "%lf %lf %lf", &scanOne, &scanFive, &scanFifteen);
- fclose(fd);
+ double scanOne = NAN;
+ double scanFive = NAN;
+ double scanFifteen = NAN;
+ int r = sscanf(loaddata, "%lf %lf %lf", &scanOne, &scanFive, &scanFifteen);
if (r != 3)
- goto err;
+ return;
*one = scanOne;
*five = scanFive;
*fifteen = scanFifteen;
- return;
-
-err:
- *one = NAN;
- *five = NAN;
- *fifteen = NAN;
}
-int Platform_getMaxPid() {
- FILE* file = fopen(PROCDIR "/sys/kernel/pid_max", "r");
- if (!file)
- return -1;
+pid_t Platform_getMaxPid(void) {
+ char piddata[32] = {0};
+
+ ssize_t pidread = xReadfile(PROCDIR "/sys/kernel/pid_max", piddata, sizeof(piddata));
+ if (pidread < 1)
+ goto err;
+
+ int pidmax = 0;
+ int match = sscanf(piddata, "%32d", &pidmax);
+ if (match != 1)
+ goto err;
+
+ return pidmax;
- int maxPid = 4194303;
- int match = fscanf(file, "%32d", &maxPid);
- (void) match;
- fclose(file);
- return maxPid;
+err:
+ return 0x3FFFFF; // 4194303
}
double Platform_setCPUValues(Meter* this, unsigned int cpu) {
- const LinuxProcessList* pl = (const LinuxProcessList*) this->pl;
- const CPUData* cpuData = &(pl->cpuData[cpu]);
+ const LinuxMachine* lhost = (const LinuxMachine*) this->host;
+ const Settings* settings = this->host->settings;
+ const CPUData* cpuData = &(lhost->cpuData[cpu]);
double total = (double) ( cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod);
double percent;
double* v = this->values;
@@ -311,28 +332,30 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;
v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;
- if (this->pl->settings->detailedCPUTime) {
+ if (settings->detailedCPUTime) {
v[CPU_METER_KERNEL] = cpuData->systemPeriod / total * 100.0;
v[CPU_METER_IRQ] = cpuData->irqPeriod / total * 100.0;
v[CPU_METER_SOFTIRQ] = cpuData->softIrqPeriod / total * 100.0;
+ this->curItems = 5;
+
v[CPU_METER_STEAL] = cpuData->stealPeriod / total * 100.0;
v[CPU_METER_GUEST] = cpuData->guestPeriod / total * 100.0;
- v[CPU_METER_IOWAIT] = cpuData->ioWaitPeriod / total * 100.0;
- this->curItems = 8;
- if (this->pl->settings->accountGuestInCPUMeter) {
- percent = v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6];
- } else {
- percent = v[0] + v[1] + v[2] + v[3] + v[4];
+ if (settings->accountGuestInCPUMeter) {
+ this->curItems = 7;
}
+
+ v[CPU_METER_IOWAIT] = cpuData->ioWaitPeriod / total * 100.0;
} else {
- v[2] = cpuData->systemAllPeriod / total * 100.0;
- v[3] = (cpuData->stealPeriod + cpuData->guestPeriod) / total * 100.0;
+ v[CPU_METER_KERNEL] = cpuData->systemAllPeriod / total * 100.0;
+ v[CPU_METER_IRQ] = (cpuData->stealPeriod + cpuData->guestPeriod) / total * 100.0;
this->curItems = 4;
- percent = v[0] + v[1] + v[2] + v[3];
}
- percent = CLAMP(percent, 0.0, 100.0);
- if (isnan(percent)) {
- percent = 0.0;
+
+ percent = sumPositiveValues(v, this->curItems);
+ percent = MINIMUM(percent, 100.0);
+
+ if (settings->detailedCPUTime) {
+ this->curItems = 8;
}
v[CPU_METER_FREQUENCY] = cpuData->frequency;
@@ -347,58 +370,88 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
}
void Platform_setMemoryValues(Meter* this) {
- const ProcessList* pl = this->pl;
- const LinuxProcessList* lpl = (const LinuxProcessList*) pl;
-
- this->total = pl->totalMem;
- this->values[0] = pl->usedMem;
- this->values[1] = pl->buffersMem;
- this->values[2] = pl->sharedMem;
- this->values[3] = pl->cachedMem;
- this->values[4] = pl->availableMem;
-
- if (lpl->zfs.enabled != 0 && !Running_containerized) {
+ const Machine* host = this->host;
+ const LinuxMachine* lhost = (const LinuxMachine*) host;
+
+ this->total = host->totalMem;
+ this->values[MEMORY_METER_USED] = host->usedMem;
+ this->values[MEMORY_METER_SHARED] = host->sharedMem;
+ this->values[MEMORY_METER_COMPRESSED] = 0; /* compressed */
+ this->values[MEMORY_METER_BUFFERS] = host->buffersMem;
+ this->values[MEMORY_METER_CACHE] = host->cachedMem;
+ this->values[MEMORY_METER_AVAILABLE] = host->availableMem;
+
+ if (lhost->zfs.enabled != 0 && !Running_containerized) {
// ZFS does not shrink below the value of zfs_arc_min.
unsigned long long int shrinkableSize = 0;
- if (lpl->zfs.size > lpl->zfs.min)
- shrinkableSize = lpl->zfs.size - lpl->zfs.min;
- this->values[0] -= shrinkableSize;
- this->values[3] += shrinkableSize;
- this->values[4] += shrinkableSize;
+ if (lhost->zfs.size > lhost->zfs.min)
+ shrinkableSize = lhost->zfs.size - lhost->zfs.min;
+ this->values[MEMORY_METER_USED] -= shrinkableSize;
+ this->values[MEMORY_METER_CACHE] += shrinkableSize;
+ this->values[MEMORY_METER_AVAILABLE] += shrinkableSize;
+ }
+
+ if (lhost->zswap.usedZswapOrig > 0 || lhost->zswap.usedZswapComp > 0) {
+ this->values[MEMORY_METER_USED] -= lhost->zswap.usedZswapComp;
+ this->values[MEMORY_METER_COMPRESSED] += lhost->zswap.usedZswapComp;
}
}
void Platform_setSwapValues(Meter* this) {
- const ProcessList* pl = this->pl;
- this->total = pl->totalSwap;
- this->values[0] = pl->usedSwap;
- this->values[1] = pl->cachedSwap;
+ const Machine* host = this->host;
+ const LinuxMachine* lhost = (const LinuxMachine*) host;
+
+ this->total = host->totalSwap;
+ this->values[SWAP_METER_USED] = host->usedSwap;
+ this->values[SWAP_METER_CACHE] = host->cachedSwap;
+ this->values[SWAP_METER_FRONTSWAP] = 0; /* frontswap -- memory that is accounted to swap but resides elsewhere */
+
+ if (lhost->zswap.usedZswapOrig > 0 || lhost->zswap.usedZswapComp > 0) {
+ /*
+ * FIXME: Zswapped pages can be both SwapUsed and SwapCached, and we do not know which.
+ *
+ * Apparently, it is possible that Zswapped > SwapUsed. This means that some of Zswapped pages
+ * were actually SwapCached, nor SwapUsed. Unfortunately, we cannot tell what exactly portion
+ * of Zswapped pages were SwapCached.
+ *
+ * For now, subtract Zswapped from SwapUsed and only if Zswapped > SwapUsed, subtract the
+ * overflow from SwapCached.
+ */
+ this->values[SWAP_METER_USED] -= lhost->zswap.usedZswapOrig;
+ if (this->values[SWAP_METER_USED] < 0) {
+ /* subtract the overflow from SwapCached */
+ this->values[SWAP_METER_CACHE] += this->values[SWAP_METER_USED];
+ this->values[SWAP_METER_USED] = 0;
+ }
+ this->values[SWAP_METER_FRONTSWAP] += lhost->zswap.usedZswapOrig;
+ }
}
void Platform_setZramValues(Meter* this) {
- const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl;
- this->total = lpl->zram.totalZram;
- this->values[0] = lpl->zram.usedZramComp;
- this->values[1] = lpl->zram.usedZramOrig;
+ const LinuxMachine* lhost = (const LinuxMachine*) this->host;
+
+ this->total = lhost->zram.totalZram;
+ this->values[ZRAM_METER_COMPRESSED] = lhost->zram.usedZramComp;
+ this->values[ZRAM_METER_UNCOMPRESSED] = lhost->zram.usedZramOrig - lhost->zram.usedZramComp;
}
void Platform_setZfsArcValues(Meter* this) {
- const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl;
+ const LinuxMachine* lhost = (const LinuxMachine*) this->host;
- ZfsArcMeter_readStats(this, &(lpl->zfs));
+ ZfsArcMeter_readStats(this, &(lhost->zfs));
}
void Platform_setZfsCompressedArcValues(Meter* this) {
- const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl;
+ const LinuxMachine* lhost = (const LinuxMachine*) this->host;
- ZfsCompressedArcMeter_readStats(this, &(lpl->zfs));
+ ZfsCompressedArcMeter_readStats(this, &(lhost->zfs));
}
char* Platform_getProcessEnv(pid_t pid) {
char procname[128];
xSnprintf(procname, sizeof(procname), PROCDIR "/%d/environ", pid);
- FILE* fd = fopen(procname, "r");
- if (!fd)
+ FILE* fp = fopen(procname, "r");
+ if (!fp)
return NULL;
char* env = NULL;
@@ -411,9 +464,9 @@ char* Platform_getProcessEnv(pid_t pid) {
size += bytes;
capacity += 4096;
env = xRealloc(env, capacity);
- } while ((bytes = fread(env + size, 1, capacity - size, fd)) > 0);
+ } while ((bytes = fread(env + size, 1, capacity - size, fp)) > 0);
- fclose(fd);
+ fclose(fp);
if (bytes < 0) {
free(env);
@@ -430,117 +483,90 @@ char* Platform_getProcessEnv(pid_t pid) {
return env;
}
-/*
- * Return the absolute path of a file given its pid&inode number
- *
- * Based on implementation of lslocks from util-linux:
- * https://sources.debian.org/src/util-linux/2.36-3/misc-utils/lslocks.c/#L162
- */
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
- struct stat sb;
- const struct dirent* de;
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
+ FileLocks_ProcessData* pdata = xCalloc(1, sizeof(FileLocks_ProcessData));
DIR* dirp;
- ssize_t len;
- int fd;
+ int dfd;
char path[PATH_MAX];
- char sym[PATH_MAX];
- char* ret = NULL;
-
- memset(path, 0, sizeof(path));
- memset(sym, 0, sizeof(sym));
-
- xSnprintf(path, sizeof(path), "%s/%d/fd/", PROCDIR, pid);
+ xSnprintf(path, sizeof(path), PROCDIR "/%d/fdinfo/", pid);
if (strlen(path) >= (sizeof(path) - 2))
- return NULL;
+ goto err;
if (!(dirp = opendir(path)))
- return NULL;
+ goto err;
- if ((fd = dirfd(dirp)) < 0 )
- goto out;
+ if ((dfd = dirfd(dirp)) == -1) {
+ closedir(dirp);
+ goto err;
+ }
- while ((de = readdir(dirp))) {
+ FileLocks_LockData** data_ref = &pdata->locks;
+ for (struct dirent* de; (de = readdir(dirp)); ) {
if (String_eq(de->d_name, ".") || String_eq(de->d_name, ".."))
continue;
- /* care only for numerical descriptors */
- if (!strtoull(de->d_name, (char **) NULL, 10))
+ errno = 0;
+ char* end = de->d_name;
+ int file = strtoull(de->d_name, &end, 10);
+ if (errno || *end)
continue;
- if (!Compat_fstatat(fd, path, de->d_name, &sb, 0) && inode != sb.st_ino)
+ int fd = openat(dfd, de->d_name, O_RDONLY | O_CLOEXEC);
+ if (fd == -1)
continue;
+ FILE* fp = fdopen(fd, "r");
+ if (!fp) {
+ close(fd);
+ continue;
+ }
- if ((len = Compat_readlinkat(fd, path, de->d_name, sym, sizeof(sym) - 1)) < 1)
- goto out;
-
- sym[len] = '\0';
-
- ret = xStrdup(sym);
- break;
- }
-
-out:
- closedir(dirp);
- return ret;
-}
+ for (char buffer[1024]; fgets(buffer, sizeof(buffer), fp); ) {
+ if (!strchr(buffer, '\n'))
+ continue;
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- FileLocks_ProcessData* pdata = xCalloc(1, sizeof(FileLocks_ProcessData));
+ if (!String_startsWith(buffer, "lock:\t"))
+ continue;
- FILE* f = fopen(PROCDIR "/locks", "r");
- if (!f) {
- pdata->error = true;
- return pdata;
- }
+ FileLocks_Data data = {.fd = file};
+ int _;
+ unsigned int maj, min;
+ char lock_end[25], locktype[32], exclusive[32], readwrite[32];
+ if (10 != sscanf(buffer + strlen("lock:\t"), "%d: %31s %31s %31s %d %x:%x:%"PRIu64" %"PRIu64" %24s",
+ &_, locktype, exclusive, readwrite, &_,
+ &maj, &min, &data.inode,
+ &data.start, lock_end))
+ continue;
- char buffer[1024];
- FileLocks_LockData** data_ref = &pdata->locks;
- while(fgets(buffer, sizeof(buffer), f)) {
- if (!strchr(buffer, '\n'))
- continue;
+ data.locktype = xStrdup(locktype);
+ data.exclusive = xStrdup(exclusive);
+ data.readwrite = xStrdup(readwrite);
+ data.dev = makedev(maj, min);
- int lock_id;
- char lock_type[16];
- char lock_excl[16];
- char lock_rw[16];
- pid_t lock_pid;
- unsigned int lock_dev[2];
- uint64_t lock_inode;
- char lock_start[25];
- char lock_end[25];
-
- if (10 != sscanf(buffer, "%d: %15s %15s %15s %d %x:%x:%"PRIu64" %24s %24s",
- &lock_id, lock_type, lock_excl, lock_rw, &lock_pid,
- &lock_dev[0], &lock_dev[1], &lock_inode,
- lock_start, lock_end))
- continue;
+ if (String_eq(lock_end, "EOF"))
+ data.end = ULLONG_MAX;
+ else
+ data.end = strtoull(lock_end, NULL, 10);
- if (pid != lock_pid)
- continue;
+ xSnprintf(path, sizeof(path), PROCDIR "/%d/fd/%s", pid, de->d_name);
+ char link[PATH_MAX];
+ ssize_t link_len;
+ if (strlen(path) < (sizeof(path) - 2) && (link_len = readlink(path, link, sizeof(link))) != -1)
+ data.filename = xStrndup(link, link_len);
- FileLocks_LockData* ldata = xCalloc(1, sizeof(FileLocks_LockData));
- FileLocks_Data* data = &ldata->data;
- data->id = lock_id;
- data->locktype = xStrdup(lock_type);
- data->exclusive = xStrdup(lock_excl);
- data->readwrite = xStrdup(lock_rw);
- data->filename = Platform_getInodeFilename(lock_pid, lock_inode);
- data->dev[0] = lock_dev[0];
- data->dev[1] = lock_dev[1];
- data->inode = lock_inode;
- data->start = strtoull(lock_start, NULL, 10);
- if (!String_eq(lock_end, "EOF")) {
- data->end = strtoull(lock_end, NULL, 10);
- } else {
- data->end = ULLONG_MAX;
+ *data_ref = xCalloc(1, sizeof(FileLocks_LockData));
+ (*data_ref)->data = data;
+ data_ref = &(*data_ref)->next;
}
- *data_ref = ldata;
- data_ref = &ldata->next;
+ fclose(fp);
}
- fclose(f);
+ closedir(dirp);
+ return pdata;
+
+err:
+ pdata->error = true;
return pdata;
}
@@ -548,30 +574,48 @@ void Platform_getPressureStall(const char* file, bool some, double* ten, double*
*ten = *sixty = *threehundred = 0;
char procname[128];
xSnprintf(procname, sizeof(procname), PROCDIR "/pressure/%s", file);
- FILE* fd = fopen(procname, "r");
- if (!fd) {
+ FILE* fp = fopen(procname, "r");
+ if (!fp) {
*ten = *sixty = *threehundred = NAN;
return;
}
- int total = fscanf(fd, "some avg10=%32lf avg60=%32lf avg300=%32lf total=%*f ", ten, sixty, threehundred);
+ int total = fscanf(fp, "some avg10=%32lf avg60=%32lf avg300=%32lf total=%*f ", ten, sixty, threehundred);
if (!some) {
- total = fscanf(fd, "full avg10=%32lf avg60=%32lf avg300=%32lf total=%*f ", ten, sixty, threehundred);
+ total = fscanf(fp, "full avg10=%32lf avg60=%32lf avg300=%32lf total=%*f ", ten, sixty, threehundred);
}
(void) total;
assert(total == 3);
- fclose(fd);
+ fclose(fp);
+}
+
+void Platform_getFileDescriptors(double* used, double* max) {
+ char buffer[128] = {0};
+
+ *used = NAN;
+ *max = 65536;
+
+ ssize_t fdread = xReadfile(PROCDIR "/sys/fs/file-nr", buffer, sizeof(buffer));
+ if (fdread < 1)
+ return;
+
+ unsigned long long v1, v2, v3;
+ int total = sscanf(buffer, "%llu %llu %llu", &v1, &v2, &v3);
+ if (total == 3) {
+ *used = v1;
+ *max = v3;
+ }
}
bool Platform_getDiskIO(DiskIOData* data) {
- FILE* fd = fopen(PROCDIR "/diskstats", "r");
- if (!fd)
+ FILE* fp = fopen(PROCDIR "/diskstats", "r");
+ if (!fp)
return false;
char lastTopDisk[32] = { '\0' };
unsigned long long int read_sum = 0, write_sum = 0, timeSpend_sum = 0;
char lineBuffer[256];
- while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ while (fgets(lineBuffer, sizeof(lineBuffer), fp)) {
char diskname[32];
unsigned long long int read_tmp, write_tmp, timeSpend_tmp;
if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu %*u %*u %llu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) {
@@ -593,7 +637,7 @@ bool Platform_getDiskIO(DiskIOData* data) {
timeSpend_sum += timeSpend_tmp;
}
}
- fclose(fd);
+ fclose(fp);
/* multiply with sector size */
data->totalBytesRead = 512 * read_sum;
data->totalBytesWritten = 512 * write_sum;
@@ -602,13 +646,13 @@ bool Platform_getDiskIO(DiskIOData* data) {
}
bool Platform_getNetworkIO(NetworkIOData* data) {
- FILE* fd = fopen(PROCDIR "/net/dev", "r");
- if (!fd)
+ FILE* fp = fopen(PROCDIR "/net/dev", "r");
+ if (!fp)
return false;
memset(data, 0, sizeof(NetworkIOData));
char lineBuffer[512];
- while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ while (fgets(lineBuffer, sizeof(lineBuffer), fp)) {
char interfaceName[32];
unsigned long long int bytesReceived, packetsReceived, bytesTransmitted, packetsTransmitted;
if (sscanf(lineBuffer, "%31s %llu %llu %*u %*u %*u %*u %*u %*u %llu %llu",
@@ -628,7 +672,7 @@ bool Platform_getNetworkIO(NetworkIOData* data) {
data->packetsTransmitted += packetsTransmitted;
}
- fclose(fd);
+ fclose(fp);
return true;
}
@@ -815,7 +859,7 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
}
}
- if (!now && full && !isnan(capacityLevel))
+ if (!now && full && isNonnegative(capacityLevel))
totalRemain += capacityLevel * fullCharge;
} else if (type == AC) {
@@ -855,12 +899,12 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) {
if (Platform_Battery_method == BAT_PROC) {
Platform_Battery_getProcData(percent, isOnAC);
- if (isnan(*percent))
+ if (!isNonnegative(*percent))
Platform_Battery_method = BAT_SYS;
}
if (Platform_Battery_method == BAT_SYS) {
Platform_Battery_getSysData(percent, isOnAC);
- if (isnan(*percent))
+ if (!isNonnegative(*percent))
Platform_Battery_method = BAT_ERR;
}
if (Platform_Battery_method == BAT_ERR) {
@@ -899,7 +943,7 @@ CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) {
case 160: {
const char* mode = optarg;
if (!mode && optind < argc && argv[optind] != NULL &&
- (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
+ (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
mode = argv[optind++];
}
@@ -1035,18 +1079,18 @@ bool Platform_init(void) {
}
}
- FILE* fd = fopen(PROCDIR "/1/mounts", "r");
- if (fd) {
+ FILE* fp = fopen(PROCDIR "/1/mounts", "r");
+ if (fp) {
char lineBuffer[256];
- while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ while (fgets(lineBuffer, sizeof(lineBuffer), fp)) {
// detect lxc or overlayfs and guess that this means we are running containerized
- if (String_startsWith(lineBuffer, "lxcfs /proc") || String_startsWith(lineBuffer, "overlay ")) {
+ if (String_startsWith(lineBuffer, "lxcfs /proc") || String_startsWith(lineBuffer, "overlay / overlay")) {
Running_containerized = true;
break;
}
}
- fclose(fd);
- } // if (fd)
+ fclose(fp);
+ }
return true;
}
diff --git a/linux/Platform.h b/linux/Platform.h
index e6fa1610..e99d1a22 100644
--- a/linux/Platform.h
+++ b/linux/Platform.h
@@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h"
-
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
@@ -23,15 +21,18 @@ in the source distribution for its full text.
#include "Macros.h"
#include "Meter.h"
#include "NetworkIOMeter.h"
+#include "Panel.h"
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "RichString.h"
+#include "Settings.h"
#include "SignalsPanel.h"
#include "CommandLine.h"
#include "generic/gettime.h"
#include "generic/hostname.h"
#include "generic/uname.h"
+
/* GNU/Hurd does not have PATH_MAX in limits.h */
#ifndef PATH_MAX
#define PATH_MAX 4096
@@ -59,7 +60,7 @@ int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, unsigned int cpu);
@@ -75,12 +76,12 @@ void Platform_setZfsCompressedArcValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred);
+void Platform_getFileDescriptors(double* used, double* max);
+
bool Platform_getDiskIO(DiskIOData* data);
bool Platform_getNetworkIO(NetworkIOData* data);
@@ -114,7 +115,9 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) {
Generic_gettime_monotonic(msec);
}
-static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
@@ -124,12 +127,30 @@ 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 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 const char* Platform_dynamicColumnName(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;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
-static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
#endif
diff --git a/linux/PressureStallMeter.c b/linux/PressureStallMeter.c
index e5089fcc..f7962475 100644
--- a/linux/PressureStallMeter.c
+++ b/linux/PressureStallMeter.c
@@ -6,6 +6,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 "linux/PressureStallMeter.h"
#include <stdbool.h>
@@ -31,6 +33,8 @@ static void PressureStallMeter_updateValues(Meter* this) {
file = "cpu";
} else if (strstr(Meter_name(this), "IO")) {
file = "io";
+ } else if (strstr(Meter_name(this), "IRQ")) {
+ file = "irq";
} else {
file = "memory";
}
@@ -114,6 +118,23 @@ const MeterClass PressureStallIOFullMeter_class = {
.description = "Pressure Stall Information, full io"
};
+const MeterClass PressureStallIRQFullMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = PressureStallMeter_display,
+ },
+ .updateValues = PressureStallMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 3,
+ .total = 100.0,
+ .attributes = PressureStallMeter_attributes,
+ .name = "PressureStallIRQFull",
+ .uiName = "PSI full IRQ",
+ .caption = "PSI full IRQ: ",
+ .description = "Pressure Stall Information, full irq"
+};
+
const MeterClass PressureStallMemorySomeMeter_class = {
.super = {
.extends = Class(Meter),
diff --git a/linux/PressureStallMeter.h b/linux/PressureStallMeter.h
index 8acf46bc..93ebd27c 100644
--- a/linux/PressureStallMeter.h
+++ b/linux/PressureStallMeter.h
@@ -19,6 +19,8 @@ extern const MeterClass PressureStallIOSomeMeter_class;
extern const MeterClass PressureStallIOFullMeter_class;
+extern const MeterClass PressureStallIRQFullMeter_class;
+
extern const MeterClass PressureStallMemorySomeMeter_class;
extern const MeterClass PressureStallMemoryFullMeter_class;
diff --git a/linux/ProcessField.h b/linux/ProcessField.h
index 17cafa96..47c4199f 100644
--- a/linux/ProcessField.h
+++ b/linux/ProcessField.h
@@ -46,6 +46,11 @@ in the source distribution for its full text.
AUTOGROUP_ID = 127, \
AUTOGROUP_NICE = 128, \
CCGROUP = 129, \
+ CONTAINER = 130, \
+ M_PRIV = 131, \
+ GPU_TIME = 132, \
+ GPU_PERCENT = 133, \
+ ISCONTAINER = 134, \
// End of list
diff --git a/linux/SELinuxMeter.c b/linux/SELinuxMeter.c
index c35cb686..323c2a17 100644
--- a/linux/SELinuxMeter.c
+++ b/linux/SELinuxMeter.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 "linux/SELinuxMeter.h"
#include "CRT.h"
diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c
index 53ae2d28..039e578d 100644
--- a/linux/SystemdMeter.c
+++ b/linux/SystemdMeter.c
@@ -5,10 +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 "linux/SystemdMeter.h"
#include <dlfcn.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -30,6 +33,7 @@ in the source distribution for its full text.
#ifdef BUILD_STATIC
#define sym_sd_bus_open_system sd_bus_open_system
+#define sym_sd_bus_open_user sd_bus_open_user
#define sym_sd_bus_get_property_string sd_bus_get_property_string
#define sym_sd_bus_get_property_trivial sd_bus_get_property_trivial
#define sym_sd_bus_unref sd_bus_unref
@@ -39,6 +43,7 @@ in the source distribution for its full text.
typedef void sd_bus;
typedef void sd_bus_error;
static int (*sym_sd_bus_open_system)(sd_bus**);
+static int (*sym_sd_bus_open_user)(sd_bus**);
static int (*sym_sd_bus_get_property_string)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char**);
static int (*sym_sd_bus_get_property_trivial)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char, void*);
static sd_bus* (*sym_sd_bus_unref)(sd_bus*);
@@ -46,37 +51,43 @@ static void* dlopenHandle = NULL;
#endif /* BUILD_STATIC */
-#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
-static sd_bus* bus = NULL;
-#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
-
#define INVALID_VALUE ((unsigned int)-1)
-static char* systemState = NULL;
-static unsigned int nFailedUnits = INVALID_VALUE;
-static unsigned int nInstalledJobs = INVALID_VALUE;
-static unsigned int nNames = INVALID_VALUE;
-static unsigned int nJobs = INVALID_VALUE;
+typedef struct SystemdMeterContext {
+#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
+ sd_bus* bus;
+#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
+ char* systemState;
+ unsigned int nFailedUnits;
+ unsigned int nInstalledJobs;
+ unsigned int nNames;
+ unsigned int nJobs;
+} SystemdMeterContext_t;
+
+static SystemdMeterContext_t ctx_system;
+static SystemdMeterContext_t ctx_user;
static void SystemdMeter_done(ATTR_UNUSED Meter* this) {
- free(systemState);
- systemState = NULL;
+ SystemdMeterContext_t* ctx = String_eq(Meter_name(this), "SystemdUser") ? &ctx_user : &ctx_system;
+
+ free(ctx->systemState);
+ ctx->systemState = NULL;
#ifdef BUILD_STATIC
# ifdef HAVE_LIBSYSTEMD
- if (bus) {
- sym_sd_bus_unref(bus);
+ if (ctx->bus) {
+ sym_sd_bus_unref(ctx->bus);
}
- bus = NULL;
+ ctx->bus = NULL;
# endif /* HAVE_LIBSYSTEMD */
#else /* BUILD_STATIC */
- if (bus && dlopenHandle) {
- sym_sd_bus_unref(bus);
+ if (ctx->bus && dlopenHandle) {
+ sym_sd_bus_unref(ctx->bus);
}
- bus = NULL;
+ ctx->bus = NULL;
- if (dlopenHandle) {
+ if (!ctx_system.systemState && !ctx_user.systemState && dlopenHandle) {
dlclose(dlopenHandle);
dlopenHandle = NULL;
}
@@ -84,7 +95,8 @@ static void SystemdMeter_done(ATTR_UNUSED Meter* this) {
}
#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
-static int updateViaLib(void) {
+static int updateViaLib(bool user) {
+ SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
#ifndef BUILD_STATIC
if (!dlopenHandle) {
dlopenHandle = dlopen("libsystemd.so.0", RTLD_LAZY);
@@ -101,6 +113,7 @@ static int updateViaLib(void) {
} while(0)
resolve(sd_bus_open_system);
+ resolve(sd_bus_open_user);
resolve(sd_bus_get_property_string);
resolve(sd_bus_get_property_trivial);
resolve(sd_bus_unref);
@@ -110,10 +123,13 @@ static int updateViaLib(void) {
#endif /* !BUILD_STATIC */
int r;
-
/* Connect to the system bus */
- if (!bus) {
- r = sym_sd_bus_open_system(&bus);
+ if (!ctx->bus) {
+ if (user) {
+ r = sym_sd_bus_open_user(&ctx->bus);
+ } else {
+ r = sym_sd_bus_open_system(&ctx->bus);
+ }
if (r < 0)
goto busfailure;
}
@@ -122,57 +138,57 @@ static int updateViaLib(void) {
static const char* const busObjectPath = "/org/freedesktop/systemd1";
static const char* const busInterfaceName = "org.freedesktop.systemd1.Manager";
- r = sym_sd_bus_get_property_string(bus,
+ r = sym_sd_bus_get_property_string(ctx->bus,
busServiceName, /* service to contact */
busObjectPath, /* object path */
busInterfaceName, /* interface name */
"SystemState", /* property name */
NULL, /* object to return error in */
- &systemState);
+ &ctx->systemState);
if (r < 0)
goto busfailure;
- r = sym_sd_bus_get_property_trivial(bus,
+ r = sym_sd_bus_get_property_trivial(ctx->bus,
busServiceName, /* service to contact */
busObjectPath, /* object path */
busInterfaceName, /* interface name */
"NFailedUnits", /* property name */
NULL, /* object to return error in */
'u', /* property type */
- &nFailedUnits);
+ &ctx->nFailedUnits);
if (r < 0)
goto busfailure;
- r = sym_sd_bus_get_property_trivial(bus,
+ r = sym_sd_bus_get_property_trivial(ctx->bus,
busServiceName, /* service to contact */
busObjectPath, /* object path */
busInterfaceName, /* interface name */
"NInstalledJobs", /* property name */
NULL, /* object to return error in */
'u', /* property type */
- &nInstalledJobs);
+ &ctx->nInstalledJobs);
if (r < 0)
goto busfailure;
- r = sym_sd_bus_get_property_trivial(bus,
+ r = sym_sd_bus_get_property_trivial(ctx->bus,
busServiceName, /* service to contact */
busObjectPath, /* object path */
busInterfaceName, /* interface name */
"NNames", /* property name */
NULL, /* object to return error in */
'u', /* property type */
- &nNames);
+ &ctx->nNames);
if (r < 0)
goto busfailure;
- r = sym_sd_bus_get_property_trivial(bus,
+ r = sym_sd_bus_get_property_trivial(ctx->bus,
busServiceName, /* service to contact */
busObjectPath, /* object path */
busInterfaceName, /* interface name */
"NJobs", /* property name */
NULL, /* object to return error in */
'u', /* property type */
- &nJobs);
+ &ctx->nJobs);
if (r < 0)
goto busfailure;
@@ -180,8 +196,8 @@ static int updateViaLib(void) {
return 0;
busfailure:
- sym_sd_bus_unref(bus);
- bus = NULL;
+ sym_sd_bus_unref(ctx->bus);
+ ctx->bus = NULL;
return -2;
#ifndef BUILD_STATIC
@@ -195,7 +211,9 @@ dlfailure:
}
#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
-static void updateViaExec(void) {
+static void updateViaExec(bool user) {
+ SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
+
if (Settings_isReadonly())
return;
@@ -225,12 +243,13 @@ static void updateViaExec(void) {
"systemctl",
"systemctl",
"show",
+ user ? "--user" : "--system",
"--property=SystemState",
"--property=NFailedUnits",
"--property=NNames",
"--property=NJobs",
"--property=NInstalledJobs",
- (char *)NULL);
+ (char*)NULL);
exit(127);
}
close(fdpair[1]);
@@ -254,15 +273,15 @@ static void updateViaExec(void) {
if (newline) {
*newline = '\0';
}
- free_and_xStrdup(&systemState, lineBuffer + strlen("SystemState="));
+ free_and_xStrdup(&ctx->systemState, lineBuffer + strlen("SystemState="));
} else if (String_startsWith(lineBuffer, "NFailedUnits=")) {
- nFailedUnits = strtoul(lineBuffer + strlen("NFailedUnits="), NULL, 10);
+ ctx->nFailedUnits = strtoul(lineBuffer + strlen("NFailedUnits="), NULL, 10);
} else if (String_startsWith(lineBuffer, "NNames=")) {
- nNames = strtoul(lineBuffer + strlen("NNames="), NULL, 10);
+ ctx->nNames = strtoul(lineBuffer + strlen("NNames="), NULL, 10);
} else if (String_startsWith(lineBuffer, "NJobs=")) {
- nJobs = strtoul(lineBuffer + strlen("NJobs="), NULL, 10);
+ ctx->nJobs = strtoul(lineBuffer + strlen("NJobs="), NULL, 10);
} else if (String_startsWith(lineBuffer, "NInstalledJobs=")) {
- nInstalledJobs = strtoul(lineBuffer + strlen("NInstalledJobs="), NULL, 10);
+ ctx->nInstalledJobs = strtoul(lineBuffer + strlen("NInstalledJobs="), NULL, 10);
}
}
@@ -270,28 +289,31 @@ static void updateViaExec(void) {
}
static void SystemdMeter_updateValues(Meter* this) {
- free(systemState);
- systemState = NULL;
- nFailedUnits = nInstalledJobs = nNames = nJobs = INVALID_VALUE;
+ bool user = String_eq(Meter_name(this), "SystemdUser");
+ SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
+
+ free(ctx->systemState);
+ ctx->systemState = NULL;
+ ctx->nFailedUnits = ctx->nInstalledJobs = ctx->nNames = ctx->nJobs = INVALID_VALUE;
#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
- if (updateViaLib() < 0)
- updateViaExec();
+ if (updateViaLib(user) < 0)
+ updateViaExec(user);
#else
- updateViaExec();
+ updateViaExec(user);
#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
- xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s", systemState ? systemState : "???");
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s", ctx->systemState ? ctx->systemState : "???");
}
static int zeroDigitColor(unsigned int value) {
switch (value) {
- case 0:
- return CRT_colors[METER_VALUE];
- case INVALID_VALUE:
- return CRT_colors[METER_VALUE_ERROR];
- default:
- return CRT_colors[METER_VALUE_NOTICE];
+ case 0:
+ return CRT_colors[METER_VALUE];
+ case INVALID_VALUE:
+ return CRT_colors[METER_VALUE_ERROR];
+ default:
+ return CRT_colors[METER_VALUE_NOTICE];
}
}
@@ -307,60 +329,72 @@ static int valueDigitColor(unsigned int value) {
}
-static void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
+static void _SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out, SystemdMeterContext_t* ctx) {
char buffer[16];
int len;
+ int color = METER_VALUE_ERROR;
- int color = (systemState && String_eq(systemState, "running")) ? METER_VALUE_OK : METER_VALUE_ERROR;
- RichString_writeAscii(out, CRT_colors[color], systemState ? systemState : "N/A");
+ if (ctx->systemState) {
+ color = String_eq(ctx->systemState, "running") ? METER_VALUE_OK :
+ String_eq(ctx->systemState, "degraded") ? METER_VALUE_ERROR : METER_VALUE_WARN;
+ }
+ RichString_writeAscii(out, CRT_colors[color], ctx->systemState ? ctx->systemState : "N/A");
RichString_appendAscii(out, CRT_colors[METER_TEXT], " (");
- if (nFailedUnits == INVALID_VALUE) {
+ if (ctx->nFailedUnits == INVALID_VALUE) {
buffer[0] = '?';
buffer[1] = '\0';
len = 1;
} else {
- len = xSnprintf(buffer, sizeof(buffer), "%u", nFailedUnits);
+ len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nFailedUnits);
}
- RichString_appendnAscii(out, zeroDigitColor(nFailedUnits), buffer, len);
+ RichString_appendnAscii(out, zeroDigitColor(ctx->nFailedUnits), buffer, len);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "/");
- if (nNames == INVALID_VALUE) {
+ if (ctx->nNames == INVALID_VALUE) {
buffer[0] = '?';
buffer[1] = '\0';
len = 1;
} else {
- len = xSnprintf(buffer, sizeof(buffer), "%u", nNames);
+ len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nNames);
}
- RichString_appendnAscii(out, valueDigitColor(nNames), buffer, len);
+ RichString_appendnAscii(out, valueDigitColor(ctx->nNames), buffer, len);
RichString_appendAscii(out, CRT_colors[METER_TEXT], " failed) (");
- if (nJobs == INVALID_VALUE) {
+ if (ctx->nJobs == INVALID_VALUE) {
buffer[0] = '?';
buffer[1] = '\0';
len = 1;
} else {
- len = xSnprintf(buffer, sizeof(buffer), "%u", nJobs);
+ len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nJobs);
}
- RichString_appendnAscii(out, zeroDigitColor(nJobs), buffer, len);
+ RichString_appendnAscii(out, zeroDigitColor(ctx->nJobs), buffer, len);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "/");
- if (nInstalledJobs == INVALID_VALUE) {
+ if (ctx->nInstalledJobs == INVALID_VALUE) {
buffer[0] = '?';
buffer[1] = '\0';
len = 1;
} else {
- len = xSnprintf(buffer, sizeof(buffer), "%u", nInstalledJobs);
+ len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nInstalledJobs);
}
- RichString_appendnAscii(out, valueDigitColor(nInstalledJobs), buffer, len);
+ RichString_appendnAscii(out, valueDigitColor(ctx->nInstalledJobs), buffer, len);
RichString_appendAscii(out, CRT_colors[METER_TEXT], " jobs)");
}
+static void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
+ _SystemdMeter_display(cast, out, &ctx_system);
+}
+
+static void SystemdUserMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
+ _SystemdMeter_display(cast, out, &ctx_user);
+}
+
static const int SystemdMeter_attributes[] = {
METER_VALUE
};
@@ -382,3 +416,21 @@ const MeterClass SystemdMeter_class = {
.description = "Systemd system state and unit overview",
.caption = "Systemd: ",
};
+
+const MeterClass SystemdUserMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = SystemdUserMeter_display
+ },
+ .updateValues = SystemdMeter_updateValues,
+ .done = SystemdMeter_done,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 0,
+ .total = 100.0,
+ .attributes = SystemdMeter_attributes,
+ .name = "SystemdUser",
+ .uiName = "Systemd user state",
+ .description = "Systemd user state and unit overview",
+ .caption = "Systemd User: ",
+};
diff --git a/linux/SystemdMeter.h b/linux/SystemdMeter.h
index a05e0872..50a793b3 100644
--- a/linux/SystemdMeter.h
+++ b/linux/SystemdMeter.h
@@ -13,4 +13,6 @@ in the source distribution for its full text.
extern const MeterClass SystemdMeter_class;
+extern const MeterClass SystemdUserMeter_class;
+
#endif /* HEADER_SystemdMeter */
diff --git a/linux/ZramMeter.c b/linux/ZramMeter.c
index e1e27b71..8329f014 100644
--- a/linux/ZramMeter.c
+++ b/linux/ZramMeter.c
@@ -1,3 +1,13 @@
+/*
+htop - linux/ZramMeter.c
+(C) 2020 Murloc Knight
+(C) 2020-2023 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 "linux/ZramMeter.h"
#include <stddef.h>
@@ -7,10 +17,12 @@
#include "Object.h"
#include "Platform.h"
#include "RichString.h"
+#include "ZramMeter.h"
-static const int ZramMeter_attributes[] = {
- ZRAM
+static const int ZramMeter_attributes[ZRAM_METER_ITEMCOUNT] = {
+ [ZRAM_METER_COMPRESSED] = ZRAM_COMPRESSED,
+ [ZRAM_METER_UNCOMPRESSED] = ZRAM_UNCOMPRESSED,
};
static void ZramMeter_updateValues(Meter* this) {
@@ -20,15 +32,13 @@ static void ZramMeter_updateValues(Meter* this) {
Platform_setZramValues(this);
- /* on print bar for compressed data size, not uncompressed */
- this->curItems = 1;
-
- written = Meter_humanUnit(buffer, this->values[0], size);
+ written = Meter_humanUnit(buffer, this->values[ZRAM_METER_COMPRESSED], size);
METER_BUFFER_CHECK(buffer, size, written);
METER_BUFFER_APPEND_CHR(buffer, size, '(');
- written = Meter_humanUnit(buffer, this->values[1], size);
+ double uncompressed = this->values[ZRAM_METER_COMPRESSED] + this->values[ZRAM_METER_UNCOMPRESSED];
+ written = Meter_humanUnit(buffer, uncompressed, size);
METER_BUFFER_CHECK(buffer, size, written);
METER_BUFFER_APPEND_CHR(buffer, size, ')');
@@ -47,11 +57,12 @@ static void ZramMeter_display(const Object* cast, RichString* out) {
Meter_humanUnit(buffer, this->total, sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
- Meter_humanUnit(buffer, this->values[0], sizeof(buffer));
+ Meter_humanUnit(buffer, this->values[ZRAM_METER_COMPRESSED], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:");
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
- Meter_humanUnit(buffer, this->values[1], sizeof(buffer));
+ double uncompressed = this->values[ZRAM_METER_COMPRESSED] + this->values[ZRAM_METER_UNCOMPRESSED];
+ Meter_humanUnit(buffer, uncompressed, sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " uncompressed:");
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
}
@@ -64,7 +75,7 @@ const MeterClass ZramMeter_class = {
},
.updateValues = ZramMeter_updateValues,
.defaultMode = BAR_METERMODE,
- .maxItems = 2,
+ .maxItems = ZRAM_METER_ITEMCOUNT,
.total = 100.0,
.attributes = ZramMeter_attributes,
.name = "Zram",
diff --git a/linux/ZramMeter.h b/linux/ZramMeter.h
index ddba1bae..14a52155 100644
--- a/linux/ZramMeter.h
+++ b/linux/ZramMeter.h
@@ -1,8 +1,20 @@
#ifndef HEADER_ZramMeter
#define HEADER_ZramMeter
+/*
+htop - linux/ZramMeter.h
+(C) 2020 Murloc Knight
+(C) 2020-2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
#include "Meter.h"
+typedef enum {
+ ZRAM_METER_COMPRESSED = 0,
+ ZRAM_METER_UNCOMPRESSED = 1,
+ ZRAM_METER_ITEMCOUNT = 2, // number of entries in this enum
+} ZramMeterValues;
extern const MeterClass ZramMeter_class;
diff --git a/linux/ZramStats.h b/linux/ZramStats.h
index 67aadcc5..f71a6c27 100644
--- a/linux/ZramStats.h
+++ b/linux/ZramStats.h
@@ -1,5 +1,13 @@
#ifndef HEADER_ZramStats
#define HEADER_ZramStats
+/*
+htop - ZramStats.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "ProcessTable.h"
typedef struct ZramStats_ {
memory_t totalZram;
diff --git a/linux/ZswapStats.h b/linux/ZswapStats.h
new file mode 100644
index 00000000..29e516f4
--- /dev/null
+++ b/linux/ZswapStats.h
@@ -0,0 +1,19 @@
+#ifndef HEADER_ZswapStats
+#define HEADER_ZswapStats
+/*
+htop - ZswapStats.h
+(C) 2022 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "ProcessTable.h"
+
+typedef struct ZswapStats_ {
+ /* amount of RAM used by the zswap pool */
+ memory_t usedZswapComp;
+ /* amount of data stored inside the zswap pool */
+ memory_t usedZswapOrig;
+} ZswapStats;
+
+#endif
diff --git a/netbsd/NetBSDMachine.c b/netbsd/NetBSDMachine.c
new file mode 100644
index 00000000..79c50c11
--- /dev/null
+++ b/netbsd/NetBSDMachine.c
@@ -0,0 +1,285 @@
+/*
+htop - NetBSDMachine.c
+(C) 2014 Hisham H. Muhammad
+(C) 2015 Michael McConville
+(C) 2021 Santhosh Raju
+(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 "netbsd/NetBSDMachine.h"
+
+#include <kvm.h>
+#include <math.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <sys/sched.h>
+#include <sys/swap.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <uvm/uvm_extern.h>
+
+#include "CRT.h"
+#include "Machine.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+
+static const struct {
+ const char* name;
+ long int scale;
+} freqSysctls[] = {
+ { "machdep.est.frequency.current", 1 },
+ { "machdep.powernow.frequency.current", 1 },
+ { "machdep.intrepid.frequency.current", 1 },
+ { "machdep.loongson.frequency.current", 1 },
+ { "machdep.cpu.frequency.current", 1 },
+ { "machdep.frequency.current", 1 },
+ { "machdep.tsc_freq", 1000000 },
+};
+
+static void NetBSDMachine_updateCPUcount(NetBSDMachine* this) {
+ Machine* super = &this->super;
+
+ // Definitions for sysctl(3), cf. https://nxr.netbsd.org/xref/src/sys/sys/sysctl.h#813
+ const int mib_ncpu_existing[] = { CTL_HW, HW_NCPU }; // Number of existing CPUs
+ const int mib_ncpu_online[] = { CTL_HW, HW_NCPUONLINE }; // Number of online/active CPUs
+
+ int r;
+ unsigned int value;
+ size_t size;
+
+ bool change = false;
+
+ // Query the number of active/online CPUs.
+ size = sizeof(value);
+ r = sysctl(mib_ncpu_online, 2, &value, &size, NULL, 0);
+ if (r < 0 || value < 1) {
+ value = 1;
+ }
+
+ if (value != super->activeCPUs) {
+ super->activeCPUs = value;
+ change = true;
+ }
+
+ // Query the total number of CPUs.
+ size = sizeof(value);
+ r = sysctl(mib_ncpu_existing, 2, &value, &size, NULL, 0);
+ if (r < 0 || value < 1) {
+ value = super->activeCPUs;
+ }
+
+ if (value != super->existingCPUs) {
+ this->cpuData = xReallocArray(this->cpuData, value + 1, sizeof(CPUData));
+ super->existingCPUs = value;
+ change = true;
+ }
+
+ // Reset CPU stats when number of online/existing CPU cores changed
+ if (change) {
+ CPUData* dAvg = &this->cpuData[0];
+ memset(dAvg, '\0', sizeof(CPUData));
+ dAvg->totalTime = 1;
+ dAvg->totalPeriod = 1;
+
+ for (unsigned int i = 0; i < super->existingCPUs; i++) {
+ CPUData* d = &this->cpuData[i + 1];
+ memset(d, '\0', sizeof(CPUData));
+ d->totalTime = 1;
+ d->totalPeriod = 1;
+ }
+ }
+}
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ const int fmib[] = { CTL_KERN, KERN_FSCALE };
+ size_t size;
+ char errbuf[_POSIX2_LINE_MAX];
+
+ NetBSDMachine* this = xCalloc(1, sizeof(NetBSDMachine));
+ Machine* super = &this->super;
+ Machine_init(super, usersTable, userId);
+
+ NetBSDMachine_updateCPUcount(this);
+
+ size = sizeof(this->fscale);
+ if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0 || this->fscale <= 0) {
+ CRT_fatalError("fscale sysctl call failed");
+ }
+
+ if ((this->pageSize = sysconf(_SC_PAGESIZE)) == -1)
+ CRT_fatalError("pagesize sysconf call failed");
+ this->pageSizeKB = this->pageSize / ONE_K;
+
+ this->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
+ if (this->kd == NULL) {
+ CRT_fatalError("kvm_openfiles() failed");
+ }
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ NetBSDMachine* this = (NetBSDMachine*) super;
+
+ Machine_done(super);
+
+ if (this->kd) {
+ kvm_close(this->kd);
+ }
+ free(this->cpuData);
+ free(this);
+}
+
+static void NetBSDMachine_scanMemoryInfo(NetBSDMachine* this) {
+ Machine* super = &this->super;
+
+ static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2};
+ struct uvmexp_sysctl uvmexp;
+ size_t size_uvmexp = sizeof(uvmexp);
+
+ if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {
+ CRT_fatalError("uvmexp sysctl call failed");
+ }
+
+ super->totalMem = uvmexp.npages * this->pageSizeKB;
+ super->buffersMem = 0;
+ super->cachedMem = (uvmexp.filepages + uvmexp.execpages) * this->pageSizeKB;
+ super->usedMem = (uvmexp.active + uvmexp.wired) * this->pageSizeKB;
+ super->totalSwap = uvmexp.swpages * this->pageSizeKB;
+ super->usedSwap = uvmexp.swpginuse * this->pageSizeKB;
+}
+
+static void getKernelCPUTimes(int cpuId, u_int64_t* times) {
+ const int mib[] = { CTL_KERN, KERN_CP_TIME, cpuId };
+ size_t length = sizeof(*times) * CPUSTATES;
+ if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {
+ CRT_fatalError("sysctl kern.cp_time2 failed");
+ }
+}
+
+static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
+ unsigned long long totalTime = 0;
+ for (int i = 0; i < CPUSTATES; i++) {
+ totalTime += times[i];
+ }
+
+ unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
+
+ cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);
+ cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);
+ cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);
+ cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);
+ cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);
+ cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);
+ cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);
+
+ cpu->totalTime = totalTime;
+ cpu->userTime = times[CP_USER];
+ cpu->niceTime = times[CP_NICE];
+ cpu->sysTime = times[CP_SYS];
+ cpu->sysAllTime = sysAllTime;
+ cpu->intrTime = times[CP_INTR];
+ cpu->idleTime = times[CP_IDLE];
+}
+
+static void NetBSDMachine_scanCPUTime(NetBSDMachine* this) {
+ const Machine* super = &this->super;
+
+ u_int64_t kernelTimes[CPUSTATES] = {0};
+ u_int64_t avg[CPUSTATES] = {0};
+
+ for (unsigned int i = 0; i < super->existingCPUs; i++) {
+ getKernelCPUTimes(i, kernelTimes);
+ CPUData* cpu = &this->cpuData[i + 1];
+ kernelCPUTimesToHtop(kernelTimes, cpu);
+
+ avg[CP_USER] += cpu->userTime;
+ avg[CP_NICE] += cpu->niceTime;
+ avg[CP_SYS] += cpu->sysTime;
+ avg[CP_INTR] += cpu->intrTime;
+ avg[CP_IDLE] += cpu->idleTime;
+ }
+
+ for (int i = 0; i < CPUSTATES; i++) {
+ avg[i] /= super->activeCPUs;
+ }
+
+ kernelCPUTimesToHtop(avg, &this->cpuData[0]);
+}
+
+static void NetBSDMachine_scanCPUFrequency(NetBSDMachine* this) {
+ const Machine* super = &this->super;
+ unsigned int cpus = super->existingCPUs;
+ bool match = false;
+ char name[64];
+ long int freq = 0;
+ size_t freqSize;
+
+ for (unsigned int i = 0; i < cpus; i++) {
+ this->cpuData[i + 1].frequency = NAN;
+ }
+
+ /* newer hardware supports per-core frequency, for e.g. ARM big.LITTLE */
+ for (unsigned int i = 0; i < cpus; i++) {
+ xSnprintf(name, sizeof(name), "machdep.cpufreq.cpu%u.current", i);
+ freqSize = sizeof(freq);
+ if (sysctlbyname(name, &freq, &freqSize, NULL, 0) != -1) {
+ this->cpuData[i + 1].frequency = freq; /* already in MHz */
+ match = true;
+ }
+ }
+
+ if (match) {
+ return;
+ }
+
+ /*
+ * Iterate through legacy sysctl nodes for single-core frequency until
+ * we find a match...
+ */
+ for (size_t i = 0; i < ARRAYSIZE(freqSysctls); i++) {
+ freqSize = sizeof(freq);
+ if (sysctlbyname(freqSysctls[i].name, &freq, &freqSize, NULL, 0) != -1) {
+ freq /= freqSysctls[i].scale; /* scale to MHz */
+ match = true;
+ break;
+ }
+ }
+
+ if (match) {
+ for (unsigned int i = 0; i < cpus; i++) {
+ this->cpuData[i + 1].frequency = freq;
+ }
+ }
+}
+
+void Machine_scan(Machine* super) {
+ NetBSDMachine* this = (NetBSDMachine*) super;
+
+ NetBSDMachine_scanMemoryInfo(this);
+ NetBSDMachine_scanCPUTime(this);
+
+ if (super->settings->showCPUFrequency) {
+ NetBSDMachine_scanCPUFrequency(this);
+ }
+}
+
+bool Machine_isCPUonline(const Machine* host, unsigned int id) {
+ assert(id < host->existingCPUs);
+ (void)host; (void)id;
+
+ // TODO: Support detecting online / offline CPUs.
+ return true;
+}
diff --git a/netbsd/NetBSDProcessList.h b/netbsd/NetBSDMachine.h
index d228f487..9d3aa0b2 100644
--- a/netbsd/NetBSDProcessList.h
+++ b/netbsd/NetBSDMachine.h
@@ -1,7 +1,7 @@
-#ifndef HEADER_NetBSDProcessList
-#define HEADER_NetBSDProcessList
+#ifndef HEADER_NetBSDMachine
+#define HEADER_NetBSDMachine
/*
-htop - NetBSDProcessList.h
+htop - NetBSDMachine.h
(C) 2014 Hisham H. Muhammad
(C) 2015 Michael McConville
(C) 2021 Santhosh Raju
@@ -14,9 +14,8 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <sys/types.h>
-#include "Hashtable.h"
-#include "ProcessList.h"
-#include "UsersTable.h"
+#include "Machine.h"
+#include "ProcessTable.h"
typedef struct CPUData_ {
@@ -41,18 +40,15 @@ typedef struct CPUData_ {
double frequency;
} CPUData;
-typedef struct NetBSDProcessList_ {
- ProcessList super;
+typedef struct NetBSDMachine_ {
+ Machine super;
kvm_t* kd;
- CPUData* cpuData;
-} NetBSDProcessList;
-
-
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
+ long fscale;
+ int pageSize;
+ int pageSizeKB;
-void ProcessList_delete(ProcessList* this);
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+ CPUData* cpuData;
+} NetBSDMachine;
#endif
diff --git a/netbsd/NetBSDProcess.c b/netbsd/NetBSDProcess.c
index 4d4ac4ee..5462e721 100644
--- a/netbsd/NetBSDProcess.c
+++ b/netbsd/NetBSDProcess.c
@@ -8,6 +8,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 "netbsd/NetBSDProcess.h"
#include <stdlib.h>
@@ -149,6 +151,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
.flags = 0,
.defaultSortDesc = true,
.autoWidth = true,
+ .autoTitleRightAlign = true,
},
[PERCENT_NORM_CPU] = {
.name = "PERCENT_NORM_CPU",
@@ -212,11 +215,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
};
-Process* NetBSDProcess_new(const Settings* settings) {
+Process* NetBSDProcess_new(const Machine* host) {
NetBSDProcess* this = xCalloc(1, sizeof(NetBSDProcess));
Object_setClass(this, Class(NetBSDProcess));
- Process_init(&this->super, settings);
- return &this->super;
+ Process_init(&this->super, host);
+ return (Process*)this;
}
void Process_delete(Object* cast) {
@@ -225,16 +228,20 @@ void Process_delete(Object* cast) {
free(this);
}
-static void NetBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
+static void NetBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const NetBSDProcess* np = (const NetBSDProcess*) super;
+
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
+ //size_t n = sizeof(buffer) - 1;
switch (field) {
// add NetBSD-specific fields here
default:
- Process_writeField(this, str, field);
+ Process_writeField(&np->super, str, field);
return;
}
+
RichString_appendWide(str, attr, buffer);
}
@@ -254,11 +261,18 @@ static int NetBSDProcess_compareByKey(const Process* v1, const Process* v2, Proc
const ProcessClass NetBSDProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = NetBSDProcess_rowWriteField
},
- .writeField = NetBSDProcess_writeField,
.compareByKey = NetBSDProcess_compareByKey
};
diff --git a/netbsd/NetBSDProcess.h b/netbsd/NetBSDProcess.h
index b9e6b26a..1c068a4a 100644
--- a/netbsd/NetBSDProcess.h
+++ b/netbsd/NetBSDProcess.h
@@ -12,9 +12,9 @@ in the source distribution for its full text.
#include <stdbool.h>
+#include "Machine.h"
#include "Object.h"
#include "Process.h"
-#include "Settings.h"
typedef struct NetBSDProcess_ {
@@ -25,7 +25,7 @@ extern const ProcessClass NetBSDProcess_class;
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-Process* NetBSDProcess_new(const Settings* settings);
+Process* NetBSDProcess_new(const Machine* host);
void Process_delete(Object* cast);
diff --git a/netbsd/NetBSDProcessList.c b/netbsd/NetBSDProcessList.c
deleted file mode 100644
index 197a150b..00000000
--- a/netbsd/NetBSDProcessList.c
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
-htop - NetBSDProcessList.c
-(C) 2014 Hisham H. Muhammad
-(C) 2015 Michael McConville
-(C) 2021 Santhosh Raju
-(C) 2021 htop dev team
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "netbsd/NetBSDProcessList.h"
-
-#include <kvm.h>
-#include <math.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/mount.h>
-#include <sys/param.h>
-#include <sys/proc.h>
-#include <sys/sched.h>
-#include <sys/swap.h>
-#include <sys/sysctl.h>
-#include <sys/types.h>
-#include <uvm/uvm_extern.h>
-
-#include "CRT.h"
-#include "Macros.h"
-#include "Object.h"
-#include "Process.h"
-#include "ProcessList.h"
-#include "Settings.h"
-#include "XUtils.h"
-#include "netbsd/NetBSDProcess.h"
-
-
-static long fscale;
-static int pageSize;
-static int pageSizeKB;
-
-static const struct {
- const char* name;
- long int scale;
-} freqSysctls[] = {
- { "machdep.est.frequency.current", 1 },
- { "machdep.powernow.frequency.current", 1 },
- { "machdep.intrepid.frequency.current", 1 },
- { "machdep.loongson.frequency.current", 1 },
- { "machdep.cpu.frequency.current", 1 },
- { "machdep.frequency.current", 1 },
- { "machdep.tsc_freq", 1000000 },
-};
-
-static void NetBSDProcessList_updateCPUcount(ProcessList* super) {
- NetBSDProcessList* opl = (NetBSDProcessList*) super;
-
- // Definitions for sysctl(3), cf. https://nxr.netbsd.org/xref/src/sys/sys/sysctl.h#813
- const int mib_ncpu_existing[] = { CTL_HW, HW_NCPU }; // Number of existing CPUs
- const int mib_ncpu_online[] = { CTL_HW, HW_NCPUONLINE }; // Number of online/active CPUs
-
- int r;
- unsigned int value;
- size_t size;
-
- bool change = false;
-
- // Query the number of active/online CPUs.
- size = sizeof(value);
- r = sysctl(mib_ncpu_online, 2, &value, &size, NULL, 0);
- if (r < 0 || value < 1) {
- value = 1;
- }
-
- if (value != super->activeCPUs) {
- super->activeCPUs = value;
- change = true;
- }
-
- // Query the total number of CPUs.
- size = sizeof(value);
- r = sysctl(mib_ncpu_existing, 2, &value, &size, NULL, 0);
- if (r < 0 || value < 1) {
- value = super->activeCPUs;
- }
-
- if (value != super->existingCPUs) {
- opl->cpuData = xReallocArray(opl->cpuData, value + 1, sizeof(CPUData));
- super->existingCPUs = value;
- change = true;
- }
-
- // Reset CPU stats when number of online/existing CPU cores changed
- if (change) {
- CPUData* dAvg = &opl->cpuData[0];
- memset(dAvg, '\0', sizeof(CPUData));
- dAvg->totalTime = 1;
- dAvg->totalPeriod = 1;
-
- for (unsigned int i = 0; i < super->existingCPUs; i++) {
- CPUData* d = &opl->cpuData[i + 1];
- memset(d, '\0', sizeof(CPUData));
- d->totalTime = 1;
- d->totalPeriod = 1;
- }
- }
-}
-
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
- const int fmib[] = { CTL_KERN, KERN_FSCALE };
- size_t size;
- char errbuf[_POSIX2_LINE_MAX];
-
- NetBSDProcessList* npl = xCalloc(1, sizeof(NetBSDProcessList));
- ProcessList* pl = (ProcessList*) npl;
- ProcessList_init(pl, Class(NetBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
-
- NetBSDProcessList_updateCPUcount(pl);
-
- size = sizeof(fscale);
- if (sysctl(fmib, 2, &fscale, &size, NULL, 0) < 0) {
- CRT_fatalError("fscale sysctl call failed");
- }
-
- if ((pageSize = sysconf(_SC_PAGESIZE)) == -1)
- CRT_fatalError("pagesize sysconf call failed");
- pageSizeKB = pageSize / ONE_K;
-
- npl->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
- if (npl->kd == NULL) {
- CRT_fatalError("kvm_openfiles() failed");
- }
-
- return pl;
-}
-
-void ProcessList_delete(ProcessList* this) {
- NetBSDProcessList* npl = (NetBSDProcessList*) this;
-
- if (npl->kd) {
- kvm_close(npl->kd);
- }
-
- free(npl->cpuData);
-
- ProcessList_done(this);
- free(this);
-}
-
-static void NetBSDProcessList_scanMemoryInfo(ProcessList* pl) {
- static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2};
- struct uvmexp_sysctl uvmexp;
- size_t size_uvmexp = sizeof(uvmexp);
-
- if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {
- CRT_fatalError("uvmexp sysctl call failed");
- }
-
- pl->totalMem = uvmexp.npages * pageSizeKB;
- pl->buffersMem = 0;
- pl->cachedMem = (uvmexp.filepages + uvmexp.execpages) * pageSizeKB;
- pl->usedMem = (uvmexp.active + uvmexp.wired) * pageSizeKB;
- pl->totalSwap = uvmexp.swpages * pageSizeKB;
- pl->usedSwap = uvmexp.swpginuse * pageSizeKB;
-}
-
-static void NetBSDProcessList_updateExe(const struct kinfo_proc2* kproc, Process* proc) {
- const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_PATHNAME };
- 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 NetBSDProcessList_updateCwd(const struct kinfo_proc2* kproc, Process* proc) {
- const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_CWD };
- 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 NetBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc2* kproc, Process* proc) {
- Process_updateComm(proc, kproc->p_comm);
-
- /*
- * Like NetBSD's top(1), we try to fall back to the command name
- * (argv[0]) if we fail to construct the full command.
- */
- char** arg = kvm_getargv2(kd, kproc, 500);
- if (arg == NULL || *arg == NULL) {
- Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
- return;
- }
-
- size_t len = 0;
- for (int i = 0; arg[i] != NULL; i++) {
- len += strlen(arg[i]) + 1; /* room for arg and trailing space or NUL */
- }
-
- /* don't use xMalloc here - we want to handle huge argv's gracefully */
- char* s;
- if ((s = malloc(len)) == NULL) {
- Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
- return;
- }
-
- *s = '\0';
-
- int start = 0;
- int end = 0;
- for (int i = 0; arg[i] != NULL; i++) {
- size_t n = strlcat(s, arg[i], len);
- if (i == 0) {
- end = MINIMUM(n, len - 1);
- /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */
- for (int j = end; j > 0; j--) {
- if (arg[0][j] == ' ' && arg[0][j - 1] != '\\') {
- end = (arg[0][j - 1] == ':') ? (j - 1) : j;
- }
- }
- }
- /* the trailing space should get truncated anyway */
- strlcat(s, " ", len);
- }
-
- Process_updateCmdline(proc, s, start, end);
-
- free(s);
-}
-
-/*
- * Borrowed with modifications from NetBSD's top(1).
- */
-static double getpcpu(const struct kinfo_proc2* kp) {
- if (fscale == 0)
- return 0.0;
-
- return 100.0 * (double)kp->p_pctcpu / fscale;
-}
-
-static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) {
- const Settings* settings = this->super.settings;
- bool hideKernelThreads = settings->hideKernelThreads;
- bool hideUserlandThreads = settings->hideUserlandThreads;
- int count = 0;
-
- const struct kinfo_proc2* kprocs = kvm_getproc2(this->kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), &count);
-
- for (int i = 0; i < count; i++) {
- const struct kinfo_proc2* kproc = &kprocs[i];
-
- bool preExisting = false;
- Process* proc = ProcessList_getProcess(&this->super, kproc->p_pid, &preExisting, NetBSDProcess_new);
-
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
- if (!preExisting) {
- proc->pid = kproc->p_pid;
- proc->ppid = kproc->p_ppid;
- proc->tpgid = kproc->p_tpgid;
- proc->tgid = kproc->p_pid;
- proc->session = kproc->p_sid;
- proc->pgrp = kproc->p__pgid;
- proc->isKernelThread = !!(kproc->p_flag & P_SYSTEM);
- proc->isUserlandThread = proc->pid != proc->tgid;
- proc->starttime_ctime = kproc->p_ustart_sec;
- Process_fillStarttimeBuffer(proc);
- ProcessList_add(&this->super, proc);
-
- proc->tty_nr = kproc->p_tdev;
- const char* name = ((dev_t)kproc->p_tdev != KERN_PROC_TTY_NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL;
- if (!name) {
- free(proc->tty_name);
- proc->tty_name = NULL;
- } else {
- free_and_xStrdup(&proc->tty_name, name);
- }
-
- NetBSDProcessList_updateExe(kproc, proc);
- NetBSDProcessList_updateProcessName(this->kd, kproc, proc);
- } else {
- if (settings->updateProcessNames) {
- NetBSDProcessList_updateProcessName(this->kd, kproc, proc);
- }
- }
-
- if (settings->ss->flags & PROCESS_FLAG_CWD) {
- NetBSDProcessList_updateCwd(kproc, proc);
- }
-
- if (proc->st_uid != kproc->p_uid) {
- proc->st_uid = kproc->p_uid;
- proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid);
- }
-
- proc->m_virt = kproc->p_vm_vsize;
- proc->m_resident = kproc->p_vm_rssize;
-
- proc->percent_mem = (proc->m_resident * pageSizeKB) / (double)(this->super.totalMem) * 100.0;
- proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0, this->super.activeCPUs * 100.0);
- Process_updateCPUFieldWidths(proc->percent_cpu);
-
- proc->nlwp = kproc->p_nlwps;
- proc->nice = kproc->p_nice - 20;
- proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
- proc->priority = kproc->p_priority - PZERO;
- proc->processor = kproc->p_cpuid;
- proc->minflt = kproc->p_uru_minflt;
- proc->majflt = kproc->p_uru_majflt;
-
- int nlwps = 0;
- const struct kinfo_lwp* klwps = kvm_getlwps(this->kd, kproc->p_pid, kproc->p_paddr, sizeof(struct kinfo_lwp), &nlwps);
-
- /* TODO: According to the link below, SDYING should be a regarded state */
- /* Taken from: https://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/sys/proc.h */
- switch (kproc->p_realstat) {
- case SIDL: proc->state = IDLE; break;
- case SACTIVE:
- // We only consider the first LWP with a one of the below states.
- for (int j = 0; j < nlwps; j++) {
- if (klwps) {
- switch (klwps[j].l_stat) {
- case LSONPROC: proc->state = RUNNING; break;
- case LSRUN: proc->state = RUNNABLE; break;
- case LSSLEEP: proc->state = SLEEPING; break;
- case LSSTOP: proc->state = STOPPED; break;
- default: proc->state = UNKNOWN;
- }
- if (proc->state != UNKNOWN)
- break;
- } else {
- proc->state = UNKNOWN;
- break;
- }
- }
- break;
- case SSTOP: proc->state = STOPPED; break;
- case SZOMB: proc->state = ZOMBIE; break;
- case SDEAD: proc->state = DEFUNCT; break;
- default: proc->state = UNKNOWN;
- }
-
- if (Process_isKernelThread(proc)) {
- this->super.kernelThreads++;
- } else if (Process_isUserlandThread(proc)) {
- this->super.userlandThreads++;
- }
-
- this->super.totalTasks++;
- if (proc->state == RUNNING) {
- this->super.runningTasks++;
- }
- proc->updated = true;
- }
-}
-
-static void getKernelCPUTimes(int cpuId, u_int64_t* times) {
- const int mib[] = { CTL_KERN, KERN_CP_TIME, cpuId };
- size_t length = sizeof(*times) * CPUSTATES;
- if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {
- CRT_fatalError("sysctl kern.cp_time2 failed");
- }
-}
-
-static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
- unsigned long long totalTime = 0;
- for (int i = 0; i < CPUSTATES; i++) {
- totalTime += times[i];
- }
-
- unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
-
- cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);
- cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);
- cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);
- cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);
- cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);
- cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);
- cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);
-
- cpu->totalTime = totalTime;
- cpu->userTime = times[CP_USER];
- cpu->niceTime = times[CP_NICE];
- cpu->sysTime = times[CP_SYS];
- cpu->sysAllTime = sysAllTime;
- cpu->intrTime = times[CP_INTR];
- cpu->idleTime = times[CP_IDLE];
-}
-
-static void NetBSDProcessList_scanCPUTime(NetBSDProcessList* this) {
- u_int64_t kernelTimes[CPUSTATES] = {0};
- u_int64_t avg[CPUSTATES] = {0};
-
- for (unsigned int i = 0; i < this->super.existingCPUs; i++) {
- getKernelCPUTimes(i, kernelTimes);
- CPUData* cpu = &this->cpuData[i + 1];
- kernelCPUTimesToHtop(kernelTimes, cpu);
-
- avg[CP_USER] += cpu->userTime;
- avg[CP_NICE] += cpu->niceTime;
- avg[CP_SYS] += cpu->sysTime;
- avg[CP_INTR] += cpu->intrTime;
- avg[CP_IDLE] += cpu->idleTime;
- }
-
- for (int i = 0; i < CPUSTATES; i++) {
- avg[i] /= this->super.activeCPUs;
- }
-
- kernelCPUTimesToHtop(avg, &this->cpuData[0]);
-}
-
-static void NetBSDProcessList_scanCPUFrequency(NetBSDProcessList* this) {
- unsigned int cpus = this->super.existingCPUs;
- bool match = false;
- char name[64];
- long int freq = 0;
- size_t freqSize;
-
- for (unsigned int i = 0; i < cpus; i++) {
- this->cpuData[i + 1].frequency = NAN;
- }
-
- /* newer hardware supports per-core frequency, for e.g. ARM big.LITTLE */
- for (unsigned int i = 0; i < cpus; i++) {
- xSnprintf(name, sizeof(name), "machdep.cpufreq.cpu%u.current", i);
- freqSize = sizeof(freq);
- if (sysctlbyname(name, &freq, &freqSize, NULL, 0) != -1) {
- this->cpuData[i + 1].frequency = freq; /* already in MHz */
- match = true;
- }
- }
-
- if (match) {
- return;
- }
-
- /*
- * Iterate through legacy sysctl nodes for single-core frequency until
- * we find a match...
- */
- for (size_t i = 0; i < ARRAYSIZE(freqSysctls); i++) {
- freqSize = sizeof(freq);
- if (sysctlbyname(freqSysctls[i].name, &freq, &freqSize, NULL, 0) != -1) {
- freq /= freqSysctls[i].scale; /* scale to MHz */
- match = true;
- break;
- }
- }
-
- if (match) {
- for (unsigned int i = 0; i < cpus; i++) {
- this->cpuData[i + 1].frequency = freq;
- }
- }
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
- NetBSDProcessList* npl = (NetBSDProcessList*) super;
-
- NetBSDProcessList_scanMemoryInfo(super);
- NetBSDProcessList_scanCPUTime(npl);
-
- if (super->settings->showCPUFrequency) {
- NetBSDProcessList_scanCPUFrequency(npl);
- }
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
- NetBSDProcessList_scanProcs(npl);
-}
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
- assert(id < super->existingCPUs);
-
- // TODO: Support detecting online / offline CPUs.
- return true;
-}
diff --git a/netbsd/NetBSDProcessTable.c b/netbsd/NetBSDProcessTable.c
new file mode 100644
index 00000000..71ae53e7
--- /dev/null
+++ b/netbsd/NetBSDProcessTable.c
@@ -0,0 +1,273 @@
+/*
+htop - NetBSDProcessTable.c
+(C) 2014 Hisham H. Muhammad
+(C) 2015 Michael McConville
+(C) 2021 Santhosh Raju
+(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 "netbsd/NetBSDProcessTable.h"
+
+#include <kvm.h>
+#include <math.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <sys/sched.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <uvm/uvm_extern.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Process.h"
+#include "ProcessTable.h"
+#include "Settings.h"
+#include "XUtils.h"
+#include "netbsd/NetBSDMachine.h"
+#include "netbsd/NetBSDProcess.h"
+
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ NetBSDProcessTable* this = xCalloc(1, sizeof(NetBSDProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = (ProcessTable*) this;
+ ProcessTable_init(super, Class(NetBSDProcess), host, pidMatchList);
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ NetBSDProcessTable* this = (NetBSDProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ free(this);
+}
+
+static void NetBSDProcessTable_updateExe(const struct kinfo_proc2* kproc, Process* proc) {
+ const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_PATHNAME };
+ 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 NetBSDProcessTable_updateCwd(const struct kinfo_proc2* kproc, Process* proc) {
+ const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_CWD };
+ 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 NetBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc2* kproc, Process* proc) {
+ Process_updateComm(proc, kproc->p_comm);
+
+ /*
+ * Like NetBSD's top(1), we try to fall back to the command name
+ * (argv[0]) if we fail to construct the full command.
+ */
+ char** arg = kvm_getargv2(kd, kproc, 500);
+ if (arg == NULL || *arg == NULL) {
+ Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
+ return;
+ }
+
+ size_t len = 0;
+ for (int i = 0; arg[i] != NULL; i++) {
+ len += strlen(arg[i]) + 1; /* room for arg and trailing space or NUL */
+ }
+
+ /* don't use xMalloc here - we want to handle huge argv's gracefully */
+ char* s;
+ if ((s = malloc(len)) == NULL) {
+ Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
+ return;
+ }
+
+ *s = '\0';
+
+ int start = 0;
+ int end = 0;
+ for (int i = 0; arg[i] != NULL; i++) {
+ size_t n = strlcat(s, arg[i], len);
+ if (i == 0) {
+ end = MINIMUM(n, len - 1);
+ /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */
+ for (int j = end; j > 0; j--) {
+ if (arg[0][j] == ' ' && arg[0][j - 1] != '\\') {
+ end = (arg[0][j - 1] == ':') ? (j - 1) : j;
+ }
+ }
+ }
+ /* the trailing space should get truncated anyway */
+ strlcat(s, " ", len);
+ }
+
+ Process_updateCmdline(proc, s, start, end);
+
+ free(s);
+}
+
+/*
+ * Borrowed with modifications from NetBSD's top(1).
+ */
+static double getpcpu(const NetBSDMachine* nhost, const struct kinfo_proc2* kp) {
+ if (nhost->fscale == 0)
+ return 0.0;
+
+ return 100.0 * (double)kp->p_pctcpu / nhost->fscale;
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ const Machine* host = super->super.host;
+ const NetBSDMachine* nhost = (const NetBSDMachine*) host;
+ const Settings* settings = host->settings;
+ bool hideKernelThreads = settings->hideKernelThreads;
+ bool hideUserlandThreads = settings->hideUserlandThreads;
+ int count = 0;
+
+ const struct kinfo_proc2* kprocs = kvm_getproc2(nhost->kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), &count);
+
+ for (int i = 0; i < count; i++) {
+ const struct kinfo_proc2* kproc = &kprocs[i];
+
+ bool preExisting = false;
+ Process* proc = ProcessTable_getProcess(super, kproc->p_pid, &preExisting, NetBSDProcess_new);
+
+ proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+
+ if (!preExisting) {
+ Process_setPid(proc, kproc->p_pid);
+ Process_setParent(proc, kproc->p_ppid);
+ Process_setThreadGroup(proc, kproc->p_pid);
+ proc->tpgid = kproc->p_tpgid;
+ proc->session = kproc->p_sid;
+ proc->pgrp = kproc->p__pgid;
+ proc->isKernelThread = !!(kproc->p_flag & P_SYSTEM);
+ proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); // eh?
+ proc->starttime_ctime = kproc->p_ustart_sec;
+ Process_fillStarttimeBuffer(proc);
+ ProcessTable_add(super, proc);
+
+ proc->tty_nr = kproc->p_tdev;
+ // KERN_PROC_TTY_NODEV is a negative constant but the type of
+ // kproc->p_tdev may be unsigned.
+ const char* name = ((dev_t)~kproc->p_tdev != (dev_t)~(KERN_PROC_TTY_NODEV)) ? devname(kproc->p_tdev, S_IFCHR) : NULL;
+ if (!name) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
+
+ NetBSDProcessTable_updateExe(kproc, proc);
+ NetBSDProcessTable_updateProcessName(nhost->kd, kproc, proc);
+ } else {
+ if (settings->updateProcessNames) {
+ NetBSDProcessTable_updateProcessName(nhost->kd, kproc, proc);
+ }
+ }
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ NetBSDProcessTable_updateCwd(kproc, proc);
+ }
+
+ if (proc->st_uid != kproc->p_uid) {
+ proc->st_uid = kproc->p_uid;
+ proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);
+ }
+
+ proc->m_virt = kproc->p_vm_vsize;
+ proc->m_resident = kproc->p_vm_rssize;
+
+ proc->percent_mem = (proc->m_resident * nhost->pageSizeKB) / (double)(host->totalMem) * 100.0;
+ proc->percent_cpu = CLAMP(getpcpu(nhost, kproc), 0.0, host->activeCPUs * 100.0);
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ proc->nlwp = kproc->p_nlwps;
+ proc->nice = kproc->p_nice - 20;
+ proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
+ proc->priority = kproc->p_priority - PZERO;
+ proc->processor = kproc->p_cpuid;
+ proc->minflt = kproc->p_uru_minflt;
+ proc->majflt = kproc->p_uru_majflt;
+
+ int nlwps = 0;
+ const struct kinfo_lwp* klwps = kvm_getlwps(nhost->kd, kproc->p_pid, kproc->p_paddr, sizeof(struct kinfo_lwp), &nlwps);
+
+ /* TODO: According to the link below, SDYING should be a regarded state */
+ /* Taken from: https://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/sys/proc.h */
+ switch (kproc->p_realstat) {
+ case SIDL: proc->state = IDLE; break;
+ case SACTIVE:
+ // We only consider the first LWP with a one of the below states.
+ for (int j = 0; j < nlwps; j++) {
+ if (klwps) {
+ switch (klwps[j].l_stat) {
+ case LSONPROC: proc->state = RUNNING; break;
+ case LSRUN: proc->state = RUNNABLE; break;
+ case LSSLEEP: proc->state = SLEEPING; break;
+ case LSSTOP: proc->state = STOPPED; break;
+ default: proc->state = UNKNOWN;
+ }
+
+ if (proc->state != UNKNOWN) {
+ break;
+ }
+ } else {
+ proc->state = UNKNOWN;
+ break;
+ }
+ }
+ break;
+ case SSTOP: proc->state = STOPPED; break;
+ case SZOMB: proc->state = ZOMBIE; break;
+ case SDEAD: proc->state = DEFUNCT; break;
+ default: proc->state = UNKNOWN;
+ }
+
+ if (Process_isKernelThread(proc)) {
+ super->kernelThreads++;
+ } else if (Process_isUserlandThread(proc)) {
+ super->userlandThreads++;
+ }
+
+ super->totalTasks++;
+ if (proc->state == RUNNING) {
+ super->runningTasks++;
+ }
+ proc->super.updated = true;
+ }
+}
diff --git a/netbsd/NetBSDProcessTable.h b/netbsd/NetBSDProcessTable.h
new file mode 100644
index 00000000..1bcfa985
--- /dev/null
+++ b/netbsd/NetBSDProcessTable.h
@@ -0,0 +1,24 @@
+#ifndef HEADER_NetBSDProcessTable
+#define HEADER_NetBSDProcessTable
+/*
+htop - NetBSDProcessTable.h
+(C) 2014 Hisham H. Muhammad
+(C) 2015 Michael McConville
+(C) 2021 Santhosh Raju
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "Hashtable.h"
+#include "ProcessTable.h"
+
+
+typedef struct NetBSDProcessTable_ {
+ ProcessTable super;
+} NetBSDProcessTable;
+
+#endif
diff --git a/netbsd/Platform.c b/netbsd/Platform.c
index cf6079db..f458c239 100644
--- a/netbsd/Platform.c
+++ b/netbsd/Platform.c
@@ -9,6 +9,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 "netbsd/Platform.h"
#include <errno.h>
@@ -38,13 +40,13 @@ in the source distribution for its full text.
#include "ClockMeter.h"
#include "DateMeter.h"
#include "DateTimeMeter.h"
+#include "FileDescriptorMeter.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
#include "MemoryMeter.h"
#include "MemorySwapMeter.h"
#include "Meter.h"
-#include "ProcessList.h"
#include "Settings.h"
#include "SignalsPanel.h"
#include "SwapMeter.h"
@@ -52,8 +54,9 @@ in the source distribution for its full text.
#include "TasksMeter.h"
#include "UptimeMeter.h"
#include "XUtils.h"
+#include "generic/fdstat_sysctl.h"
+#include "netbsd/NetBSDMachine.h"
#include "netbsd/NetBSDProcess.h"
-#include "netbsd/NetBSDProcessList.h"
/*
* The older proplib APIs will be deprecated in NetBSD 10, but we still
@@ -179,6 +182,7 @@ const MeterClass* const Platform_meterTypes[] = {
&BlankMeter_class,
&DiskIOMeter_class,
&NetworkIOMeter_class,
+ &FileDescriptorMeter_class,
NULL
};
@@ -196,7 +200,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
struct timeval bootTime, currTime;
const int mib[2] = { CTL_KERN, KERN_BOOTTIME };
size_t size = sizeof(bootTime);
@@ -227,22 +231,23 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;
}
-int Platform_getMaxPid() {
+pid_t Platform_getMaxPid(void) {
// https://nxr.netbsd.org/xref/src/sys/sys/ansi.h#__pid_t
// pid is assigned as a 32bit Integer.
return INT32_MAX;
}
double Platform_setCPUValues(Meter* this, int cpu) {
- const NetBSDProcessList* npl = (const NetBSDProcessList*) this->pl;
- const CPUData* cpuData = &npl->cpuData[cpu];
+ const Machine* host = this->host;
+ const NetBSDMachine* nhost = (const NetBSDMachine*) host;
+ const CPUData* cpuData = &nhost->cpuData[cpu];
double total = cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod;
double totalPercent;
double* v = this->values;
v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;
v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;
- if (this->pl->settings->detailedCPUTime) {
+ if (host->settings->detailedCPUTime) {
v[CPU_METER_KERNEL] = cpuData->sysPeriod / total * 100.0;
v[CPU_METER_IRQ] = cpuData->intrPeriod / total * 100.0;
v[CPU_METER_SOFTIRQ] = 0.0;
@@ -251,14 +256,12 @@ double Platform_setCPUValues(Meter* this, int cpu) {
v[CPU_METER_IOWAIT] = 0.0;
v[CPU_METER_FREQUENCY] = NAN;
this->curItems = 8;
- totalPercent = v[0] + v[1] + v[2] + v[3];
} else {
- v[2] = cpuData->sysAllPeriod / total * 100.0;
- v[3] = 0.0; // No steal nor guest on NetBSD
- totalPercent = v[0] + v[1] + v[2];
+ v[CPU_METER_KERNEL] = cpuData->sysAllPeriod / total * 100.0;
+ v[CPU_METER_IRQ] = 0.0; // No steal nor guest on NetBSD
this->curItems = 4;
}
-
+ totalPercent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ];
totalPercent = CLAMP(totalPercent, 0.0, 100.0);
v[CPU_METER_FREQUENCY] = cpuData->frequency;
@@ -268,20 +271,22 @@ double Platform_setCPUValues(Meter* this, int cpu) {
}
void Platform_setMemoryValues(Meter* this) {
- const ProcessList* pl = this->pl;
- this->total = pl->totalMem;
- this->values[0] = pl->usedMem;
- this->values[1] = pl->buffersMem;
- // this->values[2] = "shared memory, like tmpfs and shm"
- this->values[3] = pl->cachedMem;
- // this->values[4] = "available memory"
+ const Machine* host = this->host;
+ this->total = host->totalMem;
+ this->values[MEMORY_METER_USED] = host->usedMem;
+ // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux"
+ this->values[MEMORY_METER_BUFFERS] = host->buffersMem;
+ this->values[MEMORY_METER_CACHE] = host->cachedMem;
+ // this->values[MEMORY_METER_AVAILABLE] = "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;
+ const Machine* host = this->host;
+ this->total = host->totalSwap;
+ this->values[SWAP_METER_USED] = host->usedSwap;
+ // this->values[SWAP_METER_CACHE] = "pages that are both in swap and RAM, like SwapCached on linux"
+ // this->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux"
}
char* Platform_getProcessEnv(pid_t pid) {
@@ -338,20 +343,18 @@ end:
return env;
}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
(void)pid;
- (void)inode;
return NULL;
}
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+void Platform_getFileDescriptors(double* used, double* max) {
+ Generic_getFileDescriptors_sysctl(used, max);
}
bool Platform_getDiskIO(DiskIOData* data) {
const int mib[] = { CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl) };
- struct io_sysctl *iostats = NULL;
+ struct io_sysctl* iostats = NULL;
size_t size = 0;
for (int retry = 3; retry > 0; retry--) {
@@ -414,7 +417,7 @@ bool Platform_getNetworkIO(NetworkIOData* data) {
if (ifa->ifa_flags & IFF_LOOPBACK)
continue;
- const struct if_data* ifd = (const struct if_data *)ifa->ifa_data;
+ const struct if_data* ifd = (const struct if_data*)ifa->ifa_data;
data->bytesReceived += ifd->ifi_ibytes;
data->packetsReceived += ifd->ifi_ipackets;
diff --git a/netbsd/Platform.h b/netbsd/Platform.h
index 3ad51e28..a543f52d 100644
--- a/netbsd/Platform.h
+++ b/netbsd/Platform.h
@@ -55,7 +55,7 @@ int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, int cpu);
@@ -65,10 +65,10 @@ void Platform_setSwapValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+void Platform_getFileDescriptors(double* used, double* max);
+
bool Platform_getDiskIO(DiskIOData* data);
bool Platform_getNetworkIO(NetworkIOData* data);
@@ -97,7 +97,9 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) {
Generic_gettime_monotonic(msec);
}
-static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
@@ -107,12 +109,30 @@ 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 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 const char* Platform_dynamicColumnName(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;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
-static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
#endif
diff --git a/openbsd/OpenBSDMachine.c b/openbsd/OpenBSDMachine.c
new file mode 100644
index 00000000..0ff893b5
--- /dev/null
+++ b/openbsd/OpenBSDMachine.c
@@ -0,0 +1,290 @@
+/*
+htop - OpenBSDMachine.c
+(C) 2014 Hisham H. Muhammad
+(C) 2015 Michael McConville
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "openbsd/OpenBSDMachine.h"
+
+#include <kvm.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <sys/sched.h>
+#include <sys/swap.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <uvm/uvmexp.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+
+static void OpenBSDMachine_updateCPUcount(OpenBSDMachine* this) {
+ Machine* super = &this->super;
+ const int nmib[] = { CTL_HW, HW_NCPU };
+ const int mib[] = { CTL_HW, HW_NCPUONLINE };
+ int r;
+ unsigned int value;
+ size_t size;
+ bool change = false;
+
+ size = sizeof(value);
+ r = sysctl(mib, 2, &value, &size, NULL, 0);
+ if (r < 0 || value < 1) {
+ value = 1;
+ }
+
+ if (value != super->activeCPUs) {
+ super->activeCPUs = value;
+ change = true;
+ }
+
+ size = sizeof(value);
+ r = sysctl(nmib, 2, &value, &size, NULL, 0);
+ if (r < 0 || value < 1) {
+ value = super->activeCPUs;
+ }
+
+ if (value != super->existingCPUs) {
+ this->cpuData = xReallocArray(this->cpuData, value + 1, sizeof(CPUData));
+ super->existingCPUs = value;
+ change = true;
+ }
+
+ if (change) {
+ CPUData* dAvg = &this->cpuData[0];
+ memset(dAvg, '\0', sizeof(CPUData));
+ dAvg->totalTime = 1;
+ dAvg->totalPeriod = 1;
+ dAvg->online = true;
+
+ for (unsigned int i = 0; i < super->existingCPUs; i++) {
+ CPUData* d = &this->cpuData[i + 1];
+ memset(d, '\0', sizeof(CPUData));
+ d->totalTime = 1;
+ d->totalPeriod = 1;
+
+ const int ncmib[] = { CTL_KERN, KERN_CPUSTATS, i };
+ struct cpustats cpu_stats;
+
+ size = sizeof(cpu_stats);
+ if (sysctl(ncmib, 3, &cpu_stats, &size, NULL, 0) < 0) {
+ CRT_fatalError("ncmib sysctl call failed");
+ }
+ d->online = (cpu_stats.cs_flags & CPUSTATS_ONLINE);
+ }
+ }
+}
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ const int fmib[] = { CTL_KERN, KERN_FSCALE };
+ size_t size;
+ char errbuf[_POSIX2_LINE_MAX];
+
+ OpenBSDMachine* this = xCalloc(1, sizeof(OpenBSDMachine));
+ Machine* super = &this->super;
+
+ Machine_init(super, usersTable, userId);
+
+ OpenBSDMachine_updateCPUcount(this);
+
+ size = sizeof(this->fscale);
+ if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0 || this->fscale <= 0) {
+ CRT_fatalError("fscale sysctl call failed");
+ }
+
+ if ((this->pageSize = sysconf(_SC_PAGESIZE)) == -1)
+ CRT_fatalError("pagesize sysconf call failed");
+ this->pageSizeKB = this->pageSize / ONE_K;
+
+ this->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
+ if (this->kd == NULL) {
+ CRT_fatalError("kvm_openfiles() failed");
+ }
+
+ this->cpuSpeed = -1;
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ OpenBSDMachine* this = (OpenBSDMachine*) super;
+
+ if (this->kd) {
+ kvm_close(this->kd);
+ }
+ free(this->cpuData);
+ Machine_done(super);
+ free(this);
+}
+
+static void OpenBSDMachine_scanMemoryInfo(OpenBSDMachine* this) {
+ Machine* super = &this->super;
+ const int uvmexp_mib[] = { CTL_VM, VM_UVMEXP };
+ struct uvmexp uvmexp;
+ size_t size_uvmexp = sizeof(uvmexp);
+
+ if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {
+ CRT_fatalError("uvmexp sysctl call failed");
+ }
+
+ super->totalMem = uvmexp.npages * this->pageSizeKB;
+ super->usedMem = (uvmexp.npages - uvmexp.free - uvmexp.paging) * this->pageSizeKB;
+
+ // Taken from OpenBSD systat/iostat.c, top/machine.c and uvm_sysctl(9)
+ const int bcache_mib[] = { CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT };
+ struct bcachestats bcstats;
+ size_t size_bcstats = sizeof(bcstats);
+
+ if (sysctl(bcache_mib, 3, &bcstats, &size_bcstats, NULL, 0) < 0) {
+ CRT_fatalError("cannot get vfs.bcachestat");
+ }
+
+ super->cachedMem = bcstats.numbufpages * this->pageSizeKB;
+
+ /*
+ * Copyright (c) 1994 Thorsten Lockert <tholo@sigmasoft.com>
+ * All rights reserved.
+ *
+ * Taken almost directly from OpenBSD's top(1)
+ *
+ * Originally released under a BSD-3 license
+ * Modified through htop developers applying GPL-2
+ */
+ int nswap = swapctl(SWAP_NSWAP, 0, 0);
+ if (nswap > 0) {
+ struct swapent* swdev = xMallocArray(nswap, sizeof(struct swapent));
+ int rnswap = swapctl(SWAP_STATS, swdev, nswap);
+
+ /* Total things up */
+ unsigned long long int total = 0, used = 0;
+ for (int i = 0; i < rnswap; i++) {
+ if (swdev[i].se_flags & SWF_ENABLE) {
+ used += (swdev[i].se_inuse / (1024 / DEV_BSIZE));
+ total += (swdev[i].se_nblks / (1024 / DEV_BSIZE));
+ }
+ }
+
+ super->totalSwap = total;
+ super->usedSwap = used;
+
+ free(swdev);
+ } else {
+ super->totalSwap = super->usedSwap = 0;
+ }
+}
+
+static void getKernelCPUTimes(unsigned int cpuId, u_int64_t* times) {
+ const int mib[] = { CTL_KERN, KERN_CPTIME2, cpuId };
+ size_t length = sizeof(*times) * CPUSTATES;
+ if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {
+ CRT_fatalError("sysctl kern.cp_time2 failed");
+ }
+}
+
+static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
+ unsigned long long totalTime = 0;
+ for (int i = 0; i < CPUSTATES; i++) {
+ totalTime += times[i];
+ }
+
+ unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
+
+ // XXX Not sure if CP_SPIN should be added to sysAllTime.
+ // See https://github.com/openbsd/src/commit/531d8034253fb82282f0f353c086e9ad827e031c
+ #ifdef CP_SPIN
+ sysAllTime += times[CP_SPIN];
+ #endif
+
+ cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);
+ cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);
+ cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);
+ cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);
+ cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);
+ #ifdef CP_SPIN
+ cpu->spinPeriod = saturatingSub(times[CP_SPIN], cpu->spinTime);
+ #endif
+ cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);
+ cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);
+
+ cpu->totalTime = totalTime;
+ cpu->userTime = times[CP_USER];
+ cpu->niceTime = times[CP_NICE];
+ cpu->sysTime = times[CP_SYS];
+ cpu->sysAllTime = sysAllTime;
+ #ifdef CP_SPIN
+ cpu->spinTime = times[CP_SPIN];
+ #endif
+ cpu->intrTime = times[CP_INTR];
+ cpu->idleTime = times[CP_IDLE];
+}
+
+static void OpenBSDMachine_scanCPUTime(OpenBSDMachine* this) {
+ Machine* super = &this->super;
+ u_int64_t kernelTimes[CPUSTATES] = {0};
+ u_int64_t avg[CPUSTATES] = {0};
+
+ for (unsigned int i = 0; i < super->existingCPUs; i++) {
+ CPUData* cpu = &this->cpuData[i + 1];
+
+ if (!cpu->online) {
+ continue;
+ }
+
+ getKernelCPUTimes(i, kernelTimes);
+ kernelCPUTimesToHtop(kernelTimes, cpu);
+
+ avg[CP_USER] += cpu->userTime;
+ avg[CP_NICE] += cpu->niceTime;
+ avg[CP_SYS] += cpu->sysTime;
+ #ifdef CP_SPIN
+ avg[CP_SPIN] += cpu->spinTime;
+ #endif
+ avg[CP_INTR] += cpu->intrTime;
+ avg[CP_IDLE] += cpu->idleTime;
+ }
+
+ for (int i = 0; i < CPUSTATES; i++) {
+ avg[i] /= super->activeCPUs;
+ }
+
+ kernelCPUTimesToHtop(avg, &this->cpuData[0]);
+
+ {
+ const int mib[] = { CTL_HW, HW_CPUSPEED };
+ int cpuSpeed;
+ size_t size = sizeof(cpuSpeed);
+ if (sysctl(mib, 2, &cpuSpeed, &size, NULL, 0) == -1) {
+ this->cpuSpeed = -1;
+ } else {
+ this->cpuSpeed = cpuSpeed;
+ }
+ }
+}
+
+void Machine_scan(Machine* super) {
+ OpenBSDMachine* this = (OpenBSDMachine*) super;
+
+ OpenBSDMachine_updateCPUcount(this);
+ OpenBSDMachine_scanMemoryInfo(this);
+ OpenBSDMachine_scanCPUTime(this);
+}
+
+bool Machine_isCPUonline(const Machine* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ const OpenBSDMachine* this = (const OpenBSDMachine*) super;
+ return this->cpuData[id + 1].online;
+}
diff --git a/openbsd/OpenBSDProcessList.h b/openbsd/OpenBSDMachine.h
index 89fdb099..51d6a750 100644
--- a/openbsd/OpenBSDProcessList.h
+++ b/openbsd/OpenBSDMachine.h
@@ -1,7 +1,7 @@
-#ifndef HEADER_OpenBSDProcessList
-#define HEADER_OpenBSDProcessList
+#ifndef HEADER_OpenBSDMachine
+#define HEADER_OpenBSDMachine
/*
-htop - OpenBSDProcessList.h
+htop - OpenBSDMachine.h
(C) 2014 Hisham H. Muhammad
(C) 2015 Michael McConville
Released under the GNU GPLv2+, see the COPYING file
@@ -12,9 +12,7 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <sys/types.h>
-#include "Hashtable.h"
-#include "ProcessList.h"
-#include "UsersTable.h"
+#include "Machine.h"
typedef struct CPUData_ {
@@ -39,22 +37,17 @@ typedef struct CPUData_ {
bool online;
} CPUData;
-typedef struct OpenBSDProcessList_ {
- ProcessList super;
+typedef struct OpenBSDMachine_ {
+ Machine super;
kvm_t* kd;
CPUData* cpuData;
- int cpuSpeed;
-
-} OpenBSDProcessList;
-
-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);
+ long fscale;
+ int cpuSpeed;
+ int pageSize;
+ int pageSizeKB;
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+} OpenBSDMachine;
#endif
diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c
index c2f2ed4c..7f50b0f8 100644
--- a/openbsd/OpenBSDProcess.c
+++ b/openbsd/OpenBSDProcess.c
@@ -6,6 +6,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 "openbsd/OpenBSDProcess.h"
#include <stdlib.h>
@@ -147,6 +149,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
.flags = 0,
.defaultSortDesc = true,
.autoWidth = true,
+ .autoTitleRightAlign = true,
},
[PERCENT_NORM_CPU] = {
.name = "PERCENT_NORM_CPU",
@@ -204,11 +207,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
};
-Process* OpenBSDProcess_new(const Settings* settings) {
+Process* OpenBSDProcess_new(const Machine* host) {
OpenBSDProcess* this = xCalloc(1, sizeof(OpenBSDProcess));
Object_setClass(this, Class(OpenBSDProcess));
- Process_init(&this->super, settings);
- return &this->super;
+ Process_init(&this->super, host);
+ return (Process*)this;
}
void Process_delete(Object* cast) {
@@ -217,17 +220,20 @@ void Process_delete(Object* cast) {
free(this);
}
-static void OpenBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
- //const OpenBSDProcess* op = (const OpenBSDProcess*) this;
+static void OpenBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const OpenBSDProcess* op = (const OpenBSDProcess*) super;
+
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
- //int n = sizeof(buffer) - 1;
+ //size_t n = sizeof(buffer) - 1;
+
switch (field) {
// add OpenBSD-specific fields here
default:
- Process_writeField(this, str, field);
+ Process_writeField(&op->super, str, field);
return;
}
+
RichString_appendWide(str, attr, buffer);
}
@@ -247,11 +253,18 @@ static int OpenBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro
const ProcessClass OpenBSDProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = OpenBSDProcess_rowWriteField
},
- .writeField = OpenBSDProcess_writeField,
.compareByKey = OpenBSDProcess_compareByKey
};
diff --git a/openbsd/OpenBSDProcess.h b/openbsd/OpenBSDProcess.h
index 898c5377..aac4b318 100644
--- a/openbsd/OpenBSDProcess.h
+++ b/openbsd/OpenBSDProcess.h
@@ -10,9 +10,9 @@ in the source distribution for its full text.
#include <stdbool.h>
+#include "Machine.h"
#include "Object.h"
#include "Process.h"
-#include "Settings.h"
typedef struct OpenBSDProcess_ {
@@ -26,7 +26,7 @@ extern const ProcessClass OpenBSDProcess_class;
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-Process* OpenBSDProcess_new(const Settings* settings);
+Process* OpenBSDProcess_new(const Machine* host);
void Process_delete(Object* cast);
diff --git a/openbsd/OpenBSDProcessList.c b/openbsd/OpenBSDProcessList.c
deleted file mode 100644
index d070e5e9..00000000
--- a/openbsd/OpenBSDProcessList.c
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
-htop - OpenBSDProcessList.c
-(C) 2014 Hisham H. Muhammad
-(C) 2015 Michael McConville
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "openbsd/OpenBSDProcessList.h"
-
-#include <kvm.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/mount.h>
-#include <sys/param.h>
-#include <sys/proc.h>
-#include <sys/sched.h>
-#include <sys/swap.h>
-#include <sys/sysctl.h>
-#include <sys/types.h>
-#include <uvm/uvmexp.h>
-
-#include "CRT.h"
-#include "Macros.h"
-#include "Object.h"
-#include "Process.h"
-#include "ProcessList.h"
-#include "Settings.h"
-#include "XUtils.h"
-#include "openbsd/OpenBSDProcess.h"
-
-
-static long fscale;
-static int pageSize;
-static int pageSizeKB;
-
-static void OpenBSDProcessList_updateCPUcount(ProcessList* super) {
- OpenBSDProcessList* opl = (OpenBSDProcessList*) super;
- const int nmib[] = { CTL_HW, HW_NCPU };
- const int mib[] = { CTL_HW, HW_NCPUONLINE };
- int r;
- unsigned int value;
- size_t size;
- bool change = false;
-
- size = sizeof(value);
- r = sysctl(mib, 2, &value, &size, NULL, 0);
- if (r < 0 || value < 1) {
- value = 1;
- }
-
- if (value != super->activeCPUs) {
- super->activeCPUs = value;
- change = true;
- }
-
- size = sizeof(value);
- r = sysctl(nmib, 2, &value, &size, NULL, 0);
- if (r < 0 || value < 1) {
- value = super->activeCPUs;
- }
-
- if (value != super->existingCPUs) {
- opl->cpuData = xReallocArray(opl->cpuData, value + 1, sizeof(CPUData));
- super->existingCPUs = value;
- change = true;
- }
-
- if (change) {
- CPUData* dAvg = &opl->cpuData[0];
- memset(dAvg, '\0', sizeof(CPUData));
- dAvg->totalTime = 1;
- dAvg->totalPeriod = 1;
- dAvg->online = true;
-
- for (unsigned int i = 0; i < super->existingCPUs; i++) {
- CPUData* d = &opl->cpuData[i + 1];
- memset(d, '\0', sizeof(CPUData));
- d->totalTime = 1;
- d->totalPeriod = 1;
-
- const int ncmib[] = { CTL_KERN, KERN_CPUSTATS, i };
- struct cpustats cpu_stats;
-
- size = sizeof(cpu_stats);
- if (sysctl(ncmib, 3, &cpu_stats, &size, NULL, 0) < 0) {
- CRT_fatalError("ncmib sysctl call failed");
- }
- d->online = (cpu_stats.cs_flags & CPUSTATS_ONLINE);
- }
- }
-}
-
-
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
- const int fmib[] = { CTL_KERN, KERN_FSCALE };
- size_t size;
- char errbuf[_POSIX2_LINE_MAX];
-
- OpenBSDProcessList* opl = xCalloc(1, sizeof(OpenBSDProcessList));
- ProcessList* pl = (ProcessList*) opl;
- ProcessList_init(pl, Class(OpenBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
-
- OpenBSDProcessList_updateCPUcount(pl);
-
- size = sizeof(fscale);
- if (sysctl(fmib, 2, &fscale, &size, NULL, 0) < 0) {
- CRT_fatalError("fscale sysctl call failed");
- }
-
- if ((pageSize = sysconf(_SC_PAGESIZE)) == -1)
- CRT_fatalError("pagesize sysconf call failed");
- pageSizeKB = pageSize / ONE_K;
-
- opl->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
- if (opl->kd == NULL) {
- CRT_fatalError("kvm_openfiles() failed");
- }
-
- opl->cpuSpeed = -1;
-
- return pl;
-}
-
-void ProcessList_delete(ProcessList* this) {
- OpenBSDProcessList* opl = (OpenBSDProcessList*) this;
-
- if (opl->kd) {
- kvm_close(opl->kd);
- }
-
- free(opl->cpuData);
-
- ProcessList_done(this);
- free(this);
-}
-
-static void OpenBSDProcessList_scanMemoryInfo(ProcessList* pl) {
- const int uvmexp_mib[] = { CTL_VM, VM_UVMEXP };
- struct uvmexp uvmexp;
- size_t size_uvmexp = sizeof(uvmexp);
-
- if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {
- CRT_fatalError("uvmexp sysctl call failed");
- }
-
- pl->totalMem = uvmexp.npages * pageSizeKB;
- pl->usedMem = (uvmexp.npages - uvmexp.free - uvmexp.paging) * pageSizeKB;
-
- // Taken from OpenBSD systat/iostat.c, top/machine.c and uvm_sysctl(9)
- const int bcache_mib[] = { CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT };
- struct bcachestats bcstats;
- size_t size_bcstats = sizeof(bcstats);
-
- if (sysctl(bcache_mib, 3, &bcstats, &size_bcstats, NULL, 0) < 0) {
- CRT_fatalError("cannot get vfs.bcachestat");
- }
-
- pl->cachedMem = bcstats.numbufpages * pageSizeKB;
-
- /*
- * Copyright (c) 1994 Thorsten Lockert <tholo@sigmasoft.com>
- * All rights reserved.
- *
- * Taken almost directly from OpenBSD's top(1)
- *
- * Originally released under a BSD-3 license
- * Modified through htop developers applying GPL-2
- */
- int nswap = swapctl(SWAP_NSWAP, 0, 0);
- if (nswap > 0) {
- struct swapent swdev[nswap];
- int rnswap = swapctl(SWAP_STATS, swdev, nswap);
-
- /* Total things up */
- unsigned long long int total = 0, used = 0;
- for (int i = 0; i < rnswap; i++) {
- if (swdev[i].se_flags & SWF_ENABLE) {
- used += (swdev[i].se_inuse / (1024 / DEV_BSIZE));
- total += (swdev[i].se_nblks / (1024 / DEV_BSIZE));
- }
- }
-
- pl->totalSwap = total;
- pl->usedSwap = used;
- } else {
- pl->totalSwap = pl->usedSwap = 0;
- }
-}
-
-static void OpenBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
- const int mib[] = { CTL_KERN, KERN_PROC_CWD, kproc->p_pid };
- char buffer[2048];
- size_t size = sizeof(buffer);
- if (sysctl(mib, 3, 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 OpenBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
- Process_updateComm(proc, kproc->p_comm);
-
- /*
- * Like OpenBSD's top(1), we try to fall back to the command name
- * (argv[0]) if we fail to construct the full command.
- */
- char** arg = kvm_getargv(kd, kproc, 500);
- if (arg == NULL || *arg == NULL) {
- Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
- return;
- }
-
- size_t len = 0;
- for (int i = 0; arg[i] != NULL; i++) {
- len += strlen(arg[i]) + 1; /* room for arg and trailing space or NUL */
- }
-
- /* don't use xMalloc here - we want to handle huge argv's gracefully */
- char* s;
- if ((s = malloc(len)) == NULL) {
- Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
- return;
- }
-
- *s = '\0';
-
- int start = 0;
- int end = 0;
- for (int i = 0; arg[i] != NULL; i++) {
- size_t n = strlcat(s, arg[i], len);
- if (i == 0) {
- end = MINIMUM(n, len - 1);
- /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */
- for (int j = end; j > 0; j--) {
- if (arg[0][j] == ' ' && arg[0][j - 1] != '\\') {
- end = (arg[0][j - 1] == ':') ? (j - 1) : j;
- }
- }
- }
- /* the trailing space should get truncated anyway */
- strlcat(s, " ", len);
- }
-
- Process_updateCmdline(proc, s, start, end);
-
- free(s);
-}
-
-/*
- * Taken from OpenBSD's ps(1).
- */
-static double getpcpu(const struct kinfo_proc* kp) {
- if (fscale == 0)
- return 0.0;
-
- return 100.0 * (double)kp->p_pctcpu / fscale;
-}
-
-static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) {
- const Settings* settings = this->super.settings;
- const bool hideKernelThreads = settings->hideKernelThreads;
- const bool hideUserlandThreads = settings->hideUserlandThreads;
- int count = 0;
-
- const struct kinfo_proc* kprocs = kvm_getprocs(this->kd, KERN_PROC_KTHREAD | KERN_PROC_SHOW_THREADS, 0, sizeof(struct kinfo_proc), &count);
-
- for (int i = 0; i < count; i++) {
- const struct kinfo_proc* kproc = &kprocs[i];
-
- /* Ignore main threads */
- if (kproc->p_tid != -1) {
- Process* containingProcess = ProcessList_findProcess(&this->super, kproc->p_pid);
- if (containingProcess) {
- if (((OpenBSDProcess*)containingProcess)->addr == kproc->p_addr)
- continue;
-
- containingProcess->nlwp++;
- }
- }
-
- bool preExisting = false;
- Process* proc = ProcessList_getProcess(&this->super, (kproc->p_tid == -1) ? kproc->p_pid : kproc->p_tid, &preExisting, OpenBSDProcess_new);
- OpenBSDProcess* fp = (OpenBSDProcess*) proc;
-
- if (!preExisting) {
- proc->ppid = kproc->p_ppid;
- proc->tpgid = kproc->p_tpgid;
- proc->tgid = kproc->p_pid;
- proc->session = kproc->p_sid;
- proc->pgrp = kproc->p__pgid;
- proc->isKernelThread = proc->pgrp == 0;
- proc->isUserlandThread = kproc->p_tid != -1;
- proc->starttime_ctime = kproc->p_ustart_sec;
- Process_fillStarttimeBuffer(proc);
- ProcessList_add(&this->super, proc);
-
- OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
-
- if (settings->ss->flags & PROCESS_FLAG_CWD) {
- OpenBSDProcessList_updateCwd(kproc, proc);
- }
-
- proc->tty_nr = kproc->p_tdev;
- const char* name = ((dev_t)kproc->p_tdev != NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL;
- if (!name || String_eq(name, "??")) {
- free(proc->tty_name);
- proc->tty_name = NULL;
- } else {
- free_and_xStrdup(&proc->tty_name, name);
- }
- } else {
- if (settings->updateProcessNames) {
- OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
- }
- }
-
- fp->addr = kproc->p_addr;
- proc->m_virt = kproc->p_vm_dsize * pageSizeKB;
- proc->m_resident = kproc->p_vm_rssize * pageSizeKB;
-
- proc->percent_mem = proc->m_resident / (float)this->super.totalMem * 100.0F;
- proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0F, this->super.activeCPUs * 100.0F);
- Process_updateCPUFieldWidths(proc->percent_cpu);
-
- proc->nice = kproc->p_nice - 20;
- proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
- proc->priority = kproc->p_priority - PZERO;
- proc->processor = kproc->p_cpuid;
- proc->minflt = kproc->p_uru_minflt;
- proc->majflt = kproc->p_uru_majflt;
- proc->nlwp = 1;
-
- if (proc->st_uid != kproc->p_uid) {
- proc->st_uid = kproc->p_uid;
- proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid);
- }
-
- /* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */
- switch (kproc->p_stat) {
- case SIDL: proc->state = IDLE; break;
- case SRUN: proc->state = RUNNABLE; break;
- case SSLEEP: proc->state = SLEEPING; break;
- case SSTOP: proc->state = STOPPED; break;
- case SZOMB: proc->state = ZOMBIE; break;
- case SDEAD: proc->state = DEFUNCT; break;
- case SONPROC: proc->state = RUNNING; break;
- default: proc->state = UNKNOWN;
- }
-
- if (Process_isKernelThread(proc)) {
- this->super.kernelThreads++;
- } else if (Process_isUserlandThread(proc)) {
- this->super.userlandThreads++;
- }
-
- this->super.totalTasks++;
- if (proc->state == RUNNING) {
- this->super.runningTasks++;
- }
-
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
- proc->updated = true;
- }
-}
-
-static void getKernelCPUTimes(unsigned int cpuId, u_int64_t* times) {
- const int mib[] = { CTL_KERN, KERN_CPTIME2, cpuId };
- size_t length = sizeof(*times) * CPUSTATES;
- if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {
- CRT_fatalError("sysctl kern.cp_time2 failed");
- }
-}
-
-static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
- unsigned long long totalTime = 0;
- for (int i = 0; i < CPUSTATES; i++) {
- totalTime += times[i];
- }
-
- unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
-
- // XXX Not sure if CP_SPIN should be added to sysAllTime.
- // See https://github.com/openbsd/src/commit/531d8034253fb82282f0f353c086e9ad827e031c
- #ifdef CP_SPIN
- sysAllTime += times[CP_SPIN];
- #endif
-
- cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);
- cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);
- cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);
- cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);
- cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);
- #ifdef CP_SPIN
- cpu->spinPeriod = saturatingSub(times[CP_SPIN], cpu->spinTime);
- #endif
- cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);
- cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);
-
- cpu->totalTime = totalTime;
- cpu->userTime = times[CP_USER];
- cpu->niceTime = times[CP_NICE];
- cpu->sysTime = times[CP_SYS];
- cpu->sysAllTime = sysAllTime;
- #ifdef CP_SPIN
- cpu->spinTime = times[CP_SPIN];
- #endif
- cpu->intrTime = times[CP_INTR];
- cpu->idleTime = times[CP_IDLE];
-}
-
-static void OpenBSDProcessList_scanCPUTime(OpenBSDProcessList* this) {
- u_int64_t kernelTimes[CPUSTATES] = {0};
- u_int64_t avg[CPUSTATES] = {0};
-
- for (unsigned int i = 0; i < this->super.existingCPUs; i++) {
- CPUData* cpu = &this->cpuData[i + 1];
-
- if (!cpu->online) {
- continue;
- }
-
- getKernelCPUTimes(i, kernelTimes);
- kernelCPUTimesToHtop(kernelTimes, cpu);
-
- avg[CP_USER] += cpu->userTime;
- avg[CP_NICE] += cpu->niceTime;
- avg[CP_SYS] += cpu->sysTime;
- #ifdef CP_SPIN
- avg[CP_SPIN] += cpu->spinTime;
- #endif
- avg[CP_INTR] += cpu->intrTime;
- avg[CP_IDLE] += cpu->idleTime;
- }
-
- for (int i = 0; i < CPUSTATES; i++) {
- avg[i] /= this->super.activeCPUs;
- }
-
- kernelCPUTimesToHtop(avg, &this->cpuData[0]);
-
- {
- const int mib[] = { CTL_HW, HW_CPUSPEED };
- int cpuSpeed;
- size_t size = sizeof(cpuSpeed);
- if (sysctl(mib, 2, &cpuSpeed, &size, NULL, 0) == -1) {
- this->cpuSpeed = -1;
- } else {
- this->cpuSpeed = cpuSpeed;
- }
- }
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
- OpenBSDProcessList* opl = (OpenBSDProcessList*) super;
-
- OpenBSDProcessList_updateCPUcount(super);
- OpenBSDProcessList_scanMemoryInfo(super);
- OpenBSDProcessList_scanCPUTime(opl);
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
- OpenBSDProcessList_scanProcs(opl);
-}
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
- assert(id < super->existingCPUs);
-
- const OpenBSDProcessList* opl = (const OpenBSDProcessList*) super;
- return opl->cpuData[id + 1].online;
-}
diff --git a/openbsd/OpenBSDProcessTable.c b/openbsd/OpenBSDProcessTable.c
new file mode 100644
index 00000000..ded31e59
--- /dev/null
+++ b/openbsd/OpenBSDProcessTable.c
@@ -0,0 +1,245 @@
+/*
+htop - OpenBSDProcessTable.c
+(C) 2014 Hisham H. Muhammad
+(C) 2015 Michael McConville
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "openbsd/OpenBSDProcessTable.h"
+
+#include <kvm.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <sys/sched.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <uvm/uvmexp.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Process.h"
+#include "ProcessTable.h"
+#include "Settings.h"
+#include "XUtils.h"
+#include "openbsd/OpenBSDMachine.h"
+#include "openbsd/OpenBSDProcess.h"
+
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ OpenBSDProcessTable* this = xCalloc(1, sizeof(OpenBSDProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(OpenBSDProcess), host, pidMatchList);
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ OpenBSDProcessTable* this = (OpenBSDProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ free(this);
+}
+
+static void OpenBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
+ const int mib[] = { CTL_KERN, KERN_PROC_CWD, kproc->p_pid };
+ char buffer[2048];
+ size_t size = sizeof(buffer);
+ if (sysctl(mib, 3, 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 OpenBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
+ Process_updateComm(proc, kproc->p_comm);
+
+ /*
+ * Like OpenBSD's top(1), we try to fall back to the command name
+ * (argv[0]) if we fail to construct the full command.
+ */
+ char** arg = kvm_getargv(kd, kproc, 500);
+ if (arg == NULL || *arg == NULL) {
+ Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
+ return;
+ }
+
+ size_t len = 0;
+ for (int i = 0; arg[i] != NULL; i++) {
+ len += strlen(arg[i]) + 1; /* room for arg and trailing space or NUL */
+ }
+
+ /* don't use xMalloc here - we want to handle huge argv's gracefully */
+ char* s;
+ if ((s = malloc(len)) == NULL) {
+ Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
+ return;
+ }
+
+ *s = '\0';
+
+ int start = 0;
+ int end = 0;
+ for (int i = 0; arg[i] != NULL; i++) {
+ size_t n = strlcat(s, arg[i], len);
+ if (i == 0) {
+ end = MINIMUM(n, len - 1);
+ /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */
+ for (int j = end; j > 0; j--) {
+ if (arg[0][j] == ' ' && arg[0][j - 1] != '\\') {
+ end = (arg[0][j - 1] == ':') ? (j - 1) : j;
+ }
+ }
+ }
+ /* the trailing space should get truncated anyway */
+ strlcat(s, " ", len);
+ }
+
+ Process_updateCmdline(proc, s, start, end);
+
+ free(s);
+}
+
+/*
+ * Taken from OpenBSD's ps(1).
+ */
+static double getpcpu(const OpenBSDMachine* ohost, const struct kinfo_proc* kp) {
+ if (ohost->fscale == 0)
+ return 0.0;
+
+ return 100.0 * (double)kp->p_pctcpu / ohost->fscale;
+}
+
+static void OpenBSDProcessTable_scanProcs(OpenBSDProcessTable* this) {
+ Machine* host = this->super.super.host;
+ OpenBSDMachine* ohost = (OpenBSDMachine*) host;
+ const Settings* settings = host->settings;
+ const bool hideKernelThreads = settings->hideKernelThreads;
+ const bool hideUserlandThreads = settings->hideUserlandThreads;
+ int count = 0;
+
+ const struct kinfo_proc* kprocs = kvm_getprocs(ohost->kd, KERN_PROC_KTHREAD | KERN_PROC_SHOW_THREADS, 0, sizeof(struct kinfo_proc), &count);
+
+ for (int i = 0; i < count; i++) {
+ const struct kinfo_proc* kproc = &kprocs[i];
+
+ /* Ignore main threads */
+ if (kproc->p_tid != -1) {
+ Process* containingProcess = ProcessTable_findProcess(&this->super, kproc->p_pid);
+ if (containingProcess) {
+ if (((OpenBSDProcess*)containingProcess)->addr == kproc->p_addr)
+ continue;
+
+ containingProcess->nlwp++;
+ }
+ }
+
+ bool preExisting = false;
+ Process* proc = ProcessTable_getProcess(&this->super, (kproc->p_tid == -1) ? kproc->p_pid : kproc->p_tid, &preExisting, OpenBSDProcess_new);
+ OpenBSDProcess* op = (OpenBSDProcess*) proc;
+
+ if (!preExisting) {
+ Process_setParent(proc, kproc->p_ppid);
+ Process_setThreadGroup(proc, kproc->p_pid);
+ proc->tpgid = kproc->p_tpgid;
+ proc->session = kproc->p_sid;
+ proc->pgrp = kproc->p__pgid;
+ proc->isKernelThread = proc->pgrp == 0;
+ proc->isUserlandThread = kproc->p_tid != -1;
+ proc->starttime_ctime = kproc->p_ustart_sec;
+ Process_fillStarttimeBuffer(proc);
+ ProcessTable_add(&this->super, proc);
+
+ OpenBSDProcessTable_updateProcessName(ohost->kd, kproc, proc);
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ OpenBSDProcessTable_updateCwd(kproc, proc);
+ }
+
+ proc->tty_nr = kproc->p_tdev;
+ const char* name = ((dev_t)kproc->p_tdev != NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL;
+ if (!name || String_eq(name, "??")) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
+ } else {
+ if (settings->updateProcessNames) {
+ OpenBSDProcessTable_updateProcessName(ohost->kd, kproc, proc);
+ }
+ }
+
+ op->addr = kproc->p_addr;
+ proc->m_virt = kproc->p_vm_dsize * ohost->pageSizeKB;
+ proc->m_resident = kproc->p_vm_rssize * ohost->pageSizeKB;
+
+ proc->percent_mem = proc->m_resident / (float)host->totalMem * 100.0F;
+ proc->percent_cpu = CLAMP(getpcpu(ohost, kproc), 0.0F, host->activeCPUs * 100.0F);
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ proc->nice = kproc->p_nice - 20;
+ proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
+ proc->priority = kproc->p_priority - PZERO;
+ proc->processor = kproc->p_cpuid;
+ proc->minflt = kproc->p_uru_minflt;
+ proc->majflt = kproc->p_uru_majflt;
+ proc->nlwp = 1;
+
+ if (proc->st_uid != kproc->p_uid) {
+ proc->st_uid = kproc->p_uid;
+ proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);
+ }
+
+ /* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */
+ switch (kproc->p_stat) {
+ case SIDL: proc->state = IDLE; break;
+ case SRUN: proc->state = RUNNABLE; break;
+ case SSLEEP: proc->state = SLEEPING; break;
+ case SSTOP: proc->state = STOPPED; break;
+ case SZOMB: proc->state = ZOMBIE; break;
+ case SDEAD: proc->state = DEFUNCT; break;
+ case SONPROC: proc->state = RUNNING; break;
+ default: proc->state = UNKNOWN;
+ }
+
+ if (Process_isKernelThread(proc)) {
+ this->super.kernelThreads++;
+ } else if (Process_isUserlandThread(proc)) {
+ this->super.userlandThreads++;
+ }
+
+ this->super.totalTasks++;
+ if (proc->state == RUNNING) {
+ this->super.runningTasks++;
+ }
+
+ proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+ proc->super.updated = true;
+ }
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ OpenBSDProcessTable* this = (OpenBSDProcessTable*) super;
+
+ OpenBSDProcessTable_scanProcs(this);
+}
diff --git a/openbsd/OpenBSDProcessTable.h b/openbsd/OpenBSDProcessTable.h
new file mode 100644
index 00000000..f911a10b
--- /dev/null
+++ b/openbsd/OpenBSDProcessTable.h
@@ -0,0 +1,21 @@
+#ifndef HEADER_OpenBSDProcessTable
+#define HEADER_OpenBSDProcessTable
+/*
+htop - OpenBSDProcessTable.h
+(C) 2014 Hisham H. Muhammad
+(C) 2015 Michael McConville
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "ProcessTable.h"
+
+
+typedef struct OpenBSDProcessTable_ {
+ ProcessTable super;
+} OpenBSDProcessTable;
+
+#endif
diff --git a/openbsd/Platform.c b/openbsd/Platform.c
index b222bee0..a8b5d212 100644
--- a/openbsd/Platform.c
+++ b/openbsd/Platform.c
@@ -6,6 +6,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 "openbsd/Platform.h"
#include <errno.h>
@@ -28,13 +30,13 @@ in the source distribution for its full text.
#include "ClockMeter.h"
#include "DateMeter.h"
#include "DateTimeMeter.h"
+#include "FileDescriptorMeter.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
#include "MemoryMeter.h"
#include "MemorySwapMeter.h"
#include "Meter.h"
-#include "ProcessList.h"
#include "Settings.h"
#include "SignalsPanel.h"
#include "SwapMeter.h"
@@ -42,8 +44,8 @@ in the source distribution for its full text.
#include "TasksMeter.h"
#include "UptimeMeter.h"
#include "XUtils.h"
+#include "openbsd/OpenBSDMachine.h"
#include "openbsd/OpenBSDProcess.h"
-#include "openbsd/OpenBSDProcessList.h"
const ScreenDefaults Platform_defaultScreens[] = {
@@ -125,6 +127,7 @@ const MeterClass* const Platform_meterTypes[] = {
&RightCPUs4Meter_class,
&LeftCPUs8Meter_class,
&RightCPUs8Meter_class,
+ &FileDescriptorMeter_class,
&BlankMeter_class,
NULL
};
@@ -143,7 +146,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
struct timeval bootTime, currTime;
const int mib[2] = { CTL_KERN, KERN_BOOTTIME };
size_t size = sizeof(bootTime);
@@ -174,13 +177,14 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;
}
-int Platform_getMaxPid() {
+pid_t Platform_getMaxPid(void) {
return 2 * THREAD_PID_OFFSET;
}
double Platform_setCPUValues(Meter* this, unsigned int cpu) {
- const OpenBSDProcessList* pl = (const OpenBSDProcessList*) this->pl;
- const CPUData* cpuData = &(pl->cpuData[cpu]);
+ const Machine* host = this->host;
+ const OpenBSDMachine* ohost = (const OpenBSDMachine*) host;
+ const CPUData* cpuData = &ohost->cpuData[cpu];
double total;
double totalPercent;
double* v = this->values;
@@ -194,7 +198,7 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;
v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;
- if (this->pl->settings->detailedCPUTime) {
+ if (host->settings->detailedCPUTime) {
v[CPU_METER_KERNEL] = cpuData->sysPeriod / total * 100.0;
v[CPU_METER_IRQ] = cpuData->intrPeriod / total * 100.0;
v[CPU_METER_SOFTIRQ] = 0.0;
@@ -203,42 +207,43 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
v[CPU_METER_IOWAIT] = 0.0;
v[CPU_METER_FREQUENCY] = NAN;
this->curItems = 8;
- totalPercent = v[0] + v[1] + v[2] + v[3];
} else {
- v[2] = cpuData->sysAllPeriod / total * 100.0;
- v[3] = 0.0; // No steal nor guest on OpenBSD
- totalPercent = v[0] + v[1] + v[2];
+ v[CPU_METER_KERNEL] = cpuData->sysAllPeriod / total * 100.0;
+ v[CPU_METER_IRQ] = 0.0; // No steal nor guest on OpenBSD
this->curItems = 4;
}
+ totalPercent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ];
totalPercent = CLAMP(totalPercent, 0.0, 100.0);
v[CPU_METER_TEMPERATURE] = NAN;
- v[CPU_METER_FREQUENCY] = (pl->cpuSpeed != -1) ? pl->cpuSpeed : NAN;
+ v[CPU_METER_FREQUENCY] = (ohost->cpuSpeed != -1) ? ohost->cpuSpeed : NAN;
return totalPercent;
}
void Platform_setMemoryValues(Meter* this) {
- const ProcessList* pl = this->pl;
- long int usedMem = pl->usedMem;
- long int buffersMem = pl->buffersMem;
- long int cachedMem = pl->cachedMem;
+ const Machine* host = this->host;
+ long int usedMem = host->usedMem;
+ long int buffersMem = host->buffersMem;
+ long int cachedMem = host->cachedMem;
usedMem -= buffersMem + cachedMem;
- this->total = pl->totalMem;
- this->values[0] = usedMem;
- this->values[1] = buffersMem;
- // this->values[2] = "shared memory, like tmpfs and shm"
- this->values[3] = cachedMem;
- // this->values[4] = "available memory"
+ this->total = host->totalMem;
+ this->values[MEMORY_METER_USED] = usedMem;
+ // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux"
+ this->values[MEMORY_METER_BUFFERS] = buffersMem;
+ this->values[MEMORY_METER_CACHE] = cachedMem;
+ // this->values[MEMORY_METER_AVAILABLE] = "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;
+ const Machine* host = this->host;
+ this->total = host->totalSwap;
+ this->values[SWAP_METER_USED] = host->usedSwap;
+ // this->values[SWAP_METER_CACHE] = "pages that are both in swap and RAM, like SwapCached on linux"
+ // this->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux"
}
char* Platform_getProcessEnv(pid_t pid) {
@@ -296,15 +301,33 @@ end:
return env;
}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
(void)pid;
- (void)inode;
return NULL;
}
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+void Platform_getFileDescriptors(double* used, double* max) {
+ static const int mib_kern_maxfile[] = { CTL_KERN, KERN_MAXFILES };
+ int sysctl_maxfile = 0;
+ size_t size_maxfile = sizeof(int);
+ if (sysctl(mib_kern_maxfile, ARRAYSIZE(mib_kern_maxfile), &sysctl_maxfile, &size_maxfile, NULL, 0) < 0) {
+ *max = NAN;
+ } else if (size_maxfile != sizeof(int) || sysctl_maxfile < 1) {
+ *max = NAN;
+ } else {
+ *max = sysctl_maxfile;
+ }
+
+ static const int mib_kern_nfiles[] = { CTL_KERN, KERN_NFILES };
+ int sysctl_nfiles = 0;
+ size_t size_nfiles = sizeof(int);
+ if (sysctl(mib_kern_nfiles, ARRAYSIZE(mib_kern_nfiles), &sysctl_nfiles, &size_nfiles, NULL, 0) < 0) {
+ *used = NAN;
+ } else if (size_nfiles != sizeof(int) || sysctl_nfiles < 0) {
+ *used = NAN;
+ } else {
+ *used = sysctl_nfiles;
+ }
}
bool Platform_getDiskIO(DiskIOData* data) {
diff --git a/openbsd/Platform.h b/openbsd/Platform.h
index e3d61163..339616c1 100644
--- a/openbsd/Platform.h
+++ b/openbsd/Platform.h
@@ -47,7 +47,7 @@ int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, unsigned int cpu);
@@ -57,10 +57,10 @@ void Platform_setSwapValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+void Platform_getFileDescriptors(double* used, double* max);
+
bool Platform_getDiskIO(DiskIOData* data);
bool Platform_getNetworkIO(NetworkIOData* data);
@@ -91,7 +91,9 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) {
Generic_gettime_monotonic(msec);
}
-static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
@@ -101,12 +103,30 @@ 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 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 const char* Platform_dynamicColumnName(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;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
-static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
#endif
diff --git a/pcp-htop.5.in b/pcp-htop.5.in
index 0f5cb14f..ebb47487 100644
--- a/pcp-htop.5.in
+++ b/pcp-htop.5.in
@@ -1,4 +1,4 @@
-.TH "PCP-HTOP" "5" "2022" "@PACKAGE_STRING@" "File Formats"
+.TH "PCP-HTOP" "5" "2024" "@PACKAGE_STRING@" "File Formats"
.SH "NAME"
\f3pcp-htop\f1 \- pcp-htop configuration file
.SH "DESCRIPTION"
diff --git a/pcp-htop.c b/pcp-htop.c
index 2713c896..ed585ecb 100644
--- a/pcp-htop.c
+++ b/pcp-htop.c
@@ -14,13 +14,14 @@ in the source distribution for its full text.
#include "Platform.h"
+const char* program = "pcp-htop";
+
int main(int argc, char** argv) {
- const char* name = "pcp-htop";
- pmSetProgname(name);
+ pmSetProgname(program);
/* extract environment variables */
opts.flags |= PM_OPTFLAG_ENV_ONLY;
(void)pmGetOptions(argc, argv, &opts);
- return CommandLine_run(name, argc, argv);
+ return CommandLine_run(argc, argv);
}
diff --git a/pcp/InDomTable.c b/pcp/InDomTable.c
new file mode 100644
index 00000000..2f9a5008
--- /dev/null
+++ b/pcp/InDomTable.c
@@ -0,0 +1,99 @@
+/*
+htop - InDomTable.c
+(C) 2023 htop dev team
+(C) 2022-2023 Sohaib Mohammed
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "pcp/InDomTable.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "CRT.h"
+#include "DynamicColumn.h"
+#include "Hashtable.h"
+#include "Macros.h"
+#include "Platform.h"
+#include "Table.h"
+#include "Vector.h"
+#include "XUtils.h"
+
+#include "pcp/Instance.h"
+#include "pcp/Metric.h"
+#include "pcp/PCPDynamicColumn.h"
+
+
+InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey) {
+ InDomTable* this = xCalloc(1, sizeof(InDomTable));
+ Object_setClass(this, Class(InDomTable));
+ this->metricKey = metricKey;
+ this->id = indom;
+
+ Table* super = &this->super;
+ Table_init(super, Class(Row), host);
+
+ return this;
+}
+
+void InDomTable_done(InDomTable* this) {
+ Table_done(&this->super);
+}
+
+static void InDomTable_delete(Object* cast) {
+ InDomTable* this = (InDomTable*) cast;
+ InDomTable_done(this);
+ free(this);
+}
+
+static Instance* InDomTable_getInstance(InDomTable* this, int id, bool* preExisting) {
+ const Table* super = &this->super;
+ Instance* inst = (Instance*) Hashtable_get(super->table, id);
+ *preExisting = inst != NULL;
+ if (inst) {
+ assert(Vector_indexOf(super->rows, inst, Row_idEqualCompare) != -1);
+ assert(Instance_getId(inst) == id);
+ } else {
+ inst = Instance_new(super->host, this);
+ assert(inst->name == NULL);
+ Instance_setId(inst, id);
+ }
+ return inst;
+}
+
+static void InDomTable_goThroughEntries(InDomTable* this) {
+ Table* super = &this->super;
+
+ /* for every instance ... */
+ int instid = -1, offset = -1;
+ while (Metric_iterate(this->metricKey, &instid, &offset)) {
+ bool preExisting;
+ Instance* inst = InDomTable_getInstance(this, instid, &preExisting);
+ inst->offset = offset >= 0 ? offset : 0;
+
+ Row* row = (Row*) inst;
+ if (!preExisting)
+ Table_add(super, row);
+ row->updated = true;
+ row->show = true;
+ }
+}
+
+static void InDomTable_iterateEntries(Table* super) {
+ InDomTable* this = (InDomTable*) super;
+ InDomTable_goThroughEntries(this);
+}
+
+const TableClass InDomTable_class = {
+ .super = {
+ .extends = Class(Table),
+ .delete = InDomTable_delete,
+ },
+ .prepare = Table_prepareEntries,
+ .iterate = InDomTable_iterateEntries,
+ .cleanup = Table_cleanupEntries,
+};
diff --git a/pcp/InDomTable.h b/pcp/InDomTable.h
new file mode 100644
index 00000000..f44a39f6
--- /dev/null
+++ b/pcp/InDomTable.h
@@ -0,0 +1,34 @@
+#ifndef HEADER_InDomTable
+#define HEADER_InDomTable
+/*
+htop - InDomTable.h
+(C) 2023 htop dev team
+(C) 2022-2023 Sohaib Mohammed
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "Platform.h"
+#include "Table.h"
+
+
+typedef struct InDomTable_ {
+ Table super;
+ pmInDom id; /* shared by metrics in the table */
+ unsigned int metricKey; /* representative metric using this indom */
+} InDomTable;
+
+extern const TableClass InDomTable_class;
+
+InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey);
+
+void InDomTable_done(InDomTable* this);
+
+RowField RowField_keyAt(const Settings* settings, int at);
+
+void InDomTable_scan(Table* super);
+
+#endif
diff --git a/pcp/Instance.c b/pcp/Instance.c
new file mode 100644
index 00000000..8ae90512
--- /dev/null
+++ b/pcp/Instance.c
@@ -0,0 +1,163 @@
+/*
+htop - Instance.c
+(C) 2022-2023 Sohaib Mohammed
+(C) 2022-2023 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 "pcp/Instance.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "CRT.h"
+#include "DynamicColumn.h"
+#include "DynamicScreen.h"
+#include "Hashtable.h"
+#include "Machine.h"
+#include "Macros.h"
+#include "Metric.h"
+#include "Platform.h"
+#include "PCPDynamicColumn.h"
+#include "PCPDynamicScreen.h"
+#include "Row.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+#include "pcp/InDomTable.h"
+#include "pcp/Metric.h"
+
+
+Instance* Instance_new(const Machine* host, const InDomTable* indom) {
+ Instance* this = xCalloc(1, sizeof(Instance));
+ Object_setClass(this, Class(Instance));
+
+ Row* super = &this->super;
+ Row_init(super, host);
+
+ this->indom = indom;
+
+ return this;
+}
+
+void Instance_done(Instance* this) {
+ if (this->name)
+ free(this->name);
+ Row_done(&this->super);
+}
+
+static void Instance_delete(Object* cast) {
+ Instance* this = (Instance*) cast;
+ Instance_done(this);
+ free(this);
+}
+
+static void Instance_writeField(const Row* super, RichString* str, RowField field) {
+ const Instance* this = (const Instance*) super;
+ int instid = Instance_getId(this);
+
+ const Settings* settings = super->host->settings;
+ DynamicColumn* column = Hashtable_get(settings->dynamicColumns, field);
+ PCPDynamicColumn* cp = (PCPDynamicColumn*) column;
+ if (!cp)
+ return;
+
+ pmAtomValue atom;
+ pmAtomValue* ap = &atom;
+ const pmDesc* descp = Metric_desc(cp->id);
+ if (!Metric_instance(cp->id, instid, this->offset, ap, descp->type))
+ ap = NULL;
+
+ PCPDynamicColumn_writeAtomValue(cp, str, settings, cp->id, instid, descp, ap);
+
+ if (ap && descp->type == PM_TYPE_STRING)
+ free(ap->cp);
+}
+
+static const char* Instance_externalName(Row* super) {
+ Instance* this = (Instance*) super;
+
+ if (!this->name)
+ /* ignore any failure here - its safe and we try again next time */
+ (void)pmNameInDom(InDom_getId(this), Instance_getId(this), &this->name);
+ return this->name;
+}
+
+static int Instance_compareByKey(const Row* v1, const Row* v2, int key) {
+ const Instance* i1 = (const Instance*)v1;
+ const Instance* i2 = (const Instance*)v2;
+
+ if (key < 0)
+ return 0;
+
+ Hashtable* dc = Platform_dynamicColumns();
+ const PCPDynamicColumn* column = Hashtable_get(dc, key);
+ if (!column)
+ return -1;
+
+ size_t metric = column->id;
+ unsigned int type = Metric_type(metric);
+
+ pmAtomValue atom1 = {0}, atom2 = {0};
+ if (!Metric_instance(metric, i1->offset, i1->offset, &atom1, type) ||
+ !Metric_instance(metric, i2->offset, i2->offset, &atom2, type)) {
+ if (type == PM_TYPE_STRING) {
+ free(atom1.cp);
+ free(atom2.cp);
+ }
+ return -1;
+ }
+
+ switch (type) {
+ case PM_TYPE_STRING: {
+ int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp);
+ free(atom2.cp);
+ free(atom1.cp);
+ return cmp;
+ }
+ case PM_TYPE_32:
+ return SPACESHIP_NUMBER(atom2.l, atom1.l);
+ case PM_TYPE_U32:
+ return SPACESHIP_NUMBER(atom2.ul, atom1.ul);
+ case PM_TYPE_64:
+ return SPACESHIP_NUMBER(atom2.ll, atom1.ll);
+ case PM_TYPE_U64:
+ return SPACESHIP_NUMBER(atom2.ull, atom1.ull);
+ case PM_TYPE_FLOAT:
+ return SPACESHIP_NUMBER(atom2.f, atom1.f);
+ case PM_TYPE_DOUBLE:
+ return SPACESHIP_NUMBER(atom2.d, atom1.d);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int Instance_compare(const void* v1, const void* v2) {
+ const Instance* i1 = (const Instance*)v1;
+ const Instance* i2 = (const Instance*)v2;
+ const ScreenSettings* ss = i1->super.host->settings->ss;
+ RowField key = ScreenSettings_getActiveSortKey(ss);
+ int result = Instance_compareByKey(v1, v2, key);
+
+ // Implement tie-breaker (needed to make tree mode more stable)
+ if (!result)
+ return SPACESHIP_NUMBER(Instance_getId(i1), Instance_getId(i2));
+
+ return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;
+}
+
+const RowClass Instance_class = {
+ .super = {
+ .extends = Class(Row),
+ .display = Row_display,
+ .delete = Instance_delete,
+ .compare = Instance_compare,
+ },
+ .sortKeyString = Instance_externalName,
+ .writeField = Instance_writeField,
+};
diff --git a/pcp/Instance.h b/pcp/Instance.h
new file mode 100644
index 00000000..aefd6426
--- /dev/null
+++ b/pcp/Instance.h
@@ -0,0 +1,37 @@
+#ifndef HEADER_Instance
+#define HEADER_Instance
+/*
+htop - Instance.h
+(C) 2022-2023 htop dev team
+(C) 2022-2023 Sohaib Mohammed
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Hashtable.h"
+#include "Object.h"
+#include "Platform.h"
+#include "Row.h"
+
+
+typedef struct Instance_ {
+ Row super;
+
+ char* name; /* external instance name */
+ const struct InDomTable_* indom; /* instance domain */
+
+ /* default result offset to use for searching metrics with instances */
+ unsigned int offset;
+} Instance;
+
+#define InDom_getId(i_) ((i_)->indom->id)
+#define Instance_getId(i_) ((i_)->super.id)
+#define Instance_setId(i_, id_) ((i_)->super.id = (id_))
+
+extern const RowClass Instance_class;
+
+Instance* Instance_new(const Machine* host, const struct InDomTable_* indom);
+
+void Instance_done(Instance* this);
+
+#endif
diff --git a/pcp/PCPMetric.c b/pcp/Metric.c
index 606a5df0..4a3c858d 100644
--- a/pcp/PCPMetric.c
+++ b/pcp/Metric.c
@@ -1,5 +1,5 @@
/*
-htop - PCPMetric.c
+htop - Metric.c
(C) 2020-2021 htop dev team
(C) 2020-2021 Red Hat, Inc.
Released under the GNU GPLv2+, see the COPYING file
@@ -8,8 +8,9 @@ in the source distribution for its full text.
#include "config.h" // IWYU pragma: keep
-#include "pcp/PCPMetric.h"
+#include "pcp/Metric.h"
+#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
@@ -21,15 +22,15 @@ in the source distribution for its full text.
extern Platform* pcp;
-const pmDesc* PCPMetric_desc(PCPMetric metric) {
+const pmDesc* Metric_desc(Metric metric) {
return &pcp->descs[metric];
}
-int PCPMetric_type(PCPMetric metric) {
+int Metric_type(Metric metric) {
return pcp->descs[metric].type;
}
-pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type) {
+pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type) {
if (pcp->result == NULL)
return NULL;
@@ -54,14 +55,14 @@ pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, in
return atom;
}
-int PCPMetric_instanceCount(PCPMetric metric) {
+int Metric_instanceCount(Metric metric) {
pmValueSet* vset = pcp->result->vset[metric];
if (vset)
return vset->numval;
return 0;
}
-int PCPMetric_instanceOffset(PCPMetric metric, int inst) {
+int Metric_instanceOffset(Metric metric, int inst) {
pmValueSet* vset = pcp->result->vset[metric];
if (!vset || vset->numval <= 0)
return 0;
@@ -74,7 +75,7 @@ int PCPMetric_instanceOffset(PCPMetric metric, int inst) {
return 0;
}
-static pmAtomValue* PCPMetric_extract(PCPMetric metric, int inst, int offset, pmValueSet* vset, pmAtomValue* atom, int type) {
+static pmAtomValue* Metric_extract(Metric metric, int inst, int offset, pmValueSet* vset, pmAtomValue* atom, int type) {
/* extract value (using requested type) of given metric instance */
const pmDesc* desc = &pcp->descs[metric];
@@ -89,7 +90,7 @@ static pmAtomValue* PCPMetric_extract(PCPMetric metric, int inst, int offset, pm
return atom;
}
-pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type) {
+pmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type) {
pmValueSet* vset = pcp->result->vset[metric];
if (!vset || vset->numval <= 0)
@@ -97,12 +98,12 @@ pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomVa
/* fast-path using heuristic offset based on expected location */
if (offset >= 0 && offset < vset->numval && inst == vset->vlist[offset].inst)
- return PCPMetric_extract(metric, inst, offset, vset, atom, type);
+ return Metric_extract(metric, inst, offset, vset, atom, type);
/* slow-path using a linear search for the requested instance */
for (int i = 0; i < vset->numval; i++) {
if (inst == vset->vlist[i].inst)
- return PCPMetric_extract(metric, inst, i, vset, atom, type);
+ return Metric_extract(metric, inst, i, vset, atom, type);
}
return NULL;
}
@@ -113,7 +114,7 @@ pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomVa
*
* Start it off by passing offset -1 into the routine.
*/
-bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp) {
+bool Metric_iterate(Metric metric, int* instp, int* offsetp) {
if (!pcp->result)
return false;
@@ -132,15 +133,15 @@ bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp) {
}
/* Switch on/off a metric for value fetching (sampling) */
-void PCPMetric_enable(PCPMetric metric, bool enable) {
+void Metric_enable(Metric metric, bool enable) {
pcp->fetch[metric] = enable ? pcp->pmids[metric] : PM_ID_NULL;
}
-bool PCPMetric_enabled(PCPMetric metric) {
+bool Metric_enabled(Metric metric) {
return pcp->fetch[metric] != PM_ID_NULL;
}
-void PCPMetric_enableThreads(void) {
+void Metric_enableThreads(void) {
pmValueSet* vset = xCalloc(1, sizeof(pmValueSet));
vset->vlist[0].inst = PM_IN_NULL;
vset->vlist[0].value.lval = 1;
@@ -159,7 +160,7 @@ void PCPMetric_enableThreads(void) {
pmFreeResult(result);
}
-bool PCPMetric_fetch(struct timeval* timestamp) {
+bool Metric_fetch(struct timeval* timestamp) {
if (pcp->result) {
pmFreeResult(pcp->result);
pcp->result = NULL;
@@ -178,3 +179,22 @@ bool PCPMetric_fetch(struct timeval* timestamp) {
*timestamp = pcp->result->timestamp;
return true;
}
+
+void Metric_externalName(Metric metric, int inst, char** externalName) {
+ const pmDesc* desc = &pcp->descs[metric];
+ /* ignore a failure here - its safe to do so */
+ (void)pmNameInDom(desc->indom, inst, externalName);
+}
+
+int Metric_lookupText(const char* metric, char** desc) {
+ pmID pmid;
+ int sts;
+
+ sts = pmLookupName(1, &metric, &pmid);
+ if (sts < 0)
+ return sts;
+
+ if (pmLookupText(pmid, PM_TEXT_ONELINE, desc) >= 0)
+ (*desc)[0] = toupper((*desc)[0]); /* UI consistency */
+ return 0;
+}
diff --git a/pcp/PCPMetric.h b/pcp/Metric.h
index 84ccbb95..e72f6e19 100644
--- a/pcp/PCPMetric.h
+++ b/pcp/Metric.h
@@ -1,7 +1,7 @@
-#ifndef HEADER_PCPMetric
-#define HEADER_PCPMetric
+#ifndef HEADER_Metric
+#define HEADER_Metric
/*
-htop - PCPMetric.h
+htop - Metric.h
(C) 2020-2021 htop dev team
(C) 2020-2021 Red Hat, Inc.
Released under the GNU GPLv2+, see the COPYING file
@@ -22,7 +22,7 @@ in the source distribution for its full text.
#undef PACKAGE_BUGREPORT
-typedef enum PCPMetric_ {
+typedef enum Metric_ {
PCP_CONTROL_THREADS, /* proc.control.perclient.threads */
PCP_HINV_NCPU, /* hinv.ncpu */
@@ -75,6 +75,7 @@ typedef enum PCPMetric_ {
PCP_PSI_CPUSOME, /* kernel.all.pressure.cpu.some.avg */
PCP_PSI_IOSOME, /* kernel.all.pressure.io.some.avg */
PCP_PSI_IOFULL, /* kernel.all.pressure.io.full.avg */
+ PCP_PSI_IRQFULL, /* kernel.all.pressure.irq.full.avg */
PCP_PSI_MEMSOME, /* kernel.all.pressure.memory.some.avg */
PCP_PSI_MEMFULL, /* kernel.all.pressure.memory.full.avg */
PCP_ZFS_ARC_ANON_SIZE, /* zfs.arc.anon_size */
@@ -92,6 +93,10 @@ typedef enum PCPMetric_ {
PCP_ZRAM_CAPACITY, /* zram.capacity */
PCP_ZRAM_ORIGINAL, /* zram.mm_stat.data_size.original */
PCP_ZRAM_COMPRESSED, /* zram.mm_stat.data_size.compressed */
+ PCP_MEM_ZSWAP, /* mem.util.zswap */
+ PCP_MEM_ZSWAPPED, /* mem.util.zswapped */
+ PCP_VFS_FILES_COUNT, /* vfs.files.count */
+ PCP_VFS_FILES_MAX, /* vfs.files.max */
PCP_PROC_PID, /* proc.psinfo.pid */
PCP_PROC_PPID, /* proc.psinfo.ppid */
@@ -153,28 +158,32 @@ typedef enum PCPMetric_ {
PCP_PROC_SMAPS_SWAPPSS, /* proc.smaps.swappss */
PCP_METRIC_COUNT /* total metric count */
-} PCPMetric;
+} Metric;
-void PCPMetric_enable(PCPMetric metric, bool enable);
+void Metric_enable(Metric metric, bool enable);
-bool PCPMetric_enabled(PCPMetric metric);
+bool Metric_enabled(Metric metric);
-void PCPMetric_enableThreads(void);
+void Metric_enableThreads(void);
-bool PCPMetric_fetch(struct timeval* timestamp);
+bool Metric_fetch(struct timeval* timestamp);
-bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp);
+bool Metric_iterate(Metric metric, int* instp, int* offsetp);
-pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type);
+pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type);
-const pmDesc* PCPMetric_desc(PCPMetric metric);
+const pmDesc* Metric_desc(Metric metric);
-int PCPMetric_type(PCPMetric metric);
+int Metric_type(Metric metric);
-int PCPMetric_instanceCount(PCPMetric metric);
+int Metric_instanceCount(Metric metric);
-int PCPMetric_instanceOffset(PCPMetric metric, int inst);
+int Metric_instanceOffset(Metric metric, int inst);
-pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type);
+pmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type);
+
+void Metric_externalName(Metric metric, int inst, char** externalName);
+
+int Metric_lookupText(const char* metric, char** desc);
#endif
diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c
index aab25253..b0bd03e1 100644
--- a/pcp/PCPDynamicColumn.c
+++ b/pcp/PCPDynamicColumn.c
@@ -1,8 +1,7 @@
/*
htop - PCPDynamicColumn.c
-(C) 2021 Sohaib Mohammed
-(C) 2021 htop dev team
-(C) 2021 Red Hat, Inc.
+(C) 2021-2023 Sohaib Mohammed
+(C) 2021-2023 htop dev team
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -14,6 +13,7 @@ in the source distribution for its full text.
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
+#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -23,12 +23,13 @@ in the source distribution for its full text.
#include "Macros.h"
#include "Platform.h"
#include "Process.h"
-#include "ProcessList.h"
+#include "ProcessTable.h"
#include "RichString.h"
#include "XUtils.h"
+#include "linux/CGroupUtils.h"
+#include "pcp/Metric.h"
#include "pcp/PCPProcess.h"
-#include "pcp/PCPMetric.h"
static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) {
@@ -48,6 +49,10 @@ static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicCol
}
static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) {
+ /* pmLookupText */
+ if (!column->super.description)
+ Metric_lookupText(value, &column->super.description);
+
/* lookup a dynamic metric with this name, else create */
if (PCPDynamicColumn_addMetric(columns, column) == false)
return;
@@ -107,6 +112,10 @@ static bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) {
static PCPDynamicColumn* PCPDynamicColumn_new(PCPDynamicColumns* columns, const char* name) {
PCPDynamicColumn* column = xCalloc(1, sizeof(*column));
String_safeStrncpy(column->super.name, name, sizeof(column->super.name));
+ column->super.enabled = false;
+ column->percent = false;
+ column->instances = false;
+ column->defaultEnabled = true;
size_t id = columns->count + LAST_PROCESSFIELD;
Hashtable_put(columns->table, id, column);
@@ -159,6 +168,14 @@ static void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* p
free_and_xStrdup(&column->super.description, value);
} else if (value && column && String_eq(key, "width")) {
column->super.width = strtoul(value, NULL, 10);
+ } else if (value && column && String_eq(key, "format")) {
+ free_and_xStrdup(&column->format, value);
+ } else if (value && column && String_eq(key, "instances")) {
+ if (String_eq(value, "True") || String_eq(value, "true"))
+ column->instances = true;
+ } else if (value && column && (String_eq(key, "default") || String_eq(key, "enabled"))) {
+ if (String_eq(value, "False") || String_eq(value, "false"))
+ column->defaultEnabled = false;
} else if (value && column && String_eq(key, "metric")) {
PCPDynamicColumn_parseMetric(columns, column, path, lineno, value);
}
@@ -194,6 +211,12 @@ void PCPDynamicColumns_init(PCPDynamicColumns* columns) {
const char* home = getenv("HOME");
char* path;
+ if (!xdgConfigHome && !home) {
+ const struct passwd* pw = getpwuid(getuid());
+ if (pw)
+ home = pw->pw_dir;
+ }
+
columns->table = Hashtable_new(0, true);
/* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */
@@ -226,88 +249,239 @@ void PCPDynamicColumns_init(PCPDynamicColumns* columns) {
free(path);
}
+void PCPDynamicColumn_done(PCPDynamicColumn* this) {
+ DynamicColumn_done(&this->super);
+ free(this->metricName);
+ free(this->format);
+}
+
static void PCPDynamicColumns_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {
PCPDynamicColumn* column = (PCPDynamicColumn*) value;
- free(column->metricName);
- free(column->super.heading);
- free(column->super.caption);
- free(column->super.description);
+ PCPDynamicColumn_done(column);
}
void PCPDynamicColumns_done(Hashtable* table) {
Hashtable_foreach(table, PCPDynamicColumns_free, NULL);
}
-void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) {
- const PCPProcess* pp = (const PCPProcess*) proc;
- unsigned int type = PCPMetric_type(this->id);
+static void PCPDynamicColumn_setupWidth(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {
+ PCPDynamicColumn* column = (PCPDynamicColumn*) value;
- pmAtomValue atom;
- if (!PCPMetric_instance(this->id, proc->pid, pp->offset, &atom, type)) {
- RichString_appendAscii(str, CRT_colors[METER_VALUE_ERROR], "no data");
+ /* calculate column size based on config file and metric units */
+ const pmDesc* desc = Metric_desc(column->id);
+
+ if (column->instances || desc->type == PM_TYPE_STRING) {
+ column->super.width = column->width;
+ if (column->super.width == 0)
+ column->super.width = -16;
return;
}
- int width = this->super.width;
- if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
- width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
- int abswidth = abs(width);
- if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) {
- abswidth = DYNAMIC_MAX_COLUMN_WIDTH;
- width = -abswidth;
+ if (column->format) {
+ if (strcmp(column->format, "percent") == 0) {
+ column->super.width = 5;
+ return;
+ }
+ if (strcmp(column->format, "process") == 0) {
+ column->super.width = Process_pidDigits;
+ return;
+ }
}
- char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1];
- int attr = CRT_colors[DEFAULT_COLOR];
+ if (column->width) {
+ column->super.width = column->width;
+ return;
+ }
+
+ pmUnits units = desc->units;
+ if (units.dimSpace && units.dimTime)
+ column->super.width = 11; // Row_printRate
+ else if (units.dimSpace)
+ column->super.width = 5; // Row_printBytes
+ else if (units.dimCount && units.dimTime)
+ column->super.width = 11; // Row_printCount
+ else if (units.dimTime)
+ column->super.width = 8; // Row_printTime
+ else
+ column->super.width = 11; // Row_printCount
+}
+
+void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns) {
+ Hashtable_foreach(columns->table, PCPDynamicColumn_setupWidth, NULL);
+}
+
+/* normalize output units to bytes and seconds */
+static int PCPDynamicColumn_normalize(const pmDesc* desc, const pmAtomValue* ap, double* value) {
+ /* form normalized units based on the original metric units */
+ pmUnits units = desc->units;
+ if (units.dimTime)
+ units.scaleTime = PM_TIME_SEC;
+ if (units.dimSpace)
+ units.scaleSpace = PM_SPACE_BYTE;
+ if (units.dimCount)
+ units.scaleCount = PM_COUNT_ONE;
+
+ pmAtomValue atom;
+ int sts, type = desc->type;
+ if ((sts = pmConvScale(type, ap, &desc->units, &atom, &units)) < 0)
+ return sts;
+
switch (type) {
- case PM_TYPE_STRING:
- attr = CRT_colors[PROCESS_SHADOW];
- Process_printLeftAlignedField(str, attr, atom.cp, abswidth);
- free(atom.cp);
- break;
case PM_TYPE_32:
- xSnprintf(buffer, sizeof(buffer), "%*d ", width, atom.l);
- RichString_appendAscii(str, attr, buffer);
+ *value = (double) atom.l;
break;
case PM_TYPE_U32:
- xSnprintf(buffer, sizeof(buffer), "%*u ", width, atom.ul);
- RichString_appendAscii(str, attr, buffer);
+ *value = (double) atom.ul;
break;
case PM_TYPE_64:
- xSnprintf(buffer, sizeof(buffer), "%*lld ", width, (long long) atom.ll);
- RichString_appendAscii(str, attr, buffer);
+ *value = (double) atom.ll;
break;
case PM_TYPE_U64:
- xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long) atom.ull);
- RichString_appendAscii(str, attr, buffer);
+ *value = (double) atom.ull;
break;
case PM_TYPE_FLOAT:
- xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, (double) atom.f);
- RichString_appendAscii(str, attr, buffer);
+ *value = (double) atom.f;
break;
case PM_TYPE_DOUBLE:
- xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, atom.d);
- RichString_appendAscii(str, attr, buffer);
+ *value = atom.d;
break;
default:
- attr = CRT_colors[METER_VALUE_ERROR];
- RichString_appendAscii(str, attr, "no type");
- break;
+ return PM_ERR_CONV;
+ }
+
+ return 0;
+}
+
+void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const pmDesc* desc, const void* atom) {
+ const pmAtomValue* atomvalue = (const pmAtomValue*) atom;
+ char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /*space*/ 1 + /*null*/ 1];
+ int attr = CRT_colors[DEFAULT_COLOR];
+ int width = column->super.width;
+ int n;
+
+ if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
+ width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
+ int abswidth = abs(width);
+ if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) {
+ abswidth = DYNAMIC_MAX_COLUMN_WIDTH;
+ width = -abswidth;
+ }
+
+ if (atomvalue == NULL) {
+ n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A");
+ RichString_appendnAscii(str, CRT_colors[PROCESS_SHADOW], buffer, n);
+ return;
}
+
+ /* deal with instance names and metrics with string values first */
+ if (column->instances || desc->type == PM_TYPE_STRING) {
+ char* value = NULL;
+ char* dupd1 = NULL;
+ if (column->instances) {
+ attr = CRT_colors[DYNAMIC_GRAY];
+ Metric_externalName(metric, instance, &dupd1);
+ value = dupd1;
+ } else {
+ attr = CRT_colors[DYNAMIC_GREEN];
+ value = atomvalue->cp;
+ }
+ if (column->format && value) {
+ char* dupd2 = NULL;
+ if (strcmp(column->format, "command") == 0)
+ attr = CRT_colors[PROCESS_COMM];
+ else if (strcmp(column->format, "process") == 0)
+ attr = CRT_colors[PROCESS_SHADOW];
+ else if (strcmp(column->format, "device") == 0 && strncmp(value, "/dev/", 5) == 0)
+ value += 5;
+ else if (strcmp(column->format, "cgroup") == 0 && (dupd2 = CGroup_filterName(value)))
+ value = dupd2;
+ n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value);
+ if (dupd2)
+ free(dupd2);
+ } else if (value) {
+ n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value);
+ } else {
+ n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A");
+ }
+ if (dupd1)
+ free(dupd1);
+ RichString_appendnAscii(str, attr, buffer, n);
+ return;
+ }
+
+ /* deal with any numeric value - first, normalize units to bytes/seconds */
+ double value;
+ if (PCPDynamicColumn_normalize(desc, atomvalue, &value) < 0) {
+ n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "no conv");
+ RichString_appendnAscii(str, CRT_colors[METER_VALUE_ERROR], buffer, n);
+ return;
+ }
+
+ if (column->format) {
+ if (strcmp(column->format, "percent") == 0) {
+ n = Row_printPercentage(value, buffer, sizeof(buffer), width, &attr);
+ RichString_appendnAscii(str, attr, buffer, n);
+ return;
+ }
+ if (strcmp(column->format, "process") == 0) {
+ n = xSnprintf(buffer, sizeof(buffer), "%*d ", Row_pidDigits, (int)value);
+ RichString_appendnAscii(str, attr, buffer, n);
+ return;
+ }
+ }
+
+ /* width overrides unit suffix and coloring; too complex for a corner case */
+ if (column->width) {
+ if (value - (unsigned long long)value > 0) /* display floating point */
+ n = xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, value);
+ else /* display as integer */
+ n = xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long)value);
+ RichString_appendnAscii(str, CRT_colors[PROCESS], buffer, n);
+ return;
+ }
+
+ bool coloring = settings->highlightMegabytes;
+ pmUnits units = desc->units;
+ if (units.dimSpace && units.dimTime)
+ Row_printRate(str, value, coloring);
+ else if (units.dimSpace)
+ Row_printBytes(str, value, coloring);
+ else if (units.dimCount)
+ Row_printCount(str, value, coloring);
+ else if (units.dimTime)
+ Row_printTime(str, value / 10 /* hundreds of a second */, coloring);
+ else
+ Row_printCount(str, value, 0); /* e.g. PID */
+}
+
+void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) {
+ const Settings* settings = proc->super.host->settings;
+ const PCPProcess* pp = (const PCPProcess*) proc;
+ const pmDesc* desc = Metric_desc(this->id);
+ pid_t pid = Process_getPid(proc);
+
+ pmAtomValue atom;
+ pmAtomValue* ap = &atom;
+ if (!Metric_instance(this->id, pid, pp->offset, ap, desc->type))
+ ap = NULL;
+
+ PCPDynamicColumn_writeAtomValue(this, str, settings, this->id, pid, desc, ap);
}
int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) {
- const PCPDynamicColumn* column = Hashtable_get(p1->super.processList->dynamicColumns, key);
+ const Process* proc = &p1->super;
+ const Settings* settings = proc->super.host->settings;
+ const PCPDynamicColumn* column = Hashtable_get(settings->dynamicColumns, key);
if (!column)
return -1;
size_t metric = column->id;
- unsigned int type = PCPMetric_type(metric);
+ unsigned int type = Metric_type(metric);
pmAtomValue atom1 = {0}, atom2 = {0};
- if (!PCPMetric_instance(metric, p1->super.pid, p1->offset, &atom1, type) ||
- !PCPMetric_instance(metric, p2->super.pid, p2->offset, &atom2, type)) {
+ if (!Metric_instance(metric, Process_getPid(&p1->super), p1->offset, &atom1, type) ||
+ !Metric_instance(metric, Process_getPid(&p2->super), p2->offset, &atom2, type)) {
if (type == PM_TYPE_STRING) {
free(atom1.cp);
free(atom2.cp);
@@ -331,11 +505,12 @@ int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, Pr
case PM_TYPE_U64:
return SPACESHIP_NUMBER(atom2.ull, atom1.ull);
case PM_TYPE_FLOAT:
- return SPACESHIP_NUMBER(atom2.f, atom1.f);
+ return compareRealNumbers(atom2.f, atom1.f);
case PM_TYPE_DOUBLE:
- return SPACESHIP_NUMBER(atom2.d, atom1.d);
+ return compareRealNumbers(atom2.d, atom1.d);
default:
break;
}
+
return -1;
}
diff --git a/pcp/PCPDynamicColumn.h b/pcp/PCPDynamicColumn.h
index d0ffe719..ade782b7 100644
--- a/pcp/PCPDynamicColumn.h
+++ b/pcp/PCPDynamicColumn.h
@@ -1,5 +1,11 @@
#ifndef HEADER_PCPDynamicColumn
#define HEADER_PCPDynamicColumn
+/*
+htop - PCPDynamicColumn.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
#include <stddef.h>
@@ -11,15 +17,22 @@
#include "pcp/PCPProcess.h"
+struct pmDesc;
+
typedef struct PCPDynamicColumn_ {
DynamicColumn super;
char* metricName;
+ char* format;
size_t id; /* identifier for metric array lookups */
+ int width; /* optional width from configuration file */
+ bool defaultEnabled; /* default enabled in dynamic screen */
+ bool percent;
+ bool instances; /* an instance *names* column, not values */
} PCPDynamicColumn;
typedef struct PCPDynamicColumns_ {
Hashtable* table;
- size_t count; /* count of dynamic meters discovered by scan */
+ size_t count; /* count of dynamic columns discovered by scan */
size_t offset; /* start offset into the Platform metric array */
size_t cursor; /* identifier allocator for each new metric used */
} PCPDynamicColumns;
@@ -28,8 +41,14 @@ void PCPDynamicColumns_init(PCPDynamicColumns* columns);
void PCPDynamicColumns_done(Hashtable* table);
+void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns);
+
void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str);
+void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const struct pmDesc* desc, const void* atomvalue);
+
int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key);
+void PCPDynamicColumn_done(PCPDynamicColumn* this);
+
#endif
diff --git a/pcp/PCPDynamicMeter.c b/pcp/PCPDynamicMeter.c
index 7c55e4bf..11df5f0a 100644
--- a/pcp/PCPDynamicMeter.c
+++ b/pcp/PCPDynamicMeter.c
@@ -5,6 +5,7 @@ htop - PCPDynamicMeter.c
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+
#include "config.h" // IWYU pragma: keep
#include "pcp/PCPDynamicMeter.h"
@@ -12,18 +13,20 @@ in the source distribution for its full text.
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
-#include <pcp/pmapi.h>
+#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <pcp/pmapi.h>
+
#include "Macros.h"
#include "Platform.h"
#include "RichString.h"
#include "XUtils.h"
-#include "pcp/PCPMetric.h"
+#include "pcp/Metric.h"
static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) {
@@ -251,6 +254,12 @@ void PCPDynamicMeters_init(PCPDynamicMeters* meters) {
const char* home = getenv("HOME");
char* path;
+ if (!xdgConfigHome && !home) {
+ const struct passwd* pw = getpwuid(getuid());
+ if (pw)
+ home = pw->pw_dir;
+ }
+
meters->table = Hashtable_new(0, true);
/* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */
@@ -301,7 +310,7 @@ void PCPDynamicMeters_done(Hashtable* table) {
void PCPDynamicMeter_enable(PCPDynamicMeter* this) {
for (size_t i = 0; i < this->totalMetrics; i++)
- PCPMetric_enable(this->metrics[i].id, true);
+ Metric_enable(this->metrics[i].id, true);
}
void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) {
@@ -314,10 +323,10 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) {
buffer[bytes++] = '/'; /* separator */
PCPDynamicMetric* metric = &this->metrics[i];
- const pmDesc* desc = PCPMetric_desc(metric->id);
+ const pmDesc* desc = Metric_desc(metric->id);
pmAtomValue atom, raw;
- if (!PCPMetric_values(metric->id, &raw, 1, desc->type)) {
+ if (!Metric_values(metric->id, &raw, 1, desc->type)) {
bytes--; /* clear the separator */
continue;
}
@@ -342,27 +351,27 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) {
break;
case PM_TYPE_32:
bytes += conv.dimSpace ?
- Meter_humanUnit(buffer + bytes, atom.l, size - bytes) :
+ Meter_humanUnit(buffer + bytes, (double) atom.l, size - bytes) :
xSnprintf(buffer + bytes, size - bytes, "%d", atom.l);
break;
case PM_TYPE_U32:
bytes += conv.dimSpace ?
- Meter_humanUnit(buffer + bytes, atom.ul, size - bytes) :
+ Meter_humanUnit(buffer + bytes, (double) atom.ul, size - bytes) :
xSnprintf(buffer + bytes, size - bytes, "%u", atom.ul);
break;
case PM_TYPE_64:
bytes += conv.dimSpace ?
- Meter_humanUnit(buffer + bytes, atom.ll, size - bytes) :
+ Meter_humanUnit(buffer + bytes, (double) atom.ll, size - bytes) :
xSnprintf(buffer + bytes, size - bytes, "%lld", (long long) atom.ll);
break;
case PM_TYPE_U64:
bytes += conv.dimSpace ?
- Meter_humanUnit(buffer + bytes, atom.ull, size - bytes) :
+ Meter_humanUnit(buffer + bytes, (double) atom.ull, size - bytes) :
xSnprintf(buffer + bytes, size - bytes, "%llu", (unsigned long long) atom.ull);
break;
case PM_TYPE_FLOAT:
bytes += conv.dimSpace ?
- Meter_humanUnit(buffer + bytes, atom.f, size - bytes) :
+ Meter_humanUnit(buffer + bytes, (double) atom.f, size - bytes) :
xSnprintf(buffer + bytes, size - bytes, "%.2f", (double) atom.f);
break;
case PM_TYPE_DOUBLE:
@@ -373,9 +382,11 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) {
default:
break;
}
+
if (saved != bytes && metric->suffix)
bytes += xSnprintf(buffer + bytes, size - bytes, "%s", metric->suffix);
}
+
if (!bytes)
xSnprintf(buffer, size, "no data");
}
@@ -385,11 +396,11 @@ void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* met
for (size_t i = 0; i < this->totalMetrics; i++) {
PCPDynamicMetric* metric = &this->metrics[i];
- const pmDesc* desc = PCPMetric_desc(metric->id);
+ const pmDesc* desc = Metric_desc(metric->id);
pmAtomValue atom, raw;
char buffer[64];
- if (!PCPMetric_values(metric->id, &raw, 1, desc->type))
+ if (!Metric_values(metric->id, &raw, 1, desc->type))
continue;
pmUnits conv = desc->units; /* convert to canonical units */
@@ -418,27 +429,27 @@ void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* met
break;
case PM_TYPE_32:
len = conv.dimSpace ?
- Meter_humanUnit(buffer, atom.l, sizeof(buffer)) :
+ Meter_humanUnit(buffer, (double) atom.l, sizeof(buffer)) :
xSnprintf(buffer, sizeof(buffer), "%d", atom.l);
break;
case PM_TYPE_U32:
len = conv.dimSpace ?
- Meter_humanUnit(buffer, atom.ul, sizeof(buffer)) :
+ Meter_humanUnit(buffer, (double) atom.ul, sizeof(buffer)) :
xSnprintf(buffer, sizeof(buffer), "%u", atom.ul);
break;
case PM_TYPE_64:
len = conv.dimSpace ?
- Meter_humanUnit(buffer, atom.ll, sizeof(buffer)) :
+ Meter_humanUnit(buffer, (double) atom.ll, sizeof(buffer)) :
xSnprintf(buffer, sizeof(buffer), "%lld", (long long) atom.ll);
break;
case PM_TYPE_U64:
len = conv.dimSpace ?
- Meter_humanUnit(buffer, atom.ull, sizeof(buffer)) :
+ Meter_humanUnit(buffer, (double) atom.ull, sizeof(buffer)) :
xSnprintf(buffer, sizeof(buffer), "%llu", (unsigned long long) atom.ull);
break;
case PM_TYPE_FLOAT:
len = conv.dimSpace ?
- Meter_humanUnit(buffer, atom.f, sizeof(buffer)) :
+ Meter_humanUnit(buffer, (double) atom.f, sizeof(buffer)) :
xSnprintf(buffer, sizeof(buffer), "%.2f", (double) atom.f);
break;
case PM_TYPE_DOUBLE:
@@ -449,12 +460,14 @@ void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* met
default:
break;
}
+
if (len) {
RichString_appendnAscii(out, CRT_colors[metric->color], buffer, len);
if (metric->suffix)
RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->suffix);
}
}
+
if (nodata)
RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
}
diff --git a/pcp/PCPDynamicMeter.h b/pcp/PCPDynamicMeter.h
index 0e5ddd2b..3a72d13c 100644
--- a/pcp/PCPDynamicMeter.h
+++ b/pcp/PCPDynamicMeter.h
@@ -1,5 +1,11 @@
#ifndef HEADER_PCPDynamicMeter
#define HEADER_PCPDynamicMeter
+/*
+htop - PCPDynamicMeter.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
#include <stddef.h>
diff --git a/pcp/PCPDynamicScreen.c b/pcp/PCPDynamicScreen.c
new file mode 100644
index 00000000..22228225
--- /dev/null
+++ b/pcp/PCPDynamicScreen.c
@@ -0,0 +1,407 @@
+/*
+htop - PCPDynamicScreen.c
+(C) 2022 Sohaib Mohammed
+(C) 2022-2023 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 "pcp/PCPDynamicScreen.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <stdbool.h>
+#include <pcp/pmapi.h>
+
+#include "AvailableColumnsPanel.h"
+#include "Macros.h"
+#include "Platform.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+#include "pcp/InDomTable.h"
+#include "pcp/PCPDynamicColumn.h"
+
+
+static char* formatFields(PCPDynamicScreen* screen) {
+ char* columns = strdup("");
+
+ for (size_t j = 0; j < screen->totalColumns; j++) {
+ const PCPDynamicColumn* column = screen->columns[j];
+ if (column->super.enabled == false)
+ continue;
+ char* prefix = columns;
+ xAsprintf(&columns, "%s Dynamic(%s)", prefix, column->super.name);
+ free(prefix);
+ }
+
+ return columns;
+}
+
+static void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, PCPDynamicColumns* columns) {
+ for (size_t i = 0; i < screens->count; i++) {
+ PCPDynamicScreen* screen = Hashtable_get(screens->table, i);
+ if (!screen)
+ return;
+
+ /* setup default fields (columns) based on configuration */
+ for (size_t j = 0; j < screen->totalColumns; j++) {
+ PCPDynamicColumn* column = screen->columns[j];
+
+ column->id = columns->offset + columns->cursor;
+ columns->cursor++;
+ Platform_addMetric(column->id, column->metricName);
+
+ size_t id = columns->count + LAST_PROCESSFIELD;
+ Hashtable_put(columns->table, id, column);
+ columns->count++;
+
+ if (j == 0) {
+ const pmDesc* desc = Metric_desc(column->id);
+ assert(desc->indom != PM_INDOM_NULL);
+ screen->indom = desc->indom;
+ screen->key = column->id;
+ }
+ }
+ screen->super.columnKeys = formatFields(screen);
+ }
+}
+
+static PCPDynamicColumn* PCPDynamicScreen_lookupMetric(PCPDynamicScreen* screen, const char* name) {
+ PCPDynamicColumn* column = NULL;
+ size_t bytes = strlen(name) + strlen(screen->super.name) + 1; /* colon */
+ if (bytes >= sizeof(column->super.name))
+ return NULL;
+
+ bytes += 16; /* prefix, dots and terminator */
+ char* metricName = xMalloc(bytes);
+ xSnprintf(metricName, bytes, "htop.screen.%s.%s", screen->super.name, name);
+
+ for (size_t i = 0; i < screen->totalColumns; i++) {
+ column = screen->columns[i];
+ if (String_eq(column->metricName, metricName)) {
+ free(metricName);
+ return column;
+ }
+ }
+
+ /* not an existing column in this screen - create it and add to the list */
+ column = xCalloc(1, sizeof(PCPDynamicColumn));
+ xSnprintf(column->super.name, sizeof(column->super.name), "%s:%s", screen->super.name, name);
+ column->super.table = &screen->table->super;
+ column->metricName = metricName;
+ column->super.enabled = true;
+
+ size_t n = screen->totalColumns + 1;
+ screen->columns = xReallocArray(screen->columns, n, sizeof(PCPDynamicColumn*));
+ screen->columns[n - 1] = column;
+ screen->totalColumns = n;
+
+ return column;
+}
+
+static void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* path, unsigned int line, char* key, char* value) {
+ PCPDynamicColumn* column;
+ char* p;
+
+ if ((p = strchr(key, '.')) == NULL)
+ return;
+ *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */
+
+ /* lookup a dynamic column with this name, else create */
+ column = PCPDynamicScreen_lookupMetric(screen, key);
+
+ if (String_eq(p, "metric")) {
+ char* error;
+ if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) {
+ char* note;
+ xAsprintf(&note,
+ "%s: failed to parse expression in %s at line %u\n%s\n",
+ pmGetProgname(), path, line, error);
+ free(error);
+ errno = EINVAL;
+ CRT_fatalError(note);
+ free(note);
+ }
+
+ /* pmLookupText - add optional metric help text */
+ if (!column->super.description && !column->instances)
+ Metric_lookupText(value, &column->super.description);
+
+ } else {
+ /* this is a property of a dynamic column - the column expression */
+ /* may not have been observed yet; i.e. we allow for any ordering */
+
+ if (String_eq(p, "caption")) {
+ free_and_xStrdup(&column->super.caption, value);
+ } else if (String_eq(p, "heading")) {
+ free_and_xStrdup(&column->super.heading, value);
+ } else if (String_eq(p, "description")) {
+ free_and_xStrdup(&column->super.description, value);
+ } else if (String_eq(p, "width")) {
+ column->width = strtoul(value, NULL, 10);
+ } else if (String_eq(p, "format")) {
+ free_and_xStrdup(&column->format, value);
+ } else if (String_eq(p, "instances")) {
+ if (String_eq(value, "True") || String_eq(value, "true"))
+ column->instances = true;
+ free_and_xStrdup(&column->super.description, screen->super.caption);
+ } else if (String_eq(p, "default")) { /* displayed by default */
+ if (String_eq(value, "False") || String_eq(value, "false"))
+ column->defaultEnabled = column->super.enabled = false;
+ }
+ }
+}
+
+static bool PCPDynamicScreen_validateScreenName(char* key, const char* path, unsigned int line) {
+ char* p = key;
+ char* end = strrchr(key, ']');
+
+ if (end) {
+ *end = '\0';
+ } else {
+ fprintf(stderr,
+ "%s: no closing brace on screen name at %s line %u\n\"%s\"",
+ pmGetProgname(), path, line, key);
+ return false;
+ }
+
+ while (*p) {
+ if (p == key) {
+ if (!isalpha(*p) && *p != '_')
+ break;
+ } else {
+ if (!isalnum(*p) && *p != '_')
+ break;
+ }
+ p++;
+ }
+ if (*p != '\0') { /* badness */
+ fprintf(stderr,
+ "%s: invalid screen name at %s line %u\n\"%s\"",
+ pmGetProgname(), path, line, key);
+ return false;
+ }
+ return true;
+}
+
+/* Ensure a screen name has not been defined previously */
+static bool PCPDynamicScreen_uniqueName(char* key, PCPDynamicScreens* screens) {
+ return !DynamicScreen_search(screens->table, key, NULL);
+}
+
+static PCPDynamicScreen* PCPDynamicScreen_new(PCPDynamicScreens* screens, const char* name) {
+ PCPDynamicScreen* screen = xCalloc(1, sizeof(*screen));
+ String_safeStrncpy(screen->super.name, name, sizeof(screen->super.name));
+ screen->defaultEnabled = true;
+
+ size_t id = screens->count;
+ Hashtable_put(screens->table, id, screen);
+ screens->count++;
+
+ return screen;
+}
+
+static void PCPDynamicScreen_parseFile(PCPDynamicScreens* screens, const char* path) {
+ FILE* file = fopen(path, "r");
+ if (!file)
+ return;
+
+ PCPDynamicScreen* screen = NULL;
+ unsigned int lineno = 0;
+ bool ok = true;
+ for (;;) {
+ char* line = String_readLine(file);
+ if (!line)
+ break;
+ lineno++;
+
+ /* cleanup whitespace, skip comment lines */
+ char* trimmed = String_trim(line);
+ free(line);
+ if (!trimmed || !trimmed[0] || trimmed[0] == '#') {
+ free(trimmed);
+ continue;
+ }
+
+ size_t n;
+ char** config = String_split(trimmed, '=', &n);
+ free(trimmed);
+ if (config == NULL)
+ continue;
+
+ char* key = String_trim(config[0]);
+ char* value = n > 1 ? String_trim(config[1]) : NULL;
+ if (key[0] == '[') { /* new section name - i.e. new screen */
+ ok = PCPDynamicScreen_validateScreenName(key + 1, path, lineno);
+ if (ok)
+ ok = PCPDynamicScreen_uniqueName(key + 1, screens);
+ if (ok)
+ screen = PCPDynamicScreen_new(screens, key + 1);
+ if (pmDebugOptions.appl0)
+ fprintf(stderr, "[%s] screen: %s\n", path, key + 1);
+ } else if (!ok) {
+ ; /* skip this one, we're looking for a new header */
+ } else if (!value || !screen) {
+ ; /* skip this one as we always need value strings */
+ } else if (String_eq(key, "heading")) {
+ free_and_xStrdup(&screen->super.heading, value);
+ } else if (String_eq(key, "caption")) {
+ free_and_xStrdup(&screen->super.caption, value);
+ } else if (String_eq(key, "sortKey")) {
+ free_and_xStrdup(&screen->super.sortKey, value);
+ } else if (String_eq(key, "sortDirection")) {
+ screen->super.direction = strtoul(value, NULL, 10);
+ } else if (String_eq(key, "default") || String_eq(key, "enabled")) {
+ if (String_eq(value, "False") || String_eq(value, "false"))
+ screen->defaultEnabled = false;
+ else if (String_eq(value, "True") || String_eq(value, "true"))
+ screen->defaultEnabled = true; /* also default */
+ } else {
+ PCPDynamicScreen_parseColumn(screen, path, lineno, key, value);
+ }
+ String_freeArray(config);
+ free(value);
+ free(key);
+ }
+ fclose(file);
+}
+
+static void PCPDynamicScreen_scanDir(PCPDynamicScreens* screens, char* path) {
+ DIR* dir = opendir(path);
+ if (!dir)
+ return;
+
+ struct dirent* dirent;
+ while ((dirent = readdir(dir)) != NULL) {
+ if (dirent->d_name[0] == '.')
+ continue;
+
+ char* file = String_cat(path, dirent->d_name);
+ PCPDynamicScreen_parseFile(screens, file);
+ free(file);
+ }
+ closedir(dir);
+}
+
+void PCPDynamicScreens_init(PCPDynamicScreens* screens, PCPDynamicColumns* columns) {
+ const char* share = pmGetConfig("PCP_SHARE_DIR");
+ const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR");
+ const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
+ const char* override = getenv("PCP_HTOP_DIR");
+ const char* home = getenv("HOME");
+ char* path;
+
+ screens->table = Hashtable_new(0, true);
+
+ /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */
+ if (override) {
+ path = String_cat(override, "/screens/");
+ PCPDynamicScreen_scanDir(screens, path);
+ free(path);
+ }
+
+ /* next, search in home directory alongside htoprc */
+ if (xdgConfigHome)
+ path = String_cat(xdgConfigHome, "/htop/screens/");
+ else if (home)
+ path = String_cat(home, "/.config/htop/screens/");
+ else
+ path = NULL;
+ if (path) {
+ PCPDynamicScreen_scanDir(screens, path);
+ free(path);
+ }
+
+ /* next, search in the system screens directory */
+ path = String_cat(sysconf, "/htop/screens/");
+ PCPDynamicScreen_scanDir(screens, path);
+ free(path);
+
+ /* next, try the readonly system screens directory */
+ path = String_cat(share, "/htop/screens/");
+ PCPDynamicScreen_scanDir(screens, path);
+ free(path);
+
+ /* establish internal metric identifier mappings */
+ PCPDynamicScreens_appendDynamicColumns(screens, columns);
+}
+
+static void PCPDynamicScreen_done(PCPDynamicScreen* ds) {
+ DynamicScreen_done(&ds->super);
+ Object_delete(ds->table);
+ free(ds->columns);
+}
+
+static void PCPDynamicScreens_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {
+ PCPDynamicScreen* ds = (PCPDynamicScreen*) value;
+ PCPDynamicScreen_done(ds);
+}
+
+void PCPDynamicScreens_done(Hashtable* table) {
+ Hashtable_foreach(table, PCPDynamicScreens_free, NULL);
+}
+
+void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host) {
+ PCPDynamicScreen* ds;
+
+ for (size_t i = 0; i < screens->count; i++) {
+ if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)
+ continue;
+ ds->table = InDomTable_new(host, ds->indom, ds->key);
+ }
+}
+
+void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings) {
+ PCPDynamicScreen* ds;
+
+ for (size_t i = 0; i < screens->count; i++) {
+ if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)
+ continue;
+ if (ds->defaultEnabled == false)
+ continue;
+ const char* tab = ds->super.heading;
+ Settings_newDynamicScreen(settings, tab, &ds->super, &ds->table->super);
+ }
+}
+
+/* called when htoprc .dynamic line is parsed for a dynamic screen */
+void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss) {
+ PCPDynamicScreen* ds;
+
+ for (size_t i = 0; i < screens->count; i++) {
+ if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)
+ continue;
+ if (String_eq(ss->dynamic, ds->super.name) == false)
+ continue;
+ ss->table = &ds->table->super;
+ }
+}
+
+void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen) {
+ Vector_prune(availableColumns->items);
+
+ bool success;
+ unsigned int key;
+ success = DynamicScreen_search(screens, screen, &key);
+ if (!success)
+ return;
+
+ PCPDynamicScreen* dynamicScreen = Hashtable_get(screens, key);
+ if (!screen)
+ return;
+
+ for (unsigned int j = 0; j < dynamicScreen->totalColumns; j++) {
+ PCPDynamicColumn* column = dynamicScreen->columns[j];
+ const char* title = column->super.heading ? column->super.heading : column->super.name;
+ const char* text = column->super.description ? column->super.description : column->super.caption;
+ char description[256];
+ if (text)
+ xSnprintf(description, sizeof(description), "%s - %s", title, text);
+ else
+ xSnprintf(description, sizeof(description), "%s", title);
+ Panel_add(availableColumns, (Object*) ListItem_new(description, j));
+ }
+}
diff --git a/pcp/PCPDynamicScreen.h b/pcp/PCPDynamicScreen.h
new file mode 100644
index 00000000..62483941
--- /dev/null
+++ b/pcp/PCPDynamicScreen.h
@@ -0,0 +1,56 @@
+#ifndef HEADER_PCPDynamicScreen
+#define HEADER_PCPDynamicScreen
+/*
+htop - PCPDynamicScreen.h
+(C) 2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "CRT.h"
+#include "DynamicScreen.h"
+#include "Hashtable.h"
+#include "Machine.h"
+#include "Panel.h"
+#include "Settings.h"
+
+
+struct InDomTable_;
+struct PCPDynamicColumn_;
+struct PCPDynamicColumns_;
+
+typedef struct PCPDynamicScreen_ {
+ DynamicScreen super;
+
+ struct InDomTable_* table;
+ struct PCPDynamicColumn_** columns;
+ size_t totalColumns;
+
+ unsigned int indom; /* instance domain number */
+ unsigned int key; /* PCPMetric identifier */
+
+ bool defaultEnabled; /* enabled setting from configuration file */
+ /* at runtime enabled screens have entries in settings->screens */
+} PCPDynamicScreen;
+
+typedef struct PCPDynamicScreens_ {
+ Hashtable* table;
+ size_t count; /* count of dynamic screens discovered from scan */
+} PCPDynamicScreens;
+
+void PCPDynamicScreens_init(PCPDynamicScreens* screens, struct PCPDynamicColumns_* columns);
+
+void PCPDynamicScreens_done(Hashtable* table);
+
+void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host);
+
+void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings);
+
+void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss);
+
+void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen);
+
+#endif
diff --git a/pcp/PCPMachine.c b/pcp/PCPMachine.c
new file mode 100644
index 00000000..2e872534
--- /dev/null
+++ b/pcp/PCPMachine.c
@@ -0,0 +1,345 @@
+/*
+htop - PCPProcessTable.c
+(C) 2014 Hisham H. Muhammad
+(C) 2020-2023 htop dev team
+(C) 2020-2023 Red Hat, Inc.
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "pcp/PCPMachine.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "Machine.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Platform.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+#include "pcp/Metric.h"
+#include "pcp/PCPProcess.h"
+
+
+static void PCPMachine_updateCPUcount(PCPMachine* this) {
+ Machine* super = &this->super;
+ super->activeCPUs = Metric_instanceCount(PCP_PERCPU_SYSTEM);
+ unsigned int cpus = Platform_getMaxCPU();
+ if (cpus == super->existingCPUs)
+ return;
+ if (cpus == 0)
+ cpus = super->activeCPUs;
+ if (cpus <= 1)
+ cpus = super->activeCPUs = 1;
+ super->existingCPUs = cpus;
+
+ free(this->percpu);
+ free(this->values);
+
+ this->percpu = xCalloc(cpus, sizeof(pmAtomValue*));
+ for (unsigned int i = 0; i < cpus; i++)
+ this->percpu[i] = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue));
+ this->values = xCalloc(cpus, sizeof(pmAtomValue));
+}
+
+static void PCPMachine_updateMemoryInfo(Machine* host) {
+ unsigned long long int freeMem = 0;
+ unsigned long long int swapFreeMem = 0;
+ unsigned long long int sreclaimableMem = 0;
+ host->totalMem = host->usedMem = host->cachedMem = 0;
+ host->usedSwap = host->totalSwap = host->sharedMem = 0;
+
+ pmAtomValue value;
+ if (Metric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL)
+ host->totalMem = value.ull;
+ if (Metric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL)
+ freeMem = value.ull;
+ if (Metric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL)
+ host->buffersMem = value.ull;
+ if (Metric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL)
+ sreclaimableMem = value.ull;
+ if (Metric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL)
+ host->sharedMem = value.ull;
+ if (Metric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL)
+ host->cachedMem = value.ull + sreclaimableMem - host->sharedMem;
+ const memory_t usedDiff = freeMem + host->cachedMem + sreclaimableMem + host->buffersMem;
+ host->usedMem = (host->totalMem >= usedDiff) ?
+ host->totalMem - usedDiff : host->totalMem - freeMem;
+ if (Metric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL)
+ host->availableMem = MINIMUM(value.ull, host->totalMem);
+ else
+ host->availableMem = freeMem;
+ if (Metric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL)
+ swapFreeMem = value.ull;
+ if (Metric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL)
+ host->totalSwap = value.ull;
+ if (Metric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL)
+ host->cachedSwap = value.ull;
+ host->usedSwap = host->totalSwap - swapFreeMem - host->cachedSwap;
+}
+
+/* make copies of previously sampled values to avoid overwrite */
+static inline void PCPMachine_backupCPUTime(pmAtomValue* values) {
+ /* the PERIOD fields (must) mirror the TIME fields */
+ for (int metric = CPU_TOTAL_TIME; metric < CPU_TOTAL_PERIOD; metric++) {
+ values[metric + CPU_TOTAL_PERIOD] = values[metric];
+ }
+}
+
+static inline void PCPMachine_saveCPUTimePeriod(pmAtomValue* values, CPUMetric previous, pmAtomValue* latest) {
+ pmAtomValue* value;
+
+ /* new value for period */
+ value = &values[previous];
+ if (latest->ull > value->ull)
+ value->ull = latest->ull - value->ull;
+ else
+ value->ull = 0;
+
+ /* new value for time */
+ value = &values[previous - CPU_TOTAL_PERIOD];
+ value->ull = latest->ull;
+}
+
+/* using copied sampled values and new values, calculate derivations */
+static void PCPMachine_deriveCPUTime(pmAtomValue* values) {
+
+ pmAtomValue* usertime = &values[CPU_USER_TIME];
+ pmAtomValue* guesttime = &values[CPU_GUEST_TIME];
+ usertime->ull -= guesttime->ull;
+
+ pmAtomValue* nicetime = &values[CPU_NICE_TIME];
+ pmAtomValue* guestnicetime = &values[CPU_GUESTNICE_TIME];
+ nicetime->ull -= guestnicetime->ull;
+
+ pmAtomValue* idletime = &values[CPU_IDLE_TIME];
+ pmAtomValue* iowaittime = &values[CPU_IOWAIT_TIME];
+ pmAtomValue* idlealltime = &values[CPU_IDLE_ALL_TIME];
+ idlealltime->ull = idletime->ull + iowaittime->ull;
+
+ pmAtomValue* systemtime = &values[CPU_SYSTEM_TIME];
+ pmAtomValue* irqtime = &values[CPU_IRQ_TIME];
+ pmAtomValue* softirqtime = &values[CPU_SOFTIRQ_TIME];
+ pmAtomValue* systalltime = &values[CPU_SYSTEM_ALL_TIME];
+ systalltime->ull = systemtime->ull + irqtime->ull + softirqtime->ull;
+
+ pmAtomValue* virtalltime = &values[CPU_GUEST_TIME];
+ virtalltime->ull = guesttime->ull + guestnicetime->ull;
+
+ pmAtomValue* stealtime = &values[CPU_STEAL_TIME];
+ pmAtomValue* totaltime = &values[CPU_TOTAL_TIME];
+ totaltime->ull = usertime->ull + nicetime->ull + systalltime->ull +
+ idlealltime->ull + stealtime->ull + virtalltime->ull;
+
+ PCPMachine_saveCPUTimePeriod(values, CPU_USER_PERIOD, usertime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_NICE_PERIOD, nicetime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_SYSTEM_PERIOD, systemtime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_SYSTEM_ALL_PERIOD, systalltime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_IDLE_ALL_PERIOD, idlealltime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_IDLE_PERIOD, idletime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_IOWAIT_PERIOD, iowaittime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_IRQ_PERIOD, irqtime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_SOFTIRQ_PERIOD, softirqtime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_STEAL_PERIOD, stealtime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_GUEST_PERIOD, virtalltime);
+ PCPMachine_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime);
+}
+
+static void PCPMachine_updateAllCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric)
+{
+ pmAtomValue* value = &this->cpu[cpumetric];
+ if (Metric_values(metric, value, 1, PM_TYPE_U64) == NULL)
+ memset(value, 0, sizeof(pmAtomValue));
+}
+
+static void PCPMachine_updatePerCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric)
+{
+ int cpus = this->super.existingCPUs;
+ if (Metric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL)
+ memset(this->values, 0, cpus * sizeof(pmAtomValue));
+ for (int i = 0; i < cpus; i++)
+ this->percpu[i][cpumetric].ull = this->values[i].ull;
+}
+
+static void PCPMachine_updatePerCPUReal(PCPMachine* this, Metric metric, CPUMetric cpumetric)
+{
+ int cpus = this->super.existingCPUs;
+ if (Metric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL)
+ memset(this->values, 0, cpus * sizeof(pmAtomValue));
+ for (int i = 0; i < cpus; i++)
+ this->percpu[i][cpumetric].d = this->values[i].d;
+}
+
+static inline void PCPMachine_scanZswapInfo(PCPMachine* this) {
+ pmAtomValue value;
+
+ memset(&this->zswap, 0, sizeof(ZswapStats));
+ if (Metric_values(PCP_MEM_ZSWAP, &value, 1, PM_TYPE_U64))
+ this->zswap.usedZswapComp = value.ull;
+ if (Metric_values(PCP_MEM_ZSWAPPED, &value, 1, PM_TYPE_U64))
+ this->zswap.usedZswapOrig = value.ull;
+}
+
+static inline void PCPMachine_scanZfsArcstats(PCPMachine* this) {
+ unsigned long long int dbufSize = 0;
+ unsigned long long int dnodeSize = 0;
+ unsigned long long int bonusSize = 0;
+ pmAtomValue value;
+
+ memset(&this->zfs, 0, sizeof(ZfsArcStats));
+ if (Metric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.anon = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64))
+ this->zfs.min = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64))
+ this->zfs.max = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64))
+ bonusSize = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_DBUF_SIZE, &value, 1, PM_TYPE_U64))
+ dbufSize = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_DNODE_SIZE, &value, 1, PM_TYPE_U64))
+ dnodeSize = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_COMPRESSED_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.compressed = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_UNCOMPRESSED_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.uncompressed = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_HDR_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.header = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_MFU_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.MFU = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_MRU_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.MRU = value.ull / ONE_K;
+ if (Metric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.size = value.ull / ONE_K;
+
+ this->zfs.other = (dbufSize + dnodeSize + bonusSize) / ONE_K;
+ this->zfs.enabled = (this->zfs.size > 0);
+ this->zfs.isCompressed = (this->zfs.compressed > 0);
+}
+
+static void PCPMachine_scan(PCPMachine* this) {
+ Machine* super = &this->super;
+
+ PCPMachine_updateMemoryInfo(super);
+ PCPMachine_updateCPUcount(this);
+
+ PCPMachine_backupCPUTime(this->cpu);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_USER, CPU_USER_TIME);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_NICE, CPU_NICE_TIME);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_SYSTEM, CPU_SYSTEM_TIME);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_IDLE, CPU_IDLE_TIME);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_IOWAIT, CPU_IOWAIT_TIME);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_IRQ, CPU_IRQ_TIME);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_SOFTIRQ, CPU_SOFTIRQ_TIME);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_STEAL, CPU_STEAL_TIME);
+ PCPMachine_updateAllCPUTime(this, PCP_CPU_GUEST, CPU_GUEST_TIME);
+ PCPMachine_deriveCPUTime(this->cpu);
+
+ for (unsigned int i = 0; i < super->existingCPUs; i++)
+ PCPMachine_backupCPUTime(this->percpu[i]);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_USER, CPU_USER_TIME);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_NICE, CPU_NICE_TIME);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_SYSTEM, CPU_SYSTEM_TIME);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IDLE, CPU_IDLE_TIME);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IOWAIT, CPU_IOWAIT_TIME);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IRQ, CPU_IRQ_TIME);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_SOFTIRQ, CPU_SOFTIRQ_TIME);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_STEAL, CPU_STEAL_TIME);
+ PCPMachine_updatePerCPUTime(this, PCP_PERCPU_GUEST, CPU_GUEST_TIME);
+ for (unsigned int i = 0; i < super->existingCPUs; i++)
+ PCPMachine_deriveCPUTime(this->percpu[i]);
+
+ if (super->settings->showCPUFrequency)
+ PCPMachine_updatePerCPUReal(this, PCP_HINV_CPUCLOCK, CPU_FREQUENCY);
+
+ PCPMachine_scanZfsArcstats(this);
+ PCPMachine_scanZswapInfo(this);
+}
+
+void Machine_scan(Machine* super) {
+ PCPMachine* host = (PCPMachine*) super;
+ const Settings* settings = super->settings;
+ uint32_t flags = settings->ss->flags;
+ bool flagged;
+
+ for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++)
+ Metric_enable(metric, true);
+
+ flagged = settings->showCPUFrequency;
+ Metric_enable(PCP_HINV_CPUCLOCK, flagged);
+ flagged = flags & PROCESS_FLAG_LINUX_CGROUP;
+ Metric_enable(PCP_PROC_CGROUPS, flagged);
+ flagged = flags & PROCESS_FLAG_LINUX_OOM;
+ Metric_enable(PCP_PROC_OOMSCORE, flagged);
+ flagged = flags & PROCESS_FLAG_LINUX_CTXT;
+ Metric_enable(PCP_PROC_VCTXSW, flagged);
+ Metric_enable(PCP_PROC_NVCTXSW, flagged);
+ flagged = flags & PROCESS_FLAG_LINUX_SECATTR;
+ Metric_enable(PCP_PROC_LABELS, flagged);
+ flagged = flags & PROCESS_FLAG_LINUX_AUTOGROUP;
+ Metric_enable(PCP_PROC_AUTOGROUP_ID, flagged);
+ Metric_enable(PCP_PROC_AUTOGROUP_NICE, flagged);
+
+ /* Sample smaps metrics on every second pass to improve performance */
+ host->smaps_flag = !!host->smaps_flag;
+ Metric_enable(PCP_PROC_SMAPS_PSS, host->smaps_flag);
+ Metric_enable(PCP_PROC_SMAPS_SWAP, host->smaps_flag);
+ Metric_enable(PCP_PROC_SMAPS_SWAPPSS, host->smaps_flag);
+
+ struct timeval timestamp;
+ if (Metric_fetch(&timestamp) != true)
+ return;
+
+ double sample = host->timestamp;
+ host->timestamp = pmtimevalToReal(&timestamp);
+ host->period = (host->timestamp - sample) * 100;
+
+ PCPMachine_scan(host);
+}
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ PCPMachine* this = xCalloc(1, sizeof(PCPMachine));
+ Machine* super = &this->super;
+
+ Machine_init(super, usersTable, userId);
+
+ struct timeval timestamp;
+ gettimeofday(&timestamp, NULL);
+ this->timestamp = pmtimevalToReal(&timestamp);
+
+ this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue));
+ PCPMachine_updateCPUcount(this);
+
+ Platform_updateTables(super);
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ PCPMachine* this = (PCPMachine*) super;
+ Machine_done(super);
+ free(this->values);
+ for (unsigned int i = 0; i < super->existingCPUs; i++)
+ free(this->percpu[i]);
+ free(this->percpu);
+ free(this->cpu);
+ free(this);
+}
+
+bool Machine_isCPUonline(const Machine* host, unsigned int id) {
+ assert(id < host->existingCPUs);
+ (void) host;
+
+ pmAtomValue value;
+ if (Metric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32))
+ return true;
+ return false;
+}
diff --git a/pcp/PCPProcessList.h b/pcp/PCPMachine.h
index a3a7372a..6518bd49 100644
--- a/pcp/PCPProcessList.h
+++ b/pcp/PCPMachine.h
@@ -1,22 +1,21 @@
-#ifndef HEADER_PCPProcessList
-#define HEADER_PCPProcessList
+#ifndef HEADER_PCPMachine
+#define HEADER_PCPMachine
/*
-htop - PCPProcessList.h
+htop - PCPMachine.h
(C) 2014 Hisham H. Muhammad
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <stdbool.h>
#include <sys/types.h>
#include "Hashtable.h"
-#include "ProcessList.h"
+#include "Machine.h"
#include "UsersTable.h"
#include "pcp/Platform.h"
+#include "linux/ZswapStats.h"
#include "zfs/ZfsArcStats.h"
@@ -54,21 +53,19 @@ typedef enum CPUMetric_ {
CPU_METRIC_COUNT
} CPUMetric;
-typedef struct PCPProcessList_ {
- ProcessList super;
+typedef struct PCPMachine_ {
+ Machine super;
+ int smaps_flag;
+ double period;
double timestamp; /* previous sample timestamp */
+
pmAtomValue* cpu; /* aggregate values for each metric */
pmAtomValue** percpu; /* per-processor values for each metric */
pmAtomValue* values; /* per-processor buffer for just one metric */
- ZfsArcStats zfs;
-} PCPProcessList;
-
-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);
+ ZfsArcStats zfs;
+ /*ZramStats zram; -- not needed, calculated in-line in Platform.c */
+ ZswapStats zswap;
+} PCPMachine;
#endif
diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c
index b8b87cab..e8cc1aad 100644
--- a/pcp/PCPProcess.c
+++ b/pcp/PCPProcess.c
@@ -7,6 +7,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 "pcp/PCPProcess.h"
#include <math.h>
@@ -49,12 +51,13 @@ const ProcessFieldData Process_fields[] = {
[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, },
[M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, .defaultSortDesc = true, },
+ [M_PRIV] = { .name = "M_PRIV", .title = " PRIV ", .description = "The private memory size of the process - resident set size minus shared memory", .flags = 0, .defaultSortDesc = true, },
[M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, .defaultSortDesc = true, },
[M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, },
[M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, },
[M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@@ -71,7 +74,9 @@ const ProcessFieldData Process_fields[] = {
[IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
- [CGROUP] = { .name = "CGROUP", .title = " CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, },
+ [CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw) ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, },
+ [CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed) ", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, },
+ [CONTAINER] = { .name = "CONTAINER", .title = "CONTAINER ", .description = "Name of the container the process is in (guessed by heuristics)", .flags = PROCESS_FLAG_LINUX_CGROUP, },
[OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, },
[PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = 0, .defaultSortDesc = true, },
[PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, },
@@ -88,73 +93,80 @@ const ProcessFieldData Process_fields[] = {
[AUTOGROUP_NICE] = { .name = "AUTOGROUP_NICE", .title = " ANI", .description = "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },
};
-Process* PCPProcess_new(const Settings* settings) {
+Process* PCPProcess_new(const Machine* host) {
PCPProcess* this = xCalloc(1, sizeof(PCPProcess));
Object_setClass(this, Class(PCPProcess));
- Process_init(&this->super, settings);
- return &this->super;
+ Process_init(&this->super, host);
+ return (Process*)this;
}
void Process_delete(Object* cast) {
PCPProcess* this = (PCPProcess*) cast;
Process_done((Process*)cast);
+ free(this->cgroup_short);
free(this->cgroup);
free(this->secattr);
free(this);
}
-static void PCPProcess_printDelay(float delay_percent, char* buffer, int n) {
- if (isnan(delay_percent)) {
- xSnprintf(buffer, n, " N/A ");
- } else {
+static void PCPProcess_printDelay(float delay_percent, char* buffer, size_t n) {
+ if (isNonnegative(delay_percent)) {
xSnprintf(buffer, n, "%4.1f ", delay_percent);
+ } else {
+ xSnprintf(buffer, n, " N/A ");
+ }
+}
+
+static double PCPProcess_totalIORate(const PCPProcess* pp) {
+ double totalRate = NAN;
+ if (isNonnegative(pp->io_rate_read_bps)) {
+ totalRate = pp->io_rate_read_bps;
+ if (isNonnegative(pp->io_rate_write_bps)) {
+ totalRate += pp->io_rate_write_bps;
+ }
+ } else if (isNonnegative(pp->io_rate_write_bps)) {
+ totalRate = pp->io_rate_write_bps;
}
+ return totalRate;
}
-static void PCPProcess_writeField(const Process* this, RichString* str, ProcessField field) {
- const PCPProcess* pp = (const PCPProcess*) this;
- bool coloring = this->settings->highlightMegabytes;
+static void PCPProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const PCPProcess* pp = (const PCPProcess*) super;
+
+ bool coloring = super->host->settings->highlightMegabytes;
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
- int n = sizeof(buffer) - 1;
+ size_t n = sizeof(buffer) - 1;
+
switch ((int)field) {
- case CMINFLT: Process_printCount(str, pp->cminflt, coloring); return;
- case CMAJFLT: Process_printCount(str, pp->cmajflt, coloring); return;
- case M_DRS: Process_printBytes(str, pp->m_drs, coloring); return;
- case M_DT: Process_printBytes(str, pp->m_dt, coloring); return;
- case M_LRS: Process_printBytes(str, pp->m_lrs, coloring); return;
- case M_TRS: Process_printBytes(str, pp->m_trs, coloring); return;
- case M_SHARE: Process_printBytes(str, pp->m_share, coloring); return;
- case M_PSS: Process_printKBytes(str, pp->m_pss, coloring); return;
- case M_SWAP: Process_printKBytes(str, pp->m_swap, coloring); return;
- case M_PSSWP: Process_printKBytes(str, pp->m_psswp, coloring); return;
- case UTIME: Process_printTime(str, pp->utime, coloring); return;
- case STIME: Process_printTime(str, pp->stime, coloring); return;
- case CUTIME: Process_printTime(str, pp->cutime, coloring); return;
- case CSTIME: Process_printTime(str, pp->cstime, coloring); return;
- case RCHAR: Process_printBytes(str, pp->io_rchar, coloring); return;
- case WCHAR: Process_printBytes(str, pp->io_wchar, coloring); return;
- case SYSCR: Process_printCount(str, pp->io_syscr, coloring); return;
- case SYSCW: Process_printCount(str, pp->io_syscw, coloring); return;
- case RBYTES: Process_printBytes(str, pp->io_read_bytes, coloring); return;
- case WBYTES: Process_printBytes(str, pp->io_write_bytes, coloring); return;
- case CNCLWB: Process_printBytes(str, pp->io_cancelled_write_bytes, coloring); return;
- case IO_READ_RATE: Process_printRate(str, pp->io_rate_read_bps, coloring); return;
- case IO_WRITE_RATE: Process_printRate(str, pp->io_rate_write_bps, coloring); return;
- case IO_RATE: {
- double totalRate = NAN;
- if (!isnan(pp->io_rate_read_bps) && !isnan(pp->io_rate_write_bps))
- totalRate = pp->io_rate_read_bps + pp->io_rate_write_bps;
- else if (!isnan(pp->io_rate_read_bps))
- totalRate = pp->io_rate_read_bps;
- else if (!isnan(pp->io_rate_write_bps))
- totalRate = pp->io_rate_write_bps;
- else
- totalRate = NAN;
- Process_printRate(str, totalRate, coloring);
- return;
- }
- case CGROUP: xSnprintf(buffer, n, "%-10s ", pp->cgroup ? pp->cgroup : ""); break;
+ case CMINFLT: Row_printCount(str, pp->cminflt, coloring); return;
+ case CMAJFLT: Row_printCount(str, pp->cmajflt, coloring); return;
+ case M_DRS: Row_printBytes(str, pp->m_drs, coloring); return;
+ case M_DT: Row_printBytes(str, pp->m_dt, coloring); return;
+ case M_LRS: Row_printBytes(str, pp->m_lrs, coloring); return;
+ case M_TRS: Row_printBytes(str, pp->m_trs, coloring); return;
+ case M_SHARE: Row_printBytes(str, pp->m_share, coloring); return;
+ case M_PRIV: Row_printBytes(str, pp->m_priv, coloring); return;
+ case M_PSS: Row_printKBytes(str, pp->m_pss, coloring); return;
+ case M_SWAP: Row_printKBytes(str, pp->m_swap, coloring); return;
+ case M_PSSWP: Row_printKBytes(str, pp->m_psswp, coloring); return;
+ case UTIME: Row_printTime(str, pp->utime, coloring); return;
+ case STIME: Row_printTime(str, pp->stime, coloring); return;
+ case CUTIME: Row_printTime(str, pp->cutime, coloring); return;
+ case CSTIME: Row_printTime(str, pp->cstime, coloring); return;
+ case RCHAR: Row_printBytes(str, pp->io_rchar, coloring); return;
+ case WCHAR: Row_printBytes(str, pp->io_wchar, coloring); return;
+ case SYSCR: Row_printCount(str, pp->io_syscr, coloring); return;
+ case SYSCW: Row_printCount(str, pp->io_syscw, coloring); return;
+ case RBYTES: Row_printBytes(str, pp->io_read_bytes, coloring); return;
+ case WBYTES: Row_printBytes(str, pp->io_write_bytes, coloring); return;
+ case CNCLWB: Row_printBytes(str, pp->io_cancelled_write_bytes, coloring); return;
+ case IO_READ_RATE: Row_printRate(str, pp->io_rate_read_bps, coloring); return;
+ case IO_WRITE_RATE: Row_printRate(str, pp->io_rate_write_bps, coloring); return;
+ case IO_RATE: Row_printRate(str, PCPProcess_totalIORate(pp), coloring); return;
+ case CGROUP: xSnprintf(buffer, n, "%-35.35s ", pp->cgroup ? pp->cgroup : "N/A"); break;
+ case CCGROUP: xSnprintf(buffer, n, "%-35.35s ", pp->cgroup_short ? pp->cgroup_short : (pp->cgroup ? pp->cgroup : "N/A")); break;
+ case CONTAINER: xSnprintf(buffer, n, "%-35.35s ", pp->container_short ? pp->container_short : "N/A"); break;
case OOM: xSnprintf(buffer, n, "%4u ", pp->oom); break;
case PERCENT_CPU_DELAY:
PCPProcess_printDelay(pp->cpu_delay_percent, buffer, n);
@@ -192,17 +204,11 @@ static void PCPProcess_writeField(const Process* this, RichString* str, ProcessF
}
break;
default:
- Process_writeField(this, str, field);
+ Process_writeField(&pp->super, str, field);
return;
}
- RichString_appendWide(str, attr, buffer);
-}
-static double adjustNaN(double num) {
- if (isnan(num))
- return -0.0005;
-
- return num;
+ RichString_appendWide(str, attr, buffer);
}
static int PCPProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
@@ -220,6 +226,8 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process
return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs);
case M_SHARE:
return SPACESHIP_NUMBER(p1->m_share, p2->m_share);
+ case M_PRIV:
+ return SPACESHIP_NUMBER(p1->m_priv, p2->m_priv);
case M_PSS:
return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss);
case M_SWAP:
@@ -249,21 +257,25 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process
case CNCLWB:
return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes);
case IO_READ_RATE:
- return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps), adjustNaN(p2->io_rate_read_bps));
+ return compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps);
case IO_WRITE_RATE:
- return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_write_bps));
+ return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps);
case IO_RATE:
- return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps) + adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_read_bps) + adjustNaN(p2->io_rate_write_bps));
+ return compareRealNumbers(PCPProcess_totalIORate(p1), PCPProcess_totalIORate(p2));
case CGROUP:
return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);
+ case CCGROUP:
+ return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short);
+ case CONTAINER:
+ return SPACESHIP_NULLSTR(p1->container_short, p2->container_short);
case OOM:
return SPACESHIP_NUMBER(p1->oom, p2->oom);
case PERCENT_CPU_DELAY:
- return SPACESHIP_NUMBER(p1->cpu_delay_percent, p2->cpu_delay_percent);
+ return compareRealNumbers(p1->cpu_delay_percent, p2->cpu_delay_percent);
case PERCENT_IO_DELAY:
- return SPACESHIP_NUMBER(p1->blkio_delay_percent, p2->blkio_delay_percent);
+ return compareRealNumbers(p1->blkio_delay_percent, p2->blkio_delay_percent);
case PERCENT_SWAP_DELAY:
- return SPACESHIP_NUMBER(p1->swapin_delay_percent, p2->swapin_delay_percent);
+ return compareRealNumbers(p1->swapin_delay_percent, p2->swapin_delay_percent);
case CTXT:
return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff);
case SECATTR:
@@ -281,11 +293,18 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process
const ProcessClass PCPProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = PCPProcess_rowWriteField,
},
- .writeField = PCPProcess_writeField,
- .compareByKey = PCPProcess_compareByKey
+ .compareByKey = PCPProcess_compareByKey,
};
diff --git a/pcp/PCPProcess.h b/pcp/PCPProcess.h
index 46ba07fe..e9537571 100644
--- a/pcp/PCPProcess.h
+++ b/pcp/PCPProcess.h
@@ -9,13 +9,11 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <stdbool.h>
+#include "Machine.h"
#include "Object.h"
#include "Process.h"
-#include "Settings.h"
#define PROCESS_FLAG_LINUX_CGROUP 0x00000800
@@ -38,6 +36,7 @@ typedef struct PCPProcess_ {
unsigned long long int cutime;
unsigned long long int cstime;
long m_share;
+ long m_priv;
long m_pss;
long m_swap;
long m_psswp;
@@ -73,6 +72,8 @@ typedef struct PCPProcess_ {
double io_rate_read_bps;
double io_rate_write_bps;
char* cgroup;
+ char* cgroup_short;
+ char* container_short;
long int autogroup_id;
int autogroup_nice;
unsigned int oom;
@@ -93,7 +94,7 @@ extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
extern const ProcessClass PCPProcess_class;
-Process* PCPProcess_new(const Settings* settings);
+Process* PCPProcess_new(const Machine* host);
void Process_delete(Object* cast);
diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c
deleted file mode 100644
index 045f7ae0..00000000
--- a/pcp/PCPProcessList.c
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
-htop - PCPProcessList.c
-(C) 2014 Hisham H. Muhammad
-(C) 2020-2021 htop dev team
-(C) 2020-2021 Red Hat, Inc.
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "config.h" // IWYU pragma: keep
-
-#include "pcp/PCPProcessList.h"
-
-#include <assert.h>
-#include <limits.h>
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/time.h>
-
-#include "Macros.h"
-#include "Object.h"
-#include "Platform.h"
-#include "Process.h"
-#include "Settings.h"
-#include "XUtils.h"
-
-#include "pcp/PCPMetric.h"
-#include "pcp/PCPProcess.h"
-
-
-static void PCPProcessList_updateCPUcount(PCPProcessList* this) {
- ProcessList* pl = &(this->super);
- pl->activeCPUs = PCPMetric_instanceCount(PCP_PERCPU_SYSTEM);
- unsigned int cpus = Platform_getMaxCPU();
- if (cpus == pl->existingCPUs)
- return;
- if (cpus == 0)
- cpus = pl->activeCPUs;
- if (cpus <= 1)
- cpus = pl->activeCPUs = 1;
- pl->existingCPUs = cpus;
-
- free(this->percpu);
- free(this->values);
-
- this->percpu = xCalloc(cpus, sizeof(pmAtomValue *));
- for (unsigned int i = 0; i < cpus; i++)
- this->percpu[i] = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue));
- this->values = xCalloc(cpus, sizeof(pmAtomValue));
-}
-
-static char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) {
- char* name = Hashtable_get(this->users, uid);
- if (name)
- return name;
-
- pmAtomValue value;
- if (PCPMetric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) {
- Hashtable_put(this->users, uid, value.cp);
- name = value.cp;
- }
- return name;
-}
-
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
- PCPProcessList* this = xCalloc(1, sizeof(PCPProcessList));
- ProcessList* super = &(this->super);
-
- ProcessList_init(super, Class(PCPProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
-
- struct timeval timestamp;
- gettimeofday(&timestamp, NULL);
- this->timestamp = pmtimevalToReal(&timestamp);
-
- this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue));
- PCPProcessList_updateCPUcount(this);
-
- return super;
-}
-
-void ProcessList_delete(ProcessList* pl) {
- PCPProcessList* this = (PCPProcessList*) pl;
- ProcessList_done(pl);
- free(this->values);
- for (unsigned int i = 0; i < pl->existingCPUs; i++)
- free(this->percpu[i]);
- free(this->percpu);
- free(this->cpu);
- free(this);
-}
-
-static inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) {
- pmAtomValue value;
- if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_32))
- return value.l;
- return fallback;
-}
-
-static inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) {
- pmAtomValue value;
- if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_64))
- return value.l;
- return fallback;
-}
-
-static inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) {
- pmAtomValue value;
- if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U32))
- return value.ul;
- return fallback;
-}
-
-static inline unsigned long long Metric_instance_u64(int metric, int pid, int offset, unsigned long long fallback) {
- pmAtomValue value;
- if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64))
- return value.ull;
- return fallback;
-}
-
-static inline unsigned long long Metric_instance_time(int metric, int pid, int offset) {
- pmAtomValue value;
- if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64))
- return value.ull / 10;
- return 0;
-}
-
-static inline unsigned long long Metric_instance_ONE_K(int metric, int pid, int offset) {
- pmAtomValue value;
- if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64))
- return value.ull / ONE_K;
- return ULLONG_MAX;
-}
-
-static inline char Metric_instance_char(int metric, int pid, int offset, char fallback) {
- pmAtomValue value;
- if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) {
- char uchar = value.cp[0];
- free(value.cp);
- return uchar;
- }
- return fallback;
-}
-
-static inline ProcessState PCPProcessList_getProcessState(char state) {
- switch (state) {
- case '?': return UNKNOWN;
- case 'R': return RUNNING;
- case 'W': return WAITING;
- case 'D': return UNINTERRUPTIBLE_WAIT;
- case 'P': return PAGING;
- case 'T': return STOPPED;
- case 't': return TRACED;
- case 'Z': return ZOMBIE;
- case 'X': return DEFUNCT;
- case 'I': return IDLE;
- case 'S': return SLEEPING;
- default: return UNKNOWN;
- }
-}
-
-static void PCPProcessList_updateID(Process* process, int pid, int offset) {
- process->tgid = Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1);
- process->ppid = Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1);
- process->state = PCPProcessList_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?'));
-}
-
-static void PCPProcessList_updateInfo(Process* process, int pid, int offset, char* command, size_t commLen) {
- PCPProcess* pp = (PCPProcess*) process;
- pmAtomValue value;
-
- if (!PCPMetric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING))
- value.cp = xStrdup("<unknown>");
- String_safeStrncpy(command, value.cp, commLen);
- free(value.cp);
-
- process->pgrp = Metric_instance_u32(PCP_PROC_PGRP, pid, offset, 0);
- process->session = Metric_instance_u32(PCP_PROC_SESSION, pid, offset, 0);
- process->tty_nr = Metric_instance_u32(PCP_PROC_TTY, pid, offset, 0);
- process->tpgid = Metric_instance_u32(PCP_PROC_TTYPGRP, pid, offset, 0);
- process->minflt = Metric_instance_u32(PCP_PROC_MINFLT, pid, offset, 0);
- pp->cminflt = Metric_instance_u32(PCP_PROC_CMINFLT, pid, offset, 0);
- process->majflt = Metric_instance_u32(PCP_PROC_MAJFLT, pid, offset, 0);
- pp->cmajflt = Metric_instance_u32(PCP_PROC_CMAJFLT, pid, offset, 0);
- pp->utime = Metric_instance_time(PCP_PROC_UTIME, pid, offset);
- pp->stime = Metric_instance_time(PCP_PROC_STIME, pid, offset);
- pp->cutime = Metric_instance_time(PCP_PROC_CUTIME, pid, offset);
- pp->cstime = Metric_instance_time(PCP_PROC_CSTIME, pid, offset);
- process->priority = Metric_instance_u32(PCP_PROC_PRIORITY, pid, offset, 0);
- process->nice = Metric_instance_s32(PCP_PROC_NICE, pid, offset, 0);
- process->nlwp = Metric_instance_u32(PCP_PROC_THREADS, pid, offset, 0);
- process->starttime_ctime = Metric_instance_time(PCP_PROC_STARTTIME, pid, offset);
- process->processor = Metric_instance_u32(PCP_PROC_PROCESSOR, pid, offset, 0);
-
- process->time = pp->utime + pp->stime;
-}
-
-static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigned long long now) {
- pmAtomValue value;
-
- pp->io_rchar = Metric_instance_ONE_K(PCP_PROC_IO_RCHAR, pid, offset);
- pp->io_wchar = Metric_instance_ONE_K(PCP_PROC_IO_WCHAR, pid, offset);
- pp->io_syscr = Metric_instance_u64(PCP_PROC_IO_SYSCR, pid, offset, ULLONG_MAX);
- pp->io_syscw = Metric_instance_u64(PCP_PROC_IO_SYSCW, pid, offset, ULLONG_MAX);
- pp->io_cancelled_write_bytes = Metric_instance_ONE_K(PCP_PROC_IO_CANCELLED, pid, offset);
-
- if (PCPMetric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) {
- unsigned long long last_read = pp->io_read_bytes;
- pp->io_read_bytes = value.ull / ONE_K;
- pp->io_rate_read_bps = ONE_K * (pp->io_read_bytes - last_read) /
- (now - pp->io_last_scan_time);
- } else {
- pp->io_read_bytes = ULLONG_MAX;
- pp->io_rate_read_bps = NAN;
- }
-
- if (PCPMetric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) {
- unsigned long long last_write = pp->io_write_bytes;
- pp->io_write_bytes = value.ull;
- pp->io_rate_write_bps = ONE_K * (pp->io_write_bytes - last_write) /
- (now - pp->io_last_scan_time);
- } else {
- pp->io_write_bytes = ULLONG_MAX;
- pp->io_rate_write_bps = NAN;
- }
-
- pp->io_last_scan_time = now;
-}
-
-static void PCPProcessList_updateMemory(PCPProcess* pp, int pid, int offset) {
- pp->super.m_virt = Metric_instance_u32(PCP_PROC_MEM_SIZE, pid, offset, 0);
- pp->super.m_resident = Metric_instance_u32(PCP_PROC_MEM_RSS, pid, offset, 0);
- pp->m_share = Metric_instance_u32(PCP_PROC_MEM_SHARE, pid, offset, 0);
- pp->m_trs = Metric_instance_u32(PCP_PROC_MEM_TEXTRS, pid, offset, 0);
- pp->m_lrs = Metric_instance_u32(PCP_PROC_MEM_LIBRS, pid, offset, 0);
- pp->m_drs = Metric_instance_u32(PCP_PROC_MEM_DATRS, pid, offset, 0);
- pp->m_dt = Metric_instance_u32(PCP_PROC_MEM_DIRTY, pid, offset, 0);
-}
-
-static void PCPProcessList_updateSmaps(PCPProcess* pp, pid_t pid, int offset) {
- pp->m_pss = Metric_instance_u64(PCP_PROC_SMAPS_PSS, pid, offset, 0);
- pp->m_swap = Metric_instance_u64(PCP_PROC_SMAPS_SWAP, pid, offset, 0);
- pp->m_psswp = Metric_instance_u64(PCP_PROC_SMAPS_SWAPPSS, pid, offset, 0);
-}
-
-static void PCPProcessList_readOomData(PCPProcess* pp, int pid, int offset) {
- pp->oom = Metric_instance_u32(PCP_PROC_OOMSCORE, pid, offset, 0);
-}
-
-static void PCPProcessList_readAutogroup(PCPProcess* pp, int pid, int offset) {
- pp->autogroup_id = Metric_instance_s64(PCP_PROC_AUTOGROUP_ID, pid, offset, -1);
- pp->autogroup_nice = Metric_instance_s32(PCP_PROC_AUTOGROUP_NICE, pid, offset, 0);
-}
-
-static void PCPProcessList_readCtxtData(PCPProcess* pp, int pid, int offset) {
- pmAtomValue value;
- unsigned long ctxt = 0;
-
- if (PCPMetric_instance(PCP_PROC_VCTXSW, pid, offset, &value, PM_TYPE_U32))
- ctxt += value.ul;
- if (PCPMetric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32))
- ctxt += value.ul;
-
- pp->ctxt_diff = ctxt > pp->ctxt_total ? ctxt - pp->ctxt_total : 0;
- pp->ctxt_total = ctxt;
-}
-
-static char* setString(PCPMetric metric, int pid, int offset, char* string) {
- if (string)
- free(string);
- pmAtomValue value;
- if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING))
- string = value.cp;
- else
- string = NULL;
- return string;
-}
-
-static void PCPProcessList_updateTTY(Process* process, int pid, int offset) {
- process->tty_name = setString(PCP_PROC_TTYNAME, pid, offset, process->tty_name);
-}
-
-static void PCPProcessList_readCGroups(PCPProcess* pp, int pid, int offset) {
- pp->cgroup = setString(PCP_PROC_CGROUPS, pid, offset, pp->cgroup);
-}
-
-static void PCPProcessList_readSecattrData(PCPProcess* pp, int pid, int offset) {
- pp->secattr = setString(PCP_PROC_LABELS, pid, offset, pp->secattr);
-}
-
-static void PCPProcessList_readCwd(PCPProcess* pp, int pid, int offset) {
- pp->super.procCwd = setString(PCP_PROC_CWD, pid, offset, pp->super.procCwd);
-}
-
-static void PCPProcessList_updateUsername(Process* process, int pid, int offset, UsersTable* users) {
- process->st_uid = Metric_instance_u32(PCP_PROC_ID_UID, pid, offset, 0);
- process->user = setUser(users, process->st_uid, pid, offset);
-}
-
-static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, const char* comm) {
- pmAtomValue value;
- if (!PCPMetric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) {
- if (process->state != ZOMBIE)
- process->isKernelThread = true;
- Process_updateCmdline(process, NULL, 0, 0);
- return;
- }
-
- char* command = value.cp;
- int length = strlen(command);
- if (command[0] != '(') {
- process->isKernelThread = false;
- } else {
- ++command;
- --length;
- if (command[length - 1] == ')')
- command[--length] = '\0';
- process->isKernelThread = true;
- }
-
- int tokenStart = 0;
- for (int i = 0; i < length; i++) {
- /* htop considers the next character after the last / that is before
- * basenameOffset, as the start of the basename in cmdline - see
- * Process_writeCommand */
- if (command[i] == '/')
- tokenStart = i + 1;
- }
- int tokenEnd = length;
-
- Process_updateCmdline(process, command, tokenStart, tokenEnd);
- free(value.cp);
-
- Process_updateComm(process, comm);
-
- if (PCPMetric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) {
- Process_updateExe(process, value.cp[0] ? value.cp : NULL);
- free(value.cp);
- }
-}
-
-static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, struct timeval* tv) {
- ProcessList* pl = (ProcessList*) this;
- const Settings* settings = pl->settings;
-
- bool hideKernelThreads = settings->hideKernelThreads;
- bool hideUserlandThreads = settings->hideUserlandThreads;
-
- unsigned long long now = tv->tv_sec * 1000LL + tv->tv_usec / 1000LL;
- int pid = -1, offset = -1;
-
- /* for every process ... */
- while (PCPMetric_iterate(PCP_PROC_PID, &pid, &offset)) {
-
- bool preExisting;
- Process* proc = ProcessList_getProcess(pl, pid, &preExisting, PCPProcess_new);
- PCPProcess* pp = (PCPProcess*) proc;
- PCPProcessList_updateID(proc, pid, offset);
- proc->isUserlandThread = proc->pid != proc->tgid;
- pp->offset = offset >= 0 ? offset : 0;
-
- /*
- * These conditions will not trigger on first occurrence, cause we need to
- * add the process to the ProcessList and do all one time scans
- * (e.g. parsing the cmdline to detect a kernel thread)
- * But it will short-circuit subsequent scans.
- */
- if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {
- proc->updated = true;
- proc->show = false;
- if (proc->state == RUNNING)
- pl->runningTasks++;
- pl->kernelThreads++;
- pl->totalTasks++;
- continue;
- }
- if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
- proc->updated = true;
- proc->show = false;
- if (proc->state == RUNNING)
- pl->runningTasks++;
- pl->userlandThreads++;
- pl->totalTasks++;
- continue;
- }
-
- if (settings->ss->flags & PROCESS_FLAG_IO)
- PCPProcessList_updateIO(pp, pid, offset, now);
-
- PCPProcessList_updateMemory(pp, pid, offset);
-
- if ((settings->ss->flags & PROCESS_FLAG_LINUX_SMAPS) &&
- (Process_isKernelThread(proc) == false)) {
- if (PCPMetric_enabled(PCP_PROC_SMAPS_PSS))
- PCPProcessList_updateSmaps(pp, pid, offset);
- }
-
- char command[MAX_NAME + 1];
- unsigned int tty_nr = proc->tty_nr;
- unsigned long long int lasttimes = pp->utime + pp->stime;
-
- PCPProcessList_updateInfo(proc, pid, offset, command, sizeof(command));
- proc->starttime_ctime += Platform_getBootTime();
- if (tty_nr != proc->tty_nr)
- PCPProcessList_updateTTY(proc, pid, offset);
-
- float percent_cpu = (pp->utime + pp->stime - lasttimes) / period * 100.0;
- proc->percent_cpu = isnan(percent_cpu) ?
- 0.0 : CLAMP(percent_cpu, 0.0, pl->activeCPUs * 100.0);
- proc->percent_mem = proc->m_resident / (double)pl->totalMem * 100.0;
- Process_updateCPUFieldWidths(proc->percent_cpu);
-
- PCPProcessList_updateUsername(proc, pid, offset, pl->usersTable);
-
- if (!preExisting) {
- PCPProcessList_updateCmdline(proc, pid, offset, command);
- Process_fillStarttimeBuffer(proc);
- ProcessList_add(pl, proc);
- } else if (settings->updateProcessNames && proc->state != ZOMBIE) {
- PCPProcessList_updateCmdline(proc, pid, offset, command);
- }
-
- if (settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP)
- PCPProcessList_readCGroups(pp, pid, offset);
-
- if (settings->ss->flags & PROCESS_FLAG_LINUX_OOM)
- PCPProcessList_readOomData(pp, pid, offset);
-
- if (settings->ss->flags & PROCESS_FLAG_LINUX_CTXT)
- PCPProcessList_readCtxtData(pp, pid, offset);
-
- if (settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR)
- PCPProcessList_readSecattrData(pp, pid, offset);
-
- if (settings->ss->flags & PROCESS_FLAG_CWD)
- PCPProcessList_readCwd(pp, pid, offset);
-
- if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP)
- PCPProcessList_readAutogroup(pp, pid, offset);
-
- if (proc->state == ZOMBIE && !proc->cmdline && command[0]) {
- Process_updateCmdline(proc, command, 0, strlen(command));
- } else if (Process_isThread(proc)) {
- if ((settings->showThreadNames || Process_isKernelThread(proc)) && command[0]) {
- Process_updateCmdline(proc, command, 0, strlen(command));
- }
-
- if (Process_isKernelThread(proc)) {
- pl->kernelThreads++;
- } else {
- pl->userlandThreads++;
- }
- }
-
- /* Set at the end when we know if a new entry is a thread */
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) ||
- (hideUserlandThreads && Process_isUserlandThread(proc)));
-
- pl->totalTasks++;
- if (proc->state == RUNNING)
- pl->runningTasks++;
- proc->updated = true;
- }
- return true;
-}
-
-static void PCPProcessList_updateMemoryInfo(ProcessList* super) {
- unsigned long long int freeMem = 0;
- unsigned long long int swapFreeMem = 0;
- unsigned long long int sreclaimableMem = 0;
- super->totalMem = super->usedMem = super->cachedMem = 0;
- super->usedSwap = super->totalSwap = super->sharedMem = 0;
-
- pmAtomValue value;
- if (PCPMetric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL)
- super->totalMem = value.ull;
- if (PCPMetric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL)
- freeMem = value.ull;
- if (PCPMetric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL)
- super->buffersMem = value.ull;
- if (PCPMetric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL)
- sreclaimableMem = value.ull;
- if (PCPMetric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL)
- super->sharedMem = value.ull;
- if (PCPMetric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL)
- super->cachedMem = value.ull + sreclaimableMem - super->sharedMem;
- const memory_t usedDiff = freeMem + super->cachedMem + sreclaimableMem + super->buffersMem;
- super->usedMem = (super->totalMem >= usedDiff) ?
- super->totalMem - usedDiff : super->totalMem - freeMem;
- if (PCPMetric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL)
- super->availableMem = MINIMUM(value.ull, super->totalMem);
- else
- super->availableMem = freeMem;
- if (PCPMetric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL)
- swapFreeMem = value.ull;
- if (PCPMetric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL)
- super->totalSwap = value.ull;
- if (PCPMetric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL)
- super->cachedSwap = value.ull;
- super->usedSwap = super->totalSwap - swapFreeMem - super->cachedSwap;
-}
-
-/* make copies of previously sampled values to avoid overwrite */
-static inline void PCPProcessList_backupCPUTime(pmAtomValue* values) {
- /* the PERIOD fields (must) mirror the TIME fields */
- for (int metric = CPU_TOTAL_TIME; metric < CPU_TOTAL_PERIOD; metric++) {
- values[metric + CPU_TOTAL_PERIOD] = values[metric];
- }
-}
-
-static inline void PCPProcessList_saveCPUTimePeriod(pmAtomValue* values, CPUMetric previous, pmAtomValue* latest) {
- pmAtomValue* value;
-
- /* new value for period */
- value = &values[previous];
- if (latest->ull > value->ull)
- value->ull = latest->ull - value->ull;
- else
- value->ull = 0;
-
- /* new value for time */
- value = &values[previous - CPU_TOTAL_PERIOD];
- value->ull = latest->ull;
-}
-
-/* using copied sampled values and new values, calculate derivations */
-static void PCPProcessList_deriveCPUTime(pmAtomValue* values) {
-
- pmAtomValue* usertime = &values[CPU_USER_TIME];
- pmAtomValue* guesttime = &values[CPU_GUEST_TIME];
- usertime->ull -= guesttime->ull;
-
- pmAtomValue* nicetime = &values[CPU_NICE_TIME];
- pmAtomValue* guestnicetime = &values[CPU_GUESTNICE_TIME];
- nicetime->ull -= guestnicetime->ull;
-
- pmAtomValue* idletime = &values[CPU_IDLE_TIME];
- pmAtomValue* iowaittime = &values[CPU_IOWAIT_TIME];
- pmAtomValue* idlealltime = &values[CPU_IDLE_ALL_TIME];
- idlealltime->ull = idletime->ull + iowaittime->ull;
-
- pmAtomValue* systemtime = &values[CPU_SYSTEM_TIME];
- pmAtomValue* irqtime = &values[CPU_IRQ_TIME];
- pmAtomValue* softirqtime = &values[CPU_SOFTIRQ_TIME];
- pmAtomValue* systalltime = &values[CPU_SYSTEM_ALL_TIME];
- systalltime->ull = systemtime->ull + irqtime->ull + softirqtime->ull;
-
- pmAtomValue* virtalltime = &values[CPU_GUEST_TIME];
- virtalltime->ull = guesttime->ull + guestnicetime->ull;
-
- pmAtomValue* stealtime = &values[CPU_STEAL_TIME];
- pmAtomValue* totaltime = &values[CPU_TOTAL_TIME];
- totaltime->ull = usertime->ull + nicetime->ull + systalltime->ull +
- idlealltime->ull + stealtime->ull + virtalltime->ull;
-
- PCPProcessList_saveCPUTimePeriod(values, CPU_USER_PERIOD, usertime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_NICE_PERIOD, nicetime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_SYSTEM_PERIOD, systemtime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_SYSTEM_ALL_PERIOD, systalltime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_IDLE_ALL_PERIOD, idlealltime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_IDLE_PERIOD, idletime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_IOWAIT_PERIOD, iowaittime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_IRQ_PERIOD, irqtime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_SOFTIRQ_PERIOD, softirqtime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_STEAL_PERIOD, stealtime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_GUEST_PERIOD, virtalltime);
- PCPProcessList_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime);
-}
-
-static void PCPProcessList_updateAllCPUTime(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric)
-{
- pmAtomValue* value = &this->cpu[cpumetric];
- if (PCPMetric_values(metric, value, 1, PM_TYPE_U64) == NULL)
- memset(&value, 0, sizeof(pmAtomValue));
-}
-
-static void PCPProcessList_updatePerCPUTime(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric)
-{
- int cpus = this->super.existingCPUs;
- if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL)
- memset(this->values, 0, cpus * sizeof(pmAtomValue));
- for (int i = 0; i < cpus; i++)
- this->percpu[i][cpumetric].ull = this->values[i].ull;
-}
-
-static void PCPProcessList_updatePerCPUReal(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric)
-{
- int cpus = this->super.existingCPUs;
- if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL)
- memset(this->values, 0, cpus * sizeof(pmAtomValue));
- for (int i = 0; i < cpus; i++)
- this->percpu[i][cpumetric].d = this->values[i].d;
-}
-
-static inline void PCPProcessList_scanZfsArcstats(PCPProcessList* this) {
- unsigned long long int dbufSize = 0;
- unsigned long long int dnodeSize = 0;
- unsigned long long int bonusSize = 0;
- pmAtomValue value;
-
- memset(&this->zfs, 0, sizeof(ZfsArcStats));
- if (PCPMetric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64))
- this->zfs.anon = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64))
- this->zfs.min = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64))
- this->zfs.max = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64))
- bonusSize = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_DBUF_SIZE, &value, 1, PM_TYPE_U64))
- dbufSize = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_DNODE_SIZE, &value, 1, PM_TYPE_U64))
- dnodeSize = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_COMPRESSED_SIZE, &value, 1, PM_TYPE_U64))
- this->zfs.compressed = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_UNCOMPRESSED_SIZE, &value, 1, PM_TYPE_U64))
- this->zfs.uncompressed = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_HDR_SIZE, &value, 1, PM_TYPE_U64))
- this->zfs.header = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_MFU_SIZE, &value, 1, PM_TYPE_U64))
- this->zfs.MFU = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_MRU_SIZE, &value, 1, PM_TYPE_U64))
- this->zfs.MRU = value.ull / ONE_K;
- if (PCPMetric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64))
- this->zfs.size = value.ull / ONE_K;
-
- this->zfs.other = (dbufSize + dnodeSize + bonusSize) / ONE_K;
- this->zfs.enabled = (this->zfs.size > 0);
- this->zfs.isCompressed = (this->zfs.compressed > 0);
-}
-
-static void PCPProcessList_updateHeader(ProcessList* super, const Settings* settings) {
- PCPProcessList_updateMemoryInfo(super);
-
- PCPProcessList* this = (PCPProcessList*) super;
- PCPProcessList_updateCPUcount(this);
-
- PCPProcessList_backupCPUTime(this->cpu);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_USER, CPU_USER_TIME);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_NICE, CPU_NICE_TIME);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_SYSTEM, CPU_SYSTEM_TIME);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_IDLE, CPU_IDLE_TIME);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_IOWAIT, CPU_IOWAIT_TIME);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_IRQ, CPU_IRQ_TIME);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_SOFTIRQ, CPU_SOFTIRQ_TIME);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_STEAL, CPU_STEAL_TIME);
- PCPProcessList_updateAllCPUTime(this, PCP_CPU_GUEST, CPU_GUEST_TIME);
- PCPProcessList_deriveCPUTime(this->cpu);
-
- for (unsigned int i = 0; i < super->existingCPUs; i++)
- PCPProcessList_backupCPUTime(this->percpu[i]);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_USER, CPU_USER_TIME);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_NICE, CPU_NICE_TIME);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_SYSTEM, CPU_SYSTEM_TIME);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IDLE, CPU_IDLE_TIME);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IOWAIT, CPU_IOWAIT_TIME);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IRQ, CPU_IRQ_TIME);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_SOFTIRQ, CPU_SOFTIRQ_TIME);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_STEAL, CPU_STEAL_TIME);
- PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_GUEST, CPU_GUEST_TIME);
- for (unsigned int i = 0; i < super->existingCPUs; i++)
- PCPProcessList_deriveCPUTime(this->percpu[i]);
-
- if (settings->showCPUFrequency)
- PCPProcessList_updatePerCPUReal(this, PCP_HINV_CPUCLOCK, CPU_FREQUENCY);
-
- PCPProcessList_scanZfsArcstats(this);
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
- PCPProcessList* this = (PCPProcessList*) super;
- const Settings* settings = super->settings;
- bool enabled = !pauseProcessUpdate;
-
- bool flagged = settings->showCPUFrequency;
- PCPMetric_enable(PCP_HINV_CPUCLOCK, flagged);
-
- /* In pause mode do not sample per-process metric values at all */
- for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++)
- PCPMetric_enable(metric, enabled);
-
- flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP;
- PCPMetric_enable(PCP_PROC_CGROUPS, flagged && enabled);
- flagged = settings->ss->flags & PROCESS_FLAG_LINUX_OOM;
- PCPMetric_enable(PCP_PROC_OOMSCORE, flagged && enabled);
- flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CTXT;
- PCPMetric_enable(PCP_PROC_VCTXSW, flagged && enabled);
- PCPMetric_enable(PCP_PROC_NVCTXSW, flagged && enabled);
- flagged = settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR;
- PCPMetric_enable(PCP_PROC_LABELS, flagged && enabled);
- flagged = settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP;
- PCPMetric_enable(PCP_PROC_AUTOGROUP_ID, flagged && enabled);
- PCPMetric_enable(PCP_PROC_AUTOGROUP_NICE, flagged && enabled);
-
- /* Sample smaps metrics on every second pass to improve performance */
- static int smaps_flag;
- smaps_flag = !!smaps_flag;
- PCPMetric_enable(PCP_PROC_SMAPS_PSS, smaps_flag && enabled);
- PCPMetric_enable(PCP_PROC_SMAPS_SWAP, smaps_flag && enabled);
- PCPMetric_enable(PCP_PROC_SMAPS_SWAPPSS, smaps_flag && enabled);
-
- struct timeval timestamp;
- if (PCPMetric_fetch(&timestamp) != true)
- return;
-
- double sample = this->timestamp;
- this->timestamp = pmtimevalToReal(&timestamp);
-
- PCPProcessList_updateHeader(super, settings);
-
- /* In pause mode only update global data for meters (CPU, memory, etc) */
- if (pauseProcessUpdate)
- return;
-
- double period = (this->timestamp - sample) * 100;
- PCPProcessList_updateProcesses(this, period, &timestamp);
-}
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
- assert(id < super->existingCPUs);
- (void) super;
-
- pmAtomValue value;
- if (PCPMetric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32))
- return true;
- return false;
-}
diff --git a/pcp/PCPProcessTable.c b/pcp/PCPProcessTable.c
new file mode 100644
index 00000000..4999bdc2
--- /dev/null
+++ b/pcp/PCPProcessTable.c
@@ -0,0 +1,486 @@
+/*
+htop - PCPProcessTable.c
+(C) 2014 Hisham H. Muhammad
+(C) 2020-2021 htop dev team
+(C) 2020-2021 Red Hat, Inc.
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "pcp/PCPProcessTable.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "Machine.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Platform.h"
+#include "Process.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+#include "linux/CGroupUtils.h"
+#include "pcp/Metric.h"
+#include "pcp/PCPMachine.h"
+#include "pcp/PCPProcess.h"
+
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ PCPProcessTable* this = xCalloc(1, sizeof(PCPProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(PCPProcess), host, pidMatchList);
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ PCPProcessTable* this = (PCPProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ free(this);
+}
+
+static inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) {
+ pmAtomValue value;
+ if (Metric_instance(metric, pid, offset, &value, PM_TYPE_32))
+ return value.l;
+ return fallback;
+}
+
+static inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) {
+ pmAtomValue value;
+ if (Metric_instance(metric, pid, offset, &value, PM_TYPE_64))
+ return value.l;
+ return fallback;
+}
+
+static inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) {
+ pmAtomValue value;
+ if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U32))
+ return value.ul;
+ return fallback;
+}
+
+static inline unsigned long long Metric_instance_u64(int metric, int pid, int offset, unsigned long long fallback) {
+ pmAtomValue value;
+ if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64))
+ return value.ull;
+ return fallback;
+}
+
+static inline unsigned long long Metric_instance_time(int metric, int pid, int offset) {
+ pmAtomValue value;
+ if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64))
+ return value.ull / 10;
+ return 0;
+}
+
+static inline unsigned long long Metric_instance_ONE_K(int metric, int pid, int offset) {
+ pmAtomValue value;
+ if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64))
+ return value.ull / ONE_K;
+ return ULLONG_MAX;
+}
+
+static inline char Metric_instance_char(int metric, int pid, int offset, char fallback) {
+ pmAtomValue value;
+ if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) {
+ char uchar = value.cp[0];
+ free(value.cp);
+ return uchar;
+ }
+ return fallback;
+}
+
+static char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) {
+ char* name = Hashtable_get(this->users, uid);
+ if (name)
+ return name;
+
+ pmAtomValue value;
+ if (Metric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) {
+ Hashtable_put(this->users, uid, value.cp);
+ name = value.cp;
+ }
+ return name;
+}
+
+static inline ProcessState PCPProcessTable_getProcessState(char state) {
+ switch (state) {
+ case '?': return UNKNOWN;
+ case 'R': return RUNNING;
+ case 'W': return WAITING;
+ case 'D': return UNINTERRUPTIBLE_WAIT;
+ case 'P': return PAGING;
+ case 'T': return STOPPED;
+ case 't': return TRACED;
+ case 'Z': return ZOMBIE;
+ case 'X': return DEFUNCT;
+ case 'I': return IDLE;
+ case 'S': return SLEEPING;
+ default: return UNKNOWN;
+ }
+}
+
+static void PCPProcessTable_updateID(Process* process, int pid, int offset) {
+ Process_setThreadGroup(process, Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1));
+ Process_setParent(process, Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1));
+ process->state = PCPProcessTable_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?'));
+}
+
+static void PCPProcessTable_updateInfo(PCPProcess* pp, int pid, int offset, char* command, size_t commLen) {
+ Process* process = &pp->super;
+ pmAtomValue value;
+
+ if (!Metric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING))
+ value.cp = xStrdup("<unknown>");
+ String_safeStrncpy(command, value.cp, commLen);
+ free(value.cp);
+
+ process->pgrp = Metric_instance_u32(PCP_PROC_PGRP, pid, offset, 0);
+ process->session = Metric_instance_u32(PCP_PROC_SESSION, pid, offset, 0);
+ process->tty_nr = Metric_instance_u32(PCP_PROC_TTY, pid, offset, 0);
+ process->tpgid = Metric_instance_u32(PCP_PROC_TTYPGRP, pid, offset, 0);
+ process->minflt = Metric_instance_u32(PCP_PROC_MINFLT, pid, offset, 0);
+ pp->cminflt = Metric_instance_u32(PCP_PROC_CMINFLT, pid, offset, 0);
+ process->majflt = Metric_instance_u32(PCP_PROC_MAJFLT, pid, offset, 0);
+ pp->cmajflt = Metric_instance_u32(PCP_PROC_CMAJFLT, pid, offset, 0);
+ pp->utime = Metric_instance_time(PCP_PROC_UTIME, pid, offset);
+ pp->stime = Metric_instance_time(PCP_PROC_STIME, pid, offset);
+ pp->cutime = Metric_instance_time(PCP_PROC_CUTIME, pid, offset);
+ pp->cstime = Metric_instance_time(PCP_PROC_CSTIME, pid, offset);
+ process->priority = Metric_instance_u32(PCP_PROC_PRIORITY, pid, offset, 0);
+ process->nice = Metric_instance_s32(PCP_PROC_NICE, pid, offset, 0);
+ process->nlwp = Metric_instance_u32(PCP_PROC_THREADS, pid, offset, 0);
+ process->starttime_ctime = Metric_instance_time(PCP_PROC_STARTTIME, pid, offset);
+ process->processor = Metric_instance_u32(PCP_PROC_PROCESSOR, pid, offset, 0);
+
+ process->time = pp->utime + pp->stime;
+}
+
+static void PCPProcessTable_updateIO(PCPProcess* pp, int pid, int offset, unsigned long long now) {
+ pmAtomValue value;
+
+ pp->io_rchar = Metric_instance_ONE_K(PCP_PROC_IO_RCHAR, pid, offset);
+ pp->io_wchar = Metric_instance_ONE_K(PCP_PROC_IO_WCHAR, pid, offset);
+ pp->io_syscr = Metric_instance_u64(PCP_PROC_IO_SYSCR, pid, offset, ULLONG_MAX);
+ pp->io_syscw = Metric_instance_u64(PCP_PROC_IO_SYSCW, pid, offset, ULLONG_MAX);
+ pp->io_cancelled_write_bytes = Metric_instance_ONE_K(PCP_PROC_IO_CANCELLED, pid, offset);
+
+ if (Metric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) {
+ unsigned long long last_read = pp->io_read_bytes;
+ pp->io_read_bytes = value.ull / ONE_K;
+ pp->io_rate_read_bps = ONE_K * (pp->io_read_bytes - last_read) /
+ (now - pp->io_last_scan_time);
+ } else {
+ pp->io_read_bytes = ULLONG_MAX;
+ pp->io_rate_read_bps = NAN;
+ }
+
+ if (Metric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) {
+ unsigned long long last_write = pp->io_write_bytes;
+ pp->io_write_bytes = value.ull;
+ pp->io_rate_write_bps = ONE_K * (pp->io_write_bytes - last_write) /
+ (now - pp->io_last_scan_time);
+ } else {
+ pp->io_write_bytes = ULLONG_MAX;
+ pp->io_rate_write_bps = NAN;
+ }
+
+ pp->io_last_scan_time = now;
+}
+
+static void PCPProcessTable_updateMemory(PCPProcess* pp, int pid, int offset) {
+ pp->super.m_virt = Metric_instance_u32(PCP_PROC_MEM_SIZE, pid, offset, 0);
+ pp->super.m_resident = Metric_instance_u32(PCP_PROC_MEM_RSS, pid, offset, 0);
+ pp->m_share = Metric_instance_u32(PCP_PROC_MEM_SHARE, pid, offset, 0);
+ pp->m_priv = pp->super.m_resident - pp->m_share;
+ pp->m_trs = Metric_instance_u32(PCP_PROC_MEM_TEXTRS, pid, offset, 0);
+ pp->m_lrs = Metric_instance_u32(PCP_PROC_MEM_LIBRS, pid, offset, 0);
+ pp->m_drs = Metric_instance_u32(PCP_PROC_MEM_DATRS, pid, offset, 0);
+ pp->m_dt = Metric_instance_u32(PCP_PROC_MEM_DIRTY, pid, offset, 0);
+}
+
+static void PCPProcessTable_updateSmaps(PCPProcess* pp, pid_t pid, int offset) {
+ pp->m_pss = Metric_instance_u64(PCP_PROC_SMAPS_PSS, pid, offset, 0);
+ pp->m_swap = Metric_instance_u64(PCP_PROC_SMAPS_SWAP, pid, offset, 0);
+ pp->m_psswp = Metric_instance_u64(PCP_PROC_SMAPS_SWAPPSS, pid, offset, 0);
+}
+
+static void PCPProcessTable_readOomData(PCPProcess* pp, int pid, int offset) {
+ pp->oom = Metric_instance_u32(PCP_PROC_OOMSCORE, pid, offset, 0);
+}
+
+static void PCPProcessTable_readAutogroup(PCPProcess* pp, int pid, int offset) {
+ pp->autogroup_id = Metric_instance_s64(PCP_PROC_AUTOGROUP_ID, pid, offset, -1);
+ pp->autogroup_nice = Metric_instance_s32(PCP_PROC_AUTOGROUP_NICE, pid, offset, 0);
+}
+
+static void PCPProcessTable_readCtxtData(PCPProcess* pp, int pid, int offset) {
+ pmAtomValue value;
+ unsigned long ctxt = 0;
+
+ if (Metric_instance(PCP_PROC_VCTXSW, pid, offset, &value, PM_TYPE_U32))
+ ctxt += value.ul;
+ if (Metric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32))
+ ctxt += value.ul;
+
+ pp->ctxt_diff = ctxt > pp->ctxt_total ? ctxt - pp->ctxt_total : 0;
+ pp->ctxt_total = ctxt;
+}
+
+static char* setString(Metric metric, int pid, int offset, char* string) {
+ if (string)
+ free(string);
+ pmAtomValue value;
+ if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING))
+ string = value.cp;
+ else
+ string = NULL;
+ return string;
+}
+
+static void PCPProcessTable_updateTTY(Process* process, int pid, int offset) {
+ process->tty_name = setString(PCP_PROC_TTYNAME, pid, offset, process->tty_name);
+}
+
+static void PCPProcessTable_readCGroups(PCPProcess* pp, int pid, int offset) {
+ pp->cgroup = setString(PCP_PROC_CGROUPS, pid, offset, pp->cgroup);
+
+ if (pp->cgroup) {
+ char* cgroup_short = CGroup_filterName(pp->cgroup);
+ if (cgroup_short) {
+ Row_updateFieldWidth(CCGROUP, strlen(cgroup_short));
+ free_and_xStrdup(&pp->cgroup_short, cgroup_short);
+ free(cgroup_short);
+ } else {
+ //CCGROUP is alias to normal CGROUP if shortening fails
+ Row_updateFieldWidth(CCGROUP, strlen(pp->cgroup));
+ free(pp->cgroup_short);
+ pp->cgroup_short = NULL;
+ }
+
+ char* container_short = CGroup_filterName(pp->cgroup);
+ if (container_short) {
+ Row_updateFieldWidth(CONTAINER, strlen(container_short));
+ free_and_xStrdup(&pp->container_short, container_short);
+ free(container_short);
+ } else {
+ Row_updateFieldWidth(CONTAINER, strlen("N/A"));
+ free(pp->container_short);
+ pp->container_short = NULL;
+ }
+ } else {
+ free(pp->cgroup_short);
+ pp->cgroup_short = NULL;
+
+ free(pp->container_short);
+ pp->container_short = NULL;
+ }
+}
+
+static void PCPProcessTable_readSecattrData(PCPProcess* pp, int pid, int offset) {
+ pp->secattr = setString(PCP_PROC_LABELS, pid, offset, pp->secattr);
+}
+
+static void PCPProcessTable_readCwd(PCPProcess* pp, int pid, int offset) {
+ pp->super.procCwd = setString(PCP_PROC_CWD, pid, offset, pp->super.procCwd);
+}
+
+static void PCPProcessTable_updateUsername(Process* process, int pid, int offset, UsersTable* users) {
+ process->st_uid = Metric_instance_u32(PCP_PROC_ID_UID, pid, offset, 0);
+ process->user = setUser(users, process->st_uid, pid, offset);
+}
+
+static void PCPProcessTable_updateCmdline(Process* process, int pid, int offset, const char* comm) {
+ pmAtomValue value;
+ if (!Metric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) {
+ if (process->state != ZOMBIE)
+ process->isKernelThread = true;
+ Process_updateCmdline(process, NULL, 0, 0);
+ return;
+ }
+
+ char* command = value.cp;
+ int length = strlen(command);
+ if (command[0] != '(') {
+ process->isKernelThread = false;
+ } else {
+ ++command;
+ --length;
+ if (command[length - 1] == ')')
+ command[--length] = '\0';
+ process->isKernelThread = true;
+ }
+
+ int tokenEnd = 0;
+ int tokenStart = 0;
+ bool argSepSpace = false;
+
+ for (int i = 0; i < length; i++) {
+ /* htop considers the next character after the last / that is before
+ * basenameOffset, as the start of the basename in cmdline - see
+ * Process_writeCommand */
+ if (command[i] == '/')
+ tokenStart = i + 1;
+ /* special-case arguments for problematic situations like "find /" */
+ if (command[i] <= ' ')
+ argSepSpace = true;
+ }
+ tokenEnd = length;
+ if (argSepSpace)
+ tokenStart = 0;
+
+ Process_updateCmdline(process, command, tokenStart, tokenEnd);
+ free(value.cp);
+
+ Process_updateComm(process, comm);
+
+ if (Metric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) {
+ Process_updateExe(process, value.cp[0] ? value.cp : NULL);
+ free(value.cp);
+ }
+}
+
+static bool PCPProcessTable_updateProcesses(PCPProcessTable* this) {
+ ProcessTable* pt = (ProcessTable*) this;
+ Machine* host = pt->super.host;
+ PCPMachine* phost = (PCPMachine*) host;
+
+ const Settings* settings = host->settings;
+ bool hideKernelThreads = settings->hideKernelThreads;
+ bool hideUserlandThreads = settings->hideUserlandThreads;
+ uint32_t flags = settings->ss->flags;
+
+ unsigned long long now = (unsigned long long)(phost->timestamp * 1000);
+ int pid = -1, offset = -1;
+
+ /* for every process ... */
+ while (Metric_iterate(PCP_PROC_PID, &pid, &offset)) {
+
+ bool preExisting;
+ Process* proc = ProcessTable_getProcess(pt, pid, &preExisting, PCPProcess_new);
+ PCPProcess* pp = (PCPProcess*) proc;
+ PCPProcessTable_updateID(proc, pid, offset);
+ proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc);
+ pp->offset = offset >= 0 ? offset : 0;
+
+ /*
+ * These conditions will not trigger on first occurrence, cause we need to
+ * add the process to the ProcessTable and do all one time scans
+ * (e.g. parsing the cmdline to detect a kernel thread)
+ * But it will short-circuit subsequent scans.
+ */
+ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ if (proc->state == RUNNING)
+ pt->runningTasks++;
+ pt->kernelThreads++;
+ pt->totalTasks++;
+ continue;
+ }
+ if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ if (proc->state == RUNNING)
+ pt->runningTasks++;
+ pt->userlandThreads++;
+ pt->totalTasks++;
+ continue;
+ }
+
+ if (flags & PROCESS_FLAG_IO)
+ PCPProcessTable_updateIO(pp, pid, offset, now);
+
+ PCPProcessTable_updateMemory(pp, pid, offset);
+
+ if ((flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
+ if (Metric_enabled(PCP_PROC_SMAPS_PSS)) {
+ PCPProcessTable_updateSmaps(pp, pid, offset);
+ }
+ }
+
+ char command[MAX_NAME + 1];
+ unsigned int tty_nr = proc->tty_nr;
+ unsigned long long int lasttimes = pp->utime + pp->stime;
+
+ PCPProcessTable_updateInfo(pp, pid, offset, command, sizeof(command));
+ proc->starttime_ctime += Platform_getBootTime();
+ if (tty_nr != proc->tty_nr)
+ PCPProcessTable_updateTTY(proc, pid, offset);
+
+ proc->percent_cpu = NAN;
+ if (phost->period > 0.0) {
+ float percent_cpu = saturatingSub(pp->utime + pp->stime, lasttimes) / phost->period * 100.0;
+ proc->percent_cpu = MINIMUM(percent_cpu, host->activeCPUs * 100.0F);
+ }
+ proc->percent_mem = proc->m_resident / (double) host->totalMem * 100.0;
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ PCPProcessTable_updateUsername(proc, pid, offset, host->usersTable);
+
+ if (!preExisting) {
+ PCPProcessTable_updateCmdline(proc, pid, offset, command);
+ Process_fillStarttimeBuffer(proc);
+ ProcessTable_add(pt, proc);
+ } else if (settings->updateProcessNames && proc->state != ZOMBIE) {
+ PCPProcessTable_updateCmdline(proc, pid, offset, command);
+ }
+
+ if (flags & PROCESS_FLAG_LINUX_CGROUP)
+ PCPProcessTable_readCGroups(pp, pid, offset);
+
+ if (flags & PROCESS_FLAG_LINUX_OOM)
+ PCPProcessTable_readOomData(pp, pid, offset);
+
+ if (flags & PROCESS_FLAG_LINUX_CTXT)
+ PCPProcessTable_readCtxtData(pp, pid, offset);
+
+ if (flags & PROCESS_FLAG_LINUX_SECATTR)
+ PCPProcessTable_readSecattrData(pp, pid, offset);
+
+ if (flags & PROCESS_FLAG_CWD)
+ PCPProcessTable_readCwd(pp, pid, offset);
+
+ if (flags & PROCESS_FLAG_LINUX_AUTOGROUP)
+ PCPProcessTable_readAutogroup(pp, pid, offset);
+
+ if (proc->state == ZOMBIE && !proc->cmdline && command[0]) {
+ Process_updateCmdline(proc, command, 0, strlen(command));
+ } else if (Process_isThread(proc)) {
+ if ((settings->showThreadNames || Process_isKernelThread(proc)) && command[0]) {
+ Process_updateCmdline(proc, command, 0, strlen(command));
+ }
+
+ if (Process_isKernelThread(proc)) {
+ pt->kernelThreads++;
+ } else {
+ pt->userlandThreads++;
+ }
+ }
+
+ /* Set at the end when we know if a new entry is a thread */
+ proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) ||
+ (hideUserlandThreads && Process_isUserlandThread(proc)));
+
+ pt->totalTasks++;
+ if (proc->state == RUNNING)
+ pt->runningTasks++;
+ proc->super.updated = true;
+ }
+ return true;
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ PCPProcessTable* this = (PCPProcessTable*) super;
+ PCPProcessTable_updateProcesses(this);
+}
diff --git a/pcp/PCPProcessTable.h b/pcp/PCPProcessTable.h
new file mode 100644
index 00000000..e55c85ba
--- /dev/null
+++ b/pcp/PCPProcessTable.h
@@ -0,0 +1,24 @@
+#ifndef HEADER_PCPProcessTable
+#define HEADER_PCPProcessTable
+/*
+htop - PCPProcessTable.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "Hashtable.h"
+#include "ProcessTable.h"
+#include "UsersTable.h"
+
+#include "pcp/Platform.h"
+
+
+typedef struct PCPProcessTable_ {
+ ProcessTable super;
+} PCPProcessTable;
+
+#endif
diff --git a/pcp/Platform.c b/pcp/Platform.c
index 342bf439..d50edd25 100644
--- a/pcp/Platform.c
+++ b/pcp/Platform.c
@@ -1,8 +1,8 @@
/*
htop - linux/Platform.c
(C) 2014 Hisham H. Muhammad
-(C) 2020-2021 htop dev team
-(C) 2020-2021 Red Hat, Inc.
+(C) 2020-2022 htop dev team
+(C) 2020-2022 Red Hat, Inc.
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -25,6 +25,8 @@ in the source distribution for its full text.
#include "DiskIOMeter.h"
#include "DynamicColumn.h"
#include "DynamicMeter.h"
+#include "DynamicScreen.h"
+#include "FileDescriptorMeter.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
@@ -32,7 +34,7 @@ in the source distribution for its full text.
#include "MemorySwapMeter.h"
#include "Meter.h"
#include "NetworkIOMeter.h"
-#include "ProcessList.h"
+#include "ProcessTable.h"
#include "Settings.h"
#include "SwapMeter.h"
#include "SysArchMeter.h"
@@ -43,10 +45,12 @@ in the source distribution for its full text.
#include "linux/PressureStallMeter.h"
#include "linux/ZramMeter.h"
#include "linux/ZramStats.h"
+#include "pcp/Metric.h"
#include "pcp/PCPDynamicColumn.h"
#include "pcp/PCPDynamicMeter.h"
-#include "pcp/PCPMetric.h"
-#include "pcp/PCPProcessList.h"
+#include "pcp/PCPDynamicScreen.h"
+#include "pcp/PCPMachine.h"
+#include "pcp/PCPProcessTable.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsArcStats.h"
#include "zfs/ZfsCompressedArcMeter.h"
@@ -106,6 +110,7 @@ const MeterClass* const Platform_meterTypes[] = {
&PressureStallCPUSomeMeter_class,
&PressureStallIOSomeMeter_class,
&PressureStallIOFullMeter_class,
+ &PressureStallIRQFullMeter_class,
&PressureStallMemorySomeMeter_class,
&PressureStallMemoryFullMeter_class,
&ZfsArcMeter_class,
@@ -114,6 +119,7 @@ const MeterClass* const Platform_meterTypes[] = {
&DiskIOMeter_class,
&NetworkIOMeter_class,
&SysArchMeter_class,
+ &FileDescriptorMeter_class,
NULL
};
@@ -171,6 +177,7 @@ static const char* Platform_metricNames[] = {
[PCP_PSI_CPUSOME] = "kernel.all.pressure.cpu.some.avg",
[PCP_PSI_IOSOME] = "kernel.all.pressure.io.some.avg",
[PCP_PSI_IOFULL] = "kernel.all.pressure.io.full.avg",
+ [PCP_PSI_IRQFULL] = "kernel.all.pressure.irq.full.avg",
[PCP_PSI_MEMSOME] = "kernel.all.pressure.memory.some.avg",
[PCP_PSI_MEMFULL] = "kernel.all.pressure.memory.full.avg",
@@ -190,6 +197,10 @@ static const char* Platform_metricNames[] = {
[PCP_ZRAM_CAPACITY] = "zram.capacity",
[PCP_ZRAM_ORIGINAL] = "zram.mm_stat.data_size.original",
[PCP_ZRAM_COMPRESSED] = "zram.mm_stat.data_size.compressed",
+ [PCP_MEM_ZSWAP] = "mem.util.zswap",
+ [PCP_MEM_ZSWAPPED] = "mem.util.zswapped",
+ [PCP_VFS_FILES_COUNT] = "vfs.files.count",
+ [PCP_VFS_FILES_MAX] = "vfs.files.max",
[PCP_PROC_PID] = "proc.psinfo.pid",
[PCP_PROC_PPID] = "proc.psinfo.ppid",
@@ -248,7 +259,38 @@ static const char* Platform_metricNames[] = {
[PCP_METRIC_COUNT] = NULL
};
-size_t Platform_addMetric(PCPMetric id, const char* name) {
+#ifndef HAVE_PMLOOKUPDESCS
+/*
+ * pmLookupDescs(3) exists in latest versions of libpcp (5.3.6+),
+ * but for older versions we provide an implementation here. This
+ * involves multiple round trips to pmcd though, which the latest
+ * libpcp version avoids by using a protocol extension. In time,
+ * perhaps in a few years, we could remove this back-compat code.
+ */
+int pmLookupDescs(int numpmid, pmID* pmids, pmDesc* descs) {
+ int count = 0;
+
+ for (int i = 0; i < numpmid; i++) {
+ /* expect some metrics to be missing - e.g. PMDA not available */
+ if (pmids[i] == PM_ID_NULL)
+ continue;
+
+ int sts = pmLookupDesc(pmids[i], &descs[i]);
+ if (sts < 0) {
+ if (pmDebugOptions.appl0)
+ fprintf(stderr, "Error: cannot lookup metric %s(%s): %s\n",
+ pcp->names[i], pmIDStr(pcp->pmids[i]), pmErrStr(sts));
+ pmids[i] = PM_ID_NULL;
+ continue;
+ }
+
+ count++;
+ }
+ return count;
+}
+#endif
+
+size_t Platform_addMetric(Metric id, const char* name) {
unsigned int i = (unsigned int)id;
if (i >= PCP_METRIC_COUNT && i >= pcp->totalMetrics) {
@@ -317,6 +359,7 @@ bool Platform_init(void) {
pcp->columns.offset = PCP_METRIC_COUNT + pcp->meters.cursor;
PCPDynamicColumns_init(&pcp->columns);
+ PCPDynamicScreens_init(&pcp->screens, &pcp->columns);
sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids);
if (sts < 0) {
@@ -325,49 +368,43 @@ bool Platform_init(void) {
return false;
}
- for (size_t i = 0; i < pcp->totalMetrics; i++) {
- pcp->fetch[i] = PM_ID_NULL; /* default is to not sample */
-
- /* expect some metrics to be missing - e.g. PMDA not available */
- if (pcp->pmids[i] == PM_ID_NULL)
- continue;
-
- sts = pmLookupDesc(pcp->pmids[i], &pcp->descs[i]);
- if (sts < 0) {
- if (pmDebugOptions.appl0)
- fprintf(stderr, "Error: cannot lookup metric %s(%s): %s\n",
- pcp->names[i], pmIDStr(pcp->pmids[i]), pmErrStr(sts));
- pcp->pmids[i] = PM_ID_NULL;
- continue;
- }
+ sts = pmLookupDescs(pcp->totalMetrics, pcp->pmids, pcp->descs);
+ if (sts < 1) {
+ if (sts < 0)
+ fprintf(stderr, "Error: cannot lookup descriptors: %s\n", pmErrStr(sts));
+ else /* ensure we have at least one valid metric to work with */
+ fprintf(stderr, "Error: cannot find a single valid metric, exiting\n");
+ Platform_done();
+ return false;
}
/* set proc.control.perclient.threads to 1 for live contexts */
- PCPMetric_enableThreads();
+ Metric_enableThreads();
/* extract values needed for setup - e.g. cpu count, pid_max */
- PCPMetric_enable(PCP_PID_MAX, true);
- PCPMetric_enable(PCP_BOOTTIME, true);
- PCPMetric_enable(PCP_HINV_NCPU, true);
- PCPMetric_enable(PCP_PERCPU_SYSTEM, true);
- PCPMetric_enable(PCP_UNAME_SYSNAME, true);
- PCPMetric_enable(PCP_UNAME_RELEASE, true);
- PCPMetric_enable(PCP_UNAME_MACHINE, true);
- PCPMetric_enable(PCP_UNAME_DISTRO, true);
-
+ Metric_enable(PCP_PID_MAX, true);
+ Metric_enable(PCP_BOOTTIME, true);
+ Metric_enable(PCP_HINV_NCPU, true);
+ Metric_enable(PCP_PERCPU_SYSTEM, true);
+ Metric_enable(PCP_UNAME_SYSNAME, true);
+ Metric_enable(PCP_UNAME_RELEASE, true);
+ Metric_enable(PCP_UNAME_MACHINE, true);
+ Metric_enable(PCP_UNAME_DISTRO, true);
+
+ /* enable metrics for all dynamic columns (including those from dynamic screens) */
for (size_t i = pcp->columns.offset; i < pcp->columns.offset + pcp->columns.count; i++)
- PCPMetric_enable(i, true);
+ Metric_enable(i, true);
- PCPMetric_fetch(NULL);
+ Metric_fetch(NULL);
- for (PCPMetric metric = 0; metric < PCP_PROC_PID; metric++)
- PCPMetric_enable(metric, true);
- PCPMetric_enable(PCP_PID_MAX, false); /* needed one time only */
- PCPMetric_enable(PCP_BOOTTIME, false);
- PCPMetric_enable(PCP_UNAME_SYSNAME, false);
- PCPMetric_enable(PCP_UNAME_RELEASE, false);
- PCPMetric_enable(PCP_UNAME_MACHINE, false);
- PCPMetric_enable(PCP_UNAME_DISTRO, false);
+ for (Metric metric = 0; metric < PCP_PROC_PID; metric++)
+ Metric_enable(metric, true);
+ Metric_enable(PCP_PID_MAX, false); /* needed one time only */
+ Metric_enable(PCP_BOOTTIME, false);
+ Metric_enable(PCP_UNAME_SYSNAME, false);
+ Metric_enable(PCP_UNAME_RELEASE, false);
+ Metric_enable(PCP_UNAME_MACHINE, false);
+ Metric_enable(PCP_UNAME_DISTRO, false);
/* first sample (fetch) performed above, save constants */
Platform_getBootTime();
@@ -386,6 +423,10 @@ void Platform_dynamicMetersDone(Hashtable* meters) {
PCPDynamicMeters_done(meters);
}
+void Platform_dynamicScreensDone(Hashtable* screens) {
+ PCPDynamicScreens_done(screens);
+}
+
void Platform_done(void) {
pmDestroyContext(pcp->context);
if (pcp->result)
@@ -405,7 +446,7 @@ void Platform_setBindings(Htop_Action* keys) {
int Platform_getUptime(void) {
pmAtomValue value;
- if (PCPMetric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL)
+ if (Metric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL)
return 0;
return value.l;
}
@@ -414,7 +455,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*one = *five = *fifteen = 0.0;
pmAtomValue values[3] = {0};
- if (PCPMetric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) {
+ if (Metric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) {
*one = values[0].d;
*five = values[1].d;
*fifteen = values[2].d;
@@ -426,20 +467,20 @@ unsigned int Platform_getMaxCPU(void) {
return pcp->ncpu;
pmAtomValue value;
- if (PCPMetric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL)
+ if (Metric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL)
pcp->ncpu = value.ul;
else
pcp->ncpu = 1;
return pcp->ncpu;
}
-int Platform_getMaxPid(void) {
+pid_t Platform_getMaxPid(void) {
if (pcp->pidmax)
return pcp->pidmax;
pmAtomValue value;
- if (PCPMetric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL)
- return -1;
+ if (Metric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL)
+ return INT_MAX;
pcp->pidmax = value.l;
return pcp->pidmax;
}
@@ -449,13 +490,12 @@ long long Platform_getBootTime(void) {
return pcp->btime;
pmAtomValue value;
- if (PCPMetric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL)
+ if (Metric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL)
pcp->btime = value.ll;
return pcp->btime;
}
-static double Platform_setOneCPUValues(Meter* this, pmAtomValue* values) {
-
+static double Platform_setOneCPUValues(Meter* this, const Settings* settings, pmAtomValue* values) {
unsigned long long value = values[CPU_TOTAL_PERIOD].ull;
double total = (double) (value == 0 ? 1 : value);
double percent;
@@ -463,28 +503,32 @@ static double Platform_setOneCPUValues(Meter* this, pmAtomValue* values) {
double* v = this->values;
v[CPU_METER_NICE] = values[CPU_NICE_PERIOD].ull / total * 100.0;
v[CPU_METER_NORMAL] = values[CPU_USER_PERIOD].ull / total * 100.0;
- if (this->pl->settings->detailedCPUTime) {
+ if (settings->detailedCPUTime) {
v[CPU_METER_KERNEL] = values[CPU_SYSTEM_PERIOD].ull / total * 100.0;
v[CPU_METER_IRQ] = values[CPU_IRQ_PERIOD].ull / total * 100.0;
v[CPU_METER_SOFTIRQ] = values[CPU_SOFTIRQ_PERIOD].ull / total * 100.0;
+ this->curItems = 5;
+
v[CPU_METER_STEAL] = values[CPU_STEAL_PERIOD].ull / total * 100.0;
v[CPU_METER_GUEST] = values[CPU_GUEST_PERIOD].ull / total * 100.0;
+ if (settings->accountGuestInCPUMeter) {
+ this->curItems = 7;
+ }
+
v[CPU_METER_IOWAIT] = values[CPU_IOWAIT_PERIOD].ull / total * 100.0;
- this->curItems = 8;
- if (this->pl->settings->accountGuestInCPUMeter)
- percent = v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6];
- else
- percent = v[0] + v[1] + v[2] + v[3] + v[4];
} else {
- v[2] = values[CPU_SYSTEM_ALL_PERIOD].ull / total * 100.0;
+ v[CPU_METER_KERNEL] = values[CPU_SYSTEM_ALL_PERIOD].ull / total * 100.0;
value = values[CPU_STEAL_PERIOD].ull + values[CPU_GUEST_PERIOD].ull;
- v[3] = value / total * 100.0;
+ v[CPU_METER_IRQ] = value / total * 100.0;
this->curItems = 4;
- percent = v[0] + v[1] + v[2] + v[3];
}
- percent = CLAMP(percent, 0.0, 100.0);
- if (isnan(percent))
- percent = 0.0;
+
+ percent = sumPositiveValues(v, this->curItems);
+ percent = MINIMUM(percent, 100.0);
+
+ if (settings->detailedCPUTime) {
+ this->curItems = 8;
+ }
v[CPU_METER_FREQUENCY] = values[CPU_FREQUENCY].d;
v[CPU_METER_TEMPERATURE] = NAN;
@@ -493,43 +537,65 @@ static double Platform_setOneCPUValues(Meter* this, pmAtomValue* values) {
}
double Platform_setCPUValues(Meter* this, int cpu) {
- const PCPProcessList* pl = (const PCPProcessList*) this->pl;
+ const PCPMachine* phost = (const PCPMachine*) this->host;
+ const Settings* settings = this->host->settings;
+
if (cpu <= 0) /* use aggregate values */
- return Platform_setOneCPUValues(this, pl->cpu);
- return Platform_setOneCPUValues(this, pl->percpu[cpu - 1]);
+ return Platform_setOneCPUValues(this, settings, phost->cpu);
+ return Platform_setOneCPUValues(this, settings, phost->percpu[cpu - 1]);
}
void Platform_setMemoryValues(Meter* this) {
- const ProcessList* pl = this->pl;
- const PCPProcessList* ppl = (const PCPProcessList*) pl;
-
- this->total = pl->totalMem;
- this->values[0] = pl->usedMem;
- this->values[1] = pl->buffersMem;
- this->values[2] = pl->sharedMem;
- this->values[3] = pl->cachedMem;
- this->values[4] = pl->availableMem;
-
- if (ppl->zfs.enabled != 0) {
+ const Machine* host = this->host;
+ const PCPMachine* phost = (const PCPMachine*) host;
+
+ this->total = host->totalMem;
+ this->values[MEMORY_METER_USED] = host->usedMem;
+ this->values[MEMORY_METER_SHARED] = host->sharedMem;
+ this->values[MEMORY_METER_COMPRESSED] = 0;
+ this->values[MEMORY_METER_BUFFERS] = host->buffersMem;
+ this->values[MEMORY_METER_CACHE] = host->cachedMem;
+ this->values[MEMORY_METER_AVAILABLE] = host->availableMem;
+
+ if (phost->zfs.enabled != 0) {
// ZFS does not shrink below the value of zfs_arc_min.
unsigned long long int shrinkableSize = 0;
- if (ppl->zfs.size > ppl->zfs.min)
- shrinkableSize = ppl->zfs.size - ppl->zfs.min;
- this->values[0] -= shrinkableSize;
- this->values[3] += shrinkableSize;
- this->values[4] += shrinkableSize;
+ if (phost->zfs.size > phost->zfs.min)
+ shrinkableSize = phost->zfs.size - phost->zfs.min;
+ this->values[MEMORY_METER_USED] -= shrinkableSize;
+ this->values[MEMORY_METER_CACHE] += shrinkableSize;
+ this->values[MEMORY_METER_AVAILABLE] += shrinkableSize;
+ }
+
+ if (phost->zswap.usedZswapOrig > 0 || phost->zswap.usedZswapComp > 0) {
+ this->values[MEMORY_METER_USED] -= phost->zswap.usedZswapComp;
+ this->values[MEMORY_METER_COMPRESSED] += phost->zswap.usedZswapComp;
}
}
void Platform_setSwapValues(Meter* this) {
- const ProcessList* pl = this->pl;
- this->total = pl->totalSwap;
- this->values[0] = pl->usedSwap;
- this->values[1] = pl->cachedSwap;
+ const Machine* host = this->host;
+ const PCPMachine* phost = (const PCPMachine*) host;
+
+ this->total = host->totalSwap;
+ this->values[SWAP_METER_USED] = host->usedSwap;
+ this->values[SWAP_METER_CACHE] = host->cachedSwap;
+ this->values[SWAP_METER_FRONTSWAP] = 0; /* frontswap -- memory that is accounted to swap but resides elsewhere */
+
+ if (phost->zswap.usedZswapOrig > 0 || phost->zswap.usedZswapComp > 0) {
+ /* refer to linux/Platform.c::Platform_setSwapValues for details */
+ this->values[SWAP_METER_USED] -= phost->zswap.usedZswapOrig;
+ if (this->values[SWAP_METER_USED] < 0) {
+ /* subtract the overflow from SwapCached */
+ this->values[SWAP_METER_CACHE] += this->values[SWAP_METER_USED];
+ this->values[SWAP_METER_USED] = 0;
+ }
+ this->values[SWAP_METER_FRONTSWAP] += phost->zswap.usedZswapOrig;
+ }
}
void Platform_setZramValues(Meter* this) {
- int i, count = PCPMetric_instanceCount(PCP_ZRAM_CAPACITY);
+ int i, count = Metric_instanceCount(PCP_ZRAM_CAPACITY);
if (!count) {
this->total = 0;
this->values[0] = 0;
@@ -540,36 +606,40 @@ void Platform_setZramValues(Meter* this) {
pmAtomValue* values = xCalloc(count, sizeof(pmAtomValue));
ZramStats stats = {0};
- if (PCPMetric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) {
+ if (Metric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) {
for (i = 0; i < count; i++)
stats.totalZram += values[i].ull;
}
- if (PCPMetric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) {
+ if (Metric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) {
for (i = 0; i < count; i++)
stats.usedZramOrig += values[i].ull;
}
- if (PCPMetric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) {
+ if (Metric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) {
for (i = 0; i < count; i++)
stats.usedZramComp += values[i].ull;
}
free(values);
+ if (stats.usedZramComp > stats.usedZramOrig) {
+ stats.usedZramComp = stats.usedZramOrig;
+ }
+
this->total = stats.totalZram;
this->values[0] = stats.usedZramComp;
- this->values[1] = stats.usedZramOrig;
+ this->values[1] = stats.usedZramOrig - stats.usedZramComp;
}
void Platform_setZfsArcValues(Meter* this) {
- const PCPProcessList* ppl = (const PCPProcessList*) this->pl;
+ const PCPMachine* phost = (const PCPMachine*) this->host;
- ZfsArcMeter_readStats(this, &(ppl->zfs));
+ ZfsArcMeter_readStats(this, &phost->zfs);
}
void Platform_setZfsCompressedArcValues(Meter* this) {
- const PCPProcessList* ppl = (const PCPProcessList*) this->pl;
+ const PCPMachine* phost = (const PCPMachine*) this->host;
- ZfsCompressedArcMeter_readStats(this, &(ppl->zfs));
+ ZfsCompressedArcMeter_readStats(this, &phost->zfs);
}
void Platform_getHostname(char* buffer, size_t size) {
@@ -586,13 +656,13 @@ void Platform_getRelease(char** string) {
/* first call, extract just-sampled values */
pmAtomValue sysname, release, machine, distro;
- if (!PCPMetric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING))
+ if (!Metric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING))
sysname.cp = NULL;
- if (!PCPMetric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING))
+ if (!Metric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING))
release.cp = NULL;
- if (!PCPMetric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING))
+ if (!Metric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING))
machine.cp = NULL;
- if (!PCPMetric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING))
+ if (!Metric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING))
distro.cp = NULL;
size_t length = 16; /* padded for formatting characters */
@@ -640,17 +710,11 @@ void Platform_getRelease(char** string) {
char* Platform_getProcessEnv(pid_t pid) {
pmAtomValue value;
- if (!PCPMetric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING))
+ if (!Metric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING))
return NULL;
return value.cp;
}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
- (void)pid;
- (void)inode;
- return NULL;
-}
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
(void)pid;
return NULL;
@@ -659,18 +723,20 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) {
*ten = *sixty = *threehundred = 0;
- PCPMetric metric;
+ Metric metric;
if (String_eq(file, "cpu"))
metric = PCP_PSI_CPUSOME;
else if (String_eq(file, "io"))
metric = some ? PCP_PSI_IOSOME : PCP_PSI_IOFULL;
+ else if (String_eq(file, "irq"))
+ metric = PCP_PSI_IRQFULL;
else if (String_eq(file, "mem"))
metric = some ? PCP_PSI_MEMSOME : PCP_PSI_MEMFULL;
else
return;
pmAtomValue values[3] = {0};
- if (PCPMetric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) {
+ if (Metric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) {
*ten = values[0].d;
*sixty = values[1].d;
*threehundred = values[2].d;
@@ -681,11 +747,11 @@ bool Platform_getDiskIO(DiskIOData* data) {
memset(data, 0, sizeof(*data));
pmAtomValue value;
- if (PCPMetric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL)
+ if (Metric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL)
data->totalBytesRead = value.ull;
- if (PCPMetric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL)
+ if (Metric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL)
data->totalBytesWritten = value.ull;
- if (PCPMetric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL)
+ if (Metric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL)
data->totalMsTimeSpend = value.ull;
return true;
}
@@ -694,17 +760,28 @@ bool Platform_getNetworkIO(NetworkIOData* data) {
memset(data, 0, sizeof(*data));
pmAtomValue value;
- if (PCPMetric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL)
+ if (Metric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL)
data->bytesReceived = value.ull;
- if (PCPMetric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL)
+ if (Metric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL)
data->bytesTransmitted = value.ull;
- if (PCPMetric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL)
+ if (Metric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL)
data->packetsReceived = value.ull;
- if (PCPMetric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL)
+ if (Metric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL)
data->packetsTransmitted = value.ull;
return true;
}
+void Platform_getFileDescriptors(double* used, double* max) {
+ *used = NAN;
+ *max = 65536;
+
+ pmAtomValue value;
+ if (Metric_values(PCP_VFS_FILES_COUNT, &value, 1, PM_TYPE_32) != NULL)
+ *used = value.l;
+ if (Metric_values(PCP_VFS_FILES_MAX, &value, 1, PM_TYPE_32) != NULL)
+ *max = value.l;
+}
+
void Platform_getBattery(double* level, ACPresence* isOnAC) {
*level = NAN;
*isOnAC = AC_ERROR;
@@ -725,7 +802,7 @@ CommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** a
case PLATFORM_LONGOPT_HOST: /* --host=HOSTSPEC */
if (argv[optind][0] == '\0')
return STATUS_ERROR_EXIT;
- __pmAddOptHost(&opts, optarg);
+ __pmAddOptHost(&opts, optarg);
return STATUS_OK;
case PLATFORM_LONGOPT_HOSTZONE: /* --hostzone */
@@ -751,6 +828,7 @@ CommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** a
default:
break;
}
+
return STATUS_ERROR_EXIT;
}
@@ -801,10 +879,10 @@ Hashtable* Platform_dynamicColumns(void) {
return pcp->columns.table;
}
-const char* Platform_dynamicColumnInit(unsigned int key) {
+const char* Platform_dynamicColumnName(unsigned int key) {
PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key);
if (this) {
- PCPMetric_enable(this->id, true);
+ Metric_enable(this->id, true);
if (this->super.caption)
return this->super.caption;
if (this->super.heading)
@@ -822,3 +900,25 @@ bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsi
}
return false;
}
+
+Hashtable* Platform_dynamicScreens(void) {
+ return pcp->screens.table;
+}
+
+void Platform_defaultDynamicScreens(Settings* settings) {
+ PCPDynamicScreen_appendScreens(&pcp->screens, settings);
+}
+
+void Platform_addDynamicScreen(ScreenSettings* ss) {
+ PCPDynamicScreen_addDynamicScreen(&pcp->screens, ss);
+}
+
+void Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen) {
+ Hashtable* screens = pcp->screens.table;
+ PCPDynamicScreens_addAvailableColumns(availableColumns, screens, screen);
+}
+
+void Platform_updateTables(Machine* host) {
+ PCPDynamicScreen_appendTables(&pcp->screens, host);
+ PCPDynamicColumns_setupWidths(&pcp->columns);
+}
diff --git a/pcp/Platform.h b/pcp/Platform.h
index dc51c736..f43ed54f 100644
--- a/pcp/Platform.h
+++ b/pcp/Platform.h
@@ -36,9 +36,10 @@ in the source distribution for its full text.
#include "SignalsPanel.h"
#include "CommandLine.h"
+#include "pcp/Metric.h"
#include "pcp/PCPDynamicColumn.h"
#include "pcp/PCPDynamicMeter.h"
-#include "pcp/PCPMetric.h"
+#include "pcp/PCPDynamicScreen.h"
typedef struct Platform_ {
@@ -51,6 +52,7 @@ typedef struct Platform_ {
pmResult* result; /* sample values result indexed by Metric */
PCPDynamicMeters meters; /* dynamic meters via configuration files */
PCPDynamicColumns columns; /* dynamic columns via configuration files */
+ PCPDynamicScreens screens; /* dynamic screens via configuration files */
struct timeval offset; /* time offset used in archive mode only */
long long btime; /* boottime in seconds since the epoch */
char* release; /* uname and distro from this context */
@@ -82,7 +84,7 @@ long long Platform_getBootTime(void);
unsigned int Platform_getMaxCPU(void);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, int cpu);
@@ -98,8 +100,6 @@ void Platform_setZfsCompressedArcValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred);
@@ -131,7 +131,9 @@ CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);
extern pmOptions opts;
-size_t Platform_addMetric(PCPMetric id, const char* name);
+size_t Platform_addMetric(Metric id, const char* name);
+
+void Platform_getFileDescriptors(double* used, double* max);
void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec);
@@ -151,8 +153,20 @@ Hashtable* Platform_dynamicColumns(void);
void Platform_dynamicColumnsDone(Hashtable* columns);
-const char* Platform_dynamicColumnInit(unsigned int key);
+const char* Platform_dynamicColumnName(unsigned int key);
bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key);
+Hashtable* Platform_dynamicScreens(void);
+
+void Platform_defaultDynamicScreens(Settings* settings);
+
+void Platform_addDynamicScreen(ScreenSettings* ss);
+
+void Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen);
+
+void Platform_dynamicScreensDone(Hashtable* screens);
+
+void Platform_updateTables(Machine* host);
+
#endif
diff --git a/pcp/ProcessField.h b/pcp/ProcessField.h
index b3ba2657..63425612 100644
--- a/pcp/ProcessField.h
+++ b/pcp/ProcessField.h
@@ -45,6 +45,9 @@ in the source distribution for its full text.
SECATTR = 123, \
AUTOGROUP_ID = 127, \
AUTOGROUP_NICE = 128, \
+ CCGROUP = 129, \
+ CONTAINER = 130, \
+ M_PRIV = 131, \
// End of list
diff --git a/pcp/screens/biosnoop b/pcp/screens/biosnoop
new file mode 100644
index 00000000..e6cdf894
--- /dev/null
+++ b/pcp/screens/biosnoop
@@ -0,0 +1,41 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[biosnoop]
+heading = BioSnoop
+caption = BPF block I/O snoop
+default = false
+
+pid.heading = PID
+pid.caption = Process identifier
+pid.metric = bpf.biosnoop.pid
+pid.format = process
+
+disk.heading = DISK
+disk.caption = Device name
+disk.width = -7
+disk.metric = bpf.biosnoop.disk
+
+rwbs.heading = TYPE
+rwbs.caption = I/O type string
+rwbs.width = -4
+rwbs.metric = bpf.biosnoop.rwbs
+
+bytes.heading = BYTES
+bytes.caption = I/O size in bytes
+bytes.metric = bpf.biosnoop.bytes
+
+lat.heading = LAT
+lat.caption = I/O latency
+lat.metric = bpf.biosnoop.lat
+
+sector.heading = SECTOR
+sector.caption = Device sector
+sector.metric = bpf.biosnoop.sector
+
+comm.heading = Command
+comm.caption = Process command name
+comm.width = -16
+comm.metric = bpf.biosnoop.comm
+comm.format = process
diff --git a/pcp/screens/cgroups b/pcp/screens/cgroups
new file mode 100644
index 00000000..0ddc65c4
--- /dev/null
+++ b/pcp/screens/cgroups
@@ -0,0 +1,45 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[cgroups]
+heading = CGroups
+caption = Control Groups
+default = true
+
+user_cpu.heading = UTIME
+user_cpu.caption = User CPU Time
+user_cpu.metric = 1000 * rate(cgroup.cpu.stat.user)
+user_cpu.width = 6
+
+system_cpu.heading = STIME
+system_cpu.caption = Kernel CPU Time
+system_cpu.metric = 1000 * rate(cgroup.cpu.stat.system)
+system_cpu.width = 6
+
+cpu_usage.heading = CPU%
+cpu_usage.caption = CPU Utilization
+cpu_usage.metric = 100 * (rate(cgroup.cpu.stat.usage) / hinv.ncpu)
+cpu_usage.format = percent
+
+cpu_psi.heading = CPU-PSI
+cpu_psi.caption = CPU Pressure Stall Information
+cpu_psi.metric = 1000 * rate(cgroup.pressure.cpu.some.total)
+cpu_psi.width = 7
+
+mem_psi.heading = MEM-PSI
+mem_psi.caption = Memory Pressure Stall Information
+mem_psi.metric = 1000 * rate(cgroup.pressure.memory.some.total)
+mem_psi.width = 7
+
+io_psi.heading = I/O-PSI
+io_psi.caption = I/O Pressure Stall Information
+io_psi.metric = 1000 * rate(cgroup.pressure.io.some.total)
+io_psi.width = 7
+
+name.heading = Control group
+name.caption = Control group name
+name.width = -64
+name.metric = cgroup.cpu.stat.system
+name.instances = true
+name.format = cgroup
diff --git a/pcp/screens/cgroupsio b/pcp/screens/cgroupsio
new file mode 100644
index 00000000..3a431db5
--- /dev/null
+++ b/pcp/screens/cgroupsio
@@ -0,0 +1,49 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[cgroupsio]
+heading = CGroupsIO
+caption = Control Groups I/O
+default = false
+
+iops.heading = IOPS
+iops.caption = I/O operations
+iops.metric = rate(cgroup.io.stat.rios) + rate(cgroup.io.stat.wios) + rate(cgroup.io.stat.dios)
+
+readops.heading = RDIO
+readops.caption = Read operations
+readops.metric = rate(cgroup.io.stat.rios)
+readops.default = false
+
+writeops.heading = WRIO
+writeops.caption = Write operations
+writeops.metric = rate(cgroup.io.stat.wios)
+writeops.default = false
+
+directops.heading = DIO
+directops.caption = Direct I/O operations
+directops.metric = rate(cgroup.io.stat.dios)
+directops.default = false
+
+totalbytes.heading = R/W/D
+totalbytes.caption = Disk throughput
+totalbytes.metric = rate(cgroup.io.stat.rbytes) + rate(cgroup.io.stat.wbytes) + rate(cgroup.io.stat.dbytes)
+
+readbytes.heading = RBYTE
+readbytes.caption = Disk read throughput
+readbytes.metric = rate(cgroup.io.stat.rbytes)
+
+writebytes.heading = WBYTE
+writebytes.caption = Disk throughput
+writebytes.metric = rate(cgroup.io.stat.wbytes)
+
+directio.heading = DBYTE
+directio.caption = Direct I/O throughput
+directio.metric = rate(cgroup.io.stat.dbytes)
+
+name.heading = Control group device
+name.caption = Control group device
+name.width = -64
+name.metric = cgroup.io.stat.rbytes
+name.instances = true
diff --git a/pcp/screens/cgroupsmem b/pcp/screens/cgroupsmem
new file mode 100644
index 00000000..17bc1e38
--- /dev/null
+++ b/pcp/screens/cgroupsmem
@@ -0,0 +1,48 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[cgroupsmem]
+heading = CGroupsMem
+caption = Control Groups Memory
+default = false
+
+current.heading = MEM
+current.caption = Current memory
+current.metric = cgroup.memory.current
+
+usage.heading = USAGE
+usage.caption = Memory usage
+usage.metric = cgroup.memory.usage
+
+container.heading = CONTAINER
+container.caption = Container Name
+container.metric = cgroup.memory.id.container
+
+resident.heading = RSS
+resident.metric = cgroup.memory.stat.rss
+
+cresident.heading = CRSS
+cresident.metric = cgroup.memory.stat.total.rss
+
+anonmem.heading = ANON
+anonmem.metric = cgroup.memory.stat.anon
+
+filemem.heading = FILE
+filemem.metric = cgroup.memory.stat.file
+
+shared.heading = SHMEM
+shared.metric = cgroup.memory.stat.shmem
+
+swap.heading = SWAP
+swap.metric = cgroup.memory.stat.swap
+
+pgfault.heading = FAULTS
+pgfault.metric = cgroup.memory.stat.pgfaults
+
+name.heading = Control group
+name.caption = Control group name
+name.width = -64
+name.metric = cgroup.memory.current
+name.instances = true
+name.format = cgroup
diff --git a/pcp/screens/devices b/pcp/screens/devices
new file mode 100644
index 00000000..7399f82f
--- /dev/null
+++ b/pcp/screens/devices
@@ -0,0 +1,114 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[disks]
+heading = Disks
+caption = Disk devices
+
+diskdev.heading = Device
+diskdev.metric = disk.dev.read
+diskdev.instances = true
+diskdev.format = device
+diskdev.width = -8
+
+total.heading = TPS
+total.metric = rate(disk.dev.read) + rate(disk.dev.write) + rate(disk.dev.discard)
+total.caption = Rate of read requests
+
+read.heading = RR/S
+read.metric = rate(disk.dev.read)
+read.caption = Rate of read requests
+
+read_bytes.heading = RRB/S
+read_bytes.metric = rate(disk.dev.read_bytes)
+read_bytes.caption = Read throughput from the device
+
+read_merge.heading = RRQM/S
+read_merge.metric = rate(disk.dev.read_merge)
+read_merge.caption = Rate reads merged before queued
+read_merge.default = false
+
+read_merge_pct.heading = RRQM%
+read_merge_pct.metric = 100 * rate(disk.dev.read_merge) / rate(disk.dev.read)
+read_merge_pct.caption = Percentage reads merged before queued
+read_merge_pct.format = percent
+
+read_await.heading = RAWAIT
+read_await.metric = disk.dev.r_await
+read_await.default = false
+
+read_avqsz.heading = RARQSZ
+read_avqsz.metric = disk.dev.r_avg_rqsz
+read_avqsz.default = false
+
+write.heading = WR/S
+write.metric = rate(disk.dev.write)
+write.caption = Rate of write requests
+
+write_bytes.heading = WRB/S
+write_bytes.metric = rate(disk.dev.write_bytes)
+write_bytes.caption = Write throughput to the device
+
+write_merge.heading = WRQM/S
+write_merge.metric = rate(disk.dev.write_merge)
+write_merge.caption = Rate writes merged before queued
+write_merge.default = false
+
+write_merge_pct.heading = WRQM%
+write_merge_pct.metric = 100 * rate(disk.dev.write_merge) / rate(disk.dev.write)
+write_merge_pct.caption = Percentage writes merged before queued
+write_merge_pct.format = percent
+
+write_await.heading = WAWAIT
+write_await.metric = disk.dev.w_await
+write_await.default = false
+
+write_avqsz.heading = WARQSZ
+write_avqsz.metric = disk.dev.w_avg_rqsz
+write_avqsz.default = false
+
+discard.heading = DR/S
+discard.metric = rate(disk.dev.discard)
+discard.caption = Rate of discard requests
+
+discard_bytes.heading = DRB/S
+discard_bytes.metric = rate(disk.dev.discard_bytes)
+discard_bytes.caption = Discard request throughput
+discard_bytes.default = false
+
+discard_merge.heading = DRQM/S
+discard_merge.metric = rate(disk.dev.discard_merge)
+discard_merge.caption = Rate discards merged before queued
+discard_merge.default = false
+
+discard_merge_pct.heading = DRQM%
+discard_merge_pct.metric = 100 * rate(disk.dev.discard_merge) / rate(disk.dev.discard)
+discard_merge_pct.caption = Percentage discards merged before queued
+discard_merge_pct.format = percent
+discard_merge_pct.default = false
+
+discard_await.heading = DAWAIT
+discard_await.metric = disk.dev.d_await
+discard_await.default = false
+
+discard_avqsz.heading = DARQSZ
+discard_avqsz.metric = disk.dev.d_avg_rqsz
+discard_avqsz.default = false
+
+flush.heading = F/S
+flush.metric = rate(disk.dev.flush)
+flush.default = false
+flush.caption = Flushes per second
+
+flush_await.heading = FAWAIT
+flush_await.metric = disk.dev.f_await
+flush_await.default = false
+
+qlen.heading = AQU-SZ
+qlen.metric = disk.dev.avg_qlen
+
+util.heading = UTIL%
+util.metric = 100 * disk.dev.util
+util.caption = Perentage device utilization
+util.format = percent
diff --git a/pcp/screens/execsnoop b/pcp/screens/execsnoop
new file mode 100644
index 00000000..d706e764
--- /dev/null
+++ b/pcp/screens/execsnoop
@@ -0,0 +1,37 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[execsnoop]
+heading = ExecSnoop
+caption = BPF exec(2) syscall snoop
+default = false
+
+pid.heading = PID
+pid.caption = Process Identifier
+pid.metric = bpf.execsnoop.pid
+pid.format = process
+
+ppid.heading = PPID
+ppid.caption = Parent Process
+ppid.metric = bpf.execsnoop.ppid
+ppid.format = process
+
+uid.heading = UID
+uid.caption = User Identifier
+uid.metric = bpf.execsnoop.uid
+
+comm.heading = COMM
+comm.caption = Command
+comm.width = -16
+comm.metric = bpf.execsnoop.comm
+comm.format = command
+
+ret.heading = RET
+ret.caption = Return Code
+ret.metric = bpf.execsnoop.ret
+
+path.heading = Arguments
+path.caption = Arguments
+path.width = -12
+path.metric = bpf.execsnoop.args
diff --git a/pcp/screens/exitsnoop b/pcp/screens/exitsnoop
new file mode 100644
index 00000000..6c6b867c
--- /dev/null
+++ b/pcp/screens/exitsnoop
@@ -0,0 +1,48 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[exitsnoop]
+heading = ExitSnoop
+caption = BPF process exit(2) snoop
+default = false
+
+pid.heading = PID
+pid.caption = Process Identifier
+pid.metric = bpf.exitsnoop.pid
+pid.format = process
+
+ppid.heading = PPID
+ppid.caption = Parent Process
+ppid.metric = bpf.exitsnoop.ppid
+ppid.format = process
+
+tid.heading = TID
+tid.caption = Task Identifier
+tid.metric = bpf.exitsnoop.tid
+tid.format = process
+tid.default = false
+
+signal.heading = SIG
+signal.caption = Signal number
+signal.metric = bpf.exitsnoop.sig
+
+exit.heading = EXIT
+exit.caption = Exit Code
+exit.metric = bpf.exitsnoop.exit_code
+
+core.heading = CORE
+core.caption = Dumped core
+core.metric = bpf.exitsnoop.coredump
+core.default = false
+
+age.heading = AGE
+age.caption = Process age
+age.metric = bpf.exitsnoop.age
+age.default = false
+
+comm.heading = Command
+comm.caption = COMM
+comm.width = -16
+comm.metric = bpf.exitsnoop.comm
+comm.format = command
diff --git a/pcp/screens/filesystems b/pcp/screens/filesystems
new file mode 100644
index 00000000..06f3bf23
--- /dev/null
+++ b/pcp/screens/filesystems
@@ -0,0 +1,50 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[filesystems]
+heading = Filesystems
+caption = Mounted block device filesystems
+
+blockdev.heading = Device
+blockdev.metric = filesys.mountdir
+blockdev.instances = true
+blockdev.width = -14
+
+blocksize.heading = BSIZE
+blocksize.metric = filesys.blocksize
+blocksize.default = false
+
+capacity.heading = SIZE
+capacity.metric = filesys.capacity
+
+used.heading = USED
+used.metric = filesys.used
+
+free.heading = FREE
+free.metric = filesys.free
+free.default = false
+
+avail.heading = AVAIL
+avail.metric = filesys.avail
+
+full.heading = USE%
+full.metric = filesys.full
+full.format = percent
+
+usedfiles.heading = USEDF
+usedfiles.metric = filesys.usedfiles
+usedfiles.default = false
+
+freefiles.heading = FREEF
+freefiles.metric = filesys.freefiles
+freefiles.default = false
+
+maxfiles.heading = MAXF
+maxfiles.metric = filesys.maxfiles
+maxfiles.default = false
+
+mountdir.heading = Mount point
+mountdir.metric = filesys.mountdir
+mountdir.format = path
+mountdir.width = -33
diff --git a/pcp/screens/opensnoop b/pcp/screens/opensnoop
new file mode 100644
index 00000000..ec209b03
--- /dev/null
+++ b/pcp/screens/opensnoop
@@ -0,0 +1,27 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[opensnoop]
+heading = OpenSnoop
+caption = BPF open(2) syscall snoop
+default = false
+
+pid.heading = PID
+pid.metric = bpf.opensnoop.pid
+pid.format = process
+
+comm.heading = COMM
+comm.metric = bpf.opensnoop.comm
+comm.format = command
+
+fd.heading = FD
+fd.metric = bpf.opensnoop.fd
+
+err.heading = ERR
+err.metric = bpf.opensnoop.err
+
+file.heading = File name
+file.width = -32
+file.metric = bpf.opensnoop.fname
+file.format = path
diff --git a/solaris/Platform.c b/solaris/Platform.c
index 9c5acb5b..3934f789 100644
--- a/solaris/Platform.c
+++ b/solaris/Platform.c
@@ -7,6 +7,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 "solaris/Platform.h"
#include <kstat.h>
@@ -34,10 +36,12 @@ in the source distribution for its full text.
#include "HostnameMeter.h"
#include "SysArchMeter.h"
#include "UptimeMeter.h"
+#include "XUtils.h"
+
+#include "solaris/SolarisMachine.h"
+
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsCompressedArcMeter.h"
-#include "SolarisProcess.h"
-#include "SolarisProcessList.h"
const ScreenDefaults Platform_defaultScreens[] = {
@@ -144,7 +148,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
int boot_time = 0;
int curr_time = time(NULL);
struct utmpx* ent;
@@ -173,7 +177,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = plat_loadavg[LOADAVG_15MIN];
}
-int Platform_getMaxPid() {
+pid_t Platform_getMaxPid(void) {
int vproc = 32778; // Reasonable Solaris default
kstat_ctl_t* kc = kstat_open();
@@ -194,15 +198,16 @@ int Platform_getMaxPid() {
}
double Platform_setCPUValues(Meter* this, unsigned int cpu) {
- const SolarisProcessList* spl = (const SolarisProcessList*) this->pl;
- unsigned int cpus = this->pl->existingCPUs;
+ const Machine* host = this->host;
+ const SolarisMachine* shost = (const SolarisMachine*) host;
+ unsigned int cpus = host->existingCPUs;
const CPUData* cpuData = NULL;
if (cpus == 1) {
// single CPU box has everything in spl->cpus[0]
- cpuData = &(spl->cpus[0]);
+ cpuData = &(shost->cpus[0]);
} else {
- cpuData = &(spl->cpus[cpu]);
+ cpuData = &(shost->cpus[cpu]);
}
if (!cpuData->online) {
@@ -215,18 +220,17 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
v[CPU_METER_NICE] = cpuData->nicePercent;
v[CPU_METER_NORMAL] = cpuData->userPercent;
- if (this->pl->settings->detailedCPUTime) {
+ if (host->settings->detailedCPUTime) {
v[CPU_METER_KERNEL] = cpuData->systemPercent;
v[CPU_METER_IRQ] = cpuData->irqPercent;
this->curItems = 4;
- percent = v[0] + v[1] + v[2] + v[3];
} else {
- v[2] = cpuData->systemAllPercent;
+ v[CPU_METER_KERNEL] = cpuData->systemAllPercent;
this->curItems = 3;
- percent = v[0] + v[1] + v[2];
}
- percent = isnan(percent) ? 0.0 : CLAMP(percent, 0.0, 100.0);
+ percent = sumPositiveValues(v, this->curItems);
+ percent = MINIMUM(percent, 100.0);
v[CPU_METER_FREQUENCY] = cpuData->frequency;
v[CPU_METER_TEMPERATURE] = NAN;
@@ -235,32 +239,34 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) {
}
void Platform_setMemoryValues(Meter* this) {
- const ProcessList* pl = this->pl;
- this->total = pl->totalMem;
- this->values[0] = pl->usedMem;
- this->values[1] = pl->buffersMem;
- // this->values[2] = "shared memory, like tmpfs and shm"
- this->values[3] = pl->cachedMem;
- // this->values[4] = "available memory"
+ const Machine* host = this->host;
+ this->total = host->totalMem;
+ this->values[MEMORY_METER_USED] = host->usedMem;
+ // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux"
+ this->values[MEMORY_METER_BUFFERS] = host->buffersMem;
+ this->values[MEMORY_METER_CACHE] = host->cachedMem;
+ // this->values[MEMORY_METER_AVAILABLE] = "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;
+ const Machine* host = this->host;
+ this->total = host->totalSwap;
+ this->values[SWAP_METER_USED] = host->usedSwap;
+ // this->values[SWAP_METER_CACHE] = "pages that are both in swap and RAM, like SwapCached on linux"
+ // this->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux"
}
void Platform_setZfsArcValues(Meter* this) {
- const SolarisProcessList* spl = (const SolarisProcessList*) this->pl;
+ const SolarisMachine* shost = (const SolarisMachine*) this->host;
- ZfsArcMeter_readStats(this, &(spl->zfs));
+ ZfsArcMeter_readStats(this, &shost->zfs);
}
void Platform_setZfsCompressedArcValues(Meter* this) {
- const SolarisProcessList* spl = (const SolarisProcessList*) this->pl;
+ const SolarisMachine* shost = (const SolarisMachine*) this->host;
- ZfsCompressedArcMeter_readStats(this, &(spl->zfs));
+ ZfsCompressedArcMeter_readStats(this, &shost->zfs);
}
static int Platform_buildenv(void* accum, struct ps_prochandle* Phandle, uintptr_t addr, const char* str) {
@@ -308,15 +314,14 @@ char* Platform_getProcessEnv(pid_t pid) {
return xRealloc(envBuilder.env, envBuilder.size + 1);
}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
(void)pid;
- (void)inode;
return NULL;
}
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+void Platform_getFileDescriptors(double* used, double* max) {
+ *used = NAN;
+ *max = NAN;
}
bool Platform_getDiskIO(DiskIOData* data) {
diff --git a/solaris/Platform.h b/solaris/Platform.h
index 1b3dc9f5..1a31c2e7 100644
--- a/solaris/Platform.h
+++ b/solaris/Platform.h
@@ -9,8 +9,6 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <kstat.h>
/* On OmniOS /usr/include/sys/regset.h redefines ERR to 13 - \r, breaking the Enter key.
@@ -30,12 +28,12 @@ in the source distribution for its full text.
#include "Action.h"
#include "BatteryMeter.h"
+#include "CommandLine.h"
#include "DiskIOMeter.h"
#include "Hashtable.h"
#include "NetworkIOMeter.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
-#include "CommandLine.h"
#include "generic/gettime.h"
#include "generic/hostname.h"
#include "generic/uname.h"
@@ -72,7 +70,7 @@ int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, unsigned int cpu);
@@ -86,10 +84,10 @@ void Platform_setZfsCompressedArcValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+void Platform_getFileDescriptors(double* used, double* max);
+
bool Platform_getDiskIO(DiskIOData* data);
bool Platform_getNetworkIO(NetworkIOData* data);
@@ -132,7 +130,9 @@ IGNORE_WCASTQUAL_BEGIN
IGNORE_WCASTQUAL_END
}
-static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
@@ -142,12 +142,30 @@ 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 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 const char* Platform_dynamicColumnName(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;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
-static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
#endif
diff --git a/solaris/SolarisMachine.c b/solaris/SolarisMachine.c
new file mode 100644
index 00000000..5b2bdcd4
--- /dev/null
+++ b/solaris/SolarisMachine.c
@@ -0,0 +1,340 @@
+/*
+htop - SolarisMachine.c
+(C) 2014 Hisham H. Muhammad
+(C) 2017,2018 Guy M. Broome
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "solaris/SolarisMachine.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/user.h>
+#include <limits.h>
+#include <string.h>
+#include <procfs.h>
+#include <errno.h>
+#include <pwd.h>
+#include <math.h>
+#include <time.h>
+
+#include "CRT.h"
+#include "solaris/Platform.h"
+
+
+static void SolarisMachine_updateCPUcount(SolarisMachine* this) {
+ Machine* super = &this->super;
+ long int s;
+ bool change = false;
+
+ s = sysconf(_SC_NPROCESSORS_CONF);
+ if (s < 1)
+ CRT_fatalError("Cannot get existing CPU count by sysconf(_SC_NPROCESSORS_CONF)");
+
+ if (s != super->existingCPUs) {
+ if (s == 1) {
+ this->cpus = xRealloc(this->cpus, sizeof(CPUData));
+ this->cpus[0].online = true;
+ } else {
+ this->cpus = xReallocArray(this->cpus, s + 1, sizeof(CPUData));
+ this->cpus[0].online = true; /* average is always "online" */
+ for (int i = 1; i < s + 1; i++) {
+ this->cpus[i].online = false;
+ }
+ }
+
+ change = true;
+ super->existingCPUs = s;
+ }
+
+ s = sysconf(_SC_NPROCESSORS_ONLN);
+ if (s < 1)
+ CRT_fatalError("Cannot get active CPU count by sysconf(_SC_NPROCESSORS_ONLN)");
+
+ if (s != super->activeCPUs) {
+ change = true;
+ super->activeCPUs = s;
+ }
+
+ if (change) {
+ kstat_close(this->kd);
+ this->kd = kstat_open();
+ if (!this->kd)
+ CRT_fatalError("Cannot open kstat handle");
+ }
+}
+
+
+static void SolarisMachine_scanCPUTime(SolarisMachine* this) {
+ Machine* super = &this->super;
+ unsigned int activeCPUs = super->activeCPUs;
+ unsigned int existingCPUs = super->existingCPUs;
+ kstat_t* cpuinfo = NULL;
+ kstat_named_t* idletime = NULL;
+ kstat_named_t* intrtime = NULL;
+ kstat_named_t* krnltime = NULL;
+ kstat_named_t* usertime = NULL;
+ kstat_named_t* cpu_freq = NULL;
+ double idlebuf = 0;
+ double intrbuf = 0;
+ double krnlbuf = 0;
+ double userbuf = 0;
+ int arrskip = 0;
+
+ assert(existingCPUs > 0);
+ assert(this->kd);
+
+ if (existingCPUs > 1) {
+ // Store values for the stats loop one extra element up in the array
+ // to leave room for the average to be calculated afterwards
+ arrskip++;
+ }
+
+ // Calculate per-CPU statistics first
+ for (unsigned int i = 0; i < existingCPUs; i++) {
+ CPUData* cpuData = &(this->cpus[i + arrskip]);
+
+ if ((cpuinfo = kstat_lookup_wrapper(this->kd, "cpu", i, "sys")) != NULL) {
+ cpuData->online = true;
+ if (kstat_read(this->kd, cpuinfo, NULL) != -1) {
+ idletime = kstat_data_lookup_wrapper(cpuinfo, "cpu_nsec_idle");
+ intrtime = kstat_data_lookup_wrapper(cpuinfo, "cpu_nsec_intr");
+ krnltime = kstat_data_lookup_wrapper(cpuinfo, "cpu_nsec_kernel");
+ usertime = kstat_data_lookup_wrapper(cpuinfo, "cpu_nsec_user");
+ }
+ } else {
+ cpuData->online = false;
+ continue;
+ }
+
+ assert( (idletime != NULL) && (intrtime != NULL)
+ && (krnltime != NULL) && (usertime != NULL) );
+
+ if (super->settings->showCPUFrequency) {
+ if ((cpuinfo = kstat_lookup_wrapper(this->kd, "cpu_info", i, NULL)) != NULL) {
+ if (kstat_read(this->kd, cpuinfo, NULL) != -1) {
+ cpu_freq = kstat_data_lookup_wrapper(cpuinfo, "current_clock_Hz");
+ }
+ }
+
+ assert( cpu_freq != NULL );
+ }
+
+ uint64_t totaltime = (idletime->value.ui64 - cpuData->lidle)
+ + (intrtime->value.ui64 - cpuData->lintr)
+ + (krnltime->value.ui64 - cpuData->lkrnl)
+ + (usertime->value.ui64 - cpuData->luser);
+
+ // Calculate percentages of deltas since last reading
+ cpuData->userPercent = ((usertime->value.ui64 - cpuData->luser) / (double)totaltime) * 100.0;
+ cpuData->nicePercent = (double)0.0; // Not implemented on Solaris
+ cpuData->systemPercent = ((krnltime->value.ui64 - cpuData->lkrnl) / (double)totaltime) * 100.0;
+ cpuData->irqPercent = ((intrtime->value.ui64 - cpuData->lintr) / (double)totaltime) * 100.0;
+ cpuData->systemAllPercent = cpuData->systemPercent + cpuData->irqPercent;
+ cpuData->idlePercent = ((idletime->value.ui64 - cpuData->lidle) / (double)totaltime) * 100.0;
+ // Store current values to use for the next round of deltas
+ cpuData->luser = usertime->value.ui64;
+ cpuData->lkrnl = krnltime->value.ui64;
+ cpuData->lintr = intrtime->value.ui64;
+ cpuData->lidle = idletime->value.ui64;
+ // Add frequency in MHz
+ cpuData->frequency = super->settings->showCPUFrequency ? (double)cpu_freq->value.ui64 / 1E6 : NAN;
+ // Accumulate the current percentages into buffers for later average calculation
+ if (existingCPUs > 1) {
+ userbuf += cpuData->userPercent;
+ krnlbuf += cpuData->systemPercent;
+ intrbuf += cpuData->irqPercent;
+ idlebuf += cpuData->idlePercent;
+ }
+ }
+
+ if (existingCPUs > 1) {
+ CPUData* cpuData = &(this->cpus[0]);
+ cpuData->userPercent = userbuf / activeCPUs;
+ cpuData->nicePercent = (double)0.0; // Not implemented on Solaris
+ cpuData->systemPercent = krnlbuf / activeCPUs;
+ cpuData->irqPercent = intrbuf / activeCPUs;
+ cpuData->systemAllPercent = cpuData->systemPercent + cpuData->irqPercent;
+ cpuData->idlePercent = idlebuf / activeCPUs;
+ }
+}
+
+static void SolarisMachine_scanMemoryInfo(SolarisMachine* this) {
+ Machine* super = &this->super;
+ static kstat_t *meminfo = NULL;
+ int ksrphyserr = -1;
+ kstat_named_t *totalmem_pgs = NULL;
+ kstat_named_t *freemem_pgs = NULL;
+ kstat_named_t *pages = NULL;
+ struct swaptable *sl = NULL;
+ struct swapent *swapdev = NULL;
+ uint64_t totalswap = 0;
+ uint64_t totalfree = 0;
+ int nswap = 0;
+ char *spath = NULL;
+ char *spathbase = NULL;
+
+ // Part 1 - physical memory
+ if (this->kd != NULL && meminfo == NULL) {
+ // Look up the kstat chain just once, it never changes
+ meminfo = kstat_lookup_wrapper(this->kd, "unix", 0, "system_pages");
+ }
+ if (meminfo != NULL) {
+ ksrphyserr = kstat_read(this->kd, meminfo, NULL);
+ }
+ if (ksrphyserr != -1) {
+ totalmem_pgs = kstat_data_lookup_wrapper(meminfo, "physmem");
+ freemem_pgs = kstat_data_lookup_wrapper(meminfo, "freemem");
+ pages = kstat_data_lookup_wrapper(meminfo, "pagestotal");
+
+ super->totalMem = totalmem_pgs->value.ui64 * this->pageSizeKB;
+ if (super->totalMem > freemem_pgs->value.ui64 * this->pageSizeKB) {
+ super->usedMem = super->totalMem - freemem_pgs->value.ui64 * this->pageSizeKB;
+ } else {
+ super->usedMem = 0; // This can happen in non-global zone (in theory)
+ }
+ // Not sure how to implement this on Solaris - suggestions welcome!
+ super->cachedMem = 0;
+ // Not really "buffers" but the best Solaris analogue that I can find to
+ // "memory in use but not by programs or the kernel itself"
+ super->buffersMem = (totalmem_pgs->value.ui64 - pages->value.ui64) * this->pageSizeKB;
+ } else {
+ // Fall back to basic sysconf if kstat isn't working
+ super->totalMem = sysconf(_SC_PHYS_PAGES) * this->pageSize;
+ super->buffersMem = 0;
+ super->cachedMem = 0;
+ super->usedMem = super->totalMem - (sysconf(_SC_AVPHYS_PAGES) * this->pageSize);
+ }
+
+ // Part 2 - swap
+ nswap = swapctl(SC_GETNSWP, NULL);
+ if (nswap > 0) {
+ sl = xMalloc((nswap * sizeof(swapent_t)) + sizeof(int));
+ }
+ if (sl != NULL) {
+ spathbase = xMalloc( nswap * MAXPATHLEN );
+ }
+ if (spathbase != NULL) {
+ spath = spathbase;
+ swapdev = sl->swt_ent;
+ for (int i = 0; i < nswap; i++, swapdev++) {
+ swapdev->ste_path = spath;
+ spath += MAXPATHLEN;
+ }
+ sl->swt_n = nswap;
+ }
+ nswap = swapctl(SC_LIST, sl);
+ if (nswap > 0) {
+ swapdev = sl->swt_ent;
+ for (int i = 0; i < nswap; i++, swapdev++) {
+ totalswap += swapdev->ste_pages;
+ totalfree += swapdev->ste_free;
+ }
+ }
+ free(spathbase);
+ free(sl);
+ super->totalSwap = totalswap * this->pageSizeKB;
+ super->usedSwap = super->totalSwap - (totalfree * this->pageSizeKB);
+}
+
+static void SolarisMachine_scanZfsArcstats(SolarisMachine* this) {
+ kstat_named_t *cur_kstat;
+ kstat_t *arcstats;
+ int ksrphyserr;
+
+ if (this->kd == NULL)
+ return;
+
+ arcstats = kstat_lookup_wrapper(this->kd, "zfs", 0, "arcstats");
+ if (arcstats == NULL)
+ return;
+
+ ksrphyserr = kstat_read(this->kd, arcstats, NULL);
+ if (ksrphyserr == -1)
+ return;
+
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "size" );
+ this->zfs.size = cur_kstat->value.ui64 / 1024;
+ this->zfs.enabled = this->zfs.size > 0 ? 1 : 0;
+
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "c_max" );
+ this->zfs.max = cur_kstat->value.ui64 / 1024;
+
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "mfu_size" );
+ this->zfs.MFU = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
+
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "mru_size" );
+ this->zfs.MRU = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
+
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "anon_size" );
+ this->zfs.anon = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
+
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "hdr_size" );
+ this->zfs.header = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
+
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "other_size" );
+ this->zfs.other = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
+
+ if ((cur_kstat = kstat_data_lookup_wrapper( arcstats, "compressed_size" )) != NULL) {
+ this->zfs.compressed = cur_kstat->value.ui64 / 1024;
+ this->zfs.isCompressed = 1;
+
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "uncompressed_size" );
+ this->zfs.uncompressed = cur_kstat->value.ui64 / 1024;
+ } else {
+ this->zfs.isCompressed = 0;
+ }
+}
+
+void Machine_scan(Machine* super) {
+ SolarisMachine* this = (SolarisMachine*) super;
+
+ SolarisMachine_updateCPUcount(this);
+ SolarisMachine_scanCPUTime(this);
+ SolarisMachine_scanMemoryInfo(this);
+ SolarisMachine_scanZfsArcstats(this);
+}
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ SolarisMachine* this = xCalloc(1, sizeof(SolarisMachine));
+ Machine* super = &this->super;
+
+ Machine_init(super, usersTable, userId);
+
+ this->pageSize = sysconf(_SC_PAGESIZE);
+ if (this->pageSize == -1)
+ CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)");
+ this->pageSizeKB = this->pageSize / 1024;
+
+ this->kd = kstat_open();
+ if (!this->kd)
+ CRT_fatalError("Cannot open kstat handle");
+
+ SolarisMachine_updateCPUcount(this);
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ SolarisMachine* this = (SolarisMachine*) super;
+
+ Machine_done(super);
+
+ free(this->cpus);
+ if (this->kd) {
+ kstat_close(this->kd);
+ }
+ free(this);
+}
+
+bool Machine_isCPUonline(const Machine* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ const SolarisMachine* this = (const SolarisMachine*) super;
+
+ return (super->existingCPUs == 1) ? true : this->cpus[id + 1].online;
+}
diff --git a/solaris/SolarisProcessList.h b/solaris/SolarisMachine.h
index 91fd4f41..b350e919 100644
--- a/solaris/SolarisProcessList.h
+++ b/solaris/SolarisMachine.h
@@ -1,31 +1,27 @@
-#ifndef HEADER_SolarisProcessList
-#define HEADER_SolarisProcessList
+#ifndef HEADER_SolarisMachine
+#define HEADER_SolarisMachine
/*
-htop - SolarisProcessList.h
+htop - SolarisMachine.h
(C) 2014 Hisham H. Muhammad
(C) 2017,2018 Guy M. Broome
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <kstat.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/param.h>
-#include <sys/uio.h>
#include <sys/resource.h>
+#include <sys/swap.h>
#include <sys/sysconf.h>
#include <sys/sysinfo.h>
-#include <sys/swap.h>
+#include <sys/uio.h>
#include "Hashtable.h"
-#include "ProcessList.h"
+#include "Machine.h"
#include "UsersTable.h"
-#include "solaris/SolarisProcess.h"
-
#include "zfs/ZfsArcStats.h"
@@ -47,19 +43,16 @@ typedef struct CPUData_ {
bool online;
} CPUData;
-typedef struct SolarisProcessList_ {
- ProcessList super;
+typedef struct SolarisMachine_ {
+ Machine super;
+
kstat_ctl_t* kd;
CPUData* cpus;
- ZfsArcStats zfs;
-} SolarisProcessList;
-
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
-void ProcessList_delete(ProcessList* pl);
+ int pageSize;
+ int pageSizeKB;
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+ ZfsArcStats zfs;
+} SolarisMachine;
#endif
diff --git a/solaris/SolarisProcess.c b/solaris/SolarisProcess.c
index 840757e7..29e981df 100644
--- a/solaris/SolarisProcess.c
+++ b/solaris/SolarisProcess.c
@@ -6,6 +6,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 "solaris/SolarisProcess.h"
#include <stdlib.h>
@@ -13,8 +15,7 @@ in the source distribution for its full text.
#include <unistd.h>
#include <sys/syscall.h>
-#include "Process.h"
-#include "ProcessList.h"
+#include "ProcessTable.h"
#include "CRT.h"
#include "solaris/Platform.h"
@@ -40,7 +41,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@@ -59,11 +60,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[LWPID] = { .name = "LWPID", .title = "LWPID", .description = "LWP ID", .flags = 0, .pidColumn = true, },
};
-Process* SolarisProcess_new(const Settings* settings) {
+Process* SolarisProcess_new(const Machine* host) {
SolarisProcess* this = xCalloc(1, sizeof(SolarisProcess));
Object_setClass(this, Class(SolarisProcess));
- Process_init(&this->super, settings);
- return &this->super;
+ Process_init(&this->super, host);
+ return (Process*)this;
}
void Process_delete(Object* cast) {
@@ -73,11 +74,13 @@ void Process_delete(Object* cast) {
free(sp);
}
-static void SolarisProcess_writeField(const Process* this, RichString* str, ProcessField field) {
- const SolarisProcess* sp = (const SolarisProcess*) this;
+static void SolarisProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const SolarisProcess* sp = (const SolarisProcess*) super;
+
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
- int n = sizeof(buffer) - 1;
+ size_t n = sizeof(buffer) - 1;
+
switch (field) {
// add Solaris-specific fields here
case ZONEID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->zoneid); break;
@@ -85,15 +88,16 @@ static void SolarisProcess_writeField(const Process* this, RichString* str, Proc
case TASKID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->taskid); break;
case POOLID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->poolid); break;
case CONTID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->contid); break;
- case ZONE: Process_printLeftAlignedField(str, attr, sp->zname ? sp->zname : "global", ZONENAME_MAX/4); return;
+ case ZONE: Row_printLeftAlignedField(str, attr, sp->zname ? sp->zname : "global", ZONENAME_MAX/4); return;
case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realpid); break;
case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realppid); break;
case TGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realtgid); break;
case LWPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->lwpid); break;
default:
- Process_writeField(this, str, field);
+ Process_writeField(&sp->super, str, field);
return;
}
+
RichString_appendWide(str, attr, buffer);
}
@@ -127,11 +131,18 @@ static int SolarisProcess_compareByKey(const Process* v1, const Process* v2, Pro
const ProcessClass SolarisProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = SolarisProcess_rowWriteField
},
- .writeField = SolarisProcess_writeField,
.compareByKey = SolarisProcess_compareByKey
};
diff --git a/solaris/SolarisProcess.h b/solaris/SolarisProcess.h
index 13a2bc1f..292a05f2 100644
--- a/solaris/SolarisProcess.h
+++ b/solaris/SolarisProcess.h
@@ -8,8 +8,6 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h" // IWYU pragma: keep
-
#include <zone.h>
#include <sys/proc.h>
@@ -21,7 +19,8 @@ in the source distribution for its full text.
#undef ERR
#define ERR (-1)
-#include "Settings.h"
+#include "Machine.h"
+#include "Process.h"
typedef struct SolarisProcess_ {
@@ -42,7 +41,7 @@ extern const ProcessClass SolarisProcess_class;
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-Process* SolarisProcess_new(const Settings* settings);
+Process* SolarisProcess_new(const Machine* host);
void Process_delete(Object* cast);
diff --git a/solaris/SolarisProcessList.c b/solaris/SolarisProcessList.c
deleted file mode 100644
index 905cfbd4..00000000
--- a/solaris/SolarisProcessList.c
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
-htop - SolarisProcessList.c
-(C) 2014 Hisham H. Muhammad
-(C) 2017,2018 Guy M. Broome
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-
-#include "solaris/SolarisProcessList.h"
-
-#include <unistd.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/user.h>
-#include <limits.h>
-#include <string.h>
-#include <procfs.h>
-#include <errno.h>
-#include <pwd.h>
-#include <math.h>
-#include <time.h>
-
-#include "CRT.h"
-#include "solaris/Platform.h"
-#include "solaris/SolarisProcess.h"
-
-
-#define GZONE "global "
-#define UZONE "unknown "
-
-static int pageSize;
-static int pageSizeKB;
-
-static char* SolarisProcessList_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) {
- char* zname;
-
- if ( sproc->zoneid == 0 ) {
- zname = xStrdup(GZONE);
- } else if ( kd == NULL ) {
- zname = xStrdup(UZONE);
- } else {
- kstat_t* ks = kstat_lookup_wrapper( kd, "zones", sproc->zoneid, NULL );
- zname = xStrdup(ks == NULL ? UZONE : ks->ks_name);
- }
-
- return zname;
-}
-
-static void SolarisProcessList_updateCPUcount(ProcessList* super) {
- SolarisProcessList* spl = (SolarisProcessList*) super;
- long int s;
- bool change = false;
-
- s = sysconf(_SC_NPROCESSORS_CONF);
- if (s < 1)
- CRT_fatalError("Cannot get existing CPU count by sysconf(_SC_NPROCESSORS_CONF)");
-
- if (s != super->existingCPUs) {
- if (s == 1) {
- spl->cpus = xRealloc(spl->cpus, sizeof(CPUData));
- spl->cpus[0].online = true;
- } else {
- spl->cpus = xReallocArray(spl->cpus, s + 1, sizeof(CPUData));
- spl->cpus[0].online = true; /* average is always "online" */
- for (int i = 1; i < s + 1; i++) {
- spl->cpus[i].online = false;
- }
- }
-
- change = true;
- super->existingCPUs = s;
- }
-
- s = sysconf(_SC_NPROCESSORS_ONLN);
- if (s < 1)
- CRT_fatalError("Cannot get active CPU count by sysconf(_SC_NPROCESSORS_ONLN)");
-
- if (s != super->activeCPUs) {
- change = true;
- super->activeCPUs = s;
- }
-
- if (change) {
- kstat_close(spl->kd);
- spl->kd = kstat_open();
- if (!spl->kd)
- CRT_fatalError("Cannot open kstat handle");
- }
-}
-
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
- SolarisProcessList* spl = xCalloc(1, sizeof(SolarisProcessList));
- ProcessList* pl = (ProcessList*) spl;
- ProcessList_init(pl, Class(SolarisProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
-
- spl->kd = kstat_open();
- if (!spl->kd)
- CRT_fatalError("Cannot open kstat handle");
-
- pageSize = sysconf(_SC_PAGESIZE);
- if (pageSize == -1)
- CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)");
- pageSizeKB = pageSize / 1024;
-
- SolarisProcessList_updateCPUcount(pl);
-
- return pl;
-}
-
-static inline void SolarisProcessList_scanCPUTime(ProcessList* pl) {
- const SolarisProcessList* spl = (SolarisProcessList*) pl;
- unsigned int activeCPUs = pl->activeCPUs;
- unsigned int existingCPUs = pl->existingCPUs;
- kstat_t* cpuinfo = NULL;
- kstat_named_t* idletime = NULL;
- kstat_named_t* intrtime = NULL;
- kstat_named_t* krnltime = NULL;
- kstat_named_t* usertime = NULL;
- kstat_named_t* cpu_freq = NULL;
- double idlebuf = 0;
- double intrbuf = 0;
- double krnlbuf = 0;
- double userbuf = 0;
- int arrskip = 0;
-
- assert(existingCPUs > 0);
- assert(spl->kd);
-
- if (existingCPUs > 1) {
- // Store values for the stats loop one extra element up in the array
- // to leave room for the average to be calculated afterwards
- arrskip++;
- }
-
- // Calculate per-CPU statistics first
- for (unsigned int i = 0; i < existingCPUs; i++) {
- CPUData* cpuData = &(spl->cpus[i + arrskip]);
-
- if ((cpuinfo = kstat_lookup_wrapper(spl->kd, "cpu", i, "sys")) != NULL) {
- cpuData->online = true;
- if (kstat_read(spl->kd, cpuinfo, NULL) != -1) {
- idletime = kstat_data_lookup_wrapper(cpuinfo, "cpu_nsec_idle");
- intrtime = kstat_data_lookup_wrapper(cpuinfo, "cpu_nsec_intr");
- krnltime = kstat_data_lookup_wrapper(cpuinfo, "cpu_nsec_kernel");
- usertime = kstat_data_lookup_wrapper(cpuinfo, "cpu_nsec_user");
- }
- } else {
- cpuData->online = false;
- continue;
- }
-
- assert( (idletime != NULL) && (intrtime != NULL)
- && (krnltime != NULL) && (usertime != NULL) );
-
- if (pl->settings->showCPUFrequency) {
- if ((cpuinfo = kstat_lookup_wrapper(spl->kd, "cpu_info", i, NULL)) != NULL) {
- if (kstat_read(spl->kd, cpuinfo, NULL) != -1) {
- cpu_freq = kstat_data_lookup_wrapper(cpuinfo, "current_clock_Hz");
- }
- }
-
- assert( cpu_freq != NULL );
- }
-
- uint64_t totaltime = (idletime->value.ui64 - cpuData->lidle)
- + (intrtime->value.ui64 - cpuData->lintr)
- + (krnltime->value.ui64 - cpuData->lkrnl)
- + (usertime->value.ui64 - cpuData->luser);
-
- // Calculate percentages of deltas since last reading
- cpuData->userPercent = ((usertime->value.ui64 - cpuData->luser) / (double)totaltime) * 100.0;
- cpuData->nicePercent = (double)0.0; // Not implemented on Solaris
- cpuData->systemPercent = ((krnltime->value.ui64 - cpuData->lkrnl) / (double)totaltime) * 100.0;
- cpuData->irqPercent = ((intrtime->value.ui64 - cpuData->lintr) / (double)totaltime) * 100.0;
- cpuData->systemAllPercent = cpuData->systemPercent + cpuData->irqPercent;
- cpuData->idlePercent = ((idletime->value.ui64 - cpuData->lidle) / (double)totaltime) * 100.0;
- // Store current values to use for the next round of deltas
- cpuData->luser = usertime->value.ui64;
- cpuData->lkrnl = krnltime->value.ui64;
- cpuData->lintr = intrtime->value.ui64;
- cpuData->lidle = idletime->value.ui64;
- // Add frequency in MHz
- cpuData->frequency = pl->settings->showCPUFrequency ? (double)cpu_freq->value.ui64 / 1E6 : NAN;
- // Accumulate the current percentages into buffers for later average calculation
- if (existingCPUs > 1) {
- userbuf += cpuData->userPercent;
- krnlbuf += cpuData->systemPercent;
- intrbuf += cpuData->irqPercent;
- idlebuf += cpuData->idlePercent;
- }
- }
-
- if (existingCPUs > 1) {
- CPUData* cpuData = &(spl->cpus[0]);
- cpuData->userPercent = userbuf / activeCPUs;
- cpuData->nicePercent = (double)0.0; // Not implemented on Solaris
- cpuData->systemPercent = krnlbuf / activeCPUs;
- cpuData->irqPercent = intrbuf / activeCPUs;
- cpuData->systemAllPercent = cpuData->systemPercent + cpuData->irqPercent;
- cpuData->idlePercent = idlebuf / activeCPUs;
- }
-}
-
-static inline void SolarisProcessList_scanMemoryInfo(ProcessList* pl) {
- SolarisProcessList* spl = (SolarisProcessList*) pl;
- static kstat_t *meminfo = NULL;
- int ksrphyserr = -1;
- kstat_named_t *totalmem_pgs = NULL;
- kstat_named_t *freemem_pgs = NULL;
- kstat_named_t *pages = NULL;
- struct swaptable *sl = NULL;
- struct swapent *swapdev = NULL;
- uint64_t totalswap = 0;
- uint64_t totalfree = 0;
- int nswap = 0;
- char *spath = NULL;
- char *spathbase = NULL;
-
- // Part 1 - physical memory
- if (spl->kd != NULL && meminfo == NULL) {
- // Look up the kstat chain just once, it never changes
- meminfo = kstat_lookup_wrapper(spl->kd, "unix", 0, "system_pages");
- }
- if (meminfo != NULL) {
- ksrphyserr = kstat_read(spl->kd, meminfo, NULL);
- }
- if (ksrphyserr != -1) {
- totalmem_pgs = kstat_data_lookup_wrapper(meminfo, "physmem");
- freemem_pgs = kstat_data_lookup_wrapper(meminfo, "freemem");
- pages = kstat_data_lookup_wrapper(meminfo, "pagestotal");
-
- pl->totalMem = totalmem_pgs->value.ui64 * pageSizeKB;
- if (pl->totalMem > freemem_pgs->value.ui64 * pageSizeKB) {
- pl->usedMem = pl->totalMem - freemem_pgs->value.ui64 * pageSizeKB;
- } else {
- pl->usedMem = 0; // This can happen in non-global zone (in theory)
- }
- // Not sure how to implement this on Solaris - suggestions welcome!
- pl->cachedMem = 0;
- // Not really "buffers" but the best Solaris analogue that I can find to
- // "memory in use but not by programs or the kernel itself"
- pl->buffersMem = (totalmem_pgs->value.ui64 - pages->value.ui64) * pageSizeKB;
- } else {
- // Fall back to basic sysconf if kstat isn't working
- pl->totalMem = sysconf(_SC_PHYS_PAGES) * pageSize;
- pl->buffersMem = 0;
- pl->cachedMem = 0;
- pl->usedMem = pl->totalMem - (sysconf(_SC_AVPHYS_PAGES) * pageSize);
- }
-
- // Part 2 - swap
- nswap = swapctl(SC_GETNSWP, NULL);
- if (nswap > 0) {
- sl = xMalloc((nswap * sizeof(swapent_t)) + sizeof(int));
- }
- if (sl != NULL) {
- spathbase = xMalloc( nswap * MAXPATHLEN );
- }
- if (spathbase != NULL) {
- spath = spathbase;
- swapdev = sl->swt_ent;
- for (int i = 0; i < nswap; i++, swapdev++) {
- swapdev->ste_path = spath;
- spath += MAXPATHLEN;
- }
- sl->swt_n = nswap;
- }
- nswap = swapctl(SC_LIST, sl);
- if (nswap > 0) {
- swapdev = sl->swt_ent;
- for (int i = 0; i < nswap; i++, swapdev++) {
- totalswap += swapdev->ste_pages;
- totalfree += swapdev->ste_free;
- }
- }
- free(spathbase);
- free(sl);
- pl->totalSwap = totalswap * pageSizeKB;
- pl->usedSwap = pl->totalSwap - (totalfree * pageSizeKB);
-}
-
-static inline void SolarisProcessList_scanZfsArcstats(ProcessList* pl) {
- SolarisProcessList* spl = (SolarisProcessList*) pl;
- kstat_t *arcstats = NULL;
- int ksrphyserr = -1;
- kstat_named_t *cur_kstat = NULL;
-
- if (spl->kd != NULL) {
- arcstats = kstat_lookup_wrapper(spl->kd, "zfs", 0, "arcstats");
- }
- if (arcstats != NULL) {
- ksrphyserr = kstat_read(spl->kd, arcstats, NULL);
- }
- if (ksrphyserr != -1) {
- cur_kstat = kstat_data_lookup_wrapper( arcstats, "size" );
- spl->zfs.size = cur_kstat->value.ui64 / 1024;
- spl->zfs.enabled = spl->zfs.size > 0 ? 1 : 0;
-
- cur_kstat = kstat_data_lookup_wrapper( arcstats, "c_max" );
- spl->zfs.max = cur_kstat->value.ui64 / 1024;
-
- cur_kstat = kstat_data_lookup_wrapper( arcstats, "mfu_size" );
- spl->zfs.MFU = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
-
- cur_kstat = kstat_data_lookup_wrapper( arcstats, "mru_size" );
- spl->zfs.MRU = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
-
- cur_kstat = kstat_data_lookup_wrapper( arcstats, "anon_size" );
- spl->zfs.anon = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
-
- cur_kstat = kstat_data_lookup_wrapper( arcstats, "hdr_size" );
- spl->zfs.header = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
-
- cur_kstat = kstat_data_lookup_wrapper( arcstats, "other_size" );
- spl->zfs.other = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;
-
- if ((cur_kstat = kstat_data_lookup_wrapper( arcstats, "compressed_size" )) != NULL) {
- spl->zfs.compressed = cur_kstat->value.ui64 / 1024;
- spl->zfs.isCompressed = 1;
-
- cur_kstat = kstat_data_lookup_wrapper( arcstats, "uncompressed_size" );
- spl->zfs.uncompressed = cur_kstat->value.ui64 / 1024;
- } else {
- spl->zfs.isCompressed = 0;
- }
- }
-}
-
-void ProcessList_delete(ProcessList* pl) {
- SolarisProcessList* spl = (SolarisProcessList*) pl;
- ProcessList_done(pl);
- free(spl->cpus);
- if (spl->kd) {
- kstat_close(spl->kd);
- }
- free(spl);
-}
-
-static void SolarisProcessList_updateExe(pid_t pid, Process* proc) {
- char path[32];
- xSnprintf(path, sizeof(path), "/proc/%d/path/a.out", 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 SolarisProcessList_updateCwd(pid_t pid, Process* proc) {
- char path[32];
- xSnprintf(path, sizeof(path), "/proc/%d/cwd", pid);
-
- char target[PATH_MAX];
- ssize_t ret = readlink(path, target, sizeof(target) - 1);
- if (ret <= 0)
- return;
-
- target[ret] = '\0';
- free_and_xStrdup(&proc->procCwd, target);
-}
-
-/* Taken from: https://docs.oracle.com/cd/E19253-01/817-6223/6mlkidlom/index.html#tbl-sched-state */
-static inline ProcessState SolarisProcessList_getProcessState(char state) {
- switch (state) {
- case 'S': return SLEEPING;
- case 'R': return RUNNABLE;
- case 'O': return RUNNING;
- case 'Z': return ZOMBIE;
- case 'T': return STOPPED;
- case 'I': return IDLE;
- default: return UNKNOWN;
- }
-}
-
-/* NOTE: the following is a callback function of type proc_walk_f
- * and MUST conform to the appropriate definition in order
- * to work. See libproc(3LIB) on a Solaris or Illumos
- * system for more info.
- */
-
-static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr) {
- bool preExisting;
- pid_t getpid;
-
- // Setup process list
- ProcessList* pl = (ProcessList*) listptr;
- SolarisProcessList* spl = (SolarisProcessList*) listptr;
-
- id_t lwpid_real = _lwpsinfo->pr_lwpid;
- if (lwpid_real > 1023) {
- return 0;
- }
-
- pid_t lwpid = (_psinfo->pr_pid * 1024) + lwpid_real;
- bool onMasterLWP = (_lwpsinfo->pr_lwpid == _psinfo->pr_lwp.pr_lwpid);
- if (onMasterLWP) {
- getpid = _psinfo->pr_pid * 1024;
- } else {
- getpid = lwpid;
- }
-
- Process* proc = ProcessList_getProcess(pl, getpid, &preExisting, SolarisProcess_new);
- SolarisProcess* sproc = (SolarisProcess*) proc;
-
- // Common code pass 1
- proc->show = false;
- sproc->taskid = _psinfo->pr_taskid;
- sproc->projid = _psinfo->pr_projid;
- sproc->poolid = _psinfo->pr_poolid;
- sproc->contid = _psinfo->pr_contract;
- proc->priority = _lwpsinfo->pr_pri;
- proc->nice = _lwpsinfo->pr_nice - NZERO;
- proc->processor = _lwpsinfo->pr_onpro;
- proc->state = SolarisProcessList_getProcessState(_lwpsinfo->pr_sname);
- // NOTE: This 'percentage' is a 16-bit BINARY FRACTIONS where 1.0 = 0x8000
- // Source: https://docs.oracle.com/cd/E19253-01/816-5174/proc-4/index.html
- // (accessed on 18 November 2017)
- proc->percent_mem = ((uint16_t)_psinfo->pr_pctmem / (double)32768) * (double)100.0;
- proc->pgrp = _psinfo->pr_pgid;
- proc->nlwp = _psinfo->pr_nlwp;
- proc->session = _psinfo->pr_sid;
-
- proc->tty_nr = _psinfo->pr_ttydev;
- const char* name = (_psinfo->pr_ttydev != PRNODEV) ? ttyname(_psinfo->pr_ttydev) : NULL;
- if (!name) {
- free(proc->tty_name);
- proc->tty_name = NULL;
- } else {
- free_and_xStrdup(&proc->tty_name, name);
- }
-
- proc->m_resident = _psinfo->pr_rssize; // KB
- proc->m_virt = _psinfo->pr_size; // KB
-
- if (proc->st_uid != _psinfo->pr_euid) {
- proc->st_uid = _psinfo->pr_euid;
- proc->user = UsersTable_getRef(pl->usersTable, proc->st_uid);
- }
-
- if (!preExisting) {
- sproc->realpid = _psinfo->pr_pid;
- sproc->lwpid = lwpid_real;
- sproc->zoneid = _psinfo->pr_zoneid;
- sproc->zname = SolarisProcessList_readZoneName(spl->kd, sproc);
- SolarisProcessList_updateExe(_psinfo->pr_pid, proc);
-
- Process_updateComm(proc, _psinfo->pr_fname);
- Process_updateCmdline(proc, _psinfo->pr_psargs, 0, 0);
-
- if (proc->settings->ss->flags & PROCESS_FLAG_CWD) {
- SolarisProcessList_updateCwd(_psinfo->pr_pid, proc);
- }
- }
-
- // End common code pass 1
-
- if (onMasterLWP) { // Are we on the representative LWP?
- proc->ppid = (_psinfo->pr_ppid * 1024);
- proc->tgid = (_psinfo->pr_ppid * 1024);
- sproc->realppid = _psinfo->pr_ppid;
- sproc->realtgid = _psinfo->pr_ppid;
-
- // See note above (in common section) about this BINARY FRACTION
- proc->percent_cpu = ((uint16_t)_psinfo->pr_pctcpu / (double)32768) * (double)100.0;
- Process_updateCPUFieldWidths(proc->percent_cpu);
-
- proc->time = _psinfo->pr_time.tv_sec * 100 + _psinfo->pr_time.tv_nsec / 10000000;
- if (!preExisting) { // Tasks done only for NEW processes
- proc->isUserlandThread = false;
- proc->starttime_ctime = _psinfo->pr_start.tv_sec;
- }
-
- // Update proc and thread counts based on settings
- if (proc->isKernelThread && !pl->settings->hideKernelThreads) {
- pl->kernelThreads += proc->nlwp;
- pl->totalTasks += proc->nlwp + 1;
- if (proc->state == RUNNING) {
- pl->runningTasks++;
- }
- } else if (!proc->isKernelThread) {
- if (proc->state == RUNNING) {
- pl->runningTasks++;
- }
- if (pl->settings->hideUserlandThreads) {
- pl->totalTasks++;
- } else {
- pl->userlandThreads += proc->nlwp;
- pl->totalTasks += proc->nlwp + 1;
- }
- }
- proc->show = !(pl->settings->hideKernelThreads && proc->isKernelThread);
- } else { // We are not in the master LWP, so jump to the LWP handling code
- proc->percent_cpu = ((uint16_t)_lwpsinfo->pr_pctcpu / (double)32768) * (double)100.0;
- Process_updateCPUFieldWidths(proc->percent_cpu);
-
- proc->time = _lwpsinfo->pr_time.tv_sec * 100 + _lwpsinfo->pr_time.tv_nsec / 10000000;
- if (!preExisting) { // Tasks done only for NEW LWPs
- proc->isUserlandThread = true;
- proc->ppid = _psinfo->pr_pid * 1024;
- proc->tgid = _psinfo->pr_pid * 1024;
- sproc->realppid = _psinfo->pr_pid;
- sproc->realtgid = _psinfo->pr_pid;
- proc->starttime_ctime = _lwpsinfo->pr_start.tv_sec;
- }
-
- // Top-level process only gets this for the representative LWP
- if (proc->isKernelThread && !pl->settings->hideKernelThreads) {
- proc->show = true;
- }
- if (!proc->isKernelThread && !pl->settings->hideUserlandThreads) {
- proc->show = true;
- }
- } // Top-level LWP or subordinate LWP
-
- // Common code pass 2
-
- if (!preExisting) {
- if ((sproc->realppid <= 0) && !(sproc->realpid <= 1)) {
- proc->isKernelThread = true;
- } else {
- proc->isKernelThread = false;
- }
-
- Process_fillStarttimeBuffer(proc);
- ProcessList_add(pl, proc);
- }
-
- proc->updated = true;
-
- // End common code pass 2
-
- return 0;
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
- SolarisProcessList_updateCPUcount(super);
- SolarisProcessList_scanCPUTime(super);
- SolarisProcessList_scanMemoryInfo(super);
- SolarisProcessList_scanZfsArcstats(super);
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
- super->kernelThreads = 1;
- proc_walk(&SolarisProcessList_walkproc, super, PR_WALK_LWP);
-}
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
- assert(id < super->existingCPUs);
-
- const SolarisProcessList* spl = (const SolarisProcessList*) super;
-
- return (super->existingCPUs == 1) ? true : spl->cpus[id + 1].online;
-}
diff --git a/solaris/SolarisProcessTable.c b/solaris/SolarisProcessTable.c
new file mode 100644
index 00000000..29c5d938
--- /dev/null
+++ b/solaris/SolarisProcessTable.c
@@ -0,0 +1,269 @@
+/*
+htop - SolarisProcessTable.c
+(C) 2014 Hisham H. Muhammad
+(C) 2017,2018 Guy M. Broome
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "solaris/SolarisProcessTable.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/user.h>
+#include <limits.h>
+#include <string.h>
+#include <procfs.h>
+#include <errno.h>
+#include <pwd.h>
+#include <math.h>
+#include <time.h>
+
+#include "CRT.h"
+#include "solaris/Platform.h"
+#include "solaris/SolarisMachine.h"
+#include "solaris/SolarisProcess.h"
+
+
+#define GZONE "global "
+#define UZONE "unknown "
+
+static char* SolarisProcessTable_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) {
+ char* zname;
+
+ if ( sproc->zoneid == 0 ) {
+ zname = xStrdup(GZONE);
+ } else if ( kd == NULL ) {
+ zname = xStrdup(UZONE);
+ } else {
+ kstat_t* ks = kstat_lookup_wrapper( kd, "zones", sproc->zoneid, NULL );
+ zname = xStrdup(ks == NULL ? UZONE : ks->ks_name);
+ }
+
+ return zname;
+}
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ SolarisProcessTable* this = xCalloc(1, sizeof(SolarisProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(SolarisProcess), host, pidMatchList);
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ SolarisProcessTable* this = (SolarisProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ free(this);
+}
+
+static void SolarisProcessTable_updateExe(pid_t pid, Process* proc) {
+ char path[32];
+ xSnprintf(path, sizeof(path), "/proc/%d/path/a.out", 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 SolarisProcessTable_updateCwd(pid_t pid, Process* proc) {
+ char path[32];
+ xSnprintf(path, sizeof(path), "/proc/%d/cwd", pid);
+
+ char target[PATH_MAX];
+ ssize_t ret = readlink(path, target, sizeof(target) - 1);
+ if (ret <= 0)
+ return;
+
+ target[ret] = '\0';
+ free_and_xStrdup(&proc->procCwd, target);
+}
+
+/* Taken from: https://docs.oracle.com/cd/E19253-01/817-6223/6mlkidlom/index.html#tbl-sched-state */
+static inline ProcessState SolarisProcessTable_getProcessState(char state) {
+ switch (state) {
+ case 'S': return SLEEPING;
+ case 'R': return RUNNABLE;
+ case 'O': return RUNNING;
+ case 'Z': return ZOMBIE;
+ case 'T': return STOPPED;
+ case 'I': return IDLE;
+ default: return UNKNOWN;
+ }
+}
+
+/* NOTE: the following is a callback function of type proc_walk_f
+ * and MUST conform to the appropriate definition in order
+ * to work. See libproc(3LIB) on a Solaris or Illumos
+ * system for more info.
+ */
+
+static int SolarisProcessTable_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr) {
+ bool preExisting;
+ pid_t getpid;
+
+ // Setup process list
+ ProcessTable* pt = (ProcessTable*) listptr;
+ const Machine* host = pt->super.host;
+ const SolarisMachine* shost = (const SolarisMachine*) host;
+
+ id_t lwpid_real = _lwpsinfo->pr_lwpid;
+ if (lwpid_real > 1023) {
+ return 0;
+ }
+
+ pid_t lwpid = (_psinfo->pr_pid * 1024) + lwpid_real;
+ bool onMasterLWP = (_lwpsinfo->pr_lwpid == _psinfo->pr_lwp.pr_lwpid);
+ if (onMasterLWP) {
+ getpid = _psinfo->pr_pid * 1024;
+ } else {
+ getpid = lwpid;
+ }
+
+ Process* proc = ProcessTable_getProcess(pt, getpid, &preExisting, SolarisProcess_new);
+ SolarisProcess* sproc = (SolarisProcess*) proc;
+ const Settings* settings = host->settings;
+
+ // Common code pass 1
+ proc->super.show = false;
+ sproc->taskid = _psinfo->pr_taskid;
+ sproc->projid = _psinfo->pr_projid;
+ sproc->poolid = _psinfo->pr_poolid;
+ sproc->contid = _psinfo->pr_contract;
+ proc->priority = _lwpsinfo->pr_pri;
+ proc->nice = _lwpsinfo->pr_nice - NZERO;
+ proc->processor = _lwpsinfo->pr_onpro;
+ proc->state = SolarisProcessTable_getProcessState(_lwpsinfo->pr_sname);
+ // NOTE: This 'percentage' is a 16-bit BINARY FRACTIONS where 1.0 = 0x8000
+ // Source: https://docs.oracle.com/cd/E19253-01/816-5174/proc-4/index.html
+ // (accessed on 18 November 2017)
+ proc->percent_mem = ((uint16_t)_psinfo->pr_pctmem / (double)32768) * (double)100.0;
+ proc->pgrp = _psinfo->pr_pgid;
+ proc->nlwp = _psinfo->pr_nlwp;
+ proc->session = _psinfo->pr_sid;
+
+ proc->tty_nr = _psinfo->pr_ttydev;
+ const char* name = (_psinfo->pr_ttydev != PRNODEV) ? ttyname(_psinfo->pr_ttydev) : NULL;
+ if (!name) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
+
+ proc->m_resident = _psinfo->pr_rssize; // KB
+ proc->m_virt = _psinfo->pr_size; // KB
+
+ if (proc->st_uid != _psinfo->pr_euid) {
+ proc->st_uid = _psinfo->pr_euid;
+ proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);
+ }
+
+ if (!preExisting) {
+ sproc->realpid = _psinfo->pr_pid;
+ sproc->lwpid = lwpid_real;
+ sproc->zoneid = _psinfo->pr_zoneid;
+ sproc->zname = SolarisProcessTable_readZoneName(shost->kd, sproc);
+ SolarisProcessTable_updateExe(_psinfo->pr_pid, proc);
+
+ Process_updateComm(proc, _psinfo->pr_fname);
+ Process_updateCmdline(proc, _psinfo->pr_psargs, 0, 0);
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ SolarisProcessTable_updateCwd(_psinfo->pr_pid, proc);
+ }
+ }
+
+ // End common code pass 1
+
+ if (onMasterLWP) { // Are we on the representative LWP?
+ Process_setParent(proc, (_psinfo->pr_ppid * 1024));
+ Process_setThreadGroup(proc, (_psinfo->pr_ppid * 1024));
+ sproc->realppid = _psinfo->pr_ppid;
+ sproc->realtgid = _psinfo->pr_ppid;
+
+ // See note above (in common section) about this BINARY FRACTION
+ proc->percent_cpu = ((uint16_t)_psinfo->pr_pctcpu / (double)32768) * (double)100.0;
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ proc->time = _psinfo->pr_time.tv_sec * 100 + _psinfo->pr_time.tv_nsec / 10000000;
+ if (!preExisting) { // Tasks done only for NEW processes
+ proc->isUserlandThread = false;
+ proc->starttime_ctime = _psinfo->pr_start.tv_sec;
+ }
+
+ // Update proc and thread counts based on settings
+ if (proc->isKernelThread && !settings->hideKernelThreads) {
+ pt->kernelThreads += proc->nlwp;
+ pt->totalTasks += proc->nlwp + 1;
+ if (proc->state == RUNNING) {
+ pt->runningTasks++;
+ }
+ } else if (!proc->isKernelThread) {
+ if (proc->state == RUNNING) {
+ pt->runningTasks++;
+ }
+ if (settings->hideUserlandThreads) {
+ pt->totalTasks++;
+ } else {
+ pt->userlandThreads += proc->nlwp;
+ pt->totalTasks += proc->nlwp + 1;
+ }
+ }
+ proc->super.show = !(settings->hideKernelThreads && proc->isKernelThread);
+ } else { // We are not in the master LWP, so jump to the LWP handling code
+ proc->percent_cpu = ((uint16_t)_lwpsinfo->pr_pctcpu / (double)32768) * (double)100.0;
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ proc->time = _lwpsinfo->pr_time.tv_sec * 100 + _lwpsinfo->pr_time.tv_nsec / 10000000;
+ if (!preExisting) { // Tasks done only for NEW LWPs
+ proc->isUserlandThread = true;
+ Process_setParent(proc, _psinfo->pr_pid * 1024);
+ Process_setThreadGroup(proc, _psinfo->pr_pid * 1024);
+ sproc->realppid = _psinfo->pr_pid;
+ sproc->realtgid = _psinfo->pr_pid;
+ proc->starttime_ctime = _lwpsinfo->pr_start.tv_sec;
+ }
+
+ // Top-level process only gets this for the representative LWP
+ if (proc->isKernelThread && !settings->hideKernelThreads) {
+ proc->super.show = true;
+ }
+ if (!proc->isKernelThread && !settings->hideUserlandThreads) {
+ proc->super.show = true;
+ }
+ } // Top-level LWP or subordinate LWP
+
+ // Common code pass 2
+
+ if (!preExisting) {
+ if ((sproc->realppid <= 0) && !(sproc->realpid <= 1)) {
+ proc->isKernelThread = true;
+ } else {
+ proc->isKernelThread = false;
+ }
+
+ Process_fillStarttimeBuffer(proc);
+ ProcessTable_add(pt, proc);
+ }
+
+ proc->super.updated = true;
+
+ // End common code pass 2
+
+ return 0;
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ super->kernelThreads = 1;
+ proc_walk(&SolarisProcessTable_walkproc, super, PR_WALK_LWP);
+}
diff --git a/solaris/SolarisProcessTable.h b/solaris/SolarisProcessTable.h
new file mode 100644
index 00000000..7c5ae8f3
--- /dev/null
+++ b/solaris/SolarisProcessTable.h
@@ -0,0 +1,31 @@
+#ifndef HEADER_SolarisProcessTable
+#define HEADER_SolarisProcessTable
+/*
+htop - SolarisProcessTable.h
+(C) 2014 Hisham H. Muhammad
+(C) 2017,2018 Guy M. Broome
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <kstat.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/resource.h>
+#include <sys/sysconf.h>
+#include <sys/sysinfo.h>
+
+#include "Hashtable.h"
+#include "ProcessTable.h"
+#include "UsersTable.h"
+
+#include "solaris/SolarisProcess.h"
+
+
+typedef struct SolarisProcessTable_ {
+ ProcessTable super;
+} SolarisProcessTable;
+
+#endif
diff --git a/unsupported/Platform.c b/unsupported/Platform.c
index 33d7c415..dbfddd91 100644
--- a/unsupported/Platform.c
+++ b/unsupported/Platform.c
@@ -16,6 +16,7 @@ in the source distribution for its full text.
#include "ClockMeter.h"
#include "DateMeter.h"
#include "DateTimeMeter.h"
+#include "FileDescriptorMeter.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
@@ -70,6 +71,7 @@ const MeterClass* const Platform_meterTypes[] = {
&RightCPUs4Meter_class,
&LeftCPUs8Meter_class,
&RightCPUs8Meter_class,
+ &FileDescriptorMeter_class,
&BlankMeter_class,
NULL
};
@@ -90,7 +92,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
return 0;
}
@@ -100,8 +102,8 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = 0;
}
-int Platform_getMaxPid() {
- return 1;
+pid_t Platform_getMaxPid(void) {
+ return INT_MAX;
}
double Platform_setCPUValues(Meter* this, unsigned int cpu) {
@@ -129,15 +131,14 @@ char* Platform_getProcessEnv(pid_t pid) {
return NULL;
}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
(void)pid;
- (void)inode;
return NULL;
}
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+void Platform_getFileDescriptors(double* used, double* max) {
+ *used = 1337;
+ *max = 4711;
}
bool Platform_getDiskIO(DiskIOData* data) {
diff --git a/unsupported/Platform.h b/unsupported/Platform.h
index 5c874d48..c4cd06a0 100644
--- a/unsupported/Platform.h
+++ b/unsupported/Platform.h
@@ -8,6 +8,9 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+#include <stdbool.h>
+#include <sys/types.h>
+
#include "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
@@ -40,7 +43,7 @@ int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid(void);
+pid_t Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, unsigned int cpu);
@@ -50,10 +53,10 @@ void Platform_setSwapValues(Meter* this);
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+void Platform_getFileDescriptors(double* used, double* max);
+
bool Platform_getDiskIO(DiskIOData* data);
bool Platform_getNetworkIO(NetworkIOData* data);
@@ -80,7 +83,9 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) {
Generic_gettime_monotonic(msec);
}
-static inline Hashtable* Platform_dynamicMeters(void) { return NULL; }
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
@@ -90,12 +95,30 @@ 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 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 const char* Platform_dynamicColumnName(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;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
-static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; }
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
#endif
diff --git a/unsupported/UnsupportedMachine.c b/unsupported/UnsupportedMachine.c
new file mode 100644
index 00000000..a6635acc
--- /dev/null
+++ b/unsupported/UnsupportedMachine.c
@@ -0,0 +1,56 @@
+/*
+htop - UnsupportedMachine.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "UnsupportedMachine.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "Machine.h"
+
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ UnsupportedMachine* this = xCalloc(1, sizeof(UnsupportedMachine));
+ Machine* super = &this->super;
+
+ Machine_init(super, usersTable, userId);
+
+ super->existingCPUs = 1;
+ super->activeCPUs = 1;
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ UnsupportedMachine* this = (UnsupportedMachine*) super;
+ Machine_done(super);
+ free(this);
+}
+
+bool Machine_isCPUonline(const Machine* host, unsigned int id) {
+ assert(id < host->existingCPUs);
+
+ (void) host; (void) id;
+
+ return true;
+}
+
+void Machine_scan(Machine* super) {
+ super->existingCPUs = 1;
+ super->activeCPUs = 1;
+
+ super->totalMem = 0;
+ super->usedMem = 0;
+ super->buffersMem = 0;
+ super->cachedMem = 0;
+ super->sharedMem = 0;
+ super->availableMem = 0;
+
+ super->totalSwap = 0;
+ super->usedSwap = 0;
+ super->cachedSwap = 0;
+}
diff --git a/unsupported/UnsupportedMachine.h b/unsupported/UnsupportedMachine.h
new file mode 100644
index 00000000..4ec760f1
--- /dev/null
+++ b/unsupported/UnsupportedMachine.h
@@ -0,0 +1,17 @@
+#ifndef HEADER_UnsupportedMachine
+#define HEADER_UnsupportedMachine
+/*
+htop - UnsupportedMachine.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Machine.h"
+
+
+typedef struct UnsupportedMachine_ {
+ Machine super;
+} UnsupportedMachine;
+
+#endif
diff --git a/unsupported/UnsupportedProcess.c b/unsupported/UnsupportedProcess.c
index 2aca0488..0eb6ce93 100644
--- a/unsupported/UnsupportedProcess.c
+++ b/unsupported/UnsupportedProcess.c
@@ -35,7 +35,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
@@ -44,10 +44,10 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
};
-Process* UnsupportedProcess_new(const Settings* settings) {
+Process* UnsupportedProcess_new(const Machine* host) {
Process* this = xCalloc(1, sizeof(UnsupportedProcess));
Object_setClass(this, Class(UnsupportedProcess));
- Process_init(this, settings);
+ Process_init(this, host);
return this;
}
@@ -58,23 +58,24 @@ void Process_delete(Object* cast) {
free(cast);
}
-static void UnsupportedProcess_writeField(const Process* this, RichString* str, ProcessField field) {
- const UnsupportedProcess* up = (const UnsupportedProcess*) this;
- bool coloring = this->settings->highlightMegabytes;
+static void UnsupportedProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
+ const UnsupportedProcess* up = (const UnsupportedProcess*) super;
+
+ bool coloring = super->host->settings->highlightMegabytes;
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
size_t n = sizeof(buffer) - 1;
- (void) up;
(void) coloring;
(void) n;
switch (field) {
/* Add platform specific fields */
default:
- Process_writeField(this, str, field);
+ Process_writeField(&up->super, str, field);
return;
}
+
RichString_appendWide(str, attr, buffer);
}
@@ -94,11 +95,18 @@ static int UnsupportedProcess_compareByKey(const Process* v1, const Process* v2,
const ProcessClass UnsupportedProcess_class = {
.super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
+ .super = {
+ .extends = Class(Process),
+ .display = Row_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .isHighlighted = Process_rowIsHighlighted,
+ .isVisible = Process_rowIsVisible,
+ .matchesFilter = Process_rowMatchesFilter,
+ .compareByParent = Process_compareByParent,
+ .sortKeyString = Process_rowGetSortKey,
+ .writeField = UnsupportedProcess_rowWriteField
},
- .writeField = UnsupportedProcess_writeField,
.compareByKey = UnsupportedProcess_compareByKey
};
diff --git a/unsupported/UnsupportedProcess.h b/unsupported/UnsupportedProcess.h
index e30169c5..21956ddd 100644
--- a/unsupported/UnsupportedProcess.h
+++ b/unsupported/UnsupportedProcess.h
@@ -7,7 +7,7 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "Settings.h"
+#include "Machine.h"
typedef struct UnsupportedProcess_ {
@@ -19,7 +19,7 @@ typedef struct UnsupportedProcess_ {
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-Process* UnsupportedProcess_new(const Settings* settings);
+Process* UnsupportedProcess_new(const Machine* host);
void Process_delete(Object* cast);
diff --git a/unsupported/UnsupportedProcessList.h b/unsupported/UnsupportedProcessList.h
deleted file mode 100644
index cbf25afa..00000000
--- a/unsupported/UnsupportedProcessList.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef HEADER_UnsupportedProcessList
-#define HEADER_UnsupportedProcessList
-/*
-htop - UnsupportedProcessList.h
-(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2+, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "ProcessList.h"
-
-
-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/unsupported/UnsupportedProcessList.c b/unsupported/UnsupportedProcessTable.c
index 5291797f..db43f1eb 100644
--- a/unsupported/UnsupportedProcessList.c
+++ b/unsupported/UnsupportedProcessTable.c
@@ -1,66 +1,64 @@
/*
-htop - UnsupportedProcessList.c
+htop - UnsupportedProcessTable.c
(C) 2014 Hisham H. Muhammad
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "UnsupportedProcessList.h"
+#include "config.h" // IWYU pragma: keep
+
+#include "UnsupportedProcessTable.h"
#include <stdlib.h>
#include <string.h>
-#include "ProcessList.h"
+#include "ProcessTable.h"
#include "UnsupportedProcess.h"
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
- ProcessList* this = xCalloc(1, sizeof(ProcessList));
- ProcessList_init(this, Class(Process), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ UnsupportedProcessTable* this = xCalloc(1, sizeof(UnsupportedProcessTable));
+ Object_setClass(this, Class(ProcessTable));
- this->existingCPUs = 1;
- this->activeCPUs = 1;
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(Process), host, pidMatchList);
return this;
}
-void ProcessList_delete(ProcessList* this) {
- ProcessList_done(this);
+void ProcessTable_delete(Object* cast) {
+ UnsupportedProcessTable* this = (UnsupportedProcessTable*) cast;
+ ProcessTable_done(&this->super);
free(this);
}
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
+void ProcessTable_goThroughEntries(ProcessTable* super) {
bool preExisting = true;
Process* proc;
- proc = ProcessList_getProcess(super, 1, &preExisting, UnsupportedProcess_new);
+ proc = ProcessTable_getProcess(super, 1, &preExisting, UnsupportedProcess_new);
/* Empty values */
proc->time = proc->time + 10;
- proc->pid = 1;
- proc->ppid = 1;
- proc->tgid = 0;
+ Process_setPid(proc, 1);
+ Process_setParent(proc, 1);
+ Process_setThreadGroup(proc, 0);
Process_updateComm(proc, "commof16char");
Process_updateCmdline(proc, "<unsupported architecture>", 0, 0);
Process_updateExe(proc, "/path/to/executable");
- if (proc->settings->ss->flags & PROCESS_FLAG_CWD) {
+ const Settings* settings = proc->host->settings;
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
free_and_xStrdup(&proc->procCwd, "/current/working/directory");
}
- proc->updated = true;
+ proc->super.updated = true;
proc->state = RUNNING;
proc->isKernelThread = false;
proc->isUserlandThread = false;
- proc->show = true; /* Reflected in proc->settings-> "hideXXX" really */
+ proc->super.show = true; /* Reflected in settings-> "hideXXX" really */
proc->pgrp = 0;
proc->session = 0;
proc->tty_nr = 0;
@@ -88,13 +86,5 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc->majflt = 20;
if (!preExisting)
- ProcessList_add(super, proc);
-}
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
- assert(id < super->existingCPUs);
-
- (void) super; (void) id;
-
- return true;
+ ProcessTable_add(super, proc);
}
diff --git a/unsupported/UnsupportedProcessTable.h b/unsupported/UnsupportedProcessTable.h
new file mode 100644
index 00000000..1de8087d
--- /dev/null
+++ b/unsupported/UnsupportedProcessTable.h
@@ -0,0 +1,17 @@
+#ifndef HEADER_UnsupportedProcessTable
+#define HEADER_UnsupportedProcessTable
+/*
+htop - UnsupportedProcessTable.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "ProcessTable.h"
+
+
+typedef struct UnsupportedProcessTable_ {
+ ProcessTable super;
+} UnsupportedProcessTable;
+
+#endif
diff --git a/zfs/ZfsArcMeter.c b/zfs/ZfsArcMeter.c
index f124272f..32f5bb34 100644
--- a/zfs/ZfsArcMeter.c
+++ b/zfs/ZfsArcMeter.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 "zfs/ZfsArcMeter.h"
#include <stddef.h>
diff --git a/zfs/ZfsCompressedArcMeter.c b/zfs/ZfsCompressedArcMeter.c
index 2e494736..4d47040c 100644
--- a/zfs/ZfsCompressedArcMeter.c
+++ b/zfs/ZfsCompressedArcMeter.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 "zfs/ZfsCompressedArcMeter.h"
#include <stddef.h>

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