aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/dependabot.yml9
-rw-r--r--.github/workflows/build_release.yml51
-rw-r--r--.github/workflows/ci.yml98
-rw-r--r--.github/workflows/codeql-analysis.yml49
-rw-r--r--.github/workflows/htoprc71
-rw-r--r--.gitignore3
-rw-r--r--Action.c482
-rw-r--r--Action.h23
-rw-r--r--Affinity.c49
-rw-r--r--Affinity.h18
-rw-r--r--AffinityPanel.c112
-rw-r--r--AffinityPanel.h8
-rw-r--r--AvailableColumnsPanel.c62
-rw-r--r--AvailableColumnsPanel.h4
-rw-r--r--AvailableMetersPanel.c40
-rw-r--r--AvailableMetersPanel.h10
-rw-r--r--BatteryMeter.c27
-rw-r--r--BatteryMeter.h2
-rw-r--r--CPUMeter.c76
-rw-r--r--CPUMeter.h2
-rw-r--r--CRT.c306
-rw-r--r--CRT.h39
-rw-r--r--CategoriesPanel.c56
-rw-r--r--CategoriesPanel.h11
-rw-r--r--ChangeLog180
-rw-r--r--ClockMeter.c8
-rw-r--r--ClockMeter.h2
-rw-r--r--ColorsPanel.c53
-rw-r--r--ColorsPanel.h2
-rw-r--r--ColumnsPanel.c67
-rw-r--r--ColumnsPanel.h10
-rw-r--r--CommandLine.c243
-rw-r--r--CommandLine.h11
-rw-r--r--CommandScreen.c10
-rw-r--r--CommandScreen.h7
-rw-r--r--Compat.c41
-rw-r--r--Compat.h34
-rw-r--r--DateMeter.c8
-rw-r--r--DateMeter.h2
-rw-r--r--DateTimeMeter.c8
-rw-r--r--DateTimeMeter.h2
-rw-r--r--DiskIOMeter.c142
-rw-r--r--DiskIOMeter.h4
-rw-r--r--DisplayOptionsPanel.c81
-rw-r--r--DisplayOptionsPanel.h2
-rw-r--r--DynamicColumn.c18
-rw-r--r--DynamicColumn.h25
-rw-r--r--DynamicMeter.c13
-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.c13
-rw-r--r--FunctionBar.h6
-rw-r--r--Hashtable.c14
-rw-r--r--Hashtable.h2
-rw-r--r--Header.c53
-rw-r--r--Header.h12
-rw-r--r--HeaderLayout.h11
-rw-r--r--HeaderOptionsPanel.c49
-rw-r--r--HeaderOptionsPanel.h2
-rw-r--r--HostnameMeter.c2
-rw-r--r--HostnameMeter.h2
-rw-r--r--IncSet.c45
-rw-r--r--IncSet.h7
-rw-r--r--InfoScreen.c106
-rw-r--r--InfoScreen.h7
-rw-r--r--ListItem.c16
-rw-r--r--ListItem.h11
-rw-r--r--LoadAverageMeter.c18
-rw-r--r--LoadAverageMeter.h2
-rw-r--r--Machine.c124
-rw-r--r--Machine.h96
-rw-r--r--Macros.h64
-rw-r--r--MainPanel.c148
-rw-r--r--MainPanel.h23
-rw-r--r--Makefile.am131
-rw-r--r--MemoryMeter.c60
-rw-r--r--MemoryMeter.h11
-rw-r--r--MemorySwapMeter.c9
-rw-r--r--MemorySwapMeter.h2
-rw-r--r--Meter.c155
-rw-r--r--Meter.h28
-rw-r--r--MetersPanel.c39
-rw-r--r--MetersPanel.h2
-rw-r--r--NetworkIOMeter.c167
-rw-r--r--NetworkIOMeter.h8
-rw-r--r--Object.c4
-rw-r--r--Object.h4
-rw-r--r--OpenFilesScreen.c216
-rw-r--r--OpenFilesScreen.h2
-rw-r--r--OptionItem.c28
-rw-r--r--OptionItem.h12
-rw-r--r--Panel.c154
-rw-r--r--Panel.h16
-rw-r--r--Process.c871
-rw-r--r--Process.h268
-rw-r--r--ProcessList.c659
-rw-r--r--ProcessList.h129
-rw-r--r--ProcessLocksScreen.c21
-rw-r--r--ProcessLocksScreen.h6
-rw-r--r--ProcessTable.c92
-rw-r--r--ProcessTable.h52
-rw-r--r--ProvideCurses.h6
-rw-r--r--ProvideTerm.h24
-rw-r--r--README19
-rw-r--r--RichString.c10
-rw-r--r--RichString.h6
-rw-r--r--Row.c497
-rw-r--r--Row.h181
-rw-r--r--RowField.h56
-rw-r--r--Scheduling.c162
-rw-r--r--Scheduling.h50
-rw-r--r--ScreenManager.c172
-rw-r--r--ScreenManager.h15
-rw-r--r--ScreenTabsPanel.c374
-rw-r--r--ScreenTabsPanel.h61
-rw-r--r--ScreensPanel.c328
-rw-r--r--ScreensPanel.h55
-rw-r--r--Settings.c509
-rw-r--r--Settings.h78
-rw-r--r--SignalsPanel.c11
-rw-r--r--SignalsPanel.h10
-rw-r--r--SwapMeter.c25
-rw-r--r--SwapMeter.h8
-rw-r--r--SysArchMeter.c2
-rw-r--r--SysArchMeter.h2
-rw-r--r--TESTPLAN2
-rw-r--r--Table.c372
-rw-r--r--Table.h95
-rw-r--r--TasksMeter.c25
-rw-r--r--TasksMeter.h2
-rw-r--r--TraceScreen.c43
-rw-r--r--TraceScreen.h2
-rw-r--r--UptimeMeter.c4
-rw-r--r--UptimeMeter.h2
-rw-r--r--UsersTable.c4
-rw-r--r--UsersTable.h2
-rw-r--r--Vector.c127
-rw-r--r--Vector.h22
-rw-r--r--XUtils.c111
-rw-r--r--XUtils.h55
-rw-r--r--configure.ac210
-rw-r--r--darwin/DarwinMachine.c119
-rw-r--r--darwin/DarwinMachine.h28
-rw-r--r--darwin/DarwinProcess.c138
-rw-r--r--darwin/DarwinProcess.h13
-rw-r--r--darwin/DarwinProcessList.c242
-rw-r--r--darwin/DarwinProcessList.h39
-rw-r--r--darwin/DarwinProcessTable.c126
-rw-r--r--darwin/DarwinProcessTable.h22
-rw-r--r--darwin/Platform.c260
-rw-r--r--darwin/Platform.h47
-rw-r--r--darwin/PlatformHelpers.c125
-rw-r--r--darwin/PlatformHelpers.h40
-rw-r--r--darwin/ProcessField.h2
-rw-r--r--docs/images/screenshot.pngbin80937 -> 46142 bytes
-rw-r--r--docs/styleguide.md29
-rw-r--r--dragonflybsd/DragonFlyBSDMachine.c341
-rw-r--r--dragonflybsd/DragonFlyBSDMachine.h (renamed from dragonflybsd/DragonFlyBSDProcessList.h)35
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.c45
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.h6
-rw-r--r--dragonflybsd/DragonFlyBSDProcessList.c610
-rw-r--r--dragonflybsd/DragonFlyBSDProcessTable.c322
-rw-r--r--dragonflybsd/DragonFlyBSDProcessTable.h21
-rw-r--r--dragonflybsd/Platform.c83
-rw-r--r--dragonflybsd/Platform.h47
-rw-r--r--dragonflybsd/ProcessField.h6
-rw-r--r--freebsd/FreeBSDMachine.c393
-rw-r--r--freebsd/FreeBSDMachine.h (renamed from freebsd/FreeBSDProcessList.h)28
-rw-r--r--freebsd/FreeBSDProcess.c58
-rw-r--r--freebsd/FreeBSDProcess.h7
-rw-r--r--freebsd/FreeBSDProcessList.c609
-rw-r--r--freebsd/FreeBSDProcessTable.c288
-rw-r--r--freebsd/FreeBSDProcessTable.h21
-rw-r--r--freebsd/Platform.c105
-rw-r--r--freebsd/Platform.h47
-rw-r--r--freebsd/ProcessField.h7
-rw-r--r--generic/fdstat_sysctl.c80
-rw-r--r--generic/fdstat_sysctl.h13
-rw-r--r--generic/gettime.c11
-rw-r--r--generic/gettime.h2
-rw-r--r--generic/hostname.c3
-rw-r--r--generic/hostname.h2
-rw-r--r--generic/openzfs_sysctl.c8
-rw-r--r--generic/openzfs_sysctl.h2
-rw-r--r--generic/uname.c5
-rw-r--r--generic/uname.h2
-rw-r--r--htop.1.in99
-rw-r--r--htop.c6
-rw-r--r--htop.pngbin3537 -> 2616 bytes
-rw-r--r--iwyu/htop.imp9
-rwxr-xr-xiwyu/run_iwyu.sh1
-rw-r--r--linux/CGroupUtils.c532
-rw-r--r--linux/CGroupUtils.h14
-rw-r--r--linux/HugePageMeter.c16
-rw-r--r--linux/HugePageMeter.h2
-rw-r--r--linux/IOPriority.h2
-rw-r--r--linux/IOPriorityPanel.c4
-rw-r--r--linux/IOPriorityPanel.h2
-rw-r--r--linux/LibSensors.c41
-rw-r--r--linux/LibSensors.h8
-rw-r--r--linux/LinuxMachine.c695
-rw-r--r--linux/LinuxMachine.h (renamed from linux/LinuxProcessList.h)55
-rw-r--r--linux/LinuxProcess.c227
-rw-r--r--linux/LinuxProcess.h25
-rw-r--r--linux/LinuxProcessList.c2118
-rw-r--r--linux/LinuxProcessTable.c1668
-rw-r--r--linux/LinuxProcessTable.h35
-rw-r--r--linux/Platform.c652
-rw-r--r--linux/Platform.h53
-rw-r--r--linux/PressureStallMeter.c23
-rw-r--r--linux/PressureStallMeter.h4
-rw-r--r--linux/ProcessField.h5
-rw-r--r--linux/SELinuxMeter.c4
-rw-r--r--linux/SELinuxMeter.h2
-rw-r--r--linux/SystemdMeter.c210
-rw-r--r--linux/SystemdMeter.h4
-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)30
-rw-r--r--netbsd/NetBSDProcess.c43
-rw-r--r--netbsd/NetBSDProcess.h6
-rw-r--r--netbsd/NetBSDProcessList.c498
-rw-r--r--netbsd/NetBSDProcessTable.c273
-rw-r--r--netbsd/NetBSDProcessTable.h24
-rw-r--r--netbsd/Platform.c87
-rw-r--r--netbsd/Platform.h47
-rw-r--r--netbsd/ProcessField.h2
-rw-r--r--openbsd/OpenBSDMachine.c290
-rw-r--r--openbsd/OpenBSDMachine.h (renamed from openbsd/OpenBSDProcessList.h)31
-rw-r--r--openbsd/OpenBSDProcess.c46
-rw-r--r--openbsd/OpenBSDProcess.h6
-rw-r--r--openbsd/OpenBSDProcessList.c480
-rw-r--r--openbsd/OpenBSDProcessTable.c245
-rw-r--r--openbsd/OpenBSDProcessTable.h21
-rw-r--r--openbsd/Platform.c109
-rw-r--r--openbsd/Platform.h47
-rw-r--r--openbsd/ProcessField.h2
-rw-r--r--pcp-htop.5.in2
-rw-r--r--pcp-htop.c9
-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)59
-rw-r--r--pcp/Metric.h (renamed from pcp/PCPMetric.h)44
-rw-r--r--pcp/PCPDynamicColumn.c277
-rw-r--r--pcp/PCPDynamicColumn.h21
-rw-r--r--pcp/PCPDynamicMeter.c49
-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)33
-rw-r--r--pcp/PCPProcess.c167
-rw-r--r--pcp/PCPProcess.h11
-rw-r--r--pcp/PCPProcessList.c706
-rw-r--r--pcp/PCPProcessTable.c486
-rw-r--r--pcp/PCPProcessTable.h24
-rw-r--r--pcp/Platform.c376
-rw-r--r--pcp/Platform.h37
-rw-r--r--pcp/ProcessField.h5
-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--scripts/htop_suppressions.valgrind10
-rw-r--r--solaris/Platform.c110
-rw-r--r--solaris/Platform.h51
-rw-r--r--solaris/ProcessField.h2
-rw-r--r--solaris/SolarisMachine.c336
-rw-r--r--solaris/SolarisMachine.h (renamed from solaris/SolarisProcessList.h)34
-rw-r--r--solaris/SolarisProcess.c48
-rw-r--r--solaris/SolarisProcess.h8
-rw-r--r--solaris/SolarisProcessList.c542
-rw-r--r--solaris/SolarisProcessTable.c268
-rw-r--r--solaris/SolarisProcessTable.h31
-rw-r--r--unsupported/Platform.c34
-rw-r--r--unsupported/Platform.h52
-rw-r--r--unsupported/ProcessField.h2
-rw-r--r--unsupported/UnsupportedMachine.c56
-rw-r--r--unsupported/UnsupportedMachine.h17
-rw-r--r--unsupported/UnsupportedProcess.c43
-rw-r--r--unsupported/UnsupportedProcess.h6
-rw-r--r--unsupported/UnsupportedProcessList.c99
-rw-r--r--unsupported/UnsupportedProcessList.h21
-rw-r--r--unsupported/UnsupportedProcessTable.c90
-rw-r--r--unsupported/UnsupportedProcessTable.h17
-rw-r--r--zfs/ZfsArcMeter.c4
-rw-r--r--zfs/ZfsArcMeter.h2
-rw-r--r--zfs/ZfsArcStats.h3
-rw-r--r--zfs/ZfsCompressedArcMeter.c4
-rw-r--r--zfs/ZfsCompressedArcMeter.h2
305 files changed, 19259 insertions, 10706 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..5516fd6
--- /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
new file mode 100644
index 0000000..2bdf443
--- /dev/null
+++ b/.github/workflows/build_release.yml
@@ -0,0 +1,51 @@
+name: Build Source Release
+
+# Trigger whenever a release is created
+on:
+ release:
+ types:
+ - created
+
+jobs:
+ build:
+ name: build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: true
+
+ - name: archive
+ id: archive
+ run: |
+ VERSION=${{ github.event.release.tag_name }}
+ PKGNAME="htop-$VERSION"
+ SHASUM=$PKGNAME.tar.xz.sha256
+ autoreconf -i
+ mkdir -p /tmp/$PKGNAME
+ mv * /tmp/$PKGNAME
+ mv /tmp/$PKGNAME .
+ TARBALL=$PKGNAME.tar.xz
+ tar cJf $TARBALL $PKGNAME
+ sha256sum $TARBALL > $SHASUM
+ echo "::set-output name=tarball::$TARBALL"
+ echo "::set-output name=shasum::$SHASUM"
+ - name: upload tarball
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ github.event.release.upload_url }}
+ asset_path: ./${{ steps.archive.outputs.tarball }}
+ asset_name: ${{ steps.archive.outputs.tarball }}
+ asset_content_type: application/gzip
+
+ - name: upload shasum
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ github.event.release.upload_url }}
+ asset_path: ./${{ steps.archive.outputs.shasum }}
+ asset_name: ${{ steps.archive.outputs.shasum }}
+ asset_content_type: text/plain
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 617ed30..0582e05 100644
--- 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-16
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/focal/ llvm-toolchain-focal-16 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-16 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-16
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/focal/ llvm-toolchain-focal-16 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-16 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,66 +98,100 @@ 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
+ # 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
- # Until Ubuntu catches up with pcp-5.2.3+, cannot use -werror due to:
- # passing argument 2 of ‘pmLookupName’ from incompatible pointer type
- run: ./configure --enable-pcp --enable-unicode
+ run: ./configure --enable-werror --enable-pcp --enable-unicode || ( 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-16
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/focal/ llvm-toolchain-focal-16 main' -y
sudo apt-get update -q
- 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 clang-16 clang-tools-16 libncursesw5-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: scan-build-16 -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-12 -analyze-headers --status-bugs make -j"$(nproc)"
+ run: scan-build-16 -analyze-headers --status-bugs make -j"$(nproc)"
+
+ build-ubuntu-latest-clang-sanitizer:
+ runs-on: ubuntu-latest
+ env:
+ CC: clang-16
+ 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@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-16 main' -y
+ sudo apt-get update -q
+ - name: Install LLVM Toolchain
+ run: sudo apt-get install --no-install-recommends clang-16 libclang-rt-16-dev llvm-16
+ - 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 || ( cat config.log; exit 1; )
+ - name: Build
+ 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
@@ -166,6 +200,6 @@ jobs:
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 0000000..47383a7
--- /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 0000000..6c058ec
--- /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 8262449..50f3bd1 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 1d373ef..4049a95 100644
--- a/Action.c
+++ b/Action.c
@@ -1,7 +1,7 @@
/*
htop - Action.c
(C) 2015 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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);
+ 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,18 +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);
- ScreenManager_run(scr, NULL, NULL);
+ 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) {
+ 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;
@@ -116,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;
}
@@ -154,21 +164,34 @@ static bool collapseIntoParent(Panel* panel) {
}
Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) {
- Settings_setSortKey(settings, 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 ProcessField* fields = st->settings->fields;
- Hashtable* dynamicColumns = st->settings->dynamicColumns;
+ 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;
@@ -177,80 +200,119 @@ static Htop_Reaction actionSetSortColumn(State* st) {
name = String_trim(Process_fields[fields[i]].name);
}
Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i]));
- if (fields[i] == Settings_getActiveSortKey(st->settings))
+ if (fields[i] == ScreenSettings_getActiveSortKey(settings->ss))
Panel_setSelected(sortPanel, i);
free(name);
}
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);
- if (st->pauseProcessUpdate)
- ProcessList_sort(st->pl);
+ 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) {
- st->settings->treeView = !st->settings->treeView;
+ Machine* host = st->host;
+ ScreenSettings* ss = host->settings->ss;
+ ss->treeView = !ss->treeView;
+
+ if (!ss->allBranchesCollapsed)
+ Table_expandTree(host->activeTable);
+
+ host->activeTable->needsSort = true;
- if (!st->settings->allBranchesCollapsed)
- ProcessList_expandTree(st->pl);
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
}
+static Htop_Reaction actionToggleHideMeters(State* st) {
+ st->hideMeters = !st->hideMeters;
+ return HTOP_RESIZE | HTOP_KEEP_FOLLOWING;
+}
+
static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) {
- st->settings->allBranchesCollapsed = !st->settings->allBranchesCollapsed;
- if (st->settings->allBranchesCollapsed)
- ProcessList_collapseAllBranches(st->pl);
+ Machine* host = st->host;
+ ScreenSettings* ss = host->settings->ss;
+ if (!ss->treeView) {
+ return HTOP_OK;
+ }
+ ss->allBranchesCollapsed = !ss->allBranchesCollapsed;
+ if (ss->allBranchesCollapsed)
+ 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;
}
@@ -261,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);
@@ -269,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);
@@ -277,19 +339,22 @@ static Htop_Reaction actionLowerPriority(State* st) {
}
static Htop_Reaction actionInvertSortOrder(State* st) {
- Settings_invertSortOrder(st->settings);
- if (st->pauseProcessUpdate)
- ProcessList_sort(st->pl);
- 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->treeView) {
+ if (!st->host->settings->ss->treeView) {
return HTOP_OK;
}
bool changed = collapseIntoParent((Panel*)st->mainPanel);
@@ -297,7 +362,61 @@ static Htop_Reaction actionCollapseIntoParent(State* st) {
}
static Htop_Reaction actionExpandCollapseOrSortColumn(State* st) {
- return st->settings->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->host->settings;
+ settings->ssIndex++;
+ if (settings->ssIndex == settings->nScreens) {
+ settings->ssIndex = 0;
+ }
+ setActiveScreen(settings, st, settings->ssIndex);
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;
+}
+
+static Htop_Reaction actionPrevScreen(State* st) {
+ Settings* settings = st->host->settings;
+ if (settings->ssIndex == 0) {
+ settings->ssIndex = settings->nScreens - 1;
+ } else {
+ settings->ssIndex--;
+ }
+ setActiveScreen(settings, st, settings->ssIndex);
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;
+}
+
+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* tab = settings->screens[i]->heading;
+ int len = strlen(tab);
+ if (x <= s + len + 1) {
+ settings->ssIndex = i;
+ setActiveScreen(settings, st, i);
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;
+ }
+ s += len + 3;
+ }
+ return 0;
}
static Htop_Reaction actionQuit(ATTR_UNUSED State* st) {
@@ -305,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);
@@ -337,39 +457,91 @@ 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;
- Panel* signalsPanel = SignalsPanel_new();
+ static int preSelectedSignal = SIGNALSPANEL_INITSELECTEDSIGNAL;
+
+ Panel* signalsPanel = SignalsPanel_new(preSelectedSignal);
const ListItem* sgn = (ListItem*) Action_pickFromVector(st, signalsPanel, 14, true);
if (sgn && sgn->key != 0) {
+ preSelectedSignal = sgn->key;
Panel_setHeader((Panel*)st->mainPanel, "Sending...");
Panel_draw((Panel*)st->mainPanel, false, true, true, State_hideFunctionBar(st));
refresh();
- 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);
@@ -377,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;
}
@@ -388,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);
@@ -404,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);
@@ -416,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) {
@@ -435,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;
}
@@ -459,6 +642,8 @@ 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" },
{ .key = " F3 /: ", .roInactive = false, .info = "incremental name search" },
@@ -470,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" },
@@ -483,12 +669,13 @@ static const struct {
bool roInactive;
const char* info;
} helpRight[] = {
+ { .key = " S-Tab: ", .roInactive = false, .info = "switch to previous screen tab" },
{ .key = " Space: ", .roInactive = false, .info = "tag process" },
{ .key = " c: ", .roInactive = false, .info = "tag process and its children" },
{ .key = " U: ", .roInactive = false, .info = "untag all processes" },
{ .key = " F9 k: ", .roInactive = true, .info = "kill process/tagged processes" },
{ .key = " F7 ]: ", .roInactive = true, .info = "higher priority (root only)" },
- { .key = " F8 [: ", .roInactive = false, .info = "lower priority (+ nice)" },
+ { .key = " F8 [: ", .roInactive = true, .info = "lower priority (+ nice)" },
#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
{ .key = " a: ", .roInactive = true, .info = "set CPU affinity" },
#endif
@@ -498,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" },
@@ -519,52 +709,65 @@ static Htop_Reaction actionHelp(State* st) {
int line = 0;
mvaddstr(line++, 0, "htop " VERSION " - " COPYRIGHT);
- mvaddstr(line++, 0, "Released under the GNU GPLv2. See 'man' page for more info.");
+ mvaddstr(line++, 0, "Released under the GNU GPLv2+. See 'man' page for more info.");
attrset(CRT_colors[DEFAULT_COLOR]);
line++;
mvaddstr(line++, 0, "CPU usage bar: ");
+#define addbartext(attr, prefix, text) \
+ do { \
+ addattrstr(CRT_colors[DEFAULT_COLOR], prefix); \
+ addattrstr(attr, text); \
+ } while(0)
+
addattrstr(CRT_colors[BAR_BORDER], "[");
- if (st->settings->detailedCPUTime) {
- addattrstr(CRT_colors[CPU_NICE_TEXT], "low"); addstr("/");
- addattrstr(CRT_colors[CPU_NORMAL], "normal"); addstr("/");
- addattrstr(CRT_colors[CPU_SYSTEM], "kernel"); addstr("/");
- addattrstr(CRT_colors[CPU_IRQ], "irq"); addstr("/");
- addattrstr(CRT_colors[CPU_SOFTIRQ], "soft-irq"); addstr("/");
- addattrstr(CRT_colors[CPU_STEAL], "steal"); addstr("/");
- addattrstr(CRT_colors[CPU_GUEST], "guest"); addstr("/");
- addattrstr(CRT_colors[CPU_IOWAIT], "io-wait");
- addattrstr(CRT_colors[BAR_SHADOW], " used%");
+ addbartext(CRT_colors[CPU_NICE_TEXT], "", "low");
+ addbartext(CRT_colors[CPU_NORMAL], "/", "normal");
+ addbartext(CRT_colors[CPU_SYSTEM], "/", "kernel");
+ if (st->host->settings->detailedCPUTime) {
+ addbartext(CRT_colors[CPU_IRQ], "/", "irq");
+ addbartext(CRT_colors[CPU_SOFTIRQ], "/", "soft-irq");
+ addbartext(CRT_colors[CPU_STEAL], "/", "steal");
+ addbartext(CRT_colors[CPU_GUEST], "/", "guest");
+ addbartext(CRT_colors[CPU_IOWAIT], "/", "io-wait");
+ addbartext(CRT_colors[BAR_SHADOW], " ", "used%");
} else {
- addattrstr(CRT_colors[CPU_NICE_TEXT], "low-priority"); addstr("/");
- addattrstr(CRT_colors[CPU_NORMAL], "normal"); addstr("/");
- addattrstr(CRT_colors[CPU_SYSTEM], "kernel"); addstr("/");
- addattrstr(CRT_colors[CPU_GUEST], "virtualized");
- addattrstr(CRT_colors[BAR_SHADOW], " used%");
+ addbartext(CRT_colors[CPU_GUEST], "/", "guest");
+ addbartext(CRT_colors[BAR_SHADOW], " ", "used%");
}
addattrstr(CRT_colors[BAR_BORDER], "]");
+
attrset(CRT_colors[DEFAULT_COLOR]);
mvaddstr(line++, 0, "Memory bar: ");
addattrstr(CRT_colors[BAR_BORDER], "[");
- addattrstr(CRT_colors[MEMORY_USED], "used"); addstr("/");
- addattrstr(CRT_colors[MEMORY_BUFFERS_TEXT], "buffers"); addstr("/");
- addattrstr(CRT_colors[MEMORY_SHARED], "shared"); addstr("/");
- addattrstr(CRT_colors[MEMORY_CACHE], "cache");
- addattrstr(CRT_colors[BAR_SHADOW], " used/total");
+ addbartext(CRT_colors[MEMORY_USED], "", "used");
+ addbartext(CRT_colors[MEMORY_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], "/", "total");
addattrstr(CRT_colors[BAR_BORDER], "]");
+
attrset(CRT_colors[DEFAULT_COLOR]);
mvaddstr(line++, 0, "Swap bar: ");
addattrstr(CRT_colors[BAR_BORDER], "[");
- addattrstr(CRT_colors[SWAP], "used");
+ addbartext(CRT_colors[SWAP], "", "used");
#ifdef HTOP_LINUX
- addattrstr(CRT_colors[BAR_SHADOW], "/");
- addattrstr(CRT_colors[SWAP_CACHE], "cache");
- addattrstr(CRT_colors[BAR_SHADOW], " used/total");
+ addbartext(CRT_colors[SWAP_CACHE], "/", "cache");
+ addbartext(CRT_colors[SWAP_FRONTSWAP], "/", "frontswap");
#else
- addattrstr(CRT_colors[BAR_SHADOW], " used/total");
+ addbartext(CRT_colors[SWAP_CACHE], " ", "");
#endif
+ addbartext(CRT_colors[BAR_SHADOW], " ", "used");
+ addbartext(CRT_colors[BAR_SHADOW], "/", "total");
addattrstr(CRT_colors[BAR_BORDER], "]");
+
+ line++;
+
+#undef addbartext
+
attrset(CRT_colors[DEFAULT_COLOR]);
mvaddstr(line++, 0, "Type and layout of header meters are configurable in the setup screen.");
if (CRT_colorScheme == COLORSCHEME_MONOCHROME) {
@@ -572,9 +775,23 @@ static Htop_Reaction actionHelp(State* st) {
}
line++;
- mvaddstr(line++, 0, "Process state: R: running; S: sleeping; T: traced/stopped; Z: zombie; D: disk sleep");
+#define addattrstatestr(attr, state, desc) \
+ do { \
+ addattrstr(attr, state); \
+ addattrstr(CRT_colors[DEFAULT_COLOR], ": " desc); \
+ } while(0)
+
+ mvaddstr(line, 0, "Process state: ");
+ addattrstatestr(CRT_colors[PROCESS_RUN_STATE], "R", "running; ");
+ addattrstatestr(CRT_colors[PROCESS_SHADOW], "S", "sleeping; ");
+ addattrstatestr(CRT_colors[PROCESS_RUN_STATE], "t", "traced/stopped; ");
+ addattrstatestr(CRT_colors[PROCESS_D_STATE], "Z", "zombie; ");
+ addattrstatestr(CRT_colors[PROCESS_D_STATE], "D", "disk sleep");
+ attrset(CRT_colors[DEFAULT_COLOR]);
- line++;
+#undef addattrstatestr
+
+ line += 2;
const bool readonly = Settings_isReadonly();
@@ -596,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++;
@@ -615,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);
@@ -644,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);
@@ -658,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;
@@ -675,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;
@@ -711,4 +943,6 @@ void Action_setBindings(Htop_Action* keys) {
keys[KEY_F(10)] = actionQuit;
keys[KEY_F(18)] = actionExpandCollapseOrSortColumn;
keys[KEY_RECLICK] = actionExpandOrCollapse;
+ keys[KEY_SHIFT_TAB] = actionPrevScreen;
+ keys['\t'] = actionNextScreen;
}
diff --git a/Action.h b/Action.h
index 4a59072..caade03 100644
--- a/Action.h
+++ b/Action.h
@@ -3,22 +3,19 @@
/*
htop - Action.h
(C) 2015 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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,27 +33,29 @@ 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(State* st, int x);
+
Htop_Reaction Action_follow(State* st);
void Action_setBindings(Htop_Action* keys);
diff --git a/Affinity.c b/Affinity.c
index c1e73cc..fab239e 100644
--- a/Affinity.c
+++ b/Affinity.c
@@ -2,7 +2,7 @@
htop - Affinity.c
(C) 2004-2011 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 cf08cce..c218906 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.
-Released under the GNU GPLv2, see the COPYING file
+(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 d50d554..f400908 100644
--- a/AffinityPanel.c
+++ b/AffinityPanel.c
@@ -1,7 +1,7 @@
/*
htop - AffinityPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 183e447..f5b97b9 100644
--- a/AffinityPanel.h
+++ b/AffinityPanel.h
@@ -3,19 +3,19 @@
/*
htop - AffinityPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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 08cbb17..545ef7d 100644
--- a/AvailableColumnsPanel.c
+++ b/AvailableColumnsPanel.c
@@ -1,10 +1,12 @@
/*
htop - AvailableColumnsPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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 828f7e6..0d8709f 100644
--- a/AvailableColumnsPanel.h
+++ b/AvailableColumnsPanel.h
@@ -3,7 +3,7 @@
/*
htop - AvailableColumnsPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 c00aec1..9a1d367 100644
--- a/AvailableMetersPanel.c
+++ b/AvailableMetersPanel.c
@@ -1,10 +1,12 @@
/*
htop - AvailableMetersPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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,36 +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;
+ 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;
}
@@ -96,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 {
@@ -127,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
@@ -139,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;
@@ -160,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 5d77c5d..fad1e6e 100644
--- a/AvailableMetersPanel.h
+++ b/AvailableMetersPanel.h
@@ -3,25 +3,23 @@
/*
htop - AvailableMetersPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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 26a5ac4..dad7754 100644
--- a/BatteryMeter.c
+++ b/BatteryMeter.c
@@ -1,17 +1,20 @@
/*
htop - BatteryMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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/BatteryMeter.h b/BatteryMeter.h
index fa6d503..d0818b7 100644
--- a/BatteryMeter.h
+++ b/BatteryMeter.h
@@ -3,7 +3,7 @@
/*
htop - BatteryMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
This meter written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
diff --git a/CPUMeter.c b/CPUMeter.c
index 28fb730..afcddeb 100644
--- a/CPUMeter.c
+++ b/CPUMeter.c
@@ -1,7 +1,7 @@
/*
htop - CPUMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -9,14 +9,16 @@ in the source distribution for its full text.
#include "CPUMeter.h"
-#include <math.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 +42,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 +63,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 +82,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 +119,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 +135,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 +148,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 +168,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 +207,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 +215,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;
}
@@ -214,7 +234,7 @@ static void AllCPUsMeter_updateValues(Meter* this) {
}
static void CPUMeterCommonInit(Meter* this, int ncol) {
- unsigned int cpus = this->pl->existingCPUs;
+ unsigned int cpus = this->host->existingCPUs;
CPUMeterData* data = this->meterData;
if (!data) {
data = this->meterData = xMalloc(sizeof(CPUMeterData));
@@ -226,7 +246,7 @@ 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]);
}
diff --git a/CPUMeter.h b/CPUMeter.h
index 6dbf815..7f2ddb1 100644
--- a/CPUMeter.h
+++ b/CPUMeter.h
@@ -3,7 +3,7 @@
/*
htop - CPUMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/CRT.c b/CRT.c
index 2533f61..bd5453b 100644
--- a/CRT.c
+++ b/CRT.c
@@ -1,7 +1,7 @@
/*
htop - CRT.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -13,22 +13,33 @@ in the source distribution for its full text.
#include <fcntl.h>
#include <langinfo.h>
#include <signal.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include "CommandLine.h"
#include "ProvideCurses.h"
+#include "ProvideTerm.h"
#include "XUtils.h"
-#ifdef HAVE_EXECINFO_H
-#include <execinfo.h>
-#endif
-
#if !defined(NDEBUG) && defined(HAVE_MEMFD_CREATE)
#include <sys/mman.h>
#endif
+#if defined(HAVE_LIBUNWIND_H) && defined(HAVE_LIBUNWIND)
+# define PRINT_BACKTRACE
+# define UNW_LOCAL_ONLY
+# include <libunwind.h>
+# if defined(HAVE_DLADDR)
+# include <dlfcn.h>
+# endif
+#elif defined(HAVE_EXECINFO_H)
+# define PRINT_BACKTRACE
+# include <execinfo.h>
+#endif
+
#define ColorIndex(i,j) ((7-(i))*8+(j))
@@ -125,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),
@@ -147,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),
@@ -176,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),
@@ -185,22 +199,30 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Magenta, Black),
[CPU_STEAL] = ColorPair(Cyan, Black),
[CPU_GUEST] = ColorPair(Cyan, Black),
+ [PANEL_EDIT] = ColorPair(White, Blue),
+ [SCREENS_OTH_BORDER] = ColorPair(Blue, Blue),
+ [SCREENS_OTH_TEXT] = ColorPair(Black, Blue),
+ [SCREENS_CUR_BORDER] = ColorPair(Green, Green),
+ [SCREENS_CUR_TEXT] = ColorPair(Black, Green),
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Cyan, Black),
[PRESSURE_STALL_SIXTY] = A_BOLD | ColorPair(Cyan, Black),
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Black),
- [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),
@@ -250,10 +272,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,
@@ -261,6 +285,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,
@@ -287,9 +312,16 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = A_BOLD,
[CPU_STEAL] = A_DIM,
[CPU_GUEST] = A_DIM,
+ [PANEL_EDIT] = A_BOLD,
+ [SCREENS_OTH_BORDER] = A_DIM,
+ [SCREENS_OTH_TEXT] = A_DIM,
+ [SCREENS_CUR_BORDER] = A_REVERSE,
+ [SCREENS_CUR_TEXT] = A_REVERSE,
[PRESSURE_STALL_THREEHUNDRED] = A_DIM,
[PRESSURE_STALL_SIXTY] = A_NORMAL,
[PRESSURE_STALL_TEN] = A_BOLD,
+ [FILE_DESCRIPTOR_USED] = A_BOLD,
+ [FILE_DESCRIPTOR_MAX] = A_BOLD,
[ZFS_MFU] = A_NORMAL,
[ZFS_MRU] = A_NORMAL,
[ZFS_ANON] = A_DIM,
@@ -297,7 +329,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,
@@ -352,10 +385,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),
@@ -363,6 +398,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),
@@ -389,9 +425,16 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Blue, White),
[CPU_STEAL] = ColorPair(Cyan, White),
[CPU_GUEST] = ColorPair(Cyan, White),
+ [PANEL_EDIT] = ColorPair(White, Blue),
+ [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Black, White),
+ [SCREENS_OTH_TEXT] = A_BOLD | ColorPair(Black, White),
+ [SCREENS_CUR_BORDER] = ColorPair(Green, Green),
+ [SCREENS_CUR_TEXT] = ColorPair(Black, Green),
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, White),
[PRESSURE_STALL_SIXTY] = ColorPair(Black, White),
[PRESSURE_STALL_TEN] = ColorPair(Black, White),
+ [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),
@@ -399,7 +442,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),
@@ -454,10 +498,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),
@@ -465,6 +511,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),
@@ -491,9 +538,16 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Blue, Black),
[CPU_STEAL] = ColorPair(Black, Black),
[CPU_GUEST] = ColorPair(Black, Black),
+ [PANEL_EDIT] = ColorPair(White, Blue),
+ [SCREENS_OTH_BORDER] = ColorPair(Blue, Black),
+ [SCREENS_OTH_TEXT] = ColorPair(Blue, Black),
+ [SCREENS_CUR_BORDER] = ColorPair(Green, Green),
+ [SCREENS_CUR_TEXT] = ColorPair(Black, Green),
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, Black),
[PRESSURE_STALL_SIXTY] = ColorPair(Black, Black),
[PRESSURE_STALL_TEN] = ColorPair(Black, Black),
+ [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),
@@ -501,7 +555,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),
@@ -556,10 +611,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),
@@ -567,6 +624,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),
@@ -593,9 +651,16 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Black, Blue),
[CPU_STEAL] = ColorPair(White, Blue),
[CPU_GUEST] = ColorPair(White, Blue),
+ [PANEL_EDIT] = ColorPair(White, Blue),
+ [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Yellow, Blue),
+ [SCREENS_OTH_TEXT] = ColorPair(Cyan, Blue),
+ [SCREENS_CUR_BORDER] = ColorPair(Cyan, Cyan),
+ [SCREENS_CUR_TEXT] = ColorPair(Black, Cyan),
[PRESSURE_STALL_THREEHUNDRED] = A_BOLD | ColorPair(Black, Blue),
[PRESSURE_STALL_SIXTY] = A_NORMAL | ColorPair(White, Blue),
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Blue),
+ [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),
@@ -603,7 +668,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),
@@ -658,10 +724,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),
@@ -669,6 +737,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),
@@ -693,9 +762,16 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[CPU_SOFTIRQ] = ColorPair(Blue, Black),
[CPU_STEAL] = ColorPair(Cyan, Black),
[CPU_GUEST] = ColorPair(Cyan, Black),
+ [PANEL_EDIT] = ColorPair(White, Cyan),
+ [SCREENS_OTH_BORDER] = ColorPair(White, Black),
+ [SCREENS_OTH_TEXT] = ColorPair(Cyan, Black),
+ [SCREENS_CUR_BORDER] = A_BOLD | ColorPair(White, Black),
+ [SCREENS_CUR_TEXT] = A_BOLD | ColorPair(Green, Black),
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Green, Black),
[PRESSURE_STALL_SIXTY] = ColorPair(Green, Black),
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(Green, Black),
+ [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),
@@ -703,7 +779,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),
@@ -717,7 +794,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[COLORSCHEME_BROKENGRAY] = { 0 } // dynamically generated.
};
-int CRT_cursorX = 0;
+static bool CRT_retainScreenOnExit = false;
int CRT_scrollHAmount = 5;
@@ -797,7 +874,7 @@ static void dumpStderr(void) {
fprintf(stderr, ">>>>>>>>>> stderr output >>>>>>>>>>\n");
header = true;
}
- (void)! write(STDERR_FILENO, buffer, res);
+ full_write(STDERR_FILENO, buffer, res);
}
}
@@ -808,6 +885,16 @@ static void dumpStderr(void) {
stderrRedirectNewFd = -1;
}
+void CRT_debug_impl(const char* file, size_t lineno, const char* func, const char* fmt, ...) {
+ va_list args;
+
+ fprintf(stderr, "[%s:%zu (%s)]: ", file, lineno, func);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+}
+
#else /* !NDEBUG */
static void redirectStderr(void) {
@@ -822,40 +909,65 @@ 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);
}
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);
}
-void CRT_init(const Settings* settings, bool allowUnicode) {
- redirectStderr();
+#ifdef HAVE_GETMOUSE
+void CRT_setMouse(bool enabled) {
+ if (enabled) {
+#if NCURSES_MOUSE_VERSION > 1
+ mousemask(BUTTON1_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);
+#else
+ mousemask(BUTTON1_RELEASED, NULL);
+#endif
+ } else {
+ mousemask(0, NULL);
+ }
+}
+#endif
+void CRT_init(const Settings* settings, bool allowUnicode, 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);
@@ -907,6 +1019,7 @@ IGNORE_WCASTQUAL_BEGIN
define_key("\033[14~", KEY_F(4));
define_key("\033[14;2~", KEY_F(15));
define_key("\033[17;2~", KEY_F(18));
+ define_key("\033[Z", KEY_SHIFT_TAB);
char sequence[3] = "\033a";
for (char c = 'a'; c <= 'z'; c++) {
sequence[1] = c;
@@ -917,6 +1030,9 @@ IGNORE_WCASTQUAL_END
#undef define_key
#endif
}
+ if (termType && (String_startsWith(termType, "rxvt"))) {
+ define_key("\033[Z", KEY_SHIFT_TAB);
+ }
CRT_installSignalHandlers();
@@ -941,18 +1057,23 @@ IGNORE_WCASTQUAL_END
#endif
CRT_treeStrAscii;
-#ifdef HAVE_GETMOUSE
-#if NCURSES_MOUSE_VERSION > 1
- mousemask(BUTTON1_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);
-#else
- mousemask(BUTTON1_RELEASED, NULL);
-#endif
-#endif
+ CRT_setMouse(settings->enableMouse);
CRT_degreeSign = initDegreeSign();
}
-void CRT_done() {
+void CRT_done(void) {
+ int resetColor = CRT_colors ? CRT_colors[RESET_COLOR] : CRT_colorSchemes[COLORSCHEME_DEFAULT][RESET_COLOR];
+
+ attron(resetColor);
+ mvhline(LINES - 1, 0, ' ', COLS);
+ attroff(resetColor);
+ refresh();
+
+ if (CRT_retainScreenOnExit) {
+ mvcur(-1, -1, LINES - 1, 0);
+ }
+
curs_set(1);
endwin();
@@ -966,7 +1087,7 @@ void CRT_fatalError(const char* note) {
exit(2);
}
-int CRT_readKey() {
+int CRT_readKey(void) {
nocbreak();
cbreak();
nodelay(stdscr, FALSE);
@@ -975,13 +1096,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);
}
@@ -991,9 +1112,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);
}
}
@@ -1008,6 +1127,58 @@ void CRT_setColors(int colorScheme) {
CRT_colors = CRT_colorSchemes[colorScheme];
}
+#ifdef PRINT_BACKTRACE
+static void print_backtrace(void) {
+#if defined(HAVE_LIBUNWIND_H) && defined(HAVE_LIBUNWIND)
+ unw_context_t context;
+ unw_getcontext(&context);
+
+ unw_cursor_t cursor;
+ unw_init_local(&cursor, &context);
+
+ unsigned int item = 0;
+
+ while (unw_step(&cursor) > 0) {
+ unw_word_t pc;
+ unw_get_reg(&cursor, UNW_REG_IP, &pc);
+ if (pc == 0)
+ break;
+
+ char symbolName[256] = "?";
+ unw_word_t offset = 0;
+ unw_get_proc_name(&cursor, symbolName, sizeof(symbolName), &offset);
+
+ unw_proc_info_t pip;
+ pip.unwind_info = 0;
+
+ const char* fname = "?";
+ const void* ptr = 0;
+ if (unw_get_proc_info(&cursor, &pip) == 0) {
+ ptr = (const void*)(pip.start_ip + offset);
+
+ #ifdef HAVE_DLADDR
+ Dl_info dlinfo;
+ if (dladdr(ptr, &dlinfo) && dlinfo.dli_fname && *dlinfo.dli_fname)
+ fname = dlinfo.dli_fname;
+ #endif
+ }
+
+ 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\n", item++, pc, fname, symbolName, offset, ptr, frame);
+ }
+#elif defined(HAVE_EXECINFO_H)
+ void* backtraceArray[256];
+
+ size_t size = backtrace(backtraceArray, ARRAYSIZE(backtraceArray));
+ backtrace_symbols_fd(backtraceArray, size, STDERR_FILENO);
+#else
+#error No implementation for print_backtrace()!
+#endif
+}
+#endif
+
void CRT_handleSIGSEGV(int signal) {
CRT_done();
@@ -1016,13 +1187,14 @@ void CRT_handleSIGSEGV(int signal) {
"============================\n"
"Please check at https://htop.dev/issues whether this issue has already been reported.\n"
"If no similar issue has been reported before, please create a new issue with the following information:\n"
- " - 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
);
-#ifdef HAVE_EXECINFO_H
+#ifdef PRINT_BACKTRACE
fprintf(stderr, " - Backtrace of the issue (see below)\n");
#endif
@@ -1048,28 +1220,27 @@ void CRT_handleSIGSEGV(int signal) {
Settings_write(CRT_crashSettings, true);
fprintf(stderr, "\n\n");
-#ifdef HAVE_EXECINFO_H
+#ifdef PRINT_BACKTRACE
fprintf(stderr,
"Backtrace information:\n"
"----------------------\n"
);
- void* backtraceArray[256];
+ print_backtrace();
- size_t size = backtrace(backtraceArray, ARRAYSIZE(backtraceArray));
- backtrace_symbols_fd(backtraceArray, size, STDERR_FILENO);
fprintf(stderr,
"\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
);
#ifdef HTOP_DARWIN
- fprintf(stderr, " otool -tvV `which "PACKAGE"` > ~/htop.otool\n");
+ fprintf(stderr, " otool -tvV `which %s` > ~/%s.otool\n", program, program);
#else
- fprintf(stderr, " objdump -d -S -w `which "PACKAGE"` > ~/htop.objdump\n");
+ fprintf(stderr, " objdump -d -S -w `which %s` > ~/%s.objdump\n", program, program);
#endif
fprintf(stderr,
@@ -1081,12 +1252,13 @@ void CRT_handleSIGSEGV(int signal) {
fprintf(stderr,
"Running this program with debug symbols or inside a debugger may provide further insights.\n"
"\n"
- "Thank you for helping to improve "PACKAGE"!\n"
- "\n"
+ "Thank you for helping to improve %s!\n"
+ "\n",
+ program
);
/* Call old sigsegv handler; may be default exit or third party one (e.g. ASAN) */
- if (sigaction (signal, &old_sig_handler[signal], NULL) < 0) {
+ if (sigaction(signal, &old_sig_handler[signal], NULL) < 0) {
/* This avoids an infinite loop in case the handler could not be reset. */
fprintf(stderr,
"!!! Chained handler could not be restored. Forcing exit.\n"
diff --git a/CRT.h b/CRT.h
index f2c2c22..5809798 100644
--- a/CRT.h
+++ b/CRT.h
@@ -3,12 +3,10 @@
/*
htop - CRT.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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,9 +121,16 @@ typedef enum ColorElements_ {
CPU_SOFTIRQ,
CPU_STEAL,
CPU_GUEST,
+ PANEL_EDIT,
+ SCREENS_OTH_BORDER,
+ SCREENS_OTH_TEXT,
+ SCREENS_CUR_BORDER,
+ SCREENS_CUR_TEXT,
PRESSURE_STALL_TEN,
PRESSURE_STALL_SIXTY,
PRESSURE_STALL_THREEHUNDRED,
+ FILE_DESCRIPTOR_USED,
+ FILE_DESCRIPTOR_MAX,
ZFS_MFU,
ZFS_MRU,
ZFS_ANON,
@@ -130,7 +138,8 @@ typedef enum ColorElements_ {
ZFS_OTHER,
ZFS_COMPRESSED,
ZFS_RATIO,
- ZRAM,
+ ZRAM_COMPRESSED,
+ ZRAM_UNCOMPRESSED,
DYNAMIC_GRAY,
DYNAMIC_DARKGRAY,
DYNAMIC_RED,
@@ -145,11 +154,19 @@ typedef enum ColorElements_ {
void CRT_fatalError(const char* note) ATTR_NORETURN;
+#ifdef NDEBUG
+# define CRT_debug(...)
+#else
+void CRT_debug_impl(const char* file, size_t lineno, const char* func, const char* fmt, ...) ATTR_FORMAT(printf, 4, 5);
+# define CRT_debug(...) CRT_debug_impl(__FILE__, __LINE__, __func__, __VA_ARGS__)
+#endif
+
void CRT_handleSIGSEGV(int signal) ATTR_NORETURN;
-#define KEY_WHEELUP KEY_F(20)
-#define KEY_WHEELDOWN KEY_F(21)
-#define KEY_RECLICK KEY_F(22)
+#define KEY_WHEELUP KEY_F(30)
+#define KEY_WHEELDOWN KEY_F(31)
+#define KEY_RECLICK KEY_F(32)
+#define KEY_SHIFT_TAB KEY_F(33)
#define KEY_ALT(x) (KEY_F(64 - 26) + ((x) - 'A'))
extern const char* CRT_degreeSign;
@@ -172,7 +189,13 @@ extern int CRT_scrollWheelVAmount;
extern ColorScheme CRT_colorScheme;
-void CRT_init(const Settings* settings, bool allowUnicode);
+#ifdef HAVE_GETMOUSE
+void CRT_setMouse(bool enabled);
+#else
+#define CRT_setMouse(enabled)
+#endif
+
+void CRT_init(const Settings* settings, bool allowUnicode, bool retainScreenOnExit);
void CRT_done(void);
diff --git a/CategoriesPanel.c b/CategoriesPanel.c
index 21010b3..64a3f06 100644
--- a/CategoriesPanel.c
+++ b/CategoriesPanel.c
@@ -1,20 +1,20 @@
/*
htop - CategoriesPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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 "ColumnsPanel.h"
#include "DisplayOptionsPanel.h"
#include "FunctionBar.h"
#include "Header.h"
@@ -25,6 +25,9 @@ in the source distribution for its full text.
#include "MetersPanel.h"
#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,29 +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);
}
-static void CategoriesPanel_makeColumnsPage(CategoriesPanel* this) {
- Panel* columns = (Panel*) ColumnsPanel_new(this->settings);
- Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, this->settings->dynamicColumns);
+#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) {
+ Settings* settings = this->host->settings;
+ Panel* screens = (Panel*) ScreensPanel_new(settings);
+ Panel* columns = (Panel*) ((ScreensPanel*)screens)->columns;
+ 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);
}
@@ -87,11 +107,14 @@ 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 },
- { .name = "Columns", .ctor = CategoriesPanel_makeColumnsPage },
+#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 },
};
@@ -147,17 +170,16 @@ 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, "Setup");
+ Panel_setHeader(super, "Categories");
for (size_t i = 0; i < ARRAYSIZE(categoriesPanelPages); i++)
Panel_add(super, (Object*) ListItem_new(categoriesPanelPages[i].name, 0));
diff --git a/CategoriesPanel.h b/CategoriesPanel.h
index 451a483..1f50b8a 100644
--- a/CategoriesPanel.h
+++ b/CategoriesPanel.h
@@ -3,28 +3,25 @@
/*
htop - CategoriesPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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 b5725ec..b4cdf6a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,183 @@
+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
+* Restore functionality of stripExeFromCmdline setting
+* Fix some command line display settings not being honored without restart
+* Display single digit precision for CPU% greater than 99.9%
+* On Linux, FreeBSD and PCP consider only shrinkable ZFS ARC as cache
+* On Linux, increase field width of CPUD% and SWAPD% columns
+* Colorize process state characters in help screen
+* Use mousemask(3X) to enable and disable mouse control
+* Fix heap buffer overflow in Vector_compact
+* On Solaris, fix a process time scaling error
+* On Solaris, fix the build
+* On NetBSD, OpenBSD and Solaris ensure env buffer size is sufficient
+* On Linux, resolve processes exiting interfering with sampling
+* Fix 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
+
+What's new in version 3.2.0
+
+* Support for displaying multiple tabs in the user interface
+* Allow multiple filter and search terms (logical OR, separate by "|")
+* Set correct default sorting direction (defaultSortDesc)
+* Improve performance for process lookup and update
+* Rework the IOMeters initial display
+* Removed duplicate sections on COMM and EXE
+* Highlight process UNINTERRUPTIBLE_WAIT state (D)
+* Show only integer value when CPU% more than 99.9%
+* Handle rounding ambiguity between 99.9 and 100.0%
+* No longer leaves empty the last column in header
+* Fix header layout and meters reset if a header column is empty
+* Fix PID and UID column widths off-by-one error
+* On Linux, read generic sysfs batteries
+* On Linux, do not collect LRS per thread (it is process-wide)
+* On Linux, dynamically adjust the SECATTR and CGROUP column widths
+* On Linux, fix a crash in LXD
+* On FreeBSD, add support for showing process emulation
+* On Darwin, lazily set process TTY name
+* Always set SIGCHLD to default handling
+* Avoid zombie processes on signal races
+* Ensure last line is cleared when SIGINT is received
+* Instead of SIGTERM, pre-select the last sent signal
+* Internal Hashtable performance and sizing improvements
+* Add heuristics for guessing LXC or Docker from /proc/1/mounts
+* Force elapsed time display to zero if process started in the future
+* Avoid extremely large year values when printing time
+* Fix division by zero when calculating IO rates
+* Fix out of boundary writes in XUtils
+* Fix custom thread name display issue
+* Use AC_CANONICAL_HOST, not AC_CANONICAL_TARGET in configure.ac
+* Support libunwind of LLVM
+
+What's new in version 3.1.2
+
+* Bugfix for crash when storing modified settings at exit
+* Generate xz-compressed source tarball (with configure) using github actions
+* Allow -u UID with numerical value as argument
+* Added documentation for obsolete/state libraries/program files highlighting
+* Some obsolete/stale library highlighting refinements
+* Column width issues resolved
+* Dynamic UID column sizing improved
+* Discard stale information from Disk and Network I/O meters
+* Refined Linux kernel thread detection
+* Reworked process state handling
+* New CCGROUP column showing abbreviated cgroup name
+* New OFFSET column in the list of open files screen
+
+What's new in version 3.1.1
+
+* Update license headers to explicitly say GPLv2+
+* Document minimum version for libcap (thanks to James Brown)
+* Fix mouse wheel collision with autogroups nice adjustment
+* Adjust Makefile.am macro definitions for older automake versions
+* Ensure consistent reporting of MemoryMeter 'used' memory
+* Report hugepage memory as real and used memory (as before)
+* Handle procExeDeleted, usesDeletedLib without mergedCommandline mode
+* Validate meter configuration before proceeding beyond htoprc parsing
+* Properly release memory on partially read configuration
+* Handle interrupted sampling from within libpcp PDU transfers
+* On Linux, provide O_PATH value if not defined
+* On Linux, always compute procExeDeleted if already set
+* Workaround for Rosetta 2 on Darwin (thanks to Alexander Momchilov)
+* Fix FreeBSD cmdline memory leak in Process_updateCmdline, and
+* Plug a Disk I/O meter memory leak on FreeBSD (thanks to Ximalas)
+
What's new in version 3.1.0
* Updated COPYING file to remove the PLPA exemption (appendix 2)
diff --git a/ClockMeter.c b/ClockMeter.c
index 79bdecd..38f0591 100644
--- a/ClockMeter.c
+++ b/ClockMeter.c
@@ -1,7 +1,7 @@
/*
htop - ClockMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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/ClockMeter.h b/ClockMeter.h
index 675df3d..def0460 100644
--- a/ClockMeter.h
+++ b/ClockMeter.h
@@ -3,7 +3,7 @@
/*
htop - ClockMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/ColorsPanel.c b/ColorsPanel.c
index 79f001c..581c3a0 100644
--- a/ColorsPanel.c
+++ b/ColorsPanel.c
@@ -1,10 +1,12 @@
/*
htop - ColorsPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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,29 +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;
-
- CRT_setColors(mark);
- clear();
-
- result = HANDLED | REDRAW;
+
+ switch (ch) {
+ 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/ColorsPanel.h b/ColorsPanel.h
index 047f7af..373ec65 100644
--- a/ColorsPanel.h
+++ b/ColorsPanel.h
@@ -3,7 +3,7 @@
/*
htop - ColorsPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/ColumnsPanel.c b/ColumnsPanel.c
index d0c2b88..6662566 100644
--- a/ColumnsPanel.c
+++ b/ColumnsPanel.c
@@ -1,10 +1,12 @@
/*
htop - ColumnsPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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)
@@ -138,20 +125,26 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns)
Panel_add(super, (Object*) ListItem_new(name, key));
}
-ColumnsPanel* ColumnsPanel_new(Settings* settings) {
+void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns) {
+ Panel* super = (Panel*) this;
+ Panel_prune(super);
+ for (const RowField* fields = ss->fields; *fields; fields++)
+ ColumnsPanel_add(super, *fields, columns);
+ this->ss = ss;
+}
+
+ColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed) {
ColumnsPanel* this = AllocThis(ColumnsPanel);
Panel* super = (Panel*) this;
FunctionBar* fuBar = FunctionBar_new(ColumnsFunctions, NULL, NULL);
Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
- this->settings = settings;
+ this->ss = ss;
+ this->changed = changed;
this->moving = false;
Panel_setHeader(super, "Active Columns");
- Hashtable* dynamicColumns = settings->dynamicColumns;
- const ProcessField* fields = settings->fields;
- for (; *fields; fields++)
- ColumnsPanel_add(super, *fields, dynamicColumns);
+ ColumnsPanel_fill(this, ss, columns);
return this;
}
@@ -159,14 +152,14 @@ ColumnsPanel* ColumnsPanel_new(Settings* settings) {
void ColumnsPanel_update(Panel* super) {
ColumnsPanel* this = (ColumnsPanel*) super;
int size = Panel_size(super);
- this->settings->changed = true;
- this->settings->fields = xRealloc(this->settings->fields, sizeof(ProcessField) * (size + 1));
- this->settings->flags = 0;
+ *(this->changed) = true;
+ this->ss->fields = xRealloc(this->ss->fields, sizeof(ProcessField) * (size + 1));
+ this->ss->flags = 0;
for (int i = 0; i < size; i++) {
int key = ((ListItem*) Panel_get(super, i))->key;
- this->settings->fields[i] = key;
+ this->ss->fields[i] = key;
if (key < LAST_PROCESSFIELD)
- this->settings->flags |= Process_fields[key].flags;
+ this->ss->flags |= Process_fields[key].flags;
}
- this->settings->fields[size] = 0;
+ this->ss->fields[size] = 0;
}
diff --git a/ColumnsPanel.h b/ColumnsPanel.h
index 8bc806e..723369f 100644
--- a/ColumnsPanel.h
+++ b/ColumnsPanel.h
@@ -3,26 +3,30 @@
/*
htop - ColumnsPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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"
#include "Settings.h"
typedef struct ColumnsPanel_ {
Panel super;
+ ScreenSettings* ss;
+ bool* changed;
- Settings* settings;
bool moving;
} ColumnsPanel;
extern const PanelClass ColumnsPanel_class;
-ColumnsPanel* ColumnsPanel_new(Settings* settings);
+ColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed);
+
+void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns);
void ColumnsPanel_update(Panel* super);
diff --git a/CommandLine.c b/CommandLine.c
index f0d2255..09e67b8 100644
--- a/CommandLine.c
+++ b/CommandLine.c
@@ -2,7 +2,7 @@
htop - CommandLine.c
(C) 2004-2011 Hisham H. Muhammad
(C) 2020-2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -11,6 +11,7 @@ in the source distribution for its full text.
#include "CommandLine.h"
#include <assert.h>
+#include <ctype.h>
#include <getopt.h>
#include <locale.h>
#include <stdbool.h>
@@ -24,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"
@@ -47,23 +50,25 @@ static void printVersionFlag(const char* name) {
static void printHelpFlag(const char* name) {
printf("%s " VERSION "\n"
COPYRIGHT "\n"
- "Released under the GNU GPLv2.\n\n"
+ "Released under the GNU GPLv2+.\n\n"
"-C --no-color Use a monochrome color scheme\n"
"-d --delay=DELAY Set the delay between updates, in tenths of seconds\n"
"-F --filter=FILTER Show only the commands matching the given filter\n"
"-h --help Print this help screen\n"
- "-H --highlight-changes[=DELAY] Highlight new and old processes\n"
- "-M --no-mouse Disable the mouse\n"
+ "-H --highlight-changes[=DELAY] Highlight new and old processes\n", name);
+#ifdef HAVE_GETMOUSE
+ printf("-M --no-mouse Disable the mouse\n");
+#endif
+ 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"
"-u --user[=USERNAME] Show only processes for a given user (or $USER)\n"
"-U --no-unicode Do not use unicode but plain ASCII\n"
- "-V --version Print version info\n", name);
+ "-V --version Print version info\n");
Platform_longOptionsUsage(name);
printf("\n"
- "Long options may be passed with a single dash.\n\n"
"Press F1 inside %s for online help.\n"
"See 'man %s' for more information.\n", name, name);
}
@@ -76,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;
@@ -85,16 +93,19 @@ typedef struct CommandLineSettings_ {
bool readonly;
} CommandLineSettings;
-static CommandLineSettings parseArguments(const char* program, int argc, char** argv) {
+static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettings* flags) {
- CommandLineSettings flags = {
+ *flags = (CommandLineSettings) {
.pidMatchList = NULL,
.commFilter = NULL,
.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,
@@ -107,6 +118,7 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
{"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'},
@@ -124,78 +136,96 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
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);
- exit(0);
+ return STATUS_OK_EXIT;
case 'V':
printVersionFlag(program);
- exit(0);
+ return STATUS_OK_EXIT;
case 's':
assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
if (String_eq(optarg, "help")) {
for (int j = 1; j < LAST_PROCESSFIELD; j++) {
const char* name = Process_fields[j].name;
const char* description = Process_fields[j].description;
- if (name) printf("%19s %s\n", name, description);
+ if (name)
+ printf("%19s %s\n", name, description);
}
- exit(0);
+ return STATUS_OK_EXIT;
}
- flags.sortKey = 0;
+ flags->sortKey = 0;
for (int j = 1; j < LAST_PROCESSFIELD; j++) {
if (Process_fields[j].name == NULL)
continue;
if (String_eq(optarg, Process_fields[j].name)) {
- flags.sortKey = j;
+ flags->sortKey = j;
break;
}
}
- if (flags.sortKey == 0) {
+ if (flags->sortKey == 0) {
fprintf(stderr, "Error: invalid column \"%s\".\n", optarg);
- exit(1);
+ return STATUS_ERROR_EXIT;
}
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 (sscanf(optarg, "%16d", &(flags->delay)) == 1) {
+ if (flags->delay < 1)
+ flags->delay = 1;
+ if (flags->delay > 100)
+ flags->delay = 100;
} else {
fprintf(stderr, "Error: invalid delay value \"%s\".\n", optarg);
- exit(1);
+ 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++];
}
if (!username) {
- flags.userId = geteuid();
- } else if (!Action_setUserOnly(username, &(flags.userId))) {
- fprintf(stderr, "Error: invalid user \"%s\".\n", username);
- exit(1);
+ flags->userId = geteuid();
+ } else if (!Action_setUserOnly(username, &(flags->userId))) {
+ for (const char* itr = username; *itr; ++itr)
+ if (!isdigit((unsigned char)*itr)) {
+ fprintf(stderr, "Error: invalid user \"%s\".\n", username);
+ return STATUS_ERROR_EXIT;
+ }
+ flags->userId = atol(username);
}
break;
}
case 'C':
- flags.useColors = false;
+ flags->useColors = false;
break;
case 'M':
#ifdef HAVE_GETMOUSE
- flags.enableMouse = false;
+ flags->enableMouse = false;
#endif
break;
case 'U':
- flags.allowUnicode = false;
+ flags->allowUnicode = false;
break;
case 't':
- flags.treeView = true;
+ flags->treeView = true;
break;
case 'p': {
assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
@@ -203,78 +233,88 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
char* saveptr;
const char* pid = strtok_r(argCopy, ",", &saveptr);
- if (!flags.pidMatchList) {
- flags.pidMatchList = Hashtable_new(8, false);
+ if (!flags->pidMatchList) {
+ flags->pidMatchList = Hashtable_new(8, false);
}
- while(pid) {
- unsigned int num_pid = atoi(pid);
- // deepcode ignore CastIntegerToAddress: we just want a non-NUll pointer here
- Hashtable_put(flags.pidMatchList, num_pid, (void *) 1);
- pid = strtok_r(NULL, ",", &saveptr);
+ 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);
+ 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);
- exit(1);
- }
+ 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;
+ flags->highlightChanges = true;
break;
}
case 128:
- flags.readonly = true;
+ flags->readonly = true;
break;
- default:
- if (Platform_getLongOption(opt, argc, argv) == false)
- exit(1);
- break;
+ default: {
+ CommandLineStatus status;
+ if ((status = Platform_getLongOption(opt, argc, argv)) != STATUS_OK)
+ return status;
+ break;
+ }
}
}
- return flags;
+
+ 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;
@@ -283,28 +323,29 @@ int CommandLine_run(const char* name, int argc, char** argv) {
else
setlocale(LC_CTYPE, "");
- CommandLineSettings flags = parseArguments(name, argc, argv);
+ CommandLineStatus status = STATUS_OK;
+ CommandLineSettings flags = { 0 };
+
+ if ((status = parseArguments(argc, argv, &flags)) != STATUS_OK)
+ return status != STATUS_OK_EXIT ? 1 : 0;
if (flags.readonly)
Settings_enableReadonly();
- Platform_init();
-
- Process_setupColumnWidths();
+ if (!Platform_init())
+ return 1;
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)
@@ -316,7 +357,7 @@ int CommandLine_run(const char* name, int argc, char** argv) {
settings->enableMouse = false;
#endif
if (flags.treeView)
- settings->treeView = true;
+ settings->ss->treeView = true;
if (flags.highlightChanges)
settings->highlightChanges = true;
if (flags.highlightDelaySecs != -1)
@@ -325,48 +366,45 @@ int CommandLine_run(const char* name, int argc, char** argv) {
// -t -s <key> means "tree sorted by key"
// -s <key> means "list sorted by key" (previous existing behavior)
if (!flags.treeView) {
- settings->treeView = false;
+ settings->ss->treeView = false;
}
- Settings_setSortKey(settings, flags.sortKey);
+ ScreenSettings_setSortKey(settings->ss, flags.sortKey);
}
- CRT_init(settings, flags.allowUnicode);
+ 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_updateTreeFunctions(panel, settings->treeView);
+ 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);
-
- if (settings->allBranchesCollapsed)
- ProcessList_collapseAllBranches(pl);
+ Machine_scan(host);
+ Machine_scanTables(host);
+ CommandLine_delay(host, 75);
+ Machine_scan(host);
+ Machine_scanTables(host);
- ScreenManager_run(scr, NULL, NULL);
+ if (settings->ss->allBranchesCollapsed)
+ Table_collapseAllBranches(&pt->super);
- attron(CRT_colors[RESET_COLOR]);
- mvhline(LINES - 1, 0, ' ', COLS);
- attroff(CRT_colors[RESET_COLOR]);
- refresh();
+ ScreenManager_run(scr, NULL, NULL, NULL);
Platform_done();
@@ -379,7 +417,7 @@ int CommandLine_run(const char* name, int argc, char** argv) {
}
Header_delete(header);
- ProcessList_delete(pl);
+ Machine_delete(host);
ScreenManager_delete(scr);
MetersPanel_cleanup();
@@ -395,6 +433,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 b1f157e..1839500 100644
--- a/CommandLine.h
+++ b/CommandLine.h
@@ -4,11 +4,18 @@
htop - CommandLine.h
(C) 2004-2011 Hisham H. Muhammad
(C) 2020-2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
+typedef enum {
+ STATUS_OK,
+ STATUS_ERROR_EXIT,
+ 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 6a87d13..465e4c2 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 e56982b..2eef5e5 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 1077c08..d0ad2cc 100644
--- a/Compat.c
+++ b/Compat.c
@@ -1,7 +1,7 @@
/*
htop - Compat.c
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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,
@@ -44,7 +51,7 @@ int Compat_faccessat(int dirfd,
// Fallback to stat(2)/lstat(2) depending on flags
struct stat statinfo;
- if(flags) {
+ if (flags) {
ret = lstat(pathname, &statinfo);
} else {
ret = stat(pathname, &statinfo);
@@ -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 94c2ee2..2bc12dc 100644
--- a/Compat.h
+++ b/Compat.h
@@ -3,14 +3,13 @@
/*
htop - Compat.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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,31 @@ 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 will guarantee static_assert is a keyword or a macro */
+/* FIXME: replace 202300L with proper value once C23 is published */
+#if (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) < 202300L
+# 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 d01f464..0bdb30a 100644
--- a/DateMeter.c
+++ b/DateMeter.c
@@ -1,7 +1,7 @@
/*
htop - DateMeter.c
(C) 2004-2020 Hisham H. Muhammad, Michael Schönitzer
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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/DateMeter.h b/DateMeter.h
index a6ce47a..ecbfa99 100644
--- a/DateMeter.h
+++ b/DateMeter.h
@@ -3,7 +3,7 @@
/*
htop - DateMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/DateTimeMeter.c b/DateTimeMeter.c
index a042a61..dcd85ea 100644
--- a/DateTimeMeter.c
+++ b/DateTimeMeter.c
@@ -1,7 +1,7 @@
/*
htop - DateTimeMeter.c
(C) 2004-2020 Hisham H. Muhammad, Michael Schönitzer
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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/DateTimeMeter.h b/DateTimeMeter.h
index 04cc327..5196dd3 100644
--- a/DateTimeMeter.h
+++ b/DateTimeMeter.h
@@ -3,7 +3,7 @@
/*
htop - DateTimeMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/DiskIOMeter.c b/DiskIOMeter.c
index 67122a1..8d658de 100644
--- a/DiskIOMeter.c
+++ b/DiskIOMeter.c
@@ -1,21 +1,23 @@
/*
htop - DiskIOMeter.c
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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 "Object.h"
#include "Platform.h"
-#include "ProcessList.h"
#include "RichString.h"
+#include "Row.h"
#include "XUtils.h"
@@ -25,91 +27,121 @@ static const int DiskIOMeter_attributes[] = {
METER_VALUE_IOWRITE,
};
-static bool hasData = false;
-static uint32_t cached_read_diff;
-static uint32_t cached_write_diff;
+static MeterRateStatus status = RATESTATUS_INIT;
+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 */
+ /* update only every 500ms to have a sane span for rate calculation */
if (passedTimeInMs > 500) {
+ hasNewData = Platform_getDiskIO(&data);
+ if (!hasNewData) {
+ status = RATESTATUS_NODATA;
+ } else if (cached_last_update == 0) {
+ status = RATESTATUS_INIT;
+ } else if (passedTimeInMs > 30000) {
+ status = RATESTATUS_STALE;
+ } else {
+ status = RATESTATUS_DATA;
+ }
+
+ cached_last_update = host->realtimeMs;
+ }
+
+ if (hasNewData) {
static uint64_t cached_read_total;
static uint64_t cached_write_total;
static uint64_t cached_msTimeSpend_total;
- uint64_t diff;
-
- cached_last_update = pl->realtimeMs;
- DiskIOData data;
-
- hasData = Platform_getDiskIO(&data);
- if (!hasData) {
- this->values[0] = 0;
- xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
- return;
+ 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;
+ }
}
- 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;
- }
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_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;
- 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);
+ 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;
+ }
+ if (status == RATESTATUS_STALE) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale");
+ return;
+ }
+
+ 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) {
- if (!hasData) {
- RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
- return;
+ switch (status) {
+ case RATESTATUS_NODATA:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
+ return;
+ case RATESTATUS_INIT:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
+ return;
+ case RATESTATUS_STALE:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
+ return;
+ case RATESTATUS_DATA:
+ break;
}
char buffer[16];
- 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 68ea638..5ac9c48 100644
--- a/DiskIOMeter.h
+++ b/DiskIOMeter.h
@@ -3,10 +3,12 @@
/*
htop - DiskIOMeter.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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 7e05ae0..66793e1 100644
--- a/DisplayOptionsPanel.c
+++ b/DisplayOptionsPanel.c
@@ -1,7 +1,7 @@
/*
htop - DisplayOptionsPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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"
@@ -18,6 +19,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "OptionItem.h"
#include "ProvideCurses.h"
+#include "ScreensPanel.h"
static const char* const DisplayOptionsFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL};
@@ -36,39 +38,42 @@ 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_CHECK:
- CheckItem_toggle((CheckItem*)selected);
- result = HANDLED;
+ 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_NUMBER:
- NumberItem_toggle((NumberItem*)selected);
- result = HANDLED;
+ 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;
- }
- 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) {
this->settings->changed = true;
+ this->settings->lastUpdate++;
Header* header = this->scr->header;
Header_calculateHeight(header);
Header_reinit(header);
@@ -76,6 +81,7 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {
Header_draw(header);
ScreenManager_resize(this->scr);
}
+
return result;
}
@@ -97,17 +103,28 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
this->scr = scr;
Panel_setHeader(super, "Display options");
- Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->treeView)));
- Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->treeViewAlwaysByPID)));
- Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->allBranchesCollapsed)));
+
+ #define TABMSG "For current screen tab: \0"
+ char tabheader[sizeof(TABMSG) + SCREEN_NAME_LEN + 1] = TABMSG;
+ strncat(tabheader, settings->ss->heading, SCREEN_NAME_LEN);
+ Panel_add(super, (Object*) TextItem_new(tabheader));
+ #undef TABMSG
+
+ Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->ss->treeView)));
+ Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->ss->treeViewAlwaysByPID)));
+ Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->ss->allBranchesCollapsed)));
+ Panel_add(super, (Object*) TextItem_new("Global options:"));
+ Panel_add(super, (Object*) CheckItem_newByRef("Show tabs for screens", &(settings->screenTabs)));
Panel_add(super, (Object*) CheckItem_newByRef("Shadow other users' processes", &(settings->shadowOtherUsers)));
Panel_add(super, (Object*) CheckItem_newByRef("Hide kernel threads", &(settings->hideKernelThreads)));
Panel_add(super, (Object*) CheckItem_newByRef("Hide userland process threads", &(settings->hideUserlandThreads)));
+ 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", &(settings->highlightDeletedExe)));
+ 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/DisplayOptionsPanel.h b/DisplayOptionsPanel.h
index 745f125..5e87a63 100644
--- a/DisplayOptionsPanel.h
+++ b/DisplayOptionsPanel.h
@@ -3,7 +3,7 @@
/*
htop - DisplayOptionsPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/DynamicColumn.c b/DynamicColumn.c
index c6a0cc0..5f02174 100644
--- a/DynamicColumn.c
+++ b/DynamicColumn.c
@@ -3,7 +3,7 @@ htop - DynamicColumn.c
(C) 2021 Sohaib Mohammed
(C) 2021 htop dev team
(C) 2021 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 4760e6e..bdce82d 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 16c27f8..15d853b 100644
--- a/DynamicMeter.c
+++ b/DynamicMeter.c
@@ -2,7 +2,7 @@
htop - DynamicMeter.c
(C) 2021 htop dev team
(C) 2021 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 3ef0176..2bc3cba 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 0000000..9e3d5e4
--- /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 0000000..fb08ebc
--- /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* dynamics);
+
+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 0fcee83..4a36b31 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 4d44c81..118c271 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 0000000..cf1ec93
--- /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 0000000..e1b4f5f
--- /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 1c260ab..0850037 100644
--- a/FunctionBar.c
+++ b/FunctionBar.c
@@ -1,7 +1,7 @@
/*
htop - FunctionBar.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -88,11 +88,12 @@ void FunctionBar_setLabel(FunctionBar* this, int event, const char* text) {
}
}
-void FunctionBar_draw(const FunctionBar* this) {
- FunctionBar_drawExtra(this, NULL, -1, false);
+int FunctionBar_draw(const FunctionBar* this) {
+ return FunctionBar_drawExtra(this, NULL, -1, false);
}
-void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor) {
+int FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor) {
+ int cursorX = 0;
attrset(CRT_colors[FUNCTION_BAR]);
mvhline(LINES - 1, 0, ' ', COLS);
int x = 0;
@@ -113,18 +114,20 @@ void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr
}
mvaddstr(LINES - 1, x, buffer);
x += strlen(buffer);
+ cursorX = x;
}
attrset(CRT_colors[RESET_COLOR]);
if (setCursor) {
- CRT_cursorX = x;
curs_set(1);
} else {
curs_set(0);
}
currentLen = x;
+
+ return cursorX;
}
void FunctionBar_append(const char* buffer, int attr) {
diff --git a/FunctionBar.h b/FunctionBar.h
index 1f52658..f01a5ef 100644
--- a/FunctionBar.h
+++ b/FunctionBar.h
@@ -3,7 +3,7 @@
/*
htop - FunctionBar.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -29,9 +29,9 @@ void FunctionBar_delete(FunctionBar* this);
void FunctionBar_setLabel(FunctionBar* this, int event, const char* text);
-void FunctionBar_draw(const FunctionBar* this);
+int FunctionBar_draw(const FunctionBar* this);
-void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor);
+int FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor);
void FunctionBar_append(const char* buffer, int attr);
diff --git a/Hashtable.c b/Hashtable.c
index d040afa..2756b23 100644
--- a/Hashtable.c
+++ b/Hashtable.c
@@ -1,7 +1,7 @@
/*
htop - Hashtable.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -52,7 +52,7 @@ static void Hashtable_dump(const Hashtable* this) {
i,
this->buckets[i].key,
this->buckets[i].probe,
- this->buckets[i].value ? (const void*)this->buckets[i].value : "(nil)");
+ this->buckets[i].value);
if (this->buckets[i].value)
items++;
@@ -90,7 +90,7 @@ size_t Hashtable_count(const Hashtable* this) {
/* https://oeis.org/A014234 */
static const uint64_t OEISprimes[] = {
- 2, 3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191,
+ 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191,
16381, 32749, 65521, 131071, 262139, 524287, 1048573,
2097143, 4194301, 8388593, 16777213, 33554393,
67108859, 134217689, 268435399, 536870909, 1073741789,
@@ -191,10 +191,14 @@ void Hashtable_setSize(Hashtable* this, size_t size) {
if (size <= this->items)
return;
+ size_t newSize = nextPrime(size);
+ if (newSize == this->size)
+ return;
+
HashtableItem* oldBuckets = this->buckets;
size_t oldSize = this->size;
- this->size = nextPrime(size);
+ this->size = newSize;
this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem));
this->items = 0;
@@ -282,7 +286,7 @@ void* Hashtable_remove(Hashtable* this, ht_key_t key) {
/* shrink on load-factor < 0.125 */
if (8 * this->items < this->size)
- Hashtable_setSize(this, this->size / 2);
+ Hashtable_setSize(this, this->size / 3); /* account for nextPrime rounding up */
return res;
}
diff --git a/Hashtable.h b/Hashtable.h
index d6b7be2..f51a294 100644
--- a/Hashtable.h
+++ b/Hashtable.h
@@ -3,7 +3,7 @@
/*
htop - Hashtable.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/Header.c b/Header.c
index 26cd923..4fee26b 100644
--- a/Header.c
+++ b/Header.c
@@ -1,10 +1,12 @@
/*
htop - Header.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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);
@@ -92,7 +94,8 @@ static void Header_addMeterByName(Header* this, const char* name, MeterModeId mo
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 +108,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 +119,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 +134,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,7 +150,7 @@ void Header_writeBackToSettings(const Header* this) {
const Vector* vec = this->columns[col];
int len = Vector_size(vec);
- colSettings->names = len ? xCalloc(len, sizeof(char*)) : NULL;
+ colSettings->names = len ? xCalloc(len + 1, sizeof(char*)) : NULL;
colSettings->modes = len ? xCalloc(len, sizeof(int)) : NULL;
colSettings->len = len;
@@ -153,7 +158,7 @@ void Header_writeBackToSettings(const Header* this) {
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 +176,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;
}
@@ -194,7 +199,8 @@ void Header_draw(const Header* this) {
for (int y = 0; y < height; y++) {
mvhline(y, 0, ' ', COLS);
}
- const int width = COLS - pad;
+ const int numCols = HeaderLayout_getColumns(this->headerLayout);
+ const int width = COLS - 2 * pad - (numCols - 1);
int x = pad;
float roundingLoss = 0.0F;
@@ -217,6 +223,7 @@ void Header_draw(const Header* this) {
except for multi column meters. */
if (meter->mode == TEXT_METERMODE && !Meter_isMultiColumn(meter)) {
for (int j = 1; j < meter->columnWidthCount; j++) {
+ actualWidth++; /* separator column */
actualWidth += (float)width * HeaderLayout_layouts[this->headerLayout].widths[col + j] / 100.0F;
}
}
@@ -227,6 +234,7 @@ void Header_draw(const Header* this) {
}
x += floorf(colWidth);
+ x++; /* separator column */
}
}
@@ -270,7 +278,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) {
@@ -283,7 +292,19 @@ int Header_calculateHeight(Header* this) {
}
maxHeight = MAXIMUM(maxHeight, height);
}
+
+ 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 060ad02..2cc78ab 100644
--- a/Header.h
+++ b/Header.h
@@ -3,21 +3,21 @@
/*
htop - Header.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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 7be236e..473936e 100644
--- a/HeaderLayout.h
+++ b/HeaderLayout.h
@@ -3,12 +3,10 @@
/*
htop - HeaderLayout.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -18,6 +16,7 @@ in the source distribution for its full text.
typedef enum HeaderLayout_ {
+ HF_INVALID = -1,
HF_TWO_50_50,
HF_TWO_33_67,
HF_TWO_67_33,
@@ -25,6 +24,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
@@ -43,6 +45,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 701f6d6..7b5c81b 100644
--- a/HeaderOptionsPanel.c
+++ b/HeaderOptionsPanel.c
@@ -1,10 +1,12 @@
/*
htop - HeaderOptionsPanel.c
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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,29 +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;
-
- ScreenManager_resize(this->scr);
- result = HANDLED;
+ switch (ch) {
+ 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/HeaderOptionsPanel.h b/HeaderOptionsPanel.h
index 0d15ace..aee9328 100644
--- a/HeaderOptionsPanel.h
+++ b/HeaderOptionsPanel.h
@@ -3,7 +3,7 @@
/*
htop - ColorsPanel.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/HostnameMeter.c b/HostnameMeter.c
index b1570c8..78444ea 100644
--- a/HostnameMeter.c
+++ b/HostnameMeter.c
@@ -1,7 +1,7 @@
/*
htop - HostnameMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/HostnameMeter.h b/HostnameMeter.h
index 85d94d8..ec1b63a 100644
--- a/HostnameMeter.h
+++ b/HostnameMeter.h
@@ -3,7 +3,7 @@
/*
htop - HostnameMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/IncSet.c b/IncSet.c
index 9097413..71edf1c 100644
--- a/IncSet.c
+++ b/IncSet.c
@@ -1,7 +1,7 @@
/*
htop - IncSet.c
(C) 2005-2012 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -85,7 +85,7 @@ static void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) {
const char* incFilter = this->modes[INC_FILTER].buffer;
for (int i = 0; i < Vector_size(lines); i++) {
ListItem* line = (ListItem*)Vector_get(lines, i);
- if (String_contains_i(line->value, incFilter)) {
+ if (String_contains_i(line->value, incFilter, true)) {
Panel_add(panel, (Object*)line);
if (selected == (Object*)line) {
Panel_setSelected(panel, n);
@@ -105,10 +105,10 @@ static void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) {
}
}
-static bool search(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue) {
+static bool search(const IncSet* this, Panel* panel, IncMode_GetPanelValue getPanelValue) {
int size = Panel_size(panel);
for (int i = 0; i < size; i++) {
- if (String_contains_i(getPanelValue(panel, i), mode->buffer)) {
+ if (String_contains_i(getPanelValue(panel, i), this->active->buffer, true)) {
Panel_setSelected(panel, i);
return true;
}
@@ -117,6 +117,21 @@ static bool search(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getP
return false;
}
+void IncSet_activate(IncSet* this, IncType type, Panel* panel) {
+ this->active = &(this->modes[type]);
+ panel->currentBar = this->active->bar;
+ panel->cursorOn = true;
+ this->panel = panel;
+ IncSet_drawBar(this, CRT_colors[FUNCTION_BAR]);
+}
+
+static void IncSet_deactivate(IncSet* this, Panel* panel) {
+ this->active = NULL;
+ Panel_setDefaultBar(panel);
+ panel->cursorOn = false;
+ FunctionBar_draw(this->defaultBar);
+}
+
static bool IncMode_find(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue, int step) {
int size = Panel_size(panel);
int here = Panel_getSelectedIndex(panel);
@@ -133,7 +148,7 @@ static bool IncMode_find(const IncMode* mode, Panel* panel, IncMode_GetPanelValu
return false;
}
- if (String_contains_i(getPanelValue(panel, i), mode->buffer)) {
+ if (String_contains_i(getPanelValue(panel, i), mode->buffer, true)) {
Panel_setSelected(panel, i);
return true;
}
@@ -156,7 +171,7 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
doSearch = false;
} else if (0 < ch && ch < 255 && isprint((unsigned char)ch)) {
if (mode->index < INCMODE_MAX) {
- mode->buffer[mode->index] = ch;
+ mode->buffer[mode->index] = (char) ch;
mode->index++;
mode->buffer[mode->index] = 0;
if (mode->isFilter) {
@@ -194,12 +209,11 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
IncMode_reset(mode);
}
}
- this->active = NULL;
- Panel_setDefaultBar(panel);
+ IncSet_deactivate(this, panel);
doSearch = false;
}
if (doSearch) {
- this->found = search(mode, panel, getPanelValue);
+ this->found = search(this, panel, getPanelValue);
}
if (filterChanged && lines) {
updateWeakPanel(this, panel, lines);
@@ -212,14 +226,13 @@ const char* IncSet_getListItemValue(Panel* panel, int i) {
return l ? l->value : "";
}
-void IncSet_activate(IncSet* this, IncType type, Panel* panel) {
- this->active = &(this->modes[type]);
- panel->currentBar = this->active->bar;
-}
-
-void IncSet_drawBar(const IncSet* this) {
+void IncSet_drawBar(const IncSet* this, int attr) {
if (this->active) {
- FunctionBar_drawExtra(this->active->bar, this->active->buffer, (this->active->isFilter || this->found) ? -1 : CRT_colors[FAILED_SEARCH], true);
+ if (!this->active->isFilter && !this->found)
+ attr = CRT_colors[FAILED_SEARCH];
+ int cursorX = FunctionBar_drawExtra(this->active->bar, this->active->buffer, attr, true);
+ this->panel->cursorY = LINES - 1;
+ this->panel->cursorX = cursorX;
} else {
FunctionBar_draw(this->defaultBar);
}
diff --git a/IncSet.h b/IncSet.h
index e2062c6..a84407e 100644
--- a/IncSet.h
+++ b/IncSet.h
@@ -3,7 +3,7 @@
/*
htop - IncSet.h
(C) 2005-2012 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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,
@@ -32,6 +32,7 @@ typedef struct IncMode_ {
typedef struct IncSet_ {
IncMode modes[2];
IncMode* active;
+ Panel* panel;
FunctionBar* defaultBar;
bool filtering;
bool found;
@@ -57,7 +58,7 @@ const char* IncSet_getListItemValue(Panel* panel, int i);
void IncSet_activate(IncSet* this, IncType type, Panel* panel);
-void IncSet_drawBar(const IncSet* this);
+void IncSet_drawBar(const IncSet* this, int attr);
int IncSet_synthesizeEvent(IncSet* this, int x);
diff --git a/InfoScreen.c b/InfoScreen.c
index a62b7c0..c602cd4 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"
@@ -57,13 +65,13 @@ void InfoScreen_drawTitled(InfoScreen* this, const char* fmt, ...) {
attrset(CRT_colors[DEFAULT_COLOR]);
Panel_draw(this->display, true, true, true, false);
- IncSet_drawBar(this->inc);
+ IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
}
void InfoScreen_addLine(InfoScreen* this, const char* line) {
Vector_add(this->lines, (Object*) ListItem_new(line, 0));
const char* incFilter = IncSet_filter(this->inc);
- if (!incFilter || String_contains_i(line, incFilter)) {
+ if (!incFilter || String_contains_i(line, incFilter, true)) {
Panel_add(this->display, Vector_get(this->lines, Vector_size(this->lines) - 1));
}
}
@@ -72,7 +80,7 @@ void InfoScreen_appendLine(InfoScreen* this, const char* line) {
ListItem* last = (ListItem*)Vector_get(this->lines, Vector_size(this->lines) - 1);
ListItem_append(last, line);
const char* incFilter = IncSet_filter(this->inc);
- if (incFilter && Panel_get(this->display, Panel_size(this->display) - 1) != (Object*)last && String_contains_i(line, incFilter)) {
+ if (incFilter && Panel_get(this->display, Panel_size(this->display) - 1) != (Object*)last && String_contains_i(line, incFilter, true)) {
Panel_add(this->display, (Object*)last);
}
}
@@ -89,15 +97,9 @@ void InfoScreen_run(InfoScreen* this) {
while (looping) {
Panel_draw(panel, false, true, true, false);
- IncSet_drawBar(this->inc);
+ IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
- if (this->inc->active) {
- (void) move(LINES - 1, CRT_cursorX);
- }
-#ifdef HAVE_SET_ESCDELAY
- set_escdelay(25);
-#endif
- int ch = getch();
+ int ch = Panel_getCh(panel);
if (ch == ERR) {
if (As_InfoScreen(this)->onErr) {
@@ -135,49 +137,49 @@ void InfoScreen_run(InfoScreen* this) {
continue;
}
- 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);
- }
+ 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);
+ }
- 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 d7497be..469c270 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.c b/ListItem.c
index e5464d2..246bc5d 100644
--- a/ListItem.c
+++ b/ListItem.c
@@ -1,7 +1,7 @@
/*
htop - ListItem.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -18,13 +18,13 @@ in the source distribution for its full text.
#include "XUtils.h"
-static void ListItem_delete(Object* cast) {
+void ListItem_delete(Object* cast) {
ListItem* this = (ListItem*)cast;
free(this->value);
free(this);
}
-static void ListItem_display(const Object* cast, RichString* out) {
+void ListItem_display(const Object* cast, RichString* out) {
const ListItem* const this = (const ListItem*)cast;
assert (this != NULL);
@@ -38,11 +38,15 @@ static void ListItem_display(const Object* cast, RichString* out) {
RichString_appendWide(out, CRT_colors[DEFAULT_COLOR], this->value);
}
-ListItem* ListItem_new(const char* value, int key) {
- ListItem* this = AllocThis(ListItem);
+void ListItem_init(ListItem* this, const char* value, int key) {
this->value = xStrdup(value);
this->key = key;
this->moving = false;
+}
+
+ListItem* ListItem_new(const char* value, int key) {
+ ListItem* this = AllocThis(ListItem);
+ ListItem_init(this, value, key);
return this;
}
@@ -55,7 +59,7 @@ void ListItem_append(ListItem* this, const char* text) {
this->value[newLen] = '\0';
}
-static int ListItem_compare(const void* cast1, const void* cast2) {
+int ListItem_compare(const void* cast1, const void* cast2) {
const ListItem* obj1 = (const ListItem*) cast1;
const ListItem* obj2 = (const ListItem*) cast2;
return strcmp(obj1->value, obj2->value);
diff --git a/ListItem.h b/ListItem.h
index 0bbca2f..5efe874 100644
--- a/ListItem.h
+++ b/ListItem.h
@@ -3,13 +3,14 @@
/*
htop - ListItem.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#include <stdbool.h>
#include "Object.h"
+#include "RichString.h"
typedef struct ListItem_ {
@@ -21,10 +22,18 @@ typedef struct ListItem_ {
extern const ObjectClass ListItem_class;
+void ListItem_delete(Object* cast);
+
+void ListItem_display(const Object* cast, RichString* out);
+
+void ListItem_init(ListItem* this, const char* value, int key);
+
ListItem* ListItem_new(const char* value, int key);
void ListItem_append(ListItem* this, const char* text);
+int ListItem_compare(const void* cast1, const void* cast2);
+
static inline const char* ListItem_getRef(const ListItem* this) {
return this->value;
}
diff --git a/LoadAverageMeter.c b/LoadAverageMeter.c
index bb3b05e..30c58bb 100644
--- a/LoadAverageMeter.c
+++ b/LoadAverageMeter.c
@@ -1,16 +1,18 @@
/*
htop - LoadAverageMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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/LoadAverageMeter.h b/LoadAverageMeter.h
index d575ad5..a34a4a3 100644
--- a/LoadAverageMeter.h
+++ b/LoadAverageMeter.h
@@ -3,7 +3,7 @@
/*
htop - LoadAverageMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/Machine.c b/Machine.c
new file mode 100644
index 0000000..44aa27c
--- /dev/null
+++ b/Machine.c
@@ -0,0 +1,124 @@
+/*
+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)
+ Platform_gettime_monotonic(&this->monotonicMs);
+ else
+ firstScanDone = true;
+
+ 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 0000000..419911c
--- /dev/null
+++ b/Machine.h
@@ -0,0 +1,96 @@
+#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 */
+
+ 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* host, Panel* panel);
+
+void Machine_scan(Machine* this);
+
+void Machine_scanTables(Machine* this);
+
+#endif
diff --git a/Macros.h b/Macros.h
index 5e8891a..051bedb 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 e7033bf..8349023 100644
--- a/MainPanel.c
+++ b/MainPanel.c
@@ -2,90 +2,106 @@
htop - ColumnsPanel.c
(C) 2004-2015 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+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"
static const char* const MainFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL};
static const char* const MainFunctions_ro[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", " ", " ", " ", "Quit ", NULL};
-void MainPanel_updateTreeFunctions(MainPanel* this, bool mode) {
+void MainPanel_updateLabels(MainPanel* this, bool list, bool filter) {
FunctionBar* bar = MainPanel_getFunctionBar(this);
- FunctionBar_setLabel(bar, KEY_F(5), mode ? "List " : "Tree ");
+ FunctionBar_setLabel(bar, KEY_F(5), list ? "List " : "Tree ");
+ FunctionBar_setLabel(bar, KEY_F(4), filter ? "FILTER" : "Filter");
}
-static void MainPanel_pidSearch(MainPanel* this, int ch) {
+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)
return IGNORED;
- /* reset on every normal key, except mouse events while mouse support is disabled */
- if (ch != ERR && (ch != KEY_MOUSE || this->state->settings->enableMouse))
- this->state->hideProcessSelection = false;
+ /* reset on every normal key */
+ bool needReset = ch != ERR;
+ #ifdef HAVE_GETMOUSE
+ /* except mouse events while mouse support is disabled */
+ if (!(ch != KEY_MOUSE || host->settings->enableMouse))
+ needReset = false;
+ #endif
+ if (needReset)
+ this->state->hideSelection = false;
+
+ 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;
- Settings* settings = this->state->settings;
int hx = super->scrollH + x + 1;
- ProcessField field = ProcessList_keyAt(pl, hx);
- if (settings->treeView && settings->treeViewAlwaysByPID) {
- settings->treeView = false;
- settings->direction = 1;
+ RowField field = RowField_keyAt(settings, hx);
+ if (ss->treeView && ss->treeViewAlwaysByPID) {
+ ss->treeView = false;
+ ss->direction = 1;
reaction |= Action_setSortKey(settings, field);
- } else if (field == Settings_getActiveSortKey(settings)) {
- Settings_invertSortOrder(settings);
+ } else if (field == ScreenSettings_getActiveSortKey(ss)) {
+ ScreenSettings_invertSortOrder(ss);
} else {
reaction |= Action_setSortKey(settings, field);
}
reaction |= HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_SAVE_SETTINGS;
result = HANDLED;
+ } else if (EVENT_IS_SCREEN_TAB_CLICK(ch)) {
+ int x = EVENT_SCREEN_TAB_GET_X(ch);
+ reaction |= Action_setScreenTab(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) {
@@ -94,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_updateTreeFunctions(this, this->state->settings->treeView);
+ 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);
}
}
@@ -176,15 +189,16 @@ static void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) {
if (hideFunctionBar && !this->inc->active)
return;
- IncSet_drawBar(this->inc);
- if (this->state->pauseProcessUpdate) {
+ IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
+ 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 = {
@@ -197,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);
@@ -213,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 04f347d..105b46d 100644
--- a/MainPanel.h
+++ b/MainPanel.h
@@ -4,20 +4,18 @@
htop - ColumnsPanel.h
(C) 2004-2015 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+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,18 +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)
-void MainPanel_updateTreeFunctions(MainPanel* this, bool mode);
+// update the Label Keys in the MainPanel bar, list: list / tree mode, filter: filter (inc) active / inactive
+void MainPanel_updateLabels(MainPanel* this, bool list, bool filter);
-int MainPanel_selectedPid(MainPanel* this);
+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;
@@ -44,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 81b9c93..ed92afa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,3 +1,11 @@
+if !HTOP_PCP
+bin_PROGRAMS = htop
+myhtopplatprogram = htop.c
+else
+bin_PROGRAMS = pcp-htop
+myhtopplatprogram = pcp-htop.c
+endif
+
dist_man_MANS = htop.1
EXTRA_DIST = \
$(dist_man_MANS) \
@@ -16,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 = \
@@ -41,7 +49,9 @@ myhtopsources = \
DisplayOptionsPanel.c \
DynamicColumn.c \
DynamicMeter.c \
+ DynamicScreen.c \
EnvScreen.c \
+ FileDescriptorMeter.c \
FunctionBar.c \
Hashtable.c \
Header.c \
@@ -51,6 +61,7 @@ myhtopsources = \
InfoScreen.c \
ListItem.c \
LoadAverageMeter.c \
+ Machine.c \
MainPanel.c \
MemoryMeter.c \
MemorySwapMeter.c \
@@ -62,14 +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 \
@@ -99,7 +115,9 @@ myhtopheaders = \
DisplayOptionsPanel.h \
DynamicColumn.h \
DynamicMeter.h \
+ DynamicScreen.h \
EnvScreen.h \
+ FileDescriptorMeter.h \
FunctionBar.h \
Hashtable.h \
Header.h \
@@ -110,6 +128,7 @@ myhtopheaders = \
InfoScreen.h \
ListItem.h \
LoadAverageMeter.h \
+ Machine.h \
Macros.h \
MainPanel.h \
MemoryMeter.h \
@@ -122,15 +141,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 \
@@ -145,12 +171,14 @@ linux_platform_headers = \
generic/gettime.h \
generic/hostname.h \
generic/uname.h \
+ linux/CGroupUtils.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 \
@@ -158,6 +186,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
@@ -166,11 +195,13 @@ linux_platform_sources = \
generic/gettime.c \
generic/hostname.c \
generic/uname.c \
+ linux/CGroupUtils.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 \
@@ -181,7 +212,6 @@ linux_platform_sources = \
if HTOP_LINUX
AM_LDFLAGS += -rdynamic
-myhtopplatprogram = htop
myhtopplatheaders = $(linux_platform_headers)
myhtopplatsources = $(linux_platform_sources)
endif
@@ -190,10 +220,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 \
@@ -204,8 +236,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 \
@@ -214,7 +248,6 @@ freebsd_platform_sources = \
zfs/ZfsCompressedArcMeter.c
if HTOP_FREEBSD
-myhtopplatprogram = htop
myhtopplatheaders = $(freebsd_platform_headers)
myhtopplatsources = $(freebsd_platform_sources)
endif
@@ -223,24 +256,27 @@ 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
if HTOP_DRAGONFLYBSD
-myhtopplatprogram = htop
myhtopplatheaders = $(dragonflybsd_platform_headers)
myhtopplatsources = $(dragonflybsd_platform_sources)
endif
@@ -249,24 +285,27 @@ 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
-myhtopplatprogram = htop
myhtopplatheaders = $(netbsd_platform_headers)
myhtopplatsources = $(netbsd_platform_sources)
endif
@@ -278,7 +317,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
@@ -287,12 +327,12 @@ 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
if HTOP_OPENBSD
-myhtopplatprogram = htop
myhtopplatheaders = $(openbsd_platform_headers)
myhtopplatsources = $(openbsd_platform_sources)
endif
@@ -301,10 +341,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 \
@@ -315,8 +358,11 @@ 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 \
@@ -326,7 +372,6 @@ darwin_platform_sources = \
if HTOP_DARWIN
AM_LDFLAGS += -framework IOKit -framework CoreFoundation
-myhtopplatprogram = htop
myhtopplatheaders = $(darwin_platform_headers)
myhtopplatsources = $(darwin_platform_sources)
endif
@@ -340,8 +385,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
@@ -351,13 +397,13 @@ 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
if HTOP_SOLARIS
-myhtopplatprogram = htop
myhtopplatheaders = $(solaris_platform_headers)
myhtopplatsources = $(solaris_platform_sources)
endif
@@ -366,37 +412,46 @@ 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
if HTOP_PCP
-myhtopplatprogram = pcp-htop
myhtopplatheaders = $(pcp_platform_headers)
myhtopplatsources = $(pcp_platform_sources)
-pcp_htop_SOURCES = $(myhtopplatprogram).c $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
+pcp_htop_SOURCES = $(myhtopplatprogram) $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
endif
# Unsupported
@@ -406,25 +461,25 @@ 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
-myhtopplatprogram = htop
myhtopplatsources = $(unsupported_platform_sources)
myhtopplatheaders = $(unsupported_platform_headers)
endif
# ----
-bin_PROGRAMS = $(myhtopplatprogram)
-htop_SOURCES = $(myhtopplatprogram).c $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
+htop_SOURCES = $(myhtopplatprogram) $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
nodist_htop_SOURCES = config.h
target:
diff --git a/MemoryMeter.c b/MemoryMeter.c
index 75ad7e8..573b898 100644
--- a/MemoryMeter.c
+++ b/MemoryMeter.c
@@ -1,16 +1,20 @@
/*
htop - MemoryMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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, isnan(this->values[4]) ? this->values[0] : this->total - this->values[4], 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 6d7dd82..e39b3bd 100644
--- a/MemoryMeter.h
+++ b/MemoryMeter.h
@@ -3,12 +3,21 @@
/*
htop - MemoryMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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 612549d..82eddee 100644
--- a/MemorySwapMeter.c
+++ b/MemorySwapMeter.c
@@ -1,13 +1,16 @@
/*
htop - MemorySwapMeter.c
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -53,9 +56,9 @@ static void MemorySwapMeter_init(Meter* this) {
}
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))
Meter_init(data->memoryMeter);
diff --git a/MemorySwapMeter.h b/MemorySwapMeter.h
index b4180a1..a98d5e6 100644
--- a/MemorySwapMeter.h
+++ b/MemorySwapMeter.h
@@ -3,7 +3,7 @@
/*
htop - MemorySwapMeter.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/Meter.c b/Meter.c
index 73e99ce..86f8885 100644
--- a/Meter.c
+++ b/Meter.c
@@ -1,7 +1,7 @@
/*
htop - Meter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -13,13 +13,13 @@ 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"
@@ -32,12 +32,12 @@ const MeterClass Meter_class = {
}
};
-Meter* Meter_new(const struct ProcessList_* pl, unsigned int param, const MeterClass* type) {
+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->pl = pl;
+ this->host = host;
this->curItems = type->maxItems;
this->curAttributes = NULL;
this->values = type->maxItems ? xCalloc(type->maxItems, sizeof(double)) : NULL;
@@ -50,32 +50,40 @@ Meter* Meter_new(const struct ProcessList_* pl, unsigned int param, const MeterC
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;
+/* 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;
- for (;;) {
- if (value / 1024 < powi)
- break;
-
- if (prefix[1] == '\0')
+ assert(value >= 0.0);
+ while (value >= ONE_K) {
+ if (i >= ARRAYSIZE(unitPrefixes) - 1) {
+ if (value > 9999.0) {
+ return xSnprintf(buffer, size, "inf");
+ }
break;
+ }
- powi *= 1024;
- ++prefix;
+ value /= ONE_K;
+ ++i;
}
- if (*prefix == 'K')
- precision = 0;
+ int precision = 0;
- for (; precision > 0; precision--) {
- powj *= 10;
- if (value / powi < powj)
- break;
+ 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 snprintf(buffer, size, "%.*f%c", precision, (double) value / powi, *prefix);
+ return xSnprintf(buffer, size, "%.*f%c", precision, value, unitPrefixes[i]);
}
void Meter_delete(Object* cast) {
@@ -86,7 +94,7 @@ void Meter_delete(Object* cast) {
if (Meter_doneFn(this)) {
Meter_done(this);
}
- free(this->drawData);
+ free(this->drawData.values);
free(this->caption);
free(this->values);
free(this);
@@ -121,8 +129,9 @@ void Meter_setMode(Meter* this, int modeIndex) {
}
} else {
assert(modeIndex >= 1);
- free(this->drawData);
- this->drawData = NULL;
+ free(this->drawData.values);
+ this->drawData.values = NULL;
+ this->drawData.nValues = 0;
const MeterMode* mode = Meter_modes[modeIndex];
this->draw = mode->draw;
@@ -155,7 +164,7 @@ ListItem* Meter_toListItem(const Meter* this, bool moving) {
static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
- mvaddnstr(y, x, caption, w - 1);
+ mvaddnstr(y, x, caption, w);
attrset(CRT_colors[RESET_COLOR]);
int captionLen = strlen(caption);
@@ -166,7 +175,7 @@ static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
RichString_begin(out);
Meter_displayBuffer(this, &out);
- RichString_printoffnVal(out, y, x, 0, w - 1);
+ RichString_printoffnVal(out, y, x, 0, w);
RichString_delete(&out);
}
@@ -176,7 +185,6 @@ static const char BarMeterMode_characters[] = "|#*@$%&.";
static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
const char* caption = Meter_getCaption(this);
- w -= 2;
attrset(CRT_colors[METER_TEXT]);
int captionLen = 3;
mvaddnstr(y, x, caption, captionLen);
@@ -184,10 +192,11 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
w -= captionLen;
attrset(CRT_colors[BAR_BORDER]);
mvaddch(y, x, '[');
+ w--;
mvaddch(y, x + MAXIMUM(w, 0), ']');
+ w--;
attrset(CRT_colors[RESET_COLOR]);
- w--;
x++;
if (w < 1)
@@ -224,8 +233,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 +245,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 +297,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;
- if (!this->drawData) {
- this->drawData = xCalloc(1, sizeof(GraphData));
+ 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 ((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 +351,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) + 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 +364,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 +384,7 @@ static const char* const LEDMeterMode_digitsAscii[] = {
static const char* const LEDMeterMode_digitsUtf8[] = {
"┌──┐", " ┐ ", "╶──┐", "╶──┐", "╷ ╷", "┌──╴", "┌──╴", "╶──┐", "┌──┐", "┌──┐",
"│ │", " │ ", "┌──┘", " ──┤", "└──┤", "└──┐", "├──┐", " │", "├──┤", "└──┤",
- "└──┘", " ╵ ", "└──╴", "╶──┘", " ╵", "╶──┘", "└──┘", " ╵", "└──┘", " ──┘"
+ "└──┘", " ╵ ", "└──╴", "╶──┘", " ╵", "╶──┘", "└──┘", " ╵", "└──┘", "╶──┘"
};
#endif
diff --git a/Meter.h b/Meter.h
index 0e6b3f9..17d77f3 100644
--- a/Meter.h
+++ b/Meter.h
@@ -3,24 +3,22 @@
/*
htop - Meter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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 "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 { \
@@ -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;
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];
@@ -134,11 +133,20 @@ typedef enum {
LAST_METERMODE
} MeterModeId;
+typedef enum {
+ RATESTATUS_DATA,
+ RATESTATUS_INIT,
+ RATESTATUS_NODATA,
+ RATESTATUS_STALE
+} MeterRateStatus;
+
extern const MeterClass Meter_class;
-Meter* Meter_new(const ProcessList* pl, unsigned int param, const MeterClass* type);
+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);
diff --git a/MetersPanel.c b/MetersPanel.c
index 13c102d..2678fb2 100644
--- a/MetersPanel.c
+++ b/MetersPanel.c
@@ -1,10 +1,12 @@
/*
htop - MetersPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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;
+ 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,14 +164,16 @@ 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;
+ this->settings->lastUpdate++;
Header_calculateHeight(header);
ScreenManager_resize(this->scr);
}
+
return result;
}
diff --git a/MetersPanel.h b/MetersPanel.h
index cf4de60..679c12d 100644
--- a/MetersPanel.h
+++ b/MetersPanel.h
@@ -3,7 +3,7 @@
/*
htop - MetersPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/NetworkIOMeter.c b/NetworkIOMeter.c
index dcba78d..6bc2d08 100644
--- a/NetworkIOMeter.c
+++ b/NetworkIOMeter.c
@@ -1,15 +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"
@@ -18,70 +27,85 @@ static const int NetworkIOMeter_attributes[] = {
METER_VALUE_IOWRITE,
};
-static bool hasData = false;
-
-static uint32_t cached_rxb_diff;
+static MeterRateStatus status = RATESTATUS_INIT;
+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 */
+ /* update only every 500ms to have a sane span for rate calculation */
if (passedTimeInMs > 500) {
+ hasNewData = Platform_getNetworkIO(&data);
+ if (!hasNewData) {
+ status = RATESTATUS_NODATA;
+ } else if (cached_last_update == 0) {
+ status = RATESTATUS_INIT;
+ } else if (passedTimeInMs > 30000) {
+ status = RATESTATUS_STALE;
+ } else {
+ status = RATESTATUS_DATA;
+ }
+
+ cached_last_update = host->realtimeMs;
+ }
+
+ 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;
- uint64_t diff;
-
- cached_last_update = pl->realtimeMs;
- NetworkIOData data;
- hasData = Platform_getNetworkIO(&data);
- if (!hasData) {
- xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
- return;
+ 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;
+ }
}
- if (data.bytesReceived > cached_rxb_total) {
- diff = data.bytesReceived - cached_rxb_total;
- diff /= ONE_K; /* Meter_humanUnit() expects unit in kilo */
- diff = (1000 * diff) / passedTimeInMs; /* convert to per second */
- cached_rxb_diff = (uint32_t)diff;
- } else {
- cached_rxb_diff = 0;
- }
cached_rxb_total = 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_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;
}
@@ -91,32 +115,49 @@ static void NetworkIOMeter_updateValues(Meter* this) {
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);
+ 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;
+ }
+ if (status == RATESTATUS_STALE) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale");
+ return;
+ }
+
+ 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) {
- if (!hasData) {
- RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
- return;
+ switch (status) {
+ case RATESTATUS_NODATA:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
+ return;
+ case RATESTATUS_INIT:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
+ return;
+ case RATESTATUS_STALE:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
+ return;
+ case RATESTATUS_DATA:
+ break;
}
char buffer[64];
- 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 18c23ce..355b815 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 f361394..ae4d33f 100644
--- a/Object.c
+++ b/Object.c
@@ -2,10 +2,12 @@
htop - Object.c
(C) 2004-2012 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+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 e18fb97..4d7d653 100644
--- a/Object.h
+++ b/Object.h
@@ -4,12 +4,10 @@
htop - Object.h
(C) 2004-2012 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+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 75eb2d3..d6d663c 100644
--- a/OpenFilesScreen.c
+++ b/OpenFilesScreen.c
@@ -1,7 +1,7 @@
/*
htop - OpenFilesScreen.c
(C) 2005-2006 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -9,13 +9,16 @@ in the source distribution for its full text.
#include "OpenFilesScreen.h"
+#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>
+#include <sys/stat.h>
#include "Macros.h"
#include "Panel.h"
@@ -24,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[7];
+ 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;
@@ -41,20 +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 '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 */
@@ -67,14 +76,14 @@ 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 NODE NAME");
+ return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE OFFSET NODE NAME");
}
void OpenFilesScreen_delete(Object* this) {
@@ -87,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) {
@@ -115,13 +127,16 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
close(fdnull);
char buffer[32] = {0};
xSnprintf(buffer, sizeof(buffer), "%d", pid);
- execlp("lsof", "lsof", "-P", "-p", buffer, "-F", NULL);
+ // Use of NULL in variadic functions must have a pointer cast.
+ // The NULL constant is not required by standard to have a pointer type.
+ execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char*)NULL);
exit(127);
}
close(fdpair[1]);
OpenFiles_Data* item = &(pdata->data);
OpenFiles_FileData* fdata = NULL;
+ bool lsofIncludesFileSize = false;
FILE* fd = fdopen(fdpair[0], "r");
if (!fd) {
@@ -136,54 +151,75 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
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;
+ 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;
}
- 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(item->data[index]);
- item->data[index] = xStrdup(line + 1);
- 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 'o': /* file's offset */
- 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')
+ lsofIncludesFileSize = true;
+
free(line);
}
fclose(fd);
int wstatus;
- if (waitpid(child, &wstatus, 0) == -1) {
- pdata->error = 1;
- return pdata;
- }
+ while (waitpid(child, &wstatus, 0) == -1)
+ if (errno != EINTR) {
+ pdata->error = 1;
+ return pdata;
+ }
if (!WIFEXITED(wstatus)) {
pdata->error = 1;
@@ -191,6 +227,25 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
pdata->error = WEXITSTATUS(wstatus);
}
+ /* We got all information we need; no post-processing needed */
+ if (lsofIncludesFileSize)
+ return pdata;
+
+ /* On linux, `lsof -o -F` omits SIZE, so add it back. */
+ /* On macOS, `lsof -o -F` includes SIZE, so this block isn't needed. If no open files have a filesize, this will still run, unfortunately. */
+ size_t fileSizeIndex = getIndexForType('s');
+ for (fdata = pdata->files; fdata != NULL; fdata = fdata->next) {
+ item = &fdata->data;
+ const char* filename = getDataForType(item, 'n');
+
+ struct stat st;
+ if (stat(filename, &st) == 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 */
+ free_and_xStrdup(&item->data[fileSizeIndex], fileSizeBuf);
+ }
+ }
+
return pdata;
}
@@ -199,31 +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 + lenN + 7 /*spaces*/ + 1 /*null*/;
- char entry[sizeEntry];
- xSnprintf(entry, sizeof(entry), "%5.5s %-7.7s %-4.4s %-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;
@@ -232,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/OpenFilesScreen.h b/OpenFilesScreen.h
index f3c129c..43f95ea 100644
--- a/OpenFilesScreen.h
+++ b/OpenFilesScreen.h
@@ -3,7 +3,7 @@
/*
htop - OpenFilesScreen.h
(C) 2005-2006 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/OptionItem.c b/OptionItem.c
index 437f7ea..728113b 100644
--- a/OptionItem.c
+++ b/OptionItem.c
@@ -1,10 +1,12 @@
/*
htop - OptionItem.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -25,6 +27,13 @@ static void OptionItem_delete(Object* cast) {
free(this);
}
+static void TextItem_display(const Object* cast, RichString* out) {
+ const TextItem* this = (const TextItem*)cast;
+ assert (this != NULL);
+
+ RichString_appendWide(out, CRT_colors[HELP_BOLD], this->super.text);
+}
+
static void CheckItem_display(const Object* cast, RichString* out) {
const CheckItem* this = (const CheckItem*)cast;
assert (this != NULL);
@@ -68,6 +77,16 @@ const OptionItemClass OptionItem_class = {
}
};
+const OptionItemClass TextItem_class = {
+ .super = {
+ .extends = Class(OptionItem),
+ .delete = OptionItem_delete,
+ .display = TextItem_display
+ },
+ .kind = OPTION_ITEM_TEXT
+};
+
+
const OptionItemClass CheckItem_class = {
.super = {
.extends = Class(OptionItem),
@@ -77,6 +96,7 @@ const OptionItemClass CheckItem_class = {
.kind = OPTION_ITEM_CHECK
};
+
const OptionItemClass NumberItem_class = {
.super = {
.extends = Class(OptionItem),
@@ -86,6 +106,12 @@ const OptionItemClass NumberItem_class = {
.kind = OPTION_ITEM_NUMBER
};
+TextItem* TextItem_new(const char* text) {
+ TextItem* this = AllocThis(TextItem);
+ this->super.text = xStrdup(text);
+ return this;
+}
+
CheckItem* CheckItem_newByRef(const char* text, bool* ref) {
CheckItem* this = AllocThis(CheckItem);
this->super.text = xStrdup(text);
diff --git a/OptionItem.h b/OptionItem.h
index 8dd802d..ba28775 100644
--- a/OptionItem.h
+++ b/OptionItem.h
@@ -3,7 +3,7 @@
/*
htop - OptionItem.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -13,6 +13,7 @@ in the source distribution for its full text.
enum OptionItemType {
+ OPTION_ITEM_TEXT,
OPTION_ITEM_CHECK,
OPTION_ITEM_NUMBER,
};
@@ -32,6 +33,12 @@ typedef struct OptionItem_ {
char* text;
} OptionItem;
+typedef struct TextItem_ {
+ OptionItem super;
+
+ char* text;
+} TextItem;
+
typedef struct CheckItem_ {
OptionItem super;
@@ -51,9 +58,12 @@ typedef struct NumberItem_ {
} NumberItem;
extern const OptionItemClass OptionItem_class;
+extern const OptionItemClass TextItem_class;
extern const OptionItemClass CheckItem_class;
extern const OptionItemClass NumberItem_class;
+TextItem* TextItem_new(const char* text);
+
CheckItem* CheckItem_newByRef(const char* text, bool* ref);
CheckItem* CheckItem_newByVal(const char* text, bool value);
bool CheckItem_get(const CheckItem* this);
diff --git a/Panel.c b/Panel.c
index dea6401..4784a65 100644
--- a/Panel.c
+++ b/Panel.c
@@ -1,10 +1,12 @@
/*
htop - Panel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -49,6 +51,8 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type
this->y = y;
this->w = w;
this->h = h;
+ this->cursorX = 0;
+ this->cursorY = 0;
this->eventHandlerState = NULL;
this->items = Vector_new(type, owner, DEFAULT_SIZE);
this->scrollV = 0;
@@ -57,6 +61,7 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type
this->oldSelected = 0;
this->selectedLen = 0;
this->needsRedraw = true;
+ this->cursorOn = false;
this->wasFocus = false;
RichString_beginAllocated(this->header);
this->defaultBar = fuBar;
@@ -72,6 +77,11 @@ void Panel_done(Panel* this) {
RichString_delete(&this->header);
}
+void Panel_setCursorToSelection(Panel* this) {
+ this->cursorY = this->y + this->selected - this->scrollV + 1;
+ this->cursorX = this->x + this->selectedLen - this->scrollH;
+}
+
void Panel_setSelectionColor(Panel* this, ColorElements colorId) {
this->selectionColorId = colorId;
}
@@ -263,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;
@@ -330,7 +340,6 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
this->oldSelected = this->selected;
this->wasFocus = focus;
this->needsRedraw = false;
- move(0, 0);
}
static int Panel_headerHeight(const Panel* this) {
@@ -350,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
@@ -436,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;
@@ -453,7 +467,7 @@ HandlerResult Panel_selectByTyping(Panel* this, int ch) {
}
if (len < 99) {
- buffer[len] = ch;
+ buffer[len] = (char) ch;
buffer[len + 1] = '\0';
}
@@ -461,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;
@@ -470,7 +485,7 @@ HandlerResult Panel_selectByTyping(Panel* this, int ch) {
// if current word did not match,
// retry considering the character the start of a new word.
- buffer[0] = ch;
+ buffer[0] = (char) ch;
buffer[1] = '\0';
}
@@ -485,3 +500,16 @@ HandlerResult Panel_selectByTyping(Panel* this, int ch) {
return IGNORED;
}
+
+int Panel_getCh(Panel* this) {
+ if (this->cursorOn) {
+ move(this->cursorY, this->cursorX);
+ curs_set(1);
+ } else {
+ curs_set(0);
+ }
+#ifdef HAVE_SET_ESCDELAY
+ set_escdelay(25);
+#endif
+ return getch();
+}
diff --git a/Panel.h b/Panel.h
index 41dba21..286e446 100644
--- a/Panel.h
+++ b/Panel.h
@@ -3,12 +3,10 @@
/*
htop - Panel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -39,6 +37,10 @@ typedef enum HandlerResult_ {
#define EVENT_IS_HEADER_CLICK(ev_) ((ev_) >= -10000 && (ev_) <= -9000)
#define EVENT_HEADER_CLICK_GET_X(ev_) ((ev_) + 10000)
+#define EVENT_SCREEN_TAB_CLICK(x_) (-20000 + (x_))
+#define EVENT_IS_SCREEN_TAB_CLICK(ev_) ((ev_) >= -20000 && (ev_) < -10000)
+#define EVENT_SCREEN_TAB_GET_X(ev_) ((ev_) + 20000)
+
typedef HandlerResult (*Panel_EventHandler)(Panel*, int);
typedef void (*Panel_DrawFunctionBar)(Panel*, bool);
typedef void (*Panel_PrintHeader)(Panel*);
@@ -61,14 +63,16 @@ typedef struct PanelClass_ {
struct Panel_ {
Object super;
int x, y, w, h;
+ int cursorX, cursorY;
Vector* items;
int selected;
int oldSelected;
int selectedLen;
void* eventHandlerState;
int scrollV;
- short scrollH;
+ int scrollH;
bool needsRedraw;
+ bool cursorOn;
bool wasFocus;
FunctionBar* currentBar;
FunctionBar* defaultBar;
@@ -90,6 +94,8 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type
void Panel_done(Panel* this);
+void Panel_setCursorToSelection(Panel* this);
+
void Panel_setSelectionColor(Panel* this, ColorElements colorId);
void Panel_setHeader(Panel* this, const char* header);
@@ -130,4 +136,6 @@ bool Panel_onKey(Panel* this, int key);
HandlerResult Panel_selectByTyping(Panel* this, int ch);
+int Panel_getCh(Panel* this);
+
#endif
diff --git a/Process.c b/Process.c
index 556acf4..a36ab6c 100644
--- a/Process.c
+++ b/Process.c
@@ -2,7 +2,7 @@
htop - Process.c
(C) 2004-2015 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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,221 +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 = 7;
-
-void Process_setupColumnWidths() {
- int maxPid = Platform_getMaxPid();
- if (maxPid == -1)
- return;
-
- Process_pidDigits = ceil(log10(maxPid));
- assert(Process_pidDigits <= PROCESS_MAX_PID_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 (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);
}
/*
@@ -265,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
@@ -381,17 +176,19 @@ static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr
* This function makes the merged Command string. It also stores the offsets of the
* basename, comm w.r.t the merged Command string - these offsets will be used by
* Process_writeCommand() for coloring. The merged Command string is also
- * returned by Process_getCommandStr() for searching, sorting and filtering.
+ * returned by Process_getCommand() for searching, sorting and filtering.
*/
-void Process_makeCommandStr(Process* this) {
+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;
/* Nothing to do to (Re)Generate the Command string, if the process is:
* - a kernel thread, or
@@ -399,54 +196,29 @@ void Process_makeCommandStr(Process* this) {
* - a user thread and showThreadNames is not set */
if (Process_isKernelThread(this))
return;
- if (this->state == 'Z' && !this->mergedCommand.str)
- return;
- if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames))
+ if (this->state == ZOMBIE && !this->mergedCommand.str)
return;
/* this->mergedCommand.str needs updating only if its state or contents changed.
* Its content is based on the fields cmdline, comm, and exe. */
- if (
- mc->prevMergeSet == showMergedCommand &&
- mc->prevPathSet == showProgramPath &&
- mc->prevCommSet == searchCommInCmdline &&
- mc->prevCmdlineSet == stripExeFromCmdline &&
- mc->prevShowThreadNames == showThreadNames &&
- !mc->cmdlineChanged &&
- !mc->commChanged &&
- !mc->exeChanged
- ) {
+ if (mc->lastUpdate >= settingsStamp)
return;
- }
- /* The field separtor "│" has been chosen such that it will not match any
+ mc->lastUpdate = settingsStamp;
+
+ /* 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);
- /* Check for any changed fields since we last built this string */
- if (mc->cmdlineChanged || mc->commChanged || mc->exeChanged) {
- free(mc->str);
- /* Accommodate the column text, two field separators and terminating NUL */
- size_t maxLen = 2 * SEPARATOR_LEN + 1;
- maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)");
- maxLen += this->procComm ? strlen(this->procComm) : 0;
- maxLen += this->procExe ? strlen(this->procExe) : 0;
+ /* Accommodate the column text, two field separators and terminating NUL */
+ size_t maxLen = 2 * SEPARATOR_LEN + 1;
+ maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)");
+ maxLen += this->procComm ? strlen(this->procComm) : 0;
+ maxLen += this->procExe ? strlen(this->procExe) : 0;
- mc->str = xCalloc(1, maxLen);
- }
-
- /* Preserve the settings used in this run */
- mc->prevMergeSet = showMergedCommand;
- mc->prevPathSet = showProgramPath;
- mc->prevCommSet = searchCommInCmdline;
- mc->prevCmdlineSet = stripExeFromCmdline;
- mc->prevShowThreadNames = showThreadNames;
-
- /* Mark everything as unchanged */
- mc->cmdlineChanged = false;
- mc->commChanged = false;
- mc->exeChanged = false;
+ free(mc->str);
+ mc->str = xCalloc(1, maxLen);
/* Reset all locations that need extra handling when actually displaying */
mc->highlightCount = 0;
@@ -474,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];
@@ -500,17 +322,29 @@ void Process_makeCommandStr(Process* this) {
assert(cmdlineBasenameStart <= (int)strlen(cmdline));
if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */
- if (showMergedCommand && (!Process_isUserlandThread(this) || showThreadNames) && !procExe && procComm && strlen(procComm)) { /* Prefix column with comm */
+ if ((showMergedCommand || (Process_isUserlandThread(this) && showThreadNames)) && procComm && strlen(procComm)) { /* set column to or prefix it with comm */
if (strncmp(cmdline + cmdlineBasenameStart, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) {
WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
str = stpcpy(str, procComm);
+ if (!showMergedCommand)
+ return;
+
WRITE_SEPARATOR;
}
}
+ 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);
+
+ if (this->procExeDeleted)
+ WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ else if (this->usesDeletedLib)
+ WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+
(void)stpcpyWithNewlineConversion(str, cmdline + (showProgramPath ? 0 : cmdlineBasenameStart));
return;
@@ -530,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);
@@ -571,11 +407,15 @@ void Process_makeCommandStr(Process* this) {
}
if (matchLen) {
- /* strip the matched exe prefix */
- cmdline += matchLen;
+ if (stripExeFromCmdline) {
+ /* strip the matched exe prefix */
+ cmdline += matchLen;
- commStart -= matchLen;
- commEnd -= matchLen;
+ commStart -= matchLen;
+ commEnd -= matchLen;
+ } else {
+ matchLen = 0;
+ }
}
if (!matchLen || (haveCommField && *cmdline)) {
@@ -583,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);
@@ -590,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
}
@@ -598,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] == '/') {
@@ -620,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;
@@ -631,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];
@@ -658,107 +505,71 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin
if (!highlightDeleted)
continue;
+ if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR)
+ 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);
+static inline char processStateChar(ProcessState state) {
+ switch (state) {
+ case UNKNOWN: return '?';
+ case RUNNABLE: return 'U';
+ case RUNNING: return 'R';
+ case QUEUED: return 'Q';
+ case WAITING: return 'W';
+ case UNINTERRUPTIBLE_WAIT: return 'D';
+ case BLOCKED: return 'B';
+ case PAGING: return 'P';
+ case STOPPED: return 'T';
+ case TRACED: return 't';
+ case ZOMBIE: return 'Z';
+ case DEFUNCT: return 'X';
+ case IDLE: return 'I';
+ case SLEEPING: return 'S';
+ default:
+ assert(0);
+ return '!';
}
}
-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);
+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_printPercentage(float val, char* buffer, int n, int* attr) {
- if (val >= 0) {
- if (val < 99.9F) {
- if (val < 0.05F) {
- *attr = CRT_colors[PROCESS_SHADOW];
- }
- xSnprintf(buffer, n, "%4.1f ", val);
- } else if (val < 999) {
- *attr = CRT_colors[PROCESS_MEGABYTES];
- xSnprintf(buffer, n, "%3d. ", (int)val);
- } else {
- *attr = CRT_colors[PROCESS_MEGABYTES];
- xSnprintf(buffer, n, "%4d ", (int)val);
- }
- } else {
- *attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, " N/A ");
- }
-}
+void Process_writeField(const Process* this, RichString* str, RowField field) {
+ const Row* super = (const Row*) &this->super;
+ const Machine* host = super->host;
+ const Settings* settings = host->settings;
-void Process_writeField(const Process* this, RichString* str, ProcessField field) {
- char buffer[256];
- size_t n = sizeof(buffer);
+ 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];
}
- if (!this->settings->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, " ");
@@ -773,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;
@@ -788,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)
@@ -807,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: {
@@ -821,19 +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 = host->realtimeMs;
+ const uint64_t st = this->starttime_ctime * 1000;
+ const uint64_t dt =
+ rt < st ? 0 :
+ rt - st;
+ Row_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring);
return;
}
- case ELAPSED: Process_printTime(str, /* convert to hundreds of a second */ this->processList->realtimeMs / 10 - 100 * this->starttime_ctime, coloring); return;
- case MAJFLT: Process_printCount(str, this->majflt, coloring); return;
- case MINFLT: Process_printCount(str, this->minflt, coloring); return;
- case M_RESIDENT: Process_printKBytes(str, this->m_resident, coloring); return;
- case M_VIRT: Process_printKBytes(str, this->m_virt, coloring); return;
+ case 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)
@@ -841,51 +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, &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, &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, &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 ", this->state);
+ xSnprintf(buffer, n, "%c ", processStateChar(this->state));
switch (this->state) {
-#ifdef HTOP_NETBSD
- case 'P':
-#else
- case 'R':
-#endif
- attr = CRT_colors[PROCESS_RUN_STATE];
- break;
- case 'D':
- attr = CRT_colors[PROCESS_D_STATE];
- break;
- case 'I':
- case 'S':
- attr = CRT_colors[PROCESS_SHADOW];
- break;
+ 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, "%5d ", this->st_uid); break;
- case TIME: Process_printTime(str, this->time, coloring); return;
+ case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break;
+ 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:
@@ -898,15 +736,17 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
}
break;
case USER:
- if (Process_getuid != this->st_uid)
+ if (this->elevated_priv)
+ 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, 9);
+ Row_printLeftAlignedField(str, attr, this->user, 10);
return;
}
- xSnprintf(buffer, n, "%-9d ", this->st_uid);
+ xSnprintf(buffer, n, "%-10d ", this->st_uid);
break;
default:
if (DynamicColumn_writeField(this, str, field))
@@ -915,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->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);
@@ -956,136 +772,142 @@ void Process_done(Process* this) {
/* This function returns the string displayed in Command column, so that sorting
* happens on what is displayed - whether comm, full path, basename, etc.. So
* this follows Process_writeField(COMM) and Process_writeCommand */
-const char* Process_getCommandStr(const Process* this) {
- if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !this->mergedCommand.str) {
+const char* Process_getCommand(const Process* this) {
+ 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,
- .getCommandStr = Process_getCommandStr,
-};
+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) {
- return Process_setPriority(this, this->nice + delta.i);
+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_sendSignal(Process* this, Arg sgn) {
- return kill(this->pid, sgn.i) == 0;
+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);
}
-int Process_pidCompare(const void* v1, const void* v2) {
- const Process* p1 = (const Process*)v1;
- const Process* p2 = (const Process*)v2;
+static bool Process_sendSignal(Process* this, Arg sgn) {
+ return kill(Process_getPid(this), sgn.i) == 0;
+}
- return SPACESHIP_NUMBER(p1->pid, p2->pid);
+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 = p1->super.host->settings->ss;
- ProcessField key = Settings_getActiveSortKey(settings);
+ ProcessField key = ScreenSettings_getActiveSortKey(ss);
int result = Process_compareByKey(p1, p2, key);
// 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 (Settings_getActiveDirection(settings) == 1) ? result : -result;
+ return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;
}
-static uint8_t stateCompareValue(char state) {
- switch (state) {
-
- case 'S':
- return 10;
-
- case 'I':
- return 9;
-
- case 'X':
- return 8;
-
- case 'Z':
- return 7;
-
- case 't':
- return 6;
-
- case 'T':
- return 5;
-
- case 'L':
- return 4;
-
- case 'D':
- return 3;
+int Process_compareByParent(const Row* r1, const Row* r2) {
+ int result = Row_compareByParent_Base(r1, r2);
- case 'R':
- return 2;
+ if (result != 0)
+ return result;
- case '?':
- return 1;
-
- default:
- return 0;
- }
+ return Process_compare(r1, r2);
}
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) {
@@ -1094,7 +916,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
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:
@@ -1113,7 +935,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:
@@ -1129,26 +951,28 @@ 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(stateCompareValue(p1->state), stateCompareValue(p2->state));
+ return SPACESHIP_NUMBER(p1->state, p2->state);
case ST_UID:
return SPACESHIP_NUMBER(p1->st_uid, p2->st_uid);
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:
@@ -1157,8 +981,9 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
case USER:
return SPACESHIP_NULLSTR(p1->user, p2->user);
default:
+ CRT_debug("Process_compareByKey_Base() called with key %d", key);
assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */
- return SPACESHIP_NUMBER(p1->pid, p2->pid);
+ return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
}
}
@@ -1171,7 +996,8 @@ void Process_updateComm(Process* this, const char* comm) {
free(this->procComm);
this->procComm = comm ? xStrdup(comm) : NULL;
- this->mergedCommand.commChanged = true;
+
+ this->mergedCommand.lastUpdate = 0;
}
static int skipPotentialPath(const char* cmdline, int end) {
@@ -1211,7 +1037,8 @@ void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart
this->cmdline = cmdline ? xStrdup(cmdline) : NULL;
this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);
this->cmdlineBasenameEnd = basenameEnd;
- this->mergedCommand.cmdlineChanged = true;
+
+ this->mergedCommand.lastUpdate = 0;
}
void Process_updateExe(Process* this, const char* exe) {
@@ -1230,5 +1057,37 @@ void Process_updateExe(Process* this, const char* exe) {
this->procExe = NULL;
this->procExeBasenameOffset = 0;
}
- this->mergedCommand.exeChanged = true;
+
+ this->mergedCommand.lastUpdate = 0;
+}
+
+void Process_updateCPUFieldWidths(float percentage) {
+ if (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;
+
+ 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 1ba36de..3903a40 100644
--- a/Process.h
+++ b/Process.h
@@ -4,7 +4,7 @@
htop - Process.h
(C) 2004-2015 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -13,54 +13,39 @@ 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;
-
-struct Settings_;
+/* Core process states (shared by platforms)
+ * NOTE: The enum has an ordering that is important!
+ * See processStateChar in process.c for ProcessSate -> letter mapping */
+typedef enum ProcessState_ {
+ UNKNOWN = 1,
+ RUNNABLE,
+ RUNNING,
+ QUEUED,
+ WAITING,
+ UNINTERRUPTIBLE_WAIT,
+ BLOCKED,
+ PAGING,
+ STOPPED,
+ TRACED,
+ ZOMBIE,
+ DEFUNCT,
+ IDLE,
+ SLEEPING
+} ProcessState;
+
+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). */
@@ -76,35 +61,15 @@ typedef struct ProcessCmdlineHighlight_ {
* Process_writeCommand to color the string. str will be NULL for kernel
* threads and zombies */
typedef struct ProcessMergedCommand_ {
+ uint64_t lastUpdate; /* Marker based on settings->lastUpdate to track when the rendering needs refreshing */
char* str; /* merged Command string */
size_t highlightCount; /* how many portions of cmdline to highlight */
ProcessCmdlineHighlight highlights[8]; /* which portions of cmdline to highlight */
- bool cmdlineChanged : 1; /* whether cmdline changed */
- bool exeChanged : 1; /* whether exe changed */
- bool commChanged : 1; /* whether comm changed */
- bool prevMergeSet : 1; /* whether showMergedCommand was set */
- bool prevPathSet : 1; /* whether showProgramPath was set */
- bool prevCommSet : 1; /* whether findCommInCmdline was set */
- bool prevCmdlineSet : 1; /* whether stripExeFromCmdline was set */
- bool prevShowThreadNames : 1; /* whether showThreadNames was set */
} ProcessMergedCommand;
typedef struct Process_ {
/* 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;
@@ -121,6 +86,9 @@ typedef struct Process_ {
/* This is a userland thread / LWP */
bool isUserlandThread;
+ /* This process is running inside a container */
+ bool isRunningInContainer;
+
/* Controlling terminal identifier of the process */
unsigned long int tty_nr;
@@ -133,6 +101,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
+ */
+ bool elevated_priv;
+
/* Process runtime (in hundredth of a second) */
unsigned long long int time;
@@ -202,50 +177,11 @@ typedef struct Process_ {
/* Number of major faults the process has made which have required loading a memory page from disk */
unsigned long int majflt;
- /*
- * Process state (platform dependent):
- * D - Waiting
- * I - Idle
- * L - Acquiring lock
- * R - Running
- * S - Sleeping
- * T - Stopped (on a signal)
- * X - Dead
- * Z - Zombie
- * t - Tracing stop
- * ? - Unknown
- */
- char state;
-
- /* Whether the process was updated during the current scan */
- bool updated;
-
- /* Whether the process was tagged by the user */
- bool tag;
-
- /* Whether to display this process */
- bool show;
+ /* Process state enum field (platform dependent) */
+ ProcessState state;
- /* Whether this process was shown last cycle */
- bool wasShown;
-
- /* Whether to show children of this process in tree-mode */
- bool showChildren;
-
- /*
- * Internal time counts for showing new and exited processes.
- */
- uint64_t seenStampMs;
- uint64_t tombStampMs;
-
- /*
- * Internal state for tree-mode.
- */
- int indent;
- unsigned int tree_left;
- unsigned int tree_right;
- unsigned int tree_depth;
- unsigned int tree_index;
+ /* Current scheduling policy */
+ int scheduling_policy;
/*
* Internal state for merged Command display
@@ -271,39 +207,62 @@ typedef struct ProcessFieldData_ {
/* Whether the column should be sorted in descending order by default */
bool defaultSortDesc;
+
+ /* Whether the column width is dynamically adjusted (the minimum width is determined by the title length) */
+ bool autoWidth;
} ProcessFieldData;
+#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);
+void Process_writeField(const Process* row, RichString* str, ProcessField field);
int Process_compare(const void* v1, const void* v2);
+int Process_compareByParent(const Row* r1, const Row* v2);
void Process_delete(Object* cast);
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
-#define PROCESS_MAX_PID_DIGITS 19
-extern int Process_pidDigits;
+#define Process_pidDigits Row_pidDigits
+#define Process_uidDigits Row_uidDigits
-typedef Process* (*Process_New)(const struct Settings_*);
-typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField);
+typedef Process* (*Process_New)(const struct Machine_*);
typedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField);
-typedef const char* (*Process_GetCommandStr)(const Process*);
typedef struct ProcessClass_ {
- const ObjectClass super;
- const Process_WriteField writeField;
+ const RowClass super;
const Process_CompareByKey compareByKey;
- const Process_GetCommandStr getCommandStr;
} 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_getCommand(this_) (As_Process(this_)->getCommandStr ? As_Process(this_)->getCommandStr((const Process*)(this_)) : Process_getCommandStr((const Process*)(this_)))
-#define Process_compareByKey(p1_, p2_, key_) (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_))
+static inline void Process_setPid(Process* this, pid_t pid) {
+ this->super.id = pid;
+}
+
+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_getParentPid(const Process* this) {
- return this->tgid == this->pid ? this->ppid : this->tgid;
+static inline pid_t Process_getParent(const Process* this) {
+ return (pid_t)this->super.parent;
}
-static inline bool Process_isChildOf(const Process* this, pid_t pid) {
- return pid == Process_getParentPid(this);
+static inline pid_t Process_getGroupOrParent(const Process* this) {
+ return Row_getGroupOrParent(&this->super);
}
static inline bool Process_isKernelThread(const Process* this) {
@@ -322,77 +281,48 @@ 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);
-
-/* 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, 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);
-void Process_toggleTag(Process* this);
+const char* Process_rowGetSortKey(Row* super);
-bool Process_isNew(const Process* this);
+bool Process_rowSetPriority(Row* super, int priority);
-bool Process_isTomb(const Process* this);
+bool Process_rowChangePriorityBy(Row* super, Arg delta);
-bool Process_setPriority(Process* this, int priority);
+bool Process_rowSendSignal(Row* super, Arg sgn);
-bool Process_changePriorityBy(Process* this, Arg delta);
+bool Process_rowIsHighlighted(const Row* super);
-bool Process_sendSignal(Process* this, Arg sgn);
+bool Process_rowIsVisible(const Row* super, const struct Table_* table);
-int Process_pidCompare(const void* v1, const void* v2);
+bool Process_rowMatchesFilter(const Row* super, const struct Table_* table);
+
+static inline int Process_pidEqualCompare(const void* v1, const void* v2) {
+ return Row_idEqualCompare(v1, v2);
+}
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key);
-// Avoid direct calls, use Process_getCommand instead
-const char* Process_getCommandStr(const Process* this);
+const char* Process_getCommand(const Process* this);
void Process_updateComm(Process* this, const char* comm);
void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd);
void Process_updateExe(Process* this, const char* exe);
/* This function constructs the string that is displayed by
- * Process_writeCommand and also returned by Process_getCommandStr */
-void Process_makeCommandStr(Process* this);
+ * Process_writeCommand and also returned by Process_getCommand */
+void Process_makeCommandStr(Process* this, const struct Settings_ *settings);
void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str);
+void Process_updateCPUFieldWidths(float percentage);
+
#endif
diff --git a/ProcessList.c b/ProcessList.c
deleted file mode 100644
index daf871a..0000000
--- a/ProcessList.c
+++ /dev/null
@@ -1,659 +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->processes2 = Vector_new(klass, true, DEFAULT_SIZE); // tree-view auxiliary buffer
-
- this->processTable = Hashtable_new(200, false);
- this->displayTreeSet = Hashtable_new(200, false);
- this->draftingTreeSet = Hashtable_new(200, false);
-
- 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->draftingTreeSet);
- Hashtable_delete(this->displayTreeSet);
- Hashtable_delete(this->processTable);
-
- Vector_delete(this->processes2);
- Vector_delete(this->processes);
-}
-
-void ProcessList_setPanel(ProcessList* this, Panel* panel) {
- this->panel = panel;
-}
-
-static const char* alignedDynamicColumnTitle(const ProcessList* this, int key) {
- const DynamicColumn* column = Hashtable_get(this->dynamicColumns, key);
- if (column == NULL)
- return "- ";
- static char titleBuffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1];
- int width = column->width;
- if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
- width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
- xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s", width, column->heading);
- return titleBuffer;
-}
-
-static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessField field) {
- if (field >= LAST_PROCESSFIELD)
- return alignedDynamicColumnTitle(this, field);
-
- const char* title = Process_fields[field].title;
- if (!title)
- return "- ";
-
- if (!Process_fields[field].pidColumn)
- return title;
-
- static char titleBuffer[PROCESS_MAX_PID_DIGITS + /* space */ 1 + /* null-terminator */ + 1];
- xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title);
-
- return titleBuffer;
-}
-
-void ProcessList_printHeader(const ProcessList* this, RichString* header) {
- RichString_rewind(header, RichString_size(header));
-
- const Settings* settings = this->settings;
- const ProcessField* fields = settings->fields;
-
- ProcessField key = Settings_getActiveSortKey(settings);
-
- for (int i = 0; fields[i]; i++) {
- int color;
- if (settings->treeView && settings->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) == ' ') {
- RichString_rewind(header, 1); // rewind to override space
- RichString_appendnWide(header,
- CRT_colors[PANEL_SELECTION_FOCUS],
- CRT_treeStr[Settings_getActiveDirection(this->settings) == 1 ? TREE_STR_ASC : TREE_STR_DESC],
- 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_pidCompare) == -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_pidCompare) != -1);
- assert(Hashtable_get(this->processTable, p->pid) != NULL);
- assert(Hashtable_count(this->processTable) == Vector_count(this->processes));
-}
-
-void ProcessList_remove(ProcessList* this, const Process* p) {
- assert(Vector_indexOf(this->processes, p, Process_pidCompare) != -1);
- assert(Hashtable_get(this->processTable, p->pid) != NULL);
-
- const Process* pp = Hashtable_remove(this->processTable, p->pid);
- assert(pp == p); (void)pp;
-
- pid_t pid = p->pid;
- int idx = Vector_indexOf(this->processes, p, Process_pidCompare);
- assert(idx != -1);
-
- if (idx >= 0) {
- Vector_remove(this->processes, idx);
- }
-
- 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(Hashtable_count(this->processTable) == Vector_count(this->processes));
-}
-
-// ProcessList_updateTreeSetLayer sorts this->displayTreeSet,
-// relying only on itself.
-//
-// Algorithm
-//
-// The algorithm is based on `depth-first search`,
-// even though `breadth-first search` approach may be more efficient on first glance,
-// after comparison it may be not, as it's not safe to go deeper without first updating the tree structure.
-// If it would be safe that approach would likely bring an advantage in performance.
-//
-// Each call of the function looks for a 'layer'. A 'layer' is a list of processes with the same depth.
-// First it sorts a list. Then it runs the function recursively for each element of the sorted list.
-// After that it updates the settings of processes.
-//
-// It relies on `leftBound` and `rightBound` as an optimization to cut the list size at the time it builds a 'layer'.
-//
-// It uses a temporary Hashtable `draftingTreeSet` because it's not safe to traverse a tree
-// and at the same time make changes in it.
-//
-static void ProcessList_updateTreeSetLayer(ProcessList* this, unsigned int leftBound, unsigned int rightBound, unsigned int deep, unsigned int left, unsigned int right, unsigned int* index, unsigned int* treeIndex, int indent) {
-
- // It's guaranteed that layer_size is enough space
- // but most likely it needs less. Specifically on first iteration.
- int layerSize = (right - left) / 2;
-
- // check if we reach `children` of `leaves`
- if (layerSize == 0)
- return;
-
- Vector* layer = Vector_new(Vector_type(this->processes), false, layerSize);
-
- // Find all processes on the same layer (process with the same `deep` value
- // and included in a range from `leftBound` to `rightBound`).
- //
- // This loop also keeps track of left_bound and right_bound of these processes
- // in order not to lose this information once the list is sorted.
- //
- // The variables left_bound and right_bound are different from what the values lhs and rhs represent.
- // While left_bound and right_bound define a range of processes to look at, the values given by lhs and rhs are indices into an array
- //
- // In the below example note how filtering a range of indices i is different from filtering for processes in the bounds left_bound < x < right_bound …
- //
- // The nested tree set is sorted by left value, which is guaranteed upon entry/exit of this function.
- //
- // i | l | r
- // 1 | 1 | 9
- // 2 | 2 | 8
- // 3 | 4 | 5
- // 4 | 6 | 7
- for (unsigned int i = leftBound; i < rightBound; i++) {
- Process* proc = (Process*)Hashtable_get(this->displayTreeSet, i);
- assert(proc);
- if (proc && proc->tree_depth == deep && proc->tree_left > left && proc->tree_right < right) {
- if (Vector_size(layer) > 0) {
- Process* previous_process = (Process*)Vector_get(layer, Vector_size(layer) - 1);
-
- // Make a 'right_bound' of previous_process in a layer the current process's index.
- //
- // Use 'tree_depth' as a temporal variable.
- // It's safe to do as later 'tree_depth' will be renovated.
- previous_process->tree_depth = proc->tree_index;
- }
-
- Vector_add(layer, proc);
- }
- }
-
- // The loop above changes just up to process-1.
- // So the last process of the layer isn't updated by the above code.
- //
- // Thus, if present, set the `rightBound` to the last process on the layer
- if (Vector_size(layer) > 0) {
- Process* previous_process = (Process*)Vector_get(layer, Vector_size(layer) - 1);
- previous_process->tree_depth = rightBound;
- }
-
- Vector_quickSort(layer);
-
- int size = Vector_size(layer);
- for (int i = 0; i < size; i++) {
- Process* proc = (Process*)Vector_get(layer, i);
-
- unsigned int idx = (*index)++;
- int newLeft = (*treeIndex)++;
-
- int level = deep == 0 ? 0 : (int)deep - 1;
- int currentIndent = indent == -1 ? 0 : indent | (1 << level);
- int nextIndent = indent == -1 ? 0 : ((i < size - 1) ? currentIndent : indent);
-
- unsigned int newLeftBound = proc->tree_index;
- unsigned int newRightBound = proc->tree_depth;
- ProcessList_updateTreeSetLayer(this, newLeftBound, newRightBound, deep + 1, proc->tree_left, proc->tree_right, index, treeIndex, nextIndent);
-
- int newRight = (*treeIndex)++;
-
- proc->tree_left = newLeft;
- proc->tree_right = newRight;
- proc->tree_index = idx;
- proc->tree_depth = deep;
-
- if (indent == -1) {
- proc->indent = 0;
- } else if (i == size - 1) {
- proc->indent = -currentIndent;
- } else {
- proc->indent = currentIndent;
- }
-
- Hashtable_put(this->draftingTreeSet, proc->tree_index, proc);
-
- // It's not strictly necessary to do this, but doing so anyways
- // allows for checking the correctness of the inner workings.
- Hashtable_remove(this->displayTreeSet, newLeftBound);
- }
-
- Vector_delete(layer);
-}
-
-static void ProcessList_updateTreeSet(ProcessList* this) {
- unsigned int index = 0;
- unsigned int tree_index = 1;
-
- const int vsize = Vector_size(this->processes);
-
- assert(Hashtable_count(this->draftingTreeSet) == 0);
- assert((int)Hashtable_count(this->displayTreeSet) == vsize);
-
- ProcessList_updateTreeSetLayer(this, 0, vsize, 0, 0, vsize * 2 + 1, &index, &tree_index, -1);
-
- Hashtable* tmp = this->draftingTreeSet;
- this->draftingTreeSet = this->displayTreeSet;
- this->displayTreeSet = tmp;
-
- assert(Hashtable_count(this->draftingTreeSet) == 0);
- assert((int)Hashtable_count(this->displayTreeSet) == vsize);
-}
-
-static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, int direction, bool show, int* node_counter, int* node_index) {
- // On OpenBSD the kernel thread 'swapper' has pid 0.
- // Do not treat it as root of any tree.
- if (pid == 0)
- return;
-
- Vector* children = Vector_new(Class(Process), false, DEFAULT_SIZE);
-
- for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
- Process* process = (Process*)Vector_get(this->processes, i);
- if (process->show && Process_isChildOf(process, pid)) {
- process = (Process*)Vector_take(this->processes, i);
- Vector_add(children, process);
- }
- }
-
- int size = Vector_size(children);
- for (int i = 0; i < size; i++) {
- int index = (*node_index)++;
- Process* process = (Process*)Vector_get(children, i);
-
- int lft = (*node_counter)++;
-
- if (!show) {
- process->show = false;
- }
-
- int s = Vector_size(this->processes2);
- if (direction == 1) {
- Vector_add(this->processes2, process);
- } else {
- Vector_insert(this->processes2, 0, process);
- }
-
- assert(Vector_size(this->processes2) == s + 1); (void)s;
-
- int nextIndent = indent | (1 << level);
- ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < size - 1) ? nextIndent : indent, direction, show ? process->showChildren : false, node_counter, node_index);
- if (i == size - 1) {
- process->indent = -nextIndent;
- } else {
- process->indent = nextIndent;
- }
-
- int rht = (*node_counter)++;
-
- process->tree_left = lft;
- process->tree_right = rht;
- process->tree_depth = level + 1;
- process->tree_index = index;
- Hashtable_put(this->displayTreeSet, index, process);
- }
- Vector_delete(children);
-}
-
-static int ProcessList_treeProcessCompare(const void* v1, const void* v2) {
- const Process* p1 = (const Process*)v1;
- const Process* p2 = (const Process*)v2;
-
- return SPACESHIP_NUMBER(p1->tree_left, p2->tree_left);
-}
-
-static int ProcessList_treeProcessCompareByPID(const void* v1, const void* v2) {
- const Process* p1 = (const Process*)v1;
- const Process* p2 = (const Process*)v2;
-
- return SPACESHIP_NUMBER(p1->pid, p2->pid);
-}
-
-// Builds a sorted tree from scratch, without relying on previously gathered information
-static void ProcessList_buildTree(ProcessList* this) {
- int node_counter = 1;
- int node_index = 0;
- int direction = Settings_getActiveDirection(this->settings);
-
- // Sort by PID
- Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompareByPID);
- int vsize = Vector_size(this->processes);
-
- // Find all processes whose parent is not visible
- int size;
- while ((size = Vector_size(this->processes))) {
- int i;
- for (i = 0; i < size; i++) {
- Process* process = (Process*)Vector_get(this->processes, i);
-
- // Immediately consume processes hidden from view
- if (!process->show) {
- process = (Process*)Vector_take(this->processes, i);
- process->indent = 0;
- process->tree_depth = 0;
- process->tree_left = node_counter++;
- process->tree_index = node_index++;
- Vector_add(this->processes2, process);
- ProcessList_buildTreeBranch(this, process->pid, 0, 0, direction, false, &node_counter, &node_index);
- process->tree_right = node_counter++;
- Hashtable_put(this->displayTreeSet, process->tree_index, process);
- break;
- }
-
- pid_t ppid = Process_getParentPid(process);
-
- // Bisect the process vector to find parent
- int l = 0;
- int r = size;
-
- // If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0)
- // on Mac OS X 10.11.6) cancel bisecting and regard this process as
- // root.
- if (process->pid == ppid)
- r = 0;
-
- // On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2)
- // use a ppid of 0. As that PID can't exist, we can skip searching for it.
- if (!ppid)
- r = 0;
-
- while (l < r) {
- int c = (l + r) / 2;
- pid_t pid = ((Process*)Vector_get(this->processes, c))->pid;
- if (ppid == pid) {
- break;
- } else if (ppid < pid) {
- r = c;
- } else {
- l = c + 1;
- }
- }
-
- // If parent not found, then construct the tree with this node as root
- if (l >= r) {
- process = (Process*)Vector_take(this->processes, i);
- process->indent = 0;
- process->tree_depth = 0;
- process->tree_left = node_counter++;
- process->tree_index = node_index++;
- Vector_add(this->processes2, process);
- Hashtable_put(this->displayTreeSet, process->tree_index, process);
- ProcessList_buildTreeBranch(this, process->pid, 0, 0, direction, process->showChildren, &node_counter, &node_index);
- process->tree_right = node_counter++;
- break;
- }
- }
-
- // There should be no loop in the process tree
- assert(i < size);
- }
-
- // Swap listings around
- Vector* t = this->processes;
- this->processes = this->processes2;
- this->processes2 = t;
-
- // Check consistency of the built structures
- assert(Vector_size(this->processes) == vsize); (void)vsize;
- assert(Vector_size(this->processes2) == 0);
-}
-
-void ProcessList_sort(ProcessList* this) {
- if (this->settings->treeView) {
- ProcessList_updateTreeSet(this);
- Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompare);
- } else {
- Vector_insertionSort(this->processes);
- }
-}
-
-ProcessField ProcessList_keyAt(const ProcessList* this, int at) {
- int x = 0;
- const ProcessField* fields = this->settings->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;
- }
-}
-
-void ProcessList_collapseAllBranches(ProcessList* this) {
- int size = Vector_size(this->processes);
- for (int i = 0; i < size; i++) {
- Process* process = (Process*) Vector_get(this->processes, i);
- // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1
- if (process->tree_depth > 0 && process->pid > 1)
- process->showChildren = false;
- }
-}
-
-void ProcessList_rebuildPanel(ProcessList* this) {
- const char* incFilter = this->incFilter;
-
- 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->processes);
- int idx = 0;
- bool foundFollowed = false;
-
- for (int i = 0; i < processCount; i++) {
- Process* p = (Process*) Vector_get(this->processes, i);
-
- if ( (!p->show)
- || (this->userId != (uid_t) -1 && (p->st_uid != this->userId))
- || (incFilter && !(String_contains_i(Process_getCommand(p), incFilter)))
- || (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_pidCompare) != -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;
-
-
- // set scan timestamp
- static bool firstScanDone = false;
- if (firstScanDone) {
- Platform_gettime_monotonic(&this->monotonicMs);
- } else {
- this->monotonicMs = 0;
- firstScanDone = true;
- }
-
- ProcessList_goThroughEntries(this, false);
-
- for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
- Process* p = (Process*) Vector_get(this->processes, i);
- Process_makeCommandStr(p);
-
- if (p->tombStampMs > 0) {
- // remove tombed process
- if (this->monotonicMs >= p->tombStampMs) {
- ProcessList_remove(this, p);
- }
- } else if (p->updated == false) {
- // process no longer exists
- if (this->settings->highlightChanges && p->wasShown) {
- // mark tombed
- p->tombStampMs = this->monotonicMs + 1000 * this->settings->highlightDelaySecs;
- } else {
- // immediately remove
- ProcessList_remove(this, p);
- }
- }
- }
-
- if (this->settings->treeView) {
- // Clear out the hashtable to avoid any left-over processes from previous build
- //
- // The sorting algorithm relies on the fact that
- // len(this->displayTreeSet) == len(this->processes)
- Hashtable_clear(this->displayTreeSet);
-
- ProcessList_buildTree(this);
- }
-}
diff --git a/ProcessList.h b/ProcessList.h
deleted file mode 100644
index 7cd2fab..0000000
--- a/ProcessList.h
+++ /dev/null
@@ -1,129 +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;
- Vector* processes2;
- Hashtable* processTable;
- UsersTable* usersTable;
-
- Hashtable* displayTreeSet;
- Hashtable* draftingTreeSet;
-
- Hashtable* dynamicMeters; /* runtime-discovered meters */
- Hashtable* dynamicColumns; /* runtime-discovered Columns */
-
- struct timeval realtime; /* time of the current sample */
- uint64_t realtimeMs; /* current time in milliseconds */
- uint64_t monotonicMs; /* same, but from monotonic clock */
-
- Panel* panel;
- int following;
- uid_t userId;
- 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_remove(ProcessList* this, const Process* p);
-
-void ProcessList_sort(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 5759615..36a37f9 100644
--- a/ProcessLocksScreen.c
+++ b/ProcessLocksScreen.c
@@ -1,7 +1,7 @@
/*
htop - ProcessLocksScreen.c
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 c848228..417df7b 100644
--- a/ProcessLocksScreen.h
+++ b/ProcessLocksScreen.h
@@ -3,7 +3,7 @@
/*
htop - ProcessLocksScreen.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 0000000..ac6fc70
--- /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 0000000..96a517a
--- /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 4eb08a2..7ae99e6 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 GPL, see the COPYING file
+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 0000000..a8910f1
--- /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 c797f56..7ace7cd 100644
--- a/README
+++ b/README
@@ -6,7 +6,7 @@
[![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)
+[![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)
@@ -34,6 +34,7 @@ List of build-time dependencies:
* standard GNU autotools-based C toolchain
- C99 compliant compiler
- `autoconf`
+ - `automake`
- `autotools`
* `ncurses`
@@ -47,14 +48,14 @@ List of build-time dependencies:
List of additional build-time dependencies (based on feature flags):
* `sensors`
* `hwloc`
-* `libcap`
+* `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
+sudo apt install libncursesw5-dev autotools-dev autoconf automake build-essential
~~~
**Fedora/RHEL**
@@ -62,6 +63,16 @@ sudo apt install libncursesw5-dev autotools-dev autoconf build-essential
sudo dnf install ncurses-devel automake autoconf gcc
~~~
+**Archlinux/Manjaro**
+~~~ shell
+sudo pacman -S ncurses automake autoconf gcc
+~~~
+
+**macOS**
+~~~ shell
+brew install ncurses automake autoconf gcc
+~~~
+
### Compile from source:
To compile from source, download from the Git repository (`git clone` or downloads from [GitHub releases](https://github.com/htop-dev/htop/releases/)), then run:
~~~ shell
@@ -174,4 +185,4 @@ In 2020 a [team](https://github.com/orgs/htop-dev/people) took over the developm
## License
-GNU General Public License, version 2 (GPL-2.0)
+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 558c743..ed852ad 100644
--- a/RichString.c
+++ b/RichString.c
@@ -1,10 +1,12 @@
/*
htop - RichString.c
(C) 2004,2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -61,7 +63,7 @@ static inline int RichString_writeFromWide(RichString* this, int attrs, const ch
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;
@@ -79,7 +81,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 +103,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 5c456d2..70ca274 100644
--- a/RichString.h
+++ b/RichString.h
@@ -3,12 +3,10 @@
/*
htop - RichString.h
(C) 2004,2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "config.h"
-
#include "ProvideCurses.h"
@@ -18,7 +16,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; \
diff --git a/Row.c b/Row.c
new file mode 100644
index 0000000..9ea3f07
--- /dev/null
+++ b/Row.c
@@ -0,0 +1,497 @@
+/*
+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 (field == PERCENT_CPU)
+ 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 ");
+ return;
+}
+
+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;
+
+ 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_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) {
+ 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 0000000..f67c610
--- /dev/null
+++ b/Row.h
@@ -0,0 +1,181 @@
+#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 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 0000000..1e01ea3
--- /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 0000000..d5c4b8a
--- /dev/null
+++ b/Scheduling.c
@@ -0,0 +1,162 @@
+/*
+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 "???";
+ }
+}
+
+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 0000000..610503c
--- /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 914c510..e7e82e1 100644
--- a/ScreenManager.c
+++ b/ScreenManager.c
@@ -1,7 +1,7 @@
/*
htop - ScreenManager.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -12,18 +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;
@@ -33,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;
@@ -49,32 +54,58 @@ inline int ScreenManager_size(const ScreenManager* this) {
}
void ScreenManager_add(ScreenManager* this, Panel* item, int size) {
+ ScreenManager_insert(this, item, size, Vector_size(this->panels));
+}
+
+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 (this->panelCount > 0) {
- const Panel* last = (const Panel*) Vector_get(this->panels, this->panelCount - 1);
+ if (idx > 0) {
+ const Panel* last = (const Panel*) Vector_get(this->panels, idx - 1);
lastX = last->x + last->w + 1;
}
- int height = LINES - this->y1 - (this->header ? this->header->height : 0) + this->y2;
- if (size > 0) {
- Panel_resize(item, size, height);
- } else {
- Panel_resize(item, COLS - this->x1 + this->x2 - lastX, height);
+ 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 + header_height(this));
+ if (idx < this->panelCount) {
+ for (int i = idx + 1; i <= this->panelCount; i++) {
+ Panel* p = (Panel*) Vector_get(this->panels, i);
+ Panel_move(p, p->x + size, p->y);
+ }
}
- Panel_move(item, lastX, this->y1 + (this->header ? this->header->height : 0));
- Vector_add(this->panels, item);
+ Vector_insert(this->panels, idx, item);
item->needsRedraw = true;
this->panelCount++;
}
Panel* ScreenManager_remove(ScreenManager* this, int idx) {
assert(this->panelCount > idx);
+ int w = ((Panel*) Vector_get(this->panels, idx))->w;
Panel* panel = (Panel*) Vector_remove(this->panels, idx);
this->panelCount--;
+ if (idx < this->panelCount) {
+ for (int i = idx; i < this->panelCount; i++) {
+ Panel* p = (Panel*) Vector_get(this->panels, i);
+ Panel_move(p, p->x - w, p->y);
+ }
+ }
return panel;
}
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++) {
@@ -88,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) {
- 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) {
@@ -103,41 +134,99 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi
if (*rescan) {
*oldTime = newTime;
- // scan processes first - some header values are calculated there
- ProcessList_scan(pl, this->state->pauseProcessUpdate);
+ int oldUidDigits = Process_uidDigits;
+ if (!this->state->pauseUpdate && (*sortTimeout == 0 || host->settings->ss->treeView)) {
+ host->activeTable->needsSort = true;
+ *sortTimeout = 1;
+ }
+ // 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);
- if (!this->state->pauseProcessUpdate && (*sortTimeout == 0 || this->settings->treeView)) {
- ProcessList_sort(pl);
- *sortTimeout = 1;
+ // force redraw if the number of UID digits was changed
+ if (Process_uidDigits != oldUidDigits) {
+ *force_redraw = true;
}
*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(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)++;
+ if (*x >= l)
+ return false;
+ int nameLen = strlen(name);
+ int n = MINIMUM(l - *x, nameLen);
+ attrset(CRT_colors[cur ? SCREENS_CUR_TEXT : SCREENS_OTH_TEXT]);
+ mvaddnstr(*y, *x, name, n);
+ *x += n;
+ if (*x >= l)
+ return false;
+ attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);
+ mvaddch(*y, *x, ']');
+ *x += 2;
+ if (*x >= l)
+ return false;
+ return true;
+}
+
+static void ScreenManager_drawScreenTabs(ScreenManager* this) {
+ 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;
+ int x = 2;
+
+ if (this->name) {
+ drawTab(&y, &x, l, this->name, true);
+ return;
+ }
+
+ for (int s = 0; screens[s]; s++) {
+ bool ok = drawTab(&y, &x, l, screens[s]->heading, s == cur);
+ if (!ok) {
+ break;
+ }
+ }
+ attrset(CRT_colors[RESET_COLOR]);
+}
+
static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_redraw) {
+ Settings* settings = this->host->settings;
+ if (settings->screenTabs) {
+ ScreenManager_drawScreenTabs(this);
+ }
const int nPanels = this->panelCount;
for (int i = 0; i < nPanels; i++) {
Panel* panel = (Panel*) Vector_get(this->panels, i);
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));
}
}
-void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
+void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name) {
bool quit = false;
int focus = 0;
Panel* panelFocus = (Panel*) Vector_get(this->panels, focus);
+ Settings* settings = this->host->settings;
double oldTime = 0.0;
@@ -151,25 +240,30 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
int sortTimeout = 0;
int resetSortTimeout = 5;
+ this->name = name;
+
while (!quit) {
if (this->header) {
- checkRecalculation(this, &oldTime, &sortTimeout, &redraw, &rescan, &timedOut);
+ checkRecalculation(this, &oldTime, &sortTimeout, &redraw, &rescan, &timedOut, &force_redraw);
}
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;
-#ifdef HAVE_SET_ESCDELAY
- set_escdelay(25);
-#endif
- ch = getch();
+ ch = Panel_getCh(panelFocus);
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);
@@ -184,6 +278,9 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
if (mevent.y == panel->y) {
ch = EVENT_HEADER_CLICK(mevent.x - panel->x);
break;
+ } else if (settings->screenTabs && mevent.y == panel->y - 1) {
+ ch = EVENT_SCREEN_TAB_CLICK(mevent.x);
+ break;
} else if (mevent.y > panel->y && mevent.y <= panel->y + panel->h) {
ch = KEY_MOUSE;
if (panel == panelFocus || this->allowFocusChange) {
@@ -224,12 +321,14 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
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);
@@ -306,6 +405,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 bf55f17..37821bb 100644
--- a/ScreenManager.h
+++ b/ScreenManager.h
@@ -3,7 +3,7 @@
/*
htop - ScreenManager.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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"
@@ -22,14 +22,15 @@ typedef struct ScreenManager_ {
int x2;
int y2;
Vector* panels;
+ 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);
@@ -37,10 +38,12 @@ int ScreenManager_size(const ScreenManager* this);
void ScreenManager_add(ScreenManager* this, Panel* item, int size);
+void ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx);
+
Panel* ScreenManager_remove(ScreenManager* this, int idx);
void ScreenManager_resize(ScreenManager* this);
-void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey);
+void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name);
#endif
diff --git a/ScreenTabsPanel.c b/ScreenTabsPanel.c
new file mode 100644
index 0000000..e48e5fb
--- /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 0000000..fe1a313
--- /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
new file mode 100644
index 0000000..4138066
--- /dev/null
+++ b/ScreensPanel.c
@@ -0,0 +1,328 @@
+/*
+htop - ScreensPanel.c
+(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
+
+#include "ScreensPanel.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"
+#include "ProvideCurses.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+
+static void ScreenListItem_delete(Object* cast) {
+ ScreenListItem* this = (ScreenListItem*)cast;
+ if (this->ss) {
+ ScreenSettings_delete(this->ss);
+ }
+ ListItem_delete(cast);
+}
+
+ObjectClass ScreenListItem_class = {
+ .extends = Class(ListItem),
+ .display = ListItem_display,
+ .delete = ScreenListItem_delete,
+ .compare = ListItem_compare
+};
+
+ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) {
+ ScreenListItem* this = AllocThis(ScreenListItem);
+ ListItem_init((ListItem*)this, value, 0);
+ this->ss = ss;
+ return this;
+}
+
+static const char* const ScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL};
+static const char* const DynamicFunctions[] = {" ", "Rename", " ", " ", " ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL};
+
+static void ScreensPanel_delete(Object* object) {
+ Panel* super = (Panel*) object;
+
+ /* do not delete screen settings still in use */
+ int n = Panel_size(super);
+ for (int i = 0; i < n; i++) {
+ ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
+ item->ss = NULL;
+ }
+
+ Panel_delete(object);
+}
+
+static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) {
+ ScreensPanel* const this = (ScreensPanel*) super;
+
+ if (ch >= 32 && ch < 127 && ch != '=') {
+ if (this->cursor < SCREEN_NAME_LEN - 1) {
+ this->buffer[this->cursor] = (char)ch;
+ this->cursor++;
+ super->selectedLen = strlen(this->buffer);
+ Panel_setCursorToSelection(super);
+ }
+
+ 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);
+ 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;
+}
+
+static void startRenaming(Panel* super) {
+ ScreensPanel* const this = (ScreensPanel*) 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 rebuildSettingsArray(Panel* super, int selected) {
+ ScreensPanel* const this = (ScreensPanel*) super;
+
+ int n = Panel_size(super);
+ free(this->settings->screens);
+ this->settings->screens = xMallocArray(n + 1, sizeof(ScreenSettings*));
+ this->settings->screens[n] = NULL;
+ for (int i = 0; i < n; i++) {
+ ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
+ this->settings->screens[i] = item->ss;
+ }
+ this->settings->nScreens = n;
+ /* 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" });
+ ScreenListItem* item = ScreenListItem_new(name, ss);
+ int idx = Panel_getSelectedIndex(super);
+ Panel_insert(super, idx + 1, (Object*) item);
+ Panel_setSelected(super, idx + 1);
+}
+
+static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) {
+ ScreensPanel* const this = (ScreensPanel*) super;
+
+ int selected = Panel_getSelectedIndex(super);
+ ScreenListItem* oldFocus = (ScreenListItem*) Panel_getSelected(super);
+ bool shouldRebuildArray = false;
+ HandlerResult result = IGNORED;
+
+ switch (ch) {
+ case '\n':
+ case '\r':
+ case KEY_ENTER:
+ case KEY_MOUSE:
+ case KEY_RECLICK: {
+ this->moving = !this->moving;
+ Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS);
+ ListItem* item = (ListItem*) Panel_getSelected(super);
+ if (item)
+ item->moving = this->moving;
+ result = HANDLED;
+ break;
+ }
+ case EVENT_SET_SELECTED:
+ result = HANDLED;
+ break;
+ case KEY_NPAGE:
+ case KEY_PPAGE:
+ case KEY_HOME:
+ case KEY_END:
+ Panel_onKey(super, ch);
+ break;
+ case KEY_F(2):
+ case KEY_CTRL('R'):
+ startRenaming(super);
+ result = HANDLED;
+ break;
+ case KEY_F(5):
+ case KEY_CTRL('N'):
+ 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;
+ }
+ /* 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;
+ }
+ /* FALLTHRU */
+ case KEY_F(8):
+ case ']':
+ case '+':
+ Panel_moveSelectedDown(super);
+ shouldRebuildArray = true;
+ result = HANDLED;
+ break;
+ case KEY_F(9):
+ //case KEY_DC:
+ if (Panel_size(super) > 1)
+ Panel_remove(super, selected);
+ shouldRebuildArray = true;
+ result = HANDLED;
+ break;
+ default:
+ if (ch < 255 && isalpha(ch))
+ result = Panel_selectByTyping(super, ch);
+ if (result == BREAK_LOOP)
+ result = IGNORED;
+ break;
+ }
+
+ ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super);
+ if (newFocus && oldFocus != newFocus) {
+ 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, selected);
+ if (result == HANDLED)
+ ScreensPanel_update(super);
+
+ return result;
+}
+
+static HandlerResult ScreensPanel_eventHandler(Panel* super, int ch) {
+ ScreensPanel* const this = (ScreensPanel*) super;
+
+ if (this->renamingItem) {
+ return ScreensPanel_eventHandlerRenaming(super, ch);
+ } else {
+ return ScreensPanel_eventHandlerNormal(super, ch);
+ }
+}
+
+PanelClass ScreensPanel_class = {
+ .super = {
+ .extends = Class(Panel),
+ .delete = ScreensPanel_delete
+ },
+ .eventHandler = ScreensPanel_eventHandler
+};
+
+ScreensPanel* ScreensPanel_new(Settings* settings) {
+ ScreensPanel* this = AllocThis(ScreensPanel);
+ Panel* super = (Panel*) this;
+ Hashtable* columns = settings->dynamicColumns;
+ FunctionBar* fuBar = FunctionBar_new(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->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->heading;
+ Panel_add(super, (Object*) ScreenListItem_new(name, ss));
+ }
+ return this;
+}
+
+void ScreensPanel_update(Panel* super) {
+ ScreensPanel* this = (ScreensPanel*) super;
+ int size = Panel_size(super);
+ this->settings->changed = true;
+ this->settings->lastUpdate++;
+ this->settings->screens = xReallocArray(this->settings->screens, size + 1, sizeof(ScreenSettings*));
+ for (int i = 0; i < size; i++) {
+ ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
+ ScreenSettings* ss = item->ss;
+ free_and_xStrdup(&ss->heading, ((ListItem*) item)->value);
+ this->settings->screens[i] = ss;
+ }
+ this->settings->screens[size] = NULL;
+}
diff --git a/ScreensPanel.h b/ScreensPanel.h
new file mode 100644
index 0000000..0be0b82
--- /dev/null
+++ b/ScreensPanel.h
@@ -0,0 +1,55 @@
+#ifndef HEADER_ScreensPanel
+#define HEADER_ScreensPanel
+/*
+htop - ScreensPanel.h
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2022 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "AvailableColumnsPanel.h"
+#include "ColumnsPanel.h"
+#include "DynamicScreen.h"
+#include "ListItem.h"
+#include "Object.h"
+#include "Panel.h"
+#include "ScreenManager.h"
+#include "Settings.h"
+
+#ifndef SCREEN_NAME_LEN
+#define SCREEN_NAME_LEN 20
+#endif
+
+typedef struct ScreensPanel_ {
+ Panel super;
+
+ ScreenManager* scr;
+ Settings* settings;
+ ColumnsPanel* columns;
+ AvailableColumnsPanel* availableColumns;
+ char buffer[SCREEN_NAME_LEN + 1];
+ char* saved;
+ int cursor;
+ bool moving;
+ ListItem* renamingItem;
+} ScreensPanel;
+
+typedef struct ScreenListItem_ {
+ ListItem super;
+ DynamicScreen* ds;
+ ScreenSettings* ss;
+} ScreenListItem;
+
+
+extern ObjectClass ScreenListItem_class;
+
+ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss);
+
+ScreensPanel* ScreensPanel_new(Settings* settings);
+
+void ScreensPanel_update(Panel* super);
+
+#endif
diff --git a/Settings.c b/Settings.c
index 5ca998a..a01e249 100644
--- a/Settings.c
+++ b/Settings.c
@@ -1,15 +1,18 @@
/*
htop - Settings.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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 <limits.h>
+#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -18,24 +21,70 @@ 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"
+/*
+
+static char** readQuotedList(char* line) {
+ int n = 0;
+ char** list = xCalloc(sizeof(char*), 1);
+ int start = 0;
+ for (;;) {
+ while (line[start] && line[start] == ' ') {
+ start++;
+ }
+ if (line[start] != '"') {
+ break;
+ }
+ start++;
+ int close = start;
+ while (line[close] && line[close] != '"') {
+ close++;
+ }
+ int len = close - start;
+ char* item = xMalloc(len + 1);
+ strncpy(item, line + start, len);
+ item[len] = '\0';
+ list[n] = item;
+ n++;
+ list = xRealloc(list, sizeof(char*) * (n + 1));
+ start = close + 1;
+ }
+ list[n] = NULL;
+ return list;
+}
+
+static void writeQuotedList(FILE* fd, char** list) {
+ const char* sep = "";
+ for (int i = 0; list[i]; i++) {
+ fprintf(fd, "%s\"%s\"", sep, list[i]);
+ sep = " ";
+ }
+ fprintf(fd, "\n");
+}
+
+*/
+
void Settings_delete(Settings* this) {
free(this->filename);
- free(this->fields);
for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
- if (this->hColumns[i].names) {
- for (uint8_t j = 0; j < this->hColumns[i].len; j++)
- free(this->hColumns[i].names[j]);
- free(this->hColumns[i].names);
- }
+ String_freeArray(this->hColumns[i].names);
free(this->hColumns[i].modes);
}
free(this->hColumns);
+ if (this->screens) {
+ for (unsigned int i = 0; this->screens[i]; i++) {
+ ScreenSettings_delete(this->screens[i]);
+ }
+ free(this->screens);
+ }
free(this);
}
@@ -65,16 +114,58 @@ static void Settings_readMeterModes(Settings* this, const char* line, unsigned i
this->hColumns[column].modes = modes;
}
+static bool Settings_validateMeters(Settings* this) {
+ const size_t colCount = HeaderLayout_getColumns(this->hLayout);
+
+ bool anyMeter = false;
+
+ for (size_t column = 0; column < colCount; column++) {
+ char** names = this->hColumns[column].names;
+ const int* modes = this->hColumns[column].modes;
+ const size_t len = this->hColumns[column].len;
+
+ if (!len)
+ continue;
+
+ if (!names || !modes)
+ return false;
+
+ anyMeter |= !!len;
+
+ // Check for each mode there is an entry with a non-NULL name
+ for (size_t meterIdx = 0; meterIdx < len; meterIdx++)
+ if (!names[meterIdx])
+ return false;
+
+ if (names[len])
+ return false;
+ }
+
+ return anyMeter;
+}
+
static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) {
int sizes[] = { 3, 3 };
+
if (initialCpuCount > 4 && initialCpuCount <= 128) {
sizes[1]++;
}
- for (int i = 0; i < 2; i++) {
+
+ // Release any previously allocated memory
+ for (size_t i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
+ String_freeArray(this->hColumns[i].names);
+ free(this->hColumns[i].modes);
+ }
+ free(this->hColumns);
+
+ 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].len = sizes[i];
}
+
int r = 0;
if (initialCpuCount > 128) {
@@ -117,51 +208,156 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount)
this->hColumns[1].modes[r++] = TEXT_METERMODE;
}
-static void Settings_readFields(Settings* settings, const char* line) {
+static const char* toFieldName(Hashtable* columns, int id, bool* enabled) {
+ if (id < 0) {
+ if (enabled)
+ *enabled = false;
+ return NULL;
+ }
+ 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;
+}
+
+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, NULL)) {
+ return id;
+ }
+ } else {
+ // Dynamically-defined columns are always stored by-name.
+ char dynamic[32] = {0};
+ if (sscanf(str, "Dynamic(%30s)", dynamic)) {
+ char* end;
+ if ((end = strrchr(dynamic, ')')) != NULL) {
+ bool success;
+ unsigned int key;
+ *end = '\0';
+ success = DynamicColumn_search(columns, dynamic, &key) != NULL;
+ *end = ')';
+ if (success)
+ return key;
+ }
+ }
+ // Fallback to iterative scan of table of fields by-name.
+ for (int p = 1; p < LAST_PROCESSFIELD; p++) {
+ const char* pName = toFieldName(columns, p, NULL);
+ if (pName && strcmp(pName, str) == 0)
+ return p;
+ }
+ }
+ return -1;
+}
+
+static void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, const char* line) {
char* trim = String_trim(line);
char** ids = String_split(trim, ' ', NULL);
free(trim);
- settings->flags = 0;
+ /* reset default fields */
+ memset(ss->fields, '\0', LAST_PROCESSFIELD * sizeof(ProcessField));
- unsigned int i, j;
- for (j = 0, i = 0; ids[i]; i++) {
+ for (size_t j = 0, i = 0; ids[i]; i++) {
if (j >= UINT_MAX / sizeof(ProcessField))
continue;
if (j >= LAST_PROCESSFIELD) {
- settings->fields = xRealloc(settings->fields, j * sizeof(ProcessField));
- memset(&settings->fields[j], 0, sizeof(ProcessField));
- }
-
- // Dynamically-defined columns are always stored by-name.
- char dynamic[32] = {0};
- if (sscanf(ids[i], "Dynamic(%30s)", dynamic)) {
- char* end;
- if ((end = strrchr(dynamic, ')')) == NULL)
- continue;
- *end = '\0';
- unsigned int key;
- if (!DynamicColumn_search(settings->dynamicColumns, dynamic, &key))
- continue;
- settings->fields[j++] = key;
- continue;
- }
- // This "+1" is for compatibility with the older enum format.
- int id = atoi(ids[i]) + 1;
- if (id > 0 && id < LAST_PROCESSFIELD && Process_fields[id].name) {
- settings->flags |= Process_fields[id].flags;
- settings->fields[j++] = id;
+ ss->fields = xRealloc(ss->fields, (j + 1) * sizeof(ProcessField));
+ memset(&ss->fields[j], 0, sizeof(ProcessField));
}
+ int id = toFieldIndex(columns, ids[i]);
+ if (id >= 0)
+ ss->fields[j++] = id;
+ if (id > 0 && id < LAST_PROCESSFIELD)
+ ss->flags |= Process_fields[id].flags;
}
- settings->fields[j] = NULL_PROCESSFIELD;
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) {
+ .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 = treeSortKey,
+ .treeView = false,
+ .treeViewAlwaysByPID = false,
+ .allBranchesCollapsed = false,
+ };
+ return Settings_initScreenSettings(ss, this, defaults->columns);
+}
+
+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->heading);
+ free(this->dynamic);
+ free(this->fields);
+ free(this);
+}
+
+static ScreenSettings* Settings_defaultScreens(Settings* this) {
+ if (this->nScreens)
+ return this->screens[0];
+ for (unsigned int i = 0; i < Platform_numberOfDefaultScreens; i++) {
+ const ScreenDefaults* defaults = &Platform_defaultScreens[i];
+ Settings_newScreen(this, defaults);
+ }
+ 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)
return false;
+ ScreenSettings* screen = NULL;
bool didReadMeters = false;
bool didReadAny = false;
for (;;) {
@@ -181,34 +377,53 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
this->config_version = atoi(option[1]);
if (this->config_version > CONFIG_READER_MIN_VERSION) {
// the version of the config file on disk is newer than what we can read
- fprintf(stderr, "WARNING: %s specifies configuration format version v%d, but this %s binary supports up to v%d\n.", fileName, this->config_version, PACKAGE, CONFIG_READER_MIN_VERSION);
- fprintf(stderr, " The configuration version will be downgraded to v%d when %s exits.\n", CONFIG_READER_MIN_VERSION, PACKAGE);
+ fprintf(stderr, "WARNING: %s specifies configuration format\n", fileName);
+ 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);
return false;
}
- } else if (String_eq(option[0], "fields")) {
- Settings_readFields(this, option[1]);
- } else if (String_eq(option[0], "sort_key")) {
+ } else if (String_eq(option[0], "fields") && this->config_version <= 2) {
+ // old (no screen) naming also supported for backwards compatibility
+ screen = Settings_defaultScreens(this);
+ ScreenSettings_readFields(screen, this->dynamicColumns, option[1]);
+ } else if (String_eq(option[0], "sort_key") && this->config_version <= 2) {
+ // old (no screen) naming also supported for backwards compatibility
// This "+1" is for compatibility with the older enum format.
- this->sortKey = atoi(option[1]) + 1;
- } else if (String_eq(option[0], "tree_sort_key")) {
+ screen = Settings_defaultScreens(this);
+ screen->sortKey = atoi(option[1]) + 1;
+ } else if (String_eq(option[0], "tree_sort_key") && this->config_version <= 2) {
+ // old (no screen) naming also supported for backwards compatibility
// This "+1" is for compatibility with the older enum format.
- this->treeSortKey = atoi(option[1]) + 1;
- } else if (String_eq(option[0], "sort_direction")) {
- this->direction = atoi(option[1]);
- } else if (String_eq(option[0], "tree_sort_direction")) {
- this->treeDirection = atoi(option[1]);
- } else if (String_eq(option[0], "tree_view")) {
- this->treeView = atoi(option[1]);
- } else if (String_eq(option[0], "tree_view_always_by_pid")) {
- this->treeViewAlwaysByPID = atoi(option[1]);
- } else if (String_eq(option[0], "all_branches_collapsed")) {
- this->allBranchesCollapsed = atoi(option[1]);
+ screen = Settings_defaultScreens(this);
+ screen->treeSortKey = atoi(option[1]) + 1;
+ } else if (String_eq(option[0], "sort_direction") && this->config_version <= 2) {
+ // old (no screen) naming also supported for backwards compatibility
+ screen = Settings_defaultScreens(this);
+ screen->direction = atoi(option[1]);
+ } else if (String_eq(option[0], "tree_sort_direction") && this->config_version <= 2) {
+ // old (no screen) naming also supported for backwards compatibility
+ screen = Settings_defaultScreens(this);
+ screen->treeDirection = atoi(option[1]);
+ } else if (String_eq(option[0], "tree_view") && this->config_version <= 2) {
+ // old (no screen) naming also supported for backwards compatibility
+ screen = Settings_defaultScreens(this);
+ screen->treeView = atoi(option[1]);
+ } else if (String_eq(option[0], "tree_view_always_by_pid") && this->config_version <= 2) {
+ // old (no screen) naming also supported for backwards compatibility
+ screen = Settings_defaultScreens(this);
+ screen->treeViewAlwaysByPID = atoi(option[1]);
+ } else if (String_eq(option[0], "all_branches_collapsed") && this->config_version <= 2) {
+ // old (no screen) naming also supported for backwards compatibility
+ screen = Settings_defaultScreens(this);
+ screen->allBranchesCollapsed = atoi(option[1]);
} else if (String_eq(option[0], "hide_kernel_threads")) {
this->hideKernelThreads = atoi(option[1]);
} else if (String_eq(option[0], "hide_userland_threads")) {
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")) {
@@ -219,6 +434,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")) {
@@ -235,6 +452,8 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
this->showMergedCommand = atoi(option[1]);
} else if (String_eq(option[0], "header_margin")) {
this->headerMargin = atoi(option[1]);
+ } else if (String_eq(option[0], "screen_tabs")) {
+ this->screenTabs = atoi(option[1]);
} else if (String_eq(option[0], "expand_system_time")) {
// Compatibility option.
this->detailedCPUTime = atoi(option[1]);
@@ -300,23 +519,60 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
} else if (String_eq(option[0], "topology_affinity")) {
this->topologyAffinity = !!atoi(option[1]);
#endif
+ } else if (strncmp(option[0], "screen:", 7) == 0) {
+ screen = Settings_newScreen(this, &(const ScreenDefaults) { .name = option[0] + 7, .columns = option[1] });
+ } else if (String_eq(option[0], ".sort_key")) {
+ if (screen) {
+ int key = toFieldIndex(this->dynamicColumns, option[1]);
+ screen->sortKey = key > 0 ? key : PID;
+ }
+ } else if (String_eq(option[0], ".tree_sort_key")) {
+ 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]);
+ } else if (String_eq(option[0], ".tree_sort_direction")) {
+ if (screen)
+ screen->treeDirection = atoi(option[1]);
+ } else if (String_eq(option[0], ".tree_view")) {
+ if (screen)
+ screen->treeView = atoi(option[1]);
+ } else if (String_eq(option[0], ".tree_view_always_by_pid")) {
+ if (screen)
+ screen->treeViewAlwaysByPID = atoi(option[1]);
+ } else if (String_eq(option[0], ".all_branches_collapsed")) {
+ if (screen)
+ screen->allBranchesCollapsed = atoi(option[1]);
+ } else if (String_eq(option[0], ".dynamic")) {
+ if (screen) {
+ free_and_xStrdup(&screen->dynamic, option[1]);
+ Platform_addDynamicScreen(screen);
+ }
}
String_freeArray(option);
}
fclose(fd);
- if (!didReadMeters) {
+ if (!didReadMeters || !Settings_validateMeters(this))
Settings_defaultMeters(this, initialCpuCount);
- }
+ if (!this->nScreens)
+ Settings_defaultScreens(this);
return didReadAny;
}
-static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, const char* name, char separator) {
- fprintf(fd, "%s=", name);
+static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, bool byName, char separator) {
const char* sep = "";
for (unsigned int i = 0; fields[i]; i++) {
- if (fields[i] >= LAST_PROCESSFIELD) {
- const DynamicColumn* column = DynamicColumn_lookup(columns, fields[i]);
- fprintf(fd, "%sDynamic(%s)", sep, column->name);
+ if (fields[i] < LAST_PROCESSFIELD && byName) {
+ const char* pName = toFieldName(columns, fields[i], NULL);
+ fprintf(fd, "%s%s", sep, pName);
+ } else if (fields[i] >= LAST_PROCESSFIELD && byName) {
+ bool enabled;
+ const char* pName = toFieldName(columns, fields[i], &enabled);
+ if (enabled)
+ fprintf(fd, "%sDynamic(%s)", sep, pName);
} else {
// This "-1" is for compatibility with the older enum format.
fprintf(fd, "%s%d", sep, (int) fields[i] - 1);
@@ -326,32 +582,51 @@ static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns
fputc(separator, fd);
}
-static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) {
+static void writeList(FILE* fd, char** list, int len, char separator) {
const char* sep = "";
- for (uint8_t i = 0; i < this->hColumns[column].len; i++) {
- fprintf(fd, "%s%s", sep, this->hColumns[column].names[i]);
+ for (int i = 0; i < len; i++) {
+ fprintf(fd, "%s%s", sep, list[i]);
sep = " ";
}
fputc(separator, fd);
}
+static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) {
+ if (this->hColumns[column].len) {
+ writeList(fd, this->hColumns[column].names, this->hColumns[column].len, separator);
+ } else {
+ fputc('!', fd);
+ fputc(separator, fd);
+ }
+}
+
static void writeMeterModes(const Settings* this, FILE* fd, char separator, unsigned int column) {
- const char* sep = "";
- for (uint8_t i = 0; i < this->hColumns[column].len; i++) {
- fprintf(fd, "%s%d", sep, this->hColumns[column].modes[i]);
- sep = " ";
+ if (this->hColumns[column].len) {
+ 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 = " ";
+ }
+ } else {
+ fputc('!', fd);
}
+
fputc(separator, fd);
}
int Settings_write(const Settings* this, bool onCrash) {
FILE* fd;
char separator;
+ char* tmpFilename = NULL;
if (onCrash) {
fd = stderr;
separator = ';';
} else {
- fd = fopen(this->filename, "w");
+ xAsprintf(&tmpFilename, "%s.tmp.XXXXXX", this->filename);
+ int fdtmp = mkstemp(tmpFilename);
+ if (fdtmp == -1)
+ return -errno;
+ fd = fdopen(fdtmp, "w");
if (fd == NULL)
return -errno;
separator = '\n';
@@ -368,19 +643,16 @@ int Settings_write(const Settings* this, bool onCrash) {
}
printSettingString("htop_version", VERSION);
printSettingInteger("config_reader_min_version", CONFIG_READER_MIN_VERSION);
- writeFields(fd, this->fields, this->dynamicColumns, "fields", separator);
- // This "-1" is for compatibility with the older enum format.
- printSettingInteger("sort_key", this->sortKey - 1);
- printSettingInteger("sort_direction", this->direction);
- printSettingInteger("tree_sort_key", this->treeSortKey - 1);
- printSettingInteger("tree_sort_direction", this->treeDirection);
+ fprintf(fd, "fields="); writeFields(fd, this->screens[0]->fields, this->dynamicColumns, false, separator);
printSettingInteger("hide_kernel_threads", this->hideKernelThreads);
printSettingInteger("hide_userland_threads", this->hideUserlandThreads);
+ printSettingInteger("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);
@@ -388,10 +660,8 @@ int Settings_write(const Settings* this, bool onCrash) {
printSettingInteger("find_comm_in_cmdline", this->findCommInCmdline);
printSettingInteger("strip_exe_from_cmdline", this->stripExeFromCmdline);
printSettingInteger("show_merged_command", this->showMergedCommand);
- printSettingInteger("tree_view", this->treeView);
- printSettingInteger("tree_view_always_by_pid", this->treeViewAlwaysByPID);
- printSettingInteger("all_branches_collapsed", this->allBranchesCollapsed);
printSettingInteger("header_margin", this->headerMargin);
+ printSettingInteger("screen_tabs", this->screenTabs);
printSettingInteger("detailed_cpu_time", this->detailedCPUTime);
printSettingInteger("cpu_count_from_one", this->countCPUsFromOne);
printSettingInteger("show_cpu_usage", this->showCPUUsage);
@@ -420,6 +690,40 @@ int Settings_write(const Settings* this, bool onCrash) {
writeMeterModes(this, fd, separator, i);
}
+ // Legacy compatibility with older versions of htop
+ printSettingInteger("tree_view", this->screens[0]->treeView);
+ // This "-1" is for compatibility with the older enum format.
+ printSettingInteger("sort_key", this->screens[0]->sortKey - 1);
+ printSettingInteger("tree_sort_key", this->screens[0]->treeSortKey - 1);
+ printSettingInteger("sort_direction", this->screens[0]->direction);
+ printSettingInteger("tree_sort_direction", this->screens[0]->treeDirection);
+ printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByPID);
+ printSettingInteger("all_branches_collapsed", this->screens[0]->allBranchesCollapsed);
+
+ for (unsigned int i = 0; i < this->nScreens; i++) {
+ ScreenSettings* ss = this->screens[i];
+ const char* sortKey = toFieldName(this->dynamicColumns, ss->sortKey, NULL);
+ const char* treeSortKey = toFieldName(this->dynamicColumns, ss->treeSortKey, NULL);
+
+ fprintf(fd, "screen:%s=", ss->heading);
+ writeFields(fd, ss->fields, this->dynamicColumns, true, separator);
+ if (ss->dynamic) {
+ printSettingString(".dynamic", ss->dynamic);
+ if (ss->sortKey && ss->sortKey != PID)
+ fprintf(fd, "%s=Dynamic(%s)%c", ".sort_key", sortKey, separator);
+ if (ss->treeSortKey && ss->treeSortKey != PID)
+ fprintf(fd, "%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(".sort_direction", ss->direction);
+ printSettingInteger(".tree_sort_direction", ss->treeDirection);
+ printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed);
+ }
+
#undef printSettingString
#undef printSettingInteger
@@ -434,27 +738,31 @@ int Settings_write(const Settings* this, bool onCrash) {
if (fclose(fd) != 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->dynamicScreens = dynamicScreens;
this->dynamicColumns = dynamicColumns;
+ this->dynamicMeters = dynamicMeters;
this->hLayout = HF_TWO_50_50;
this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
- this->sortKey = PERCENT_CPU;
- this->treeSortKey = PID;
- this->direction = -1;
- this->treeDirection = 1;
+
this->shadowOtherUsers = false;
this->showThreadNames = false;
this->hideKernelThreads = true;
this->hideUserlandThreads = false;
- this->treeView = false;
- this->allBranchesCollapsed = false;
+ this->hideRunningInContainer = false;
this->highlightBaseName = false;
this->highlightDeletedExe = true;
+ this->shadowDistPathPrefix = false;
this->highlightMegabytes = true;
this->detailedCPUTime = false;
this->countCPUsFromOne = false;
@@ -477,15 +785,9 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
#ifdef HAVE_LIBHWLOC
this->topologyAffinity = false;
#endif
- this->fields = xCalloc(LAST_PROCESSFIELD + 1, sizeof(ProcessField));
- // TODO: turn 'fields' into a Vector,
- // (and ProcessFields into proper objects).
- this->flags = 0;
- const ProcessField* defaults = Platform_defaultFields;
- for (int i = 0; defaults[i]; i++) {
- this->fields[i] = defaults[i];
- this->flags |= Process_fields[defaults[i]].flags;
- }
+
+ this->screens = xCalloc(Platform_numberOfDefaultScreens * sizeof(ScreenSettings*), 1);
+ this->nScreens = 0;
char* legacyDotfile = NULL;
const char* rcfile = getenv("HTOPRC");
@@ -493,9 +795,10 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
this->filename = xStrdup(rcfile);
} else {
const char* home = getenv("HOME");
- if (!home)
- home = "";
-
+ if (!home) {
+ const struct passwd* pw = getpwuid(getuid());
+ home = pw ? pw->pw_dir : "";
+ }
const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
char* configDir = NULL;
char* htopDir = NULL;
@@ -541,21 +844,29 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
ok = Settings_read(this, this->filename, initialCpuCount);
}
if (!ok) {
+ this->screenTabs = true;
this->changed = true;
ok = Settings_read(this, SYSCONFDIR "/htoprc", initialCpuCount);
}
if (!ok) {
Settings_defaultMeters(this, initialCpuCount);
+ Settings_defaultScreens(this);
}
+
+ this->ssIndex = 0;
+ this->ss = this->screens[this->ssIndex];
+
+ this->lastUpdate = 1;
+
return this;
}
-void Settings_invertSortOrder(Settings* this) {
+void ScreenSettings_invertSortOrder(ScreenSettings* this) {
int* attr = (this->treeView) ? &(this->treeDirection) : &(this->direction);
*attr = (*attr == 1) ? -1 : 1;
}
-void Settings_setSortKey(Settings* this, ProcessField sortKey) {
+void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) {
if (this->treeViewAlwaysByPID || !this->treeView) {
this->sortKey = sortKey;
this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
@@ -586,7 +897,7 @@ void Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout) {
} else if (newColumns < oldColumns) {
for (unsigned int i = newColumns; i < oldColumns; i++) {
if (this->hColumns[i].names) {
- for (uint8_t j = 0; j < this->hColumns[i].len; j++)
+ for (size_t j = 0; j < this->hColumns[i].len; j++)
free(this->hColumns[i].names[j]);
free(this->hColumns[i].names);
}
diff --git a/Settings.h b/Settings.h
index 20f583c..a7740ff 100644
--- a/Settings.h
+++ b/Settings.h
@@ -3,47 +3,72 @@
/*
htop - Settings.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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 "Row.h"
+#include "RowField.h"
#define DEFAULT_DELAY 15
-#define CONFIG_READER_MIN_VERSION 2
+#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 {
- uint8_t len;
+ size_t len;
char** names;
int* modes;
} MeterColumnSetting;
+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;
+ RowField sortKey;
+ RowField treeSortKey;
+ bool treeView;
+ bool treeViewAlwaysByPID;
+ bool allBranchesCollapsed;
+} ScreenSettings;
+
typedef struct Settings_ {
char* filename;
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;
+ unsigned int ssIndex;
+ ScreenSettings* ss;
- ProcessField* fields;
- uint32_t flags;
int colorScheme;
int delay;
- int direction;
- int treeDirection;
- ProcessField sortKey;
- ProcessField treeSortKey;
-
bool countCPUsFromOne;
bool detailedCPUTime;
bool showCPUUsage;
@@ -52,16 +77,15 @@ typedef struct Settings_ {
bool showCPUTemperature;
bool degreeFahrenheit;
#endif
- bool treeView;
- bool treeViewAlwaysByPID;
- bool allBranchesCollapsed;
bool showProgramPath;
bool shadowOtherUsers;
bool showThreadNames;
bool hideKernelThreads;
+ bool hideRunningInContainer;
bool hideUserlandThreads;
bool highlightBaseName;
bool highlightDeletedExe;
+ bool shadowDistPathPrefix;
bool highlightMegabytes;
bool highlightThreads;
bool highlightChanges;
@@ -72,6 +96,7 @@ typedef struct Settings_ {
bool updateProcessNames;
bool accountGuestInCPUMeter;
bool headerMargin;
+ bool screenTabs;
#ifdef HAVE_GETMOUSE
bool enableMouse;
#endif
@@ -81,17 +106,18 @@ typedef struct Settings_ {
#endif
bool changed;
+ uint64_t lastUpdate;
} Settings;
#define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu))
-static inline ProcessField Settings_getActiveSortKey(const Settings* this) {
+static inline RowField ScreenSettings_getActiveSortKey(const ScreenSettings* this) {
return (this->treeView)
- ? (this->treeViewAlwaysByPID ? PID : this->treeSortKey)
+ ? (this->treeViewAlwaysByPID ? 1 : this->treeSortKey)
: this->sortKey;
}
-static inline int Settings_getActiveDirection(const Settings* this) {
+static inline int ScreenSettings_getActiveDirection(const ScreenSettings* this) {
return this->treeView ? this->treeDirection : this->direction;
}
@@ -99,11 +125,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 Settings_invertSortOrder(Settings* this);
+void ScreenSettings_invertSortOrder(ScreenSettings* this);
-void Settings_setSortKey(Settings* this, ProcessField sortKey);
+void ScreenSettings_setSortKey(ScreenSettings* this, RowField sortKey);
void Settings_enableReadonly(void);
diff --git a/SignalsPanel.c b/SignalsPanel.c
index 055e3e6..28d307e 100644
--- a/SignalsPanel.c
+++ b/SignalsPanel.c
@@ -1,13 +1,15 @@
/*
htop - SignalsPanel.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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)
-#include <signal.h>
#include <stdbool.h>
#include "FunctionBar.h"
@@ -18,15 +20,14 @@ in the source distribution for its full text.
#include "XUtils.h"
-Panel* SignalsPanel_new() {
+Panel* SignalsPanel_new(int preSelectedSignal) {
Panel* this = Panel_new(1, 1, 1, 1, Class(ListItem), true, FunctionBar_newEnterEsc("Send ", "Cancel "));
- const int defaultSignal = SIGTERM;
int defaultPosition = 15;
unsigned int i;
for (i = 0; i < Platform_numberOfSignals; i++) {
Panel_set(this, i, (Object*) ListItem_new(Platform_signals[i].name, Platform_signals[i].number));
// signal 15 is not always the 15th signal in the table
- if (Platform_signals[i].number == defaultSignal) {
+ if (Platform_signals[i].number == preSelectedSignal) {
defaultPosition = i;
}
}
diff --git a/SignalsPanel.h b/SignalsPanel.h
index 0dfe24c..20fb4a6 100644
--- a/SignalsPanel.h
+++ b/SignalsPanel.h
@@ -3,18 +3,24 @@
/*
htop - SignalsPanel.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#include "Panel.h"
+#ifndef HTOP_SOLARIS
+#include <signal.h>
+#endif
+
typedef struct SignalItem_ {
const char* name;
int number;
} SignalItem;
-Panel* SignalsPanel_new(void);
+#define SIGNALSPANEL_INITSELECTEDSIGNAL SIGTERM
+
+Panel* SignalsPanel_new(int preSelectedSignal);
#endif
diff --git a/SwapMeter.c b/SwapMeter.c
index 63f58c5..1055a6e 100644
--- a/SwapMeter.c
+++ b/SwapMeter.c
@@ -1,7 +1,7 @@
/*
htop - SwapMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 9c4e312..94b8dc8 100644
--- a/SwapMeter.h
+++ b/SwapMeter.h
@@ -3,12 +3,18 @@
/*
htop - SwapMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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/SysArchMeter.c b/SysArchMeter.c
index 64b96c9..1985caa 100644
--- a/SysArchMeter.c
+++ b/SysArchMeter.c
@@ -1,7 +1,7 @@
/*
htop - SysArchMeter.c
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/SysArchMeter.h b/SysArchMeter.h
index fa6adfe..50b3869 100644
--- a/SysArchMeter.h
+++ b/SysArchMeter.h
@@ -3,7 +3,7 @@
/*
htop - SysArchMeter.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#include "Meter.h"
diff --git a/TESTPLAN b/TESTPLAN
index 88fe039..b6ddfa6 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 0000000..d807ce5
--- /dev/null
+++ b/Table.c
@@ -0,0 +1,372 @@
+/*
+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_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)");
+ }
+ }
+}
+
+// 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 0000000..fa85fd6
--- /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 29d3dd1..aa41e63 100644
--- a/TasksMeter.c
+++ b/TasksMeter.c
@@ -1,16 +1,19 @@
/*
htop - TasksMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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/TasksMeter.h b/TasksMeter.h
index 4ae6ce6..9f9ba63 100644
--- a/TasksMeter.h
+++ b/TasksMeter.h
@@ -3,7 +3,7 @@
/*
htop - TasksMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/TraceScreen.c b/TraceScreen.c
index 93981c5..8a69b67 100644
--- a/TraceScreen.c
+++ b/TraceScreen.c
@@ -1,7 +1,7 @@
/*
htop - TraceScreen.c
(C) 2005-2006 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -10,6 +10,7 @@ in the source distribution for its full text.
#include "TraceScreen.h"
#include <assert.h>
+#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
@@ -47,7 +48,9 @@ void TraceScreen_delete(Object* cast) {
TraceScreen* this = (TraceScreen*) cast;
if (this->child > 0) {
kill(this->child, SIGTERM);
- waitpid(this->child, NULL, 0);
+ while (waitpid(this->child, NULL, 0) == -1)
+ if (errno != EINTR)
+ break;
}
if (this->strace) {
@@ -59,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) {
@@ -86,12 +89,26 @@ bool TraceScreen_forkTracer(TraceScreen* this) {
close(fdpair[1]);
char buffer[32] = {0};
- xSnprintf(buffer, sizeof(buffer), "%d", this->super.process->pid);
- execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, NULL);
-
- // Should never reach here, unless execlp fails ...
- const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH.";
- (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);
}
@@ -159,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/TraceScreen.h b/TraceScreen.h
index fd34cee..7b2101e 100644
--- a/TraceScreen.h
+++ b/TraceScreen.h
@@ -3,7 +3,7 @@
/*
htop - TraceScreen.h
(C) 2005-2006 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/UptimeMeter.c b/UptimeMeter.c
index 9036e53..622deda 100644
--- a/UptimeMeter.c
+++ b/UptimeMeter.c
@@ -1,10 +1,12 @@
/*
htop - UptimeMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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/UptimeMeter.h b/UptimeMeter.h
index 36a5d88..5a852ad 100644
--- a/UptimeMeter.h
+++ b/UptimeMeter.h
@@ -3,7 +3,7 @@
/*
htop - UptimeMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/UsersTable.c b/UsersTable.c
index fdbfd68..6586a4b 100644
--- a/UsersTable.c
+++ b/UsersTable.c
@@ -1,7 +1,7 @@
/*
htop - UsersTable.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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/UsersTable.h b/UsersTable.h
index ef7d0bb..ecfd214 100644
--- a/UsersTable.h
+++ b/UsersTable.h
@@ -3,7 +3,7 @@
/*
htop - UsersTable.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/Vector.c b/Vector.c
index 40e6203..0e08c65 100644
--- a/Vector.c
+++ b/Vector.c
@@ -1,10 +1,12 @@
/*
htop - Vector.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -29,6 +31,8 @@ Vector* Vector_new(const ObjectClass* type, bool owner, int size) {
this->items = 0;
this->type = type;
this->owner = owner;
+ this->dirty_index = -1;
+ this->dirty_count = 0;
return this;
}
@@ -44,31 +48,33 @@ void Vector_delete(Vector* this) {
free(this);
}
+static inline bool Vector_isDirty(const Vector* this) {
+ if (this->dirty_count > 0) {
+ assert(0 <= this->dirty_index && this->dirty_index < this->items);
+ assert(this->dirty_count <= this->items);
+ return true;
+ }
+ assert(this->dirty_index == -1);
+ return false;
+}
+
#ifndef NDEBUG
static bool Vector_isConsistent(const Vector* this) {
assert(this->items <= this->arraySize);
-
- if (this->owner) {
- for (int i = 0; i < this->items; i++) {
- if (!this->array[i]) {
- return false;
- }
- }
- }
+ assert(!Vector_isDirty(this));
return true;
}
-unsigned int Vector_count(const Vector* this) {
- unsigned int items = 0;
+bool Vector_countEquals(const Vector* this, unsigned int expectedCount) {
+ unsigned int n = 0;
for (int i = 0; i < this->items; i++) {
if (this->array[i]) {
- items++;
+ n++;
}
}
- assert(items == (unsigned int)this->items);
- return items;
+ return n == expectedCount;
}
Object* Vector_get(const Vector* this, int idx) {
@@ -88,13 +94,16 @@ int Vector_size(const Vector* this) {
void Vector_prune(Vector* this) {
assert(Vector_isConsistent(this));
if (this->owner) {
- for (int i = 0; i < this->items; i++)
+ for (int i = 0; i < this->items; i++) {
if (this->array[i]) {
Object_delete(this->array[i]);
- //this->array[i] = NULL;
}
+ }
}
this->items = 0;
+ this->dirty_index = -1;
+ this->dirty_count = 0;
+ memset(this->array, '\0', this->arraySize * sizeof(Object*));
}
//static int comparisons = 0;
@@ -185,15 +194,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));
}
@@ -208,7 +215,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]));
@@ -227,7 +234,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;
}
@@ -242,6 +249,61 @@ Object* Vector_remove(Vector* this, int idx) {
}
}
+Object* Vector_softRemove(Vector* this, int idx) {
+ assert(idx >= 0 && idx < this->items);
+
+ Object* removed = this->array[idx];
+ assert(removed);
+ if (removed) {
+ this->array[idx] = NULL;
+
+ this->dirty_count++;
+ if (this->dirty_index < 0 || idx < this->dirty_index) {
+ this->dirty_index = idx;
+ }
+
+ if (this->owner) {
+ Object_delete(removed);
+ return NULL;
+ }
+ }
+
+ return removed;
+}
+
+void Vector_compact(Vector* this) {
+ if (!Vector_isDirty(this)) {
+ return;
+ }
+
+ const int size = this->items;
+ assert(0 <= this->dirty_index && this->dirty_index < size);
+ assert(this->array[this->dirty_index] == NULL);
+
+ int idx = this->dirty_index;
+
+ // one deletion: use memmove, which should be faster
+ if (this->dirty_count == 1) {
+ memmove(&this->array[idx], &this->array[idx + 1], (this->items - idx - 1) * sizeof(this->array[0]));
+ this->array[this->items - 1] = NULL;
+ } else {
+ // 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;
+ this->dirty_index = -1;
+ this->dirty_count = 0;
+
+ assert(Vector_isConsistent(this));
+}
+
void Vector_moveUp(Vector* this, int idx) {
assert(idx >= 0 && idx < this->items);
assert(Vector_isConsistent(this));
@@ -272,14 +334,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;
@@ -329,11 +392,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/Vector.h b/Vector.h
index 5347ad7..b7b3903 100644
--- a/Vector.h
+++ b/Vector.h
@@ -3,7 +3,7 @@
/*
htop - Vector.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -22,6 +22,11 @@ typedef struct Vector_ {
int arraySize;
int growthRate;
int items;
+ /* lowest index of a pending soft remove/delete operation,
+ used to speed up compaction */
+ int dirty_index;
+ /* count of soft deletes, required for Vector_count to work in debug mode */
+ int dirty_count;
bool owner;
} Vector;
@@ -44,6 +49,15 @@ Object* Vector_take(Vector* this, int idx);
Object* Vector_remove(Vector* this, int idx);
+/* Vector_softRemove marks the item at index idx for deletion without
+ reclaiming any space. If owned, the item is immediately freed.
+
+ Vector_compact must be called to reclaim space.*/
+Object* Vector_softRemove(Vector* this, int idx);
+
+/* Vector_compact reclaims space free'd up by Vector_softRemove, if any. */
+void Vector_compact(Vector* this);
+
void Vector_moveUp(Vector* this, int idx);
void Vector_moveDown(Vector* this, int idx);
@@ -54,7 +68,11 @@ void Vector_set(Vector* this, int idx, void* data_);
Object* Vector_get(const Vector* this, int idx);
int Vector_size(const Vector* this);
-unsigned int Vector_count(const Vector* this);
+
+/* Vector_countEquals returns true if the number of non-NULL items
+ in the Vector is equal to expectedCount. This is only for debugging
+ and consistency checks. */
+bool Vector_countEquals(const Vector* this, unsigned int expectedCount);
#else /* NDEBUG */
diff --git a/XUtils.c b/XUtils.c
index c589d78..160386c 100644
--- a/XUtils.c
+++ b/XUtils.c
@@ -1,7 +1,7 @@
/*
htop - StringUtils.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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();
@@ -94,13 +96,30 @@ void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size)
return ret;
}
-inline bool String_contains_i(const char* s1, const char* s2) {
- return strcasestr(s1, s2) != NULL;
+inline bool String_contains_i(const char* s1, const char* s2, bool multi) {
+ // we have a multi-string search term, handle as special case for performance reasons
+ if (multi && strstr(s2, "|")) {
+ size_t nNeedles;
+ char** needles = String_split(s2, '|', &nNeedles);
+ for (size_t i = 0; i < nNeedles; i++) {
+ if (strcasestr(s1, needles[i]) != NULL) {
+ String_freeArray(needles);
+ return true;
+ }
+ }
+ String_freeArray(needles);
+ return false;
+ } else {
+ return strcasestr(s1, s2) != NULL;
+ }
}
char* String_cat(const char* s1, const char* s2) {
const size_t l1 = strlen(s1);
const size_t l2 = strlen(s2);
+ if (SIZE_MAX - l1 <= l2) {
+ fail();
+ }
char* out = xMalloc(l1 + l2 + 1);
memcpy(out, s1, l1);
memcpy(out + l1, s2, l2);
@@ -122,10 +141,10 @@ char* String_trim(const char* in) {
}
char** String_split(const char* s, char sep, size_t* n) {
- const unsigned int rate = 10;
+ const size_t rate = 10;
char** out = xCalloc(rate, sizeof(char*));
size_t ctr = 0;
- unsigned int blocks = rate;
+ size_t blocks = rate;
const char* where;
while ((where = strchr(s, sep)) != NULL) {
size_t size = (size_t)(where - s);
@@ -160,36 +179,9 @@ void String_freeArray(char** s) {
free(s);
}
-char* String_getToken(const char* line, const unsigned short int numMatch) {
- const size_t len = strlen(line);
- char inWord = 0;
- unsigned short int count = 0;
- char match[50];
-
- size_t foundCount = 0;
-
- for (size_t i = 0; i < len; i++) {
- char lastState = inWord;
- inWord = line[i] == ' ' ? 0 : 1;
-
- if (lastState == 0 && inWord == 1)
- count++;
-
- if (inWord == 1) {
- if (count == numMatch && line[i] != ' ' && line[i] != '\0' && line[i] != '\n' && line[i] != (char)EOF) {
- match[foundCount] = line[i];
- foundCount++;
- }
- }
- }
-
- match[foundCount] = '\0';
- return xStrdup(match);
-}
-
char* String_readLine(FILE* fd) {
- const unsigned int step = 1024;
- unsigned int bufSize = step;
+ const size_t step = 1024;
+ size_t bufSize = step;
char* buffer = xMalloc(step + 1);
char* at = buffer;
for (;;) {
@@ -275,6 +267,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);
@@ -291,10 +284,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;
@@ -323,3 +319,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 13f4be4..68b9483 100644
--- a/XUtils.h
+++ b/XUtils.h
@@ -3,13 +3,19 @@
/*
htop - StringUtils.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+(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,43 +42,82 @@ 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 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;
}
+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_getToken(const char* line, unsigned short int numMatch) ATTR_MALLOC;
-
+ATTR_NONNULL
char* String_readLine(FILE* fd) 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);
+
+/* 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 e93afbd..e4df238 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,13 +6,13 @@
# ----------------------------------------------------------------------
AC_PREREQ([2.69])
-AC_INIT([htop], [3.1.0], [htop@groups.io], [], [https://htop.dev/])
+AC_INIT([htop], [3.3.0], [htop@groups.io], [], [https://htop.dev/])
AC_CONFIG_SRCDIR([htop.c])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_HEADERS([config.h])
-AC_CANONICAL_TARGET
+AC_CANONICAL_HOST
AM_INIT_AUTOMAKE([-Wall std-options subdir-objects])
# ----------------------------------------------------------------------
@@ -22,7 +22,7 @@ AM_INIT_AUTOMAKE([-Wall std-options subdir-objects])
# Checks for platform.
# ----------------------------------------------------------------------
-case "$target_os" in
+case "$host_os" in
linux*|gnu*)
my_htop_platform=linux
AC_DEFINE([HTOP_LINUX], [], [Building for Linux.])
@@ -60,6 +60,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 +74,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 +153,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 +171,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 +197,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,7 +272,7 @@ 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()])])
@@ -253,19 +313,26 @@ AC_SEARCH_LIBS([clock_gettime], [rt])
AC_CHECK_FUNCS([ \
clock_gettime \
+ dladdr \
faccessat \
fstatat \
host_get_clock_service \
memfd_create\
openat \
readlinkat \
+ sched_getscheduler \
+ sched_setscheduler \
+ strchrnul \
])
-# Add -lexecinfo if needed
-AC_SEARCH_LIBS([backtrace], [execinfo])
-
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
@@ -291,15 +358,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
])
@@ -340,6 +407,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])
@@ -355,13 +427,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
@@ -398,7 +475,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
@@ -409,6 +486,46 @@ if test "x$enable_affinity" = xyes; then
fi
+AC_ARG_ENABLE([unwind],
+ [AS_HELP_STRING([--enable-unwind],
+ [enable unwind support for printing backtraces; requires libunwind @<:@default=check@:>@])],
+ [],
+ [enable_unwind=check])
+case "$enable_unwind" in
+ check)
+ enable_unwind=yes
+ if test "$enable_static" = yes; then
+ AC_CHECK_LIB([lzma], [lzma_index_buffer_decode])
+ fi
+ AC_CHECK_LIB([unwind], [backtrace], [], [enable_unwind=no])
+ AC_CHECK_HEADERS([libunwind.h], [], [
+ old_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -I/usr/include/libunwind"
+ AC_CHECK_HEADERS([libunwind/libunwind.h], [], [
+ enable_unwind=no
+ CFLAGS="$old_CFLAGS"
+ ])
+ ])
+ ;;
+ no)
+ ;;
+ yes)
+ AC_CHECK_LIB([unwind], [backtrace], [], [AC_MSG_ERROR([can not find required library libunwind])])
+ AC_CHECK_HEADERS([libunwind.h], [], [
+ 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])])
+ ])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_unwind' for --enable-unwind])
+ ;;
+esac
+if test "x$enable_unwind" = xno; then
+ # Fall back to backtrace(3) and add -lexecinfo if needed
+ AC_SEARCH_LIBS([backtrace], [execinfo])
+fi
+
+
AC_ARG_ENABLE([hwloc],
[AS_HELP_STRING([--enable-hwloc],
[enable hwloc support for CPU affinity; disables affinity support; requires libhwloc @<:@default=no@:>@])],
@@ -418,14 +535,27 @@ case "$enable_hwloc" in
no)
;;
yes)
- AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])])
- AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])])
+ m4_ifdef([PKG_PROG_PKG_CONFIG], [
+ PKG_PROG_PKG_CONFIG()
+ PKG_CHECK_MODULES(HWLOC, hwloc, [
+ 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])])
+ ])
+ ], [
+ AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])])
+ AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])])
+ ])
;;
*)
AC_MSG_ERROR([bad value '$enable_hwloc' for --enable-hwloc])
;;
esac
+
AC_ARG_WITH([os-release],
[AS_HELP_STRING([--with-os-release=FILE],
[location of an os-release file @<:@default=/etc/os-release@:>@])],
@@ -533,7 +663,7 @@ case "$enable_delayacct" in
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"
+ AM_CFLAGS="$AM_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
@@ -548,7 +678,7 @@ case "$enable_delayacct" in
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"
+ AM_CFLAGS="$AM_CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
], [
@@ -599,7 +729,7 @@ fi
# Checks for compiler warnings.
# ----------------------------------------------------------------------
-AM_CFLAGS="\
+AM_CFLAGS="$AM_CFLAGS\
-Wall\
-Wcast-align\
-Wcast-qual\
@@ -617,11 +747,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],
[
@@ -639,7 +764,9 @@ AS_VAR_IF(CACHEVAR,yes,
AS_VAR_POPDEF([CACHEVAR])dnl
])dnl AX_CHECK_COMPILE_FLAGS
-AX_CHECK_COMPILE_FLAG([-Wnull-dereference], [AM_CFLAGS="$AM_CFLAGS -Wnull-dereference"], , [-Werror])
+AX_CHECK_COMPILE_FLAG([-Wextra-semi-stmt], [AM_CFLAGS="$AM_CFLAGS -Wextra-semi-stmt"], , [-Werror=unknown-warning-option]) dnl the autoconf check itself generates -Wextra-semi-stmt
+AX_CHECK_COMPILE_FLAG([-Wimplicit-int-conversion], [AM_CFLAGS="$AM_CFLAGS -Wimplicit-int-conversion"], , [-Werror])
+AX_CHECK_COMPILE_FLAG([-Wnull-dereference], [AM_CFLAGS="$AM_CFLAGS -Wnull-dereference"], , [-Werror])
AC_ARG_ENABLE([werror],
[AS_HELP_STRING([--enable-werror],
@@ -657,6 +784,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
@@ -672,7 +815,7 @@ AC_SUBST([AM_CPPFLAGS])
# We're done, let's go!
# ----------------------------------------------------------------------
-AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2021 htop dev team."], [Copyright message.])
+AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2024 htop dev team."], [Copyright message.])
AM_CONDITIONAL([HTOP_LINUX], [test "$my_htop_platform" = linux])
AM_CONDITIONAL([HTOP_FREEBSD], [test "$my_htop_platform" = freebsd])
@@ -715,6 +858,7 @@ AC_MSG_RESULT([
(Linux) capabilities: $enable_capabilities
unicode: $enable_unicode
affinity: $enable_affinity
+ unwind: $enable_unwind
hwloc: $enable_hwloc
debug: $enable_debug
static: $enable_static
diff --git a/darwin/DarwinMachine.c b/darwin/DarwinMachine.c
new file mode 100644
index 0000000..582d496
--- /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 0000000..3135b58
--- /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 20e91f7..1e315eb 100644
--- a/darwin/DarwinProcess.c
+++ b/darwin/DarwinProcess.c
@@ -1,7 +1,7 @@
/*
htop - DarwinProcess.c
(C) 2015 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -12,9 +12,11 @@ in the source distribution for its full text.
#include <stdlib.h>
#include <string.h>
#include <mach/mach.h>
+#include <sys/dirent.h>
#include "CRT.h"
#include "Process.h"
+#include "darwin/DarwinMachine.h"
#include "darwin/Platform.h"
@@ -26,7 +28,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, .pidColumn = true, },
[PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, .pidColumn = true, },
[SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .flags = 0, .pidColumn = true, },
- [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, },
+ [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = PROCESS_FLAG_TTY, },
[TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, .pidColumn = true, },
[MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, },
[MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, },
@@ -37,11 +39,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
+ [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_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, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
@@ -50,10 +52,10 @@ 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;
@@ -70,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);
}
@@ -145,7 +150,7 @@ static void DarwinProcess_updateCmdLine(const struct kinfo_proc* k, Process* pro
}
/* Allocate space for the arguments. */
- procargs = (char*)xMalloc(argmax);
+ procargs = (char*)malloc(argmax);
if ( procargs == NULL ) {
goto ERROR_A;
}
@@ -276,15 +281,28 @@ static long long int nanosecondsToCentiseconds(uint64_t nanoseconds) {
return nanoseconds / nanoseconds_per_second * centiseconds_per_second;
}
+static char* DarwinProcess_getDevname(dev_t dev) {
+ if (dev == NODEV) {
+ return NULL;
+ }
+ char buf[sizeof("/dev/") + MAXNAMLEN];
+ char* name = devname_r(dev, S_IFCHR, buf, MAXNAMLEN);
+ if (name) {
+ return xStrdup(name);
+ }
+ return NULL;
+}
+
void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) {
DarwinProcess* dp = (DarwinProcess*)proc;
+ 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
@@ -297,24 +315,17 @@ 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;
-
proc->tty_nr = ps->kp_eproc.e_tdev;
- const char* name = (ps->kp_eproc.e_tdev != NODEV) ? devname(ps->kp_eproc.e_tdev, S_IFCHR) : NULL;
- if (!name) {
- free(proc->tty_name);
- proc->tty_name = NULL;
- } else {
- free_and_xStrdup(&proc->tty_name, name);
- }
+ proc->tty_name = NULL;
proc->starttime_ctime = ep->p_starttime.tv_sec;
Process_fillStarttimeBuffer(proc);
@@ -322,25 +333,44 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
DarwinProcess_updateExe(ep->p_pid, proc);
DarwinProcess_updateCmdLine(ps, proc);
- if (proc->settings->flags & PROCESS_FLAG_CWD) {
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
DarwinProcess_updateCwd(ep->p_pid, proc);
}
}
+ if (proc->tty_name == NULL && (dev_t)proc->tty_nr != NODEV) {
+ /* The call to devname() is extremely expensive (due to lstat)
+ * and represents ~95% of htop's CPU usage when there is high
+ * process turnover.
+ *
+ * To mitigate this we only fetch TTY information if the TTY
+ * field is enabled in the settings.
+ */
+ if (settings->ss->flags & PROCESS_FLAG_TTY) {
+ proc->tty_name = DarwinProcess_getDevname(proc->tty_nr);
+ if (!proc->tty_name) {
+ /* devname failed: prevent us from calling it again */
+ proc->tty_nr = NODEV;
+ }
+ }
+ }
+
/* Mutable information */
proc->nice = ep->p_nice;
proc->priority = ep->p_priority;
- proc->state = (ep->p_stat == SZOMB) ? 'Z' : '?';
+ 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);
@@ -354,6 +384,7 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList*
} else {
proc->super.percent_cpu = 0.0;
}
+ Process_updateCPUFieldWidths(proc->super.percent_cpu);
proc->super.time = nanosecondsToCentiseconds(total_current_time_ns);
proc->super.nlwp = pti.pti_threadnum;
@@ -361,15 +392,15 @@ 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;
}
}
@@ -386,12 +417,12 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) {
return;
}
- if (proc->state == 'Z') {
+ if (proc->state == ZOMBIE) {
return;
}
task_t port;
- ret = task_for_pid(mach_task_self(), proc->pid, &port);
+ ret = task_for_pid(mach_task_self(), Process_getPid(proc), &port);
if (ret != KERN_SUCCESS) {
dp->taskAccess = false;
return;
@@ -430,25 +461,32 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) {
vm_deallocate(mach_task_self(), (vm_address_t) thread_list, sizeof(thread_port_array_t) * thread_count);
mach_port_deallocate(mach_task_self(), port);
- char state = '?';
+ /* Taken from: https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/thread_info.h#L129 */
switch (run_state) {
- case TH_STATE_RUNNING: state = 'R'; break;
- case TH_STATE_STOPPED: state = 'S'; break;
- case TH_STATE_WAITING: state = 'W'; break;
- case TH_STATE_UNINTERRUPTIBLE: state = 'U'; break;
- case TH_STATE_HALTED: state = 'H'; break;
+ 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;
}
- proc->state = 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 0a49eea..496b179 100644
--- a/darwin/DarwinProcess.h
+++ b/darwin/DarwinProcess.h
@@ -3,14 +3,17 @@
/*
htop - DarwinProcess.h
(C) 2015 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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
typedef struct DarwinProcess_ {
Process super;
@@ -25,13 +28,13 @@ 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.
diff --git a/darwin/DarwinProcessList.c b/darwin/DarwinProcessList.c
deleted file mode 100644
index 7dd86ff..0000000
--- a/darwin/DarwinProcessList.c
+++ /dev/null
@@ -1,242 +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 "generic/openzfs_sysctl.h"
-#include "zfs/ZfsArcStats.h"
-
-
-struct kern {
- short int version[3];
-};
-
-static void GetKernelVersion(struct kern* k) {
- static short int version_[3] = {0};
- if (!version_[0]) {
- // just in case it fails someday
- version_[0] = version_[1] = version_[2] = -1;
- char str[256] = {0};
- size_t size = sizeof(str);
- int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);
- if (ret == 0) {
- sscanf(str, "%hd.%hd.%hd", &version_[0], &version_[1], &version_[2]);
- }
- }
- memcpy(k->version, version_, sizeof(version_));
-}
-
-/* compare the given os version with the one installed returns:
-0 if equals the installed version
-positive value if less than the installed version
-negative value if more than the installed version
-*/
-static int CompareKernelVersion(short int major, short int minor, short int component) {
- struct kern k;
- GetKernelVersion(&k);
-
- if (k.version[0] != major) {
- return k.version[0] - major;
- }
- if (k.version[1] != minor) {
- return k.version[1] - minor;
- }
- if (k.version[2] != component) {
- return k.version[2] - component;
- }
-
- return 0;
-}
-
-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 = ! ( CompareKernelVersion(17, 0, 0) >= 0 && CompareKernelVersion(17, 5, 0) < 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 af1140b..0000000
--- 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 0000000..850b503
--- /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);
+ }
+
+ 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 0000000..7467bfd
--- /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 93262bb..387910e 100644
--- a/darwin/Platform.c
+++ b/darwin/Platform.c
@@ -2,7 +2,7 @@
htop - darwin/Platform.c
(C) 2014 Hisham H. Muhammad
(C) 2015 David C. Hunt
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 <sys/_types/_mach_port_t.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,7 +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"
@@ -42,12 +58,21 @@ in the source distribution for its full text.
#include <mach/clock.h>
#include <mach/mach.h>
#endif
+
#ifdef HAVE_MACH_MACH_TIME_H
#include <mach/mach_time.h>
#endif
-const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+};
+
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
@@ -116,6 +141,9 @@ const MeterClass* const Platform_meterTypes[] = {
&RightCPUs8Meter_class,
&ZfsArcMeter_class,
&ZfsCompressedArcMeter_class,
+ &DiskIOMeter_class,
+ &NetworkIOMeter_class,
+ &FileDescriptorMeter_class,
&BlankMeter_class,
NULL
};
@@ -124,16 +152,11 @@ static double Platform_nanosecondsPerMachTick = 1.0;
static double Platform_nanosecondsPerSchedulerTick = -1;
-void Platform_init(void) {
- // Check if we can determine the timebase used on this system.
- // If the API is unavailable assume we get our timebase in nanoseconds.
-#ifdef HAVE_MACH_TIMEBASE_INFO
- mach_timebase_info_data_t info;
- mach_timebase_info(&info);
- Platform_nanosecondsPerMachTick = (double)info.numer / (double)info.denom;
-#else
- Platform_nanosecondsPerMachTick = 1.0;
-#endif
+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();
// Determine the number of scheduler clock ticks per second
errno = 0;
@@ -145,6 +168,19 @@ void 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;
}
// Converts ticks in the Mach "timebase" to nanoseconds.
@@ -168,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);
@@ -196,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];
@@ -226,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 */
@@ -255,16 +291,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) {
@@ -274,19 +311,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) {
@@ -340,27 +379,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, /* protocal 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 e7647db..f67db8f 100644
--- a/darwin/Platform.h
+++ b/darwin/Platform.h
@@ -4,7 +4,7 @@
htop - darwin/Platform.h
(C) 2014 Hisham H. Muhammad
(C) 2015 David C. Hunt
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -19,13 +19,16 @@ in the source distribution for its full text.
#include "NetworkIOMeter.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
#include "darwin/DarwinProcess.h"
#include "generic/gettime.h"
#include "generic/hostname.h"
#include "generic/uname.h"
-extern const ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -33,7 +36,7 @@ extern const unsigned int Platform_numberOfSignals;
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
// Converts ticks in the Mach "timebase" to nanoseconds.
// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant.
@@ -51,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);
@@ -65,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);
@@ -87,8 +90,8 @@ static inline void Platform_getRelease(char** string) {
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
-static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
- return false;
+static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return STATUS_ERROR_EXIT;
}
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
@@ -97,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) { }
@@ -107,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
new file mode 100644
index 0000000..a4ea82b
--- /dev/null
+++ b/darwin/PlatformHelpers.c
@@ -0,0 +1,125 @@
+/*
+htop - darwin/PlatformHelpers.c
+(C) 2018 Pierre Malhaire, 2020-2021 htop dev team, 2021 Alexander Momchilov
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "darwin/PlatformHelpers.h"
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/sysctl.h>
+
+#include "CRT.h"
+
+#ifdef HAVE_MACH_MACH_TIME_H
+#include <mach/mach_time.h>
+#endif
+
+
+void Platform_GetKernelVersion(KernelVersion* k) {
+ static KernelVersion cachedKernelVersion;
+
+ if (!cachedKernelVersion.major) {
+ // just in case it fails someday
+ cachedKernelVersion = (KernelVersion) { -1, -1, -1 };
+ char str[256] = {0};
+ size_t size = sizeof(str);
+ int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);
+ if (ret == 0) {
+ sscanf(str, "%hd.%hd.%hd", &cachedKernelVersion.major, &cachedKernelVersion.minor, &cachedKernelVersion.patch);
+ }
+ }
+ memcpy(k, &cachedKernelVersion, sizeof(cachedKernelVersion));
+}
+
+int Platform_CompareKernelVersion(KernelVersion v) {
+ struct KernelVersion actualVersion;
+ Platform_GetKernelVersion(&actualVersion);
+
+ if (actualVersion.major != v.major) {
+ return actualVersion.major - v.major;
+ }
+ if (actualVersion.minor != v.minor) {
+ return actualVersion.minor - v.minor;
+ }
+ if (actualVersion.patch != v.patch) {
+ return actualVersion.patch - v.patch;
+ }
+
+ return 0;
+}
+
+bool Platform_KernelVersionIsBetween(KernelVersion lowerBound, KernelVersion upperBound) {
+ return 0 <= Platform_CompareKernelVersion(lowerBound)
+ && Platform_CompareKernelVersion(upperBound) < 0;
+}
+
+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"
+ "errno: %i, %s\n", errno, strerror(errno));
+
+ String_safeStrncpy(cpuBrandString, "UNKNOWN!", cpuBrandStringSize);
+ }
+}
+
+// Adapted from https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment
+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;
+
+ fprintf(stderr,
+ "WARN: Could not determine if this process was running in a translation environment like Rosetta 2.\n"
+ "Assuming that we're not.\n"
+ "errno: %i, %s\n", errno, strerror(errno));
+
+ return false;
+ }
+ return ret;
+}
+
+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
+ return 1.0;
+#else
+ mach_timebase_info_data_t info;
+
+ /* WORKAROUND for `mach_timebase_info` giving incorrect values on M1 under Rosetta 2.
+ * rdar://FB9546856 https://openradar.appspot.com/radar?id=5055988478509056
+ *
+ * We don't know exactly what feature/attribute of the M1 chip causes this mistake under Rosetta 2.
+ * Until we have more Apple ARM chips to compare against, the best we can do is special-case
+ * the "Apple M1" chip specifically when running under Rosetta 2.
+ */
+
+ char cpuBrandString[1024] = "";
+ Platform_getCPUBrandString(cpuBrandString, sizeof(cpuBrandString));
+
+ bool isRunningUnderRosetta2 = Platform_isRunningTranslated();
+
+ // Kernel version 20.0.0 is macOS 11.0 (Big Sur)
+ bool isBuggedVersion = Platform_KernelVersionIsBetween((KernelVersion) {20, 0, 0}, (KernelVersion) {999, 999, 999});
+
+ if (isRunningUnderRosetta2 && String_eq(cpuBrandString, "Apple M1") && isBuggedVersion) {
+ // In this case `mach_timebase_info` provides the wrong value, so we hard-code the correct factor,
+ // as determined from `mach_timebase_info` when the process running natively.
+ info = (mach_timebase_info_data_t) { .numer = 125, .denom = 3 };
+ } else {
+ // No workarounds needed, use the OS-provided value.
+ mach_timebase_info(&info);
+ }
+
+ return (double)info.numer / (double)info.denom;
+#endif
+}
diff --git a/darwin/PlatformHelpers.h b/darwin/PlatformHelpers.h
new file mode 100644
index 0000000..45aea1a
--- /dev/null
+++ b/darwin/PlatformHelpers.h
@@ -0,0 +1,40 @@
+#ifndef HEADER_PlatformHelpers
+#define HEADER_PlatformHelpers
+/*
+htop - darwin/PlatformHelpers.h
+(C) 2018 Pierre Malhaire, 2020-2022 htop dev team, 2021 Alexander Momchilov
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+
+typedef struct KernelVersion {
+ short int major;
+ short int minor;
+ short int patch;
+} KernelVersion;
+
+void Platform_GetKernelVersion(KernelVersion* k);
+
+/* compare the given os version with the one installed returns:
+0 if equals the installed version
+positive value if less than the installed version
+negative value if more than the installed version
+*/
+int Platform_CompareKernelVersion(KernelVersion v);
+
+// lowerBound <= currentVersion < upperBound
+bool Platform_KernelVersionIsBetween(KernelVersion lowerBound, KernelVersion upperBound);
+
+double Platform_calculateNanosecondsPerMachTick(void);
+
+void Platform_getCPUBrandString(char* cpuBrandString, size_t cpuBrandStringSize);
+
+bool Platform_isRunningTranslated(void);
+
+double Platform_calculateNanosecondsPerMachTick(void);
+
+#endif
diff --git a/darwin/ProcessField.h b/darwin/ProcessField.h
index 25dbb45..05fbc3b 100644
--- a/darwin/ProcessField.h
+++ b/darwin/ProcessField.h
@@ -3,7 +3,7 @@
/*
htop - darwin/ProcessField.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/docs/images/screenshot.png b/docs/images/screenshot.png
index 0ff3cfe..85cb9dd 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 d8fe264..92e69f5 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`.
@@ -45,8 +45,8 @@ Example:
#define HEADER_FILENAME
/*
htop - Filename.h
-(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
```
@@ -54,6 +54,8 @@ in the source distribution for its full text.
Import and use of headers
-------------------------
+We use the GPLv2+ as a shorthand indication that we release `htop` under the GNU Public license version 2 but are totally fine with users opting to apply the "any later version" clause.
+
Every file should import headers for all symbols it's using.
Thus when using a symbol from a header, even if that symbol is already imported by something else you use, you should declare an import for that header.
Doing so allows for easier restructuring of the code when things need to be moved around.
@@ -63,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)
@@ -73,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
--------------
@@ -95,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
--------------------
@@ -167,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.
@@ -194,7 +203,7 @@ They can be a great asset to structure the flow of a method.
If you want to automate formatting your code, the following command gives you a good baseline of how it should look:
```bash
-astyle -r -xb -s3 -p -xg -c -k1 -W1 \*.c \*.h
+astyle -r -xb -s3 -p -xg -c -k1 -W1 -H \*.c \*.h
```
Working with System APIs
@@ -217,7 +226,7 @@ The primary user documentation should be the man file which you can find in `hto
Additional documentation, like this file, should be written in gh-style markdown.
Make each sentence one line.
-Markdown will combined these in output formats.
+Markdown will combine these in output formats.
It does only insert a paragraph if you insert a blank line into the source file.
This way git can better diff and present the changes when documentation is altered.
@@ -228,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.
@@ -237,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 0000000..fd5b58b
--- /dev/null
+++ b/dragonflybsd/DragonFlyBSDMachine.c
@@ -0,0 +1,341 @@
+/*
+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) {
+ const DragonFlyBSDMachine* this = (const 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 = (DragonFlyBSDProcessTable*) 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(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);
+}
diff --git a/dragonflybsd/DragonFlyBSDProcessList.h b/dragonflybsd/DragonFlyBSDMachine.h
index 8a5b31c..0d4d8ef 100644
--- a/dragonflybsd/DragonFlyBSDProcessList.h
+++ b/dragonflybsd/DragonFlyBSDMachine.h
@@ -1,10 +1,10 @@
-#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
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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(DragonFlyBSDMachine* host, int jailid);
#endif
diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c
index 13cef0a..4be2198 100644
--- a/dragonflybsd/DragonFlyBSDProcess.c
+++ b/dragonflybsd/DragonFlyBSDProcess.c
@@ -2,7 +2,7 @@
htop - dragonflybsd/DragonFlyBSDProcess.c
(C) 2015 Hisham H. Muhammad
(C) 2017 Diederik de Groot
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -37,11 +37,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
+ [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_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, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
@@ -52,10 +52,10 @@ 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);
+ Process_init(&this->super, host);
return &this->super;
}
@@ -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 22cf975..92747b1 100644
--- a/dragonflybsd/DragonFlyBSDProcess.h
+++ b/dragonflybsd/DragonFlyBSDProcess.h
@@ -4,7 +4,7 @@
htop - dragonflybsd/DragonFlyBSDProcess.h
(C) 2015 Hisham H. Muhammad
(C) 2017 Diederik de Groot
-Released under the GNU GPLv2, see the COPYING file
+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 "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 08e3d7b..0000000
--- a/dragonflybsd/DragonFlyBSDProcessList.c
+++ /dev/null
@@ -1,610 +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(cpus, sizeof_cp_time_array);
- dfpl->cp_time_n = xCalloc(cpus, sizeof_cp_time_array);
- 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);
-}
-
-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->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);
-
- 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
- switch (kproc->kp_stat) {
- case SIDL: proc->state = 'I'; 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 = 'I';
- isIdleProcess = true;
- } else {
- proc->state = 'S';
- }
- else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) // interruptible lwkt wait
- proc->state = 'S';
- else if (kproc->kp_paddr) // uninterruptible wait
- proc->state = 'D';
- else // uninterruptible lwkt wait
- proc->state = 'B';
- break;
- case LSRUN:
- if (kproc->kp_lwp.kl_stat == LSRUN) {
- if (!(kproc->kp_lwp.kl_tdflags & (TDF_RUNNING | TDF_RUNQ)))
- proc->state = 'Q';
- else
- proc->state = 'R';
- }
- break;
- case LSSTOP:
- proc->state = 'T';
- break;
- default:
- proc->state = 'A';
- break;
- }
- break;
- case SSTOP: proc->state = 'T'; break;
- case SZOMB: proc->state = 'Z'; break;
- case SCORE: proc->state = 'C'; break;
- default: proc->state = '?';
- }
-
- if (kproc->kp_flags & P_SWAPPEDOUT)
- proc->state = 'W';
- if (kproc->kp_flags & P_TRACED)
- proc->state = 'T';
- if (kproc->kp_flags & P_JAILED)
- proc->state = 'J';
-
- if (Process_isKernelThread(proc))
- super->kernelThreads++;
-
- super->totalTasks++;
-
- if (proc->state == 'R')
- super->runningTasks++;
-
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
- proc->updated = true;
- }
-}
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
- assert(id < super->existingCPUs);
-
- // TODO: support offline CPUs and hot swapping
- (void) super; (void) id;
-
- return true;
-}
diff --git a/dragonflybsd/DragonFlyBSDProcessTable.c b/dragonflybsd/DragonFlyBSDProcessTable.c
new file mode 100644
index 0000000..e36086f
--- /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) {
+ const 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->host;
+ const DragonFlyMachine* dhost = (const DragonFlyMachine*) 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->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 0000000..e8ff1af
--- /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 62a1fb6..25afa8b 100644
--- a/dragonflybsd/Platform.c
+++ b/dragonflybsd/Platform.c
@@ -2,10 +2,12 @@
htop - dragonflybsd/Platform.c
(C) 2014 Hisham H. Muhammad
(C) 2017 Diederik de Groot
-Released under the GNU GPLv2, see the COPYING file
+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 <math.h>
@@ -20,20 +22,32 @@ 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/DragonFlyBSDProcess.h"
-#include "dragonflybsd/DragonFlyBSDProcessList.h"
+#include "dragonflybsd/DragonFlyBSDProcessTable.h"
+#include "generic/fdstat_sysctl.h"
+
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+};
-const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
@@ -101,12 +115,14 @@ const MeterClass* const Platform_meterTypes[] = {
&RightCPUs4Meter_class,
&LeftCPUs8Meter_class,
&RightCPUs8Meter_class,
+ &FileDescriptorMeter_class,
&BlankMeter_class,
NULL
};
-void Platform_init(void) {
+bool Platform_init(void) {
/* no platform-specific setup needed */
+ return true;
}
void Platform_done(void) {
@@ -118,7 +134,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);
@@ -149,7 +165,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);
@@ -160,15 +176,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;
@@ -176,18 +193,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 (super->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;
@@ -196,22 +212,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) {
@@ -220,15 +237,13 @@ 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) {
diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h
index 48ed1e9..606b004 100644
--- a/dragonflybsd/Platform.h
+++ b/dragonflybsd/Platform.h
@@ -4,7 +4,7 @@
htop - dragonflybsd/Platform.h
(C) 2014 Hisham H. Muhammad
(C) 2017 Diederik de Groot
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -23,12 +23,15 @@ in the source distribution for its full text.
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
#include "generic/gettime.h"
#include "generic/hostname.h"
#include "generic/uname.h"
-extern const ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -36,7 +39,7 @@ extern const unsigned int Platform_numberOfSignals;
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -46,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);
@@ -56,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);
@@ -78,8 +81,8 @@ static inline void Platform_getRelease(char** string) {
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
-static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
- return false;
+static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return STATUS_ERROR_EXIT;
}
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
@@ -90,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) { }
@@ -100,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/dragonflybsd/ProcessField.h b/dragonflybsd/ProcessField.h
index 03dfc14..1409675 100644
--- a/dragonflybsd/ProcessField.h
+++ b/dragonflybsd/ProcessField.h
@@ -3,14 +3,14 @@
/*
htop - dragonflybsd/ProcessField.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#define PLATFORM_PROCESS_FIELDS \
- JID = 100, \
- JAIL = 101, \
+ JID = 100, \
+ JAIL = 101, \
\
DUMMY_BUMP_FIELD = CWD, \
// End of list
diff --git a/freebsd/FreeBSDMachine.c b/freebsd/FreeBSDMachine.c
new file mode 100644
index 0000000..d781414
--- /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 ae82205..f34b568 100644
--- a/freebsd/FreeBSDProcessList.h
+++ b/freebsd/FreeBSDMachine.h
@@ -1,9 +1,9 @@
-#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
+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 9f0386b..e7582a7 100644
--- a/freebsd/FreeBSDProcess.c
+++ b/freebsd/FreeBSDProcess.c
@@ -1,10 +1,12 @@
/*
htop - FreeBSDProcess.c
(C) 2015 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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"
@@ -36,51 +39,61 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
+ [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_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, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
[PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process", .flags = 0, },
[PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, },
[CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
+#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);
+ Process_init(&this->super, machine);
return &this->super;
}
void Process_delete(Object* cast) {
FreeBSDProcess* this = (FreeBSDProcess*) cast;
Process_done((Process*)cast);
+ free(this->emul);
free(this->jname);
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:
+ 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);
}
@@ -94,6 +107,8 @@ static int FreeBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro
return SPACESHIP_NUMBER(p1->jid, p2->jid);
case JAIL:
return SPACESHIP_NULLSTR(p1->jname, p2->jname);
+ case EMULATION:
+ return SPACESHIP_NULLSTR(p1->emul, p2->emul);
default:
return Process_compareByKey_Base(v1, v2, key);
}
@@ -101,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 0e0bd59..012cfa2 100644
--- a/freebsd/FreeBSDProcess.h
+++ b/freebsd/FreeBSDProcess.h
@@ -3,7 +3,7 @@
/*
htop - FreeBSDProcess.h
(C) 2015 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -11,20 +11,21 @@ in the source distribution for its full text.
#include "Object.h"
#include "Process.h"
-#include "Settings.h"
+#include "Machine.h"
typedef struct FreeBSDProcess_ {
Process super;
int jid;
char* jname;
+ char* emul;
} FreeBSDProcess;
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 48c0648..0000000
--- a/freebsd/FreeBSDProcessList.c
+++ /dev/null
@@ -1,609 +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(cpus, sizeof_cp_time_array);
- fpl->cp_time_n = xCalloc(cpus, sizeof_cp_time_array);
- 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);
-}
-
-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;
- 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->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);
- }
- }
-
- // 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);
-
- 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;
- }
-
- switch (kproc->ki_stat) {
- case SIDL: proc->state = 'I'; break;
- case SRUN: proc->state = 'R'; break;
- case SSLEEP: proc->state = 'S'; break;
- case SSTOP: proc->state = 'T'; break;
- case SZOMB: proc->state = 'Z'; break;
- case SWAIT: proc->state = 'D'; break;
- case SLOCK: proc->state = 'L'; break;
- default: proc->state = '?';
- }
-
- if (Process_isKernelThread(proc))
- super->kernelThreads++;
-
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
- super->totalTasks++;
- if (proc->state == 'R')
- super->runningTasks++;
- proc->updated = true;
- }
-}
-
-bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
- assert(id < super->existingCPUs);
-
- // TODO: support offline CPUs and hot swapping
- (void) super; (void) id;
-
- return true;
-}
diff --git a/freebsd/FreeBSDProcessTable.c b/freebsd/FreeBSDProcessTable.c
new file mode 100644
index 0000000..9e18b8a
--- /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 0000000..23a6ab2
--- /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 527a455..9be7195 100644
--- a/freebsd/Platform.c
+++ b/freebsd/Platform.c
@@ -1,7 +1,7 @@
/*
htop - freebsd/Platform.c
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -31,27 +31,35 @@ 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"
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+};
-const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
@@ -123,12 +131,14 @@ const MeterClass* const Platform_meterTypes[] = {
&ZfsArcMeter_class,
&ZfsCompressedArcMeter_class,
&DiskIOMeter_class,
+ &FileDescriptorMeter_class,
&NetworkIOMeter_class,
NULL
};
-void Platform_init(void) {
+bool Platform_init(void) {
/* no platform-specific setup needed */
+ return true;
}
void Platform_done(void) {
@@ -140,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);
@@ -171,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);
@@ -182,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);
@@ -218,33 +224,47 @@ 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;
+ 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 (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) {
@@ -268,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) {
@@ -284,7 +302,8 @@ bool Platform_getDiskIO(DiskIOData* data) {
if (devstat_checkversion(NULL) < 0)
return false;
- struct devinfo info = { 0 };
+ // use static to plug memory leak; see #841
+ static struct devinfo info = { 0 };
struct statinfo current = { .dinfo = &info };
// get number of devices
diff --git a/freebsd/Platform.h b/freebsd/Platform.h
index e7e274c..c358d85 100644
--- a/freebsd/Platform.h
+++ b/freebsd/Platform.h
@@ -3,7 +3,7 @@
/*
htop - freebsd/Platform.h
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -19,12 +19,15 @@ in the source distribution for its full text.
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
#include "generic/gettime.h"
#include "generic/hostname.h"
#include "generic/uname.h"
-extern const ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -32,7 +35,7 @@ extern const unsigned int Platform_numberOfSignals;
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -42,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);
@@ -56,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);
@@ -78,8 +81,8 @@ static inline void Platform_getRelease(char** string) {
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
-static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
- return false;
+static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return STATUS_ERROR_EXIT;
}
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
@@ -90,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) { }
@@ -100,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/freebsd/ProcessField.h b/freebsd/ProcessField.h
index 6fba6bc..d20900d 100644
--- a/freebsd/ProcessField.h
+++ b/freebsd/ProcessField.h
@@ -3,14 +3,15 @@
/*
htop - freebsd/ProcessField.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#define PLATFORM_PROCESS_FIELDS \
- JID = 100, \
- JAIL = 101, \
+ JID = 100, \
+ JAIL = 101, \
+ EMULATION = 102, \
\
DUMMY_BUMP_FIELD = CWD, \
// End of list
diff --git a/generic/fdstat_sysctl.c b/generic/fdstat_sysctl.c
new file mode 100644
index 0000000..ea374fb
--- /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_DRAGONFLY)
+ Generic_getFileDescriptors_sysctl_internal(
+ "kern.maxfiles", NULL, 0, sizeof(struct kinfo_file), 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 0000000..107fcab
--- /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 af06ef3..209f523 100644
--- a/generic/gettime.c
+++ b/generic/gettime.c
@@ -1,9 +1,10 @@
/*
htop - generic/gettime.c
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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"
@@ -49,9 +50,13 @@ void Generic_gettime_monotonic(uint64_t* msec) {
else
*msec = 0;
-#else
+#else /* lower resolution gettimeofday() should be always available */
-# error "No monotonic clock available"
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) == 0)
+ *msec = ((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000);
+ else
+ *msec = 0;
#endif
}
diff --git a/generic/gettime.h b/generic/gettime.h
index c124d03..91fe43d 100644
--- a/generic/gettime.h
+++ b/generic/gettime.h
@@ -3,7 +3,7 @@
/*
htop - generic/gettime.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/generic/hostname.c b/generic/hostname.c
index 2e3d5c3..e327582 100644
--- a/generic/hostname.c
+++ b/generic/hostname.c
@@ -1,9 +1,10 @@
/*
htop - generic/hostname.c
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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/hostname.h b/generic/hostname.h
index 32f0c46..1e6c52d 100644
--- a/generic/hostname.h
+++ b/generic/hostname.h
@@ -3,7 +3,7 @@
/*
htop - generic/hostname.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/generic/openzfs_sysctl.c b/generic/openzfs_sysctl.c
index c7d79bc..bcd37dc 100644
--- a/generic/openzfs_sysctl.c
+++ b/generic/openzfs_sysctl.c
@@ -1,7 +1,7 @@
/*
htop - generic/openzfs_sysctl.c
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -15,6 +15,7 @@ in the source distribution for its full text.
static int MIB_kstat_zfs_misc_arcstats_size[5];
+static int MIB_kstat_zfs_misc_arcstats_c_min[5];
static int MIB_kstat_zfs_misc_arcstats_c_max[5];
static int MIB_kstat_zfs_misc_arcstats_mfu_size[5];
static int MIB_kstat_zfs_misc_arcstats_mru_size[5];
@@ -35,6 +36,7 @@ void openzfs_sysctl_init(ZfsArcStats* stats) {
len = 5;
sysctlnametomib("kstat.zfs.misc.arcstats.size", MIB_kstat_zfs_misc_arcstats_size, &len);
+ sysctlnametomib("kstat.zfs.misc.arcstats.c_min", MIB_kstat_zfs_misc_arcstats_c_min, &len);
sysctlnametomib("kstat.zfs.misc.arcstats.c_max", MIB_kstat_zfs_misc_arcstats_c_max, &len);
sysctlnametomib("kstat.zfs.misc.arcstats.mfu_size", MIB_kstat_zfs_misc_arcstats_mfu_size, &len);
sysctlnametomib("kstat.zfs.misc.arcstats.mru_size", MIB_kstat_zfs_misc_arcstats_mru_size, &len);
@@ -61,6 +63,10 @@ void openzfs_sysctl_updateArcStats(ZfsArcStats* stats) {
sysctl(MIB_kstat_zfs_misc_arcstats_size, 5, &(stats->size), &len, NULL, 0);
stats->size /= 1024;
+ len = sizeof(stats->min);
+ sysctl(MIB_kstat_zfs_misc_arcstats_c_min, 5, &(stats->min), &len, NULL, 0);
+ stats->min /= 1024;
+
len = sizeof(stats->max);
sysctl(MIB_kstat_zfs_misc_arcstats_c_max, 5, &(stats->max), &len, NULL, 0);
stats->max /= 1024;
diff --git a/generic/openzfs_sysctl.h b/generic/openzfs_sysctl.h
index dab1305..27fa720 100644
--- a/generic/openzfs_sysctl.h
+++ b/generic/openzfs_sysctl.h
@@ -3,7 +3,7 @@
/*
htop - generic/openzfs_sysctl.h
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/generic/uname.c b/generic/uname.c
index 9263d82..b5bb583 100644
--- a/generic/uname.c
+++ b/generic/uname.c
@@ -1,9 +1,10 @@
/*
htop - generic/uname.c
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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"
@@ -85,7 +86,7 @@ char* Generic_uname(void) {
if (uname_result == 0) {
size_t written = xSnprintf(savedString, sizeof(savedString), "%s %s [%s]", uname_info.sysname, uname_info.release, uname_info.machine);
- if (!String_contains_i(savedString, distro) && sizeof(savedString) > written)
+ if (!String_contains_i(savedString, distro, false) && sizeof(savedString) > written)
snprintf(savedString + written, sizeof(savedString) - written, " @ %s", distro);
} else {
snprintf(savedString, sizeof(savedString), "%s", distro);
diff --git a/generic/uname.h b/generic/uname.h
index 820d085..940d64c 100644
--- a/generic/uname.h
+++ b/generic/uname.h
@@ -3,7 +3,7 @@
/*
htop - generic/uname.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/htop.1.in b/htop.1.in
index ceec216..2376b7a 100644
--- a/htop.1.in
+++ b/htop.1.in
@@ -1,4 +1,4 @@
-.TH "HTOP" "1" "2020" "@PACKAGE_STRING@" "User Commands"
+.TH "HTOP" "1" "2024" "@PACKAGE_STRING@" "User Commands"
.SH "NAME"
htop, pcp-htop \- interactive process viewer
.SH "SYNOPSIS"
@@ -50,7 +50,8 @@ Start
in monochrome mode
.TP
\fB\-F \-\-filter=FILTER
-Filter processes by command
+Filter processes by terms matching the commands. The terms are matched
+case-insensitive and as fixed strings (not regexs). You can separate multiple terms with "|".
.TP
\fB\-h \-\-help
Display a help message and exit
@@ -62,7 +63,7 @@ Show only the given PIDs
Sort by this column (use \-\-sort\-key help for a column list).
This will force a list view unless you specify -t at the same time.
.TP
-\fB\-u \-\-user=USERNAME\fR
+\fB\-u \-\-user=USERNAME|UID\fR
Show only the processes of a given user
.TP
\fB\-U \-\-no-unicode\fR
@@ -95,6 +96,10 @@ held.
The following commands are supported while in
.BR htop :
.TP 5
+.B Tab, Shift-Tab
+Select the next / the previous screen tab to display.
+You can enable showing the screen tab names in the Setup screen (F2).
+.TP
.B Up, Alt-k
Select (highlight) the previous process in the process list. Scroll the list
if necessary.
@@ -175,6 +180,8 @@ bindings take precedence.
Incremental process filtering: type in part of a process command line and
only processes whose names match will be shown. To cancel filtering,
enter the Filter option again and press Esc.
+The matching is done case-insensitive. Terms are fixed strings (no regex).
+You can separate multiple terms with "|".
.TP
.B F5, t
Tree view: organize processes by parenthood, and layout the relations
@@ -251,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
@@ -285,26 +296,43 @@ is active, the executable path (/proc/[pid]/exe) and the command name
(/proc/[pid]/comm) are also shown merged with the command line, if available.
The program basename is highlighted if set in the configuration. Additional
-highlighting can be configured for stale executables (cf. Exe column below).
+highlighting can be configured for stale executables (cf. EXE column below).
.TP
-.B Comm
+.B COMM
The command name of the process obtained from /proc/[pid]/comm, if readable.
+
+Requires Linux kernel 2.6.33 or newer.
.TP
-.B Exe
+.B EXE
The abbreviated basename of the executable of the process, obtained from
/proc/[pid]/exe, if readable. htop is able to read this file on linux for ALL
the processes only if it has the capability CAP_SYS_PTRACE or root privileges.
The basename is marked in red if the executable used to run the process has
-been replaced or deleted on disk since the process started. This additional
-markup can be configured.
+been replaced or deleted on disk since the process started. The information is
+obtained by processing the contents of /proc/[pid]/exe.
+
+Furthermore the basename is marked in yellow if any library is reported as having
+been replaced or deleted on disk since it was last loaded. The information is
+obtained by processing the contents of /proc/[pid]/maps.
+
+When deciding the color the replacement of the main executable always takes
+precedence over replacement of any other library. If only the memory map indicates
+a replacement of the main executable, this will show as if any other library had
+been replaced or deleted.
+
+This additional color markup can be configured in the "Display Options" section of
+the setup screen.
+
+Displaying EXE requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED.
.TP
.B PID
The process ID.
.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)
@@ -425,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
@@ -476,7 +509,23 @@ The I/O rate of write(2) in bytes per second, for the process.
The I/O rate, IO_READ_RATE + IO_WRITE_RATE (see above).
.TP
.B CGROUP
-Which cgroup the process is in.
+Which cgroup the process is in. For a shortened view see the CCGROUP column below.
+.TP
+.B CCGROUP
+Shortened view of the cgroup name that the process is in.
+This performs some pattern-based replacements to shorten the displayed string and thus condense the information.
+ \fB/*.slice\fR is shortened to \fB/[*]\fR (exceptions below)
+ \fB/system.slice\fR is shortened to \fB/[S]\fR
+ \fB/user.slice\fR is shortened to \fB/[U]\fR
+ \fB/user-*.slice\fR is shortened to \fB/[U:*]\fR (directly preceding \fB/[U]\fR before dropped)
+ \fB/machine.slice\fR is shortened to \fB/[M]\fR
+ \fB/machine-*.scope\fR is shortened to \fB/[SNC:*]\fR (SNC: systemd nspawn container), uppercase for the monitor
+ \fB/lxc.monitor.*\fR is shortened to \fB/[LXC:*]\fR
+ \fB/lxc.payload.*\fR is shortened to \fB/[lxc:*]\fR
+ \fB/*.scope\fR is shortened to \fB/!*\fR
+ \fB/*.service\fR is shortened to \fB/*\fR (suffix removed)
+
+Encountered escape sequences (e.g. from systemd) inside the cgroup name are not decoded.
.TP
.B OOM
OOM killer score.
@@ -499,12 +548,6 @@ The percentage of time spent waiting for the completion of synchronous block I/O
.B PERCENT_SWAP_DELAY (SWAPD%)
The percentage of time spent swapping in pages. Requires CAP_NET_ADMIN.
.TP
-.B COMM
-The command name for the process. Requires Linux kernel 2.6.33 or newer.
-.TP
-.B EXE
-The executable file of the process as reported by the kernel. Requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED.
-.TP
.B AGRP
The autogroup identifier for the process. Requires Linux CFS to be enabled.
.TP
@@ -559,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
@@ -601,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),
@@ -632,3 +684,12 @@ Nowadays it is maintained by the community at <htop@groups.io>.
.B pcp-htop
is maintained as a collaboration between the <htop@groups.io> and <pcp@groups.io>
communities, and forms part of the Performance Co-Pilot suite of tools.
+.SH "COPYRIGHT"
+Copyright \(co 2004-2019 Hisham Muhammad.
+.br
+Copyright \(co 2020-2024 htop dev team.
+.LP
+License GPLv2+: GNU General Public License version 2 or, at your option, any later version.
+.LP
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
diff --git a/htop.c b/htop.c
index 426b59a..7155793 100644
--- a/htop.c
+++ b/htop.c
@@ -2,7 +2,7 @@
htop - htop.c
(C) 2004-2011 Hisham H. Muhammad
(C) 2020-2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 90020ad..7809d22 100644
--- a/htop.png
+++ b/htop.png
Binary files differ
diff --git a/iwyu/htop.imp b/iwyu/htop.imp
index 1416d74..e2fc72b 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 6139ebe..8456240 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
new file mode 100644
index 0000000..f352b8e
--- /dev/null
+++ b/linux/CGroupUtils.c
@@ -0,0 +1,532 @@
+/*
+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;
+ size_t size;
+ size_t pos;
+} StrBuf_state;
+
+typedef bool (*StrBuf_putc_t)(StrBuf_state* p, char c);
+
+static bool StrBuf_putc_count(StrBuf_state* p, ATTR_UNUSED char c) {
+ p->pos++;
+ return true;
+}
+
+static bool StrBuf_putc_write(StrBuf_state* p, char c) {
+ if (p->pos >= p->size)
+ return false;
+
+ p->buf[p->pos] = c;
+ p->pos++;
+ return true;
+}
+
+static bool StrBuf_putsn(StrBuf_state* p, StrBuf_putc_t w, const char* s, size_t count) {
+ for (; count; count--)
+ if (!w(p, *s++))
+ return false;
+
+ return true;
+}
+
+static bool StrBuf_putsz(StrBuf_state* p, StrBuf_putc_t w, const char* s) {
+ while (*s)
+ if (!w(p, *s++))
+ return false;
+
+ return true;
+}
+
+static bool Label_checkEqual(const char* labelStart, size_t labelLen, const char* expected) {
+ return labelLen == strlen(expected) && String_startsWith(labelStart, expected);
+}
+
+static bool Label_checkPrefix(const char* labelStart, size_t labelLen, const char* expected) {
+ return labelLen > strlen(expected) && String_startsWith(labelStart, expected);
+}
+
+static bool Label_checkSuffix(const char* labelStart, size_t labelLen, const char* expected) {
+ 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) {
+ while (*cgroup) {
+ if ('/' == *cgroup) {
+ while ('/' == *cgroup)
+ cgroup++;
+
+ if (!w(s, '/'))
+ return false;
+
+ continue;
+ }
+
+ const char* labelStart = cgroup;
+ const char* nextSlash = String_strchrnul(labelStart, '/');
+ const size_t labelLen = nextSlash - labelStart;
+
+ if (Label_checkEqual(labelStart, labelLen, str_system_slice)) {
+ cgroup = nextSlash;
+
+ if (!StrBuf_putsz(s, w, "[S]"))
+ return false;
+
+ if (String_startsWith(cgroup, str_system_slice_prefix)) {
+ cgroup = String_strchrnul(cgroup + 1, '/');
+ continue;
+ }
+
+ continue;
+ }
+
+ if (Label_checkEqual(labelStart, labelLen, str_machine_slice)) {
+ cgroup = nextSlash;
+
+ if (!StrBuf_putsz(s, w, "[M]"))
+ return false;
+
+ continue;
+ }
+
+ if (Label_checkEqual(labelStart, labelLen, str_user_slice)) {
+ cgroup = nextSlash;
+
+ if (!StrBuf_putsz(s, w, "[U]"))
+ return false;
+
+ if (!String_startsWith(cgroup, str_user_slice_prefix))
+ continue;
+
+ 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))
+ continue;
+
+ const size_t sliceNameLen = sliceSpec - (cgroup + strlen(str_user_slice_prefix));
+
+ s->pos--;
+ if (!w(s, ':'))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_user_slice_prefix), sliceNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = userSliceSlash;
+
+ continue;
+ }
+
+ if (Label_checkSuffix(labelStart, labelLen, str_slice_suffix)) {
+ const size_t sliceNameLen = labelLen - strlen(str_slice_suffix);
+
+ if (!w(s, '['))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup, sliceNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ 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;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ if (Label_checkPrefix(labelStart, labelLen, str_lxc_monitor_prefix)) {
+ const size_t cgroupNameLen = labelLen - strlen(str_lxc_monitor_prefix);
+
+ if (!StrBuf_putsz(s, w, "[LXC:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_monitor_prefix), cgroupNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ // LXC legacy cgroup naming
+ if (Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy) ||
+ Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) {
+ bool isMonitor = Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy);
+
+ labelStart = nextSlash;
+ while (*labelStart == '/')
+ labelStart++;
+
+ nextSlash = String_strchrnul(labelStart, '/');
+ if (nextSlash - labelStart > 0) {
+ if (!StrBuf_putsz(s, w, isMonitor ? "[LXC:" : "[lxc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+ continue;
+ }
+
+ labelStart = cgroup;
+ nextSlash = labelStart + labelLen;
+ }
+
+ if (Label_checkSuffix(labelStart, labelLen, str_service_suffix)) {
+ const size_t serviceNameLen = labelLen - strlen(str_service_suffix);
+
+ if (String_startsWith(cgroup, "user@")) {
+ cgroup = nextSlash;
+
+ while (*cgroup == '/')
+ cgroup++;
+
+ continue;
+ }
+
+ if (!StrBuf_putsn(s, w, cgroup, serviceNameLen))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ 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 (!StrBuf_putsz(s, w, is_monitor ? "[SNC:" : "[snc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ 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_snap_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_snap_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "!snap:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_snap_scope_prefix), nextDot - (labelStart + strlen(str_snap_scope_prefix))))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ } 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, '!'))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup, scopeNameLen))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ // Default behavior: Copy the full label
+ cgroup = labelStart;
+
+ if (!StrBuf_putsn(s, w, cgroup, labelLen))
+ return false;
+
+ cgroup = nextSlash;
+ }
+
+ return true;
+}
+
+char* CGroup_filterName(const char* cgroup) {
+ StrBuf_state s = {
+ .buf = NULL,
+ .size = 0,
+ .pos = 0,
+ };
+
+ if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_count)) {
+ return NULL;
+ }
+
+ s.buf = xCalloc(s.pos + 1, sizeof(char));
+ s.size = s.pos;
+ s.pos = 0;
+
+ if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_write)) {
+ free(s.buf);
+ return NULL;
+ }
+
+ 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
new file mode 100644
index 0000000..972a15b
--- /dev/null
+++ b/linux/CGroupUtils.h
@@ -0,0 +1,14 @@
+#ifndef HEADER_CGroupUtils
+#define HEADER_CGroupUtils
+/*
+htop - CGroupUtils.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+char* CGroup_filterName(const char* cgroup);
+char* CGroup_filterContainer(const char* cgroup);
+
+#endif /* HEADER_CGroupUtils */
diff --git a/linux/HugePageMeter.c b/linux/HugePageMeter.c
index 8c637fd..058ab4b 100644
--- a/linux/HugePageMeter.c
+++ b/linux/HugePageMeter.c
@@ -1,10 +1,12 @@
/*
htop - HugePageMeter.c
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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/HugePageMeter.h b/linux/HugePageMeter.h
index e3b867d..d74a19e 100644
--- a/linux/HugePageMeter.h
+++ b/linux/HugePageMeter.h
@@ -3,7 +3,7 @@
/*
htop - HugePageMeter.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/linux/IOPriority.h b/linux/IOPriority.h
index 2b37cb0..78bc470 100644
--- a/linux/IOPriority.h
+++ b/linux/IOPriority.h
@@ -3,7 +3,7 @@
/*
htop - IOPriority.h
(C) 2004-2012 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
Based on ionice,
diff --git a/linux/IOPriorityPanel.c b/linux/IOPriorityPanel.c
index 1bcbf4b..8d0ef7b 100644
--- a/linux/IOPriorityPanel.c
+++ b/linux/IOPriorityPanel.c
@@ -1,10 +1,12 @@
/*
htop - IOPriorityPanel.c
(C) 2004-2012 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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/IOPriorityPanel.h b/linux/IOPriorityPanel.h
index 3aa7610..cb5b338 100644
--- a/linux/IOPriorityPanel.h
+++ b/linux/IOPriorityPanel.h
@@ -3,7 +3,7 @@
/*
htop - IOPriorityPanel.h
(C) 2004-2012 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/linux/LibSensors.c b/linux/LibSensors.c
index 9a27fe5..5373416 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,7 +229,7 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns
}
/* Only package temperature - copy to all cores */
- if (coreTempCount == 0 && !isnan(data[0])) {
+ if (coreTempCount == 0 && !isNaN(data[0])) {
for (unsigned int i = 1; i <= existingCPUs; i++)
data[i] = data[0];
@@ -229,22 +238,20 @@ 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;
+ if (coreTempCount > 0 && isNaN(data[0])) {
+ double maxTemp = -HUGE_VAL;
for (unsigned int i = 1; i <= existingCPUs; i++) {
- if (isnan(data[i]))
- continue;
-
- maxTemp = MAXIMUM(maxTemp, data[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])) {
+ if (coreTempCount == 1 && !isNaN(data[1])) {
for (unsigned int i = 2; i <= existingCPUs; i++)
data[i] = data[1];
@@ -264,6 +271,8 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns
out:
for (unsigned int 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 aa89979..6f05448 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 0000000..ae2930d
--- /dev/null
+++ b/linux/LinuxMachine.c
@@ -0,0 +1,695 @@
+/*
+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 (!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;
+ 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) = 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);
+
+ unsigned int lastAdjCpuId = 0;
+
+ 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;
+
+ 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;
+ }
+
+ 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")) {
+ ProcessTable* pt = (ProcessTable*) super->processTable;
+ pt->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;
+ Machine_done(super);
+ 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 a5640e2..309b485 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
+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,26 +53,20 @@ typedef struct CPUData_ {
bool online;
} CPUData;
-typedef struct TtyDriver_ {
- char* path;
- unsigned int major;
- unsigned int minorFrom;
- unsigned int minorTo;
-} TtyDriver;
+typedef struct LinuxMachine_ {
+ Machine super;
-typedef struct LinuxProcessList_ {
- ProcessList super;
+ long jiffies;
+ int pageSize;
+ int pageSizeKB;
- CPUData* cpuData;
+ /* 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 */
- TtyDriver* ttyDrivers;
- bool haveSmapsRollup;
- bool haveAutogroup;
+ double period;
- #ifdef HAVE_DELAYACCT
- struct nl_sock* netlink_socket;
- int netlink_family;
- #endif
+ CPUData* cpuData;
memory_t totalHugePageMem;
memory_t usedHugePageMem[HTOP_HUGEPAGE_COUNT];
@@ -85,7 +75,8 @@ typedef struct LinuxProcessList_ {
ZfsArcStats zfs;
ZramStats zram;
-} LinuxProcessList;
+ ZswapStats zswap;
+} LinuxMachine;
#ifndef PROCDIR
#define PROCDIR "/proc"
@@ -115,12 +106,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 75638e4..dc4f259 100644
--- a/linux/LinuxProcess.c
+++ b/linux/LinuxProcess.c
@@ -2,13 +2,17 @@
htop - LinuxProcess.c
(C) 2014 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+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,14 +57,15 @@ 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, },
- [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, },
+ [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_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, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
@@ -78,39 +83,46 @@ 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 ", .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, .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
- [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD%", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
- [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
- [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWPD%", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
+ [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
+ [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
+ [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWPD% ", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
#endif
[M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
[M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
[M_PSSWP] = { .name = "M_PSSWP", .title = " PSSWP ", .description = "shows proportional swap share of this mapping, unlike \"Swap\", this does not take into account swapped out page of underlying shmem objects", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
[CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, .defaultSortDesc = true, },
- [SECATTR] = { .name = "SECATTR", .title = " Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, },
+ [SECATTR] = { .name = "SECATTR", .title = "Security Attribute", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, .autoWidth = true, },
[PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process from /proc/[pid]/comm", .flags = 0, },
[PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, },
[CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
[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, },
+#ifdef SCHEDULER_SUPPORT
+ [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, },
+#endif
};
-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);
+ Process_init(&this->super, host);
return &this->super;
}
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
free(this->ctid);
@@ -140,22 +152,29 @@ static int LinuxProcess_effectiveIOPriority(const LinuxProcess* this) {
#define SYS_ioprio_set __NR_ioprio_set
#endif
-IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this) {
+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) {
@@ -165,9 +184,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)
@@ -176,69 +196,79 @@ 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 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;
@@ -246,7 +276,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, "%-10s ", lp->cgroup ? lp->cgroup : ""); 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);
@@ -267,9 +299,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
break;
}
#ifdef HAVE_DELAYACCT
- case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, &attr); break;
- case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, &attr); break;
- case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, &attr); break;
+ case PERCENT_CPU_DELAY: 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) {
@@ -277,7 +309,7 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
}
xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff);
break;
- case SECATTR: snprintf(buffer, n, "%-30s ", lp->secattr ? lp->secattr : "?"); 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);
@@ -290,8 +322,8 @@ 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 ");
@@ -301,14 +333,8 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
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) {
@@ -324,6 +350,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:
@@ -353,11 +381,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);
@@ -370,15 +398,19 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce
#endif
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);
#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));
@@ -397,11 +429,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 20399d9..388e50d 100644
--- a/linux/LinuxProcess.h
+++ b/linux/LinuxProcess.h
@@ -4,19 +4,18 @@
htop - LinuxProcess.h
(C) 2014 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+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
@@ -41,6 +40,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;
@@ -48,6 +48,9 @@ typedef struct LinuxProcess_ {
long m_drs;
long m_lrs;
+ /* Process flags */
+ unsigned long int flags;
+
/* Data read (in bytes) */
unsigned long long io_rchar;
@@ -86,6 +89,8 @@ typedef struct LinuxProcess_ {
unsigned int vxid;
#endif
char* cgroup;
+ char* cgroup_short;
+ char* container_short;
unsigned int oom;
#ifdef HAVE_DELAYACCT
unsigned long long int delay_read_time;
@@ -114,17 +119,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 8d82b00..0000000
--- a/linux/LinuxProcessList.c
+++ /dev/null
@@ -1,2118 +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/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
-
-
-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 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;
-
- DIR* dir = opendir("/sys/devices/system/cpu");
- if (!dir) {
- this->cpuData = xReallocArrayZero(this->cpuData, super->existingCPUs ? (super->existingCPUs + 1) : 0, 2, sizeof(CPUData));
- this->cpuData[0].online = true; /* average is always "online" */
- this->cpuData[1].online = true;
- super->activeCPUs = 1;
- super->existingCPUs = 1;
- return;
- }
-
- unsigned int currExisting = super->existingCPUs;
-
- const struct dirent* entry;
- while ((entry = readdir(dir)) != NULL) {
- if (entry->d_type != DT_DIR)
- 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);
-
-#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;
-}
-
-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;
-}
-
-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 = 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;
-
- /* Skip (9) flags - %u */
- location = strchr(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;
- 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 = (process->io_read_bytes - last_read) * /*ms to s*/1000 / (realtimeMs - process->io_last_scan_time_ms);
- }
- 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 = (process->io_write_bytes - last_write) * /*ms to s*/1000 / (realtimeMs - process->io_last_scan_time_ms);
- }
- 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;
-
- 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;
- }
- 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 = strchr(buffer, ':');
- if (!group)
- break;
-
- if (at != output) {
- *at = ';';
- at++;
- left--;
- }
- int wrote = snprintf(at, left, "%s", group);
- left -= wrote;
- }
- fclose(file);
- free_and_xStrdup(&process->cgroup, output);
-}
-
-#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';
- }
- 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;
-
- if (amtRead == 0) {
- if (process->state != 'Z') {
- process->isKernelThread = true;
- }
- Process_updateCmdline(process, NULL, 0, 0);
- return true;
- }
-
- 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 && !String_startsWith(filename, process->procExe))) {
-
- 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';
-
- process->mergedCommand.exeChanged |= oldExeDeleted ^ process->procExeDeleted;
- }
-
- 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 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;
-
-#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;
-
- 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;
-
-#ifdef HAVE_OPENAT
- int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
- if (procFd < 0)
- goto errorReadingProcess;
-#else
- char procFd[4096];
- xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name);
-#endif
-
- LinuxProcessList_recurseProcTree(this, procFd, "task", proc, period);
-
- /*
- * 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 (settings->flags & PROCESS_FLAG_IO)
- LinuxProcessList_readIoFile(lp, procFd, pl->realtimeMs);
-
- if (!LinuxProcessList_readStatmFile(lp, procFd))
- goto errorReadingProcess;
-
- {
- bool prev = proc->usesDeletedLib;
-
- if ((settings->flags & PROCESS_FLAG_LINUX_LRS_FIX) ||
- (settings->highlightDeletedExe && !proc->procExeDeleted && !proc->isKernelThread && !proc->isUserlandThread)) {
- // 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, settings->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;
- }
-
- proc->mergedCommand.exeChanged |= prev ^ proc->usesDeletedLib;
- }
-
- if ((settings->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 (tty_nr != proc->tty_nr && this->ttyDrivers) {
- free(proc->tty_name);
- proc->tty_name = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
- }
-
- if (settings->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;
-
- if (! LinuxProcessList_updateUser(pl, proc, procFd))
- goto errorReadingProcess;
-
- if (!preExisting) {
-
- #ifdef HAVE_OPENVZ
- if (settings->flags & PROCESS_FLAG_LINUX_OPENVZ) {
- LinuxProcessList_readOpenVZData(lp, procFd);
- }
- #endif
-
- #ifdef HAVE_VSERVER
- if (settings->flags & PROCESS_FLAG_LINUX_VSERVER) {
- LinuxProcessList_readVServerData(lp, procFd);
- }
- #endif
-
- if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
- goto errorReadingProcess;
- }
-
- Process_fillStarttimeBuffer(proc);
-
- ProcessList_add(pl, proc);
- } else {
- if (settings->updateProcessNames && proc->state != 'Z') {
- if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
- goto errorReadingProcess;
- }
- }
- }
-
- #ifdef HAVE_DELAYACCT
- if (settings->flags & PROCESS_FLAG_LINUX_DELAYACCT) {
- LinuxProcessList_readDelayAcctData(this, lp);
- }
- #endif
-
- if (settings->flags & PROCESS_FLAG_LINUX_CGROUP) {
- LinuxProcessList_readCGroupFile(lp, procFd);
- }
-
- if (settings->flags & PROCESS_FLAG_LINUX_OOM) {
- LinuxProcessList_readOomData(lp, procFd);
- }
-
- if (settings->flags & PROCESS_FLAG_LINUX_CTXT) {
- LinuxProcessList_readCtxtData(lp, procFd);
- }
-
- if (settings->flags & PROCESS_FLAG_LINUX_SECATTR) {
- LinuxProcessList_readSecattrData(lp, procFd);
- }
-
- if (settings->flags & PROCESS_FLAG_CWD) {
- LinuxProcessList_readCwd(lp, procFd);
- }
-
- if ((settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {
- LinuxProcessList_readAutogroup(lp, procFd);
- }
-
- if (!proc->cmdline && statCommand[0] &&
- (proc->state == 'Z' || 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) {
- ProcessList_remove(pl, proc);
- } else {
- 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; \
- }
-
- 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; \
- }
- #define tryReadFlag(label, variable, flag) \
- if (String_startsWith(buffer, label)) { \
- (flag) = sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
- break; \
- }
-
- switch (buffer[0]) {
- case 'c':
- 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.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 scanCPUFreqencyFromSysCPUFreq(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 scanCPUFreqencyFromCPUinfo(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) ||
- (sscanf(buffer, "processor: %d", &cpuid) == 1)
- ) {
- continue;
- } else if (
- (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
- (sscanf(buffer, "cpu MHz: %lf", &frequency) == 1) ||
- (sscanf(buffer, "clock : %lfMHz", &frequency) == 1) ||
- (sscanf(buffer, "clock: %lfMHz", &frequency) == 1)
- ) {
- if (cpuid < 0 || (unsigned int)cpuid > (existingCPUs - 1)) {
- 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 (scanCPUFreqencyFromSysCPUFreq(this) == 0) {
- return;
- }
-
- scanCPUFreqencyFromCPUinfo(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->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 0000000..039a64e
--- /dev/null
+++ b/linux/LinuxProcessTable.c
@@ -0,0 +1,1668 @@
+/*
+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 <unistd.h>
+#include <sys/stat.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 "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/LinuxMachine.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
+
+/* Not exposed yet. Defined at include/linux/sched.h */
+#ifndef PF_KTHREAD
+#define PF_KTHREAD 0x00200000
+#endif
+
+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 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;
+
+ 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 LinuxProcessTable_initNetlinkSocket(LinuxProcessTable* 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
+
+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);
+
+ 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
+ if (this->netlink_socket) {
+ nl_close(this->netlink_socket);
+ nl_socket_free(this->netlink_socket);
+ }
+ #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;
+ }
+}
+
+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;
+}
+
+static bool LinuxProcessTable_readStatusFile(Process* process, openat_arg_t procFd) {
+ LinuxProcess* lp = (LinuxProcess*) process;
+
+ unsigned long ctxt = 0;
+#ifdef HAVE_VSERVER
+ lp->vxid = 0;
+#endif
+
+ FILE* statusfile = fopenat(procFd, "status", "r");
+ if (!statusfile)
+ return false;
+
+ char buffer[PROC_LINE_LENGTH + 1];
+
+ 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 = true;
+
+ } else if (String_startsWith(buffer, "CapPrm:")) {
+ char* ptr = buffer + strlen("CapPrm:");
+ while (*ptr == ' ' || *ptr == '\t')
+ ptr++;
+
+ uint64_t cap_permitted = fast_strtoull_hex(&ptr, 16);
+ process->elevated_priv = cap_permitted != 0 && process->st_uid != 0;
+
+ } 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;
+}
+
+static bool LinuxProcessTable_updateUser(const Machine* host, 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(host->usersTable, sstat.st_uid);
+ }
+
+ return true;
+}
+
+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;
+}
+
+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;
+ }
+}
+
+static bool LinuxProcessTable_readStatmFile(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host) {
+ 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 *= host->pageSizeKB;
+ process->super.m_resident *= host->pageSizeKB;
+
+ process->m_priv = process->super.m_resident - (process->m_share * host->pageSizeKB);
+ }
+
+ return r == 7;
+}
+
+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* 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 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
+
+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;
+ }
+}
+
+static void LinuxProcessTable_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 LinuxProcessTable_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 LinuxProcessTable_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';
+ }
+
+ Row_updateFieldWidth(SECATTR, strlen(buffer));
+
+ if (process->secattr && String_eq(process->secattr, buffer)) {
+ return;
+ }
+ free_and_xStrdup(&process->secattr, buffer);
+}
+
+static void LinuxProcessTable_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
+ 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';
+
+ 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(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;
+}
+
+static void LinuxProcessTable_readDelayAcctData(LinuxProcessTable* this, LinuxProcess* process) {
+ struct nl_msg* msg;
+
+ if (!this->netlink_socket) {
+ LinuxProcessTable_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_getPid(&process->super)) < 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 LinuxProcessTable_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
+ 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;
+ }
+
+ return true;
+}
+
+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 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 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 Process* parent) {
+ 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 (parent && pid == Process_getPid(parent))
+ 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, parent ? Process_getPid(parent) : pid);
+ proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc);
+
+ LinuxProcessTable_recurseProcTree(this, procFd, lhost, "task", proc);
+
+ /*
+ * 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) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
+
+ bool scanMainThread = !hideUserlandThreads && !Process_isKernelThread(proc) && !parent;
+ if (ss->flags & PROCESS_FLAG_IO)
+ LinuxProcessTable_readIoFile(lp, procFd, scanMainThread);
+
+ if (!LinuxProcessTable_readStatmFile(lp, procFd, lhost))
+ 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 && 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) {
+ LinuxProcessTable_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 (!LinuxProcessTable_readStatFile(lp, procFd, lhost, scanMainThread, 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 = LinuxProcessTable_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) {
+ LinuxProcess_updateIOPriority(proc);
+ }
+
+ 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))
+ goto errorReadingProcess;
+
+ if (!LinuxProcessTable_readStatusFile(proc, procFd))
+ 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)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+
+ 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)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+ }
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_CGROUP)
+ LinuxProcessTable_readCGroupFile(lp, procFd);
+
+ #ifdef HAVE_DELAYACCT
+ if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) {
+ LinuxProcessTable_readDelayAcctData(this, lp);
+ }
+ #endif
+
+ if (ss->flags & PROCESS_FLAG_LINUX_OOM) {
+ LinuxProcessTable_readOomData(lp, procFd);
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) {
+ LinuxProcessTable_readSecattrData(lp, procFd);
+ }
+
+ if (ss->flags & PROCESS_FLAG_CWD) {
+ LinuxProcessTable_readCwd(lp, procFd);
+ }
+
+ if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {
+ LinuxProcessTable_readAutogroup(lp, procFd);
+ }
+
+ #ifdef SCHEDULER_SUPPORT
+ if (ss->flags & PROCESS_FLAG_SCHEDPOL) {
+ Scheduling_readProcessPolicy(proc);
+ }
+ #endif
+
+ if (!proc->cmdline && statCommand[0] &&
+ (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+
+ /*
+ * Final section after all data has been gathered
+ */
+
+ proc->super.updated = true;
+ Compat_openatArgClose(procFd);
+
+ if (hideRunningInContainer && proc->isRunningInContainer) {
+ 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;
+ const Machine* host = super->super.host;
+ const Settings* settings = host->settings;
+ const LinuxMachine* lhost = (const 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;
+ }
+
+ /* 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 0000000..b87f9b0
--- /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 05023d5..8dc8bb5 100644
--- a/linux/Platform.c
+++ b/linux/Platform.c
@@ -1,17 +1,17 @@
/*
htop - linux/Platform.c
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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,9 +44,7 @@ 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"
@@ -52,17 +53,18 @@ in the source distribution for its full text.
#include "XUtils.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
@@ -70,6 +72,10 @@ in the source distribution for its full text.
#include "LibSensors.h"
#endif
+#ifndef O_PATH
+#define O_PATH 010000000 // declare for ancient glibc versions
+#endif
+
#ifdef HAVE_LIBCAP
enum CapMode {
@@ -79,7 +85,22 @@ enum CapMode {
};
#endif
-const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+bool Running_containerized = false;
+
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+ {
+ .name = "I/O",
+ .columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command",
+ .sortKey = "IO_RATE",
+ },
+};
+
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
@@ -142,7 +163,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();
}
@@ -157,7 +178,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;
@@ -219,6 +240,7 @@ const MeterClass* const Platform_meterTypes[] = {
&PressureStallCPUSomeMeter_class,
&PressureStallIOSomeMeter_class,
&PressureStallIOFullMeter_class,
+ &PressureStallIRQFullMeter_class,
&PressureStallMemorySomeMeter_class,
&PressureStallMemoryFullMeter_class,
&ZfsArcMeter_class,
@@ -228,10 +250,12 @@ const MeterClass* const Platform_meterTypes[] = {
&NetworkIOMeter_class,
&SELinuxMeter_class,
&SystemdMeter_class,
+ &SystemdUserMeter_class,
+ &FileDescriptorMeter_class,
NULL
};
-int Platform_getUptime() {
+int Platform_getUptime(void) {
double uptime = 0;
FILE* fd = fopen(PROCDIR "/uptime", "r");
if (fd) {
@@ -266,12 +290,12 @@ err:
*fifteen = NAN;
}
-int Platform_getMaxPid() {
+pid_t Platform_getMaxPid(void) {
+ pid_t maxPid = 4194303;
FILE* file = fopen(PROCDIR "/sys/kernel/pid_max", "r");
if (!file)
- return -1;
+ return maxPid;
- int maxPid = 4194303;
int match = fscanf(file, "%32d", &maxPid);
(void) match;
fclose(file);
@@ -279,8 +303,9 @@ int Platform_getMaxPid() {
}
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;
@@ -292,28 +317,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;
@@ -328,46 +355,81 @@ 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 > lpl->totalHugePageMem ? pl->totalMem - lpl->totalHugePageMem : pl->totalMem;
- this->values[0] = pl->usedMem > lpl->totalHugePageMem ? pl->usedMem - lpl->totalHugePageMem : 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) {
- this->values[0] -= lpl->zfs.size;
- this->values[3] += lpl->zfs.size;
+ 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 (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) {
@@ -406,117 +468,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;
-
- 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;
-}
-
-FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- FileLocks_ProcessData* pdata = xCalloc(1, sizeof(FileLocks_ProcessData));
-
- FILE* f = fopen(PROCDIR "/locks", "r");
- if (!f) {
- pdata->error = true;
- return pdata;
- }
-
- char buffer[1024];
- FileLocks_LockData** data_ref = &pdata->locks;
- while(fgets(buffer, sizeof(buffer), f)) {
- if (!strchr(buffer, '\n'))
+ FILE* f = fdopen(fd, "r");
+ if (!f) {
+ close(fd);
continue;
+ }
- 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;
+ for (char buffer[1024]; fgets(buffer, sizeof(buffer), f); ) {
+ if (!strchr(buffer, '\n'))
+ continue;
- if (pid != lock_pid)
- continue;
+ if (!String_startsWith(buffer, "lock:\t"))
+ continue;
- 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;
+ 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;
+
+ data.locktype = xStrdup(locktype);
+ data.exclusive = xStrdup(exclusive);
+ data.readwrite = xStrdup(readwrite);
+ data.dev = makedev(maj, min);
+
+ if (String_eq(lock_end, "EOF"))
+ data.end = ULLONG_MAX;
+ else
+ data.end = strtoull(lock_end, NULL, 10);
+
+ 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);
+
+ *data_ref = xCalloc(1, sizeof(FileLocks_LockData));
+ (*data_ref)->data = data;
+ data_ref = &(*data_ref)->next;
}
- *data_ref = ldata;
- data_ref = &ldata->next;
+ fclose(f);
}
- fclose(f);
+ closedir(dirp);
+ return pdata;
+
+err:
+ pdata->error = true;
return pdata;
}
@@ -538,6 +573,24 @@ void Platform_getPressureStall(const char* file, bool some, double* ten, double*
fclose(fd);
}
+void Platform_getFileDescriptors(double* used, double* max) {
+ *used = NAN;
+ *max = 65536;
+
+ FILE* fd = fopen(PROCDIR "/sys/fs/file-nr", "r");
+ if (!fd)
+ return;
+
+ unsigned long long v1, v2, v3;
+ int total = fscanf(fd, "%llu %llu %llu", &v1, &v2, &v3);
+ if (total == 3) {
+ *used = v1;
+ *max = v3;
+ }
+
+ fclose(fd);
+}
+
bool Platform_getDiskIO(DiskIOData* data) {
FILE* fd = fopen(PROCDIR "/diskstats", "r");
if (!fd)
@@ -611,133 +664,85 @@ bool Platform_getNetworkIO(NetworkIOData* data) {
// Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
-#define MAX_BATTERIES 64
#define PROC_BATTERY_DIR PROCDIR "/acpi/battery"
#define PROC_POWERSUPPLY_DIR PROCDIR "/acpi/ac_adapter"
+#define PROC_POWERSUPPLY_ACSTATE_FILE PROC_POWERSUPPLY_DIR "/AC/state"
#define SYS_POWERSUPPLY_DIR "/sys/class/power_supply"
// ----------------------------------------
// READ FROM /proc
// ----------------------------------------
-static unsigned long int parseBatInfo(const char* fileName, const unsigned short int lineNum, const unsigned short int wordNum) {
- const char batteryPath[] = PROC_BATTERY_DIR;
- DIR* batteryDir = opendir(batteryPath);
+static double Platform_Battery_getProcBatInfo(void) {
+ DIR* batteryDir = opendir(PROC_BATTERY_DIR);
if (!batteryDir)
- return 0;
-
- char* batteries[MAX_BATTERIES];
- unsigned int nBatteries = 0;
- memset(batteries, 0, MAX_BATTERIES * sizeof(char*));
+ return NAN;
- while (nBatteries < MAX_BATTERIES) {
- const struct dirent* dirEntry = readdir(batteryDir);
- if (!dirEntry)
- break;
+ uint64_t totalFull = 0;
+ uint64_t totalRemain = 0;
+ struct dirent* dirEntry = NULL;
+ while ((dirEntry = readdir(batteryDir))) {
const char* entryName = dirEntry->d_name;
if (!String_startsWith(entryName, "BAT"))
continue;
- batteries[nBatteries] = xStrdup(entryName);
- nBatteries++;
- }
- closedir(batteryDir);
+ char filePath[256];
+ char bufInfo[1024] = {0};
+ xSnprintf(filePath, sizeof(filePath), "%s/%s/info", PROC_BATTERY_DIR, entryName);
+ ssize_t r = xReadfile(filePath, bufInfo, sizeof(bufInfo));
+ if (r < 0)
+ continue;
- unsigned long int total = 0;
- for (unsigned int i = 0; i < nBatteries; i++) {
- char infoPath[30];
- xSnprintf(infoPath, sizeof infoPath, "%s%s/%s", batteryPath, batteries[i], fileName);
+ char bufState[1024] = {0};
+ xSnprintf(filePath, sizeof(filePath), "%s/%s/state", PROC_BATTERY_DIR, entryName);
+ r = xReadfile(filePath, bufState, sizeof(bufState));
+ if (r < 0)
+ continue;
- FILE* file = fopen(infoPath, "r");
- if (!file)
- break;
+ const char* line;
- char* line = NULL;
- for (unsigned short int j = 0; j < lineNum; j++) {
- free(line);
- line = String_readLine(file);
- if (!line)
+ //Getting total charge for all batteries
+ char* buf = bufInfo;
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ char field[100] = {0};
+ int val = 0;
+ if (2 != sscanf(line, "%99[^:]:%d", field, &val))
+ continue;
+
+ if (String_eq(field, "last full capacity")) {
+ totalFull += val;
break;
+ }
}
- fclose(file);
-
- if (!line)
- break;
-
- char* foundNumStr = String_getToken(line, wordNum);
- const unsigned long int foundNum = atoi(foundNumStr);
- free(foundNumStr);
- free(line);
+ //Getting remaining charge for all batteries
+ buf = bufState;
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ char field[100] = {0};
+ int val = 0;
+ if (2 != sscanf(line, "%99[^:]:%d", field, &val))
+ continue;
- total += foundNum;
+ if (String_eq(field, "remaining capacity")) {
+ totalRemain += val;
+ break;
+ }
+ }
}
- for (unsigned int i = 0; i < nBatteries; i++)
- free(batteries[i]);
+ closedir(batteryDir);
- return total;
+ return totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN;
}
static ACPresence procAcpiCheck(void) {
- ACPresence isOn = AC_ERROR;
- const char* power_supplyPath = PROC_POWERSUPPLY_DIR;
- DIR* dir = opendir(power_supplyPath);
- if (!dir)
+ char buffer[1024] = {0};
+ ssize_t r = xReadfile(PROC_POWERSUPPLY_ACSTATE_FILE, buffer, sizeof(buffer));
+ if (r < 1)
return AC_ERROR;
- for (;;) {
- const struct dirent* dirEntry = readdir(dir);
- if (!dirEntry)
- break;
-
- const char* entryName = dirEntry->d_name;
-
- if (entryName[0] != 'A')
- continue;
-
- char statePath[256];
- xSnprintf(statePath, sizeof(statePath), "%s/%s/state", power_supplyPath, entryName);
- FILE* file = fopen(statePath, "r");
- if (!file) {
- isOn = AC_ERROR;
- continue;
- }
- char* line = String_readLine(file);
-
- fclose(file);
-
- if (!line)
- continue;
-
- char* isOnline = String_getToken(line, 2);
- free(line);
-
- if (String_eq(isOnline, "on-line"))
- isOn = AC_PRESENT;
- else
- isOn = AC_ABSENT;
- free(isOnline);
- if (isOn == AC_PRESENT)
- break;
- }
-
- closedir(dir);
-
- return isOn;
-}
-
-static double Platform_Battery_getProcBatInfo(void) {
- const unsigned long int totalFull = parseBatInfo("info", 3, 4);
- if (totalFull == 0)
- return NAN;
-
- const unsigned long int totalRemain = parseBatInfo("state", 5, 3);
- if (totalRemain == 0)
- return NAN;
-
- return totalRemain * 100.0 / (double) totalFull;
+ return String_eq(buffer, "on-line") ? AC_PRESENT : AC_ABSENT;
}
static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {
@@ -750,7 +755,6 @@ static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {
// ----------------------------------------
static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
-
*percent = NAN;
*isOnAC = AC_ERROR;
@@ -758,68 +762,81 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
if (!dir)
return;
- unsigned long int totalFull = 0;
- unsigned long int totalRemain = 0;
-
- for (;;) {
- const struct dirent* dirEntry = readdir(dir);
- if (!dirEntry)
- break;
+ uint64_t totalFull = 0;
+ uint64_t totalRemain = 0;
+ const struct dirent* dirEntry;
+ while ((dirEntry = readdir(dir))) {
const char* entryName = dirEntry->d_name;
- char filePath[256];
- xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/type", entryName);
-
- char type[8];
- ssize_t r = xReadfile(filePath, type, sizeof(type));
- if (r < 3)
+#ifdef HAVE_OPENAT
+ int entryFd = openat(dirfd(dir), entryName, O_DIRECTORY | O_PATH);
+ if (entryFd < 0)
continue;
+#else
+ char entryFd[4096];
+ xSnprintf(entryFd, sizeof(entryFd), SYS_POWERSUPPLY_DIR "/%s", entryName);
+#endif
- if (type[0] == 'B' && type[1] == 'a' && type[2] == 't') {
- xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/uevent", entryName);
+ enum { AC, BAT } type;
+ if (String_startsWith(entryName, "BAT")) {
+ type = BAT;
+ } else if (String_startsWith(entryName, "AC")) {
+ type = AC;
+ } else {
+ char buffer[32];
+ ssize_t ret = xReadfileat(entryFd, "type", buffer, sizeof(buffer));
+ if (ret <= 0)
+ goto next;
+
+ /* drop optional trailing newlines */
+ for (char* buf = &buffer[(size_t)ret - 1]; *buf == '\n'; buf--)
+ *buf = '\0';
+
+ if (String_eq(buffer, "Battery"))
+ type = BAT;
+ else if (String_eq(buffer, "Mains"))
+ type = AC;
+ else
+ goto next;
+ }
+ if (type == BAT) {
char buffer[1024];
- r = xReadfile(filePath, buffer, sizeof(buffer));
- if (r < 0) {
- closedir(dir);
- return;
- }
+ ssize_t r = xReadfileat(entryFd, "uevent", buffer, sizeof(buffer));
+ if (r < 0)
+ goto next;
- char* buf = buffer;
- const char* line;
bool full = false;
bool now = false;
- int fullSize = 0;
- double capacityLevel = NAN;
- #define match(str,prefix) \
- (String_startsWith(str,prefix) ? (str) + strlen(prefix) : NULL)
+ double fullCharge = 0;
+ double capacityLevel = NAN;
+ const char* line;
+ char* buf = buffer;
while ((line = strsep(&buf, "\n")) != NULL) {
- const char* ps = match(line, "POWER_SUPPLY_");
- if (!ps)
+ char field[100] = {0};
+ int val = 0;
+ if (2 != sscanf(line, "POWER_SUPPLY_%99[^=]=%d", field, &val))
continue;
- const char* capacity = match(ps, "CAPACITY=");
- if (capacity)
- capacityLevel = atoi(capacity) / 100.0;
- const char* energy = match(ps, "ENERGY_");
- if (!energy)
- energy = match(ps, "CHARGE_");
- if (!energy)
+
+ if (String_eq(field, "CAPACITY")) {
+ capacityLevel = val / 100.0;
continue;
- const char* value = (!full) ? match(energy, "FULL=") : NULL;
- if (value) {
- fullSize = atoi(value);
- totalFull += fullSize;
+ }
+
+ if (String_eq(field, "ENERGY_FULL") || String_eq(field, "CHARGE_FULL")) {
+ fullCharge = val;
+ totalFull += fullCharge;
full = true;
if (now)
break;
continue;
}
- value = (!now) ? match(energy, "NOW=") : NULL;
- if (value) {
- totalRemain += atoi(value);
+
+ if (String_eq(field, "ENERGY_NOW") || String_eq(field, "CHARGE_NOW")) {
+ totalRemain += val;
now = true;
if (full)
break;
@@ -827,23 +844,18 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
}
}
- #undef match
-
- if (!now && full && !isnan(capacityLevel))
- totalRemain += (capacityLevel * fullSize);
+ if (!now && full && isNonnegative(capacityLevel))
+ totalRemain += capacityLevel * fullCharge;
- } else if (entryName[0] == 'A') {
+ } else if (type == AC) {
if (*isOnAC != AC_ERROR)
- continue;
-
- xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/online", entryName);
+ goto next;
char buffer[2];
-
- r = xReadfile(filePath, buffer, sizeof(buffer));
+ ssize_t r = xReadfileat(entryFd, "online", buffer, sizeof(buffer));
if (r < 1) {
- closedir(dir);
- return;
+ *isOnAC = AC_ERROR;
+ goto next;
}
if (buffer[0] == '0')
@@ -851,7 +863,11 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
else if (buffer[0] == '1')
*isOnAC = AC_PRESENT;
}
+
+next:
+ Compat_openatArgClose(entryFd);
}
+
closedir(dir);
*percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN;
@@ -868,12 +884,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) {
@@ -901,7 +917,7 @@ void Platform_longOptionsUsage(const char* name)
#endif
}
-bool Platform_getLongOption(int opt, int argc, char** argv) {
+CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) {
#ifndef HAVE_LIBCAP
(void) argc;
(void) argv;
@@ -912,7 +928,7 @@ bool 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++];
}
@@ -924,16 +940,16 @@ bool Platform_getLongOption(int opt, int argc, char** argv) {
Platform_capabilitiesMode = CAP_MODE_STRICT;
} else {
fprintf(stderr, "Error: invalid capabilities mode \"%s\".\n", mode);
- exit(1);
+ return STATUS_ERROR_EXIT;
}
- return true;
+ return STATUS_OK;
}
#endif
default:
break;
}
- return false;
+ return STATUS_ERROR_EXIT;
}
#ifdef HAVE_LIBCAP
@@ -1022,20 +1038,46 @@ static int dropCapabilities(enum CapMode mode) {
}
#endif
-void Platform_init(void) {
+bool Platform_init(void) {
#ifdef HAVE_LIBCAP
if (dropCapabilities(Platform_capabilitiesMode) < 0)
- exit(1);
+ return false;
#endif
if (access(PROCDIR, R_OK) != 0) {
fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR);
- exit(1);
+ return false;
}
#ifdef HAVE_SENSORS_SENSORS_H
LibSensors_init();
#endif
+
+ char target[PATH_MAX];
+ ssize_t ret = readlink(PROCDIR "/self/ns/pid", target, sizeof(target) - 1);
+ if (ret > 0) {
+ target[ret] = '\0';
+
+ if (!String_eq("pid:[4026531836]", target)) { // magic constant PROC_PID_INIT_INO from include/linux/proc_ns.h#L46
+ Running_containerized = true;
+ return true; // early return
+ }
+ }
+
+ FILE* fd = fopen(PROCDIR "/1/mounts", "r");
+ if (fd) {
+ char lineBuffer[256];
+ while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ // detect lxc or overlayfs and guess that this means we are running containerized
+ if (String_startsWith(lineBuffer, "lxcfs /proc") || String_startsWith(lineBuffer, "overlay / overlay")) {
+ Running_containerized = true;
+ break;
+ }
+ }
+ fclose(fd);
+ } // if (fd)
+
+ return true;
}
void Platform_done(void) {
diff --git a/linux/Platform.h b/linux/Platform.h
index 0d6d4a9..e99d1a2 100644
--- a/linux/Platform.h
+++ b/linux/Platform.h
@@ -3,12 +3,10 @@
/*
htop - linux/Platform.h
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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,21 +21,27 @@ 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
#endif
-extern const ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -45,17 +49,18 @@ extern const unsigned int Platform_numberOfSignals;
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
-
+bool Platform_init(void);
void Platform_done(void);
+extern bool Running_containerized;
+
void Platform_setBindings(Htop_Action* keys);
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);
@@ -71,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);
@@ -100,7 +105,7 @@ static inline void Platform_getRelease(char** string) {
void Platform_longOptionsUsage(const char* name);
-bool Platform_getLongOption(int opt, int argc, char** argv);
+CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
Generic_gettime_realtime(tv, msec);
@@ -110,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) { }
@@ -120,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 ff34f9b..f796247 100644
--- a/linux/PressureStallMeter.c
+++ b/linux/PressureStallMeter.c
@@ -2,10 +2,12 @@
htop - PressureStallMeter.c
(C) 2004-2011 Hisham H. Muhammad
(C) 2019 Ran Benita
-Released under the GNU GPLv2, see the COPYING file
+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 7317b35..93ebd27 100644
--- a/linux/PressureStallMeter.h
+++ b/linux/PressureStallMeter.h
@@ -6,7 +6,7 @@
htop - PressureStallMeter.h
(C) 2004-2011 Hisham H. Muhammad
(C) 2019 Ran Benita
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 69bdff0..581a982 100644
--- a/linux/ProcessField.h
+++ b/linux/ProcessField.h
@@ -3,7 +3,7 @@
/*
htop - linux/ProcessField.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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/linux/SELinuxMeter.c b/linux/SELinuxMeter.c
index e3b076d..323c2a1 100644
--- a/linux/SELinuxMeter.c
+++ b/linux/SELinuxMeter.c
@@ -1,10 +1,12 @@
/*
htop - SELinuxMeter.c
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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/SELinuxMeter.h b/linux/SELinuxMeter.h
index 453940c..d8a04db 100644
--- a/linux/SELinuxMeter.h
+++ b/linux/SELinuxMeter.h
@@ -3,7 +3,7 @@
/*
htop - SELinuxMeter.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c
index 245bb7b..e13c646 100644
--- a/linux/SystemdMeter.c
+++ b/linux/SystemdMeter.c
@@ -1,14 +1,17 @@
/*
htop - SystemdMeter.c
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -39,6 +42,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 +50,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 +94,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 +112,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 +122,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 +137,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 +195,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 +210,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;
@@ -219,15 +236,19 @@ static void updateViaExec(void) {
exit(1);
dup2(fdnull, STDERR_FILENO);
close(fdnull);
- execlp("systemctl",
- "systemctl",
- "show",
- "--property=SystemState",
- "--property=NFailedUnits",
- "--property=NNames",
- "--property=NJobs",
- "--property=NInstalledJobs",
- NULL);
+ // Use of NULL in variadic functions must have a pointer cast.
+ // The NULL constant is not required by standard to have a pointer type.
+ execlp(
+ "systemctl",
+ "systemctl",
+ "show",
+ user ? "--user" : "--system",
+ "--property=SystemState",
+ "--property=NFailedUnits",
+ "--property=NNames",
+ "--property=NJobs",
+ "--property=NInstalledJobs",
+ (char*)NULL);
exit(127);
}
close(fdpair[1]);
@@ -251,15 +272,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);
}
}
@@ -267,28 +288,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];
}
}
@@ -304,60 +328,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
};
@@ -379,3 +415,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 98ce6f7..50a793b 100644
--- a/linux/SystemdMeter.h
+++ b/linux/SystemdMeter.h
@@ -4,7 +4,7 @@
/*
htop - SystemdMeter.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 e1e27b7..8329f01 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 ddba1ba..14a5215 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 67aadcc..f71a6c2 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 0000000..29e516f
--- /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 0000000..79c50c1
--- /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 7000bd6..9d3aa0b 100644
--- a/netbsd/NetBSDProcessList.h
+++ b/netbsd/NetBSDMachine.h
@@ -1,12 +1,12 @@
-#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
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 2573d06..f58cdf2 100644
--- a/netbsd/NetBSDProcess.c
+++ b/netbsd/NetBSDProcess.c
@@ -4,10 +4,12 @@ htop - NetBSDProcess.c
(C) 2015 Michael McConville
(C) 2021 Santhosh Raju
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -138,16 +140,17 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
},
[ST_UID] = {
.name = "ST_UID",
- .title = " UID ",
+ .title = "UID",
.description = "User ID of the process owner",
.flags = 0,
},
[PERCENT_CPU] = {
.name = "PERCENT_CPU",
- .title = "CPU% ",
+ .title = " CPU%",
.description = "Percentage of the CPU time the process used in the last sampling",
.flags = 0,
.defaultSortDesc = true,
+ .autoWidth = true,
},
[PERCENT_NORM_CPU] = {
.name = "PERCENT_NORM_CPU",
@@ -155,6 +158,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
.description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)",
.flags = 0,
.defaultSortDesc = true,
+ .autoWidth = true,
},
[PERCENT_MEM] = {
.name = "PERCENT_MEM",
@@ -165,7 +169,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
},
[USER] = {
.name = "USER",
- .title = "USER ",
+ .title = "USER ",
.description = "Username of the process owner (or user ID if name cannot be determined)",
.flags = 0,
},
@@ -210,10 +214,10 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
};
-Process* NetBSDProcess_new(const Settings* settings) {
- NetBSDProcess* this = xCalloc(sizeof(NetBSDProcess), 1);
+Process* NetBSDProcess_new(const Machine* host) {
+ NetBSDProcess* this = xCalloc(1, sizeof(NetBSDProcess));
Object_setClass(this, Class(NetBSDProcess));
- Process_init(&this->super, settings);
+ Process_init(&this->super, host);
return &this->super;
}
@@ -223,16 +227,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);
}
@@ -252,11 +260,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 a264749..1c068a4 100644
--- a/netbsd/NetBSDProcess.h
+++ b/netbsd/NetBSDProcess.h
@@ -6,15 +6,15 @@ htop - NetBSDProcess.h
(C) 2015 Michael McConville
(C) 2021 Santhosh Raju
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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 8452050..0000000
--- a/netbsd/NetBSDProcessList.c
+++ /dev/null
@@ -1,498 +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);
-}
-
-/*
- * 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->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);
- 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);
-
- switch (kproc->p_realstat) {
- case SIDL: proc->state = 'I'; 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 = 'P'; break;
- case LSRUN: proc->state = 'R'; break;
- case LSSLEEP: proc->state = 'S'; break;
- case LSSTOP: proc->state = 'T'; break;
- default: proc->state = '?';
- }
- if (proc->state != '?')
- break;
- } else {
- proc->state = '?';
- break;
- }
- }
- break;
- case SSTOP: proc->state = 'T'; break;
- case SZOMB: proc->state = 'Z'; break;
- case SDEAD: proc->state = 'D'; break;
- default: proc->state = '?';
- }
-
- if (Process_isKernelThread(proc)) {
- this->super.kernelThreads++;
- } else if (Process_isUserlandThread(proc)) {
- this->super.userlandThreads++;
- }
-
- this->super.totalTasks++;
- // SRUN ('R') means runnable, not running
- if (proc->state == 'P') {
- 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 0000000..71ae53e
--- /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 0000000..1bcfa98
--- /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 62d90ed..f458c23 100644
--- a/netbsd/Platform.c
+++ b/netbsd/Platform.c
@@ -5,10 +5,12 @@ htop - netbsd/Platform.c
(C) 2021 Santhosh Raju
(C) 2021 Nia Alarie
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+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
@@ -66,7 +69,15 @@ in the source distribution for its full text.
#define prop_number_signed_value prop_number_integer_value
#endif
-const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+};
+
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
/*
* See /usr/include/sys/signal.h
@@ -171,11 +182,13 @@ const MeterClass* const Platform_meterTypes[] = {
&BlankMeter_class,
&DiskIOMeter_class,
&NetworkIOMeter_class,
+ &FileDescriptorMeter_class,
NULL
};
-void Platform_init(void) {
+bool Platform_init(void) {
/* no platform-specific setup needed */
+ return true;
}
void Platform_done(void) {
@@ -187,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);
@@ -218,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;
@@ -242,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;
@@ -259,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) {
@@ -302,7 +316,13 @@ char* Platform_getProcessEnv(pid_t pid) {
for (char** p = ptr; *p; p++) {
size_t len = strlen(*p) + 1;
- if (size + len > capacity) {
+ while (size + len > capacity) {
+ if (capacity > (SIZE_MAX / 2)) {
+ free(env);
+ env = NULL;
+ goto end;
+ }
+
capacity *= 2;
env = xRealloc(env, capacity);
}
@@ -318,24 +338,23 @@ char* Platform_getProcessEnv(pid_t pid) {
env[size + 1] = 0;
}
+end:
(void) kvm_close(kt);
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--) {
@@ -398,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 0420ad1..a543f52 100644
--- a/netbsd/Platform.h
+++ b/netbsd/Platform.h
@@ -6,7 +6,7 @@ htop - netbsd/Platform.h
(C) 2015 Michael McConville
(C) 2021 Santhosh Raju
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -24,6 +24,7 @@ in the source distribution for its full text.
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
#include "generic/gettime.h"
#include "generic/hostname.h"
#include "generic/uname.h"
@@ -33,7 +34,9 @@ in the source distribution for its full text.
#define PLATFORM_LONG_OPTIONS \
// End of list
-extern const ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
/* see /usr/include/sys/signal.h */
extern const SignalItem Platform_signals[];
@@ -42,7 +45,7 @@ extern const unsigned int Platform_numberOfSignals;
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -52,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);
@@ -62,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);
@@ -82,8 +85,8 @@ static inline void Platform_getRelease(char** string) {
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
-static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
- return false;
+static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return STATUS_ERROR_EXIT;
}
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
@@ -94,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) { }
@@ -104,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/netbsd/ProcessField.h b/netbsd/ProcessField.h
index 61e6ca1..87d4a69 100644
--- a/netbsd/ProcessField.h
+++ b/netbsd/ProcessField.h
@@ -3,7 +3,7 @@
/*
htop - netbsd/ProcessField.h
(C) 2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/openbsd/OpenBSDMachine.c b/openbsd/OpenBSDMachine.c
new file mode 100644
index 0000000..0ff893b
--- /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 5245705..51d6a75 100644
--- a/openbsd/OpenBSDProcessList.h
+++ b/openbsd/OpenBSDMachine.h
@@ -1,10 +1,10 @@
-#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
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 90994a7..681d516 100644
--- a/openbsd/OpenBSDProcess.c
+++ b/openbsd/OpenBSDProcess.c
@@ -2,10 +2,12 @@
htop - OpenBSDProcess.c
(C) 2015 Hisham H. Muhammad
(C) 2015 Michael McConville
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -136,16 +138,17 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
},
[ST_UID] = {
.name = "ST_UID",
- .title = " UID ",
+ .title = "UID",
.description = "User ID of the process owner",
.flags = 0,
},
[PERCENT_CPU] = {
.name = "PERCENT_CPU",
- .title = "CPU% ",
+ .title = " CPU%",
.description = "Percentage of the CPU time the process used in the last sampling",
.flags = 0,
.defaultSortDesc = true,
+ .autoWidth = true,
},
[PERCENT_NORM_CPU] = {
.name = "PERCENT_NORM_CPU",
@@ -153,6 +156,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
.description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)",
.flags = 0,
.defaultSortDesc = true,
+ .autoWidth = true,
},
[PERCENT_MEM] = {
.name = "PERCENT_MEM",
@@ -163,7 +167,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
},
[USER] = {
.name = "USER",
- .title = "USER ",
+ .title = "USER ",
.description = "Username of the process owner (or user ID if name cannot be determined)",
.flags = 0,
},
@@ -202,10 +206,10 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
};
-Process* OpenBSDProcess_new(const Settings* settings) {
- OpenBSDProcess* this = xCalloc(sizeof(OpenBSDProcess), 1);
+Process* OpenBSDProcess_new(const Machine* host) {
+ OpenBSDProcess* this = xCalloc(1, sizeof(OpenBSDProcess));
Object_setClass(this, Class(OpenBSDProcess));
- Process_init(&this->super, settings);
+ Process_init(&this->super, host);
return &this->super;
}
@@ -215,17 +219,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);
}
@@ -245,11 +252,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 ce078a0..aac4b31 100644
--- a/openbsd/OpenBSDProcess.h
+++ b/openbsd/OpenBSDProcess.h
@@ -4,15 +4,15 @@
htop - OpenBSDProcess.h
(C) 2015 Hisham H. Muhammad
(C) 2015 Michael McConville
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
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 cb47395..0000000
--- a/openbsd/OpenBSDProcessList.c
+++ /dev/null
@@ -1,480 +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);
-}
-
-/*
- * 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->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);
- 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);
- }
-
- switch (kproc->p_stat) {
- case SIDL: proc->state = 'I'; break;
- case SRUN: proc->state = 'P'; break;
- case SSLEEP: proc->state = 'S'; break;
- case SSTOP: proc->state = 'T'; break;
- case SZOMB: proc->state = 'Z'; break;
- case SDEAD: proc->state = 'D'; break;
- case SONPROC: proc->state = 'R'; break;
- default: proc->state = '?';
- }
-
- if (Process_isKernelThread(proc)) {
- this->super.kernelThreads++;
- } else if (Process_isUserlandThread(proc)) {
- this->super.userlandThreads++;
- }
-
- this->super.totalTasks++;
- if (proc->state == 'R') {
- 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 0000000..f2980a4
--- /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 this;
+}
+
+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 0000000..f911a10
--- /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 a62381f..a8b5d21 100644
--- a/openbsd/Platform.c
+++ b/openbsd/Platform.c
@@ -2,10 +2,12 @@
htop - openbsd/Platform.c
(C) 2014 Hisham H. Muhammad
(C) 2015 Michael McConville
-Released under the GNU GPLv2, see the COPYING file
+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,11 +44,19 @@ 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 ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+};
+
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
/*
* See /usr/include/sys/signal.h
@@ -117,12 +127,14 @@ const MeterClass* const Platform_meterTypes[] = {
&RightCPUs4Meter_class,
&LeftCPUs8Meter_class,
&RightCPUs8Meter_class,
+ &FileDescriptorMeter_class,
&BlankMeter_class,
NULL
};
-void Platform_init(void) {
+bool Platform_init(void) {
/* no platform-specific setup needed */
+ return true;
}
void Platform_done(void) {
@@ -134,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);
@@ -165,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;
@@ -185,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;
@@ -194,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) {
@@ -260,7 +274,13 @@ char* Platform_getProcessEnv(pid_t pid) {
for (char** p = ptr; *p; p++) {
size_t len = strlen(*p) + 1;
- if (size + len > capacity) {
+ while (size + len > capacity) {
+ if (capacity > (SIZE_MAX / 2)) {
+ free(env);
+ env = NULL;
+ goto end;
+ }
+
capacity *= 2;
env = xRealloc(env, capacity);
}
@@ -276,19 +296,38 @@ char* Platform_getProcessEnv(pid_t pid) {
env[size + 1] = 0;
}
+end:
(void) kvm_close(kt);
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 bf43ae5..339616c 100644
--- a/openbsd/Platform.h
+++ b/openbsd/Platform.h
@@ -4,7 +4,7 @@
htop - openbsd/Platform.h
(C) 2014 Hisham H. Muhammad
(C) 2015 Michael McConville
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -20,12 +20,15 @@ in the source distribution for its full text.
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
#include "generic/gettime.h"
#include "generic/hostname.h"
#include "generic/uname.h"
-extern const ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
/* see /usr/include/sys/signal.h */
extern const SignalItem Platform_signals[];
@@ -34,7 +37,7 @@ extern const unsigned int Platform_numberOfSignals;
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -44,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);
@@ -54,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);
@@ -76,8 +79,8 @@ static inline void Platform_getRelease(char** string) {
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
-static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
- return false;
+static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return STATUS_ERROR_EXIT;
}
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
@@ -88,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) { }
@@ -98,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/openbsd/ProcessField.h b/openbsd/ProcessField.h
index be4e51e..b8e8d6b 100644
--- a/openbsd/ProcessField.h
+++ b/openbsd/ProcessField.h
@@ -3,7 +3,7 @@
/*
htop - openbsd/ProcessField.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/pcp-htop.5.in b/pcp-htop.5.in
index de62bfd..ebb4748 100644
--- a/pcp-htop.5.in
+++ b/pcp-htop.5.in
@@ -1,4 +1,4 @@
-.TH "PCP-HTOP" "5" "2021" "@PACKAGE_STRING@" "File Formats"
+.TH "PCP-HTOP" "5" "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 855bb3f..ed585ec 100644
--- a/pcp-htop.c
+++ b/pcp-htop.c
@@ -2,7 +2,7 @@
htop - pcp-htop.c
(C) 2004-2011 Hisham H. Muhammad
(C) 2020-2021 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 0000000..2f9a500
--- /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 0000000..f44a39f
--- /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 0000000..8ae9051
--- /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 0000000..aefd642
--- /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 c8cfe71..4a3c858 100644
--- a/pcp/PCPMetric.c
+++ b/pcp/Metric.c
@@ -1,15 +1,16 @@
/*
-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
+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/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,12 +160,15 @@ 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;
}
- int sts = pmFetch(pcp->totalMetrics, pcp->fetch, &pcp->result);
+ int sts, count = 0;
+ do {
+ sts = pmFetch(pcp->totalMetrics, pcp->fetch, &pcp->result);
+ } while (sts == PM_ERR_IPC && ++count < 3);
if (sts < 0) {
if (pmDebugOptions.appl0)
fprintf(stderr, "Error: cannot fetch metric values: %s\n",
@@ -175,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 7d62503..e72f6e1 100644
--- a/pcp/PCPMetric.h
+++ b/pcp/Metric.h
@@ -1,10 +1,10 @@
-#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
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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,12 +75,14 @@ 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 */
PCP_ZFS_ARC_BONUS_SIZE, /* zfs.arc.bonus_size */
PCP_ZFS_ARC_COMPRESSED_SIZE, /* zfs.arc.compressed_size */
PCP_ZFS_ARC_UNCOMPRESSED_SIZE, /* zfs.arc.uncompressed_size */
+ PCP_ZFS_ARC_C_MIN, /* zfs.arc.c_min */
PCP_ZFS_ARC_C_MAX, /* zfs.arc.c_max */
PCP_ZFS_ARC_DBUF_SIZE, /* zfs.arc.dbuf_size */
PCP_ZFS_ARC_DNODE_SIZE, /* zfs.arc.dnode_size */
@@ -91,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 */
@@ -152,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 29dc9f9..b0bd03e 100644
--- a/pcp/PCPDynamicColumn.c
+++ b/pcp/PCPDynamicColumn.c
@@ -1,9 +1,8 @@
/*
htop - PCPDynamicColumn.c
-(C) 2021 Sohaib Mohammed
-(C) 2021 htop dev team
-(C) 2021 Red Hat, Inc.
-Released under the GNU GPLv2, see the COPYING file
+(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 d0ffe71..ade782b 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 ccea813..11df5f0 100644
--- a/pcp/PCPDynamicMeter.c
+++ b/pcp/PCPDynamicMeter.c
@@ -2,9 +2,10 @@
htop - PCPDynamicMeter.c
(C) 2021 htop dev team
(C) 2021 Red Hat, Inc.
-Released under the GNU GPLv2, see the COPYING file
+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 0e5ddd2..3a72d13 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 0000000..2222822
--- /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 0000000..6248394
--- /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 0000000..2e87253
--- /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 07f6c39..6518bd4 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
+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 fa61506..69e2972 100644
--- a/pcp/PCPProcess.c
+++ b/pcp/PCPProcess.c
@@ -3,10 +3,12 @@ htop - PCPProcess.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
+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,15 +51,16 @@ 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, },
- [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, },
+ [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_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, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, },
@@ -71,10 +74,12 @@ 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, },
+ [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, },
[PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWAPD% ", .description = "Swapin delay %", .flags = 0, .defaultSortDesc = true, },
[M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it.", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
[M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
@@ -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);
+ Process_init(&this->super, host);
return &this->super;
}
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 e844097..e953757 100644
--- a/pcp/PCPProcess.h
+++ b/pcp/PCPProcess.h
@@ -5,17 +5,15 @@ htop - PCPProcess.h
(C) 2014 Hisham H. Muhammad
(C) 2020 htop dev team
(C) 2020-2021 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+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 b55b5a4..0000000
--- a/pcp/PCPProcessList.c
+++ /dev/null
@@ -1,706 +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 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 = 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 != 'Z')
- 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 == 'R')
- pl->runningTasks++;
- pl->kernelThreads++;
- pl->totalTasks++;
- continue;
- }
- if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
- proc->updated = true;
- proc->show = false;
- if (proc->state == 'R')
- pl->runningTasks++;
- pl->userlandThreads++;
- pl->totalTasks++;
- continue;
- }
-
- if (settings->flags & PROCESS_FLAG_IO)
- PCPProcessList_updateIO(pp, pid, offset, now);
-
- PCPProcessList_updateMemory(pp, pid, offset);
-
- if ((settings->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;
-
- 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 != 'Z') {
- PCPProcessList_updateCmdline(proc, pid, offset, command);
- }
-
- if (settings->flags & PROCESS_FLAG_LINUX_CGROUP)
- PCPProcessList_readCGroups(pp, pid, offset);
-
- if (settings->flags & PROCESS_FLAG_LINUX_OOM)
- PCPProcessList_readOomData(pp, pid, offset);
-
- if (settings->flags & PROCESS_FLAG_LINUX_CTXT)
- PCPProcessList_readCtxtData(pp, pid, offset);
-
- if (settings->flags & PROCESS_FLAG_LINUX_SECATTR)
- PCPProcessList_readSecattrData(pp, pid, offset);
-
- if (settings->flags & PROCESS_FLAG_CWD)
- PCPProcessList_readCwd(pp, pid, offset);
-
- if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP)
- PCPProcessList_readAutogroup(pp, pid, offset);
-
- if (proc->state == 'Z' && !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 == 'R')
- 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_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->flags & PROCESS_FLAG_LINUX_CGROUP;
- PCPMetric_enable(PCP_PROC_CGROUPS, flagged && enabled);
- flagged = settings->flags & PROCESS_FLAG_LINUX_OOM;
- PCPMetric_enable(PCP_PROC_OOMSCORE, flagged && enabled);
- flagged = settings->flags & PROCESS_FLAG_LINUX_CTXT;
- PCPMetric_enable(PCP_PROC_VCTXSW, flagged && enabled);
- PCPMetric_enable(PCP_PROC_NVCTXSW, flagged && enabled);
- flagged = settings->flags & PROCESS_FLAG_LINUX_SECATTR;
- PCPMetric_enable(PCP_PROC_LABELS, flagged && enabled);
- flagged = settings->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;
- PCPMetric_fetch(&timestamp);
-
- 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 0000000..4999bdc
--- /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 0000000..e55c85b
--- /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 29055b9..0b5f334 100644
--- a/pcp/Platform.c
+++ b/pcp/Platform.c
@@ -1,9 +1,9 @@
/*
htop - linux/Platform.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
+(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"
@@ -54,9 +58,20 @@ in the source distribution for its full text.
Platform* pcp;
-ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, (int)M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+ {
+ .name = "I/O",
+ .columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command",
+ .sortKey = "IO_RATE",
+ },
+};
-int Platform_numberOfFields = LAST_PROCESSFIELD;
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
@@ -95,6 +110,7 @@ const MeterClass* const Platform_meterTypes[] = {
&PressureStallCPUSomeMeter_class,
&PressureStallIOSomeMeter_class,
&PressureStallIOFullMeter_class,
+ &PressureStallIRQFullMeter_class,
&PressureStallMemorySomeMeter_class,
&PressureStallMemoryFullMeter_class,
&ZfsArcMeter_class,
@@ -103,6 +119,7 @@ const MeterClass* const Platform_meterTypes[] = {
&DiskIOMeter_class,
&NetworkIOMeter_class,
&SysArchMeter_class,
+ &FileDescriptorMeter_class,
NULL
};
@@ -160,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",
@@ -167,6 +185,7 @@ static const char* Platform_metricNames[] = {
[PCP_ZFS_ARC_BONUS_SIZE] = "zfs.arc.bonus_size",
[PCP_ZFS_ARC_COMPRESSED_SIZE] = "zfs.arc.compressed_size",
[PCP_ZFS_ARC_UNCOMPRESSED_SIZE] = "zfs.arc.uncompressed_size",
+ [PCP_ZFS_ARC_C_MIN] = "zfs.arc.c_min",
[PCP_ZFS_ARC_C_MAX] = "zfs.arc.c_max",
[PCP_ZFS_ARC_DBUF_SIZE] = "zfs.arc.dbuf_size",
[PCP_ZFS_ARC_DNODE_SIZE] = "zfs.arc.dnode_size",
@@ -178,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",
@@ -236,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) {
@@ -257,7 +311,7 @@ size_t Platform_addMetric(PCPMetric id, const char* name) {
/* global state from the environment and command line arguments */
pmOptions opts;
-void Platform_init(void) {
+bool Platform_init(void) {
const char* source;
if (opts.context == PM_CONTEXT_ARCHIVE) {
source = opts.archives[0];
@@ -277,12 +331,12 @@ void Platform_init(void) {
}
if (sts < 0) {
fprintf(stderr, "Cannot setup PCP metric source: %s\n", pmErrStr(sts));
- exit(1);
+ return false;
}
/* setup timezones and other general startup preparation completion */
if (pmGetContextOptions(sts, &opts) < 0 || opts.errors) {
pmflush();
- exit(1);
+ return false;
}
pcp = xCalloc(1, sizeof(Platform));
@@ -305,62 +359,60 @@ void 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) {
fprintf(stderr, "Error: cannot lookup metric names: %s\n", pmErrStr(sts));
- exit(1);
+ Platform_done();
+ 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();
Platform_getRelease(0);
Platform_getMaxCPU();
Platform_getMaxPid();
+
+ return true;
}
void Platform_dynamicColumnsDone(Hashtable* columns) {
@@ -371,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)
@@ -390,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;
}
@@ -399,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;
@@ -411,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 UINT_MAX;
pcp->pidmax = value.l;
return pcp->pidmax;
}
@@ -434,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;
@@ -448,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;
@@ -478,38 +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) {
- this->values[0] -= ppl->zfs.size;
- this->values[3] += ppl->zfs.size;
+ 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 (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;
@@ -520,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) {
@@ -566,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 */
@@ -620,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;
@@ -639,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;
@@ -661,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;
}
@@ -674,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;
@@ -697,16 +794,16 @@ void Platform_longOptionsUsage(ATTR_UNUSED const char* name) {
" --timezone=TZ set reporting timezone\n");
}
-bool Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) {
+CommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) {
/* libpcp export without a header definition */
extern void __pmAddOptHost(pmOptions*, char*);
switch (opt) {
case PLATFORM_LONGOPT_HOST: /* --host=HOSTSPEC */
if (argv[optind][0] == '\0')
- return false;
- __pmAddOptHost(&opts, optarg);
- return true;
+ return STATUS_ERROR_EXIT;
+ __pmAddOptHost(&opts, optarg);
+ return STATUS_OK;
case PLATFORM_LONGOPT_HOSTZONE: /* --hostzone */
if (opts.timezone) {
@@ -715,23 +812,24 @@ bool Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) {
} else {
opts.tzflag = 1;
}
- return true;
+ return STATUS_OK;
case PLATFORM_LONGOPT_TIMEZONE: /* --timezone=TZ */
if (argv[optind][0] == '\0')
- return false;
+ return STATUS_ERROR_EXIT;
if (opts.tzflag) {
pmprintf("%s: at most one of -Z and -z allowed\n", pmGetProgname());
opts.errors++;
} else {
opts.timezone = optarg;
}
- return true;
+ return STATUS_OK;
default:
break;
}
- return false;
+
+ return STATUS_ERROR_EXIT;
}
void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
@@ -781,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)
@@ -802,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 14e5746..f43ed54 100644
--- a/pcp/Platform.h
+++ b/pcp/Platform.h
@@ -5,7 +5,7 @@ htop - pcp/Platform.h
(C) 2014 Hisham H. Muhammad
(C) 2020-2021 htop dev team
(C) 2020-2021 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -34,10 +34,12 @@ in the source distribution for its full text.
#include "ProcessLocksScreen.h"
#include "RichString.h"
#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_ {
@@ -50,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 */
@@ -57,9 +60,9 @@ typedef struct Platform_ {
unsigned int ncpu; /* maximum processor count configured */
} Platform;
-extern ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
-extern int Platform_numberOfFields;
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -67,7 +70,7 @@ extern const unsigned int Platform_numberOfSignals;
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -81,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);
@@ -97,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);
@@ -126,11 +127,13 @@ enum {
void Platform_longOptionsUsage(const char* name);
-bool Platform_getLongOption(int opt, int argc, char** argv);
+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);
@@ -150,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 979addc..6342561 100644
--- a/pcp/ProcessField.h
+++ b/pcp/ProcessField.h
@@ -5,7 +5,7 @@ htop - pcp/ProcessField.h
(C) 2014 Hisham H. Muhammad
(C) 2021 htop dev team
(C) 2020-2021 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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 0000000..e6cdf89
--- /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 0000000..0ddc65c
--- /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 0000000..3a431db
--- /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 0000000..17bc1e3
--- /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 0000000..7399f82
--- /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 0000000..d706e76
--- /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 0000000..6c6b867
--- /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 0000000..06f3bf2
--- /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 0000000..ec209b0
--- /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/scripts/htop_suppressions.valgrind b/scripts/htop_suppressions.valgrind
index 3e5c9f1..0ba4539 100644
--- a/scripts/htop_suppressions.valgrind
+++ b/scripts/htop_suppressions.valgrind
@@ -45,3 +45,13 @@
fun:CRT_setColors
fun:CRT_init
}
+
+{
+ <devstat internal memory>
+ Memcheck:Leak
+ match-leak-kinds: possible,reachable
+ ...
+ obj:*/libdevstat*
+ ...
+ fun:Platform_getDiskIO
+}
diff --git a/solaris/Platform.c b/solaris/Platform.c
index 7439195..5faa91a 100644
--- a/solaris/Platform.c
+++ b/solaris/Platform.c
@@ -3,10 +3,12 @@ htop - solaris/Platform.c
(C) 2014 Hisham H. Muhammad
(C) 2015 David C. Hunt
(C) 2017,2018 Guy M. Broome
-Released under the GNU GPLv2, see the COPYING file
+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,12 +36,24 @@ 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[] = {
+ {
+ .name = "Default",
+ .columns = "PID LWPID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+};
+
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
+
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
{ .name = " 1 SIGHUP", .number = 1 },
@@ -87,8 +101,6 @@ const SignalItem Platform_signals[] = {
const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
-const ProcessField Platform_defaultFields[] = { PID, LWPID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
-
const MeterClass* const Platform_meterTypes[] = {
&CPUMeter_class,
&ClockMeter_class,
@@ -122,8 +134,9 @@ const MeterClass* const Platform_meterTypes[] = {
NULL
};
-void Platform_init(void) {
+bool Platform_init(void) {
/* no platform-specific setup needed */
+ return true;
}
void Platform_done(void) {
@@ -135,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;
@@ -164,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();
@@ -185,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) {
@@ -206,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 (super->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;
@@ -226,48 +239,55 @@ 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 = (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 = (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) {
envAccum* accump = accum;
(void) Phandle;
(void) addr;
+
size_t thissz = strlen(str);
- if ((thissz + 2) > (accump->capacity - accump->size)) {
- accump->env = xRealloc(accump->env, accump->capacity *= 2);
- }
- if ((thissz + 2) > (accump->capacity - accump->size)) {
- return 1;
+
+ while ((thissz + 2) > (accump->capacity - accump->size)) {
+ if (accump->capacity > (SIZE_MAX / 2))
+ return 1;
+
+ accump->capacity *= 2;
+ accump->env = xRealloc(accump->env, accump->capacity);
}
- strlcpy( accump->env + accump->size, str, (accump->capacity - accump->size));
+
+ strlcpy( accump->env + accump->size, str, accump->capacity - accump->size);
strncpy( accump->env + accump->size + thissz + 1, "\n", 2);
- accump->size = accump->size + thissz + 1;
+
+ accump->size += thissz + 1;
return 0;
}
@@ -290,13 +310,8 @@ char* Platform_getProcessEnv(pid_t pid) {
Prelease(Phandle, 0);
strncpy( envBuilder.env + envBuilder.size, "\0", 1);
- return envBuilder.env;
-}
-char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
- (void)pid;
- (void)inode;
- return NULL;
+ return xRealloc(envBuilder.env, envBuilder.size + 1);
}
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
@@ -304,6 +319,11 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
return NULL;
}
+void Platform_getFileDescriptors(double* used, double* max) {
+ *used = NAN;
+ *max = NAN;
+}
+
bool Platform_getDiskIO(DiskIOData* data) {
// TODO
(void)data;
diff --git a/solaris/Platform.h b/solaris/Platform.h
index 0a379a1..1a31c2e 100644
--- a/solaris/Platform.h
+++ b/solaris/Platform.h
@@ -5,12 +5,10 @@ htop - solaris/Platform.h
(C) 2014 Hisham H. Muhammad
(C) 2015 David C. Hunt
(C) 2017,2018 Guy M. Broome
-Released under the GNU GPLv2, see the COPYING file
+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,6 +28,7 @@ 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"
@@ -51,15 +50,17 @@ typedef struct envAccum_ {
char* env;
} envAccum;
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
+
extern const SignalItem Platform_signals[];
extern const unsigned int Platform_numberOfSignals;
-extern const ProcessField Platform_defaultFields[];
-
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -69,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);
@@ -83,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);
@@ -105,8 +106,8 @@ static inline void Platform_getRelease(char** string) {
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
-static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
- return false;
+static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return STATUS_ERROR_EXIT;
}
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
@@ -129,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) { }
@@ -139,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/ProcessField.h b/solaris/ProcessField.h
index 727c641..65dbcdc 100644
--- a/solaris/ProcessField.h
+++ b/solaris/ProcessField.h
@@ -3,7 +3,7 @@
/*
htop - solaris/ProcessField.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/solaris/SolarisMachine.c b/solaris/SolarisMachine.c
new file mode 100644
index 0000000..4f740d3
--- /dev/null
+++ b/solaris/SolarisMachine.c
@@ -0,0 +1,336 @@
+/*
+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;
+ hsuper->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;
+
+ 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 f653b7c..2208a88 100644
--- a/solaris/SolarisProcessList.h
+++ b/solaris/SolarisMachine.h
@@ -1,31 +1,26 @@
-#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
+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 "UsersTable.h"
-#include "solaris/SolarisProcess.h"
-
#include "zfs/ZfsArcStats.h"
@@ -47,19 +42,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 57071ab..449861b 100644
--- a/solaris/SolarisProcess.c
+++ b/solaris/SolarisProcess.c
@@ -2,10 +2,12 @@
htop - SolarisProcess.c
(C) 2015 Hisham H. Muhammad
(C) 2017,2018 Guy M. Broome
-Released under the GNU GPLv2, see the COPYING file
+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>
@@ -14,7 +16,7 @@ in the source distribution for its full text.
#include <sys/syscall.h>
#include "Process.h"
-#include "ProcessList.h"
+#include "ProcessTable.h"
#include "CRT.h"
#include "solaris/Platform.h"
@@ -39,11 +41,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
+ [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_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, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
@@ -59,10 +61,10 @@ 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);
+ Process_init(&this->super, host);
return &this->super;
}
@@ -73,11 +75,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 +89,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 +132,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 0fd458d..1a8d18c 100644
--- a/solaris/SolarisProcess.h
+++ b/solaris/SolarisProcess.h
@@ -4,12 +4,10 @@
htop - SolarisProcess.h
(C) 2015 Hisham H. Muhammad
(C) 2017,2018 Guy M. Broome
-Released under the GNU GPLv2, see the COPYING file
+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,7 @@ in the source distribution for its full text.
#undef ERR
#define ERR (-1)
-#include "Settings.h"
+#include "Machine.h"
typedef struct SolarisProcess_ {
@@ -42,7 +40,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 45bc5ba..0000000
--- a/solaris/SolarisProcessList.c
+++ /dev/null
@@ -1,542 +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);
-}
-
-/* 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 = _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->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;
- proc->time = _psinfo->pr_time.tv_sec;
- 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 == 'O') {
- pl->runningTasks++;
- }
- } else if (!proc->isKernelThread) {
- if (proc->state == 'O') {
- 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;
- proc->time = _lwpsinfo->pr_time.tv_sec;
- 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 0000000..549c753
--- /dev/null
+++ b/solaris/SolarisProcessTable.c
@@ -0,0 +1,268 @@
+/*
+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/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;
+ SolarisProcessTable* spt = (SolarisProcessTable*) listptr;
+ Machine* host = pt->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->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(spt->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->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 0000000..7c5ae8f
--- /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 6fbe518..dbfddd9 100644
--- a/unsupported/Platform.c
+++ b/unsupported/Platform.c
@@ -2,7 +2,7 @@
htop - unsupported/Platform.c
(C) 2014 Hisham H. Muhammad
(C) 2015 David C. Hunt
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -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"
@@ -27,14 +28,22 @@ in the source distribution for its full text.
#include "UptimeMeter.h"
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+};
+
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
+
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
};
const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
-const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
-
const MeterClass* const Platform_meterTypes[] = {
&CPUMeter_class,
&ClockMeter_class,
@@ -62,14 +71,16 @@ const MeterClass* const Platform_meterTypes[] = {
&RightCPUs4Meter_class,
&LeftCPUs8Meter_class,
&RightCPUs8Meter_class,
+ &FileDescriptorMeter_class,
&BlankMeter_class,
NULL
};
static const char Platform_unsupported[] = "unsupported";
-void Platform_init(void) {
+bool Platform_init(void) {
/* no platform-specific setup needed */
+ return true;
}
void Platform_done(void) {
@@ -81,7 +92,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
return 0;
}
@@ -91,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) {
@@ -120,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 cdf1d0f..c4cd06a 100644
--- a/unsupported/Platform.h
+++ b/unsupported/Platform.h
@@ -4,10 +4,13 @@
htop - unsupported/Platform.h
(C) 2014 Hisham H. Muhammad
(C) 2015 David C. Hunt
-Released under the GNU GPLv2, see the COPYING file
+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"
@@ -15,19 +18,22 @@ in the source distribution for its full text.
#include "NetworkIOMeter.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
#include "generic/gettime.h"
#include "unsupported/UnsupportedProcess.h"
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
+
extern const SignalItem Platform_signals[];
extern const unsigned int Platform_numberOfSignals;
-extern const ProcessField Platform_defaultFields[];
-
extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -37,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);
@@ -47,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);
@@ -65,8 +71,8 @@ void Platform_getRelease(char** string);
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
-static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
- return false;
+static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
+ return STATUS_ERROR_EXIT;
}
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
@@ -77,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) { }
@@ -87,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/ProcessField.h b/unsupported/ProcessField.h
index 8c07107..0c908ea 100644
--- a/unsupported/ProcessField.h
+++ b/unsupported/ProcessField.h
@@ -3,7 +3,7 @@
/*
htop - unsupported/ProcessField.h
(C) 2020 htop dev team
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/unsupported/UnsupportedMachine.c b/unsupported/UnsupportedMachine.c
new file mode 100644
index 0000000..a6635ac
--- /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 0000000..4ec760f
--- /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 1b9159b..3d6d883 100644
--- a/unsupported/UnsupportedProcess.c
+++ b/unsupported/UnsupportedProcess.c
@@ -1,7 +1,7 @@
/*
htop - UnsupportedProcess.c
(C) 2015 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
@@ -34,20 +34,20 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
+ [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_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, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
};
-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,12 +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,
- .getCommandStr = NULL,
.compareByKey = UnsupportedProcess_compareByKey
};
diff --git a/unsupported/UnsupportedProcess.h b/unsupported/UnsupportedProcess.h
index f39a3fb..21956dd 100644
--- a/unsupported/UnsupportedProcess.h
+++ b/unsupported/UnsupportedProcess.h
@@ -3,11 +3,11 @@
/*
htop - UnsupportedProcess.h
(C) 2015 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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.c b/unsupported/UnsupportedProcessList.c
deleted file mode 100644
index 0486e49..0000000
--- a/unsupported/UnsupportedProcessList.c
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
-htop - UnsupportedProcessList.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 <stdlib.h>
-#include <string.h>
-
-#include "ProcessList.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);
-
- this->existingCPUs = 1;
- this->activeCPUs = 1;
-
- return this;
-}
-
-void ProcessList_delete(ProcessList* this) {
- ProcessList_done(this);
- free(this);
-}
-
-void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
-
- // in pause mode only gather global data for meters (CPU/memory/...)
- if (pauseProcessUpdate) {
- return;
- }
-
- bool preExisting = true;
- Process* proc;
-
- proc = ProcessList_getProcess(super, 1, &preExisting, UnsupportedProcess_new);
-
- /* Empty values */
- proc->time = proc->time + 10;
- proc->pid = 1;
- proc->ppid = 1;
- proc->tgid = 0;
-
- Process_updateComm(proc, "commof16char");
- Process_updateCmdline(proc, "<unsupported architecture>", 0, 0);
- Process_updateExe(proc, "/path/to/executable");
-
- if (proc->settings->flags & PROCESS_FLAG_CWD) {
- free_and_xStrdup(&proc->procCwd, "/current/working/directory");
- }
-
- proc->updated = true;
-
- proc->state = 'R';
- proc->isKernelThread = false;
- proc->isUserlandThread = false;
- proc->show = true; /* Reflected in proc->settings-> "hideXXX" really */
- proc->pgrp = 0;
- proc->session = 0;
- proc->tty_nr = 0;
- proc->tty_name = NULL;
- proc->tpgid = 0;
- proc->processor = 0;
-
- proc->percent_cpu = 2.5;
- proc->percent_mem = 2.5;
-
- proc->st_uid = 0;
- proc->user = "nobody"; /* Update whenever proc->st_uid is changed */
-
- proc->priority = 0;
- proc->nice = 0;
- proc->nlwp = 1;
- proc->starttime_ctime = 1433116800; // Jun 01, 2015
- Process_fillStarttimeBuffer(proc);
-
- proc->m_virt = 100;
- proc->m_resident = 100;
-
- proc->minflt = 20;
- 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;
-}
diff --git a/unsupported/UnsupportedProcessList.h b/unsupported/UnsupportedProcessList.h
deleted file mode 100644
index 08dae0c..0000000
--- 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/UnsupportedProcessTable.c b/unsupported/UnsupportedProcessTable.c
new file mode 100644
index 0000000..db43f1e
--- /dev/null
+++ b/unsupported/UnsupportedProcessTable.c
@@ -0,0 +1,90 @@
+/*
+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 "config.h" // IWYU pragma: keep
+
+#include "UnsupportedProcessTable.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "ProcessTable.h"
+#include "UnsupportedProcess.h"
+
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ UnsupportedProcessTable* this = xCalloc(1, sizeof(UnsupportedProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(Process), host, pidMatchList);
+
+ return this;
+}
+
+void ProcessTable_delete(Object* cast) {
+ UnsupportedProcessTable* this = (UnsupportedProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ free(this);
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ bool preExisting = true;
+ Process* proc;
+
+ proc = ProcessTable_getProcess(super, 1, &preExisting, UnsupportedProcess_new);
+
+ /* Empty values */
+ proc->time = proc->time + 10;
+ 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");
+
+ const Settings* settings = proc->host->settings;
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ free_and_xStrdup(&proc->procCwd, "/current/working/directory");
+ }
+
+ proc->super.updated = true;
+
+ proc->state = RUNNING;
+ proc->isKernelThread = false;
+ proc->isUserlandThread = false;
+ proc->super.show = true; /* Reflected in settings-> "hideXXX" really */
+ proc->pgrp = 0;
+ proc->session = 0;
+ proc->tty_nr = 0;
+ proc->tty_name = NULL;
+ proc->tpgid = 0;
+ proc->processor = 0;
+
+ proc->percent_cpu = 2.5;
+ proc->percent_mem = 2.5;
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ proc->st_uid = 0;
+ proc->user = "nobody"; /* Update whenever proc->st_uid is changed */
+
+ proc->priority = 0;
+ proc->nice = 0;
+ proc->nlwp = 1;
+ proc->starttime_ctime = 1433116800; // Jun 01, 2015
+ Process_fillStarttimeBuffer(proc);
+
+ proc->m_virt = 100;
+ proc->m_resident = 100;
+
+ proc->minflt = 20;
+ proc->majflt = 20;
+
+ if (!preExisting)
+ ProcessTable_add(super, proc);
+}
diff --git a/unsupported/UnsupportedProcessTable.h b/unsupported/UnsupportedProcessTable.h
new file mode 100644
index 0000000..1de8087
--- /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 7653a35..32f5bb3 100644
--- a/zfs/ZfsArcMeter.c
+++ b/zfs/ZfsArcMeter.c
@@ -1,10 +1,12 @@
/*
htop - ZfsArcMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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/ZfsArcMeter.h b/zfs/ZfsArcMeter.h
index 19a0454..3f6ea5a 100644
--- a/zfs/ZfsArcMeter.h
+++ b/zfs/ZfsArcMeter.h
@@ -3,7 +3,7 @@
/*
htop - ZfsArcMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
diff --git a/zfs/ZfsArcStats.h b/zfs/ZfsArcStats.h
index d891dc2..1fe7236 100644
--- a/zfs/ZfsArcStats.h
+++ b/zfs/ZfsArcStats.h
@@ -3,13 +3,14 @@
/*
htop - ZfsArcStats.h
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
typedef struct ZfsArcStats_ {
int enabled;
int isCompressed;
+ unsigned long long int min;
unsigned long long int max;
unsigned long long int size;
unsigned long long int MFU;
diff --git a/zfs/ZfsCompressedArcMeter.c b/zfs/ZfsCompressedArcMeter.c
index 29ac3c4..4d47040 100644
--- a/zfs/ZfsCompressedArcMeter.c
+++ b/zfs/ZfsCompressedArcMeter.c
@@ -1,10 +1,12 @@
/*
htop - ZfsCompressedArcMeter.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+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>
diff --git a/zfs/ZfsCompressedArcMeter.h b/zfs/ZfsCompressedArcMeter.h
index 9a53430..6e2f45f 100644
--- a/zfs/ZfsCompressedArcMeter.h
+++ b/zfs/ZfsCompressedArcMeter.h
@@ -3,7 +3,7 @@
/*
htop - ZfsCompressedArcMeter.h
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

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