aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Lange <DLange@git.local>2023-02-05 03:20:15 +0100
committerDaniel Lange <DLange@git.local>2023-02-05 03:20:15 +0100
commitb90fdf618f13b2ba4f64ba0f1861088c2ae58844 (patch)
treeda70bf44b2423f6f8e9a070c063fed79d190b489
parent65357c8c46154de4e4eca14075bfe5523bb5fc14 (diff)
downloaddebian_htop-b90fdf618f13b2ba4f64ba0f1861088c2ae58844.tar.gz
debian_htop-b90fdf618f13b2ba4f64ba0f1861088c2ae58844.tar.bz2
debian_htop-b90fdf618f13b2ba4f64ba0f1861088c2ae58844.zip
New upstream version 3.2.2
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--.github/dependabot.yml9
-rw-r--r--.github/workflows/build_release.yml51
-rw-r--r--.github/workflows/ci.yml113
-rw-r--r--.github/workflows/codeql-analysis.yml49
-rw-r--r--.gitignore11
-rw-r--r--.travis.yml18
-rw-r--r--Action.c579
-rw-r--r--Action.h31
-rw-r--r--Affinity.c34
-rw-r--r--Affinity.h22
-rw-r--r--AffinityPanel.c54
-rw-r--r--AffinityPanel.h7
-rw-r--r--AvailableColumnsPanel.c59
-rw-r--r--AvailableColumnsPanel.h6
-rw-r--r--AvailableMetersPanel.c108
-rw-r--r--AvailableMetersPanel.h12
-rw-r--r--BatteryMeter.c35
-rw-r--r--BatteryMeter.h3
-rw-r--r--CONTRIBUTING.md6
-rw-r--r--COPYING58
-rw-r--r--CPUMeter.c200
-rw-r--r--CPUMeter.h3
-rw-r--r--CRT.c630
-rw-r--r--CRT.h86
-rw-r--r--CategoriesPanel.c90
-rw-r--r--CategoriesPanel.h5
-rw-r--r--ChangeLog333
-rw-r--r--ClockMeter.c13
-rw-r--r--ClockMeter.h3
-rw-r--r--ColorsPanel.c35
-rw-r--r--ColorsPanel.h7
-rw-r--r--ColumnsPanel.c63
-rw-r--r--ColumnsPanel.h10
-rw-r--r--CommandLine.c425
-rw-r--r--CommandLine.h19
-rw-r--r--CommandScreen.c8
-rw-r--r--Compat.c43
-rw-r--r--Compat.h7
-rw-r--r--DateMeter.c16
-rw-r--r--DateMeter.h3
-rw-r--r--DateTimeMeter.c16
-rw-r--r--DateTimeMeter.h3
-rw-r--r--DiskIOMeter.c107
-rw-r--r--DiskIOMeter.h9
-rw-r--r--DisplayOptionsPanel.c42
-rw-r--r--DisplayOptionsPanel.h3
-rw-r--r--DynamicColumn.c66
-rw-r--r--DynamicColumn.h34
-rw-r--r--DynamicMeter.c131
-rw-r--r--DynamicMeter.h28
-rw-r--r--EnvScreen.c29
-rw-r--r--EnvScreen.h5
-rw-r--r--FunctionBar.c20
-rw-r--r--FunctionBar.h7
-rw-r--r--Hashtable.c109
-rw-r--r--Hashtable.h32
-rw-r--r--Header.c265
-rw-r--r--Header.h24
-rw-r--r--HeaderLayout.h78
-rw-r--r--HeaderOptionsPanel.c88
-rw-r--r--HeaderOptionsPanel.h26
-rw-r--r--HostnameMeter.c10
-rw-r--r--HostnameMeter.h3
-rw-r--r--IncSet.c101
-rw-r--r--IncSet.h12
-rw-r--r--InfoScreen.c67
-rw-r--r--InfoScreen.h1
-rw-r--r--ListItem.c26
-rw-r--r--ListItem.h11
-rw-r--r--LoadAverageMeter.c73
-rw-r--r--LoadAverageMeter.h3
-rw-r--r--Macros.h37
-rw-r--r--MainPanel.c110
-rw-r--r--MainPanel.h9
-rw-r--r--Makefile.am254
-rw-r--r--MemoryMeter.c63
-rw-r--r--MemoryMeter.h11
-rw-r--r--MemorySwapMeter.c107
-rw-r--r--MemorySwapMeter.h15
-rw-r--r--Meter.c175
-rw-r--r--Meter.h41
-rw-r--r--MetersPanel.c28
-rw-r--r--MetersPanel.h2
-rw-r--r--NetworkIOMeter.c144
-rw-r--r--NetworkIOMeter.h8
-rw-r--r--Object.c6
-rw-r--r--Object.h13
-rw-r--r--OpenFilesScreen.c67
-rw-r--r--OpenFilesScreen.h3
-rw-r--r--OptionItem.c46
-rw-r--r--OptionItem.h12
-rw-r--r--Panel.c144
-rw-r--r--Panel.h55
-rw-r--r--Process.c1277
-rw-r--r--Process.h323
-rw-r--r--ProcessList.c627
-rw-r--r--ProcessList.h75
-rw-r--r--ProcessLocksScreen.c17
-rw-r--r--ProcessLocksScreen.h6
-rw-r--r--ProvideCurses.h4
-rw-r--r--README164
-rw-r--r--RichString.c160
-rw-r--r--RichString.h44
-rw-r--r--ScreenManager.c210
-rw-r--r--ScreenManager.h16
-rw-r--r--ScreensPanel.c340
-rw-r--r--ScreensPanel.h53
-rw-r--r--Settings.c729
-rw-r--r--Settings.h83
-rw-r--r--SignalsPanel.c11
-rw-r--r--SignalsPanel.h13
-rw-r--r--SwapMeter.c36
-rw-r--r--SwapMeter.h8
-rw-r--r--SysArchMeter.c44
-rw-r--r--SysArchMeter.h14
-rw-r--r--TESTPLAN4
-rw-r--r--TasksMeter.c58
-rw-r--r--TasksMeter.h3
-rw-r--r--TraceScreen.c62
-rw-r--r--TraceScreen.h10
-rw-r--r--UptimeMeter.c10
-rw-r--r--UptimeMeter.h3
-rw-r--r--UsersTable.c4
-rw-r--r--UsersTable.h3
-rw-r--r--Vector.c83
-rw-r--r--Vector.h28
-rw-r--r--XUtils.c143
-rw-r--r--XUtils.h35
-rw-r--r--configure.ac758
-rw-r--r--darwin/DarwinProcess.c249
-rw-r--r--darwin/DarwinProcess.h13
-rw-r--r--darwin/DarwinProcessList.c95
-rw-r--r--darwin/DarwinProcessList.h6
-rw-r--r--darwin/Platform.c279
-rw-r--r--darwin/Platform.h86
-rw-r--r--darwin/PlatformHelpers.c126
-rw-r--r--darwin/PlatformHelpers.h40
-rw-r--r--darwin/ProcessField.h18
-rw-r--r--docs/images/screenshot.pngbin80937 -> 46142 bytes
-rw-r--r--docs/styleguide.md27
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.c132
-rw-r--r--dragonflybsd/DragonFlyBSDProcess.h30
-rw-r--r--dragonflybsd/DragonFlyBSDProcessList.c287
-rw-r--r--dragonflybsd/DragonFlyBSDProcessList.h26
-rw-r--r--dragonflybsd/Platform.c97
-rw-r--r--dragonflybsd/Platform.h79
-rw-r--r--dragonflybsd/ProcessField.h19
-rw-r--r--freebsd/FreeBSDProcess.c132
-rw-r--r--freebsd/FreeBSDProcess.h30
-rw-r--r--freebsd/FreeBSDProcessList.c398
-rw-r--r--freebsd/FreeBSDProcessList.h17
-rw-r--r--freebsd/Platform.c109
-rw-r--r--freebsd/Platform.h75
-rw-r--r--freebsd/ProcessField.h20
-rw-r--r--generic/gettime.c61
-rw-r--r--generic/gettime.h18
-rw-r--r--generic/hostname.c17
-rw-r--r--generic/hostname.h15
-rw-r--r--generic/openzfs_sysctl.c (renamed from zfs/openzfs_sysctl.c)12
-rw-r--r--generic/openzfs_sysctl.h (renamed from zfs/openzfs_sysctl.h)5
-rw-r--r--generic/uname.c98
-rw-r--r--generic/uname.h12
-rw-r--r--htop.1.in260
-rw-r--r--htop.c353
-rw-r--r--htop.pngbin3537 -> 2616 bytes
-rw-r--r--iwyu/htop.imp6
-rw-r--r--linux/CGroupUtils.c341
-rw-r--r--linux/CGroupUtils.h16
-rw-r--r--linux/HugePageMeter.c106
-rw-r--r--linux/HugePageMeter.h15
-rw-r--r--linux/IOPriority.h4
-rw-r--r--linux/IOPriorityPanel.c7
-rw-r--r--linux/IOPriorityPanel.h4
-rw-r--r--linux/LibSensors.c211
-rw-r--r--linux/LibSensors.h11
-rw-r--r--linux/LinuxProcess.c830
-rw-r--r--linux/LinuxProcess.h181
-rw-r--r--linux/LinuxProcessList.c1366
-rw-r--r--linux/LinuxProcessList.h19
-rw-r--r--linux/Platform.c857
-rw-r--r--linux/Platform.h97
-rw-r--r--linux/PressureStallMeter.c26
-rw-r--r--linux/PressureStallMeter.h3
-rw-r--r--linux/ProcessField.h52
-rw-r--r--linux/SELinuxMeter.c13
-rw-r--r--linux/SELinuxMeter.h3
-rw-r--r--linux/SystemdMeter.c119
-rw-r--r--linux/SystemdMeter.h3
-rw-r--r--linux/ZramMeter.c22
-rw-r--r--linux/ZramMeter.h1
-rw-r--r--linux/ZramStats.h6
-rw-r--r--netbsd/NetBSDProcess.c264
-rw-r--r--netbsd/NetBSDProcess.h32
-rw-r--r--netbsd/NetBSDProcessList.c504
-rw-r--r--netbsd/NetBSDProcessList.h58
-rw-r--r--netbsd/Platform.c509
-rw-r--r--netbsd/Platform.h124
-rw-r--r--netbsd/ProcessField.h15
-rw-r--r--netbsd/README.md32
-rw-r--r--openbsd/OpenBSDProcess.c104
-rw-r--r--openbsd/OpenBSDProcess.h20
-rw-r--r--openbsd/OpenBSDProcessList.c285
-rw-r--r--openbsd/OpenBSDProcessList.h11
-rw-r--r--openbsd/Platform.c91
-rw-r--r--openbsd/Platform.h75
-rw-r--r--openbsd/ProcessField.h15
-rw-r--r--pcp-htop.5.in237
-rw-r--r--pcp-htop.c26
-rw-r--r--pcp/PCPDynamicColumn.c348
-rw-r--r--pcp/PCPDynamicColumn.h35
-rw-r--r--pcp/PCPDynamicMeter.c468
-rw-r--r--pcp/PCPDynamicMeter.h44
-rw-r--r--pcp/PCPMetric.c180
-rw-r--r--pcp/PCPMetric.h180
-rw-r--r--pcp/PCPProcess.c291
-rw-r--r--pcp/PCPProcess.h102
-rw-r--r--pcp/PCPProcessList.c727
-rw-r--r--pcp/PCPProcessList.h74
-rw-r--r--pcp/Platform.c842
-rw-r--r--pcp/Platform.h156
-rw-r--r--pcp/ProcessField.h51
-rw-r--r--pcp/columns/container10
-rw-r--r--pcp/columns/delayacct10
-rw-r--r--pcp/columns/fdcount10
-rw-r--r--pcp/columns/guest17
-rw-r--r--pcp/columns/memory39
-rw-r--r--pcp/columns/sched10
-rw-r--r--pcp/columns/swap15
-rw-r--r--pcp/columns/tcp31
-rw-r--r--pcp/columns/udp31
-rw-r--r--pcp/columns/wchan17
-rw-r--r--pcp/meters/entropy9
-rw-r--r--pcp/meters/freespace11
-rw-r--r--pcp/meters/ipc13
-rw-r--r--pcp/meters/locks15
-rw-r--r--pcp/meters/memcache11
-rw-r--r--pcp/meters/mysql71
-rw-r--r--pcp/meters/postfix20
-rw-r--r--pcp/meters/redis39
-rw-r--r--pcp/meters/tcp21
-rw-r--r--scripts/htop_suppressions.valgrind50
-rwxr-xr-xscripts/run_valgrind.sh2
-rw-r--r--solaris/Platform.c130
-rw-r--r--solaris/Platform.h104
-rw-r--r--solaris/ProcessField.h24
-rw-r--r--solaris/SolarisProcess.c155
-rw-r--r--solaris/SolarisProcess.h42
-rw-r--r--solaris/SolarisProcessList.c305
-rw-r--r--solaris/SolarisProcessList.h36
-rw-r--r--unsupported/Platform.c112
-rw-r--r--unsupported/Platform.h84
-rw-r--r--unsupported/ProcessField.h15
-rw-r--r--unsupported/UnsupportedProcess.c100
-rw-r--r--unsupported/UnsupportedProcess.h18
-rw-r--r--unsupported/UnsupportedProcessList.c49
-rw-r--r--unsupported/UnsupportedProcessList.h9
-rw-r--r--zfs/ZfsArcMeter.c57
-rw-r--r--zfs/ZfsArcMeter.h5
-rw-r--r--zfs/ZfsArcStats.c10
-rw-r--r--zfs/ZfsArcStats.h3
-rw-r--r--zfs/ZfsCompressedArcMeter.c43
-rw-r--r--zfs/ZfsCompressedArcMeter.h5
263 files changed, 20089 insertions, 6631 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..6ba9da6
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+open_collective: htop
diff --git a/.github/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..655853c
--- /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@v3
+ 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 e416c67..2e75ec0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,17 +2,22 @@ name: CI
on: [ push, pull_request ]
+env:
+ # Enable format attributes in ncurses headers
+ # Enable fortified memory/string handling
+ CPPFLAGS: -DGCC_PRINTF -DGCC_SCANF -D_FORTIFY_SOURCE=2
+
jobs:
build-ubuntu-latest-minimal-gcc:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Install Dependencies
- run: sudo apt-get install libncursesw5-dev
+ run: sudo apt-get install --no-install-recommends libncursesw5-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-linux-affinity --disable-unicode --without-sensors
+ run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors
- name: Enable compatibility modes
run: |
sed -i 's/#define HAVE_FSTATAT 1/#undef HAVE_FSTATAT/g' config.h
@@ -21,90 +26,144 @@ jobs:
- name: Build
run: make -k
- name: Distcheck
- run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror --enable-linux-affinity --disable-unicode --without-sensors"
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror --enable-affinity --disable-unicode --disable-sensors"
build-ubuntu-latest-minimal-clang:
runs-on: ubuntu-latest
env:
- CC: clang-11
+ CC: clang-15
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: install clang repo
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -
- sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main' -y
sudo apt-get update -q
- name: Install Dependencies
- run: sudo apt-get install clang-11 libncursesw5-dev
+ run: sudo apt-get install --no-install-recommends clang-15 libncursesw5-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-linux-affinity --disable-unicode --without-sensors
+ run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors
- name: Build
run: make -k
- name: Distcheck
- run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror --enable-linux-affinity --disable-unicode --without-sensors"
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror --enable-affinity --disable-unicode --disable-sensors"
build-ubuntu-latest-full-featured-gcc:
runs-on: ubuntu-latest
+ # Enable LTO, might trigger additional warnings on advanced inlining
+ env:
+ CFLAGS: -O3 -g -flto
+ LDFLAGS: -O3 -g -flto -Wl,--as-needed
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Install Dependencies
- run: sudo apt-get install libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev
+ run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors
+ run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities
- name: Build
run: make -k
- name: Distcheck
- run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors'
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities'
build-ubuntu-latest-full-featured-clang:
runs-on: ubuntu-latest
env:
- CC: clang-11
+ CC: clang-15
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: install clang repo
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -
- sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main' -y
sudo apt-get update -q
- name: Install Dependencies
- run: sudo apt-get install clang-11 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev
+ run: sudo apt-get install --no-install-recommends clang-15 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 -k
+ - name: Distcheck
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities'
+
+ build-ubuntu-latest-gcc-static:
+ runs-on: ubuntu-latest
+ # Enable LTO, might trigger additional warnings on advanced inlining
+ env:
+ CFLAGS: -O3 -g -flto
+ LDFLAGS: -O3 -g -flto
+ steps:
+ - uses: actions/checkout@v3
+ - 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-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors
+ run: ./configure --enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities
- name: Build
run: make -k
- name: Distcheck
- run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors'
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities'
+
+ build-ubuntu-latest-pcp:
+ # we want PCP v5.2.3+
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Dependencies
+ run: sudo apt-get install --no-install-recommends libpcp3-dev libncursesw5-dev libtinfo-dev libgpm-dev
+ - name: Bootstrap
+ run: ./autogen.sh
+ - name: Configure
+ run: ./configure --enable-werror --enable-pcp --enable-unicode
+ - name: Build
+ run: make -k
build-ubuntu-latest-clang-analyzer:
runs-on: ubuntu-latest
env:
- CC: clang-11
+ CC: clang-15
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: install clang repo
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -
- sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' -y
+ sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main' -y
sudo apt-get update -q
- name: Install Dependencies
- run: sudo apt-get install clang-11 clang-tools-11 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev
+ run: sudo apt-get install --no-install-recommends clang-15 clang-tools-15 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev
- name: Bootstrap
run: ./autogen.sh
- name: Configure
- run: scan-build-11 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors
+ run: scan-build-15 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-delayacct --enable-sensors --enable-capabilities
- name: Build
- run: scan-build-11 -analyze-headers --status-bugs make -j"$(nproc)"
+ run: scan-build-15 -analyze-headers --status-bugs make -j"$(nproc)"
+
+ build-macos-latest-clang:
+ runs-on: macOS-latest
+ env:
+ CC: clang
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Dependencies
+ run: brew install automake
+ - name: Bootstrap
+ run: ./autogen.sh
+ - name: Configure
+ run: ./configure --enable-werror
+ - name: Build
+ run: make -k
+ - name: Distcheck
+ run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-werror"
whitespace_check:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- 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..17356b6
--- /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@v3
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ 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@v2
diff --git a/.gitignore b/.gitignore
index 3d522b0..50f3bd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
-# the binary:
+# the binaries:
htop
+pcp-htop
# all object files
*.o
@@ -36,6 +37,7 @@ config.sub
configure
depcomp
htop.1
+pcp-htop.5
install-sh
libtool
ltmain.sh
@@ -45,3 +47,10 @@ stamp-h1
# files related to valgrind/callgrind
callgrind.out.*
+
+# IDE workspace configurations
+/.idea/
+/.vscode/
+
+# Language Servers
+/.cache/clangd/
diff --git a/.travis.yml b/.travis.yml
index bb79560..f62e42f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,21 +6,11 @@ compiler:
os:
- freebsd
- - linux
- - osx
-
-arch:
- - amd64
- - s390x
-
-before_script:
- if [[ ${TRAVIS_CPU_ARCH} == 's390x' ]]; then
- sudo apt-get update && sudo apt-get install -y libncursesw5-dev ;
- fi
script:
- ./autogen.sh
- # clang might warn about C11 generic selections in isnan()
- - CFLAGS=-Wno-c11-extensions ./configure --enable-werror
+ - ./configure --enable-werror
- make -k
- - CFLAGS=-Wno-c11-extensions make distcheck DISTCHECK_CONFIGURE_FLAGS=--enable-werror
+ - make distcheck DISTCHECK_CONFIGURE_FLAGS=--enable-werror
+ - sudo make install
+ - make installcheck
diff --git a/Action.c b/Action.c
index 66934be..81432f5 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.
*/
@@ -13,9 +13,10 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdlib.h>
+#include "CRT.h"
#include "CategoriesPanel.h"
#include "CommandScreen.h"
-#include "CRT.h"
+#include "DynamicColumn.h"
#include "EnvScreen.h"
#include "FunctionBar.h"
#include "Hashtable.h"
@@ -34,40 +35,39 @@ in the source distribution for its full text.
#include "Vector.h"
#include "XUtils.h"
-#if (defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY))
+#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
#include "Affinity.h"
#include "AffinityPanel.h"
#endif
Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) {
- Panel* panel = st->panel;
+ MainPanel* mainPanel = st->mainPanel;
Header* header = st->header;
- Settings* settings = st->settings;
- int y = panel->y;
- ScreenManager* scr = ScreenManager_new(header, settings, st, false);
+ int y = ((Panel*)mainPanel)->y;
+ ScreenManager* scr = ScreenManager_new(header, st->settings, st, false);
scr->allowFocusChange = false;
- ScreenManager_add(scr, list, x - 1);
- ScreenManager_add(scr, panel, -1);
+ ScreenManager_add(scr, list, x);
+ ScreenManager_add(scr, (Panel*)mainPanel, -1);
Panel* panelFocus;
int ch;
bool unfollow = false;
- int pid = followProcess ? MainPanel_selectedPid((MainPanel*)panel) : -1;
+ int pid = followProcess ? MainPanel_selectedPid(mainPanel) : -1;
if (followProcess && header->pl->following == -1) {
header->pl->following = pid;
unfollow = true;
}
- ScreenManager_run(scr, &panelFocus, &ch);
+ ScreenManager_run(scr, &panelFocus, &ch, NULL);
if (unfollow) {
header->pl->following = -1;
}
ScreenManager_delete(scr);
- Panel_move(panel, 0, y);
- Panel_resize(panel, COLS, LINES - y - 1);
+ Panel_move((Panel*)mainPanel, 0, y);
+ Panel_resize((Panel*)mainPanel, COLS, LINES - y - 1);
if (panelFocus == list && ch == 13) {
if (followProcess) {
- Process* selected = (Process*)Panel_getSelected(panel);
+ const Process* selected = (const Process*)Panel_getSelected((Panel*)mainPanel);
if (selected && selected->pid == pid)
return Panel_getSelected(list);
@@ -84,14 +84,11 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess)
static void Action_runSetup(State* st) {
ScreenManager* scr = ScreenManager_new(st->header, st->settings, st, true);
- CategoriesPanel* panelCategories = CategoriesPanel_new(scr, st->settings, st->header, st->pl);
- ScreenManager_add(scr, (Panel*) panelCategories, 16);
- CategoriesPanel_makeMetersPage(panelCategories);
- Panel* panelFocus;
- int ch;
- ScreenManager_run(scr, &panelFocus, &ch);
+ CategoriesPanel_new(scr, st->settings, st->header, st->pl);
+ ScreenManager_run(scr, NULL, NULL, "Setup");
ScreenManager_delete(scr);
if (st->settings->changed) {
+ CRT_setMouse(st->settings->enableMouse);
Header_writeBackToSettings(st->header);
}
}
@@ -104,7 +101,7 @@ static bool changePriority(MainPanel* panel, int delta) {
return anyTagged;
}
-static void addUserToVector(hkey_t key, void* userCast, void* panelCast) {
+static void addUserToVector(ht_key_t key, void* userCast, void* panelCast) {
const char* user = userCast;
Panel* panel = panelCast;
Panel_add(panel, (Object*) ListItem_new(user, key));
@@ -141,7 +138,7 @@ static bool expandCollapse(Panel* panel) {
}
static bool collapseIntoParent(Panel* panel) {
- Process* p = (Process*) Panel_getSelected(panel);
+ const Process* p = (Process*) Panel_getSelected(panel);
if (!p)
return false;
@@ -158,42 +155,48 @@ static bool collapseIntoParent(Panel* panel) {
}
Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) {
- settings->sortKey = sortKey;
- settings->direction = 1;
+ ScreenSettings_setSortKey(settings->ss, sortKey);
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_UPDATE_PANELHDR | HTOP_KEEP_FOLLOWING;
}
-static Htop_Reaction sortBy(State* st) {
+// ----------------------------------------
+
+static Htop_Reaction actionSetSortColumn(State* st) {
Htop_Reaction reaction = HTOP_OK;
- Panel* sortPanel = Panel_new(0, 0, 0, 0, true, Class(ListItem), FunctionBar_newEnterEsc("Sort ", "Cancel "));
+ Panel* sortPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Sort ", "Cancel "));
Panel_setHeader(sortPanel, "Sort by");
- ProcessField* fields = st->settings->fields;
+ const Settings* settings = st->settings;
+ const ProcessField* fields = settings->ss->fields;
+ Hashtable* dynamicColumns = settings->dynamicColumns;
for (int i = 0; fields[i]; i++) {
- char* name = String_trim(Process_fields[fields[i]].name);
+ char* name = NULL;
+ if (fields[i] >= LAST_PROCESSFIELD) {
+ DynamicColumn* column = Hashtable_get(dynamicColumns, fields[i]);
+ if (!column)
+ continue;
+ name = xStrdup(column->caption ? column->caption : column->name);
+ } else {
+ name = String_trim(Process_fields[fields[i]].name);
+ }
Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i]));
- if (fields[i] == st->settings->sortKey)
+ if (fields[i] == ScreenSettings_getActiveSortKey(settings->ss))
Panel_setSelected(sortPanel, i);
free(name);
}
- ListItem* field = (ListItem*) Action_pickFromVector(st, sortPanel, 15, false);
+ const ListItem* field = (const ListItem*) Action_pickFromVector(st, sortPanel, 14, false);
if (field) {
reaction |= Action_setSortKey(st->settings, field->key);
}
Object_delete(sortPanel);
- if (st->pauseProcessUpdate)
- ProcessList_sort(st->pl);
+ st->pl->needsSort = true;
return reaction | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
}
-// ----------------------------------------
-
-static Htop_Reaction actionResize(State* st) {
- clear();
- Panel_resize(st->panel, COLS, LINES - (st->panel->y) - 1);
- return HTOP_REDRAW_BAR;
+static Htop_Reaction actionSortByPID(State* st) {
+ return Action_setSortKey(st->settings, PID);
}
static Htop_Reaction actionSortByMemory(State* st) {
@@ -210,91 +213,158 @@ static Htop_Reaction actionSortByTime(State* st) {
static Htop_Reaction actionToggleKernelThreads(State* st) {
st->settings->hideKernelThreads = !st->settings->hideKernelThreads;
- return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS;
+ st->settings->lastUpdate++;
+
+ return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionToggleUserlandThreads(State* st) {
st->settings->hideUserlandThreads = !st->settings->hideUserlandThreads;
- return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS;
+ st->settings->lastUpdate++;
+
+ return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
+}
+
+static Htop_Reaction actionToggleRunningInContainer(State* st) {
+ st->settings->hideRunningInContainer = !st->settings->hideRunningInContainer;
+ st->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;
+ st->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;
+ st->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;
- if (st->settings->treeView) {
- st->settings->direction = 1;
- }
+ ScreenSettings* ss = st->settings->ss;
+ ss->treeView = !ss->treeView;
- ProcessList_expandTree(st->pl);
+ if (!ss->allBranchesCollapsed)
+ ProcessList_expandTree(st->pl);
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
}
-static Htop_Reaction actionIncFilter(State* st) {
- IncSet* inc = ((MainPanel*)st->panel)->inc;
- IncSet_activate(inc, INC_FILTER, st->panel);
- st->pl->incFilter = IncSet_filter(inc);
- return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;
+static Htop_Reaction actionToggleHideMeters(State* st) {
+ st->hideMeters = !st->hideMeters;
+ return HTOP_RESIZE | HTOP_KEEP_FOLLOWING;
}
-static Htop_Reaction actionIncSearch(State* st) {
- IncSet_reset(((MainPanel*)st->panel)->inc, INC_SEARCH);
- IncSet_activate(((MainPanel*)st->panel)->inc, INC_SEARCH, st->panel);
- return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;
+static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) {
+ ScreenSettings* ss = st->settings->ss;
+ if (!ss->treeView) {
+ return HTOP_OK;
+ }
+ ss->allBranchesCollapsed = !ss->allBranchesCollapsed;
+ if (ss->allBranchesCollapsed)
+ ProcessList_collapseAllBranches(st->pl);
+ else
+ ProcessList_expandTree(st->pl);
+ return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
}
-static Htop_Reaction actionIncNext(State* st) {
- IncSet_next(((MainPanel*)st->panel)->inc, INC_SEARCH, st->panel, (IncMode_GetPanelValue) MainPanel_getValue);
+static Htop_Reaction actionIncFilter(State* st) {
+ IncSet* inc = (st->mainPanel)->inc;
+ IncSet_activate(inc, INC_FILTER, (Panel*)st->mainPanel);
+ st->pl->incFilter = IncSet_filter(inc);
return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;
}
-static Htop_Reaction actionIncPrev(State* st) {
- IncSet_prev(((MainPanel*)st->panel)->inc, INC_SEARCH, st->panel, (IncMode_GetPanelValue) MainPanel_getValue);
+static Htop_Reaction actionIncSearch(State* st) {
+ IncSet_reset(st->mainPanel->inc, INC_SEARCH);
+ IncSet_activate(st->mainPanel->inc, INC_SEARCH, (Panel*)st->mainPanel);
return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionHigherPriority(State* st) {
- bool changed = changePriority((MainPanel*)st->panel, -1);
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ bool changed = changePriority(st->mainPanel, -1);
return changed ? HTOP_REFRESH : HTOP_OK;
}
static Htop_Reaction actionLowerPriority(State* st) {
- bool changed = changePriority((MainPanel*)st->panel, 1);
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ bool changed = changePriority(st->mainPanel, 1);
return changed ? HTOP_REFRESH : HTOP_OK;
}
static Htop_Reaction actionInvertSortOrder(State* st) {
- Settings_invertSortOrder(st->settings);
- return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
-}
-
-static Htop_Reaction actionSetSortColumn(State* st) {
- return sortBy(st);
+ ScreenSettings_invertSortOrder(st->settings->ss);
+ st->pl->needsSort = true;
+ return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_UPDATE_PANELHDR;
}
static Htop_Reaction actionExpandOrCollapse(State* st) {
- bool changed = expandCollapse(st->panel);
+ if (!st->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->settings->ss->treeView) {
return HTOP_OK;
}
- bool changed = collapseIntoParent(st->panel);
+ bool changed = collapseIntoParent((Panel*)st->mainPanel);
return changed ? HTOP_RECALCULATE : HTOP_OK;
}
static Htop_Reaction actionExpandCollapseOrSortColumn(State* st) {
- return st->settings->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st);
+ return st->settings->ss->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st);
+}
+
+static Htop_Reaction actionNextScreen(State* st) {
+ Settings* settings = st->settings;
+ settings->ssIndex++;
+ if (settings->ssIndex == settings->nScreens) {
+ settings->ssIndex = 0;
+ }
+ settings->ss = settings->screens[settings->ssIndex];
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH;
+}
+
+static Htop_Reaction actionPrevScreen(State* st) {
+ Settings* settings = st->settings;
+ if (settings->ssIndex == 0) {
+ settings->ssIndex = settings->nScreens - 1;
+ } else {
+ settings->ssIndex--;
+ }
+ settings->ss = settings->screens[settings->ssIndex];
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH;
+}
+
+Htop_Reaction Action_setScreenTab(Settings* settings, int x) {
+ int s = 2;
+ for (unsigned int i = 0; i < settings->nScreens; i++) {
+ if (x < s) {
+ return 0;
+ }
+ const char* name = settings->screens[i]->name;
+ int len = strlen(name);
+ if (x <= s + len + 1) {
+ settings->ssIndex = i;
+ settings->ss = settings->screens[i];
+ return HTOP_UPDATE_PANELHDR | HTOP_REFRESH;
+ }
+ s += len + 3;
+ }
+ return 0;
}
static Htop_Reaction actionQuit(ATTR_UNUSED State* st) {
@@ -302,13 +372,14 @@ static Htop_Reaction actionQuit(ATTR_UNUSED State* st) {
}
static Htop_Reaction actionSetAffinity(State* st) {
- if (st->pl->cpuCount == 1)
+ if (Settings_isReadonly())
return HTOP_OK;
-#if (defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY))
- Panel* panel = st->panel;
+ if (st->pl->activeCPUs == 1)
+ return HTOP_OK;
- Process* p = (Process*) Panel_getSelected(panel);
+#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
+ const Process* p = (const Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -318,46 +389,52 @@ static Htop_Reaction actionSetAffinity(State* st) {
int width;
Panel* affinityPanel = AffinityPanel_new(st->pl, affinity1, &width);
- width += 1; /* we add a gap between the panels */
Affinity_delete(affinity1);
- void* set = Action_pickFromVector(st, affinityPanel, width, true);
+ const void* set = Action_pickFromVector(st, affinityPanel, width, true);
if (set) {
Affinity* affinity2 = AffinityPanel_getAffinity(affinityPanel, st->pl);
- bool ok = MainPanel_foreachProcess((MainPanel*)panel, Affinity_set, (Arg) { .v = affinity2 }, NULL);
+ bool ok = MainPanel_foreachProcess(st->mainPanel, Affinity_set, (Arg) { .v = affinity2 }, NULL);
if (!ok)
beep();
Affinity_delete(affinity2);
}
Object_delete(affinityPanel);
-#endif
return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
+#else
+ return HTOP_OK;
+#endif
+
}
static Htop_Reaction actionKill(State* st) {
- Panel* signalsPanel = SignalsPanel_new();
- ListItem* sgn = (ListItem*) Action_pickFromVector(st, signalsPanel, 15, true);
- if (sgn) {
- if (sgn->key != 0) {
- Panel_setHeader(st->panel, "Sending...");
- Panel_draw(st->panel, true, true);
- refresh();
- MainPanel_foreachProcess((MainPanel*)st->panel, Process_sendSignal, (Arg) { .i = sgn->key }, NULL);
- napms(500);
- }
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ 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);
+ 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, true, Class(ListItem), FunctionBar_newEnterEsc("Show ", "Cancel "));
+ 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);
Vector_insertionSort(usersPanel->items);
ListItem* allUsers = ListItem_new("All users", -1);
Panel_insert(usersPanel, 0, (Object*) allUsers);
- ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 20, false);
+ const ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 19, false);
if (picked) {
if (picked == allUsers) {
st->pl->userId = (uid_t)-1;
@@ -370,22 +447,21 @@ static Htop_Reaction actionFilterByUser(State* st) {
}
Htop_Reaction Action_follow(State* st) {
- st->pl->following = MainPanel_selectedPid((MainPanel*)st->panel);
- Panel_setSelectionColor(st->panel, CRT_colors[PANEL_SELECTION_FOLLOW]);
+ st->pl->following = MainPanel_selectedPid(st->mainPanel);
+ Panel_setSelectionColor((Panel*)st->mainPanel, PANEL_SELECTION_FOLLOW);
return HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionSetup(State* st) {
Action_runSetup(st);
- // TODO: shouldn't need this, colors should be dynamic
- int headerHeight = Header_calculateHeight(st->header);
- Panel_move(st->panel, 0, headerHeight);
- Panel_resize(st->panel, COLS, LINES-headerHeight-1);
- return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
+ return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR | HTOP_RESIZE;
}
static Htop_Reaction actionLsof(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -398,8 +474,9 @@ static Htop_Reaction actionLsof(State* st) {
}
static Htop_Reaction actionShowLocks(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
- if (!p) return HTOP_OK;
+ const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
+ if (!p)
+ return HTOP_OK;
ProcessLocksScreen* pls = ProcessLocksScreen_new(p);
InfoScreen_run((InfoScreen*)pls);
ProcessLocksScreen_delete((Object*)pls);
@@ -409,7 +486,10 @@ static Htop_Reaction actionShowLocks(State* st) {
}
static Htop_Reaction actionStrace(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -425,12 +505,12 @@ static Htop_Reaction actionStrace(State* st) {
}
static Htop_Reaction actionTag(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
Process_toggleTag(p);
- Panel_onKey(st->panel, KEY_DOWN);
+ Panel_onKey((Panel*)st->mainPanel, KEY_DOWN);
return HTOP_OK;
}
@@ -446,49 +526,54 @@ static Htop_Reaction actionTogglePauseProcessUpdate(State* st) {
static const struct {
const char* key;
+ bool roInactive;
const char* info;
} helpLeft[] = {
- { .key = " Arrows: ", .info = "scroll process list" },
- { .key = " Digits: ", .info = "incremental PID search" },
- { .key = " F3 /: ", .info = "incremental name search" },
- { .key = " F4 \\: ",.info = "incremental name filtering" },
- { .key = " F5 t: ", .info = "tree view" },
- { .key = " p: ", .info = "toggle program path" },
- { .key = " m: ", .info = "toggle merged command" },
- { .key = " Z: ", .info = "pause/resume process updates" },
- { .key = " u: ", .info = "show processes of a single user" },
- { .key = " H: ", .info = "hide/show user process threads" },
- { .key = " K: ", .info = "hide/show kernel threads" },
- { .key = " F: ", .info = "cursor follows process" },
- { .key = " F6 + -: ", .info = "expand/collapse tree" },
- { .key = " P M T: ", .info = "sort by CPU%, MEM% or TIME" },
- { .key = " I: ", .info = "invert sort order" },
- { .key = " F6 > .: ", .info = "select sort column" },
+ { .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" },
+ { .key = " F4 \\: ", .roInactive = false, .info = "incremental name filtering" },
+ { .key = " F5 t: ", .roInactive = false, .info = "tree view" },
+ { .key = " p: ", .roInactive = false, .info = "toggle program path" },
+ { .key = " m: ", .roInactive = false, .info = "toggle merged command" },
+ { .key = " Z: ", .roInactive = false, .info = "pause/resume process updates" },
+ { .key = " u: ", .roInactive = false, .info = "show processes of a single user" },
+ { .key = " H: ", .roInactive = false, .info = "hide/show user process threads" },
+ { .key = " K: ", .roInactive = false, .info = "hide/show kernel threads" },
+ { .key = " F: ", .roInactive = false, .info = "cursor follows process" },
+ { .key = " + - *: ", .roInactive = false, .info = "expand/collapse tree/toggle all" },
+ { .key = "N P M T: ", .roInactive = false, .info = "sort by PID, CPU%, MEM% or TIME" },
+ { .key = " I: ", .roInactive = false, .info = "invert sort order" },
+ { .key = " F6 > .: ", .roInactive = false, .info = "select sort column" },
{ .key = NULL, .info = NULL }
};
static const struct {
const char* key;
+ bool roInactive;
const char* info;
} helpRight[] = {
- { .key = " Space: ", .info = "tag process" },
- { .key = " c: ", .info = "tag process and its children" },
- { .key = " U: ", .info = "untag all processes" },
- { .key = " F9 k: ", .info = "kill process/tagged processes" },
- { .key = " F7 ]: ", .info = "higher priority (root only)" },
- { .key = " F8 [: ", .info = "lower priority (+ nice)" },
-#if (defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY))
- { .key = " a: ", .info = "set CPU affinity" },
+ { .key = " 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 = true, .info = "lower priority (+ nice)" },
+#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))
+ { .key = " a: ", .roInactive = true, .info = "set CPU affinity" },
#endif
- { .key = " e: ", .info = "show process environment" },
- { .key = " i: ", .info = "set IO priority" },
- { .key = " l: ", .info = "list open files with lsof" },
- { .key = " x: ", .info = "list file locks of process" },
- { .key = " s: ", .info = "trace syscalls with strace" },
- { .key = " w: ", .info = "wrap process command in multiple lines" },
- { .key = " F2 C S: ", .info = "setup" },
- { .key = " F1 h: ", .info = "show this help screen" },
- { .key = " F10 q: ", .info = "quit" },
+ { .key = " e: ", .roInactive = false, .info = "show process environment" },
+ { .key = " i: ", .roInactive = true, .info = "set IO priority" },
+ { .key = " l: ", .roInactive = true, .info = "list open files with lsof" },
+ { .key = " x: ", .roInactive = false, .info = "list file locks of process" },
+ { .key = " s: ", .roInactive = true, .info = "trace syscalls with strace" },
+ { .key = " w: ", .roInactive = false, .info = "wrap process command in multiple lines" },
+ { .key = " F2 C S: ", .roInactive = false, .info = "setup" },
+ { .key = " F1 h ?: ", .roInactive = false, .info = "show this help screen" },
+ { .key = " F10 q: ", .roInactive = false, .info = "quit" },
{ .key = NULL, .info = NULL }
};
@@ -498,8 +583,6 @@ static inline void addattrstr( int attr, const char* str) {
}
static Htop_Reaction actionHelp(State* st) {
- Settings* settings = st->settings;
-
clear();
attrset(CRT_colors[HELP_BOLD]);
@@ -509,45 +592,63 @@ 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 (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->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], "virtualiz");
- 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_CACHE], "cache");
- addattrstr(CRT_colors[BAR_SHADOW], " used/total");
+ addbartext(CRT_colors[MEMORY_USED], "", "used");
+ addbartext(CRT_colors[MEMORY_BUFFERS_TEXT], "/", "buffers");
+ addbartext(CRT_colors[MEMORY_SHARED], "/", "shared");
+ addbartext(CRT_colors[MEMORY_CACHE], "/", "cache");
+ addbartext(CRT_colors[BAR_SHADOW], " ", "used");
+ addbartext(CRT_colors[BAR_SHADOW], "/", "total");
addattrstr(CRT_colors[BAR_BORDER], "]");
+
attrset(CRT_colors[DEFAULT_COLOR]);
mvaddstr(line++, 0, "Swap bar: ");
addattrstr(CRT_colors[BAR_BORDER], "[");
- addattrstr(CRT_colors[SWAP], "used");
- addattrstr(CRT_colors[BAR_SHADOW], " used/total");
+ addbartext(CRT_colors[SWAP], "", "used");
+#ifdef HTOP_LINUX
+ addbartext(CRT_colors[SWAP_CACHE], "/", "cache");
+#else
+ 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) {
@@ -555,31 +656,47 @@ 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();
int item;
for (item = 0; helpLeft[item].key; item++) {
- attrset(CRT_colors[DEFAULT_COLOR]);
- mvaddstr(line + item, 9, helpLeft[item].info);
- attrset(CRT_colors[HELP_BOLD]);
- mvaddstr(line + item, 0, helpLeft[item].key);
+ attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[DEFAULT_COLOR]);
+ mvaddstr(line + item, 10, helpLeft[item].info);
+ attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[HELP_BOLD]);
+ mvaddstr(line + item, 1, helpLeft[item].key);
if (String_eq(helpLeft[item].key, " H: ")) {
- attrset(CRT_colors[PROCESS_THREAD]);
- mvaddstr(line + item, 32, "threads");
+ attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[PROCESS_THREAD]);
+ mvaddstr(line + item, 33, "threads");
} else if (String_eq(helpLeft[item].key, " K: ")) {
- attrset(CRT_colors[PROCESS_THREAD]);
- mvaddstr(line + item, 26, "threads");
+ attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[PROCESS_THREAD]);
+ mvaddstr(line + item, 27, "threads");
}
}
int leftHelpItems = item;
for (item = 0; helpRight[item].key; item++) {
- attrset(CRT_colors[HELP_BOLD]);
- mvaddstr(line + item, 40, helpRight[item].key);
- attrset(CRT_colors[DEFAULT_COLOR]);
- mvaddstr(line + item, 49, helpRight[item].info);
+ attrset((helpRight[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[HELP_BOLD]);
+ mvaddstr(line + item, 41, helpRight[item].key);
+ attrset((helpRight[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[DEFAULT_COLOR]);
+ mvaddstr(line + item, 50, helpRight[item].info);
}
line += MAXIMUM(leftHelpItems, item);
line++;
@@ -591,28 +708,28 @@ static Htop_Reaction actionHelp(State* st) {
CRT_readKey();
clear();
- return HTOP_RECALCULATE | HTOP_REDRAW_BAR;
+ return HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING;
}
static Htop_Reaction actionUntagAll(State* st) {
- for (int i = 0; i < Panel_size(st->panel); i++) {
- Process* p = (Process*) Panel_get(st->panel, i);
+ for (int i = 0; i < Panel_size((Panel*)st->mainPanel); i++) {
+ Process* p = (Process*) Panel_get((Panel*)st->mainPanel, i);
p->tag = false;
}
return HTOP_REFRESH;
}
static Htop_Reaction actionTagAllChildren(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
- tagAllChildren(st->panel, p);
+ tagAllChildren((Panel*)st->mainPanel, p);
return HTOP_OK;
}
static Htop_Reaction actionShowEnvScreen(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -625,7 +742,7 @@ static Htop_Reaction actionShowEnvScreen(State* st) {
}
static Htop_Reaction actionShowCommandScreen(State* st) {
- Process* p = (Process*) Panel_getSelected(st->panel);
+ Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
@@ -638,60 +755,62 @@ static Htop_Reaction actionShowCommandScreen(State* st) {
}
void Action_setBindings(Htop_Action* keys) {
- keys[KEY_RESIZE] = actionResize;
- keys['M'] = actionSortByMemory;
- keys['T'] = actionSortByTime;
- keys['P'] = actionSortByCPU;
- keys['H'] = actionToggleUserlandThreads;
- keys['K'] = actionToggleKernelThreads;
- keys['p'] = actionToggleProgramPath;
- keys['m'] = actionToggleMergedCommand;
- keys['t'] = actionToggleTreeView;
- keys[KEY_F(5)] = actionToggleTreeView;
- keys[KEY_F(4)] = actionIncFilter;
- keys['\\'] = actionIncFilter;
- keys[KEY_F(3)] = actionIncSearch;
- keys['/'] = actionIncSearch;
- keys['n'] = actionIncNext;
- keys['N'] = actionIncPrev;
-
- keys[']'] = actionHigherPriority;
- keys[KEY_F(7)] = actionHigherPriority;
- keys['['] = actionLowerPriority;
- keys[KEY_F(8)] = actionLowerPriority;
- keys['I'] = actionInvertSortOrder;
- keys[KEY_F(6)] = actionSetSortColumn;
- keys[KEY_F(18)] = actionExpandCollapseOrSortColumn;
- keys['<'] = actionSetSortColumn;
+ keys[' '] = actionTag;
+ keys['#'] = actionToggleHideMeters;
+ keys['*'] = actionExpandOrCollapseAllBranches;
+ keys['+'] = actionExpandOrCollapse;
keys[','] = actionSetSortColumn;
- keys['>'] = actionSetSortColumn;
+ keys['-'] = actionExpandOrCollapse;
keys['.'] = actionSetSortColumn;
- keys[KEY_F(10)] = actionQuit;
- keys['q'] = actionQuit;
- keys['a'] = actionSetAffinity;
- keys[KEY_F(9)] = actionKill;
- keys['k'] = actionKill;
- keys[KEY_RECLICK] = actionExpandOrCollapse;
- keys['+'] = actionExpandOrCollapse;
+ keys['/'] = actionIncSearch;
+ keys['<'] = actionSetSortColumn;
keys['='] = actionExpandOrCollapse;
- keys['-'] = actionExpandOrCollapse;
- keys['\177'] = actionCollapseIntoParent;
- keys['u'] = actionFilterByUser;
+ keys['>'] = actionSetSortColumn;
+ keys['?'] = actionHelp;
+ keys['C'] = actionSetup;
keys['F'] = Action_follow;
+ keys['H'] = actionToggleUserlandThreads;
+ keys['I'] = actionInvertSortOrder;
+ keys['K'] = actionToggleKernelThreads;
+ keys['M'] = actionSortByMemory;
+ keys['N'] = actionSortByPID;
+ keys['O'] = actionToggleRunningInContainer;
+ keys['P'] = actionSortByCPU;
keys['S'] = actionSetup;
- keys['C'] = actionSetup;
- keys[KEY_F(2)] = actionSetup;
- keys['x'] = actionShowLocks;
- keys['l'] = actionLsof;
- keys['s'] = actionStrace;
- keys[' '] = actionTag;
- keys['\014'] = actionRedraw; // Ctrl+L
- keys[KEY_F(1)] = actionHelp;
- keys['h'] = actionHelp;
- keys['?'] = actionHelp;
+ keys['T'] = actionSortByTime;
keys['U'] = actionUntagAll;
+ keys['Z'] = actionTogglePauseProcessUpdate;
+ keys['['] = actionLowerPriority;
+ keys['\014'] = actionRedraw; // Ctrl+L
+ keys['\177'] = actionCollapseIntoParent;
+ keys['\\'] = actionIncFilter;
+ keys[']'] = actionHigherPriority;
+ keys['a'] = actionSetAffinity;
keys['c'] = actionTagAllChildren;
keys['e'] = actionShowEnvScreen;
+ keys['h'] = actionHelp;
+ keys['k'] = actionKill;
+ keys['l'] = actionLsof;
+ keys['m'] = actionToggleMergedCommand;
+ keys['p'] = actionToggleProgramPath;
+ keys['q'] = actionQuit;
+ keys['s'] = actionStrace;
+ keys['t'] = actionToggleTreeView;
+ keys['u'] = actionFilterByUser;
keys['w'] = actionShowCommandScreen;
- keys['Z'] = actionTogglePauseProcessUpdate;
+ keys['x'] = actionShowLocks;
+ keys[KEY_F(1)] = actionHelp;
+ keys[KEY_F(2)] = actionSetup;
+ keys[KEY_F(3)] = actionIncSearch;
+ keys[KEY_F(4)] = actionIncFilter;
+ keys[KEY_F(5)] = actionToggleTreeView;
+ keys[KEY_F(6)] = actionSetSortColumn;
+ keys[KEY_F(7)] = actionHigherPriority;
+ keys[KEY_F(8)] = actionLowerPriority;
+ keys[KEY_F(9)] = actionKill;
+ 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 1251911..09b68bd 100644
--- a/Action.h
+++ b/Action.h
@@ -3,7 +3,7 @@
/*
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.
*/
@@ -20,27 +20,36 @@ in the source distribution for its full text.
#include "Settings.h"
#include "UsersTable.h"
+
typedef enum {
- HTOP_OK = 0x00,
- HTOP_REFRESH = 0x01,
- HTOP_RECALCULATE = 0x03, // implies HTOP_REFRESH
- HTOP_SAVE_SETTINGS = 0x04,
- HTOP_KEEP_FOLLOWING = 0x08,
- HTOP_QUIT = 0x10,
- HTOP_REDRAW_BAR = 0x20,
- HTOP_UPDATE_PANELHDR = 0x41, // implies HTOP_REFRESH
+ HTOP_OK = 0x00,
+ HTOP_REFRESH = 0x01,
+ HTOP_RECALCULATE = 0x02 | HTOP_REFRESH,
+ HTOP_SAVE_SETTINGS = 0x04,
+ HTOP_KEEP_FOLLOWING = 0x08,
+ HTOP_QUIT = 0x10,
+ HTOP_REDRAW_BAR = 0x20,
+ HTOP_UPDATE_PANELHDR = 0x40 | HTOP_REFRESH,
+ HTOP_RESIZE = 0x80 | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR,
} Htop_Reaction;
+struct MainPanel_; // IWYU pragma: keep
+
typedef struct State_ {
Settings* settings;
UsersTable* ut;
ProcessList* pl;
- Panel* panel;
+ struct MainPanel_* mainPanel;
Header* header;
bool pauseProcessUpdate;
bool hideProcessSelection;
+ bool hideMeters;
} State;
+static inline bool State_hideFunctionBar(const State* st) {
+ return st->settings->hideFunctionBar == 2 || (st->settings->hideFunctionBar == 1 && st->hideProcessSelection);
+}
+
typedef Htop_Reaction (*Htop_Action)(State* st);
Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess);
@@ -49,6 +58,8 @@ bool Action_setUserOnly(const char* userName, uid_t* userId);
Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey);
+Htop_Reaction Action_setScreenTab(Settings* settings, int x);
+
Htop_Reaction Action_follow(State* st);
void Action_setBindings(Htop_Action* keys);
diff --git a/Affinity.c b/Affinity.c
index c157885..dc6a7fb 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.
*/
@@ -14,7 +14,7 @@ in the source distribution for its full text.
#include "XUtils.h"
-#ifdef HAVE_LIBHWLOC
+#if defined(HAVE_LIBHWLOC)
#include <hwloc.h>
#include <hwloc/bitmap.h>
#ifdef __linux__
@@ -22,7 +22,7 @@ in the source distribution for its full text.
#else
#define HTOP_HWLOC_CPUBIND_FLAG HWLOC_CPUBIND_PROCESS
#endif
-#elif defined(HAVE_LINUX_AFFINITY)
+#elif defined(HAVE_AFFINITY)
#include <sched.h>
#endif
@@ -30,7 +30,7 @@ in the source distribution for its full text.
Affinity* Affinity_new(ProcessList* pl) {
Affinity* this = xCalloc(1, sizeof(Affinity));
this->size = 8;
- this->cpus = xCalloc(this->size, sizeof(int));
+ this->cpus = xCalloc(this->size, sizeof(unsigned int));
this->pl = pl;
return this;
}
@@ -40,32 +40,32 @@ void Affinity_delete(Affinity* this) {
free(this);
}
-void Affinity_add(Affinity* this, int id) {
+void Affinity_add(Affinity* this, unsigned int id) {
if (this->used == this->size) {
this->size *= 2;
- this->cpus = xRealloc(this->cpus, sizeof(int) * this->size);
+ this->cpus = xRealloc(this->cpus, sizeof(unsigned int) * this->size);
}
this->cpus[this->used] = id;
this->used++;
}
-#ifdef HAVE_LIBHWLOC
+#if defined(HAVE_LIBHWLOC)
-Affinity* Affinity_get(Process* proc, ProcessList* pl) {
+Affinity* Affinity_get(const Process* proc, ProcessList* pl) {
hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
bool ok = (hwloc_get_proc_cpubind(pl->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);
Affinity* affinity = NULL;
if (ok) {
affinity = Affinity_new(pl);
if (hwloc_bitmap_last(cpuset) == -1) {
- for (int i = 0; i < pl->cpuCount; i++) {
+ for (unsigned int i = 0; i < pl->existingCPUs; i++) {
Affinity_add(affinity, i);
}
} else {
- unsigned int id;
- hwloc_bitmap_foreach_begin(id, cpuset);
- Affinity_add(affinity, id);
+ int id;
+ hwloc_bitmap_foreach_begin(id, cpuset)
+ Affinity_add(affinity, (unsigned)id);
hwloc_bitmap_foreach_end();
}
}
@@ -76,7 +76,7 @@ Affinity* Affinity_get(Process* proc, ProcessList* pl) {
bool Affinity_set(Process* proc, Arg arg) {
Affinity* this = arg.v;
hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
- for (int i = 0; i < this->used; i++) {
+ for (unsigned int i = 0; i < this->used; i++) {
hwloc_bitmap_set(cpuset, this->cpus[i]);
}
bool ok = (hwloc_set_proc_cpubind(this->pl->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);
@@ -84,16 +84,16 @@ bool Affinity_set(Process* proc, Arg arg) {
return ok;
}
-#elif defined(HAVE_LINUX_AFFINITY)
+#elif defined(HAVE_AFFINITY)
-Affinity* Affinity_get(Process* proc, ProcessList* pl) {
+Affinity* Affinity_get(const Process* proc, ProcessList* pl) {
cpu_set_t cpuset;
bool ok = (sched_getaffinity(proc->pid, sizeof(cpu_set_t), &cpuset) == 0);
if (!ok)
return NULL;
Affinity* affinity = Affinity_new(pl);
- for (int i = 0; i < pl->cpuCount; i++) {
+ for (unsigned int i = 0; i < pl->existingCPUs; i++) {
if (CPU_ISSET(i, &cpuset)) {
Affinity_add(affinity, i);
}
@@ -105,7 +105,7 @@ bool Affinity_set(Process* proc, Arg arg) {
Affinity* this = arg.v;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
- for (int i = 0; i < this->used; i++) {
+ for (unsigned int i = 0; i < this->used; i++) {
CPU_SET(this->cpus[i], &cpuset);
}
bool ok = (sched_setaffinity(proc->pid, sizeof(unsigned long), &cpuset) == 0);
diff --git a/Affinity.h b/Affinity.h
index 97c8e46..5e7bfe2 100644
--- a/Affinity.h
+++ b/Affinity.h
@@ -4,7 +4,7 @@
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
+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 "ProcessList.h"
-#if defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY)
+#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)
#include <stdbool.h>
#include "Object.h"
@@ -20,30 +20,30 @@ in the source distribution for its full text.
#endif
-#if defined(HAVE_LIBHWLOC) && defined(HAVE_LINUX_AFFINITY)
-#error hwloc and linux affinity are mutual exclusive.
+#if defined(HAVE_LIBHWLOC) && defined(HAVE_AFFINITY)
+#error hwloc and affinity support are mutual exclusive.
#endif
typedef struct Affinity_ {
ProcessList* pl;
- int size;
- int used;
- int* cpus;
+ unsigned int size;
+ unsigned int used;
+ unsigned int* cpus;
} Affinity;
Affinity* Affinity_new(ProcessList* pl);
void Affinity_delete(Affinity* this);
-void Affinity_add(Affinity* this, int id);
+void Affinity_add(Affinity* this, unsigned int id);
-#if defined(HAVE_LIBHWLOC) || defined(HAVE_LINUX_AFFINITY)
+#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)
-Affinity* Affinity_get(Process* proc, ProcessList* pl);
+Affinity* Affinity_get(const Process* proc, ProcessList* pl);
bool Affinity_set(Process* proc, Arg arg);
-#endif /* HAVE_LIBHWLOC || HAVE_LINUX_AFFINITY */
+#endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */
#endif
diff --git a/AffinityPanel.c b/AffinityPanel.c
index e491b52..b724397 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.
*/
@@ -59,25 +59,25 @@ static void MaskItem_delete(Object* cast) {
static void MaskItem_display(const Object* cast, RichString* out) {
const MaskItem* this = (const MaskItem*)cast;
assert (this != NULL);
- RichString_append(out, CRT_colors[CHECK_BOX], "[");
+ RichString_appendAscii(out, CRT_colors[CHECK_BOX], "[");
if (this->value == 2) {
- RichString_append(out, CRT_colors[CHECK_MARK], "x");
+ RichString_appendAscii(out, CRT_colors[CHECK_MARK], "x");
} else if (this->value == 1) {
- RichString_append(out, CRT_colors[CHECK_MARK], "o");
+ RichString_appendAscii(out, CRT_colors[CHECK_MARK], "o");
} else {
- RichString_append(out, CRT_colors[CHECK_MARK], " ");
+ RichString_appendAscii(out, CRT_colors[CHECK_MARK], " ");
}
- RichString_append(out, CRT_colors[CHECK_BOX], "]");
- RichString_append(out, CRT_colors[CHECK_TEXT], " ");
+ RichString_appendAscii(out, CRT_colors[CHECK_BOX], "]");
+ RichString_appendAscii(out, CRT_colors[CHECK_TEXT], " ");
if (this->indent) {
- RichString_append(out, CRT_colors[PROCESS_TREE], this->indent);
- RichString_append(out, CRT_colors[PROCESS_TREE],
- this->sub_tree == 2
- ? CRT_treeStr[TREE_STR_OPEN]
- : CRT_treeStr[TREE_STR_SHUT]);
- RichString_append(out, CRT_colors[CHECK_TEXT], " ");
+ RichString_appendWide(out, CRT_colors[PROCESS_TREE], this->indent);
+ RichString_appendWide(out, CRT_colors[PROCESS_TREE],
+ this->sub_tree == 2
+ ? CRT_treeStr[TREE_STR_OPEN]
+ : CRT_treeStr[TREE_STR_SHUT]);
+ RichString_appendAscii(out, CRT_colors[CHECK_TEXT], " ");
}
- RichString_append(out, CRT_colors[CHECK_TEXT], this->text);
+ RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->text);
}
static const ObjectClass MaskItem_class = {
@@ -173,7 +173,6 @@ static void AffinityPanel_update(AffinityPanel* this, bool keepSelected) {
Panel* super = (Panel*) this;
FunctionBar_setLabel(super->currentBar, KEY_F(3), this->topoView ? "Collapse/Expand" : "");
- FunctionBar_draw(super->currentBar);
int oldSelected = Panel_getSelectedIndex(super);
Panel_prune(super);
@@ -202,7 +201,7 @@ static HandlerResult AffinityPanel_eventHandler(Panel* super, int ch) {
MaskItem* selected = (MaskItem*) Panel_getSelected(super);
bool keepSelected = true;
- switch(ch) {
+ switch (ch) {
case KEY_MOUSE:
case KEY_RECLICK:
case ' ':
@@ -281,7 +280,7 @@ static MaskItem* AffinityPanel_addObject(AffinityPanel* this, hwloc_obj_t obj, u
indent_buf[0] = '\0';
if (depth > 0) {
for (unsigned i = 1; i < depth; i++) {
- xSnprintf(&indent_buf[off], left, "%s ", (indent & (1u << i)) ? CRT_treeStr[TREE_STR_VERT] : " ");
+ xSnprintf(&indent_buf[off], left, "%s ", (indent & (1U << i)) ? CRT_treeStr[TREE_STR_VERT] : " ");
size_t len = strlen(&indent_buf[off]);
off += len;
left -= len;
@@ -323,9 +322,9 @@ static MaskItem* AffinityPanel_addObject(AffinityPanel* this, hwloc_obj_t obj, u
static MaskItem* AffinityPanel_buildTopology(AffinityPanel* this, hwloc_obj_t obj, unsigned indent, MaskItem* parent) {
MaskItem* item = AffinityPanel_addObject(this, obj, indent, parent);
if (obj->next_sibling) {
- indent |= (1u << obj->depth);
+ indent |= (1U << obj->depth);
} else {
- indent &= ~(1u << obj->depth);
+ indent &= ~(1U << obj->depth);
}
for (unsigned i = 0; i < obj->arity; i++) {
@@ -358,7 +357,7 @@ static const char* const AffinityPanelFunctions[] = {
static const char* const AffinityPanelKeys[] = {"Enter", "Esc", "F1", "F2", "F3"};
static const int AffinityPanelEvents[] = {13, 27, KEY_F(1), KEY_F(2), KEY_F(3)};
-Panel* AffinityPanel_new(ProcessList* pl, Affinity* affinity, int* width) {
+Panel* AffinityPanel_new(ProcessList* pl, const Affinity* affinity, int* width) {
AffinityPanel* this = AllocThis(AffinityPanel);
Panel* super = (Panel*) this;
Panel_init(super, 1, 1, 1, 1, Class(MaskItem), false, FunctionBar_new(AffinityPanelFunctions, AffinityPanelKeys, AffinityPanelEvents));
@@ -383,8 +382,11 @@ Panel* AffinityPanel_new(ProcessList* pl, Affinity* affinity, int* width) {
Panel_setHeader(super, "Use CPUs:");
- int curCpu = 0;
- for (int i = 0; i < pl->cpuCount; i++) {
+ unsigned int curCpu = 0;
+ for (unsigned int i = 0; i < pl->existingCPUs; i++) {
+ if (!ProcessList_isCPUonline(this->pl, i))
+ continue;
+
char number[16];
xSnprintf(number, 9, "CPU %d", Settings_cpuId(pl->settings, i));
unsigned cpu_width = 4 + strlen(number);
@@ -419,17 +421,17 @@ Panel* AffinityPanel_new(ProcessList* pl, Affinity* affinity, int* width) {
}
Affinity* AffinityPanel_getAffinity(Panel* super, ProcessList* pl) {
- AffinityPanel* this = (AffinityPanel*) super;
+ const AffinityPanel* this = (AffinityPanel*) super;
Affinity* affinity = Affinity_new(pl);
#ifdef HAVE_LIBHWLOC
int i;
hwloc_bitmap_foreach_begin(i, this->workCpuset)
- Affinity_add(affinity, i);
+ Affinity_add(affinity, (unsigned)i);
hwloc_bitmap_foreach_end();
#else
- for (int i = 0; i < this->pl->cpuCount; i++) {
- MaskItem* item = (MaskItem*)Vector_get(this->cpuids, i);
+ for (int i = 0; i < Vector_size(this->cpuids); i++) {
+ const MaskItem* item = (const MaskItem*)Vector_get(this->cpuids, i);
if (item->value) {
Affinity_add(affinity, item->cpu);
}
diff --git a/AffinityPanel.h b/AffinityPanel.h
index fdefeae..87b1b85 100644
--- a/AffinityPanel.h
+++ b/AffinityPanel.h
@@ -3,17 +3,18 @@
/*
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 "Panel.h"
#include "Affinity.h"
+#include "Panel.h"
#include "ProcessList.h"
+
extern const PanelClass AffinityPanel_class;
-Panel* AffinityPanel_new(ProcessList* pl, Affinity* affinity, int* width);
+Panel* AffinityPanel_new(ProcessList* pl, const Affinity* affinity, int* width);
Affinity* AffinityPanel_getAffinity(Panel* super, ProcessList* pl);
diff --git a/AvailableColumnsPanel.c b/AvailableColumnsPanel.c
index 8945bd2..b8c09c7 100644
--- a/AvailableColumnsPanel.c
+++ b/AvailableColumnsPanel.c
@@ -1,21 +1,23 @@
/*
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 "AvailableColumnsPanel.h"
+#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include "ColumnsPanel.h"
+#include "DynamicColumn.h"
#include "FunctionBar.h"
+#include "Hashtable.h"
#include "ListItem.h"
#include "Object.h"
-#include "Platform.h"
#include "Process.h"
#include "ProvideCurses.h"
#include "XUtils.h"
@@ -30,11 +32,20 @@ static void AvailableColumnsPanel_delete(Object* object) {
free(this);
}
+static void AvailableColumnsPanel_insert(AvailableColumnsPanel* this, int at, int key) {
+ const char* name;
+ if (key >= LAST_PROCESSFIELD)
+ name = DynamicColumn_init(key);
+ else
+ name = Process_fields[key].name;
+ Panel_insert(this->columns, at, (Object*) ListItem_new(name, key));
+}
+
static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) {
AvailableColumnsPanel* this = (AvailableColumnsPanel*) super;
HandlerResult result = IGNORED;
- switch(ch) {
+ switch (ch) {
case 13:
case KEY_ENTER:
case KEY_F(5):
@@ -43,10 +54,9 @@ static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) {
if (!selected)
break;
- int key = selected->key;
int at = Panel_getSelectedIndex(this->columns);
- Panel_insert(this->columns, at, (Object*) ListItem_new(Process_fields[key].name, key));
- Panel_setSelected(this->columns, at+1);
+ AvailableColumnsPanel_insert(this, at, selected->key);
+ Panel_setSelected(this->columns, at + 1);
ColumnsPanel_update(this->columns);
result = HANDLED;
break;
@@ -69,21 +79,44 @@ const PanelClass AvailableColumnsPanel_class = {
.eventHandler = AvailableColumnsPanel_eventHandler
};
-AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns) {
- AvailableColumnsPanel* this = AllocThis(AvailableColumnsPanel);
- Panel* super = (Panel*) this;
- FunctionBar* fuBar = FunctionBar_new(AvailableColumnsFunctions, NULL, NULL);
- Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
+static void AvailableColumnsPanel_addDynamicColumn(ht_key_t key, void* value, void* data) {
+ const DynamicColumn* column = (const DynamicColumn*) value;
+ Panel* super = (Panel*) data;
+ const char* title = column->caption ? column->caption : column->heading;
+ if (!title)
+ title = column->name; // fallback to the only mandatory field
+ char description[256];
+ xSnprintf(description, sizeof(description), "%s - %s", title, column->description);
+ Panel_add(super, (Object*) ListItem_new(description, key));
+}
- Panel_setHeader(super, "Available Columns");
+// Handle DynamicColumns entries in the AvailableColumnsPanel
+static void AvailableColumnsPanel_addDynamicColumns(Panel* super, Hashtable* dynamicColumns) {
+ assert(dynamicColumns);
+ Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, super);
+}
- for (int i = 1; i < Platform_numberOfFields; i++) {
+// Handle remaining Platform Meter entries in the AvailableColumnsPanel
+static void AvailableColumnsPanel_addPlatformColumn(Panel* super) {
+ for (int i = 1; i < LAST_PROCESSFIELD; i++) {
if (i != COMM && Process_fields[i].description) {
char description[256];
xSnprintf(description, sizeof(description), "%s - %s", Process_fields[i].name, Process_fields[i].description);
Panel_add(super, (Object*) ListItem_new(description, i));
}
}
+}
+
+AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns) {
+ AvailableColumnsPanel* this = AllocThis(AvailableColumnsPanel);
+ Panel* super = (Panel*) this;
+ FunctionBar* fuBar = FunctionBar_new(AvailableColumnsFunctions, NULL, NULL);
+ Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
+
+ Panel_setHeader(super, "Available Columns");
+ AvailableColumnsPanel_addPlatformColumn(super);
+ AvailableColumnsPanel_addDynamicColumns(super, dynamicColumns);
+
this->columns = columns;
return this;
}
diff --git a/AvailableColumnsPanel.h b/AvailableColumnsPanel.h
index 8672eb9..aca5906 100644
--- a/AvailableColumnsPanel.h
+++ b/AvailableColumnsPanel.h
@@ -3,12 +3,14 @@
/*
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.
*/
+#include "Hashtable.h"
#include "Panel.h"
+
typedef struct AvailableColumnsPanel_ {
Panel super;
Panel* columns;
@@ -16,6 +18,6 @@ typedef struct AvailableColumnsPanel_ {
extern const PanelClass AvailableColumnsPanel_class;
-AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns);
+AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns);
#endif
diff --git a/AvailableMetersPanel.c b/AvailableMetersPanel.c
index ce01255..c7ab89b 100644
--- a/AvailableMetersPanel.c
+++ b/AvailableMetersPanel.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -12,9 +12,12 @@ in the source distribution for its full text.
#include <stdlib.h>
#include "CPUMeter.h"
+#include "DynamicMeter.h"
#include "FunctionBar.h"
+#include "Hashtable.h"
#include "Header.h"
#include "ListItem.h"
+#include "Macros.h"
#include "Meter.h"
#include "MetersPanel.h"
#include "Object.h"
@@ -27,15 +30,15 @@ static void AvailableMetersPanel_delete(Object* object) {
Panel* super = (Panel*) object;
AvailableMetersPanel* this = (AvailableMetersPanel*) object;
Panel_done(super);
+ free(this->meterPanels);
free(this);
}
-static inline void AvailableMetersPanel_addMeter(Header* header, Panel* panel, const MeterClass* type, int param, int column) {
- Meter* meter = Header_addMeterByClass(header, type, param, column);
- Panel_add(panel, (Object*) Meter_toListItem(meter, false));
- Panel_setSelected(panel, Panel_size(panel) - 1);
- MetersPanel_setMoving((MetersPanel*)panel, true);
- FunctionBar_draw(panel->currentBar);
+static inline void AvailableMetersPanel_addMeter(Header* header, MetersPanel* panel, const MeterClass* type, unsigned int param, size_t column) {
+ const Meter* meter = Header_addMeterByClass(header, type, param, column);
+ Panel_add((Panel*)panel, (Object*) Meter_toListItem(meter, false));
+ Panel_setSelected((Panel*)panel, Panel_size((Panel*)panel) - 1);
+ MetersPanel_setMoving(panel, true);
}
static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
@@ -46,17 +49,17 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
if (!selected)
return IGNORED;
- int param = selected->key & 0xff;
+ unsigned int param = selected->key & 0xffff;
int type = selected->key >> 16;
HandlerResult result = IGNORED;
bool update = false;
- switch(ch) {
+ switch (ch) {
case KEY_F(5):
case 'l':
case 'L':
{
- AvailableMetersPanel_addMeter(header, this->leftPanel, Platform_meterTypes[type], param, 0);
+ AvailableMetersPanel_addMeter(header, this->meterPanels[0], Platform_meterTypes[type], param, 0);
result = HANDLED;
update = true;
break;
@@ -68,7 +71,7 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
case 'r':
case 'R':
{
- AvailableMetersPanel_addMeter(header, this->rightPanel, Platform_meterTypes[type], param, 1);
+ AvailableMetersPanel_addMeter(header, this->meterPanels[this->columns - 1], Platform_meterTypes[type], param, this->columns - 1);
result = (KEY_LEFT << 16) | SYNTH_KEY;
update = true;
break;
@@ -76,9 +79,11 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {
}
if (update) {
this->settings->changed = true;
+ this->settings->lastUpdate++;
Header_calculateHeight(header);
+ Header_updateData(header);
Header_draw(header);
- ScreenManager_resize(this->scr, this->scr->x1, header->height, this->scr->x2, this->scr->y2);
+ ScreenManager_resize(this->scr);
}
return result;
}
@@ -91,7 +96,51 @@ const PanelClass AvailableMetersPanel_class = {
.eventHandler = AvailableMetersPanel_eventHandler
};
-AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, Panel* leftMeters, Panel* rightMeters, ScreenManager* scr, ProcessList* pl) {
+// Handle (&CPUMeter_class) entries in the AvailableMetersPanel
+static void AvailableMetersPanel_addCPUMeters(Panel* super, const MeterClass* type, const ProcessList* pl) {
+ if (pl->existingCPUs > 1) {
+ Panel_add(super, (Object*) ListItem_new("CPU average", 0));
+ for (unsigned int i = 1; i <= pl->existingCPUs; i++) {
+ char buffer[50];
+ xSnprintf(buffer, sizeof(buffer), "%s %d", type->uiName, Settings_cpuId(pl->settings, i - 1));
+ Panel_add(super, (Object*) ListItem_new(buffer, i));
+ }
+ } else {
+ Panel_add(super, (Object*) ListItem_new(type->uiName, 1));
+ }
+}
+
+typedef struct {
+ Panel* super;
+ unsigned int id;
+ unsigned int offset;
+} DynamicIterator;
+
+static void AvailableMetersPanel_addDynamicMeter(ATTR_UNUSED ht_key_t key, void* value, void* data) {
+ const DynamicMeter* meter = (const DynamicMeter*)value;
+ DynamicIterator* iter = (DynamicIterator*)data;
+ unsigned int identifier = (iter->offset << 16) | iter->id;
+ const char* label = meter->description ? meter->description : meter->caption;
+ if (!label)
+ label = meter->name; /* last fallback to name, guaranteed set */
+ Panel_add(iter->super, (Object*) ListItem_new(label, identifier));
+ iter->id++;
+}
+
+// Handle (&DynamicMeter_class) entries in the AvailableMetersPanel
+static void AvailableMetersPanel_addDynamicMeters(Panel* super, const ProcessList* pl, unsigned int offset) {
+ DynamicIterator iter = { .super = super, .id = 1, .offset = offset };
+ assert(pl->dynamicMeters != NULL);
+ Hashtable_foreach(pl->dynamicMeters, AvailableMetersPanel_addDynamicMeter, &iter);
+}
+
+// Handle remaining Platform Meter entries in the AvailableMetersPanel
+static void AvailableMetersPanel_addPlatformMeter(Panel* super, const MeterClass* type, unsigned int offset) {
+ const char* label = type->description ? type->description : type->uiName;
+ Panel_add(super, (Object*) ListItem_new(label, offset << 16));
+}
+
+AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, size_t columns, MetersPanel** meterPanels, ScreenManager* scr, const ProcessList* pl) {
AvailableMetersPanel* this = AllocThis(AvailableMetersPanel);
Panel* super = (Panel*) this;
FunctionBar* fuBar = FunctionBar_newEnterEsc("Add ", "Done ");
@@ -99,31 +148,24 @@ AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* heade
this->settings = settings;
this->header = header;
- this->leftPanel = leftMeters;
- this->rightPanel = rightMeters;
+ this->columns = columns;
+ this->meterPanels = meterPanels;
this->scr = scr;
Panel_setHeader(super, "Available meters");
- // Platform_meterTypes[0] should be always (&CPUMeter_class), which we will
- // handle separately in the code below.
- for (int i = 1; Platform_meterTypes[i]; i++) {
+ // Platform_meterTypes[0] should be always (&CPUMeter_class) which we will
+ // handle separately in the code below. Likewise, identifiers for Dynamic
+ // Meters are handled separately - similar to CPUs, this allows generation
+ // of multiple different Meters (also using 'param' to distinguish them).
+ for (unsigned int i = 1; Platform_meterTypes[i]; i++) {
const MeterClass* type = Platform_meterTypes[i];
assert(type != &CPUMeter_class);
- const char* label = type->description ? type->description : type->uiName;
- Panel_add(super, (Object*) ListItem_new(label, i << 16));
- }
- // Handle (&CPUMeter_class)
- const MeterClass* type = &CPUMeter_class;
- int cpus = pl->cpuCount;
- if (cpus > 1) {
- Panel_add(super, (Object*) ListItem_new("CPU average", 0));
- for (int i = 1; i <= cpus; i++) {
- char buffer[50];
- xSnprintf(buffer, sizeof(buffer), "%s %d", type->uiName, Settings_cpuId(this->settings, i - 1));
- Panel_add(super, (Object*) ListItem_new(buffer, i));
- }
- } else {
- Panel_add(super, (Object*) ListItem_new("CPU", 1));
+ if (type == &DynamicMeter_class)
+ AvailableMetersPanel_addDynamicMeters(super, pl, i);
+ else
+ AvailableMetersPanel_addPlatformMeter(super, type, i);
}
+ AvailableMetersPanel_addCPUMeters(super, &CPUMeter_class, pl);
+
return this;
}
diff --git a/AvailableMetersPanel.h b/AvailableMetersPanel.h
index f735936..1c0555a 100644
--- a/AvailableMetersPanel.h
+++ b/AvailableMetersPanel.h
@@ -3,28 +3,32 @@
/*
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 "MetersPanel.h"
#include "Panel.h"
#include "ProcessList.h"
#include "ScreenManager.h"
#include "Settings.h"
+
typedef struct AvailableMetersPanel_ {
Panel super;
ScreenManager* scr;
Settings* settings;
Header* header;
- Panel* leftPanel;
- Panel* rightPanel;
+ size_t columns;
+ MetersPanel** meterPanels;
} AvailableMetersPanel;
extern const PanelClass AvailableMetersPanel_class;
-AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, Panel* leftMeters, Panel* rightMeters, ScreenManager* scr, ProcessList* pl);
+AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, size_t columns, MetersPanel** meterPanels, ScreenManager* scr, const ProcessList* pl);
#endif
diff --git a/BatteryMeter.c b/BatteryMeter.c
index 4836809..33d17b7 100644
--- a/BatteryMeter.c
+++ b/BatteryMeter.c
@@ -1,7 +1,7 @@
/*
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).
@@ -21,7 +21,7 @@ static const int BatteryMeter_attributes[] = {
BATTERY
};
-static void BatteryMeter_updateValues(Meter* this, char* buffer, size_t len) {
+static void BatteryMeter_updateValues(Meter* this) {
ACPresence isOnAC;
double percent;
@@ -29,30 +29,27 @@ static void BatteryMeter_updateValues(Meter* this, char* buffer, size_t len) {
if (isnan(percent)) {
this->values[0] = NAN;
- xSnprintf(buffer, len, "N/A");
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "N/A");
return;
}
this->values[0] = percent;
- const char *onAcText, *onBatteryText, *unknownText;
-
- unknownText = "%.1f%%";
- if (this->mode == TEXT_METERMODE) {
- onAcText = "%.1f%% (Running on A/C)";
- onBatteryText = "%.1f%% (Running on battery)";
- } else {
- onAcText = "%.1f%%(A/C)";
- onBatteryText = "%.1f%%(bat)";
+ 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;
}
- if (isOnAC == AC_PRESENT) {
- xSnprintf(buffer, len, onAcText, percent);
- } else if (isOnAC == AC_ABSENT) {
- xSnprintf(buffer, len, onBatteryText, percent);
- } else {
- xSnprintf(buffer, len, unknownText, percent);
- }
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%%s", percent, text);
}
const MeterClass BatteryMeter_class = {
diff --git a/BatteryMeter.h b/BatteryMeter.h
index b6e8c52..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).
@@ -11,6 +11,7 @@ This meter written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
#include "Meter.h"
+
typedef enum ACPresence_ {
AC_ABSENT,
AC_PRESENT,
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 65eb95c..dd759c2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -29,9 +29,9 @@ are always included, please send those in!
Feature Requests
----------------
-Please label Github issues that are feature requests with the [`feature
-request`](https://github.com/htop-dev/htop/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22feature+request%22+)
-label.
+Please label Github issues that are feature requests with one of the `feature request`
+labels. If you can't do this yourself, don't worry. The friendly folks from the
+core team will distribute and fixup Github labels as part of the regular reviews.
Style Guide
-----------
diff --git a/COPYING b/COPYING
index 1bd75b9..d159169 100644
--- a/COPYING
+++ b/COPYING
@@ -1,12 +1,12 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 675 Mass Ave, Cambridge, MA 02139, USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
- Preamble
+ Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) You can apply it to
+the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
-
- GNU GENERAL PUBLIC LICENSE
+
+ GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
- NO WARRANTY
+ NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
- END OF TERMS AND CONDITIONS
-
- Appendix: How to Apply These Terms to Your New Programs
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
@@ -291,7 +291,7 @@ convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
- Copyright (C) 19yy <name of author>
+ Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -303,16 +303,16 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
- Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -335,21 +335,5 @@ necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General
+library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
-
-
- Appendix 2: Special exception concerning PLPA
-
- In the following exception, "PLPA" means (i) code released by the
- Portable Linux Processor Affinity Project, or (ii) derivative works of
- such code, in both cases provided that the code is covered entirely by
- free software licensing terms.
-
- As a special exception to the GNU GPL, the licensors of htop give you
- permission to combine GNU GPL-licensed code in htop (and derivative
- works of such code) with PLPA. You may copy and distribute such a
- combined work following the terms of the GNU GPL for htop and the
- applicable licenses of the version of PLPA used in your combined work,
- provided that you include the source code of such version of PLPA when
- and as the GNU GPL requires distribution of source code.
diff --git a/CPUMeter.c b/CPUMeter.c
index 97fc3f0..ba00595 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.
*/
@@ -10,7 +10,6 @@ in the source distribution for its full text.
#include "CPUMeter.h"
#include <math.h>
-#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -35,39 +34,50 @@ static const int CPUMeter_attributes[] = {
};
typedef struct CPUMeterData_ {
- int cpus;
+ unsigned int cpus;
Meter** meters;
} CPUMeterData;
static void CPUMeter_init(Meter* this) {
- int cpu = this->param;
- if (this->pl->cpuCount > 1) {
+ unsigned int cpu = this->param;
+ if (cpu == 0) {
+ Meter_setCaption(this, "Avg");
+ } else if (this->pl->activeCPUs > 1) {
char caption[10];
- xSnprintf(caption, sizeof(caption), "%3d", Settings_cpuId(this->pl->settings, cpu - 1));
+ xSnprintf(caption, sizeof(caption), "%3u", Settings_cpuId(this->pl->settings, cpu - 1));
Meter_setCaption(this, caption);
}
- if (this->param == 0)
- Meter_setCaption(this, "Avg");
}
-static void CPUMeter_updateValues(Meter* this, char* buffer, size_t size) {
- int cpu = this->param;
- if (cpu > this->pl->cpuCount) {
- xSnprintf(buffer, size, "absent");
- for (uint8_t i = 0; i < this->curItems; i++)
- this->values[i] = 0;
+// Custom uiName runtime logic to include the param (processor)
+static void CPUMeter_getUiName(const Meter* this, char* buffer, size_t length) {
+ if (this->param > 0)
+ xSnprintf(buffer, length, "%s %u", Meter_uiName(this), this->param);
+ else
+ xSnprintf(buffer, length, "%s", Meter_uiName(this));
+}
+
+static void CPUMeter_updateValues(Meter* this) {
+ memset(this->values, 0, sizeof(double) * CPU_METER_ITEMCOUNT);
+
+ unsigned int cpu = this->param;
+ if (cpu > this->pl->existingCPUs) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "absent");
+ return;
+ }
+
+ double percent = Platform_setCPUValues(this, cpu);
+ if (isnan(percent)) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "offline");
return;
}
- memset(this->values, 0, sizeof(double) * CPU_METER_ITEMCOUNT);
char cpuUsageBuffer[8] = { 0 };
char cpuFrequencyBuffer[16] = { 0 };
char cpuTemperatureBuffer[16] = { 0 };
- double percent = Platform_setCPUValues(this, cpu);
-
if (this->pl->settings->showCPUUsage) {
- xSnprintf(cpuUsageBuffer, sizeof(cpuUsageBuffer), "%5.1f%%", percent);
+ xSnprintf(cpuUsageBuffer, sizeof(cpuUsageBuffer), "%.1f%%", percent);
}
if (this->pl->settings->showCPUFrequency) {
@@ -79,7 +89,7 @@ static void CPUMeter_updateValues(Meter* this, char* buffer, size_t size) {
}
}
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
if (this->pl->settings->showCPUTemperature) {
double cpuTemperature = this->values[CPU_METER_TEMPERATURE];
if (isnan(cpuTemperature))
@@ -91,7 +101,7 @@ static void CPUMeter_updateValues(Meter* this, char* buffer, size_t size) {
}
#endif
- xSnprintf(buffer, size, "%s%s%s%s%s",
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s%s%s%s%s",
cpuUsageBuffer,
(cpuUsageBuffer[0] && (cpuFrequencyBuffer[0] || cpuTemperatureBuffer[0])) ? " " : "",
cpuFrequencyBuffer,
@@ -101,76 +111,95 @@ static void CPUMeter_updateValues(Meter* this, char* buffer, size_t size) {
static void CPUMeter_display(const Object* cast, RichString* out) {
char buffer[50];
+ int len;
const Meter* this = (const Meter*)cast;
- RichString_prune(out);
- if (this->param > this->pl->cpuCount) {
- RichString_append(out, CRT_colors[METER_TEXT], "absent");
+
+ if (this->param > this->pl->existingCPUs) {
+ RichString_appendAscii(out, CRT_colors[METER_SHADOW], " absent");
+ return;
+ }
+
+ if (this->curItems == 0) {
+ RichString_appendAscii(out, CRT_colors[METER_SHADOW], " offline");
return;
}
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NORMAL]);
- RichString_append(out, CRT_colors[METER_TEXT], ":");
- RichString_append(out, CRT_colors[CPU_NORMAL], buffer);
+
+ 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) {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_KERNEL]);
- RichString_append(out, CRT_colors[METER_TEXT], "sy:");
- RichString_append(out, CRT_colors[CPU_SYSTEM], buffer);
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
- RichString_append(out, CRT_colors[METER_TEXT], "ni:");
- RichString_append(out, CRT_colors[CPU_NICE_TEXT], buffer);
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]);
- RichString_append(out, CRT_colors[METER_TEXT], "hi:");
- RichString_append(out, CRT_colors[CPU_IRQ], buffer);
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_SOFTIRQ]);
- RichString_append(out, CRT_colors[METER_TEXT], "si:");
- RichString_append(out, CRT_colors[CPU_SOFTIRQ], buffer);
+ 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);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "ni:");
+ RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "hi:");
+ RichString_appendnAscii(out, CRT_colors[CPU_IRQ], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_SOFTIRQ]);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "si:");
+ RichString_appendnAscii(out, CRT_colors[CPU_SOFTIRQ], buffer, len);
if (!isnan(this->values[CPU_METER_STEAL])) {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_STEAL]);
- RichString_append(out, CRT_colors[METER_TEXT], "st:");
- RichString_append(out, CRT_colors[CPU_STEAL], buffer);
+ 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])) {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_GUEST]);
- RichString_append(out, CRT_colors[METER_TEXT], "gu:");
- RichString_append(out, CRT_colors[CPU_GUEST], buffer);
+ 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);
}
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IOWAIT]);
- RichString_append(out, CRT_colors[METER_TEXT], "wa:");
- RichString_append(out, CRT_colors[CPU_IOWAIT], buffer);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IOWAIT]);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "wa:");
+ RichString_appendnAscii(out, CRT_colors[CPU_IOWAIT], buffer, len);
} else {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_KERNEL]);
- RichString_append(out, CRT_colors[METER_TEXT], "sys:");
- RichString_append(out, CRT_colors[CPU_SYSTEM], buffer);
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
- RichString_append(out, CRT_colors[METER_TEXT], "low:");
- RichString_append(out, CRT_colors[CPU_NICE_TEXT], buffer);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_KERNEL]);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "sys:");
+ RichString_appendnAscii(out, CRT_colors[CPU_SYSTEM], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "low:");
+ RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len);
if (!isnan(this->values[CPU_METER_IRQ])) {
- xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]);
- RichString_append(out, CRT_colors[METER_TEXT], "vir:");
- RichString_append(out, CRT_colors[CPU_GUEST], buffer);
+ 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);
}
}
- #ifdef HAVE_SENSORS_SENSORS_H
+ if (this->pl->settings->showCPUFrequency) {
+ char cpuFrequencyBuffer[10];
+ double cpuFrequency = this->values[CPU_METER_FREQUENCY];
+ if (isnan(cpuFrequency)) {
+ len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A ");
+ } else {
+ len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "%4uMHz ", (unsigned)cpuFrequency);
+ }
+ 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) {
char cpuTemperatureBuffer[10];
double cpuTemperature = this->values[CPU_METER_TEMPERATURE];
if (isnan(cpuTemperature)) {
- xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A");
+ len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A");
} else if (this->pl->settings->degreeFahrenheit) {
- xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sF", cpuTemperature * 9 / 5 + 32, CRT_degreeSign);
+ len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sF", cpuTemperature * 9 / 5 + 32, CRT_degreeSign);
} else {
- xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sC", cpuTemperature, CRT_degreeSign);
+ len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sC", cpuTemperature, CRT_degreeSign);
}
- RichString_append(out, CRT_colors[METER_TEXT], "temp:");
- RichString_append(out, CRT_colors[METER_VALUE], cpuTemperatureBuffer);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "temp:");
+ RichString_appendnWide(out, CRT_colors[METER_VALUE], cpuTemperatureBuffer, len);
}
#endif
}
-static void AllCPUsMeter_getRange(Meter* this, int* start, int* count) {
- CPUMeterData* data = this->meterData;
- int cpus = data->cpus;
- switch(Meter_name(this)[0]) {
+static void AllCPUsMeter_getRange(const Meter* this, int* start, int* count) {
+ const CPUMeterData* data = this->meterData;
+ unsigned int cpus = data->cpus;
+ switch (Meter_name(this)[0]) {
default:
case 'A': // All
*start = 0;
@@ -178,17 +207,26 @@ static void AllCPUsMeter_getRange(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;
}
}
+static void AllCPUsMeter_updateValues(Meter* this) {
+ CPUMeterData* data = this->meterData;
+ Meter** meters = data->meters;
+ int start, count;
+ AllCPUsMeter_getRange(this, &start, &count);
+ for (int i = 0; i < count; i++)
+ Meter_updateValues(meters[i]);
+}
+
static void CPUMeterCommonInit(Meter* this, int ncol) {
- int cpus = this->pl->cpuCount;
+ unsigned int cpus = this->pl->existingCPUs;
CPUMeterData* data = this->meterData;
if (!data) {
data = this->meterData = xMalloc(sizeof(CPUMeterData));
@@ -316,6 +354,7 @@ const MeterClass CPUMeter_class = {
.display = CPUMeter_display
},
.updateValues = CPUMeter_updateValues,
+ .getUiName = CPUMeter_getUiName,
.defaultMode = BAR_METERMODE,
.maxItems = CPU_METER_ITEMCOUNT,
.total = 100.0,
@@ -332,6 +371,7 @@ const MeterClass AllCPUsMeter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
.total = 100.0,
.attributes = CPUMeter_attributes,
@@ -351,7 +391,9 @@ const MeterClass AllCPUs2Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "AllCPUs2",
@@ -370,7 +412,9 @@ const MeterClass LeftCPUsMeter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "LeftCPUs",
@@ -389,7 +433,9 @@ const MeterClass RightCPUsMeter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "RightCPUs",
@@ -408,7 +454,9 @@ const MeterClass LeftCPUs2Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "LeftCPUs2",
@@ -427,7 +475,9 @@ const MeterClass RightCPUs2Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "RightCPUs2",
@@ -446,7 +496,9 @@ const MeterClass AllCPUs4Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "AllCPUs4",
@@ -465,7 +517,9 @@ const MeterClass LeftCPUs4Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "LeftCPUs4",
@@ -484,7 +538,9 @@ const MeterClass RightCPUs4Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "RightCPUs4",
@@ -503,7 +559,9 @@ const MeterClass AllCPUs8Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "AllCPUs8",
@@ -522,7 +580,9 @@ const MeterClass LeftCPUs8Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "LeftCPUs8",
@@ -541,7 +601,9 @@ const MeterClass RightCPUs8Meter_class = {
.delete = Meter_delete,
.display = CPUMeter_display
},
+ .updateValues = AllCPUsMeter_updateValues,
.defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
.total = 100.0,
.attributes = CPUMeter_attributes,
.name = "RightCPUs8",
diff --git a/CPUMeter.h b/CPUMeter.h
index 989451a..7f2ddb1 100644
--- a/CPUMeter.h
+++ b/CPUMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
typedef enum {
CPU_METER_NICE = 0,
CPU_METER_NORMAL = 1,
diff --git a/CRT.c b/CRT.c
index 68f6405..64c259f 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.
*/
@@ -10,8 +10,10 @@ in the source distribution for its full text.
#include "CRT.h"
#include <errno.h>
+#include <fcntl.h>
#include <langinfo.h>
#include <signal.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -20,8 +22,20 @@ in the source distribution for its full text.
#include "ProvideCurses.h"
#include "XUtils.h"
-#ifdef HAVE_EXECINFO_H
-#include <execinfo.h>
+#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
@@ -41,28 +55,33 @@ in the source distribution for its full text.
#define ColorPairGrayBlack ColorPair(Magenta,Magenta)
#define ColorIndexGrayBlack ColorIndex(Magenta,Magenta)
-static const char* const CRT_treeStrAscii[TREE_STR_COUNT] = {
- "-", // TREE_STR_HORZ
- "|", // TREE_STR_VERT
- "`", // TREE_STR_RTEE
- "`", // TREE_STR_BEND
- ",", // TREE_STR_TEND
- "+", // TREE_STR_OPEN
- "-", // TREE_STR_SHUT
+#define ColorPairWhiteDefault ColorPair(Red, Red)
+#define ColorIndexWhiteDefault ColorIndex(Red, Red)
+
+static const char* const CRT_treeStrAscii[LAST_TREE_STR] = {
+ [TREE_STR_VERT] = "|",
+ [TREE_STR_RTEE] = "`",
+ [TREE_STR_BEND] = "`",
+ [TREE_STR_TEND] = ",",
+ [TREE_STR_OPEN] = "+",
+ [TREE_STR_SHUT] = "-",
+ [TREE_STR_ASC] = "+",
+ [TREE_STR_DESC] = "-",
};
#ifdef HAVE_LIBNCURSESW
-static const char* const CRT_treeStrUtf8[TREE_STR_COUNT] = {
- "\xe2\x94\x80", // TREE_STR_HORZ ─
- "\xe2\x94\x82", // TREE_STR_VERT │
- "\xe2\x94\x9c", // TREE_STR_RTEE ├
- "\xe2\x94\x94", // TREE_STR_BEND └
- "\xe2\x94\x8c", // TREE_STR_TEND ┌
- "+", // TREE_STR_OPEN +, TODO use 🮯 'BOX DRAWINGS LIGHT HORIZONTAL
- // WITH VERTICAL STROKE' (U+1FBAF, "\xf0\x9f\xae\xaf") when
- // Unicode 13 is common
- "\xe2\x94\x80", // TREE_STR_SHUT ─
+static const char* const CRT_treeStrUtf8[LAST_TREE_STR] = {
+ [TREE_STR_VERT] = "\xe2\x94\x82", // │
+ [TREE_STR_RTEE] = "\xe2\x94\x9c", // ├
+ [TREE_STR_BEND] = "\xe2\x94\x94", // └
+ [TREE_STR_TEND] = "\xe2\x94\x8c", // ┌
+ [TREE_STR_OPEN] = "+", // +, TODO use 🮯 'BOX DRAWINGS LIGHT HORIZONTAL
+ // WITH VERTICAL STROKE' (U+1FBAF, "\xf0\x9f\xae\xaf") when
+ // Unicode 13 is common
+ [TREE_STR_SHUT] = "\xe2\x94\x80", // ─
+ [TREE_STR_ASC] = "\xe2\x96\xb3", // △
+ [TREE_STR_DESC] = "\xe2\x96\xbd", // ▽
};
bool CRT_utf8 = false;
@@ -71,6 +90,7 @@ bool CRT_utf8 = false;
const char* const* CRT_treeStr = CRT_treeStrAscii;
+static const Settings* CRT_crashSettings;
static const int* CRT_delay;
const char* CRT_degreeSign;
@@ -92,7 +112,7 @@ static const char* initDegreeSign(void) {
const int* CRT_colors;
-int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
+static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[COLORSCHEME_DEFAULT] = {
[RESET_COLOR] = ColorPair(White, Black),
[DEFAULT_COLOR] = ColorPair(White, Black),
@@ -109,13 +129,15 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = A_BOLD | ColorPair(Cyan, Black),
[BATTERY] = A_BOLD | ColorPair(Cyan, Black),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Black),
+ [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
[METER_TEXT] = ColorPair(Cyan, Black),
[METER_VALUE] = A_BOLD | ColorPair(Cyan, Black),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),
[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),
[LED_COLOR] = ColorPair(Green, Black),
[TASKS_RUNNING] = A_BOLD | ColorPair(Green, Black),
[PROCESS] = A_NORMAL,
@@ -125,7 +147,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = ColorPair(Green, Black),
[PROCESS_BASENAME] = A_BOLD | ColorPair(Cyan, Black),
[PROCESS_TREE] = ColorPair(Cyan, Black),
- [PROCESS_R_STATE] = ColorPair(Green, Black),
+ [PROCESS_RUN_STATE] = ColorPair(Green, Black),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),
@@ -134,21 +156,28 @@ 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),
[BAR_BORDER] = A_BOLD,
[BAR_SHADOW] = A_BOLD | ColorPairGrayBlack,
[SWAP] = ColorPair(Red, Black),
+ [SWAP_CACHE] = ColorPair(Yellow, Black),
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Black),
[GRAPH_2] = ColorPair(Cyan, Black),
[MEMORY_USED] = ColorPair(Green, Black),
- [MEMORY_BUFFERS] = ColorPair(Blue, Black),
+ [MEMORY_BUFFERS] = A_BOLD | ColorPair(Blue, Black),
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Blue, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
+ [MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [HUGEPAGE_1] = ColorPair(Green, Black),
+ [HUGEPAGE_2] = ColorPair(Yellow, Black),
+ [HUGEPAGE_3] = ColorPair(Red, Black),
+ [HUGEPAGE_4] = 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),
[LOAD] = A_BOLD,
[HELP_BOLD] = A_BOLD | ColorPair(Cyan, Black),
+ [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,
[CLOCK] = A_BOLD,
[DATE] = A_BOLD,
[DATETIME] = A_BOLD,
@@ -156,7 +185,7 @@ 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),
@@ -165,17 +194,31 @@ 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),
+ [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),
+ [DYNAMIC_GRAY] = ColorPairGrayBlack,
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
+ [DYNAMIC_RED] = ColorPair(Red, Black),
+ [DYNAMIC_GREEN] = ColorPair(Green, Black),
+ [DYNAMIC_BLUE] = A_BOLD | ColorPair(Blue, Black),
+ [DYNAMIC_CYAN] = ColorPair(Cyan, Black),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
+ [DYNAMIC_WHITE] = ColorPair(White, Black),
},
[COLORSCHEME_MONOCHROME] = {
[RESET_COLOR] = A_NORMAL,
@@ -193,6 +236,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = A_BOLD,
[BATTERY] = A_BOLD,
[LARGE_NUMBER] = A_BOLD,
+ [METER_SHADOW] = A_DIM,
[METER_TEXT] = A_NORMAL,
[METER_VALUE] = A_BOLD,
[METER_VALUE_ERROR] = A_BOLD,
@@ -200,6 +244,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[METER_VALUE_IOWRITE] = A_NORMAL,
[METER_VALUE_NOTICE] = A_BOLD,
[METER_VALUE_OK] = A_NORMAL,
+ [METER_VALUE_WARN] = A_BOLD,
[LED_COLOR] = A_NORMAL,
[TASKS_RUNNING] = A_BOLD,
[PROCESS] = A_NORMAL,
@@ -209,7 +254,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = A_BOLD,
[PROCESS_BASENAME] = A_BOLD,
[PROCESS_TREE] = A_BOLD,
- [PROCESS_R_STATE] = A_BOLD,
+ [PROCESS_RUN_STATE] = A_BOLD,
[PROCESS_D_STATE] = A_BOLD,
[PROCESS_HIGH_PRIORITY] = A_BOLD,
[PROCESS_LOW_PRIORITY] = A_DIM,
@@ -222,17 +267,24 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = A_BOLD,
[BAR_SHADOW] = A_DIM,
[SWAP] = A_BOLD,
+ [SWAP_CACHE] = A_NORMAL,
[GRAPH_1] = A_BOLD,
[GRAPH_2] = A_NORMAL,
[MEMORY_USED] = A_BOLD,
[MEMORY_BUFFERS] = A_NORMAL,
[MEMORY_BUFFERS_TEXT] = A_NORMAL,
[MEMORY_CACHE] = A_NORMAL,
+ [MEMORY_SHARED] = A_NORMAL,
+ [HUGEPAGE_1] = A_BOLD,
+ [HUGEPAGE_2] = A_NORMAL,
+ [HUGEPAGE_3] = A_REVERSE | A_BOLD,
+ [HUGEPAGE_4] = A_REVERSE,
[LOAD_AVERAGE_FIFTEEN] = A_DIM,
[LOAD_AVERAGE_FIVE] = A_NORMAL,
[LOAD_AVERAGE_ONE] = A_BOLD,
[LOAD] = A_BOLD,
[HELP_BOLD] = A_BOLD,
+ [HELP_SHADOW] = A_DIM,
[CLOCK] = A_BOLD,
[DATE] = A_BOLD,
[DATETIME] = A_BOLD,
@@ -249,6 +301,11 @@ 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,
@@ -260,6 +317,15 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = A_BOLD,
[ZFS_RATIO] = A_BOLD,
[ZRAM] = A_NORMAL,
+ [DYNAMIC_GRAY] = A_DIM,
+ [DYNAMIC_DARKGRAY] = A_DIM,
+ [DYNAMIC_RED] = A_BOLD,
+ [DYNAMIC_GREEN] = A_NORMAL,
+ [DYNAMIC_BLUE] = A_NORMAL,
+ [DYNAMIC_CYAN] = A_BOLD,
+ [DYNAMIC_MAGENTA] = A_NORMAL,
+ [DYNAMIC_YELLOW] = A_NORMAL,
+ [DYNAMIC_WHITE] = A_BOLD,
},
[COLORSCHEME_BLACKONWHITE] = {
[RESET_COLOR] = ColorPair(Black, White),
@@ -277,6 +343,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = ColorPair(Yellow, White),
[BATTERY] = ColorPair(Yellow, White),
[LARGE_NUMBER] = ColorPair(Red, White),
+ [METER_SHADOW] = ColorPair(Blue, White),
[METER_TEXT] = ColorPair(Blue, White),
[METER_VALUE] = ColorPair(Black, White),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, White),
@@ -284,6 +351,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[METER_VALUE_IOWRITE] = ColorPair(Yellow, White),
[METER_VALUE_NOTICE] = A_BOLD | ColorPair(Yellow, White),
[METER_VALUE_OK] = ColorPair(Green, White),
+ [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, White),
[LED_COLOR] = ColorPair(Green, White),
[TASKS_RUNNING] = ColorPair(Green, White),
[PROCESS] = ColorPair(Black, White),
@@ -293,7 +361,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = ColorPair(Green, White),
[PROCESS_BASENAME] = ColorPair(Blue, White),
[PROCESS_TREE] = ColorPair(Green, White),
- [PROCESS_R_STATE] = ColorPair(Green, White),
+ [PROCESS_RUN_STATE] = ColorPair(Green, White),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, White),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, White),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, White),
@@ -306,17 +374,24 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = ColorPair(Blue, White),
[BAR_SHADOW] = ColorPair(Black, White),
[SWAP] = ColorPair(Red, White),
+ [SWAP_CACHE] = ColorPair(Yellow, White),
[GRAPH_1] = A_BOLD | ColorPair(Blue, White),
[GRAPH_2] = ColorPair(Blue, White),
[MEMORY_USED] = ColorPair(Green, White),
[MEMORY_BUFFERS] = ColorPair(Cyan, White),
[MEMORY_BUFFERS_TEXT] = ColorPair(Cyan, White),
[MEMORY_CACHE] = ColorPair(Yellow, White),
+ [MEMORY_SHARED] = ColorPair(Magenta, White),
+ [HUGEPAGE_1] = ColorPair(Green, White),
+ [HUGEPAGE_2] = ColorPair(Yellow, White),
+ [HUGEPAGE_3] = ColorPair(Red, White),
+ [HUGEPAGE_4] = ColorPair(Blue, White),
[LOAD_AVERAGE_FIFTEEN] = ColorPair(Black, White),
[LOAD_AVERAGE_FIVE] = ColorPair(Black, White),
[LOAD_AVERAGE_ONE] = ColorPair(Black, White),
[LOAD] = ColorPair(Black, White),
[HELP_BOLD] = ColorPair(Blue, White),
+ [HELP_SHADOW] = A_BOLD | ColorPair(Black, White),
[CLOCK] = ColorPair(Black, White),
[DATE] = ColorPair(Black, White),
[DATETIME] = ColorPair(Black, White),
@@ -333,6 +408,11 @@ 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),
@@ -343,13 +423,22 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_OTHER] = ColorPair(Magenta, White),
[ZFS_COMPRESSED] = ColorPair(Cyan, White),
[ZFS_RATIO] = ColorPair(Magenta, White),
- [ZRAM] = ColorPair(Yellow, White)
+ [ZRAM] = ColorPair(Yellow, White),
+ [DYNAMIC_GRAY] = ColorPair(Black, White),
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPair(Black, White),
+ [DYNAMIC_RED] = ColorPair(Red, White),
+ [DYNAMIC_GREEN] = ColorPair(Green, White),
+ [DYNAMIC_BLUE] = ColorPair(Blue, White),
+ [DYNAMIC_CYAN] = ColorPair(Yellow, White),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, White),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, White),
+ [DYNAMIC_WHITE] = A_BOLD | ColorPair(Black, White),
},
[COLORSCHEME_LIGHTTERMINAL] = {
- [RESET_COLOR] = ColorPair(Blue, Black),
- [DEFAULT_COLOR] = ColorPair(Blue, Black),
+ [RESET_COLOR] = ColorPair(Black, Black),
+ [DEFAULT_COLOR] = ColorPair(Black, Black),
[FUNCTION_BAR] = ColorPair(Black, Cyan),
- [FUNCTION_KEY] = ColorPair(Blue, Black),
+ [FUNCTION_KEY] = ColorPair(Black, Black),
[PANEL_HEADER_FOCUS] = ColorPair(Black, Green),
[PANEL_HEADER_UNFOCUS] = ColorPair(Black, Green),
[PANEL_SELECTION_FOCUS] = ColorPair(Black, Cyan),
@@ -361,23 +450,25 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = ColorPair(Yellow, Black),
[BATTERY] = ColorPair(Yellow, Black),
[LARGE_NUMBER] = ColorPair(Red, Black),
+ [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
[METER_TEXT] = ColorPair(Blue, Black),
- [METER_VALUE] = ColorPair(Blue, Black),
+ [METER_VALUE] = ColorPair(Black, Black),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),
[METER_VALUE_IOREAD] = ColorPair(Green, Black),
[METER_VALUE_IOWRITE] = ColorPair(Yellow, Black),
- [METER_VALUE_NOTICE] = A_BOLD | ColorPair(Yellow, Black),
+ [METER_VALUE_NOTICE] = A_BOLD | ColorPairWhiteDefault,
[METER_VALUE_OK] = ColorPair(Green, Black),
+ [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, Black),
[LED_COLOR] = ColorPair(Green, Black),
[TASKS_RUNNING] = ColorPair(Green, Black),
- [PROCESS] = ColorPair(Blue, Black),
+ [PROCESS] = ColorPair(Black, Black),
[PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack,
- [PROCESS_TAG] = ColorPair(Yellow, Blue),
+ [PROCESS_TAG] = ColorPair(White, Blue),
[PROCESS_MEGABYTES] = ColorPair(Blue, Black),
[PROCESS_GIGABYTES] = ColorPair(Green, Black),
[PROCESS_BASENAME] = ColorPair(Green, Black),
[PROCESS_TREE] = ColorPair(Blue, Black),
- [PROCESS_R_STATE] = ColorPair(Green, Black),
+ [PROCESS_RUN_STATE] = ColorPair(Green, Black),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),
@@ -390,44 +481,65 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = ColorPair(Blue, Black),
[BAR_SHADOW] = ColorPairGrayBlack,
[SWAP] = ColorPair(Red, Black),
+ [SWAP_CACHE] = ColorPair(Yellow, Black),
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Black),
[GRAPH_2] = ColorPair(Cyan, Black),
[MEMORY_USED] = ColorPair(Green, Black),
[MEMORY_BUFFERS] = ColorPair(Cyan, Black),
[MEMORY_BUFFERS_TEXT] = ColorPair(Cyan, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
- [LOAD_AVERAGE_FIFTEEN] = ColorPair(Blue, Black),
- [LOAD_AVERAGE_FIVE] = ColorPair(Blue, Black),
- [LOAD_AVERAGE_ONE] = ColorPair(Yellow, Black),
- [LOAD] = ColorPair(Yellow, Black),
+ [MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [HUGEPAGE_1] = ColorPair(Green, Black),
+ [HUGEPAGE_2] = ColorPair(Yellow, Black),
+ [HUGEPAGE_3] = ColorPair(Red, Black),
+ [HUGEPAGE_4] = ColorPair(Blue, Black),
+ [LOAD_AVERAGE_FIFTEEN] = ColorPair(Black, Black),
+ [LOAD_AVERAGE_FIVE] = ColorPair(Black, Black),
+ [LOAD_AVERAGE_ONE] = ColorPair(Black, Black),
+ [LOAD] = ColorPairWhiteDefault,
[HELP_BOLD] = ColorPair(Blue, Black),
- [CLOCK] = ColorPair(Yellow, Black),
- [DATE] = ColorPair(White, Black),
- [DATETIME] = ColorPair(White, Black),
+ [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,
+ [CLOCK] = ColorPairWhiteDefault,
+ [DATE] = ColorPairWhiteDefault,
+ [DATETIME] = ColorPairWhiteDefault,
[CHECK_BOX] = ColorPair(Blue, Black),
- [CHECK_MARK] = ColorPair(Blue, Black),
- [CHECK_TEXT] = ColorPair(Blue, Black),
- [HOSTNAME] = ColorPair(Yellow, Black),
+ [CHECK_MARK] = ColorPair(Black, Black),
+ [CHECK_TEXT] = ColorPair(Black, Black),
+ [HOSTNAME] = ColorPairWhiteDefault,
[CPU_NICE] = ColorPair(Cyan, Black),
[CPU_NICE_TEXT] = ColorPair(Cyan, Black),
[CPU_NORMAL] = ColorPair(Green, Black),
[CPU_SYSTEM] = ColorPair(Red, Black),
- [CPU_IOWAIT] = A_BOLD | ColorPair(Blue, Black),
+ [CPU_IOWAIT] = A_BOLD | ColorPair(Black, Black),
[CPU_IRQ] = A_BOLD | ColorPair(Blue, Black),
[CPU_SOFTIRQ] = ColorPair(Blue, Black),
- [CPU_STEAL] = ColorPair(Blue, Black),
- [CPU_GUEST] = ColorPair(Blue, Black),
- [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Blue, Black),
- [PRESSURE_STALL_SIXTY] = ColorPair(Blue, Black),
- [PRESSURE_STALL_TEN] = 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),
[ZFS_MFU] = ColorPair(Cyan, Black),
[ZFS_MRU] = ColorPair(Yellow, Black),
[ZFS_ANON] = A_BOLD | ColorPair(Magenta, Black),
- [ZFS_HEADER] = ColorPair(Blue, Black),
+ [ZFS_HEADER] = ColorPair(Black, Black),
[ZFS_OTHER] = A_BOLD | ColorPair(Magenta, Black),
[ZFS_COMPRESSED] = ColorPair(Cyan, Black),
[ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Black),
[ZRAM] = ColorPair(Yellow, Black),
+ [DYNAMIC_GRAY] = ColorPairGrayBlack,
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
+ [DYNAMIC_RED] = ColorPair(Red, Black),
+ [DYNAMIC_GREEN] = ColorPair(Green, Black),
+ [DYNAMIC_BLUE] = ColorPair(Blue, Black),
+ [DYNAMIC_CYAN] = ColorPair(Cyan, Black),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
+ [DYNAMIC_WHITE] = ColorPairWhiteDefault,
},
[COLORSCHEME_MIDNIGHT] = {
[RESET_COLOR] = ColorPair(White, Blue),
@@ -445,6 +557,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = A_BOLD | ColorPair(Yellow, Blue),
[BATTERY] = A_BOLD | ColorPair(Yellow, Blue),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Blue),
+ [METER_SHADOW] = ColorPair(Cyan, Blue),
[METER_TEXT] = ColorPair(Cyan, Blue),
[METER_VALUE] = A_BOLD | ColorPair(Cyan, Blue),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Blue),
@@ -452,6 +565,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[METER_VALUE_IOWRITE] = ColorPair(Black, Blue),
[METER_VALUE_NOTICE] = A_BOLD | ColorPair(White, Blue),
[METER_VALUE_OK] = ColorPair(Green, Blue),
+ [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, Black),
[LED_COLOR] = ColorPair(Green, Blue),
[TASKS_RUNNING] = A_BOLD | ColorPair(Green, Blue),
[PROCESS] = ColorPair(White, Blue),
@@ -461,7 +575,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_GIGABYTES] = ColorPair(Green, Blue),
[PROCESS_BASENAME] = A_BOLD | ColorPair(Cyan, Blue),
[PROCESS_TREE] = ColorPair(Cyan, Blue),
- [PROCESS_R_STATE] = ColorPair(Green, Blue),
+ [PROCESS_RUN_STATE] = ColorPair(Green, Blue),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Blue),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Blue),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Blue),
@@ -474,17 +588,24 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = A_BOLD | ColorPair(Yellow, Blue),
[BAR_SHADOW] = ColorPair(Cyan, Blue),
[SWAP] = ColorPair(Red, Blue),
+ [SWAP_CACHE] = A_BOLD | ColorPair(Yellow, Blue),
[GRAPH_1] = A_BOLD | ColorPair(Cyan, Blue),
[GRAPH_2] = ColorPair(Cyan, Blue),
[MEMORY_USED] = A_BOLD | ColorPair(Green, Blue),
[MEMORY_BUFFERS] = A_BOLD | ColorPair(Cyan, Blue),
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Cyan, Blue),
[MEMORY_CACHE] = A_BOLD | ColorPair(Yellow, Blue),
+ [MEMORY_SHARED] = A_BOLD | ColorPair(Magenta, Blue),
+ [HUGEPAGE_1] = A_BOLD | ColorPair(Green, Blue),
+ [HUGEPAGE_2] = A_BOLD | ColorPair(Yellow, Blue),
+ [HUGEPAGE_3] = A_BOLD | ColorPair(Red, Blue),
+ [HUGEPAGE_4] = A_BOLD | ColorPair(White, Blue),
[LOAD_AVERAGE_FIFTEEN] = A_BOLD | ColorPair(Black, Blue),
[LOAD_AVERAGE_FIVE] = A_NORMAL | ColorPair(White, Blue),
[LOAD_AVERAGE_ONE] = A_BOLD | ColorPair(White, Blue),
[LOAD] = A_BOLD | ColorPair(White, Blue),
[HELP_BOLD] = A_BOLD | ColorPair(Cyan, Blue),
+ [HELP_SHADOW] = A_BOLD | ColorPair(Black, Blue),
[CLOCK] = ColorPair(White, Blue),
[DATE] = ColorPair(White, Blue),
[DATETIME] = ColorPair(White, Blue),
@@ -501,6 +622,11 @@ 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),
@@ -512,6 +638,15 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = A_BOLD | ColorPair(White, Blue),
[ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Blue),
[ZRAM] = A_BOLD | ColorPair(Yellow, Blue),
+ [DYNAMIC_GRAY] = ColorPairGrayBlack,
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
+ [DYNAMIC_RED] = ColorPair(Red, Blue),
+ [DYNAMIC_GREEN] = ColorPair(Green, Blue),
+ [DYNAMIC_BLUE] = ColorPair(Black, Blue),
+ [DYNAMIC_CYAN] = ColorPair(Cyan, Blue),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, Blue),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, Blue),
+ [DYNAMIC_WHITE] = ColorPair(White, Blue),
},
[COLORSCHEME_BLACKNIGHT] = {
[RESET_COLOR] = ColorPair(Cyan, Black),
@@ -529,13 +664,15 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[UPTIME] = ColorPair(Green, Black),
[BATTERY] = ColorPair(Green, Black),
[LARGE_NUMBER] = A_BOLD | ColorPair(Red, Black),
+ [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,
[METER_TEXT] = ColorPair(Cyan, Black),
[METER_VALUE] = ColorPair(Green, Black),
[METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),
[METER_VALUE_IOREAD] = ColorPair(Green, Black),
[METER_VALUE_IOWRITE] = ColorPair(Blue, Black),
- [METER_VALUE_NOTICE] = A_BOLD | ColorPair(Yellow, Black),
+ [METER_VALUE_NOTICE] = A_BOLD | ColorPair(White, Black),
[METER_VALUE_OK] = ColorPair(Green, Black),
+ [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, Black),
[LED_COLOR] = ColorPair(Green, Black),
[TASKS_RUNNING] = A_BOLD | ColorPair(Green, Black),
[PROCESS] = ColorPair(Cyan, Black),
@@ -549,7 +686,7 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, Black),
[PROCESS_COMM] = ColorPair(Magenta, Black),
[PROCESS_THREAD_COMM] = ColorPair(Yellow, Black),
- [PROCESS_R_STATE] = ColorPair(Green, Black),
+ [PROCESS_RUN_STATE] = ColorPair(Green, Black),
[PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),
[PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),
[PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),
@@ -558,17 +695,24 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[BAR_BORDER] = A_BOLD | ColorPair(Green, Black),
[BAR_SHADOW] = ColorPair(Cyan, Black),
[SWAP] = ColorPair(Red, Black),
+ [SWAP_CACHE] = ColorPair(Yellow, Black),
[GRAPH_1] = A_BOLD | ColorPair(Green, Black),
[GRAPH_2] = ColorPair(Green, Black),
[MEMORY_USED] = ColorPair(Green, Black),
[MEMORY_BUFFERS] = ColorPair(Blue, Black),
[MEMORY_BUFFERS_TEXT] = A_BOLD | ColorPair(Blue, Black),
[MEMORY_CACHE] = ColorPair(Yellow, Black),
+ [MEMORY_SHARED] = ColorPair(Magenta, Black),
+ [HUGEPAGE_1] = ColorPair(Green, Black),
+ [HUGEPAGE_2] = ColorPair(Yellow, Black),
+ [HUGEPAGE_3] = ColorPair(Red, Black),
+ [HUGEPAGE_4] = ColorPair(Blue, Black),
[LOAD_AVERAGE_FIFTEEN] = ColorPair(Green, Black),
[LOAD_AVERAGE_FIVE] = ColorPair(Green, Black),
[LOAD_AVERAGE_ONE] = A_BOLD | ColorPair(Green, Black),
[LOAD] = A_BOLD,
[HELP_BOLD] = A_BOLD | ColorPair(Cyan, Black),
+ [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,
[CLOCK] = ColorPair(Green, Black),
[CHECK_BOX] = ColorPair(Green, Black),
[CHECK_MARK] = A_BOLD | ColorPair(Green, Black),
@@ -583,6 +727,11 @@ 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),
@@ -594,71 +743,185 @@ int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = ColorPair(Blue, Black),
[ZFS_RATIO] = ColorPair(Magenta, Black),
[ZRAM] = ColorPair(Yellow, Black),
+ [DYNAMIC_GRAY] = ColorPairGrayBlack,
+ [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
+ [DYNAMIC_RED] = ColorPair(Red, Black),
+ [DYNAMIC_GREEN] = ColorPair(Green, Black),
+ [DYNAMIC_BLUE] = ColorPair(Blue, Black),
+ [DYNAMIC_CYAN] = ColorPair(Cyan, Black),
+ [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
+ [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
+ [DYNAMIC_WHITE] = ColorPair(White, Black),
},
[COLORSCHEME_BROKENGRAY] = { 0 } // dynamically generated.
};
-int CRT_cursorX = 0;
-
int CRT_scrollHAmount = 5;
int CRT_scrollWheelVAmount = 10;
-const char* CRT_termType;
-
-int CRT_colorScheme = 0;
-
-long CRT_pageSize = -1;
-long CRT_pageSizeKB = -1;
+ColorScheme CRT_colorScheme = COLORSCHEME_DEFAULT;
ATTR_NORETURN
-static void CRT_handleSIGTERM(int sgn) {
- (void) sgn;
+static void CRT_handleSIGTERM(ATTR_UNUSED int sgn) {
CRT_done();
- exit(0);
+ _exit(0);
}
-#ifdef HAVE_SETUID_ENABLED
+#ifndef NDEBUG
-static int CRT_euid = -1;
+static int stderrRedirectNewFd = -1;
+static int stderrRedirectBackupFd = -1;
-static int CRT_egid = -1;
+static int createStderrCacheFile(void) {
+#if defined(HAVE_MEMFD_CREATE)
+ return memfd_create("htop.stderr-redirect", 0);
+#elif defined(O_TMPFILE)
+ return open("/tmp", O_TMPFILE | O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
+#else
+ char tmpName[] = "htop.stderr-redirectXXXXXX";
+ mode_t curUmask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ int r = mkstemp(tmpName);
+ umask(curUmask);
+ if (r < 0)
+ return r;
-void CRT_dropPrivileges() {
- CRT_egid = getegid();
- CRT_euid = geteuid();
- if (setegid(getgid()) == -1) {
- CRT_fatalError("Fatal error: failed dropping group privileges");
- }
- if (seteuid(getuid()) == -1) {
- CRT_fatalError("Fatal error: failed dropping user privileges");
- }
+ (void) unlink(tmpName);
+
+ return r;
+#endif /* HAVE_MEMFD_CREATE */
}
-void CRT_restorePrivileges() {
- if (CRT_egid == -1 || CRT_euid == -1) {
- CRT_fatalError("Fatal error: internal inconsistency");
+static void redirectStderr(void) {
+ stderrRedirectNewFd = createStderrCacheFile();
+ if (stderrRedirectNewFd < 0) {
+ /* ignore failure */
+ return;
}
- if (setegid(CRT_egid) == -1) {
- CRT_fatalError("Fatal error: failed restoring group privileges");
- }
- if (seteuid(CRT_euid) == -1) {
- CRT_fatalError("Fatal error: failed restoring user privileges");
+
+ stderrRedirectBackupFd = dup(STDERR_FILENO);
+ dup2(stderrRedirectNewFd, STDERR_FILENO);
+}
+
+static void dumpStderr(void) {
+ if (stderrRedirectNewFd < 0)
+ return;
+
+ fsync(STDERR_FILENO);
+ dup2(stderrRedirectBackupFd, STDERR_FILENO);
+ close(stderrRedirectBackupFd);
+ stderrRedirectBackupFd = -1;
+ lseek(stderrRedirectNewFd, 0, SEEK_SET);
+
+ bool header = false;
+ char buffer[8192];
+ for (;;) {
+ errno = 0;
+ ssize_t res = read(stderrRedirectNewFd, buffer, sizeof(buffer));
+ if (res < 0) {
+ if (errno == EINTR)
+ continue;
+
+ break;
+ }
+
+ if (res == 0) {
+ break;
+ }
+
+ if (res > 0) {
+ if (!header) {
+ fprintf(stderr, ">>>>>>>>>> stderr output >>>>>>>>>>\n");
+ header = true;
+ }
+ full_write(STDERR_FILENO, buffer, res);
+ }
}
+
+ if (header)
+ fprintf(stderr, "\n<<<<<<<<<< stderr output <<<<<<<<<<\n");
+
+ close(stderrRedirectNewFd);
+ 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");
}
-#endif /* HAVE_SETUID_ENABLED */
+#else /* !NDEBUG */
+
+static void redirectStderr(void) {
+}
+
+static void dumpStderr(void) {
+}
+
+#endif /* !NDEBUG */
static struct sigaction old_sig_handler[32];
-// TODO: pass an instance of Settings instead.
+static void CRT_installSignalHandlers(void) {
+ struct sigaction act;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = (int)SA_RESETHAND | SA_NODEFER;
+ act.sa_handler = CRT_handleSIGSEGV;
+ sigaction (SIGSEGV, &act, &old_sig_handler[SIGSEGV]);
+ sigaction (SIGFPE, &act, &old_sig_handler[SIGFPE]);
+ sigaction (SIGILL, &act, &old_sig_handler[SIGILL]);
+ sigaction (SIGBUS, &act, &old_sig_handler[SIGBUS]);
+ sigaction (SIGPIPE, &act, &old_sig_handler[SIGPIPE]);
+ sigaction (SIGSYS, &act, &old_sig_handler[SIGSYS]);
+ sigaction (SIGABRT, &act, &old_sig_handler[SIGABRT]);
+
+ signal(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);
+
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+}
+
+#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 int* delay, int colorScheme, bool allowUnicode) {
+void CRT_init(const Settings* settings, bool allowUnicode) {
initscr();
+ redirectStderr();
noecho();
- CRT_delay = delay;
- CRT_colors = CRT_colorSchemes[colorScheme];
- CRT_colorScheme = colorScheme;
+ CRT_crashSettings = settings;
+ CRT_delay = &(settings->delay);
+ CRT_colors = CRT_colorSchemes[settings->colorScheme];
+ CRT_colorScheme = settings->colorScheme;
for (int i = 0; i < LAST_COLORELEMENT; i++) {
unsigned int color = CRT_colorSchemes[COLORSCHEME_DEFAULT][i];
@@ -669,21 +932,27 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
nonl();
intrflush(stdscr, false);
keypad(stdscr, true);
+#ifdef HAVE_GETMOUSE
mouseinterval(0);
+#endif
curs_set(0);
if (has_colors()) {
start_color();
}
- CRT_termType = getenv("TERM");
- if (String_eq(CRT_termType, "linux")) {
+ const char* termType = getenv("TERM");
+ if (termType && String_eq(termType, "linux")) {
CRT_scrollHAmount = 20;
} else {
CRT_scrollHAmount = 5;
}
- if (String_startsWith(CRT_termType, "xterm") || String_eq(CRT_termType, "vt220")) {
+ if (termType && (String_startsWith(termType, "xterm") || String_eq(termType, "vt220"))) {
+#ifdef HTOP_NETBSD
+#define define_key(s_, k_) define_key((char*)s_, k_)
+IGNORE_WCASTQUAL_BEGIN
+#endif
define_key("\033[H", KEY_HOME);
define_key("\033[F", KEY_END);
define_key("\033[7~", KEY_HOME);
@@ -692,32 +961,29 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
define_key("\033OQ", KEY_F(2));
define_key("\033OR", KEY_F(3));
define_key("\033OS", KEY_F(4));
+ define_key("\033O2R", KEY_F(15));
define_key("\033[11~", KEY_F(1));
define_key("\033[12~", KEY_F(2));
define_key("\033[13~", KEY_F(3));
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;
define_key(sequence, KEY_ALT('A' + (c - 'a')));
}
+#ifdef HTOP_NETBSD
+IGNORE_WCASTQUAL_END
+#undef define_key
+#endif
+ }
+ if (termType && (String_startsWith(termType, "rxvt"))) {
+ define_key("\033[Z", KEY_SHIFT_TAB);
}
- struct sigaction act;
- sigemptyset (&act.sa_mask);
- act.sa_flags = (int)SA_RESETHAND | SA_NODEFER;
- act.sa_handler = CRT_handleSIGSEGV;
- sigaction (SIGSEGV, &act, &old_sig_handler[SIGSEGV]);
- sigaction (SIGFPE, &act, &old_sig_handler[SIGFPE]);
- sigaction (SIGILL, &act, &old_sig_handler[SIGILL]);
- sigaction (SIGBUS, &act, &old_sig_handler[SIGBUS]);
- sigaction (SIGPIPE, &act, &old_sig_handler[SIGPIPE]);
- sigaction (SIGSYS, &act, &old_sig_handler[SIGSYS]);
- sigaction (SIGABRT, &act, &old_sig_handler[SIGABRT]);
-
- signal(SIGTERM, CRT_handleSIGTERM);
- signal(SIGQUIT, CRT_handleSIGTERM);
+ CRT_installSignalHandlers();
use_default_colors();
if (!has_colors())
@@ -740,33 +1006,33 @@ void CRT_init(const int* delay, int colorScheme, bool allowUnicode) {
#endif
CRT_treeStrAscii;
-#if NCURSES_MOUSE_VERSION > 1
- mousemask(BUTTON1_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);
-#else
- mousemask(BUTTON1_RELEASED, NULL);
-#endif
-
- CRT_pageSize = sysconf(_SC_PAGESIZE);
- if (CRT_pageSize == -1)
- CRT_fatalError("Fatal error: Can not get PAGE_SIZE by sysconf(_SC_PAGESIZE)");
- CRT_pageSizeKB = CRT_pageSize / 1024;
+ 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();
+
curs_set(1);
endwin();
+
+ dumpStderr();
}
void CRT_fatalError(const char* note) {
- char* sysMsg = strerror(errno);
+ const char* sysMsg = strerror(errno);
CRT_done();
fprintf(stderr, "%s: %s\n", note, sysMsg);
exit(2);
}
-int CRT_readKey() {
+int CRT_readKey(void) {
nocbreak();
cbreak();
nodelay(stdscr, FALSE);
@@ -775,13 +1041,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);
}
@@ -790,7 +1056,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) {
+ if (ColorIndex(i, j) != ColorIndexGrayBlack && ColorIndex(i, j) != ColorIndexWhiteDefault) {
short int bg = (colorScheme != COLORSCHEME_BLACKNIGHT)
? (j == 0 ? -1 : j)
: j;
@@ -803,9 +1069,64 @@ void CRT_setColors(int colorScheme) {
short int grayBlackBg = (colorScheme != COLORSCHEME_BLACKNIGHT) ? -1 : 0;
init_pair(ColorIndexGrayBlack, grayBlackFg, grayBlackBg);
+ init_pair(ColorIndexWhiteDefault, White, -1);
+
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 char* frame = "";
+ if (unw_is_signal_frame(&cursor) > 0)
+ frame = "{signal frame}";
+
+ fprintf(stderr, "%2u: %#14lx %s (%s+%#lx) [%p]%s%s\n", item++, pc, fname, symbolName, offset, ptr, frame ? " " : "", 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();
@@ -814,14 +1135,17 @@ void CRT_handleSIGSEGV(int signal) {
"============================\n"
"Please check at https://htop.dev/issues whether this issue has already been reported.\n"
"If no similar issue has been reported before, please create a new issue with the following information:\n"
- "\n"
- "- Your htop version (htop --version)\n"
- "- Your OS and kernel version (uname -a)\n"
- "- Your distribution and release (lsb_release -a)\n"
- "- Likely steps to reproduce (How did it happened?)\n"
-#ifdef HAVE_EXECINFO_H
- "- Backtrace of the issue (see below)\n"
+ " - Your "PACKAGE" version: '"VERSION"'\n"
+ " - Your OS and kernel version (uname -a)\n"
+ " - Your distribution and release (lsb_release -a)\n"
+ " - Likely steps to reproduce (How did it happen?)\n"
+ );
+
+#ifdef PRINT_BACKTRACE
+ fprintf(stderr, " - Backtrace of the issue (see below)\n");
#endif
+
+ fprintf(stderr,
"\n"
);
@@ -837,42 +1161,44 @@ void CRT_handleSIGSEGV(int signal) {
signal, signal_str
);
-#ifdef HAVE_EXECINFO_H
+ fprintf(stderr,
+ "Setting information:\n"
+ "--------------------\n");
+ Settings_write(CRT_crashSettings, true);
+ fprintf(stderr, "\n\n");
+
+#ifdef PRINT_BACKTRACE
fprintf(stderr,
"Backtrace information:\n"
"----------------------\n"
- "The following function calls were active when the issue was detected:\n"
- "---\n"
);
- void *backtraceArray[256];
+ print_backtrace();
- size_t size = backtrace(backtraceArray, ARRAYSIZE(backtraceArray));
- backtrace_symbols_fd(backtraceArray, size, 2);
fprintf(stderr,
- "---\n"
"\n"
- "To make the above information more practical to work with,\n"
- "you should provide a disassembly of your binary.\n"
+ "To make the above information more practical to work with, "
+ "please also provide a disassembly of your "PACKAGE" binary. "
"This can usually be done by running the following command:\n"
"\n"
+ );
+
#ifdef HTOP_DARWIN
- " otool -tvV `which htop` > ~/htop.otool\n"
+ fprintf(stderr, " otool -tvV `which "PACKAGE"` > ~/htop.otool\n");
#else
- " objdump -d -S -w `which htop` > ~/htop.objdump\n"
+ fprintf(stderr, " objdump -d -S -w `which "PACKAGE"` > ~/htop.objdump\n");
#endif
+
+ fprintf(stderr,
"\n"
"Please include the generated file in your report.\n"
- "\n"
);
#endif
fprintf(stderr,
"Running this program with debug symbols or inside a debugger may provide further insights.\n"
"\n"
- "Thank you for helping to improve htop!\n"
- "\n"
- "htop " VERSION " aborting.\n"
+ "Thank you for helping to improve "PACKAGE"!\n"
"\n"
);
diff --git a/CRT.h b/CRT.h
index ec3fdaf..c06d3ae 100644
--- a/CRT.h
+++ b/CRT.h
@@ -3,7 +3,7 @@
/*
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.
*/
@@ -13,29 +13,31 @@ in the source distribution for its full text.
#include "Macros.h"
#include "ProvideCurses.h"
+#include "Settings.h"
typedef enum TreeStr_ {
- TREE_STR_HORZ,
TREE_STR_VERT,
TREE_STR_RTEE,
TREE_STR_BEND,
TREE_STR_TEND,
TREE_STR_OPEN,
TREE_STR_SHUT,
- TREE_STR_COUNT
+ TREE_STR_ASC,
+ TREE_STR_DESC,
+ LAST_TREE_STR
} TreeStr;
-typedef enum ColorSchemes_ {
- COLORSCHEME_DEFAULT = 0,
+typedef enum ColorScheme_ {
+ COLORSCHEME_DEFAULT,
COLORSCHEME_MONOCHROME,
COLORSCHEME_BLACKONWHITE,
COLORSCHEME_LIGHTTERMINAL,
COLORSCHEME_MIDNIGHT,
COLORSCHEME_BLACKNIGHT,
COLORSCHEME_BROKENGRAY,
- LAST_COLORSCHEME,
-} ColorSchemes;
+ LAST_COLORSCHEME
+} ColorScheme;
typedef enum ColorElements_ {
RESET_COLOR,
@@ -51,6 +53,7 @@ typedef enum ColorElements_ {
PANEL_SELECTION_FOLLOW,
PANEL_SELECTION_UNFOCUS,
LARGE_NUMBER,
+ METER_SHADOW,
METER_TEXT,
METER_VALUE,
METER_VALUE_ERROR,
@@ -58,18 +61,20 @@ typedef enum ColorElements_ {
METER_VALUE_IOWRITE,
METER_VALUE_NOTICE,
METER_VALUE_OK,
+ METER_VALUE_WARN,
LED_COLOR,
UPTIME,
BATTERY,
TASKS_RUNNING,
SWAP,
+ SWAP_CACHE,
PROCESS,
PROCESS_SHADOW,
PROCESS_TAG,
PROCESS_MEGABYTES,
PROCESS_GIGABYTES,
PROCESS_TREE,
- PROCESS_R_STATE,
+ PROCESS_RUN_STATE,
PROCESS_D_STATE,
PROCESS_BASENAME,
PROCESS_HIGH_PRIORITY,
@@ -88,6 +93,11 @@ typedef enum ColorElements_ {
MEMORY_BUFFERS,
MEMORY_BUFFERS_TEXT,
MEMORY_CACHE,
+ MEMORY_SHARED,
+ HUGEPAGE_1,
+ HUGEPAGE_2,
+ HUGEPAGE_3,
+ HUGEPAGE_4,
LOAD,
LOAD_AVERAGE_FIFTEEN,
LOAD_AVERAGE_FIVE,
@@ -99,6 +109,7 @@ typedef enum ColorElements_ {
DATE,
DATETIME,
HELP_BOLD,
+ HELP_SHADOW,
HOSTNAME,
CPU_NICE,
CPU_NICE_TEXT,
@@ -109,6 +120,11 @@ typedef enum ColorElements_ {
CPU_SOFTIRQ,
CPU_STEAL,
CPU_GUEST,
+ PANEL_EDIT,
+ SCREENS_OTH_BORDER,
+ SCREENS_OTH_TEXT,
+ SCREENS_CUR_BORDER,
+ SCREENS_CUR_TEXT,
PRESSURE_STALL_TEN,
PRESSURE_STALL_SIXTY,
PRESSURE_STALL_THREEHUNDRED,
@@ -120,16 +136,33 @@ typedef enum ColorElements_ {
ZFS_COMPRESSED,
ZFS_RATIO,
ZRAM,
+ DYNAMIC_GRAY,
+ DYNAMIC_DARKGRAY,
+ DYNAMIC_RED,
+ DYNAMIC_GREEN,
+ DYNAMIC_BLUE,
+ DYNAMIC_CYAN,
+ DYNAMIC_MAGENTA,
+ DYNAMIC_YELLOW,
+ DYNAMIC_WHITE,
LAST_COLORELEMENT
} ColorElements;
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;
@@ -144,39 +177,26 @@ extern const char* const* CRT_treeStr;
extern const int* CRT_colors;
-extern int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT];
-
extern int CRT_cursorX;
extern int CRT_scrollHAmount;
extern int CRT_scrollWheelVAmount;
-extern const char* CRT_termType;
-
-extern int CRT_colorScheme;
-
-extern long CRT_pageSize;
-extern long CRT_pageSizeKB;
-
-#ifdef HAVE_SETUID_ENABLED
+extern ColorScheme CRT_colorScheme;
-void CRT_dropPrivileges(void);
-
-void CRT_restorePrivileges(void);
-
-#else /* HAVE_SETUID_ENABLED */
-
-/* Turn setuid operations into NOPs */
-static inline void CRT_dropPrivileges(void) { }
-static inline void CRT_restorePrivileges(void) { }
-
-#endif /* HAVE_SETUID_ENABLED */
+#ifdef HAVE_GETMOUSE
+void CRT_setMouse(bool enabled);
+#else
+#define CRT_setMouse(enabled)
+#endif
-void CRT_init(const int* delay, int colorScheme, bool allowUnicode);
+void CRT_init(const Settings* settings, bool allowUnicode);
void CRT_done(void);
+void CRT_resetSignalHandlers(void);
+
int CRT_readKey(void);
void CRT_disableDelay(void);
diff --git a/CategoriesPanel.c b/CategoriesPanel.c
index 4ee1ad4..6e905ce 100644
--- a/CategoriesPanel.c
+++ b/CategoriesPanel.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -14,14 +14,19 @@ in the source distribution for its full text.
#include "AvailableColumnsPanel.h"
#include "AvailableMetersPanel.h"
#include "ColorsPanel.h"
-#include "ColumnsPanel.h"
#include "DisplayOptionsPanel.h"
#include "FunctionBar.h"
+#include "Header.h"
+#include "HeaderLayout.h"
+#include "HeaderOptionsPanel.h"
#include "ListItem.h"
+#include "Macros.h"
#include "MetersPanel.h"
#include "Object.h"
#include "ProvideCurses.h"
+#include "ScreensPanel.h"
#include "Vector.h"
+#include "XUtils.h"
static const char* const CategoriesFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL};
@@ -33,14 +38,24 @@ static void CategoriesPanel_delete(Object* object) {
free(this);
}
-void CategoriesPanel_makeMetersPage(CategoriesPanel* this) {
- MetersPanel* leftMeters = MetersPanel_new(this->settings, "Left column", this->header->columns[0], this->scr);
- MetersPanel* rightMeters = MetersPanel_new(this->settings, "Right column", this->header->columns[1], this->scr);
- leftMeters->rightNeighbor = rightMeters;
- rightMeters->leftNeighbor = leftMeters;
- Panel* availableMeters = (Panel*) AvailableMetersPanel_new(this->settings, this->header, (Panel*) leftMeters, (Panel*) rightMeters, this->scr, this->pl);
- ScreenManager_add(this->scr, (Panel*) leftMeters, 20);
- ScreenManager_add(this->scr, (Panel*) rightMeters, 20);
+static void CategoriesPanel_makeMetersPage(CategoriesPanel* this) {
+ size_t columns = HeaderLayout_getColumns(this->scr->header->headerLayout);
+ MetersPanel** meterPanels = xMallocArray(columns, sizeof(MetersPanel*));
+
+ for (size_t i = 0; i < columns; i++) {
+ char titleBuffer[32];
+ xSnprintf(titleBuffer, sizeof(titleBuffer), "Column %zu", i + 1);
+ meterPanels[i] = MetersPanel_new(this->settings, titleBuffer, this->header->columns[i], this->scr);
+
+ if (i != 0) {
+ meterPanels[i]->leftNeighbor = meterPanels[i - 1];
+ meterPanels[i - 1]->rightNeighbor = meterPanels[i];
+ }
+
+ ScreenManager_add(this->scr, (Panel*) meterPanels[i], 20);
+ }
+
+ Panel* availableMeters = (Panel*) AvailableMetersPanel_new(this->settings, this->header, columns, meterPanels, this->scr, this->pl);
ScreenManager_add(this->scr, availableMeters, -1);
}
@@ -50,17 +65,38 @@ static void CategoriesPanel_makeDisplayOptionsPage(CategoriesPanel* this) {
}
static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) {
- Panel* colors = (Panel*) ColorsPanel_new(this->settings, this->scr);
+ Panel* colors = (Panel*) ColorsPanel_new(this->settings);
ScreenManager_add(this->scr, colors, -1);
}
-static void CategoriesPanel_makeColumnsPage(CategoriesPanel* this) {
- Panel* columns = (Panel*) ColumnsPanel_new(this->settings);
- Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns);
+static void CategoriesPanel_makeScreensPage(CategoriesPanel* this) {
+ Panel* screens = (Panel*) ScreensPanel_new(this->settings);
+ Panel* columns = (Panel*) ((ScreensPanel*)screens)->columns;
+ Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, this->settings->dynamicColumns);
+ ScreenManager_add(this->scr, screens, 20);
ScreenManager_add(this->scr, columns, 20);
ScreenManager_add(this->scr, availableColumns, -1);
}
+static void CategoriesPanel_makeHeaderOptionsPage(CategoriesPanel* this) {
+ Panel* colors = (Panel*) HeaderOptionsPanel_new(this->settings, this->scr);
+ ScreenManager_add(this->scr, colors, -1);
+}
+
+typedef void (* CategoriesPanel_makePageFunc)(CategoriesPanel* ref);
+typedef struct CategoriesPanelPage_ {
+ const char* name;
+ CategoriesPanel_makePageFunc ctor;
+} CategoriesPanelPage;
+
+static const CategoriesPanelPage categoriesPanelPages[] = {
+ { .name = "Display options", .ctor = CategoriesPanel_makeDisplayOptionsPage },
+ { .name = "Header layout", .ctor = CategoriesPanel_makeHeaderOptionsPage },
+ { .name = "Meters", .ctor = CategoriesPanel_makeMetersPage },
+ { .name = "Screens", .ctor = CategoriesPanel_makeScreensPage },
+ { .name = "Colors", .ctor = CategoriesPanel_makeColorsPage },
+};
+
static HandlerResult CategoriesPanel_eventHandler(Panel* super, int ch) {
CategoriesPanel* this = (CategoriesPanel*) super;
@@ -98,19 +134,8 @@ static HandlerResult CategoriesPanel_eventHandler(Panel* super, int ch) {
for (int i = 1; i < size; i++)
ScreenManager_remove(this->scr, 1);
- switch (selected) {
- case 0:
- CategoriesPanel_makeMetersPage(this);
- break;
- case 1:
- CategoriesPanel_makeDisplayOptionsPage(this);
- break;
- case 2:
- CategoriesPanel_makeColorsPage(this);
- break;
- case 3:
- CategoriesPanel_makeColumnsPage(this);
- break;
+ if (selected >= 0 && (size_t)selected < ARRAYSIZE(categoriesPanelPages)) {
+ categoriesPanelPages[selected].ctor(this);
}
}
return result;
@@ -134,10 +159,11 @@ CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Settings* settings, Hea
this->settings = settings;
this->header = header;
this->pl = pl;
- Panel_setHeader(super, "Setup");
- Panel_add(super, (Object*) ListItem_new("Meters", 0));
- Panel_add(super, (Object*) ListItem_new("Display options", 0));
- Panel_add(super, (Object*) ListItem_new("Colors", 0));
- Panel_add(super, (Object*) ListItem_new("Columns", 0));
+ Panel_setHeader(super, "Categories");
+ for (size_t i = 0; i < ARRAYSIZE(categoriesPanelPages); i++)
+ Panel_add(super, (Object*) ListItem_new(categoriesPanelPages[i].name, 0));
+
+ ScreenManager_add(scr, super, 16);
+ categoriesPanelPages[0].ctor(this);
return this;
}
diff --git a/CategoriesPanel.h b/CategoriesPanel.h
index 0582c64..825cd06 100644
--- a/CategoriesPanel.h
+++ b/CategoriesPanel.h
@@ -3,7 +3,7 @@
/*
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.
*/
@@ -13,6 +13,7 @@ in the source distribution for its full text.
#include "ScreenManager.h"
#include "Settings.h"
+
typedef struct CategoriesPanel_ {
Panel super;
ScreenManager* scr;
@@ -22,8 +23,6 @@ typedef struct CategoriesPanel_ {
ProcessList* pl;
} CategoriesPanel;
-void CategoriesPanel_makeMetersPage(CategoriesPanel* this);
-
extern const PanelClass CategoriesPanel_class;
CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Settings* settings, Header* header, ProcessList* pl);
diff --git a/ChangeLog b/ChangeLog
index 0b70458..5717a52 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,336 @@
+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 ProcessList quadratic removal when scanning processes
+* Under LXC, limit CPU count to that given by /proc/cpuinfo
+* Improve container detection for LXC
+* Some minor documentation fixes
+
+What's new in version 3.2.0
+
+* Support for displaying multiple tabs in the user interface
+* Allow multiple filter and search terms (logical OR, separate by "|")
+* Set correct default sorting direction (defaultSortDesc)
+* Improve performance for process lookup and update
+* Rework the IOMeters initial display
+* Removed duplicate sections on COMM and EXE
+* Highlight process UNINTERRUPTIBLE_WAIT state (D)
+* Show only integer value when CPU% more than 99.9%
+* Handle rounding ambiguity between 99.9 and 100.0%
+* No longer leaves empty the last column in header
+* Fix header layout and meters reset if a header column is empty
+* Fix PID and UID column widths off-by-one error
+* On Linux, read generic sysfs batteries
+* On Linux, do not collect LRS per thread (it is process-wide)
+* On Linux, dynamically adjust the SECATTR and CGROUP column widths
+* On Linux, fix a crash in LXD
+* On FreeBSD, add support for showing process emulation
+* On Darwin, lazily set process TTY name
+* Always set SIGCHLD to default handling
+* Avoid zombie processes on signal races
+* Ensure last line is cleared when SIGINT is received
+* Instead of SIGTERM, pre-select the last sent signal
+* Internal Hashtable performance and sizing improvements
+* Add heuristics for guessing LXC or Docker from /proc/1/mounts
+* Force elapsed time display to zero if process started in the future
+* Avoid extremely large year values when printing time
+* Fix division by zero when calculating IO rates
+* Fix out of boundary writes in XUtils
+* Fix custom thread name display issue
+* Use AC_CANONICAL_HOST, not AC_CANONICAL_TARGET in configure.ac
+* Support libunwind of LLVM
+
+What's new in version 3.1.2
+
+* Bugfix for crash when storing modified settings at exit
+* 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)
+ With this change the license is now GPLv2 without any additional wording.
+* Improved default sort ordering
+ Note for users: This may lead to an inverted sort order on startup of
+ htop 3.1.0 compared to previous versions.
+ This is due to what is stored in your htoprc file. Solution: Press I
+ (to invert sort order).
+ This changed setting will be saved by htop on exit as long as it can
+ write to your htoprc file.
+* The compile-time option to cater specifically for running htop as
+ setuid has been removed
+* Add read-only option
+ This allows htop to be run in an non-intrusive fashion where it acts only
+ as a process viewer disabling all functions to manipulate system state.
+ Note: This is not a security feature!
+* Move the code for handling the command line formatting related tasks
+ to be shared across all platforms
+ This means important features like stale binary/library highlighting
+ can now be available on all supported platforms.
+* Make the EXE and COMM columns available on all platforms
+ All supported platforms have the name of the executable (EXE) and a
+ self-chosen thread/command name (COMM) available one way or the other.
+ Moving this column to be handled as a platform-independently available
+ information simplifies the markup of the command line.
+* Introduce configuration file versioning and config_reader_min_version
+ Starting with this version the configuration file contains a version
+ identifying the minimum version of the configuration parser needed to
+ fully understand the configuration file format.
+ Old configuration file formats are automatically upgraded when
+ saving the config file (htoprc).
+* Make the configuration parser friendlier to users (thanks to Bart Bakker)
+ With this change only settings that cannot be parsed properly are
+ reset to their defaults.
+* Improve default display for systems with many CPUs
+* Add the process ELAPSED time column
+* Improve the process STATE column sorting
+* Reworked handling resize and redrawing of the UI
+* Fixed an issue where the LED meter mode could overflow allotted space
+* Allow text mode Meters to span empty neighbors to the right
+* Rescale graph meters when value of total changes
+ (thanks to Michael Schönitzer)
+* Update generic process field display
+ Usually "uninteresting" values in columns like 1 thread, nice value
+ of 0, CPU and memory of 0%, idle/sleeping state, etc. are shown with
+ reduced intensity (dark grey)
+* Option and key ("*") to collapse / expand all branches under PID 1
+ (and PID 2 if kernel threads are shown) (thanks to Krishna Chaitanya)
+* Keep following a process when inverting the sort order, displaying
+ the help screen or hiding/unhiding userland threads.
+ If a thread is currently selected the selection is updated to point
+ to the thread's parent process. (thanks to Gonzalo, et.al.)
+* Reorder process scanning to be performed before updating the display
+ of the meters in the header
+* Always check the user for a process for any changes.
+ This affects multiple platforms that previously didn't correctly handle
+ the user field for a process to change at runtime (e.g. due to seteuid
+ or similar syscalls).
+* Disable mouse option when support is unavailable
+* Support curses libraries without ncurses mouse support
+ (thanks to Santhosh Raju)
+* Support offline and hot-swapping of CPUs on all platforms
+* Fix the CPU Meter for machines with more than 256 CPUs
+* Supplemented the "show updated/deleted executables" feature (red basename)
+ to indicate when linked libraries were updated (yellow basename)
+* Apply the stale binary highlighting for the EXE column in addition to
+ the command line field
+* Add new combined Memory and Swap meter
+* Implement bar and graph mode for NetworkIO Meter
+ (thanks to Michael F. Schönitzer)
+* Rework TTY column to be more consistent across platforms
+* Make the CWD column generally available on all platforms
+ (thanks to Santhosh Raju et. al.)
+* Add Performance Co-Pilot (PCP) platform support
+ This is added via a separate pcp-htop(1) binary which provides remote host
+ analysis, new Meters for any PCP metric and new Columns for any PCP process
+ metric - see the pcp-htop(5) man page for further details.
+ (thanks to Sohaib Mohamed)
+* Add Linux columns and key bindings for process autogroup identifier
+ and nice value
+* Change available and used memory reporting on Linux to be based on
+ MemAvailable (Kernel 3.14+) (thanks to Chris Cheney and Tomas Wido)
+* Add a new SysArchMeter showing kernel and platform information
+ (thanks to ahgamut)
+* Linux memory usage explicitly treats tmpfs memory usage as shared memory
+ This is to make memory used by tmpfs visible as this cannot be freed
+ unlike normal filesystem cache data.
+* Exclude zram devices when calculating DiskIO on Linux
+* Use PATH lookup for systemctl in systemd meter (thanks to Scott Olson)
+* Add native platform support for NetBSD
+ This allows htop to run on NetBSD without the need for active Linux
+ emulation of the procfs filesystem.
+ (thanks to Santhosh Raju and Nia Alarie)
+* Add NetworkIO, DiskIO, CPU frequency, and battery meter support on NetBSD
+ (thanks to Nia Alarie)
+* Fix NetBSD display of in-use and cached memory (thanks to Nia Alarie)
+* Rework NetBSD CPU and memory accounting (thanks to Santhosh Raju)
+* Fix NetBSD accounting of user and kernel threads (thanks to Santhosh Raju)
+* Initial work to allow building with default libcurses on NetBSD
+ (thanks to Santhosh Raju)
+* FreeBSD updates - implement process majflt and processor column values
+* Add FreeBSD support for CPU frequency and temperature
+* Fixes and cleanups for ZFS Meters and metrics
+* Correctly color the ZFS ARC ratio (thanks to Ross Williams)
+* Bugfixes related to CPU time display/calculations for darwin on M1 systems
+ (thanks to Alexander Momchilov)
+* Harmonize the handling of multiple batteries across different platforms.
+ The system is now considered to run on AC if at least one power supply
+ marked as AC is found in the system.
+ Battery capacity is summed up over all batteries found.
+ This also changes the old behavior that batteries reported by the
+ system after the first AC adapter where sometimes ignored.
+* Correctly handle multiple batteries on Darwin.
+ Resolves a possible memory leak on systems with multiple batteries.
+* Handle Linux Shmem being part of Cached in the MemoryMeter
+* Add SwapCached to the Linux swap meter (thanks to David Zarzycki)
+* Convert process time to days if applicable (thanks to David Zarzycki)
+* Always show the number of threads in the TaskMeter, even when threads
+ are not shown in the process list
+* Fix Linux --drop-capabilities option handling
+* Correctly detect failure to initialize Linux boottime
+* Overhaul the Linux memory fields to partition them like free(1) now does
+* Improve the Linux process I/O column values
+* Rework the libsensors parsing on Linux
+* Update the MemoryMeter to display shared memory
+* Update OpenBSD platform - implement additional columns, scan LWP,
+ proper markup for STATE, show CPU frequency
+* Fix the tree view on OpenBSD when hiding kernel threads
+* Remove old InfoScreen lines before re-scanning (thanks to Øystein Hiåsen)
+* Document historic naming of Light-Weight Processes column aka threads
+* Improve user interaction when the last process entry is selected
+* Draw the panel header on the TraceScreen (thanks to Youngjae Lee)
+* Add mouse wheel scroll and fix mouse selection on the InfoScreen
+ (thanks to Youngjae Lee)
+* Add a HugepageMeter and subtract hugepages from normal memory
+* Display wide characters in LED meters and restore non-wide ncurses support
+* Add command line option to drop Linux capabilities
+* Support scheduler affinity on platforms beyond Linux
+* Report on any failure to write the configuration file
+* Cache stderr to be able to print assert messages.
+ These messages are shown in case htop terminates unexpectedly.
+* Print current settings on crash
+* Reset signal handlers on program exit
+* Add configure script option to create a static htop binary
+* Resolved longer-standing compilation issues on Solaris/Illumos
+* Check for availability of set_escdelay in configure
+ (thanks to Stefan Polluks)
+* Build system updates for autotools 2.70
+
+What's new in version 3.0.5
+
+* BUGFIX / SECURITY: InfoScreen: fix uncontrolled format string
+* BUGFIX: Improve white text in the Light Terminal colour scheme
+ (both of the above thanks to V)
+* Enable the function bar on the main screen to be hidden (see Setup -> Display options)
+* BUGFIX: Reduce layout issues esp. around printing wide characters (not complete yet)
+* BUGFIX: Make the follow function exit cleanly after followed process died
+* Solaris: make Process callbacks static
+* Update help and man page for improved -t / -s options
+* Drop usage of formatted error messages from <err.h>
+* Show arrow indicating order of sorted process column
+* Lots of plumbing around the internal Hashtable, hardening and code cleanups
+* LibSensors: add support for Ryzen CPUs
+ (thanks to Matej Dian)
+* BUGFIX: Fix CPU percentage on M1 silicon Macs
+ (thanks to Luke Groeninger)
+* LoadMeter: dynamically adjust color and total of bar
+* Find libsensors.so.4 for Fedora and friends
+* Add support to display CPU frequencies on Solarish platforms
+ (thanks to Dominik Hassler)
+* Enable going back to previous search matches (Shift-F3)
+* Added keybind 'N' for sorting by PID (drops 'n'/'N' as not used before much)
+ (thanks to Jake Mannens)
+
+What's new in version 3.0.4
+
+* Separate tree and list sort orders
+* Invert Process_compare so that superclass matches run first
+ (thanks to Hisham Muhammad)
+* Unhardcode Mac OS tick-to-milliseconds conversion
+ (thanks to Alexander Momchilov)
+* Check if clock_gettime needs linking of librt
+* Define O_PATH if not already defined
+ (thanks to Chris Burr)
+* Add column on Mac for processes running under translation
+ (thanks to Dániel Bakai)
+* Configure check for additional linker flags for keypad(3)
+* PSI Meter: constant width and only print ten-duration as bar
+* Sort in paused mode after inverting sort order
+* Handle absence of package CPU temperature
+* Meter: restore non-wide-character build
+* LibSensors: restore temperature for Raspberry Pi
+* MainPanel: do not reset hideProcessSelection on KEY_SHUFFLE
+* BarMeter: rework text padding
+* Panel: rework drawing of FunctionBar
+* Meter: fix artifacts with very tiny width
+* DragonFlyBSD updates
+* BUGFIX: Fix dlopen issue for libsensors5 for some platforms
+* BUGFIX: Fix broken tree display on inverted sort order
+* BUGFIX: Fix pause mode ("Z") in tree view
+* BUGFIX: Correct timebase for non-x86 CPUs on Darwin
+* BUGFIX: Avoid NULL dereference on zombie processes
+* Document dynamic bindings and assumed external configuration
+* Update key mapping documentation for sorting
+
What's new in version 3.0.3
* Process sorting in 'tree' mode
diff --git a/ClockMeter.c b/ClockMeter.c
index 023ca42..8e3b66e 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.
*/
@@ -10,21 +10,24 @@ in the source distribution for its full text.
#include "ClockMeter.h"
#include <time.h>
+#include <sys/time.h>
#include "CRT.h"
#include "Object.h"
+#include "ProcessList.h"
static const int ClockMeter_attributes[] = {
CLOCK
};
-static void ClockMeter_updateValues(Meter* this, char* buffer, size_t size) {
- time_t t = time(NULL);
+static void ClockMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+
struct tm result;
- struct tm* lt = localtime_r(&t, &result);
+ const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
this->values[0] = lt->tm_hour * 60 + lt->tm_min;
- strftime(buffer, size, "%H:%M:%S", lt);
+ strftime(this->txtBuffer, sizeof(this->txtBuffer), "%H:%M:%S", lt);
}
const MeterClass ClockMeter_class = {
diff --git a/ClockMeter.h b/ClockMeter.h
index ecd4b6a..def0460 100644
--- a/ClockMeter.h
+++ b/ClockMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass ClockMeter_class;
#endif
diff --git a/ColorsPanel.c b/ColorsPanel.c
index 2fb1c92..5900884 100644
--- a/ColorsPanel.c
+++ b/ColorsPanel.c
@@ -1,23 +1,22 @@
/*
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 "ColorsPanel.h"
+#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include "CRT.h"
#include "FunctionBar.h"
-#include "Header.h"
+#include "Macros.h"
#include "Object.h"
#include "OptionItem.h"
#include "ProvideCurses.h"
-#include "RichString.h"
-#include "Vector.h"
// TO ADD A NEW SCHEME:
@@ -51,35 +50,32 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) {
ColorsPanel* this = (ColorsPanel*) super;
HandlerResult result = IGNORED;
- int mark = Panel_getSelectedIndex(super);
+ int mark;
- switch(ch) {
+ 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;
- result = HANDLED;
- }
-
- if (result == HANDLED) {
this->settings->changed = true;
- const Header* header = this->scr->header;
+ this->settings->lastUpdate++;
+
CRT_setColors(mark);
clear();
- Panel* menu = (Panel*) Vector_get(this->scr->panels, 0);
- Header_draw(header);
- FunctionBar_draw(super->currentBar);
- RichString_setAttr(&(super->header), CRT_colors[PANEL_HEADER_FOCUS]);
- RichString_setAttr(&(menu->header), CRT_colors[PANEL_HEADER_UNFOCUS]);
- ScreenManager_resize(this->scr, this->scr->x1, header->height, this->scr->x2, this->scr->y2);
+
+ result = HANDLED | REDRAW;
}
+
return result;
}
@@ -91,14 +87,15 @@ const PanelClass ColorsPanel_class = {
.eventHandler = ColorsPanel_eventHandler
};
-ColorsPanel* ColorsPanel_new(Settings* settings, ScreenManager* scr) {
+ColorsPanel* ColorsPanel_new(Settings* settings) {
ColorsPanel* this = AllocThis(ColorsPanel);
Panel* super = (Panel*) this;
FunctionBar* fuBar = FunctionBar_new(ColorsFunctions, NULL, NULL);
Panel_init(super, 1, 1, 1, 1, Class(CheckItem), true, fuBar);
this->settings = settings;
- this->scr = scr;
+
+ assert(ARRAYSIZE(ColorSchemeNames) == LAST_COLORSCHEME + 1);
Panel_setHeader(super, "Colors");
for (int i = 0; ColorSchemeNames[i] != NULL; i++) {
diff --git a/ColorsPanel.h b/ColorsPanel.h
index f63ca35..373ec65 100644
--- a/ColorsPanel.h
+++ b/ColorsPanel.h
@@ -3,23 +3,22 @@
/*
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.
*/
#include "Panel.h"
-#include "ScreenManager.h"
#include "Settings.h"
+
typedef struct ColorsPanel_ {
Panel super;
Settings* settings;
- ScreenManager* scr;
} ColorsPanel;
extern const PanelClass ColorsPanel_class;
-ColorsPanel* ColorsPanel_new(Settings* settings, ScreenManager* scr);
+ColorsPanel* ColorsPanel_new(Settings* settings);
#endif
diff --git a/ColumnsPanel.c b/ColumnsPanel.c
index b2a8246..d53fff2 100644
--- a/ColumnsPanel.c
+++ b/ColumnsPanel.c
@@ -1,17 +1,20 @@
/*
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 "ColumnsPanel.h"
+#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include "CRT.h"
+#include "DynamicColumn.h"
#include "FunctionBar.h"
+#include "Hashtable.h"
#include "ListItem.h"
#include "Object.h"
#include "Process.h"
@@ -35,7 +38,7 @@ 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:
@@ -44,7 +47,7 @@ static HandlerResult ColumnsPanel_eventHandler(Panel* super, int ch) {
{
if (selected < size - 1) {
this->moving = !(this->moving);
- Panel_setSelectionColor(super, this->moving ? CRT_colors[PANEL_SELECTION_FOLLOW] : CRT_colors[PANEL_SELECTION_FOCUS]);
+ Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS);
ListItem* selectedItem = (ListItem*) Panel_getSelected(super);
if (selectedItem)
selectedItem->moving = this->moving;
@@ -115,35 +118,61 @@ const PanelClass ColumnsPanel_class = {
.eventHandler = ColumnsPanel_eventHandler
};
-ColumnsPanel* ColumnsPanel_new(Settings* settings) {
+static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) {
+ const char* name;
+ if (key < LAST_PROCESSFIELD) {
+ name = Process_fields[key].name;
+ } else {
+ const DynamicColumn* column = Hashtable_get(columns, key);
+ assert(column);
+ if (!column) {
+ name = NULL;
+ } else {
+ name = column->caption ? column->caption : column->heading;
+ if (!name)
+ name = column->name; /* name is a mandatory field */
+ }
+ }
+ if (name == NULL)
+ name = "- ";
+ Panel_add(super, (Object*) ListItem_new(name, key));
+}
+
+void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns) {
+ Panel* super = (Panel*) this;
+ Panel_prune(super);
+ for (const ProcessField* fields = ss->fields; *fields; fields++)
+ ColumnsPanel_add(super, *fields, columns);
+ this->ss = ss;
+}
+
+ColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed) {
ColumnsPanel* this = AllocThis(ColumnsPanel);
Panel* super = (Panel*) this;
FunctionBar* fuBar = FunctionBar_new(ColumnsFunctions, NULL, NULL);
Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
- this->settings = settings;
+ this->ss = ss;
+ this->changed = changed;
this->moving = false;
Panel_setHeader(super, "Active Columns");
- ProcessField* fields = this->settings->fields;
- for (; *fields; fields++) {
- if (Process_fields[*fields].name) {
- Panel_add(super, (Object*) ListItem_new(Process_fields[*fields].name, *fields));
- }
- }
+ ColumnsPanel_fill(this, ss, columns);
+
return this;
}
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->settings->flags |= Process_fields[key].flags;
+ this->ss->fields[i] = key;
+ if (key < LAST_PROCESSFIELD)
+ 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 173e039..63f6f92 100644
--- a/ColumnsPanel.h
+++ b/ColumnsPanel.h
@@ -3,7 +3,7 @@
/*
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.
*/
@@ -12,16 +12,20 @@ in the source distribution for its full text.
#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
new file mode 100644
index 0000000..682e054
--- /dev/null
+++ b/CommandLine.c
@@ -0,0 +1,425 @@
+/*
+htop - CommandLine.c
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "CommandLine.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "Action.h"
+#include "CRT.h"
+#include "DynamicColumn.h"
+#include "DynamicMeter.h"
+#include "Hashtable.h"
+#include "Header.h"
+#include "IncSet.h"
+#include "MainPanel.h"
+#include "MetersPanel.h"
+#include "Panel.h"
+#include "Platform.h"
+#include "Process.h"
+#include "ProcessList.h"
+#include "ProvideCurses.h"
+#include "ScreenManager.h"
+#include "Settings.h"
+#include "UsersTable.h"
+#include "XUtils.h"
+
+
+static void printVersionFlag(const char* name) {
+ printf("%s " VERSION "\n", name);
+}
+
+static void printHelpFlag(const char* name) {
+ printf("%s " VERSION "\n"
+ COPYRIGHT "\n"
+ "Released under the GNU GPLv2+.\n\n"
+ "-C --no-color Use a monochrome color scheme\n"
+ "-d --delay=DELAY Set the delay between updates, in tenths of seconds\n"
+ "-F --filter=FILTER Show only the commands matching the given filter\n"
+ "-h --help Print this help screen\n"
+ "-H --highlight-changes[=DELAY] Highlight new and old processes\n", name);
+#ifdef HAVE_GETMOUSE
+ printf("-M --no-mouse Disable the mouse\n");
+#endif
+ printf("-p --pid=PID[,PID,PID...] Show only the given PIDs\n"
+ " --readonly Disable all system and process changing features\n"
+ "-s --sort-key=COLUMN Sort by COLUMN in list view (try --sort-key=help for a list)\n"
+ "-t --tree Show the tree view (can be combined with -s)\n"
+ "-u --user[=USERNAME] Show only processes for a given user (or $USER)\n"
+ "-U --no-unicode Do not use unicode but plain ASCII\n"
+ "-V --version Print version info\n");
+ Platform_longOptionsUsage(name);
+ printf("\n"
+ "Press F1 inside %s for online help.\n"
+ "See 'man %s' for more information.\n", name, name);
+}
+
+// ----------------------------------------
+
+typedef struct CommandLineSettings_ {
+ Hashtable* pidMatchList;
+ char* commFilter;
+ uid_t userId;
+ int sortKey;
+ int delay;
+ bool useColors;
+#ifdef HAVE_GETMOUSE
+ bool enableMouse;
+#endif
+ bool treeView;
+ bool allowUnicode;
+ bool highlightChanges;
+ int highlightDelaySecs;
+ bool readonly;
+} CommandLineSettings;
+
+static CommandLineStatus parseArguments(const char* program, int argc, char** argv, 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,
+ .useColors = true,
+#ifdef HAVE_GETMOUSE
+ .enableMouse = true,
+#endif
+ .treeView = false,
+ .allowUnicode = true,
+ .highlightChanges = false,
+ .highlightDelaySecs = -1,
+ .readonly = false,
+ };
+
+ const struct option long_opts[] =
+ {
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {"delay", required_argument, 0, 'd'},
+ {"sort-key", required_argument, 0, 's'},
+ {"user", optional_argument, 0, 'u'},
+ {"no-color", no_argument, 0, 'C'},
+ {"no-colour", no_argument, 0, 'C'},
+ {"no-mouse", no_argument, 0, 'M'},
+ {"no-unicode", no_argument, 0, 'U'},
+ {"tree", no_argument, 0, 't'},
+ {"pid", required_argument, 0, 'p'},
+ {"filter", required_argument, 0, 'F'},
+ {"highlight-changes", optional_argument, 0, 'H'},
+ {"readonly", no_argument, 0, 128},
+ PLATFORM_LONG_OPTIONS
+ {0, 0, 0, 0}
+ };
+
+ int opt, opti = 0;
+ /* Parse arguments */
+ while ((opt = getopt_long(argc, argv, "hVMCs:td:u::Up:F:H::", long_opts, &opti))) {
+ if (opt == EOF)
+ break;
+ switch (opt) {
+ case 'h':
+ printHelpFlag(program);
+ return STATUS_OK_EXIT;
+ case 'V':
+ printVersionFlag(program);
+ 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);
+ }
+ return STATUS_OK_EXIT;
+ }
+ flags->sortKey = 0;
+ for (int j = 1; j < LAST_PROCESSFIELD; j++) {
+ if (Process_fields[j].name == NULL)
+ continue;
+ if (String_eq(optarg, Process_fields[j].name)) {
+ flags->sortKey = j;
+ break;
+ }
+ }
+ if (flags->sortKey == 0) {
+ fprintf(stderr, "Error: invalid column \"%s\".\n", optarg);
+ 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;
+ } else {
+ fprintf(stderr, "Error: invalid delay value \"%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))) {
+ 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;
+ break;
+ case 'M':
+#ifdef HAVE_GETMOUSE
+ flags->enableMouse = false;
+#endif
+ break;
+ case 'U':
+ flags->allowUnicode = false;
+ break;
+ case 't':
+ flags->treeView = true;
+ break;
+ case 'p': {
+ assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
+ char* argCopy = xStrdup(optarg);
+ char* saveptr;
+ const char* pid = strtok_r(argCopy, ",", &saveptr);
+
+ if (!flags->pidMatchList) {
+ flags->pidMatchList = Hashtable_new(8, false);
+ }
+
+ while (pid) {
+ unsigned int num_pid = atoi(pid);
+ // deepcode ignore CastIntegerToAddress: we just want a non-NULL pointer here
+ Hashtable_put(flags->pidMatchList, num_pid, (void*) 1);
+ pid = strtok_r(NULL, ",", &saveptr);
+ }
+ free(argCopy);
+
+ break;
+ }
+ case 'F': {
+ assert(optarg);
+ free_and_xStrdup(&flags->commFilter, optarg);
+ break;
+ }
+ case 'H': {
+ const char* delay = optarg;
+ if (!delay && optind < argc && argv[optind] != NULL &&
+ (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
+ delay = argv[optind++];
+ }
+ if (delay) {
+ if (sscanf(delay, "%16d", &(flags->highlightDelaySecs)) == 1) {
+ if (flags->highlightDelaySecs < 1)
+ flags->highlightDelaySecs = 1;
+ } else {
+ fprintf(stderr, "Error: invalid highlight delay value \"%s\".\n", delay);
+ return STATUS_ERROR_EXIT;
+ }
+ }
+ flags->highlightChanges = true;
+ break;
+ }
+ case 128:
+ flags->readonly = true;
+ break;
+
+ default: {
+ CommandLineStatus status;
+ if ((status = Platform_getLongOption(opt, argc, argv)) != STATUS_OK)
+ return status;
+ break;
+ }
+ }
+ }
+
+ 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) {
+ struct timespec req = {
+ .tv_sec = 0,
+ .tv_nsec = millisec * 1000000L
+ };
+ while (nanosleep(&req, &req) == -1)
+ continue;
+ Platform_gettime_realtime(&pl->realtime, &pl->realtimeMs);
+}
+
+static void setCommFilter(State* state, char** commFilter) {
+ ProcessList* pl = state->pl;
+ IncSet* inc = state->mainPanel->inc;
+
+ IncSet_setFilter(inc, *commFilter);
+ pl->incFilter = IncSet_filter(inc);
+
+ free(*commFilter);
+ *commFilter = NULL;
+}
+
+int CommandLine_run(const char* name, int argc, char** argv) {
+
+ /* initialize locale */
+ const char* lc_ctype;
+ if ((lc_ctype = getenv("LC_CTYPE")) || (lc_ctype = getenv("LC_ALL")))
+ setlocale(LC_CTYPE, lc_ctype);
+ else
+ setlocale(LC_CTYPE, "");
+
+ CommandLineStatus status = STATUS_OK;
+ CommandLineSettings flags = { 0 };
+
+ if ((status = parseArguments(name, argc, argv, &flags)) != STATUS_OK)
+ return status != STATUS_OK_EXIT ? 1 : 0;
+
+ if (flags.readonly)
+ Settings_enableReadonly();
+
+ if (!Platform_init())
+ return 1;
+
+ Process_setupColumnWidths();
+
+ UsersTable* ut = UsersTable_new();
+ Hashtable* dc = DynamicColumns_new();
+ Hashtable* dm = DynamicMeters_new();
+ if (!dc)
+ dc = Hashtable_new(0, true);
+
+ ProcessList* pl = ProcessList_new(ut, dm, dc, flags.pidMatchList, flags.userId);
+
+ Settings* settings = Settings_new(pl->activeCPUs, dc);
+ pl->settings = settings;
+
+ Header* header = Header_new(pl, settings, 2);
+
+ Header_populateFromSettings(header);
+
+ if (flags.delay != -1)
+ settings->delay = flags.delay;
+ if (!flags.useColors)
+ settings->colorScheme = COLORSCHEME_MONOCHROME;
+#ifdef HAVE_GETMOUSE
+ if (!flags.enableMouse)
+ settings->enableMouse = false;
+#endif
+ if (flags.treeView)
+ settings->ss->treeView = true;
+ if (flags.highlightChanges)
+ settings->highlightChanges = true;
+ if (flags.highlightDelaySecs != -1)
+ settings->highlightDelaySecs = flags.highlightDelaySecs;
+ if (flags.sortKey > 0) {
+ // -t -s <key> means "tree sorted by key"
+ // -s <key> means "list sorted by key" (previous existing behavior)
+ if (!flags.treeView) {
+ settings->ss->treeView = false;
+ }
+ ScreenSettings_setSortKey(settings->ss, flags.sortKey);
+ }
+
+ CRT_init(settings, flags.allowUnicode);
+
+ MainPanel* panel = MainPanel_new();
+ ProcessList_setPanel(pl, (Panel*) panel);
+
+ MainPanel_updateLabels(panel, settings->ss->treeView, flags.commFilter);
+
+ State state = {
+ .settings = settings,
+ .ut = ut,
+ .pl = pl,
+ .mainPanel = panel,
+ .header = header,
+ .pauseProcessUpdate = false,
+ .hideProcessSelection = false,
+ .hideMeters = false,
+ };
+
+ MainPanel_setState(panel, &state);
+ if (flags.commFilter)
+ setCommFilter(&state, &(flags.commFilter));
+
+ ScreenManager* scr = ScreenManager_new(header, settings, &state, true);
+ ScreenManager_add(scr, (Panel*) panel, -1);
+
+ ProcessList_scan(pl, false);
+ CommandLine_delay(pl, 75);
+ ProcessList_scan(pl, false);
+
+ if (settings->ss->allBranchesCollapsed)
+ ProcessList_collapseAllBranches(pl);
+
+ ScreenManager_run(scr, NULL, NULL, NULL);
+
+ Platform_done();
+
+ CRT_done();
+
+ if (settings->changed) {
+ int r = Settings_write(settings, false);
+ if (r < 0)
+ fprintf(stderr, "Can not save configuration to %s: %s\n", settings->filename, strerror(-r));
+ }
+
+ Header_delete(header);
+ ProcessList_delete(pl);
+
+ ScreenManager_delete(scr);
+ MetersPanel_cleanup();
+
+ UsersTable_delete(ut);
+
+ if (flags.pidMatchList)
+ Hashtable_delete(flags.pidMatchList);
+
+ CRT_resetSignalHandlers();
+
+ /* Delete these last, since they can get accessed in the crash handler */
+ Settings_delete(settings);
+ DynamicColumns_delete(dc);
+ DynamicMeters_delete(dm);
+
+ return 0;
+}
diff --git a/CommandLine.h b/CommandLine.h
new file mode 100644
index 0000000..fbdede8
--- /dev/null
+++ b/CommandLine.h
@@ -0,0 +1,19 @@
+#ifndef HEADER_CommandLine
+#define HEADER_CommandLine
+/*
+htop - CommandLine.h
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+typedef enum {
+ STATUS_OK,
+ STATUS_ERROR_EXIT,
+ STATUS_OK_EXIT
+} CommandLineStatus;
+
+int CommandLine_run(const char* name, int argc, char** argv);
+
+#endif
diff --git a/CommandScreen.c b/CommandScreen.c
index d342829..6a87d13 100644
--- a/CommandScreen.c
+++ b/CommandScreen.c
@@ -2,13 +2,13 @@
#include "CommandScreen.h"
+#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "Macros.h"
#include "Panel.h"
#include "ProvideCurses.h"
-#include "XUtils.h"
static void CommandScreen_scan(InfoScreen* this) {
@@ -17,9 +17,10 @@ static void CommandScreen_scan(InfoScreen* this) {
Panel_prune(panel);
const char* p = Process_getCommand(this->process);
- char* line = xMalloc(COLS + 1);
+ char line[COLS + 1];
int line_offset = 0, last_spc = -1, len;
for (; *p != '\0'; p++, line_offset++) {
+ assert(line_offset >= 0 && (size_t)line_offset < sizeof(line));
line[line_offset] = *p;
if (*p == ' ') {
last_spc = line_offset;
@@ -41,7 +42,6 @@ static void CommandScreen_scan(InfoScreen* this) {
InfoScreen_addLine(this, line);
}
- free(line);
Panel_setSelected(panel, idx);
}
@@ -60,7 +60,7 @@ const InfoScreenClass CommandScreen_class = {
CommandScreen* CommandScreen_new(Process* process) {
CommandScreen* this = AllocThis(CommandScreen);
- return (CommandScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 3, " ");
+ return (CommandScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " ");
}
void CommandScreen_delete(Object* this) {
diff --git a/Compat.c b/Compat.c
index 43d02ec..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,
@@ -37,14 +44,14 @@ int Compat_faccessat(int dirfd,
#endif
// Error out on unsupported configurations
- if (dirfd != AT_FDCWD || mode != F_OK) {
+ if (dirfd != (int)AT_FDCWD || mode != F_OK) {
errno = EINVAL;
return -1;
}
// 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..1c4794e 100644
--- a/Compat.h
+++ b/Compat.h
@@ -3,7 +3,7 @@
/*
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.
*/
@@ -56,4 +56,9 @@ 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);
+
#endif /* HEADER_Compat */
diff --git a/DateMeter.c b/DateMeter.c
index bd6a306..9628596 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.
*/
@@ -10,28 +10,30 @@ in the source distribution for its full text.
#include "DateMeter.h"
#include <time.h>
+#include <sys/time.h>
#include "CRT.h"
#include "Object.h"
+#include "ProcessList.h"
static const int DateMeter_attributes[] = {
DATE
};
-static void DateMeter_updateValues(Meter* this, char* buffer, size_t size) {
- time_t t = time(NULL);
+static void DateMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+
struct tm result;
- struct tm* lt = localtime_r(&t, &result);
+ const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
this->values[0] = lt->tm_yday;
int year = lt->tm_year + 1900;
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
this->total = 366;
- }
- else {
+ } else {
this->total = 365;
}
- strftime(buffer, size, "%F", lt);
+ strftime(this->txtBuffer, sizeof(this->txtBuffer), "%F", lt);
}
const MeterClass DateMeter_class = {
diff --git a/DateMeter.h b/DateMeter.h
index 6345576..ecbfa99 100644
--- a/DateMeter.h
+++ b/DateMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass DateMeter_class;
#endif
diff --git a/DateTimeMeter.c b/DateTimeMeter.c
index 0d231cd..1044ff3 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.
*/
@@ -10,28 +10,30 @@ in the source distribution for its full text.
#include "DateTimeMeter.h"
#include <time.h>
+#include <sys/time.h>
#include "CRT.h"
#include "Object.h"
+#include "ProcessList.h"
static const int DateTimeMeter_attributes[] = {
DATETIME
};
-static void DateTimeMeter_updateValues(Meter* this, char* buffer, size_t size) {
- time_t t = time(NULL);
+static void DateTimeMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+
struct tm result;
- struct tm* lt = localtime_r(&t, &result);
+ const struct tm* lt = localtime_r(&pl->realtime.tv_sec, &result);
int year = lt->tm_year + 1900;
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
this->total = 366;
- }
- else {
+ } else {
this->total = 365;
}
this->values[0] = lt->tm_yday;
- strftime(buffer, size, "%F %H:%M:%S", lt);
+ strftime(this->txtBuffer, sizeof(this->txtBuffer), "%F %H:%M:%S", lt);
}
const MeterClass DateTimeMeter_class = {
diff --git a/DateTimeMeter.h b/DateTimeMeter.h
index 6cb73c2..5196dd3 100644
--- a/DateTimeMeter.h
+++ b/DateTimeMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass DateTimeMeter_class;
#endif
diff --git a/DiskIOMeter.c b/DiskIOMeter.c
index 0105ce3..adab8f7 100644
--- a/DiskIOMeter.c
+++ b/DiskIOMeter.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -9,12 +9,13 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdio.h>
-#include <sys/time.h>
#include "CRT.h"
#include "Macros.h"
+#include "Meter.h"
#include "Object.h"
#include "Platform.h"
+#include "ProcessList.h"
#include "RichString.h"
#include "XUtils.h"
@@ -25,93 +26,127 @@ static const int DiskIOMeter_attributes[] = {
METER_VALUE_IOWRITE,
};
-static bool hasData = false;
-static unsigned long int cached_read_diff = 0;
-static unsigned long int cached_write_diff = 0;
-static double cached_utilisation_diff = 0.0;
+static MeterRateStatus status = RATESTATUS_INIT;
+static uint32_t cached_read_diff;
+static uint32_t cached_write_diff;
+static double cached_utilisation_diff;
-static void DiskIOMeter_updateValues(Meter* this, char* buffer, size_t len) {
- static unsigned long long int cached_last_update = 0;
+static void DiskIOMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
- struct timeval tv;
- gettimeofday(&tv, NULL);
- unsigned long long int timeInMilliSeconds = (unsigned long long int)tv.tv_sec * 1000 + (unsigned long long int)tv.tv_usec / 1000;
- unsigned long long int passedTimeInMs = timeInMilliSeconds - cached_last_update;
+ static uint64_t cached_last_update;
+ uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update;
- /* update only every 500ms */
+ /* update only every 500ms to have a sane span for rate calculation */
if (passedTimeInMs > 500) {
- static unsigned long int cached_read_total = 0;
- static unsigned long int cached_write_total = 0;
- static unsigned long int cached_msTimeSpend_total = 0;
-
- cached_last_update = timeInMilliSeconds;
+ static uint64_t cached_read_total;
+ static uint64_t cached_write_total;
+ static uint64_t cached_msTimeSpend_total;
+ uint64_t diff;
DiskIOData data;
+ if (!Platform_getDiskIO(&data)) {
+ status = RATESTATUS_NODATA;
+ } else if (cached_last_update == 0) {
+ status = RATESTATUS_INIT;
+ } else if (passedTimeInMs > 30000) {
+ status = RATESTATUS_STALE;
+ } else {
+ status = RATESTATUS_DATA;
+ }
- hasData = Platform_getDiskIO(&data);
- if (!hasData) {
- this->values[0] = 0;
- xSnprintf(buffer, len, "no data");
+ cached_last_update = pl->realtimeMs;
+
+ if (status == RATESTATUS_NODATA) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
return;
}
if (data.totalBytesRead > cached_read_total) {
- cached_read_diff = (data.totalBytesRead - cached_read_total) / 1024; /* Meter_humanUnit() expects unit in kilo */
+ diff = data.totalBytesRead - cached_read_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ diff /= ONE_K; /* convert to KiB/s */
+ cached_read_diff = (uint32_t)diff;
} else {
cached_read_diff = 0;
}
cached_read_total = data.totalBytesRead;
if (data.totalBytesWritten > cached_write_total) {
- cached_write_diff = (data.totalBytesWritten - cached_write_total) / 1024; /* Meter_humanUnit() expects unit in kilo */
+ diff = data.totalBytesWritten - cached_write_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ diff /= ONE_K; /* convert to KiB/s */
+ cached_write_diff = (uint32_t)diff;
} else {
cached_write_diff = 0;
}
cached_write_total = data.totalBytesWritten;
if (data.totalMsTimeSpend > cached_msTimeSpend_total) {
- cached_utilisation_diff = 100 * (double)(data.totalMsTimeSpend - cached_msTimeSpend_total) / passedTimeInMs;
+ diff = data.totalMsTimeSpend - cached_msTimeSpend_total;
+ cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs;
} else {
cached_utilisation_diff = 0.0;
}
cached_msTimeSpend_total = data.totalMsTimeSpend;
}
+ if (status == RATESTATUS_INIT) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init");
+ return;
+ }
+ if (status == RATESTATUS_STALE) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale");
+ return;
+ }
+
this->values[0] = cached_utilisation_diff;
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(buffer, len, "%sB %sB %.1f%%", bufferRead, bufferWrite, cached_utilisation_diff);
+ snprintf(this->txtBuffer, sizeof(this->txtBuffer), "r:%siB/s w:%siB/s %.1f%%", bufferRead, bufferWrite, cached_utilisation_diff);
}
-static void DIskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
- if (!hasData) {
- RichString_write(out, CRT_colors[METER_VALUE_ERROR], "no data");
+static void DiskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
+ switch (status) {
+ case RATESTATUS_NODATA:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
+ return;
+ case RATESTATUS_INIT:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
+ return;
+ case RATESTATUS_STALE:
+ RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
return;
+ case RATESTATUS_DATA:
+ break;
}
char buffer[16];
+ int len;
int color = cached_utilisation_diff > 40.0 ? METER_VALUE_NOTICE : METER_VALUE;
- xSnprintf(buffer, sizeof(buffer), "%.1f%%", cached_utilisation_diff);
- RichString_write(out, CRT_colors[color], buffer);
+ len = xSnprintf(buffer, sizeof(buffer), "%.1f%%", cached_utilisation_diff);
+ RichString_appendnAscii(out, CRT_colors[color], buffer, len);
- RichString_append(out, CRT_colors[METER_TEXT], " read: ");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " read: ");
Meter_humanUnit(buffer, cached_read_diff, sizeof(buffer));
- RichString_append(out, CRT_colors[METER_VALUE_IOREAD], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], "iB/s");
- RichString_append(out, CRT_colors[METER_TEXT], " write: ");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " write: ");
Meter_humanUnit(buffer, cached_write_diff, sizeof(buffer));
- RichString_append(out, CRT_colors[METER_VALUE_IOWRITE], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s");
}
const MeterClass DiskIOMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
- .display = DIskIOMeter_display
+ .display = DiskIOMeter_display
},
.updateValues = DiskIOMeter_updateValues,
.defaultMode = TEXT_METERMODE,
diff --git a/DiskIOMeter.h b/DiskIOMeter.h
index b2b3e8d..3b03e32 100644
--- a/DiskIOMeter.h
+++ b/DiskIOMeter.h
@@ -3,16 +3,17 @@
/*
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 "Meter.h"
+
typedef struct DiskIOData_ {
- unsigned long int totalBytesRead;
- unsigned long int totalBytesWritten;
- unsigned long int totalMsTimeSpend;
+ uint64_t totalBytesRead;
+ uint64_t totalBytesWritten;
+ uint64_t totalMsTimeSpend;
} DiskIOData;
extern const MeterClass DiskIOMeter_class;
diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c
index ed37319..f9fa9b1 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.
*/
@@ -18,6 +18,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "OptionItem.h"
#include "ProvideCurses.h"
+#include "ScreensPanel.h"
static const char* const DisplayOptionsFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL};
@@ -43,6 +44,8 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {
case KEY_RECLICK:
case ' ':
switch (OptionItem_kind(selected)) {
+ case OPTION_ITEM_TEXT:
+ break;
case OPTION_ITEM_CHECK:
CheckItem_toggle((CheckItem*)selected);
result = HANDLED;
@@ -69,11 +72,13 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {
if (result == HANDLED) {
this->settings->changed = true;
+ this->settings->lastUpdate++;
Header* header = this->scr->header;
Header_calculateHeight(header);
Header_reinit(header);
+ Header_updateData(header);
Header_draw(header);
- ScreenManager_resize(this->scr, this->scr->x1, header->height, this->scr->x2, this->scr->y2);
+ ScreenManager_resize(this->scr);
}
return result;
}
@@ -96,14 +101,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)));
+
+ #define TABMSG "For current screen tab: \0"
+ char tabheader[sizeof(TABMSG) + SCREEN_NAME_LEN + 1] = TABMSG;
+ strncat(tabheader, settings->ss->name, SCREEN_NAME_LEN);
+ Panel_add(super, (Object*) TextItem_new(tabheader));
+ #undef TABMSG
+
+ Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->ss->treeView)));
+ Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->ss->treeViewAlwaysByPID)));
+ Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->ss->allBranchesCollapsed)));
+ Panel_add(super, (Object*) TextItem_new("Global options:"));
+ Panel_add(super, (Object*) CheckItem_newByRef("Show tabs for screens", &(settings->screenTabs)));
Panel_add(super, (Object*) CheckItem_newByRef("Shadow other users' processes", &(settings->shadowOtherUsers)));
Panel_add(super, (Object*) CheckItem_newByRef("Hide kernel threads", &(settings->hideKernelThreads)));
Panel_add(super, (Object*) CheckItem_newByRef("Hide userland process threads", &(settings->hideUserlandThreads)));
+ Panel_add(super, (Object*) CheckItem_newByRef("Hide processes running in containers", &(settings->hideRunningInContainer)));
Panel_add(super, (Object*) CheckItem_newByRef("Display threads in a different color", &(settings->highlightThreads)));
Panel_add(super, (Object*) CheckItem_newByRef("Show custom thread names", &(settings->showThreadNames)));
Panel_add(super, (Object*) CheckItem_newByRef("Show program path", &(settings->showProgramPath)));
Panel_add(super, (Object*) CheckItem_newByRef("Highlight program \"basename\"", &(settings->highlightBaseName)));
+ Panel_add(super, (Object*) CheckItem_newByRef("Highlight out-dated/removed programs (red) / libraries (yellow)", &(settings->highlightDeletedExe)));
+ Panel_add(super, (Object*) CheckItem_newByRef("Shadow distribution path prefixes", &(settings->shadowDistPathPrefix)));
Panel_add(super, (Object*) CheckItem_newByRef("Merge exe, comm and cmdline in Command", &(settings->showMergedCommand)));
Panel_add(super, (Object*) CheckItem_newByRef("- Try to find comm in cmdline (when Command is merged)", &(settings->findCommInCmdline)));
Panel_add(super, (Object*) CheckItem_newByRef("- Try to strip exe from cmdline (when Command is merged)", &(settings->stripExeFromCmdline)));
@@ -115,14 +134,25 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
Panel_add(super, (Object*) CheckItem_newByRef("Add guest time in CPU meter percentage", &(settings->accountGuestInCPUMeter)));
Panel_add(super, (Object*) CheckItem_newByRef("Also show CPU percentage numerically", &(settings->showCPUUsage)));
Panel_add(super, (Object*) CheckItem_newByRef("Also show CPU frequency", &(settings->showCPUFrequency)));
- #ifdef HAVE_SENSORS_SENSORS_H
- Panel_add(super, (Object*) CheckItem_newByRef("Also show CPU temperature (requires libsensors)", &(settings->showCPUTemperature)));
+ #ifdef BUILD_WITH_CPU_TEMP
+ Panel_add(super, (Object*) CheckItem_newByRef(
+ #if defined(HTOP_LINUX)
+ "Also show CPU temperature (requires libsensors)",
+ #elif defined(HTOP_FREEBSD)
+ "Also show CPU temperature",
+ #else
+ #error Unknown temperature implementation!
+ #endif
+ &(settings->showCPUTemperature)));
Panel_add(super, (Object*) CheckItem_newByRef("- Show temperature in degree Fahrenheit instead of Celsius", &(settings->degreeFahrenheit)));
#endif
+ #ifdef HAVE_GETMOUSE
Panel_add(super, (Object*) CheckItem_newByRef("Enable the mouse", &(settings->enableMouse)));
+ #endif
Panel_add(super, (Object*) NumberItem_newByRef("Update interval (in seconds)", &(settings->delay), -1, 1, 255));
Panel_add(super, (Object*) CheckItem_newByRef("Highlight new and old processes", &(settings->highlightChanges)));
- Panel_add(super, (Object*) NumberItem_newByRef("- Highlight time (in seconds)", &(settings->highlightDelaySecs), 0, 1, 24*60*60));
+ Panel_add(super, (Object*) NumberItem_newByRef("- Highlight time (in seconds)", &(settings->highlightDelaySecs), 0, 1, 24 * 60 * 60));
+ Panel_add(super, (Object*) NumberItem_newByRef("Hide main function bar (0 - off, 1 - on ESC until next input, 2 - permanently)", &(settings->hideFunctionBar), 0, 0, 2));
#ifdef HAVE_LIBHWLOC
Panel_add(super, (Object*) CheckItem_newByRef("Show topology when selecting affinity by default", &(settings->topologyAffinity)));
#endif
diff --git a/DisplayOptionsPanel.h b/DisplayOptionsPanel.h
index 02b67a0..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.
*/
@@ -11,6 +11,7 @@ in the source distribution for its full text.
#include "ScreenManager.h"
#include "Settings.h"
+
typedef struct DisplayOptionsPanel_ {
Panel super;
diff --git a/DynamicColumn.c b/DynamicColumn.c
new file mode 100644
index 0000000..bd038df
--- /dev/null
+++ b/DynamicColumn.c
@@ -0,0 +1,66 @@
+/*
+htop - DynamicColumn.c
+(C) 2021 Sohaib Mohammed
+(C) 2021 htop dev team
+(C) 2021 Red Hat, Inc. All Rights Reserved.
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "DynamicColumn.h"
+
+#include <stddef.h>
+
+#include "Platform.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+
+Hashtable* DynamicColumns_new(void) {
+ return Platform_dynamicColumns();
+}
+
+void DynamicColumns_delete(Hashtable* dynamics) {
+ if (dynamics) {
+ Platform_dynamicColumnsDone(dynamics);
+ Hashtable_delete(dynamics);
+ }
+}
+
+const char* DynamicColumn_init(unsigned int key) {
+ return Platform_dynamicColumnInit(key);
+}
+
+typedef struct {
+ const char* name;
+ const DynamicColumn* data;
+ unsigned int key;
+} DynamicIterator;
+
+static void DynamicColumn_compare(ht_key_t key, void* value, void* data) {
+ const DynamicColumn* column = (const DynamicColumn*)value;
+ DynamicIterator* iter = (DynamicIterator*)data;
+ if (String_eq(iter->name, column->name)) {
+ iter->data = column;
+ iter->key = key;
+ }
+}
+
+const DynamicColumn* DynamicColumn_search(Hashtable* dynamics, const char* name, unsigned int* key) {
+ DynamicIterator iter = { .key = 0, .data = NULL, .name = name };
+ if (dynamics)
+ Hashtable_foreach(dynamics, DynamicColumn_compare, &iter);
+ if (key)
+ *key = iter.key;
+ return iter.data;
+}
+
+const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key) {
+ return (const DynamicColumn*) Hashtable_get(dynamics, key);
+}
+
+bool DynamicColumn_writeField(const Process* proc, RichString* str, unsigned int key) {
+ return Platform_dynamicColumnWriteField(proc, str, key);
+}
diff --git a/DynamicColumn.h b/DynamicColumn.h
new file mode 100644
index 0000000..4760e6e
--- /dev/null
+++ b/DynamicColumn.h
@@ -0,0 +1,34 @@
+#ifndef HEADER_DynamicColumn
+#define HEADER_DynamicColumn
+
+#include <stdbool.h>
+
+#include "Hashtable.h"
+#include "Process.h"
+#include "RichString.h"
+
+
+#define DYNAMIC_MAX_COLUMN_WIDTH 28
+#define DYNAMIC_DEFAULT_COLUMN_WIDTH -5
+
+typedef struct DynamicColumn_ {
+ char name[32]; /* unique, internal-only name */
+ char* heading; /* displayed in main screen */
+ char* caption; /* displayed in setup menu (short name) */
+ char* description; /* displayed in setup menu (detail) */
+ int width; /* display width +/- for value alignment */
+} DynamicColumn;
+
+Hashtable* DynamicColumns_new(void);
+
+void DynamicColumns_delete(Hashtable* dynamics);
+
+const char* DynamicColumn_init(unsigned int key);
+
+const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key);
+
+const DynamicColumn* DynamicColumn_search(Hashtable* dynamics, const char* name, unsigned int* key);
+
+bool DynamicColumn_writeField(const Process* proc, RichString* str, unsigned int key);
+
+#endif
diff --git a/DynamicMeter.c b/DynamicMeter.c
new file mode 100644
index 0000000..a8cd76c
--- /dev/null
+++ b/DynamicMeter.c
@@ -0,0 +1,131 @@
+/*
+htop - DynamicMeter.c
+(C) 2021 htop dev team
+(C) 2021 Red Hat, Inc. All Rights Reserved.
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "DynamicMeter.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "CRT.h"
+#include "Object.h"
+#include "Platform.h"
+#include "ProcessList.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+
+static const int DynamicMeter_attributes[] = {
+ DYNAMIC_GRAY,
+ DYNAMIC_DARKGRAY,
+ DYNAMIC_RED,
+ DYNAMIC_GREEN,
+ DYNAMIC_BLUE,
+ DYNAMIC_CYAN,
+ DYNAMIC_MAGENTA,
+ DYNAMIC_YELLOW,
+ DYNAMIC_WHITE
+};
+
+Hashtable* DynamicMeters_new(void) {
+ return Platform_dynamicMeters();
+}
+
+void DynamicMeters_delete(Hashtable* dynamics) {
+ if (dynamics) {
+ Platform_dynamicMetersDone(dynamics);
+ Hashtable_delete(dynamics);
+ }
+}
+
+typedef struct {
+ unsigned int key;
+ const char* name;
+ bool found;
+} DynamicIterator;
+
+static void DynamicMeter_compare(ht_key_t key, void* value, void* data) {
+ const DynamicMeter* meter = (const DynamicMeter*)value;
+ DynamicIterator* iter = (DynamicIterator*)data;
+ if (String_eq(iter->name, meter->name)) {
+ iter->found = true;
+ iter->key = key;
+ }
+}
+
+bool DynamicMeter_search(Hashtable* dynamics, const char* name, unsigned int* key) {
+ DynamicIterator iter = { .key = 0, .name = name, .found = false };
+ if (dynamics)
+ Hashtable_foreach(dynamics, DynamicMeter_compare, &iter);
+ if (key)
+ *key = iter.key;
+ return iter.found;
+}
+
+const char* DynamicMeter_lookup(Hashtable* dynamics, unsigned int key) {
+ const DynamicMeter* meter = Hashtable_get(dynamics, key);
+ return meter ? meter->name : NULL;
+}
+
+static void DynamicMeter_init(Meter* meter) {
+ Platform_dynamicMeterInit(meter);
+}
+
+static void DynamicMeter_updateValues(Meter* meter) {
+ Platform_dynamicMeterUpdateValues(meter);
+}
+
+static void DynamicMeter_display(const Object* cast, RichString* out) {
+ const Meter* meter = (const Meter*)cast;
+ Platform_dynamicMeterDisplay(meter, out);
+}
+
+static const char* DynamicMeter_getCaption(const Meter* this) {
+ const ProcessList* pl = this->pl;
+ const DynamicMeter* meter = Hashtable_get(pl->dynamicMeters, this->param);
+ if (meter)
+ return meter->caption ? meter->caption : meter->name;
+ return this->caption;
+}
+
+static void DynamicMeter_getUiName(const Meter* this, char* name, size_t length) {
+ const ProcessList* pl = this->pl;
+ const DynamicMeter* meter = Hashtable_get(pl->dynamicMeters, this->param);
+ if (meter) {
+ const char* uiName = meter->caption;
+ if (uiName) {
+ int len = strlen(uiName);
+ if (len > 2 && uiName[len - 2] == ':')
+ len -= 2;
+ xSnprintf(name, length, "%.*s", len, uiName);
+ } else {
+ xSnprintf(name, length, "%s", meter->name);
+ }
+ }
+}
+
+const MeterClass DynamicMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = DynamicMeter_display
+ },
+ .init = DynamicMeter_init,
+ .updateValues = DynamicMeter_updateValues,
+ .getCaption = DynamicMeter_getCaption,
+ .getUiName = DynamicMeter_getUiName,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 0,
+ .total = 100.0,
+ .attributes = DynamicMeter_attributes,
+ .name = "Dynamic",
+ .uiName = "Dynamic",
+ .caption = "",
+};
diff --git a/DynamicMeter.h b/DynamicMeter.h
new file mode 100644
index 0000000..3ef0176
--- /dev/null
+++ b/DynamicMeter.h
@@ -0,0 +1,28 @@
+#ifndef HEADER_DynamicMeter
+#define HEADER_DynamicMeter
+
+#include <stdbool.h>
+
+#include "Hashtable.h"
+#include "Meter.h"
+
+
+typedef struct DynamicMeter_ {
+ char name[32]; /* unique name, cannot contain spaces */
+ char* caption;
+ char* description;
+ unsigned int type;
+ double maximum;
+} DynamicMeter;
+
+Hashtable* DynamicMeters_new(void);
+
+void DynamicMeters_delete(Hashtable* dynamics);
+
+const char* DynamicMeter_lookup(Hashtable* dynamics, unsigned int key);
+
+bool DynamicMeter_search(Hashtable* dynamics, const char* name, unsigned int* key);
+
+extern const MeterClass DynamicMeter_class;
+
+#endif
diff --git a/EnvScreen.c b/EnvScreen.c
index ae63d3e..0fcee83 100644
--- a/EnvScreen.c
+++ b/EnvScreen.c
@@ -5,7 +5,6 @@
#include <stdlib.h>
#include <string.h>
-#include "CRT.h"
#include "Macros.h"
#include "Panel.h"
#include "Platform.h"
@@ -14,40 +13,29 @@
#include "XUtils.h"
-const InfoScreenClass EnvScreen_class = {
- .super = {
- .extends = Class(Object),
- .delete = EnvScreen_delete
- },
- .scan = EnvScreen_scan,
- .draw = EnvScreen_draw
-};
-
EnvScreen* EnvScreen_new(Process* process) {
EnvScreen* this = xMalloc(sizeof(EnvScreen));
Object_setClass(this, Class(EnvScreen));
- return (EnvScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 3, " ");
+ return (EnvScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " ");
}
void EnvScreen_delete(Object* this) {
free(InfoScreen_done((InfoScreen*)this));
}
-void EnvScreen_draw(InfoScreen* this) {
+static void EnvScreen_draw(InfoScreen* this) {
InfoScreen_drawTitled(this, "Environment of process %d - %s", this->process->pid, Process_getCommand(this->process));
}
-void EnvScreen_scan(InfoScreen* this) {
+static void EnvScreen_scan(InfoScreen* this) {
Panel* panel = this->display;
int idx = MAXIMUM(Panel_getSelectedIndex(panel), 0);
Panel_prune(panel);
- CRT_dropPrivileges();
char* env = Platform_getProcessEnv(this->process->pid);
- CRT_restorePrivileges();
if (env) {
- for (char* p = env; *p; p = strrchr(p, 0) + 1)
+ for (const char* p = env; *p; p = strrchr(p, 0) + 1)
InfoScreen_addLine(this, p);
free(env);
}
@@ -59,3 +47,12 @@ void EnvScreen_scan(InfoScreen* this) {
Vector_insertionSort(panel->items);
Panel_setSelected(panel, idx);
}
+
+const InfoScreenClass EnvScreen_class = {
+ .super = {
+ .extends = Class(Object),
+ .delete = EnvScreen_delete
+ },
+ .scan = EnvScreen_scan,
+ .draw = EnvScreen_draw
+};
diff --git a/EnvScreen.h b/EnvScreen.h
index bf38580..4d44c81 100644
--- a/EnvScreen.h
+++ b/EnvScreen.h
@@ -5,6 +5,7 @@
#include "Object.h"
#include "Process.h"
+
typedef struct EnvScreen_ {
InfoScreen super;
} EnvScreen;
@@ -15,8 +16,4 @@ EnvScreen* EnvScreen_new(Process* process);
void EnvScreen_delete(Object* this);
-void EnvScreen_draw(InfoScreen* this);
-
-void EnvScreen_scan(InfoScreen* this);
-
#endif
diff --git a/FunctionBar.c b/FunctionBar.c
index 627bc77..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;
@@ -112,18 +113,21 @@ void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr
attrset(attr);
}
mvaddstr(LINES - 1, x, buffer);
- attrset(CRT_colors[RESET_COLOR]);
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) {
@@ -132,10 +136,10 @@ void FunctionBar_append(const char* buffer, int attr) {
} else {
attrset(attr);
}
- mvaddstr(LINES - 1, currentLen, buffer);
+ mvaddstr(LINES - 1, currentLen + 1, buffer);
attrset(CRT_colors[RESET_COLOR]);
- currentLen += strlen(buffer);
+ currentLen += strlen(buffer) + 1;
}
int FunctionBar_synthesizeEvent(const FunctionBar* this, int pos) {
diff --git a/FunctionBar.h b/FunctionBar.h
index 925e323..f01a5ef 100644
--- a/FunctionBar.h
+++ b/FunctionBar.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include <stdbool.h>
+
typedef struct FunctionBar_ {
int size;
char** functions;
@@ -28,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 1beb2bb..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.
*/
@@ -11,44 +11,62 @@ in the source distribution for its full text.
#include <assert.h>
#include <stdint.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "CRT.h"
#include "Macros.h"
#include "XUtils.h"
+#ifndef NDEBUG
+#include <stdio.h>
+#endif
+
+
+typedef struct HashtableItem_ {
+ ht_key_t key;
+ size_t probe;
+ void* value;
+} HashtableItem;
+
+struct Hashtable_ {
+ size_t size;
+ HashtableItem* buckets;
+ size_t items;
+ bool owner;
+};
+
#ifndef NDEBUG
static void Hashtable_dump(const Hashtable* this) {
- fprintf(stderr, "Hashtable %p: size=%u items=%u owner=%s\n",
+ fprintf(stderr, "Hashtable %p: size=%zu items=%zu owner=%s\n",
(const void*)this,
this->size,
this->items,
this->owner ? "yes" : "no");
- unsigned int items = 0;
- for (unsigned int i = 0; i < this->size; i++) {
- fprintf(stderr, " item %5u: key = %5u probe = %2u value = %p\n",
+ size_t items = 0;
+ for (size_t i = 0; i < this->size; i++) {
+ fprintf(stderr, " item %5zu: key = %5u probe = %2zu value = %p\n",
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++;
}
- fprintf(stderr, "Hashtable %p: items=%u counted=%u\n",
+ fprintf(stderr, "Hashtable %p: items=%zu counted=%zu\n",
(const void*)this,
this->items,
items);
}
static bool Hashtable_isConsistent(const Hashtable* this) {
- unsigned int items = 0;
- for (unsigned int i = 0; i < this->size; i++) {
+ size_t items = 0;
+ for (size_t i = 0; i < this->size; i++) {
if (this->buckets[i].value)
items++;
}
@@ -58,9 +76,9 @@ static bool Hashtable_isConsistent(const Hashtable* this) {
return res;
}
-unsigned int Hashtable_count(const Hashtable* this) {
- unsigned int items = 0;
- for (unsigned int i = 0; i < this->size; i++) {
+size_t Hashtable_count(const Hashtable* this) {
+ size_t items = 0;
+ for (size_t i = 0; i < this->size; i++) {
if (this->buckets[i].value)
items++;
}
@@ -72,7 +90,7 @@ unsigned int 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,
@@ -80,18 +98,17 @@ static const uint64_t OEISprimes[] = {
34359738337, 68719476731, 137438953447
};
-static uint64_t nextPrime(unsigned int n) {
- assert(n <= OEISprimes[ARRAYSIZE(OEISprimes) - 1]);
-
- for (unsigned int i = 0; i < ARRAYSIZE(OEISprimes); i++) {
+static size_t nextPrime(size_t n) {
+ /* on 32-bit make sure we do not return primes not fitting in size_t */
+ for (size_t i = 0; i < ARRAYSIZE(OEISprimes) && OEISprimes[i] < SIZE_MAX; i++) {
if (n <= OEISprimes[i])
return OEISprimes[i];
}
- return OEISprimes[ARRAYSIZE(OEISprimes) - 1];
+ CRT_fatalError("Hashtable: no prime found");
}
-Hashtable* Hashtable_new(unsigned int size, bool owner) {
+Hashtable* Hashtable_new(size_t size, bool owner) {
Hashtable* this;
this = xMalloc(sizeof(Hashtable));
@@ -115,7 +132,7 @@ void Hashtable_clear(Hashtable* this) {
assert(Hashtable_isConsistent(this));
if (this->owner)
- for (unsigned int i = 0; i < this->size; i++)
+ for (size_t i = 0; i < this->size; i++)
free(this->buckets[i].value);
memset(this->buckets, 0, this->size * sizeof(HashtableItem));
@@ -124,11 +141,11 @@ void Hashtable_clear(Hashtable* this) {
assert(Hashtable_isConsistent(this));
}
-static void insert(Hashtable* this, hkey_t key, void* value) {
- unsigned int index = key % this->size;
- unsigned int probe = 0;
+static void insert(Hashtable* this, ht_key_t key, void* value) {
+ size_t index = key % this->size;
+ size_t probe = 0;
#ifndef NDEBUG
- unsigned int origIndex = index;
+ size_t origIndex = index;
#endif
for (;;) {
@@ -167,22 +184,26 @@ static void insert(Hashtable* this, hkey_t key, void* value) {
}
}
-void Hashtable_setSize(Hashtable* this, unsigned int size) {
+void Hashtable_setSize(Hashtable* this, size_t size) {
assert(Hashtable_isConsistent(this));
if (size <= this->items)
return;
+ size_t newSize = nextPrime(size);
+ if (newSize == this->size)
+ return;
+
HashtableItem* oldBuckets = this->buckets;
- unsigned int oldSize = this->size;
+ size_t oldSize = this->size;
- this->size = nextPrime(size);
+ this->size = newSize;
this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem));
this->items = 0;
/* rehash */
- for (unsigned int i = 0; i < oldSize; i++) {
+ for (size_t i = 0; i < oldSize; i++) {
if (!oldBuckets[i].value)
continue;
@@ -194,15 +215,19 @@ void Hashtable_setSize(Hashtable* this, unsigned int size) {
assert(Hashtable_isConsistent(this));
}
-void Hashtable_put(Hashtable* this, hkey_t key, void* value) {
+void Hashtable_put(Hashtable* this, ht_key_t key, void* value) {
assert(Hashtable_isConsistent(this));
assert(this->size > 0);
assert(value);
/* grow on load-factor > 0.7 */
- if (10 * this->items > 7 * this->size)
+ if (10 * this->items > 7 * this->size) {
+ if (SIZE_MAX / 2 < this->size)
+ CRT_fatalError("Hashtable: size overflow");
+
Hashtable_setSize(this, 2 * this->size);
+ }
insert(this, key, value);
@@ -211,11 +236,11 @@ void Hashtable_put(Hashtable* this, hkey_t key, void* value) {
assert(this->size > this->items);
}
-void* Hashtable_remove(Hashtable* this, hkey_t key) {
- unsigned int index = key % this->size;
- unsigned int probe = 0;
+void* Hashtable_remove(Hashtable* this, ht_key_t key) {
+ size_t index = key % this->size;
+ size_t probe = 0;
#ifndef NDEBUG
- unsigned int origIndex = index;
+ size_t origIndex = index;
#endif
assert(Hashtable_isConsistent(this));
@@ -230,7 +255,7 @@ void* Hashtable_remove(Hashtable* this, hkey_t key) {
res = this->buckets[index].value;
}
- unsigned int next = (index + 1) % this->size;
+ size_t next = (index + 1) % this->size;
while (this->buckets[next].value && this->buckets[next].probe > 0) {
this->buckets[index] = this->buckets[next];
@@ -261,17 +286,17 @@ void* Hashtable_remove(Hashtable* this, hkey_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;
}
-void* Hashtable_get(Hashtable* this, hkey_t key) {
- unsigned int index = key % this->size;
- unsigned int probe = 0;
+void* Hashtable_get(Hashtable* this, ht_key_t key) {
+ size_t index = key % this->size;
+ size_t probe = 0;
void* res = NULL;
#ifndef NDEBUG
- unsigned int origIndex = index;
+ size_t origIndex = index;
#endif
assert(Hashtable_isConsistent(this));
@@ -296,7 +321,7 @@ void* Hashtable_get(Hashtable* this, hkey_t key) {
void Hashtable_foreach(Hashtable* this, Hashtable_PairFunction f, void* userData) {
assert(Hashtable_isConsistent(this));
- for (unsigned int i = 0; i < this->size; i++) {
+ for (size_t i = 0; i < this->size; i++) {
HashtableItem* walk = &this->buckets[i];
if (walk->value)
f(walk->key, walk->value, userData);
diff --git a/Hashtable.h b/Hashtable.h
index 6d93478..f51a294 100644
--- a/Hashtable.h
+++ b/Hashtable.h
@@ -3,49 +3,39 @@
/*
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.
*/
#include <stdbool.h>
+#include <stddef.h>
-typedef unsigned int hkey_t;
+typedef unsigned int ht_key_t;
-typedef void(*Hashtable_PairFunction)(hkey_t key, void* value, void* userdata);
+typedef void(*Hashtable_PairFunction)(ht_key_t key, void* value, void* userdata);
-typedef struct HashtableItem_ {
- hkey_t key;
- unsigned int probe;
- void* value;
-} HashtableItem;
-
-typedef struct Hashtable_ {
- unsigned int size;
- HashtableItem* buckets;
- unsigned int items;
- bool owner;
-} Hashtable;
+typedef struct Hashtable_ Hashtable;
#ifndef NDEBUG
-unsigned int Hashtable_count(const Hashtable* this);
+size_t Hashtable_count(const Hashtable* this);
#endif /* NDEBUG */
-Hashtable* Hashtable_new(unsigned int size, bool owner);
+Hashtable* Hashtable_new(size_t size, bool owner);
void Hashtable_delete(Hashtable* this);
void Hashtable_clear(Hashtable* this);
-void Hashtable_setSize(Hashtable* this, unsigned int size);
+void Hashtable_setSize(Hashtable* this, size_t size);
-void Hashtable_put(Hashtable* this, hkey_t key, void* value);
+void Hashtable_put(Hashtable* this, ht_key_t key, void* value);
-void* Hashtable_remove(Hashtable* this, hkey_t key);
+void* Hashtable_remove(Hashtable* this, ht_key_t key);
-void* Hashtable_get(Hashtable* this, hkey_t key);
+void* Hashtable_get(Hashtable* this, ht_key_t key);
void Hashtable_foreach(Hashtable* this, Hashtable_PairFunction f, void* userData);
diff --git a/Header.c b/Header.c
index 24c4077..1953c02 100644
--- a/Header.c
+++ b/Header.c
@@ -1,18 +1,23 @@
/*
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 "Header.h"
+#include <assert.h>
+#include <math.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CRT.h"
+#include "CPUMeter.h"
+#include "DynamicMeter.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
@@ -20,15 +25,17 @@ in the source distribution for its full text.
#include "XUtils.h"
-Header* Header_new(struct ProcessList_* pl, Settings* settings, int nrColumns) {
+Header* Header_new(ProcessList* pl, Settings* settings, HeaderLayout hLayout) {
Header* this = xCalloc(1, sizeof(Header));
- this->columns = xCalloc(nrColumns, sizeof(Vector*));
+ this->columns = xMallocArray(HeaderLayout_getColumns(hLayout), sizeof(Vector*));
this->settings = settings;
this->pl = pl;
- this->nrColumns = nrColumns;
+ this->headerLayout = hLayout;
+
Header_forEachColumn(this, i) {
this->columns[i] = Vector_new(Class(Meter), true, DEFAULT_SIZE);
}
+
return this;
}
@@ -36,42 +43,120 @@ void Header_delete(Header* this) {
Header_forEachColumn(this, i) {
Vector_delete(this->columns[i]);
}
+
free(this->columns);
free(this);
}
+void Header_setLayout(Header* this, HeaderLayout hLayout) {
+ size_t oldColumns = HeaderLayout_getColumns(this->headerLayout);
+ size_t newColumns = HeaderLayout_getColumns(hLayout);
+
+ this->headerLayout = hLayout;
+
+ if (newColumns == oldColumns)
+ return;
+
+ if (newColumns > oldColumns) {
+ this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*));
+ for (size_t i = oldColumns; i < newColumns; i++)
+ this->columns[i] = Vector_new(Class(Meter), true, DEFAULT_SIZE);
+ } else {
+ // move meters from to-be-deleted columns into last one
+ for (size_t i = newColumns; i < oldColumns; i++) {
+ for (int j = this->columns[i]->items - 1; j >= 0; j--) {
+ Vector_add(this->columns[newColumns - 1], Vector_take(this->columns[i], j));
+ }
+ Vector_delete(this->columns[i]);
+ }
+ this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*));
+ }
+
+ Header_calculateHeight(this);
+}
+
+static void Header_addMeterByName(Header* this, const char* name, MeterModeId mode, unsigned int column) {
+ assert(column < HeaderLayout_getColumns(this->headerLayout));
+
+ Vector* meters = this->columns[column];
+
+ const char* paren = strchr(name, '(');
+ unsigned int param = 0;
+ size_t nameLen;
+ if (paren) {
+ int ok = sscanf(paren, "(%10u)", &param); // CPUMeter
+ if (!ok) {
+ char dynamic[32] = {0};
+ if (sscanf(paren, "(%30s)", dynamic)) { // DynamicMeter
+ char* end;
+ if ((end = strrchr(dynamic, ')')) == NULL)
+ return; // htoprc parse failure
+ *end = '\0';
+ if (!DynamicMeter_search(this->pl->dynamicMeters, dynamic, &param))
+ return; // name lookup failure
+ } else {
+ param = 0;
+ }
+ }
+ nameLen = paren - name;
+ } else {
+ nameLen = strlen(name);
+ }
+
+ for (const MeterClass* const* type = Platform_meterTypes; *type; type++) {
+ if (0 == strncmp(name, (*type)->name, nameLen) && (*type)->name[nameLen] == '\0') {
+ Meter* meter = Meter_new(this->pl, param, *type);
+ if (mode != 0) {
+ Meter_setMode(meter, mode);
+ }
+ Vector_add(meters, meter);
+ break;
+ }
+ }
+}
+
void Header_populateFromSettings(Header* this) {
+ Header_setLayout(this, this->settings->hLayout);
+
Header_forEachColumn(this, col) {
- MeterColumnSettings* colSettings = &this->settings->columns[col];
- for (int i = 0; i < colSettings->len; i++) {
- Header_addMeterByName(this, colSettings->names[i], col);
- if (colSettings->modes[i] != 0) {
- Header_setMode(this, i, colSettings->modes[i], col);
- }
+ const MeterColumnSetting* colSettings = &this->settings->hColumns[col];
+ Vector_prune(this->columns[col]);
+ for (size_t i = 0; i < colSettings->len; i++) {
+ Header_addMeterByName(this, colSettings->names[i], colSettings->modes[i], col);
}
}
+
Header_calculateHeight(this);
}
void Header_writeBackToSettings(const Header* this) {
+ Settings_setHeaderLayout(this->settings, this->headerLayout);
+
Header_forEachColumn(this, col) {
- MeterColumnSettings* colSettings = &this->settings->columns[col];
+ MeterColumnSetting* colSettings = &this->settings->hColumns[col];
- String_freeArray(colSettings->names);
+ if (colSettings->names) {
+ for (size_t j = 0; j < colSettings->len; j++)
+ free(colSettings->names[j]);
+ free(colSettings->names);
+ }
free(colSettings->modes);
- Vector* vec = this->columns[col];
+ const Vector* vec = this->columns[col];
int len = Vector_size(vec);
- colSettings->names = xCalloc(len + 1, sizeof(char*));
- colSettings->modes = xCalloc(len, sizeof(int));
+ colSettings->names = len ? xCalloc(len + 1, sizeof(char*)) : NULL;
+ colSettings->modes = len ? xCalloc(len, sizeof(int)) : NULL;
colSettings->len = len;
for (int i = 0; i < len; i++) {
- Meter* meter = (Meter*) Vector_get(vec, i);
+ const Meter* meter = (Meter*) Vector_get(vec, i);
char* name;
- if (meter->param) {
- xAsprintf(&name, "%s(%d)", As_Meter(meter)->name, meter->param);
+ if (meter->param && As_Meter(meter) == &DynamicMeter_class) {
+ const char* dynamic = DynamicMeter_lookup(this->pl->dynamicMeters, meter->param);
+ xAsprintf(&name, "%s(%s)", As_Meter(meter)->name, dynamic);
+ } else if (meter->param && As_Meter(meter) == &CPUMeter_class) {
+ xAsprintf(&name, "%s(%u)", As_Meter(meter)->name, meter->param);
} else {
xAsprintf(&name, "%s", As_Meter(meter)->name);
}
@@ -81,44 +166,9 @@ void Header_writeBackToSettings(const Header* this) {
}
}
-MeterModeId Header_addMeterByName(Header* this, char* name, int column) {
- Vector* meters = this->columns[column];
-
- char* paren = strchr(name, '(');
- int param = 0;
- if (paren) {
- int ok = sscanf(paren, "(%10d)", &param);
- if (!ok)
- param = 0;
- *paren = '\0';
- }
- MeterModeId mode = TEXT_METERMODE;
- for (const MeterClass* const* type = Platform_meterTypes; *type; type++) {
- if (String_eq(name, (*type)->name)) {
- Meter* meter = Meter_new(this->pl, param, *type);
- Vector_add(meters, meter);
- mode = meter->mode;
- break;
- }
- }
-
- if (paren)
- *paren = '(';
+Meter* Header_addMeterByClass(Header* this, const MeterClass* type, unsigned int param, unsigned int column) {
+ assert(column < HeaderLayout_getColumns(this->headerLayout));
- return mode;
-}
-
-void Header_setMode(Header* this, int i, MeterModeId mode, int column) {
- Vector* meters = this->columns[column];
-
- if (i >= Vector_size(meters))
- return;
-
- Meter* meter = (Meter*) Vector_get(meters, i);
- Meter_setMode(meter, mode);
-}
-
-Meter* Header_addMeterByClass(Header* this, const MeterClass* type, int param, int column) {
Vector* meters = this->columns[column];
Meter* meter = Meter_new(this->pl, param, type);
@@ -126,18 +176,6 @@ Meter* Header_addMeterByClass(Header* this, const MeterClass* type, int param, i
return meter;
}
-int Header_size(Header* this, int column) {
- Vector* meters = this->columns[column];
- return Vector_size(meters);
-}
-
-MeterModeId Header_readMeterMode(Header* this, int i, int column) {
- Vector* meters = this->columns[column];
-
- Meter* meter = (Meter*) Vector_get(meters, i);
- return meter->mode;
-}
-
void Header_reinit(Header* this) {
Header_forEachColumn(this, col) {
for (int i = 0; i < Vector_size(this->columns[col]); i++) {
@@ -150,40 +188,117 @@ void Header_reinit(Header* this) {
}
void Header_draw(const Header* this) {
- int height = this->height;
- int pad = this->pad;
+ const int height = this->height;
+ const int pad = this->pad;
attrset(CRT_colors[RESET_COLOR]);
for (int y = 0; y < height; y++) {
mvhline(y, 0, ' ', COLS);
}
- int width = COLS / this->nrColumns - (pad * this->nrColumns - 1) - 1;
+ const int numCols = HeaderLayout_getColumns(this->headerLayout);
+ const int width = COLS - 2 * pad - (numCols - 1);
int x = pad;
+ float roundingLoss = 0.0F;
Header_forEachColumn(this, col) {
Vector* meters = this->columns[col];
+ float colWidth = (float)width * HeaderLayout_layouts[this->headerLayout].widths[col] / 100.0F;
+
+ roundingLoss += colWidth - floorf(colWidth);
+ if (roundingLoss >= 1.0F) {
+ colWidth += 1.0F;
+ roundingLoss -= 1.0F;
+ }
+
for (int y = (pad / 2), i = 0; i < Vector_size(meters); i++) {
Meter* meter = (Meter*) Vector_get(meters, i);
- meter->draw(meter, x, y, width);
+
+ float actualWidth = colWidth;
+
+ /* Let meters in text mode expand to the right on empty neighbors;
+ except for multi column meters. */
+ if (meter->mode == TEXT_METERMODE && !Meter_isMultiColumn(meter)) {
+ for (int j = 1; j < meter->columnWidthCount; j++) {
+ actualWidth++; /* separator column */
+ actualWidth += (float)width * HeaderLayout_layouts[this->headerLayout].widths[col + j] / 100.0F;
+ }
+ }
+
+ assert(meter->draw);
+ meter->draw(meter, x, y, floorf(actualWidth));
y += meter->h;
}
- x += width + pad;
+
+ x += floorf(colWidth);
+ x++; /* separator column */
+ }
+}
+
+void Header_updateData(Header* this) {
+ Header_forEachColumn(this, col) {
+ Vector* meters = this->columns[col];
+ int items = Vector_size(meters);
+ for (int i = 0; i < items; i++) {
+ Meter* meter = (Meter*) Vector_get(meters, i);
+ Meter_updateValues(meter);
+ }
+ }
+}
+
+/*
+ * Calculate how many columns the current meter is allowed to span,
+ * by counting how many columns to the right are empty or contain a BlankMeter.
+ * Returns the number of columns to span, i.e. if the direct neighbor is occupied 1.
+ */
+static int calcColumnWidthCount(const Header* this, const Meter* curMeter, const int pad, const unsigned int curColumn, const int curHeight) {
+ for (size_t i = curColumn + 1; i < HeaderLayout_getColumns(this->headerLayout); i++) {
+ const Vector* meters = this->columns[i];
+
+ int height = pad;
+ for (int j = 0; j < Vector_size(meters); j++) {
+ const Meter* meter = (const Meter*) Vector_get(meters, j);
+
+ if (height >= curHeight + curMeter->h)
+ break;
+
+ height += meter->h;
+ if (height <= curHeight)
+ continue;
+
+ if (!Object_isA((const Object*) meter, (const ObjectClass*) &BlankMeter_class))
+ return i - curColumn;
+ }
}
+
+ return HeaderLayout_getColumns(this->headerLayout) - curColumn;
}
int Header_calculateHeight(Header* this) {
- int pad = this->settings->headerMargin ? 2 : 0;
+ const int pad = this->settings->headerMargin ? 2 : 0;
int maxHeight = pad;
Header_forEachColumn(this, col) {
- Vector* meters = this->columns[col];
+ const Vector* meters = this->columns[col];
int height = pad;
for (int i = 0; i < Vector_size(meters); i++) {
Meter* meter = (Meter*) Vector_get(meters, i);
+ meter->columnWidthCount = calcColumnWidthCount(this, meter, pad, col, height);
height += meter->h;
}
maxHeight = MAXIMUM(maxHeight, height);
}
+
+ if (maxHeight == pad) {
+ maxHeight = 0;
+ this->pad = 0;
+ } else {
+ this->pad = pad;
+ }
+
+ if (this->settings->screenTabs) {
+ maxHeight++;
+ }
+
this->height = maxHeight;
- this->pad = pad;
+
return maxHeight;
}
diff --git a/Header.h b/Header.h
index 870fbd8..954d434 100644
--- a/Header.h
+++ b/Header.h
@@ -3,48 +3,46 @@
/*
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 "HeaderLayout.h"
#include "Meter.h"
#include "ProcessList.h"
#include "Settings.h"
#include "Vector.h"
+
typedef struct Header_ {
Vector** columns;
Settings* settings;
ProcessList* pl;
- int nrColumns;
+ HeaderLayout headerLayout;
int pad;
int height;
} Header;
-#define Header_forEachColumn(this_, i_) for (int (i_)=0; (i_) < (this_)->nrColumns; ++(i_))
+#define Header_forEachColumn(this_, i_) for (size_t (i_)=0, H_fEC_numColumns_ = HeaderLayout_getColumns((this_)->headerLayout); (i_) < H_fEC_numColumns_; ++(i_))
-Header* Header_new(ProcessList* pl, Settings* settings, int nrColumns);
+Header* Header_new(ProcessList* pl, Settings* settings, HeaderLayout hLayout);
void Header_delete(Header* this);
+void Header_setLayout(Header* this, HeaderLayout hLayout);
+
void Header_populateFromSettings(Header* this);
void Header_writeBackToSettings(const Header* this);
-MeterModeId Header_addMeterByName(Header* this, char* name, int column);
-
-void Header_setMode(Header* this, int i, MeterModeId mode, int column);
-
-Meter* Header_addMeterByClass(Header* this, const MeterClass* type, int param, int column);
-
-int Header_size(Header* this, int column);
-
-MeterModeId Header_readMeterMode(Header* this, int i, int column);
+Meter* Header_addMeterByClass(Header* this, const MeterClass* type, unsigned int param, unsigned int column);
void Header_reinit(Header* this);
void Header_draw(const Header* this);
+void Header_updateData(Header* this);
+
int Header_calculateHeight(Header* this);
#endif
diff --git a/HeaderLayout.h b/HeaderLayout.h
new file mode 100644
index 0000000..1cf7bf7
--- /dev/null
+++ b/HeaderLayout.h
@@ -0,0 +1,78 @@
+#ifndef HEADER_HeaderLayout
+#define HEADER_HeaderLayout
+/*
+htop - HeaderLayout.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "Macros.h"
+#include "XUtils.h"
+
+
+typedef enum HeaderLayout_ {
+ HF_INVALID = -1,
+ HF_TWO_50_50,
+ HF_TWO_33_67,
+ HF_TWO_67_33,
+ HF_THREE_33_34_33,
+ HF_THREE_25_25_50,
+ HF_THREE_25_50_25,
+ HF_THREE_50_25_25,
+ HF_THREE_40_20_40,
+ HF_FOUR_25_25_25_25,
+ LAST_HEADER_LAYOUT
+} HeaderLayout;
+
+static const struct {
+ uint8_t columns;
+ const uint8_t widths[4];
+ const char* name;
+ const char* description;
+} HeaderLayout_layouts[LAST_HEADER_LAYOUT] = {
+ [HF_TWO_50_50] = { 2, { 50, 50, 0, 0 }, "two_50_50", "2 columns - 50/50 (default)", },
+ [HF_TWO_33_67] = { 2, { 33, 67, 0, 0 }, "two_33_67", "2 columns - 33/67", },
+ [HF_TWO_67_33] = { 2, { 67, 33, 0, 0 }, "two_67_33", "2 columns - 67/33", },
+ [HF_THREE_33_34_33] = { 3, { 33, 34, 33, 0 }, "three_33_34_33", "3 columns - 33/34/33", },
+ [HF_THREE_25_25_50] = { 3, { 25, 25, 50, 0 }, "three_25_25_50", "3 columns - 25/25/50", },
+ [HF_THREE_25_50_25] = { 3, { 25, 50, 25, 0 }, "three_25_50_25", "3 columns - 25/50/25", },
+ [HF_THREE_50_25_25] = { 3, { 50, 25, 25, 0 }, "three_50_25_25", "3 columns - 50/25/25", },
+ [HF_THREE_40_20_40] = { 3, { 40, 20, 40, 0 }, "three_40_20_40", "3 columns - 40/20/40", },
+ [HF_FOUR_25_25_25_25] = { 4, { 25, 25, 25, 25 }, "four_25_25_25_25", "4 columns - 25/25/25/25", },
+};
+
+static inline size_t HeaderLayout_getColumns(HeaderLayout hLayout) {
+ /* assert the layout is initialized */
+ assert(0 <= hLayout);
+ assert(hLayout < LAST_HEADER_LAYOUT);
+ assert(HeaderLayout_layouts[hLayout].name[0]);
+ assert(HeaderLayout_layouts[hLayout].description[0]);
+ return HeaderLayout_layouts[hLayout].columns;
+}
+
+static inline const char* HeaderLayout_getName(HeaderLayout hLayout) {
+ /* assert the layout is initialized */
+ assert(0 <= hLayout);
+ assert(hLayout < LAST_HEADER_LAYOUT);
+ assert(HeaderLayout_layouts[hLayout].name[0]);
+ assert(HeaderLayout_layouts[hLayout].description[0]);
+ return HeaderLayout_layouts[hLayout].name;
+}
+
+static inline HeaderLayout HeaderLayout_fromName(const char* name) {
+ for (size_t i = 0; i < LAST_HEADER_LAYOUT; i++) {
+ if (String_eq(HeaderLayout_layouts[i].name, name))
+ return (HeaderLayout) i;
+ }
+
+ return LAST_HEADER_LAYOUT;
+}
+
+#endif /* HEADER_HeaderLayout */
diff --git a/HeaderOptionsPanel.c b/HeaderOptionsPanel.c
new file mode 100644
index 0000000..25d1ddb
--- /dev/null
+++ b/HeaderOptionsPanel.c
@@ -0,0 +1,88 @@
+/*
+htop - HeaderOptionsPanel.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "HeaderOptionsPanel.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "CRT.h"
+#include "FunctionBar.h"
+#include "Header.h"
+#include "HeaderLayout.h"
+#include "Object.h"
+#include "OptionItem.h"
+#include "ProvideCurses.h"
+
+
+static const char* const HeaderOptionsFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL};
+
+static void HeaderOptionsPanel_delete(Object* object) {
+ Panel* super = (Panel*) object;
+ HeaderOptionsPanel* this = (HeaderOptionsPanel*) object;
+ Panel_done(super);
+ free(this);
+}
+
+static HandlerResult HeaderOptionsPanel_eventHandler(Panel* super, int ch) {
+ HeaderOptionsPanel* this = (HeaderOptionsPanel*) super;
+
+ HandlerResult result = IGNORED;
+ int mark;
+
+ switch (ch) {
+ case 0x0a:
+ case 0x0d:
+ case KEY_ENTER:
+ case KEY_MOUSE:
+ case KEY_RECLICK:
+ case ' ':
+ mark = Panel_getSelectedIndex(super);
+ assert(mark >= 0);
+ assert(mark < LAST_HEADER_LAYOUT);
+
+ for (int i = 0; i < LAST_HEADER_LAYOUT; i++)
+ CheckItem_set((CheckItem*)Panel_get(super, i), false);
+ CheckItem_set((CheckItem*)Panel_get(super, mark), true);
+
+ Header_setLayout(this->scr->header, mark);
+ this->settings->changed = true;
+ this->settings->lastUpdate++;
+
+ ScreenManager_resize(this->scr);
+
+ result = HANDLED;
+ }
+
+ return result;
+}
+
+const PanelClass HeaderOptionsPanel_class = {
+ .super = {
+ .extends = Class(Panel),
+ .delete = HeaderOptionsPanel_delete
+ },
+ .eventHandler = HeaderOptionsPanel_eventHandler
+};
+
+HeaderOptionsPanel* HeaderOptionsPanel_new(Settings* settings, ScreenManager* scr) {
+ HeaderOptionsPanel* this = AllocThis(HeaderOptionsPanel);
+ Panel* super = (Panel*) this;
+ FunctionBar* fuBar = FunctionBar_new(HeaderOptionsFunctions, NULL, NULL);
+ Panel_init(super, 1, 1, 1, 1, Class(CheckItem), true, fuBar);
+
+ this->scr = scr;
+ this->settings = settings;
+
+ Panel_setHeader(super, "Header Layout");
+ for (int i = 0; i < LAST_HEADER_LAYOUT; i++) {
+ Panel_add(super, (Object*) CheckItem_newByVal(HeaderLayout_layouts[i].description, false));
+ }
+ CheckItem_set((CheckItem*)Panel_get(super, scr->header->headerLayout), true);
+ return this;
+}
diff --git a/HeaderOptionsPanel.h b/HeaderOptionsPanel.h
new file mode 100644
index 0000000..aee9328
--- /dev/null
+++ b/HeaderOptionsPanel.h
@@ -0,0 +1,26 @@
+#ifndef HEADER_HeaderOptionsPanel
+#define HEADER_HeaderOptionsPanel
+/*
+htop - ColorsPanel.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Panel.h"
+#include "ScreenManager.h"
+#include "Settings.h"
+
+
+typedef struct HeaderOptionsPanel_ {
+ Panel super;
+
+ ScreenManager* scr;
+ Settings* settings;
+} HeaderOptionsPanel;
+
+extern const PanelClass HeaderOptionsPanel_class;
+
+HeaderOptionsPanel* HeaderOptionsPanel_new(Settings* settings, ScreenManager* scr);
+
+#endif /* HEADER_HeaderOptionsPanel */
diff --git a/HostnameMeter.c b/HostnameMeter.c
index af8e349..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.
*/
@@ -9,19 +9,17 @@ in the source distribution for its full text.
#include "HostnameMeter.h"
-#include <unistd.h>
-
#include "CRT.h"
#include "Object.h"
+#include "Platform.h"
static const int HostnameMeter_attributes[] = {
HOSTNAME
};
-static void HostnameMeter_updateValues(Meter* this, char* buffer, size_t size) {
- (void) this;
- gethostname(buffer, size - 1);
+static void HostnameMeter_updateValues(Meter* this) {
+ Platform_getHostname(this->txtBuffer, sizeof(this->txtBuffer));
}
const MeterClass HostnameMeter_class = {
diff --git a/HostnameMeter.h b/HostnameMeter.h
index 77fe3da..ec1b63a 100644
--- a/HostnameMeter.h
+++ b/HostnameMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass HostnameMeter_class;
#endif
diff --git a/IncSet.c b/IncSet.c
index d280caf..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.
*/
@@ -29,9 +29,16 @@ void IncSet_reset(IncSet* this, IncType type) {
IncMode_reset(&this->modes[type]);
}
-static const char* const searchFunctions[] = {"Next ", "Cancel ", " Search: ", NULL};
-static const char* const searchKeys[] = {"F3", "Esc", " "};
-static int searchEvents[] = {KEY_F(3), 27, ERR};
+void IncSet_setFilter(IncSet* this, const char* filter) {
+ IncMode* mode = &this->modes[INC_FILTER];
+ size_t len = String_safeStrncpy(mode->buffer, filter, sizeof(mode->buffer));
+ mode->index = len;
+ this->filtering = true;
+}
+
+static const char* const searchFunctions[] = {"Next ", "Prev ", "Cancel ", " Search: ", NULL};
+static const char* const searchKeys[] = {"F3", "S-F3", "Esc", " "};
+static const int searchEvents[] = {KEY_F(3), KEY_F(15), 27, ERR};
static inline void IncMode_initSearch(IncMode* search) {
memset(search, 0, sizeof(IncMode));
@@ -41,7 +48,7 @@ static inline void IncMode_initSearch(IncMode* search) {
static const char* const filterFunctions[] = {"Done ", "Clear ", " Filter: ", NULL};
static const char* const filterKeys[] = {"Enter", "Esc", " "};
-static int filterEvents[] = {13, 27, ERR};
+static const int filterEvents[] = {13, 27, ERR};
static inline void IncMode_initFilter(IncMode* filter) {
memset(filter, 0, sizeof(IncMode));
@@ -54,12 +61,13 @@ static inline void IncMode_done(IncMode* mode) {
}
IncSet* IncSet_new(FunctionBar* bar) {
- IncSet* this = xCalloc(1, sizeof(IncSet));
+ IncSet* this = xMalloc(sizeof(IncSet));
IncMode_initSearch(&(this->modes[INC_SEARCH]));
IncMode_initFilter(&(this->modes[INC_FILTER]));
this->active = NULL;
- this->filtering = false;
this->defaultBar = bar;
+ this->filtering = false;
+ this->found = false;
return this;
}
@@ -69,15 +77,15 @@ void IncSet_delete(IncSet* this) {
free(this);
}
-static void updateWeakPanel(IncSet* this, Panel* panel, Vector* lines) {
- Object* selected = Panel_getSelected(panel);
+static void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) {
+ const Object* selected = Panel_getSelected(panel);
Panel_prune(panel);
if (this->filtering) {
int n = 0;
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);
@@ -97,25 +105,34 @@ static void updateWeakPanel(IncSet* this, Panel* panel, Vector* lines) {
}
}
-static bool search(IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue) {
+static bool search(const IncSet* this, Panel* panel, IncMode_GetPanelValue getPanelValue) {
int size = Panel_size(panel);
- bool found = false;
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);
- found = true;
- break;
+ return true;
}
}
- FunctionBar_drawExtra(mode->bar,
- mode->buffer,
- found ? -1 : CRT_colors[FAILED_SEARCH],
- true);
- return found;
+ 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 bool IncMode_find(IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue, int step) {
+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);
int i = here;
@@ -131,21 +148,13 @@ static bool IncMode_find(IncMode* mode, Panel* panel, IncMode_GetPanelValue getP
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;
}
}
}
-bool IncSet_next(IncSet* this, IncType type, Panel* panel, IncMode_GetPanelValue getPanelValue) {
- return IncMode_find(&this->modes[type], panel, getPanelValue, 1);
-}
-
-bool IncSet_prev(IncSet* this, IncType type, Panel* panel, IncMode_GetPanelValue getPanelValue) {
- return IncMode_find(&this->modes[type], panel, getPanelValue, -1);
-}
-
bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue getPanelValue, Vector* lines) {
if (ch == ERR)
return true;
@@ -154,15 +163,15 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
int size = Panel_size(panel);
bool filterChanged = false;
bool doSearch = true;
- if (ch == KEY_F(3)) {
+ if (ch == KEY_F(3) || ch == KEY_F(15)) {
if (size == 0)
return true;
- IncMode_find(mode, panel, getPanelValue, 1);
+ IncMode_find(mode, panel, getPanelValue, ch == KEY_F(3) ? 1 : -1);
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) {
@@ -172,7 +181,7 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
}
}
}
- } else if ((ch == KEY_BACKSPACE || ch == 127)) {
+ } else if (ch == KEY_BACKSPACE || ch == 127) {
if (mode->index > 0) {
mode->index--;
mode->buffer[mode->index] = 0;
@@ -187,7 +196,7 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
doSearch = false;
}
} else if (ch == KEY_RESIZE) {
- Panel_resize(panel, COLS, LINES - panel->y - 1);
+ doSearch = (mode->index > 0);
} else {
if (mode->isFilter) {
filterChanged = true;
@@ -200,13 +209,11 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
IncMode_reset(mode);
}
}
- this->active = NULL;
- Panel_setDefaultBar(panel);
- FunctionBar_draw(this->defaultBar);
+ 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);
@@ -215,19 +222,17 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
}
const char* IncSet_getListItemValue(Panel* panel, int i) {
- ListItem* l = (ListItem*) Panel_get(panel, i);
+ const ListItem* l = (const ListItem*) Panel_get(panel, i);
return l ? l->value : "";
}
-void IncSet_activate(IncSet* this, IncType type, Panel* panel) {
- this->active = &(this->modes[type]);
- FunctionBar_drawExtra(this->active->bar, this->active->buffer, -1, true);
- 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, -1, 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 b07840f..15b5d5d 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.
*/
@@ -14,6 +14,7 @@ in the source distribution for its full text.
#include "Panel.h"
#include "Vector.h"
+
#define INCMODE_MAX 40
typedef enum {
@@ -31,6 +32,7 @@ typedef struct IncMode_ {
typedef struct IncSet_ {
IncMode modes[2];
IncMode* active;
+ Panel* panel;
FunctionBar* defaultBar;
bool filtering;
bool found;
@@ -40,6 +42,8 @@ static inline const char* IncSet_filter(const IncSet* this) {
return this->filtering ? this->modes[INC_FILTER].buffer : NULL;
}
+void IncSet_setFilter(IncSet* this, const char* filter);
+
typedef const char* (*IncMode_GetPanelValue)(Panel*, int);
void IncSet_reset(IncSet* this, IncType type);
@@ -48,17 +52,13 @@ IncSet* IncSet_new(FunctionBar* bar);
void IncSet_delete(IncSet* this);
-bool IncSet_next(IncSet* this, IncType type, Panel* panel, IncMode_GetPanelValue getPanelValue);
-
-bool IncSet_prev(IncSet* this, IncType type, Panel* panel, IncMode_GetPanelValue getPanelValue);
-
bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue getPanelValue, Vector* lines);
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 ceb29f7..105d9c3 100644
--- a/InfoScreen.c
+++ b/InfoScreen.c
@@ -4,7 +4,6 @@
#include <stdarg.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#include "CRT.h"
@@ -19,16 +18,16 @@ static const char* const InfoScreenFunctions[] = {"Search ", "Filter ", "Refresh
static const char* const InfoScreenKeys[] = {"F3", "F4", "F5", "Esc"};
-static int InfoScreenEvents[] = {KEY_F(3), KEY_F(4), KEY_F(5), 27};
+static const int InfoScreenEvents[] = {KEY_F(3), KEY_F(4), KEY_F(5), 27};
InfoScreen* InfoScreen_init(InfoScreen* this, const Process* process, FunctionBar* bar, int height, const char* panelHeader) {
this->process = process;
if (!bar) {
bar = FunctionBar_new(InfoScreenFunctions, InfoScreenKeys, InfoScreenEvents);
}
- this->display = Panel_new(0, 1, COLS, height, false, Class(ListItem), bar);
+ this->display = Panel_new(0, 1, COLS, height, Class(ListItem), false, bar);
this->inc = IncSet_new(bar);
- this->lines = Vector_new(this->display->items->type, true, DEFAULT_SIZE);
+ this->lines = Vector_new(Vector_type(this->display->items), true, DEFAULT_SIZE);
Panel_setHeader(this->display, panelHeader);
return this;
}
@@ -44,27 +43,27 @@ void InfoScreen_drawTitled(InfoScreen* this, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
- char* title = xMalloc(COLS + 1);
- int len = vsnprintf(title, COLS + 1, fmt, ap);
+ char title[COLS + 1];
+ int len = vsnprintf(title, sizeof(title), fmt, ap);
+ va_end(ap);
+
if (len > COLS) {
memset(&title[COLS - 3], '.', 3);
}
attrset(CRT_colors[METER_TEXT]);
mvhline(0, 0, ' ', COLS);
- mvwprintw(stdscr, 0, 0, title);
+ mvaddstr(0, 0, title);
attrset(CRT_colors[DEFAULT_COLOR]);
- this->display->needsRedraw = true;
- Panel_draw(this->display, true, true);
- IncSet_drawBar(this->inc);
- free(title);
- va_end(ap);
+ Panel_draw(this->display, true, true, true, false);
+
+ 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));
}
}
@@ -73,7 +72,7 @@ void InfoScreen_appendLine(InfoScreen* this, const char* line) {
ListItem* last = (ListItem*)Vector_get(this->lines, Vector_size(this->lines) - 1);
ListItem_append(last, line);
const char* incFilter = IncSet_filter(this->inc);
- if (incFilter && Panel_get(this->display, Panel_size(this->display) - 1) != (Object*)last && String_contains_i(line, incFilter)) {
+ if (incFilter && Panel_get(this->display, Panel_size(this->display) - 1) != (Object*)last && String_contains_i(line, incFilter, true)) {
Panel_add(this->display, (Object*)last);
}
}
@@ -89,13 +88,10 @@ void InfoScreen_run(InfoScreen* this) {
bool looping = true;
while (looping) {
- Panel_draw(panel, true, true);
+ Panel_draw(panel, false, true, true, false);
+ IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
- if (this->inc->active) {
- (void) move(LINES - 1, CRT_cursorX);
- }
- set_escdelay(25);
- int ch = getch();
+ int ch = Panel_getCh(panel);
if (ch == ERR) {
if (As_InfoScreen(this)->onErr) {
@@ -104,25 +100,36 @@ void InfoScreen_run(InfoScreen* this) {
}
}
+#ifdef HAVE_GETMOUSE
if (ch == KEY_MOUSE) {
MEVENT mevent;
int ok = getmouse(&mevent);
if (ok == OK) {
- if (mevent.y >= panel->y && mevent.y < LINES - 1) {
- Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV);
- ch = 0;
- } else if (mevent.y == LINES - 1) {
- ch = IncSet_synthesizeEvent(this->inc, mevent.x);
+ if (mevent.bstate & BUTTON1_RELEASED) {
+ if (mevent.y >= panel->y && mevent.y < LINES - 1) {
+ Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV - 1);
+ ch = 0;
+ } else if (mevent.y == LINES - 1) {
+ ch = IncSet_synthesizeEvent(this->inc, mevent.x);
+ }
+ }
+ #if NCURSES_MOUSE_VERSION > 1
+ else if (mevent.bstate & BUTTON4_PRESSED) {
+ ch = KEY_WHEELUP;
+ } else if (mevent.bstate & BUTTON5_PRESSED) {
+ ch = KEY_WHEELDOWN;
}
+ #endif
}
}
+#endif
if (this->inc->active) {
IncSet_handleKey(this->inc, ch, panel, IncSet_getListItemValue, this->lines);
continue;
}
- switch(ch) {
+ switch (ch) {
case ERR:
continue;
case KEY_F(3):
@@ -135,8 +142,10 @@ void InfoScreen_run(InfoScreen* this) {
break;
case KEY_F(5):
clear();
- if (As_InfoScreen(this)->scan)
+ if (As_InfoScreen(this)->scan) {
+ Vector_prune(this->lines);
InfoScreen_scan(this);
+ }
InfoScreen_draw(this);
break;
@@ -151,8 +160,10 @@ void InfoScreen_run(InfoScreen* this) {
break;
case KEY_RESIZE:
Panel_resize(panel, COLS, LINES - 2);
- if (As_InfoScreen(this)->scan)
+ if (As_InfoScreen(this)->scan) {
+ Vector_prune(this->lines);
InfoScreen_scan(this);
+ }
InfoScreen_draw(this);
break;
diff --git a/InfoScreen.h b/InfoScreen.h
index 0d80367..d7497be 100644
--- a/InfoScreen.h
+++ b/InfoScreen.h
@@ -16,7 +16,6 @@ typedef struct InfoScreen_ {
Object super;
const Process* process;
Panel* display;
- FunctionBar* bar;
IncSet* inc;
Vector* lines;
} InfoScreen;
diff --git a/ListItem.c b/ListItem.c
index c3c1a7d..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,33 +18,35 @@ 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);
if (this->moving) {
- RichString_write(out, CRT_colors[DEFAULT_COLOR],
+ RichString_writeWide(out, CRT_colors[DEFAULT_COLOR],
#ifdef HAVE_LIBNCURSESW
- CRT_utf8 ? "↕ " :
+ CRT_utf8 ? "↕ " :
#endif
- "+ ");
- } else {
- RichString_prune(out);
+ "+ ");
}
- RichString_append(out, CRT_colors[DEFAULT_COLOR], this->value);
+ 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;
}
@@ -57,7 +59,7 @@ void ListItem_append(ListItem* this, const char* text) {
this->value[newLen] = '\0';
}
-static long 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 87a7c07..b2c3e06 100644
--- a/ListItem.h
+++ b/ListItem.h
@@ -3,7 +3,7 @@
/*
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.
*/
@@ -11,6 +11,7 @@ in the source distribution for its full text.
#include "Object.h"
+
typedef struct ListItem_ {
Object super;
char* value;
@@ -20,10 +21,18 @@ typedef struct ListItem_ {
extern const ObjectClass ListItem_class;
+void ListItem_delete(Object* cast);
+
+void ListItem_display(const Object* cast, RichString* out);
+
+void ListItem_init(ListItem* this, const char* value, int key);
+
ListItem* ListItem_new(const char* value, int key);
void ListItem_append(ListItem* this, const char* text);
+int ListItem_compare(const void* cast1, const void* cast2);
+
static inline const char* ListItem_getRef(const ListItem* this) {
return this->value;
}
diff --git a/LoadAverageMeter.c b/LoadAverageMeter.c
index d5424cd..3fe3d90 100644
--- a/LoadAverageMeter.c
+++ b/LoadAverageMeter.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -10,6 +10,7 @@ in the source distribution for its full text.
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
+#include "ProcessList.h"
#include "RichString.h"
#include "XUtils.h"
@@ -24,36 +25,78 @@ static const int LoadMeter_attributes[] = {
LOAD
};
-static void LoadAverageMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static const int OK_attributes[] = {
+ METER_VALUE_OK
+};
+
+static const int Medium_attributes[] = {
+ METER_VALUE_WARN
+};
+
+static const int High_attributes[] = {
+ METER_VALUE_ERROR
+};
+
+static void LoadAverageMeter_updateValues(Meter* this) {
Platform_getLoadAverage(&this->values[0], &this->values[1], &this->values[2]);
- xSnprintf(buffer, size, "%.2f/%.2f/%.2f", this->values[0], this->values[1], this->values[2]);
+
+ // only show bar for 1min value
+ this->curItems = 1;
+
+ // change bar color and total based on value
+ if (this->values[0] < 1.0) {
+ this->curAttributes = OK_attributes;
+ this->total = 1.0;
+ } else if (this->values[0] < this->pl->activeCPUs) {
+ this->curAttributes = Medium_attributes;
+ this->total = this->pl->activeCPUs;
+ } else {
+ this->curAttributes = High_attributes;
+ this->total = 2 * this->pl->activeCPUs;
+ }
+
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.2f/%.2f/%.2f", this->values[0], this->values[1], this->values[2]);
}
static void LoadAverageMeter_display(const Object* cast, RichString* out) {
const Meter* this = (const Meter*)cast;
char buffer[20];
- xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[0]);
- RichString_write(out, CRT_colors[LOAD_AVERAGE_ONE], buffer);
- xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[1]);
- RichString_append(out, CRT_colors[LOAD_AVERAGE_FIVE], buffer);
- xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[2]);
- RichString_append(out, CRT_colors[LOAD_AVERAGE_FIFTEEN], buffer);
+ int len;
+
+ len = xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[0]);
+ RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_ONE], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[1]);
+ RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_FIVE], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[2]);
+ RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_FIFTEEN], buffer, len);
}
-static void LoadMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void LoadMeter_updateValues(Meter* this) {
double five, fifteen;
Platform_getLoadAverage(&this->values[0], &five, &fifteen);
- if (this->values[0] > this->total) {
- this->total = this->values[0];
+
+ // change bar color and total based on value
+ if (this->values[0] < 1.0) {
+ this->curAttributes = OK_attributes;
+ this->total = 1.0;
+ } else if (this->values[0] < this->pl->activeCPUs) {
+ this->curAttributes = Medium_attributes;
+ this->total = this->pl->activeCPUs;
+ } else {
+ this->curAttributes = High_attributes;
+ this->total = 2 * this->pl->activeCPUs;
}
- xSnprintf(buffer, size, "%.2f", this->values[0]);
+
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.2f", this->values[0]);
}
static void LoadMeter_display(const Object* cast, RichString* out) {
const Meter* this = (const Meter*)cast;
char buffer[20];
- xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[0]);
- RichString_write(out, CRT_colors[LOAD], buffer);
+ int len;
+
+ len = xSnprintf(buffer, sizeof(buffer), "%.2f ", this->values[0]);
+ RichString_appendnAscii(out, CRT_colors[LOAD], buffer, len);
}
const MeterClass LoadAverageMeter_class = {
diff --git a/LoadAverageMeter.h b/LoadAverageMeter.h
index 776c8bf..a34a4a3 100644
--- a/LoadAverageMeter.h
+++ b/LoadAverageMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass LoadAverageMeter_class;
extern const MeterClass LoadMeter_class;
diff --git a/Macros.h b/Macros.h
index 64aaefa..5e8891a 100644
--- a/Macros.h
+++ b/Macros.h
@@ -4,27 +4,31 @@
#include <assert.h> // IWYU pragma: keep
#ifndef MINIMUM
-#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
+#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAXIMUM
-#define MAXIMUM(a, b) ((a) > (b) ? (a) : (b))
+#define MAXIMUM(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef CLAMP
-#define CLAMP(x, low, high) (assert((low) <= (high)), ((x) > (high)) ? (high) : MAXIMUM(x, low))
+#define CLAMP(x, low, high) (assert((low) <= (high)), ((x) > (high)) ? (high) : MAXIMUM(x, low))
#endif
#ifndef ARRAYSIZE
-#define ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
#ifndef SPACESHIP_NUMBER
-#define SPACESHIP_NUMBER(a, b) (((a) > (b)) - ((a) < (b)))
+#define SPACESHIP_NUMBER(a, b) (((a) > (b)) - ((a) < (b)))
#endif
#ifndef SPACESHIP_NULLSTR
-#define SPACESHIP_NULLSTR(a, b) strcmp((a) ? (a) : "", (b) ? (b) : "")
+#define SPACESHIP_NULLSTR(a, b) strcmp((a) ? (a) : "", (b) ? (b) : "")
+#endif
+
+#ifndef SPACESHIP_DEFAULTSTR
+#define SPACESHIP_DEFAULTSTR(a, b, s) strcmp((a) ? (a) : (s), (b) ? (b) : (s))
#endif
#ifdef __GNUC__ // defined by GCC and Clang
@@ -33,6 +37,7 @@
#define ATTR_NONNULL __attribute__((nonnull))
#define ATTR_NORETURN __attribute__((noreturn))
#define ATTR_UNUSED __attribute__((unused))
+#define ATTR_MALLOC __attribute__((malloc))
#else /* __GNUC__ */
@@ -40,13 +45,26 @@
#define ATTR_NONNULL
#define ATTR_NORETURN
#define ATTR_UNUSED
+#define ATTR_MALLOC
#endif /* __GNUC__ */
+#ifdef HAVE_ATTR_ALLOC_SIZE
+
+#define ATTR_ALLOC_SIZE1(a) __attribute__((alloc_size (a)))
+#define ATTR_ALLOC_SIZE2(a, b) __attribute__((alloc_size (a, b)))
+
+#else
+
+#define ATTR_ALLOC_SIZE1(a)
+#define ATTR_ALLOC_SIZE2(a, b)
+
+#endif /* HAVE_ATTR_ALLOC_SIZE */
+
// ignore casts discarding const specifier, e.g.
// const char [] -> char * / void *
// const char *[2]' -> char *const *
-#ifdef __clang__
+#if defined(__clang__)
#define IGNORE_WCASTQUAL_BEGIN _Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wcast-qual\"")
#define IGNORE_WCASTQUAL_END _Pragma("clang diagnostic pop")
@@ -59,4 +77,9 @@
#define IGNORE_WCASTQUAL_END
#endif
+/* This subtraction is used by Linux / NetBSD / OpenBSD for calculation of CPU usage items. */
+static inline unsigned long long saturatingSub(unsigned long long a, unsigned long long b) {
+ return a > b ? a - b : 0;
+}
+
#endif
diff --git a/MainPanel.c b/MainPanel.c
index 5e3a54c..89b4e7d 100644
--- a/MainPanel.c
+++ b/MainPanel.c
@@ -2,7 +2,7 @@
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.
*/
@@ -21,18 +21,20 @@ in the source distribution for its full text.
#include "XUtils.h"
-static const char* const MainFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL};
+static const char* const MainFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL};
+static const char* const MainFunctions_ro[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", " ", " ", " ", "Quit ", NULL};
-void MainPanel_updateTreeFunctions(MainPanel* this, bool mode) {
+void MainPanel_updateLabels(MainPanel* this, bool list, bool filter) {
FunctionBar* bar = MainPanel_getFunctionBar(this);
- FunctionBar_setLabel(bar, KEY_F(5), mode ? "Sorted" : "Tree ");
+ FunctionBar_setLabel(bar, KEY_F(5), list ? "List " : "Tree ");
+ FunctionBar_setLabel(bar, KEY_F(4), filter ? "FILTER" : "Filter");
}
-void MainPanel_pidSearch(MainPanel* this, int ch) {
+static void MainPanel_pidSearch(MainPanel* this, int ch) {
Panel* super = (Panel*) this;
pid_t pid = ch - 48 + this->pidSearch;
for (int i = 0; i < Panel_size(super); i++) {
- Process* p = (Process*) Panel_get(super, i);
+ const Process* p = (const Process*) Panel_get(super, i);
if (p && p->pid == pid) {
Panel_setSelected(super, i);
break;
@@ -44,6 +46,11 @@ void MainPanel_pidSearch(MainPanel* this, int ch) {
}
}
+static const char* MainPanel_getValue(Panel* this, int i) {
+ const Process* p = (const Process*) Panel_get(this, i);
+ return Process_getCommand(p);
+}
+
static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
MainPanel* this = (MainPanel*) super;
@@ -51,24 +58,45 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
Htop_Reaction reaction = HTOP_OK;
- if (ch != ERR)
+ /* Let supervising ScreenManager handle resize */
+ if (ch == KEY_RESIZE)
+ return IGNORED;
+
+ /* reset on every normal key */
+ bool needReset = ch != ERR;
+ #ifdef HAVE_GETMOUSE
+ /* except mouse events while mouse support is disabled */
+ if (!(ch != KEY_MOUSE || this->state->settings->enableMouse))
+ needReset = false;
+ #endif
+ if (needReset)
this->state->hideProcessSelection = false;
+ Settings* settings = this->state->settings;
+ ScreenSettings* ss = settings->ss;
+
if (EVENT_IS_HEADER_CLICK(ch)) {
int x = EVENT_HEADER_CLICK_GET_X(ch);
const ProcessList* pl = this->state->pl;
- Settings* settings = this->state->settings;
int hx = super->scrollH + x + 1;
ProcessField field = ProcessList_keyAt(pl, hx);
- if (field == settings->sortKey) {
- Settings_invertSortOrder(settings);
+ if (ss->treeView && ss->treeViewAlwaysByPID) {
+ ss->treeView = false;
+ ss->direction = 1;
+ reaction |= Action_setSortKey(settings, field);
+ } else if (field == ScreenSettings_getActiveSortKey(ss)) {
+ ScreenSettings_invertSortOrder(ss);
} else {
reaction |= Action_setSortKey(settings, field);
}
reaction |= HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_SAVE_SETTINGS;
result = HANDLED;
+ } else if (EVENT_IS_SCREEN_TAB_CLICK(ch)) {
+ int x = EVENT_SCREEN_TAB_GET_X(ch);
+ reaction |= Action_setScreenTab(settings, x);
+ result = HANDLED;
} else if (ch != ERR && this->inc->active) {
- bool filterChanged = IncSet_handleKey(this->inc, ch, super, (IncMode_GetPanelValue) MainPanel_getValue, NULL);
+ bool filterChanged = IncSet_handleKey(this->inc, ch, super, MainPanel_getValue, NULL);
if (filterChanged) {
this->state->pl->incFilter = IncSet_filter(this->inc);
reaction = HTOP_REFRESH | HTOP_REDRAW_BAR;
@@ -94,48 +122,42 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
}
}
- if (reaction & HTOP_REDRAW_BAR) {
- MainPanel_updateTreeFunctions(this, this->state->settings->treeView);
- IncSet_drawBar(this->inc);
- if (this->state->pauseProcessUpdate) {
- FunctionBar_append("PAUSED", CRT_colors[PAUSED]);
- }
+ if ((reaction & HTOP_REDRAW_BAR) == HTOP_REDRAW_BAR) {
+ MainPanel_updateLabels(this, settings->ss->treeView, this->state->pl->incFilter);
}
- if (reaction & HTOP_UPDATE_PANELHDR) {
- ProcessList_printHeader(this->state->pl, Panel_getHeader(super));
+ if ((reaction & HTOP_RESIZE) == HTOP_RESIZE) {
+ result |= RESIZE;
}
- if (reaction & HTOP_REFRESH) {
+ if ((reaction & HTOP_UPDATE_PANELHDR) == HTOP_UPDATE_PANELHDR) {
result |= REDRAW;
}
- if (reaction & HTOP_RECALCULATE) {
+ if ((reaction & HTOP_REFRESH) == HTOP_REFRESH) {
+ result |= REFRESH;
+ }
+ if ((reaction & HTOP_RECALCULATE) == HTOP_RECALCULATE) {
result |= RESCAN;
}
- if (reaction & HTOP_SAVE_SETTINGS) {
+ if ((reaction & HTOP_SAVE_SETTINGS) == HTOP_SAVE_SETTINGS) {
this->state->settings->changed = true;
}
- if (reaction & HTOP_QUIT) {
+ if ((reaction & HTOP_QUIT) == HTOP_QUIT) {
return BREAK_LOOP;
}
- if (!(reaction & HTOP_KEEP_FOLLOWING)) {
+ if ((reaction & HTOP_KEEP_FOLLOWING) != HTOP_KEEP_FOLLOWING) {
this->state->pl->following = -1;
- Panel_setSelectionColor(super, CRT_colors[PANEL_SELECTION_FOCUS]);
+ Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
}
return result;
}
int MainPanel_selectedPid(MainPanel* this) {
- Process* p = (Process*) Panel_getSelected((Panel*)this);
+ const Process* p = (const Process*) Panel_getSelected((Panel*)this);
if (p) {
return p->pid;
}
return -1;
}
-const char* MainPanel_getValue(MainPanel* this, int i) {
- Process* p = (Process*) Panel_get((Panel*)this, i);
- return Process_getCommand(p);
-}
-
bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged) {
Panel* super = (Panel*) this;
bool ok = true;
@@ -160,17 +182,37 @@ bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Ar
return ok;
}
+static void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) {
+ MainPanel* this = (MainPanel*) super;
+
+ // Do not hide active search and filter bar.
+ if (hideFunctionBar && !this->inc->active)
+ return;
+
+ IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
+ if (this->state->pauseProcessUpdate) {
+ FunctionBar_append("PAUSED", CRT_colors[PAUSED]);
+ }
+}
+
+static void MainPanel_printHeader(Panel* super) {
+ MainPanel* this = (MainPanel*) super;
+ ProcessList_printHeader(this->state->pl, &super->header);
+}
+
const PanelClass MainPanel_class = {
.super = {
.extends = Class(Panel),
.delete = MainPanel_delete
},
- .eventHandler = MainPanel_eventHandler
+ .eventHandler = MainPanel_eventHandler,
+ .drawFunctionBar = MainPanel_drawFunctionBar,
+ .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(MainFunctions, NULL, NULL));
+ Panel_init((Panel*) this, 1, 1, 1, 1, Class(Process), false, FunctionBar_new(Settings_isReadonly() ? MainFunctions_ro : MainFunctions, NULL, NULL));
this->keys = xCalloc(KEY_MAX, sizeof(Htop_Action));
this->inc = IncSet_new(MainPanel_getFunctionBar(this));
diff --git a/MainPanel.h b/MainPanel.h
index 03a1aff..bd22acd 100644
--- a/MainPanel.h
+++ b/MainPanel.h
@@ -4,7 +4,7 @@
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.
*/
@@ -32,14 +32,11 @@ typedef bool(*MainPanel_ForeachProcessFn)(Process*, Arg);
#define MainPanel_getFunctionBar(this_) (((Panel*)(this_))->defaultBar)
-void MainPanel_updateTreeFunctions(MainPanel* this, bool mode);
-
-void MainPanel_pidSearch(MainPanel* this, int ch);
+// 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);
-const char* MainPanel_getValue(MainPanel* this, int i);
-
bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged);
extern const PanelClass MainPanel_class;
diff --git a/Makefile.am b/Makefile.am
index 09790fa..8af1864 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,10 +1,22 @@
-AUTOMAKE_OPTIONS = subdir-objects
-
+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) htop.desktop htop.png htop.svg \
-install-sh autogen.sh missing
+EXTRA_DIST = \
+ $(dist_man_MANS) \
+ autogen.sh \
+ htop.desktop \
+ htop.png \
+ htop.svg \
+ build-aux/compile \
+ build-aux/depcomp \
+ build-aux/install-sh \
+ build-aux/missing
applicationsdir = $(datadir)/applications
applications_DATA = htop.desktop
pixmapdir = $(datadir)/pixmaps
@@ -12,7 +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 += -pedantic -std=c99 -D_XOPEN_SOURCE_EXTENDED -DSYSCONFDIR="\"$(sysconfdir)\"" -I"$(top_srcdir)/$(my_htop_platform)"
AM_LDFLAGS =
myhtopsources = \
@@ -26,6 +38,7 @@ myhtopsources = \
ClockMeter.c \
ColorsPanel.c \
ColumnsPanel.c \
+ CommandLine.c \
CommandScreen.c \
Compat.c \
CPUMeter.c \
@@ -34,18 +47,21 @@ myhtopsources = \
DateTimeMeter.c \
DiskIOMeter.c \
DisplayOptionsPanel.c \
+ DynamicColumn.c \
+ DynamicMeter.c \
EnvScreen.c \
FunctionBar.c \
Hashtable.c \
Header.c \
+ HeaderOptionsPanel.c \
HostnameMeter.c \
- htop.c \
IncSet.c \
InfoScreen.c \
ListItem.c \
LoadAverageMeter.c \
MainPanel.c \
MemoryMeter.c \
+ MemorySwapMeter.c \
Meter.c \
MetersPanel.c \
NetworkIOMeter.c \
@@ -58,9 +74,11 @@ myhtopsources = \
ProcessLocksScreen.c \
RichString.c \
ScreenManager.c \
+ ScreensPanel.c \
Settings.c \
SignalsPanel.c \
SwapMeter.c \
+ SysArchMeter.c \
TasksMeter.c \
TraceScreen.c \
UptimeMeter.c \
@@ -81,16 +99,21 @@ myhtopheaders = \
ClockMeter.h \
ColorsPanel.h \
ColumnsPanel.h \
+ CommandLine.h \
CommandScreen.h \
Compat.h \
DateMeter.h \
DateTimeMeter.h \
DiskIOMeter.h \
DisplayOptionsPanel.h \
+ DynamicColumn.h \
+ DynamicMeter.h \
EnvScreen.h \
FunctionBar.h \
Hashtable.h \
Header.h \
+ HeaderLayout.h \
+ HeaderOptionsPanel.h \
HostnameMeter.h \
IncSet.h \
InfoScreen.h \
@@ -99,6 +122,7 @@ myhtopheaders = \
Macros.h \
MainPanel.h \
MemoryMeter.h \
+ MemorySwapMeter.h \
Meter.h \
MetersPanel.h \
NetworkIOMeter.h \
@@ -112,9 +136,11 @@ myhtopheaders = \
ProvideCurses.h \
RichString.h \
ScreenManager.h \
+ ScreensPanel.h \
Settings.h \
SignalsPanel.h \
SwapMeter.h \
+ SysArchMeter.h \
TasksMeter.h \
TraceScreen.h \
UptimeMeter.h \
@@ -126,6 +152,11 @@ myhtopheaders = \
# -----
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 \
@@ -133,6 +164,7 @@ linux_platform_headers = \
linux/LinuxProcessList.h \
linux/Platform.h \
linux/PressureStallMeter.h \
+ linux/ProcessField.h \
linux/SELinuxMeter.h \
linux/SystemdMeter.h \
linux/ZramMeter.h \
@@ -141,9 +173,12 @@ linux_platform_headers = \
zfs/ZfsArcStats.h \
zfs/ZfsCompressedArcMeter.h
-if HTOP_LINUX
-AM_LDFLAGS += -rdynamic
-myhtopplatsources = \
+linux_platform_sources = \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c \
+ linux/CGroupUtils.c \
+ linux/HugePageMeter.c \
linux/IOPriorityPanel.c \
linux/LibSensors.c \
linux/LinuxProcess.c \
@@ -154,121 +189,246 @@ myhtopplatsources = \
linux/SystemdMeter.c \
linux/ZramMeter.c \
zfs/ZfsArcMeter.c \
- zfs/ZfsArcStats.c \
zfs/ZfsCompressedArcMeter.c
+if HTOP_LINUX
+AM_LDFLAGS += -rdynamic
myhtopplatheaders = $(linux_platform_headers)
+myhtopplatsources = $(linux_platform_sources)
endif
# FreeBSD
# -------
freebsd_platform_headers = \
- freebsd/Platform.h \
freebsd/FreeBSDProcessList.h \
freebsd/FreeBSDProcess.h \
+ freebsd/Platform.h \
+ freebsd/ProcessField.h \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/openzfs_sysctl.h \
+ generic/uname.h \
zfs/ZfsArcMeter.h \
- zfs/ZfsCompressedArcMeter.h \
zfs/ZfsArcStats.h \
- zfs/openzfs_sysctl.h
+ zfs/ZfsCompressedArcMeter.h
-if HTOP_FREEBSD
-myhtopplatsources = freebsd/Platform.c freebsd/FreeBSDProcessList.c \
-freebsd/FreeBSDProcess.c \
-zfs/ZfsArcMeter.c zfs/ZfsCompressedArcMeter.c zfs/ZfsArcStats.c zfs/openzfs_sysctl.c
+freebsd_platform_sources = \
+ freebsd/Platform.c \
+ freebsd/FreeBSDProcessList.c \
+ freebsd/FreeBSDProcess.c \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/openzfs_sysctl.c \
+ generic/uname.c \
+ zfs/ZfsArcMeter.c \
+ zfs/ZfsCompressedArcMeter.c
+if HTOP_FREEBSD
myhtopplatheaders = $(freebsd_platform_headers)
+myhtopplatsources = $(freebsd_platform_sources)
endif
# DragonFlyBSD
# ------------
dragonflybsd_platform_headers = \
- dragonflybsd/Platform.h \
dragonflybsd/DragonFlyBSDProcessList.h \
- dragonflybsd/DragonFlyBSDProcess.h
+ dragonflybsd/DragonFlyBSDProcess.h \
+ dragonflybsd/Platform.h \
+ dragonflybsd/ProcessField.h \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h
+
+dragonflybsd_platform_sources = \
+ dragonflybsd/DragonFlyBSDProcessList.c \
+ dragonflybsd/DragonFlyBSDProcess.c \
+ dragonflybsd/Platform.c \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c
if HTOP_DRAGONFLYBSD
-AM_LDFLAGS += -lkvm -lkinfo
-myhtopplatsources = dragonflybsd/Platform.c dragonflybsd/DragonFlyBSDProcessList.c \
-dragonflybsd/DragonFlyBSDProcess.c
-
myhtopplatheaders = $(dragonflybsd_platform_headers)
+myhtopplatsources = $(dragonflybsd_platform_sources)
+endif
+
+# NetBSD
+# -------
+
+netbsd_platform_headers = \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h \
+ netbsd/Platform.h \
+ netbsd/ProcessField.h \
+ netbsd/NetBSDProcess.h \
+ netbsd/NetBSDProcessList.h
+
+netbsd_platform_sources = \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c \
+ netbsd/Platform.c \
+ netbsd/NetBSDProcess.c \
+ netbsd/NetBSDProcessList.c
+
+if HTOP_NETBSD
+myhtopplatheaders = $(netbsd_platform_headers)
+myhtopplatsources = $(netbsd_platform_sources)
endif
# OpenBSD
# -------
openbsd_platform_headers = \
- openbsd/Platform.h \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h \
openbsd/OpenBSDProcessList.h \
- openbsd/OpenBSDProcess.h
+ openbsd/OpenBSDProcess.h \
+ openbsd/Platform.h \
+ openbsd/ProcessField.h
-if HTOP_OPENBSD
-myhtopplatsources = openbsd/Platform.c openbsd/OpenBSDProcessList.c \
-openbsd/OpenBSDProcess.c
+openbsd_platform_sources = \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c \
+ openbsd/OpenBSDProcessList.c \
+ openbsd/OpenBSDProcess.c \
+ openbsd/Platform.c
+if HTOP_OPENBSD
myhtopplatheaders = $(openbsd_platform_headers)
+myhtopplatsources = $(openbsd_platform_sources)
endif
# Darwin
# ------
darwin_platform_headers = \
- darwin/Platform.h \
darwin/DarwinProcess.h \
darwin/DarwinProcessList.h \
+ darwin/Platform.h \
+ darwin/PlatformHelpers.h \
+ darwin/ProcessField.h \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/openzfs_sysctl.h \
+ generic/uname.h \
zfs/ZfsArcMeter.h \
- zfs/ZfsCompressedArcMeter.h \
zfs/ZfsArcStats.h \
- zfs/openzfs_sysctl.h
+ zfs/ZfsCompressedArcMeter.h
+
+darwin_platform_sources = \
+ darwin/Platform.c \
+ darwin/PlatformHelpers.c \
+ darwin/DarwinProcess.c \
+ darwin/DarwinProcessList.c \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/openzfs_sysctl.c \
+ generic/uname.c \
+ zfs/ZfsArcMeter.c \
+ zfs/ZfsCompressedArcMeter.c
if HTOP_DARWIN
AM_LDFLAGS += -framework IOKit -framework CoreFoundation
-myhtopplatsources = darwin/Platform.c darwin/DarwinProcess.c \
-darwin/DarwinProcessList.c \
-zfs/ZfsArcMeter.c zfs/ZfsCompressedArcMeter.c zfs/ZfsArcStats.c zfs/openzfs_sysctl.c
-
myhtopplatheaders = $(darwin_platform_headers)
+myhtopplatsources = $(darwin_platform_sources)
endif
# Solaris
# -------
solaris_platform_headers = \
+ generic/gettime.h \
+ generic/hostname.h \
+ generic/uname.h \
+ solaris/ProcessField.h \
solaris/Platform.h \
solaris/SolarisProcess.h \
solaris/SolarisProcessList.h \
zfs/ZfsArcMeter.h \
- zfs/ZfsCompressedArcMeter.h \
- zfs/ZfsArcStats.h
+ zfs/ZfsArcStats.h \
+ zfs/ZfsCompressedArcMeter.h
-if HTOP_SOLARIS
-myhtopplatsources = solaris/Platform.c \
-solaris/SolarisProcess.c solaris/SolarisProcessList.c \
-zfs/ZfsArcMeter.c zfs/ZfsCompressedArcMeter.c zfs/ZfsArcStats.c
+solaris_platform_sources = \
+ generic/gettime.c \
+ generic/hostname.c \
+ generic/uname.c \
+ solaris/Platform.c \
+ solaris/SolarisProcess.c \
+ solaris/SolarisProcessList.c \
+ zfs/ZfsArcMeter.c \
+ zfs/ZfsCompressedArcMeter.c
+if HTOP_SOLARIS
myhtopplatheaders = $(solaris_platform_headers)
+myhtopplatsources = $(solaris_platform_sources)
+endif
+
+# Performance Co-Pilot (PCP)
+# --------------------------
+
+pcp_platform_headers = \
+ linux/PressureStallMeter.h \
+ linux/ZramMeter.h \
+ linux/ZramStats.h \
+ pcp/PCPDynamicColumn.h \
+ pcp/PCPDynamicMeter.h \
+ pcp/PCPMetric.h \
+ pcp/PCPProcess.h \
+ pcp/PCPProcessList.h \
+ pcp/Platform.h \
+ pcp/ProcessField.h \
+ zfs/ZfsArcMeter.h \
+ zfs/ZfsArcStats.h \
+ zfs/ZfsCompressedArcMeter.h
+
+pcp_platform_sources = \
+ linux/PressureStallMeter.c \
+ linux/ZramMeter.c \
+ pcp/PCPDynamicColumn.c \
+ pcp/PCPDynamicMeter.c \
+ pcp/PCPMetric.c \
+ pcp/PCPProcess.c \
+ pcp/PCPProcessList.c \
+ pcp/Platform.c \
+ zfs/ZfsArcMeter.c \
+ zfs/ZfsCompressedArcMeter.c
+
+if HTOP_PCP
+myhtopplatheaders = $(pcp_platform_headers)
+myhtopplatsources = $(pcp_platform_sources)
+pcp_htop_SOURCES = $(myhtopplatprogram) $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
endif
# Unsupported
# -----------
unsupported_platform_headers = \
+ generic/gettime.h \
unsupported/Platform.h \
+ unsupported/ProcessField.h \
unsupported/UnsupportedProcess.h \
unsupported/UnsupportedProcessList.h
-if HTOP_UNSUPPORTED
-myhtopplatsources = unsupported/Platform.c \
-unsupported/UnsupportedProcess.c unsupported/UnsupportedProcessList.c
+unsupported_platform_sources = \
+ generic/gettime.c \
+ unsupported/Platform.c \
+ unsupported/UnsupportedProcess.c \
+ unsupported/UnsupportedProcessList.c
+if HTOP_UNSUPPORTED
+myhtopplatsources = $(unsupported_platform_sources)
myhtopplatheaders = $(unsupported_platform_headers)
endif
# ----
-htop_SOURCES = $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
+htop_SOURCES = $(myhtopplatprogram) $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)
nodist_htop_SOURCES = config.h
target:
@@ -278,10 +438,10 @@ profile:
$(MAKE) all AM_CPPFLAGS="-pg -O2 -DNDEBUG"
debug:
- $(MAKE) all AM_CPPFLAGS="-ggdb -DDEBUG"
+ $(MAKE) all AM_CPPFLAGS="-ggdb3 -Og" CFLAGS="`printf ' %s ' "$(CFLAGS)"|sed -E 's#[[:space:]]-O[^[:space:]]+[[:space:]]# #g'` -ggdb3 -Og"
coverage:
- $(MAKE) all AM_CPPFLAGS="-fprofile-arcs -ftest-coverage -DDEBUG" AM_LDFLAGS="-lgcov"
+ $(MAKE) all AM_CPPFLAGS="-fprofile-arcs -ftest-coverage" AM_LDFLAGS="-lgcov"
cppcheck:
cppcheck -q -v . --enable=all -DHAVE_OPENVZ
diff --git a/MemoryMeter.c b/MemoryMeter.c
index 9830bf5..ac01dfe 100644
--- a/MemoryMeter.c
+++ b/MemoryMeter.c
@@ -1,12 +1,15 @@
/*
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 "MemoryMeter.h"
+#include <math.h>
+#include <stddef.h>
+
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
@@ -16,14 +19,24 @@ in the source distribution for its full text.
static const int MemoryMeter_attributes[] = {
MEMORY_USED,
MEMORY_BUFFERS,
+ MEMORY_SHARED,
MEMORY_CACHE
};
-static void MemoryMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void MemoryMeter_updateValues(Meter* this) {
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
int written;
+
+ /* shared and available memory are not supported on all platforms */
+ this->values[MEMORY_METER_SHARED] = NAN;
+ this->values[MEMORY_METER_AVAILABLE] = NAN;
Platform_setMemoryValues(this);
- written = Meter_humanUnit(buffer, this->values[0], size);
+ /* Do not print available memory in bar mode */
+ this->curItems = 4;
+
+ written = Meter_humanUnit(buffer, this->values[MEMORY_METER_USED], size);
METER_BUFFER_CHECK(buffer, size, written);
METER_BUFFER_APPEND_CHR(buffer, size, '/');
@@ -34,18 +47,36 @@ static void MemoryMeter_updateValues(Meter* this, char* buffer, size_t size) {
static void MemoryMeter_display(const Object* cast, RichString* out) {
char buffer[50];
const Meter* this = (const Meter*)cast;
- RichString_write(out, CRT_colors[METER_TEXT], ":");
- Meter_humanUnit(buffer, this->total, 50);
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
- Meter_humanUnit(buffer, this->values[0], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " used:");
- RichString_append(out, CRT_colors[MEMORY_USED], buffer);
- Meter_humanUnit(buffer, this->values[1], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " buffers:");
- RichString_append(out, CRT_colors[MEMORY_BUFFERS_TEXT], buffer);
- Meter_humanUnit(buffer, this->values[2], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " cache:");
- RichString_append(out, CRT_colors[MEMORY_CACHE], buffer);
+
+ 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[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[MEMORY_METER_BUFFERS], 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[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[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[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);
+ }
}
const MeterClass MemoryMeter_class = {
@@ -56,7 +87,7 @@ const MeterClass MemoryMeter_class = {
},
.updateValues = MemoryMeter_updateValues,
.defaultMode = BAR_METERMODE,
- .maxItems = 3,
+ .maxItems = 5,
.total = 100.0,
.attributes = MemoryMeter_attributes,
.name = "Memory",
diff --git a/MemoryMeter.h b/MemoryMeter.h
index d299483..cc2a160 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_BUFFERS = 1,
+ MEMORY_METER_SHARED = 2,
+ MEMORY_METER_CACHE = 3,
+ MEMORY_METER_AVAILABLE = 4,
+ MEMORY_METER_ITEMCOUNT = 5, // number of entries in this enum
+} MemoryMeterValues;
+
extern const MeterClass MemoryMeter_class;
#endif
diff --git a/MemorySwapMeter.c b/MemorySwapMeter.c
new file mode 100644
index 0000000..46b2a6b
--- /dev/null
+++ b/MemorySwapMeter.c
@@ -0,0 +1,107 @@
+/*
+htop - MemorySwapMeter.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "MemorySwapMeter.h"
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "Macros.h"
+#include "MemoryMeter.h"
+#include "Object.h"
+#include "SwapMeter.h"
+#include "XUtils.h"
+
+
+typedef struct MemorySwapMeterData_ {
+ Meter* memoryMeter;
+ Meter* swapMeter;
+} MemorySwapMeterData;
+
+static void MemorySwapMeter_updateValues(Meter* this) {
+ MemorySwapMeterData* data = this->meterData;
+
+ Meter_updateValues(data->memoryMeter);
+ Meter_updateValues(data->swapMeter);
+}
+
+static void MemorySwapMeter_draw(Meter* this, int x, int y, int w) {
+ MemorySwapMeterData* data = this->meterData;
+
+ /* Use the same width for each sub meter to align with CPU meter */
+ const int colwidth = w / 2;
+ const int diff = w - colwidth * 2;
+
+ assert(data->memoryMeter->draw);
+ data->memoryMeter->draw(data->memoryMeter, x, y, colwidth);
+ assert(data->swapMeter->draw);
+ data->swapMeter->draw(data->swapMeter, x + colwidth + diff, y, colwidth);
+}
+
+static void MemorySwapMeter_init(Meter* this) {
+ MemorySwapMeterData* data = this->meterData;
+
+ if (!data) {
+ data = this->meterData = xMalloc(sizeof(MemorySwapMeterData));
+ data->memoryMeter = NULL;
+ data->swapMeter = NULL;
+ }
+
+ if (!data->memoryMeter)
+ data->memoryMeter = Meter_new(this->pl, 0, (const MeterClass*) Class(MemoryMeter));
+ if (!data->swapMeter)
+ data->swapMeter = Meter_new(this->pl, 0, (const MeterClass*) Class(SwapMeter));
+
+ if (Meter_initFn(data->memoryMeter))
+ Meter_init(data->memoryMeter);
+ if (Meter_initFn(data->swapMeter))
+ Meter_init(data->swapMeter);
+
+ if (this->mode == 0)
+ this->mode = BAR_METERMODE;
+
+ this->h = MAXIMUM(Meter_modes[data->memoryMeter->mode]->h, Meter_modes[data->swapMeter->mode]->h);
+}
+
+static void MemorySwapMeter_updateMode(Meter* this, int mode) {
+ MemorySwapMeterData* data = this->meterData;
+
+ this->mode = mode;
+
+ Meter_setMode(data->memoryMeter, mode);
+ Meter_setMode(data->swapMeter, mode);
+
+ this->h = MAXIMUM(Meter_modes[data->memoryMeter->mode]->h, Meter_modes[data->swapMeter->mode]->h);
+}
+
+static void MemorySwapMeter_done(Meter* this) {
+ MemorySwapMeterData* data = this->meterData;
+
+ Meter_delete((Object*)data->swapMeter);
+ Meter_delete((Object*)data->memoryMeter);
+
+ free(data);
+}
+
+const MeterClass MemorySwapMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ },
+ .updateValues = MemorySwapMeter_updateValues,
+ .defaultMode = CUSTOM_METERMODE,
+ .isMultiColumn = true,
+ .name = "MemorySwap",
+ .uiName = "Memory & Swap",
+ .description = "Combined memory and swap usage",
+ .caption = "M&S",
+ .draw = MemorySwapMeter_draw,
+ .init = MemorySwapMeter_init,
+ .updateMode = MemorySwapMeter_updateMode,
+ .done = MemorySwapMeter_done
+};
diff --git a/MemorySwapMeter.h b/MemorySwapMeter.h
new file mode 100644
index 0000000..a98d5e6
--- /dev/null
+++ b/MemorySwapMeter.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_MemorySwapMeter
+#define HEADER_MemorySwapMeter
+/*
+htop - MemorySwapMeter.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Meter.h"
+
+
+extern const MeterClass MemorySwapMeter_class;
+
+#endif
diff --git a/Meter.c b/Meter.c
index 945911c..164c4d3 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.
*/
@@ -32,13 +32,14 @@ const MeterClass Meter_class = {
}
};
-Meter* Meter_new(const struct ProcessList_* pl, int param, const MeterClass* type) {
+Meter* Meter_new(const struct ProcessList_* pl, unsigned int param, const MeterClass* type) {
Meter* this = xCalloc(1, sizeof(Meter));
Object_setClass(this, type);
this->h = 1;
this->param = param;
this->pl = pl;
this->curItems = type->maxItems;
+ this->curAttributes = NULL;
this->values = type->maxItems ? xCalloc(type->maxItems, sizeof(double)) : NULL;
this->total = type->total;
this->caption = xStrdup(type->caption);
@@ -92,15 +93,14 @@ void Meter_delete(Object* cast) {
}
void Meter_setCaption(Meter* this, const char* caption) {
- free(this->caption);
- this->caption = xStrdup(caption);
+ free_and_xStrdup(&this->caption, caption);
}
-static inline void Meter_displayBuffer(const Meter* this, const char* buffer, RichString* out) {
+static inline void Meter_displayBuffer(const Meter* this, RichString* out) {
if (Object_displayFn(this)) {
Object_display(this, out);
} else {
- RichString_write(out, CRT_colors[Meter_attributes(this)[0]], buffer);
+ RichString_writeWide(out, CRT_colors[Meter_attributes(this)[0]], this->txtBuffer);
}
}
@@ -131,21 +131,20 @@ void Meter_setMode(Meter* this, int modeIndex) {
this->mode = modeIndex;
}
-ListItem* Meter_toListItem(Meter* this, bool moving) {
+ListItem* Meter_toListItem(const Meter* this, bool moving) {
char mode[20];
if (this->mode) {
xSnprintf(mode, sizeof(mode), " [%s]", Meter_modes[this->mode]->uiName);
} else {
mode[0] = '\0';
}
- char number[10];
- if (this->param > 0) {
- xSnprintf(number, sizeof(number), " %d", this->param);
- } else {
- number[0] = '\0';
- }
+ char name[32];
+ if (Meter_getUiNameFn(this))
+ Meter_getUiName(this, name, sizeof(name));
+ else
+ xSnprintf(name, sizeof(name), "%s", Meter_uiName(this));
char buffer[50];
- xSnprintf(buffer, sizeof(buffer), "%s%s%s", Meter_uiName(this), number, mode);
+ xSnprintf(buffer, sizeof(buffer), "%s%s", name, mode);
ListItem* li = ListItem_new(buffer, 0);
li->moving = moving;
return li;
@@ -154,19 +153,21 @@ ListItem* Meter_toListItem(Meter* this, bool moving) {
/* ---------- TextMeterMode ---------- */
static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
- char buffer[METER_BUFFER_LEN];
- Meter_updateValues(this, buffer, sizeof(buffer));
- (void) w;
-
+ const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
- mvaddstr(y, x, this->caption);
- int captionLen = strlen(this->caption);
- x += captionLen;
+ mvaddnstr(y, x, caption, w);
attrset(CRT_colors[RESET_COLOR]);
+
+ int captionLen = strlen(caption);
+ x += captionLen;
+ w -= captionLen;
+ if (w <= 0)
+ return;
+
RichString_begin(out);
- Meter_displayBuffer(this, buffer, &out);
- RichString_printVal(out, y, x);
- RichString_end(out);
+ Meter_displayBuffer(this, &out);
+ RichString_printoffnVal(out, y, x, 0, w);
+ RichString_delete(&out);
}
/* ---------- BarMeterMode ---------- */
@@ -174,35 +175,48 @@ static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
static const char BarMeterMode_characters[] = "|#*@$%&.";
static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
- char buffer[METER_BUFFER_LEN];
- Meter_updateValues(this, buffer, sizeof(buffer));
-
- w -= 2;
+ const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
int captionLen = 3;
- mvaddnstr(y, x, this->caption, captionLen);
+ mvaddnstr(y, x, caption, captionLen);
x += captionLen;
w -= captionLen;
attrset(CRT_colors[BAR_BORDER]);
mvaddch(y, x, '[');
- mvaddch(y, x + w, ']');
+ w--;
+ mvaddch(y, x + MAXIMUM(w, 0), ']');
+ w--;
attrset(CRT_colors[RESET_COLOR]);
- w--;
x++;
if (w < 1)
return;
// The text in the bar is right aligned;
- // calculate needed padding and generate leading spaces
- const int textLen = mbstowcs(NULL, buffer, 0);
- const int padding = CLAMP(w - textLen, 0, w);
-
+ // Pad with maximal spaces and then calculate needed starting position offset
RichString_begin(bar);
- RichString_appendChr(&bar, ' ', padding);
- RichString_append(&bar, 0, buffer);
- assert(RichString_sizeVal(bar) >= w);
+ RichString_appendChr(&bar, 0, ' ', w);
+ RichString_appendWide(&bar, 0, this->txtBuffer);
+ int startPos = RichString_sizeVal(bar) - w;
+ if (startPos > w) {
+ // Text is too large for bar
+ // Truncate meter text at a space character
+ for (int pos = 2 * w; pos > w; pos--) {
+ if (RichString_getCharVal(bar, pos) == ' ') {
+ while (pos > w && RichString_getCharVal(bar, pos - 1) == ' ')
+ pos--;
+ startPos = pos - w;
+ break;
+ }
+ }
+
+ // If still too large, print the start not the end
+ startPos = MINIMUM(startPos, w);
+ }
+ assert(startPos >= 0);
+ assert(startPos <= w);
+ assert(startPos + w <= RichString_sizeVal(bar));
int blockSizes[10];
@@ -220,11 +234,11 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
// (Control against invalid values)
nextOffset = CLAMP(nextOffset, 0, w);
for (int j = offset; j < nextOffset; j++)
- if (RichString_getCharVal(bar, j) == ' ') {
+ if (RichString_getCharVal(bar, startPos + j) == ' ') {
if (CRT_colorScheme == COLORSCHEME_MONOCHROME) {
- RichString_setChar(&bar, j, BarMeterMode_characters[i]);
+ RichString_setChar(&bar, startPos + j, BarMeterMode_characters[i]);
} else {
- RichString_setChar(&bar, j, '|');
+ RichString_setChar(&bar, startPos + j, '|');
}
}
offset = nextOffset;
@@ -233,17 +247,18 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
// ...then print the buffer.
offset = 0;
for (uint8_t i = 0; i < this->curItems; i++) {
- RichString_setAttrn(&bar, CRT_colors[Meter_attributes(this)[i]], offset, offset + blockSizes[i] - 1);
- RichString_printoffnVal(bar, y, x + offset, offset, blockSizes[i]);
+ int attr = this->curAttributes ? this->curAttributes[i] : Meter_attributes(this)[i];
+ RichString_setAttrn(&bar, CRT_colors[attr], startPos + offset, blockSizes[i]);
+ RichString_printoffnVal(bar, y, x + offset, startPos + offset, MINIMUM(blockSizes[i], w - offset));
offset += blockSizes[i];
offset = CLAMP(offset, 0, w);
}
if (offset < w) {
- RichString_setAttrn(&bar, CRT_colors[BAR_SHADOW], offset, w - 1);
- RichString_printoffnVal(bar, y, x + offset, offset, w - offset);
+ RichString_setAttrn(&bar, CRT_colors[BAR_SHADOW], startPos + offset, w - offset);
+ RichString_printoffnVal(bar, y, x + offset, startPos + offset, w - offset);
}
- RichString_end(bar);
+ RichString_delete(&bar);
move(y, x + w + 1);
attrset(CRT_colors[RESET_COLOR]);
@@ -271,17 +286,17 @@ static const char* const GraphMeterMode_dotsAscii[] = {
/*20*/":", /*21*/":", /*22*/":"
};
-static const char* const* GraphMeterMode_dots;
-static int GraphMeterMode_pixPerRow;
-
static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
+ const ProcessList* pl = this->pl;
if (!this->drawData) {
this->drawData = xCalloc(1, sizeof(GraphData));
}
GraphData* data = this->drawData;
- const int nValues = METER_BUFFER_LEN;
+ const int nValues = METER_GRAPHDATA_SIZE;
+ const char* const* GraphMeterMode_dots;
+ int GraphMeterMode_pixPerRow;
#ifdef HAVE_LIBNCURSESW
if (CRT_utf8) {
GraphMeterMode_dots = GraphMeterMode_dotsUtf8;
@@ -293,41 +308,38 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
GraphMeterMode_pixPerRow = PIXPERROW_ASCII;
}
+ const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
int captionLen = 3;
- mvaddnstr(y, x, this->caption, captionLen);
+ mvaddnstr(y, x, caption, captionLen);
x += captionLen;
w -= captionLen;
- struct timeval now;
- gettimeofday(&now, NULL);
- if (!timercmp(&now, &(data->time), <)) {
+ if (!timercmp(&pl->realtime, &(data->time), <)) {
int globalDelay = this->pl->settings->delay;
- struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay - ((globalDelay / 10) * 10)) * 100000 };
- timeradd(&now, &delay, &(data->time));
+ struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay % 10) * 100000L };
+ timeradd(&pl->realtime, &delay, &(data->time));
for (int i = 0; i < nValues - 1; i++)
data->values[i] = data->values[i + 1];
- char buffer[METER_BUFFER_LEN];
- Meter_updateValues(this, buffer, sizeof(buffer));
-
double value = 0.0;
for (uint8_t i = 0; i < this->curItems; i++)
value += this->values[i];
- value /= this->total;
data->values[nValues - 1] = value;
}
- int i = nValues - (w * 2) + 2, k = 0;
+ int i = nValues - (w * 2), k = 0;
if (i < 0) {
k = -i / 2;
i = 0;
}
for (; i < nValues - 1; i += 2, k++) {
int pix = GraphMeterMode_pixPerRow * GRAPH_HEIGHT;
- int v1 = CLAMP((int) lround(data->values[i] * pix), 1, pix);
- int v2 = CLAMP((int) lround(data->values[i + 1] * pix), 1, pix);
+ if (this->total < 1)
+ this->total = 1;
+ int v1 = CLAMP((int) lround(data->values[i] / this->total * pix), 1, pix);
+ int v2 = CLAMP((int) lround(data->values[i + 1] / this->total * pix), 1, pix);
int colorIdx = GRAPH_1;
for (int line = 0; line < GRAPH_HEIGHT; line++) {
@@ -364,12 +376,10 @@ static const char* const* LEDMeterMode_digits;
static void LEDMeterMode_drawDigit(int x, int y, int n) {
for (int i = 0; i < 3; i++)
- mvaddstr(y+i, x, LEDMeterMode_digits[i * 10 + n]);
+ mvaddstr(y + i, x, LEDMeterMode_digits[i * 10 + n]);
}
static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
- (void) w;
-
#ifdef HAVE_LIBNCURSESW
if (CRT_utf8)
LEDMeterMode_digits = LEDMeterMode_digitsUtf8;
@@ -377,11 +387,8 @@ static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
#endif
LEDMeterMode_digits = LEDMeterMode_digitsAscii;
- char buffer[METER_BUFFER_LEN];
- Meter_updateValues(this, buffer, sizeof(buffer));
-
RichString_begin(out);
- Meter_displayBuffer(this, buffer, &out);
+ Meter_displayBuffer(this, &out);
int yText =
#ifdef HAVE_LIBNCURSESW
@@ -389,21 +396,32 @@ static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
#endif
y + 2;
attrset(CRT_colors[LED_COLOR]);
- mvaddstr(yText, x, this->caption);
- int xx = x + strlen(this->caption);
+ const char* caption = Meter_getCaption(this);
+ mvaddstr(yText, x, caption);
+ int xx = x + strlen(caption);
int len = RichString_sizeVal(out);
for (int i = 0; i < len; i++) {
int c = RichString_getCharVal(out, i);
if (c >= '0' && c <= '9') {
- LEDMeterMode_drawDigit(xx, y, c - 48);
+ if (xx - x + 4 > w)
+ break;
+
+ LEDMeterMode_drawDigit(xx, y, c - '0');
xx += 4;
} else {
+ if (xx - x + 1 > w)
+ break;
+#ifdef HAVE_LIBNCURSESW
+ const cchar_t wc = { .chars = { c, '\0' }, .attr = 0 }; /* use LED_COLOR from attrset() */
+ mvadd_wch(yText, xx, &wc);
+#else
mvaddch(yText, xx, c);
+#endif
xx += 1;
}
}
attrset(CRT_colors[RESET_COLOR]);
- RichString_end(out);
+ RichString_delete(&out);
}
static MeterMode BarMeterMode = {
@@ -441,14 +459,11 @@ const MeterMode* const Meter_modes[] = {
/* Blank meter */
-static void BlankMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t size) {
- if (size > 0) {
- *buffer = 0;
- }
+static void BlankMeter_updateValues(Meter* this) {
+ this->txtBuffer[0] = '\0';
}
-static void BlankMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
- RichString_prune(out);
+static void BlankMeter_display(ATTR_UNUSED const Object* cast, ATTR_UNUSED RichString* out) {
}
static const int BlankMeter_attributes[] = {
diff --git a/Meter.h b/Meter.h
index cb05405..bd7604a 100644
--- a/Meter.h
+++ b/Meter.h
@@ -3,13 +3,14 @@
/*
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>
@@ -18,7 +19,8 @@ in the source distribution for its full text.
#include "ProcessList.h"
-#define METER_BUFFER_LEN 256
+#define METER_TXTBUFFER_LEN 256
+#define METER_GRAPHDATA_SIZE 256
#define METER_BUFFER_CHECK(buffer, size, written) \
do { \
@@ -49,16 +51,20 @@ typedef struct Meter_ Meter;
typedef void(*Meter_Init)(Meter*);
typedef void(*Meter_Done)(Meter*);
typedef void(*Meter_UpdateMode)(Meter*, int);
-typedef void(*Meter_UpdateValues)(Meter*, char*, size_t);
+typedef void(*Meter_UpdateValues)(Meter*);
typedef void(*Meter_Draw)(Meter*, int, int, int);
+typedef const char* (*Meter_GetCaption)(const Meter*);
+typedef void(*Meter_GetUiName)(const Meter*, char*, size_t);
typedef struct MeterClass_ {
const ObjectClass super;
const Meter_Init init;
const Meter_Done done;
const Meter_UpdateMode updateMode;
- const Meter_Draw draw;
const Meter_UpdateValues updateValues;
+ const Meter_Draw draw;
+ const Meter_GetCaption getCaption;
+ const Meter_GetUiName getUiName;
const int defaultMode;
const double total;
const int* const attributes;
@@ -67,6 +73,7 @@ typedef struct MeterClass_ {
const char* const caption; /* prefix in the actual header */
const char* const description; /* optional meter description in header setup menu */
const uint8_t maxItems;
+ const bool isMultiColumn; /* whether the meter draws multiple sub-columns (defaults to false) */
} MeterClass;
#define As_Meter(this_) ((const MeterClass*)((this_)->super.klass))
@@ -77,16 +84,20 @@ typedef struct MeterClass_ {
#define Meter_updateMode(this_, m_) As_Meter(this_)->updateMode((Meter*)(this_), m_)
#define Meter_drawFn(this_) As_Meter(this_)->draw
#define Meter_doneFn(this_) As_Meter(this_)->done
-#define Meter_updateValues(this_, buf_, sz_) \
- As_Meter(this_)->updateValues((Meter*)(this_), buf_, sz_)
+#define Meter_updateValues(this_) As_Meter(this_)->updateValues((Meter*)(this_))
+#define Meter_getUiNameFn(this_) As_Meter(this_)->getUiName
+#define Meter_getUiName(this_,n_,l_) As_Meter(this_)->getUiName((const Meter*)(this_),n_,l_)
+#define Meter_getCaptionFn(this_) As_Meter(this_)->getCaption
+#define Meter_getCaption(this_) (Meter_getCaptionFn(this_) ? As_Meter(this_)->getCaption((const Meter*)(this_)) : (this_)->caption)
#define Meter_defaultMode(this_) As_Meter(this_)->defaultMode
#define Meter_attributes(this_) As_Meter(this_)->attributes
#define Meter_name(this_) As_Meter(this_)->name
#define Meter_uiName(this_) As_Meter(this_)->uiName
+#define Meter_isMultiColumn(this_) As_Meter(this_)->isMultiColumn
typedef struct GraphData_ {
struct timeval time;
- double values[METER_BUFFER_LEN];
+ double values[METER_GRAPHDATA_SIZE];
} GraphData;
struct Meter_ {
@@ -95,11 +106,14 @@ struct Meter_ {
char* caption;
int mode;
- int param;
+ unsigned int param;
GraphData* drawData;
int h;
+ int columnWidthCount; /**< only used internally by the Header */
const ProcessList* pl;
uint8_t curItems;
+ const int* curAttributes;
+ char txtBuffer[METER_TXTBUFFER_LEN];
double* values;
double total;
void* meterData;
@@ -120,9 +134,16 @@ 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, int param, const MeterClass* type);
+Meter* Meter_new(const ProcessList* pl, unsigned int param, const MeterClass* type);
int Meter_humanUnit(char* buffer, unsigned long int value, size_t size);
@@ -132,7 +153,7 @@ void Meter_setCaption(Meter* this, const char* caption);
void Meter_setMode(Meter* this, int modeIndex);
-ListItem* Meter_toListItem(Meter* this, bool moving);
+ListItem* Meter_toListItem(const Meter* this, bool moving);
extern const MeterMode* const Meter_modes[];
diff --git a/MetersPanel.c b/MetersPanel.c
index 7e47ad8..580e41b 100644
--- a/MetersPanel.c
+++ b/MetersPanel.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -20,9 +20,9 @@ in the source distribution for its full text.
// Note: In code the meters are known to have bar/text/graph "Modes", but in UI
// we call them "Styles".
-static const char* const MetersFunctions[] = {"Style ", "Move ", " ", "Delete", "Done ", NULL};
-static const char* const MetersKeys[] = {"Space", "Enter", " ", "Del", "F10"};
-static int MetersEvents[] = {' ', 13, ERR, KEY_DC, KEY_F(10)};
+static const char* const MetersFunctions[] = {"Style ", "Move ", " ", "Delete", "Done ", NULL};
+static const char* const MetersKeys[] = {"Space", "Enter", "", "Del", "F10"};
+static const int MetersEvents[] = {' ', 13, ERR, KEY_DC, KEY_F(10)};
// We avoid UTF-8 arrows ← → here as they might display full-width on Chinese
// terminals, breaking our aligning.
@@ -30,10 +30,10 @@ static int MetersEvents[] = {' ', 13, ERR, KEY_DC, KEY_F(10)};
// considered "Ambiguous characters".
static const char* const MetersMovingFunctions[] = {"Style ", "Lock ", "Up ", "Down ", "Left ", "Right ", " ", "Delete", "Done ", NULL};
static const char* const MetersMovingKeys[] = {"Space", "Enter", "Up", "Dn", "<-", "->", " ", "Del", "F10"};
-static int MetersMovingEvents[] = {' ', 13, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, ERR, KEY_DC, KEY_F(10)};
+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;
@@ -55,13 +55,12 @@ void MetersPanel_setMoving(MetersPanel* this, bool moving) {
selected->moving = moving;
}
if (!moving) {
- Panel_setSelectionColor(super, CRT_colors[PANEL_SELECTION_FOCUS]);
+ Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
Panel_setDefaultBar(super);
} else {
- Panel_setSelectionColor(super, CRT_colors[PANEL_SELECTION_FOLLOW]);
+ Panel_setSelectionColor(super, PANEL_SELECTION_FOLLOW);
super->currentBar = Meters_movingBar;
}
- FunctionBar_draw(this->super.currentBar);
}
static inline bool moveToNeighbor(MetersPanel* this, MetersPanel* neighbor, int selected) {
@@ -92,7 +91,7 @@ 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:
@@ -111,7 +110,8 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {
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;
@@ -185,9 +185,9 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {
if (result == HANDLED || sideMove) {
Header* header = this->scr->header;
this->settings->changed = true;
+ this->settings->lastUpdate++;
Header_calculateHeight(header);
- Header_draw(header);
- ScreenManager_resize(this->scr, this->scr->x1, header->height, this->scr->x2, this->scr->y2);
+ ScreenManager_resize(this->scr);
}
return result;
}
@@ -217,7 +217,7 @@ MetersPanel* MetersPanel_new(Settings* settings, const char* header, Vector* met
this->leftNeighbor = NULL;
Panel_setHeader(super, header);
for (int i = 0; i < Vector_size(meters); i++) {
- Meter* meter = (Meter*) Vector_get(meters, i);
+ const Meter* meter = (const Meter*) Vector_get(meters, i);
Panel_add(super, (Object*) Meter_toListItem(meter, false));
}
return this;
diff --git a/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 90ec990..d41eafa 100644
--- a/NetworkIOMeter.c
+++ b/NetworkIOMeter.c
@@ -1,13 +1,15 @@
#include "NetworkIOMeter.h"
#include <stdbool.h>
-#include <stddef.h>
-#include <sys/time.h>
+#include <stdint.h>
#include "CRT.h"
#include "Macros.h"
+#include "Meter.h"
#include "Object.h"
#include "Platform.h"
+#include "Process.h"
+#include "ProcessList.h"
#include "RichString.h"
#include "XUtils.h"
@@ -17,95 +19,135 @@ static const int NetworkIOMeter_attributes[] = {
METER_VALUE_IOWRITE,
};
-static bool hasData = false;
+static MeterRateStatus status = RATESTATUS_INIT;
+static uint32_t cached_rxb_diff;
+static uint32_t cached_rxp_diff;
+static uint32_t cached_txb_diff;
+static uint32_t cached_txp_diff;
-static unsigned long int cached_rxb_diff = 0;
-static unsigned long int cached_rxp_diff = 0;
-static unsigned long int cached_txb_diff = 0;
-static unsigned long int cached_txp_diff = 0;
+static void NetworkIOMeter_updateValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+ static uint64_t cached_last_update = 0;
-static void NetworkIOMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t len) {
- static unsigned long long int cached_last_update = 0;
+ uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update;
- struct timeval tv;
- gettimeofday(&tv, NULL);
- unsigned long long int timeInMilliSeconds = (unsigned long long int)tv.tv_sec * 1000 + (unsigned long long int)tv.tv_usec / 1000;
- unsigned long long int passedTimeInMs = timeInMilliSeconds - cached_last_update;
-
- /* update only every 500ms */
+ /* update only every 500ms to have a sane span for rate calculation */
if (passedTimeInMs > 500) {
- static unsigned long int cached_rxb_total = 0;
- static unsigned long int cached_rxp_total = 0;
- static unsigned long int cached_txb_total = 0;
- static unsigned long int cached_txp_total = 0;
-
- cached_last_update = timeInMilliSeconds;
+ static uint64_t cached_rxb_total;
+ static uint64_t cached_rxp_total;
+ static uint64_t cached_txb_total;
+ static uint64_t cached_txp_total;
+ uint64_t diff;
+
+ NetworkIOData data;
+ if (!Platform_getNetworkIO(&data)) {
+ status = RATESTATUS_NODATA;
+ } else if (cached_last_update == 0) {
+ status = RATESTATUS_INIT;
+ } else if (passedTimeInMs > 30000) {
+ status = RATESTATUS_STALE;
+ } else {
+ status = RATESTATUS_DATA;
+ }
- unsigned long int bytesReceived, packetsReceived, bytesTransmitted, packetsTransmitted;
+ cached_last_update = pl->realtimeMs;
- hasData = Platform_getNetworkIO(&bytesReceived, &packetsReceived, &bytesTransmitted, &packetsTransmitted);
- if (!hasData) {
- xSnprintf(buffer, len, "no data");
+ if (status == RATESTATUS_NODATA) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
return;
}
- if (bytesReceived > cached_rxb_total) {
- cached_rxb_diff = (bytesReceived - cached_rxb_total) / 1024; /* Meter_humanUnit() expects unit in kilo */
- cached_rxb_diff = 1000.0 * cached_rxb_diff / passedTimeInMs; /* convert to per second */
+ if (data.bytesReceived > cached_rxb_total) {
+ diff = data.bytesReceived - cached_rxb_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ diff /= ONE_K; /* convert to KiB/s */
+ cached_rxb_diff = (uint32_t)diff;
} else {
cached_rxb_diff = 0;
}
- cached_rxb_total = bytesReceived;
+ cached_rxb_total = data.bytesReceived;
- if (packetsReceived > cached_rxp_total) {
- cached_rxp_diff = packetsReceived - cached_rxp_total;
+ if (data.packetsReceived > cached_rxp_total) {
+ diff = data.packetsReceived - cached_rxp_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ cached_rxp_diff = (uint32_t)diff;
} else {
cached_rxp_diff = 0;
}
- cached_rxp_total = packetsReceived;
+ cached_rxp_total = data.packetsReceived;
- if (bytesTransmitted > cached_txb_total) {
- cached_txb_diff = (bytesTransmitted - cached_txb_total) / 1024; /* Meter_humanUnit() expects unit in kilo */
- cached_txb_diff = 1000.0 * cached_txb_diff / passedTimeInMs; /* convert to per second */
+ if (data.bytesTransmitted > cached_txb_total) {
+ diff = data.bytesTransmitted - cached_txb_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ diff /= ONE_K; /* convert to KiB/s */
+ cached_txb_diff = (uint32_t)diff;
} else {
cached_txb_diff = 0;
}
- cached_txb_total = bytesTransmitted;
+ cached_txb_total = data.bytesTransmitted;
- if (packetsTransmitted > cached_txp_total) {
- cached_txp_diff = packetsTransmitted - cached_txp_total;
+ if (data.packetsTransmitted > cached_txp_total) {
+ diff = data.packetsTransmitted - cached_txp_total;
+ diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */
+ cached_txp_diff = (uint32_t)diff;
} else {
cached_txp_diff = 0;
}
- cached_txp_total = packetsTransmitted;
+ cached_txp_total = data.packetsTransmitted;
+ }
+
+ if (status == RATESTATUS_INIT) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init");
+ return;
+ }
+ if (status == RATESTATUS_STALE) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale");
+ return;
+ }
+
+ this->values[0] = cached_rxb_diff;
+ this->values[1] = cached_txb_diff;
+ if (cached_rxb_diff + cached_txb_diff > this->total) {
+ this->total = cached_rxb_diff + cached_txb_diff;
}
char bufferBytesReceived[12], bufferBytesTransmitted[12];
Meter_humanUnit(bufferBytesReceived, cached_rxb_diff, sizeof(bufferBytesReceived));
Meter_humanUnit(bufferBytesTransmitted, cached_txb_diff, sizeof(bufferBytesTransmitted));
- xSnprintf(buffer, len, "rx:%siB/s tx:%siB/s", bufferBytesReceived, bufferBytesTransmitted);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "rx:%siB/s tx:%siB/s %d/%dpkts/s",
+ bufferBytesReceived, bufferBytesTransmitted, cached_rxp_diff, cached_txp_diff);
}
static void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
- if (!hasData) {
- RichString_write(out, CRT_colors[METER_VALUE_ERROR], "no data");
+ 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_write(out, CRT_colors[METER_TEXT], "rx: ");
+ RichString_writeAscii(out, CRT_colors[METER_TEXT], "rx: ");
Meter_humanUnit(buffer, cached_rxb_diff, sizeof(buffer));
- RichString_append(out, CRT_colors[METER_VALUE_IOREAD], buffer);
- RichString_append(out, CRT_colors[METER_VALUE_IOREAD], "iB/s");
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], "iB/s");
- RichString_append(out, CRT_colors[METER_TEXT], " tx: ");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " tx: ");
Meter_humanUnit(buffer, cached_txb_diff, sizeof(buffer));
- RichString_append(out, CRT_colors[METER_VALUE_IOWRITE], buffer);
- RichString_append(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s");
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s");
- xSnprintf(buffer, sizeof(buffer), " (%lu/%lu packets) ", cached_rxp_diff, cached_txp_diff);
- RichString_append(out, CRT_colors[METER_TEXT], buffer);
+ len = xSnprintf(buffer, sizeof(buffer), " (%u/%u pkts/s) ", cached_rxp_diff, cached_txp_diff);
+ RichString_appendnAscii(out, CRT_colors[METER_TEXT], buffer, len);
}
const MeterClass NetworkIOMeter_class = {
@@ -116,7 +158,7 @@ const MeterClass NetworkIOMeter_class = {
},
.updateValues = NetworkIOMeter_updateValues,
.defaultMode = TEXT_METERMODE,
- .maxItems = 0,
+ .maxItems = 2,
.total = 100.0,
.attributes = NetworkIOMeter_attributes,
.name = "NetworkIO",
diff --git a/NetworkIOMeter.h b/NetworkIOMeter.h
index 311b5e6..18c23ce 100644
--- a/NetworkIOMeter.h
+++ b/NetworkIOMeter.h
@@ -3,6 +3,14 @@
#include "Meter.h"
+
+typedef struct NetworkIOData_ {
+ uint64_t bytesReceived;
+ uint64_t packetsReceived;
+ uint64_t bytesTransmitted;
+ uint64_t packetsTransmitted;
+} NetworkIOData;
+
extern const MeterClass NetworkIOMeter_class;
#endif /* HEADER_NetworkIOMeter */
diff --git a/Object.c b/Object.c
index 0a29d01..f009148 100644
--- a/Object.c
+++ b/Object.c
@@ -2,7 +2,7 @@
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.
*/
@@ -15,8 +15,6 @@ const ObjectClass Object_class = {
.extends = NULL
};
-#ifndef NDEBUG
-
bool Object_isA(const Object* o, const ObjectClass* klass) {
if (!o)
return false;
@@ -29,5 +27,3 @@ bool Object_isA(const Object* o, const ObjectClass* klass) {
return false;
}
-
-#endif /* NDEBUG */
diff --git a/Object.h b/Object.h
index 2c3ba9f..d5b3bb3 100644
--- a/Object.h
+++ b/Object.h
@@ -4,27 +4,24 @@
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>
#include "RichString.h"
#include "XUtils.h" // IWYU pragma: keep
-#ifndef NDEBUG
-#include <stdbool.h>
-#endif
-
struct Object_;
typedef struct Object_ Object;
typedef void(*Object_Display)(const Object*, RichString*);
-typedef long(*Object_Compare)(const void*, const void*);
+typedef int(*Object_Compare)(const void*, const void*);
typedef void(*Object_Delete)(Object*);
#define Object_getClass(obj_) ((const Object*)(obj_))->klass
@@ -57,10 +54,6 @@ typedef union {
extern const ObjectClass Object_class;
-#ifndef NDEBUG
-
bool Object_isA(const Object* o, const ObjectClass* klass);
-#endif /* NDEBUG */
-
#endif
diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c
index b1137c7..4256575 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 <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"
@@ -25,7 +28,7 @@ in the source distribution for its full text.
typedef struct OpenFiles_Data_ {
- char* data[7];
+ char* data[8];
} OpenFiles_Data;
typedef struct OpenFiles_ProcessData_ {
@@ -55,6 +58,8 @@ static size_t getIndexForType(char type) {
return 5;
case 't':
return 6;
+ case 'o':
+ return 7;
}
/* should never reach here */
@@ -74,7 +79,7 @@ OpenFilesScreen* OpenFilesScreen_new(const Process* process) {
} else {
this->pid = process->pid;
}
- return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 3, " 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) {
@@ -115,13 +120,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) {
@@ -155,8 +163,17 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
case 't': /* file's type */
{
size_t index = getIndexForType(cmd);
- free(item->data[index]);
- item->data[index] = xStrdup(line + 1);
+ free_and_xStrdup(&item->data[index], line + 1);
+ break;
+ }
+ case 'o': /* file's offset */
+ {
+ size_t index = getIndexForType(cmd);
+ if (String_startsWith(line + 1, "0t")) {
+ free_and_xStrdup(&item->data[index], line + 3);
+ } else {
+ free_and_xStrdup(&item->data[index], line + 1);
+ }
break;
}
case 'c': /* process command name */
@@ -166,7 +183,6 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
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 */
@@ -175,15 +191,20 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
/* 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 +212,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;
}
@@ -213,14 +253,15 @@ static void OpenFilesScreen_scan(InfoScreen* this) {
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*/;
+ size_t sizeEntry = 5 + 7 + 4 + 10 + 10 + 10 + 10 + lenN + 8 /*spaces*/ + 1 /*null*/;
char entry[sizeEntry];
- xSnprintf(entry, sizeof(entry), "%5.5s %-7.7s %-4.4s %-10.10s %10.10s %10.10s %s",
+ xSnprintf(entry, sizeof(entry), "%5.5s %-7.7s %-4.4s %-10.10s %10.10s %10.10s %10.10s %s",
getDataForType(data, 'f'),
getDataForType(data, 't'),
getDataForType(data, 'a'),
getDataForType(data, 'D'),
getDataForType(data, 's'),
+ getDataForType(data, 'o'),
getDataForType(data, 'i'),
getDataForType(data, 'n'));
InfoScreen_addLine(this, entry);
diff --git a/OpenFilesScreen.h b/OpenFilesScreen.h
index 0fbafe0..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.
*/
@@ -13,6 +13,7 @@ in the source distribution for its full text.
#include "Object.h"
#include "Process.h"
+
typedef struct OpenFilesScreen_ {
InfoScreen super;
pid_t pid;
diff --git a/OptionItem.c b/OptionItem.c
index 00001fc..962c0a9 100644
--- a/OptionItem.c
+++ b/OptionItem.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -25,18 +25,25 @@ 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);
- RichString_write(out, CRT_colors[CHECK_BOX], "[");
+ RichString_writeAscii(out, CRT_colors[CHECK_BOX], "[");
if (CheckItem_get(this)) {
- RichString_append(out, CRT_colors[CHECK_MARK], "x");
+ RichString_appendAscii(out, CRT_colors[CHECK_MARK], "x");
} else {
- RichString_append(out, CRT_colors[CHECK_MARK], " ");
+ RichString_appendAscii(out, CRT_colors[CHECK_MARK], " ");
}
- RichString_append(out, CRT_colors[CHECK_BOX], "] ");
- RichString_append(out, CRT_colors[CHECK_TEXT], this->super.text);
+ RichString_appendAscii(out, CRT_colors[CHECK_BOX], "] ");
+ RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->super.text);
}
static void NumberItem_display(const Object* cast, RichString* out) {
@@ -44,7 +51,7 @@ static void NumberItem_display(const Object* cast, RichString* out) {
assert (this != NULL);
char buffer[12];
- RichString_write(out, CRT_colors[CHECK_BOX], "[");
+ RichString_writeAscii(out, CRT_colors[CHECK_BOX], "[");
int written;
if (this->scale < 0) {
written = xSnprintf(buffer, sizeof(buffer), "%.*f", -this->scale, pow(10, this->scale) * NumberItem_get(this));
@@ -53,12 +60,12 @@ static void NumberItem_display(const Object* cast, RichString* out) {
} else {
written = xSnprintf(buffer, sizeof(buffer), "%d", NumberItem_get(this));
}
- RichString_append(out, CRT_colors[CHECK_MARK], buffer);
- RichString_append(out, CRT_colors[CHECK_BOX], "]");
+ RichString_appendnAscii(out, CRT_colors[CHECK_MARK], buffer, written);
+ RichString_appendAscii(out, CRT_colors[CHECK_BOX], "]");
for (int i = written; i < 5; i++) {
- RichString_append(out, CRT_colors[CHECK_BOX], " ");
+ RichString_appendAscii(out, CRT_colors[CHECK_BOX], " ");
}
- RichString_append(out, CRT_colors[CHECK_TEXT], this->super.text);
+ RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->super.text);
}
const OptionItemClass OptionItem_class = {
@@ -68,6 +75,16 @@ const OptionItemClass OptionItem_class = {
}
};
+const OptionItemClass TextItem_class = {
+ .super = {
+ .extends = Class(OptionItem),
+ .delete = OptionItem_delete,
+ .display = TextItem_display
+ },
+ .kind = OPTION_ITEM_TEXT
+};
+
+
const OptionItemClass CheckItem_class = {
.super = {
.extends = Class(OptionItem),
@@ -77,6 +94,7 @@ const OptionItemClass CheckItem_class = {
.kind = OPTION_ITEM_CHECK
};
+
const OptionItemClass NumberItem_class = {
.super = {
.extends = Class(OptionItem),
@@ -86,6 +104,12 @@ const OptionItemClass NumberItem_class = {
.kind = OPTION_ITEM_NUMBER
};
+TextItem* TextItem_new(const char* text) {
+ TextItem* this = AllocThis(TextItem);
+ this->super.text = xStrdup(text);
+ return this;
+}
+
CheckItem* CheckItem_newByRef(const char* text, bool* ref) {
CheckItem* this = AllocThis(CheckItem);
this->super.text = xStrdup(text);
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 21dfbe2..d1bc6a7 100644
--- a/Panel.c
+++ b/Panel.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -30,7 +30,7 @@ const PanelClass Panel_class = {
.eventHandler = Panel_selectByTyping,
};
-Panel* Panel_new(int x, int y, int w, int h, bool owner, const ObjectClass* type, FunctionBar* fuBar) {
+Panel* Panel_new(int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar) {
Panel* this;
this = xMalloc(sizeof(Panel));
Object_setClass(this, Class(Panel));
@@ -49,17 +49,22 @@ 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;
this->scrollH = 0;
this->selected = 0;
this->oldSelected = 0;
+ this->selectedLen = 0;
this->needsRedraw = true;
+ this->cursorOn = false;
+ this->wasFocus = false;
RichString_beginAllocated(this->header);
this->defaultBar = fuBar;
this->currentBar = fuBar;
- this->selectionColor = CRT_colors[PANEL_SELECTION_FOCUS];
+ this->selectionColorId = PANEL_SELECTION_FOCUS;
}
void Panel_done(Panel* this) {
@@ -67,22 +72,20 @@ void Panel_done(Panel* this) {
free(this->eventHandlerState);
Vector_delete(this->items);
FunctionBar_delete(this->defaultBar);
- RichString_end(this->header);
+ RichString_delete(&this->header);
}
-void Panel_setSelectionColor(Panel* this, int color) {
- this->selectionColor = color;
+void Panel_setCursorToSelection(Panel* this) {
+ this->cursorY = this->y + this->selected - this->scrollV + 1;
+ this->cursorX = this->x + this->selectedLen - this->scrollH;
}
-RichString* Panel_getHeader(Panel* this) {
- assert (this != NULL);
-
- this->needsRedraw = true;
- return &(this->header);
+void Panel_setSelectionColor(Panel* this, ColorElements colorId) {
+ this->selectionColorId = colorId;
}
inline void Panel_setHeader(Panel* this, const char* header) {
- RichString_write(&(this->header), CRT_colors[PANEL_HEADER_FOCUS], header);
+ RichString_writeWide(&(this->header), CRT_colors[PANEL_HEADER_FOCUS], header);
this->needsRedraw = true;
}
@@ -97,10 +100,6 @@ void Panel_move(Panel* this, int x, int y) {
void Panel_resize(Panel* this, int w, int h) {
assert (this != NULL);
- if (RichString_sizeVal(this->header) > 0) {
- h--;
- }
-
this->w = w;
this->h = h;
this->needsRedraw = true;
@@ -181,13 +180,13 @@ void Panel_moveSelectedDown(Panel* this) {
}
}
-int Panel_getSelectedIndex(Panel* this) {
+int Panel_getSelectedIndex(const Panel* this) {
assert (this != NULL);
return this->selected;
}
-int Panel_size(Panel* this) {
+int Panel_size(const Panel* this) {
assert (this != NULL);
return Vector_size(this->items);
@@ -217,7 +216,7 @@ void Panel_splice(Panel* this, Vector* from) {
this->needsRedraw = true;
}
-void Panel_draw(Panel* this, bool focus, bool highlightSelected) {
+void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelected, bool hideFunctionBar) {
assert (this != NULL);
int size = Vector_size(this->items);
@@ -226,12 +225,21 @@ void Panel_draw(Panel* this, bool focus, bool highlightSelected) {
int x = this->x;
int h = this->h;
+ if (hideFunctionBar)
+ h++;
+
+ const int header_attr = focus
+ ? CRT_colors[PANEL_HEADER_FOCUS]
+ : CRT_colors[PANEL_HEADER_UNFOCUS];
+ if (force_redraw) {
+ if (Panel_printHeaderFn(this))
+ Panel_printHeader(this);
+ else
+ RichString_setAttr(&this->header, header_attr);
+ }
int headerLen = RichString_sizeVal(this->header);
if (headerLen > 0) {
- int attr = focus
- ? CRT_colors[PANEL_HEADER_FOCUS]
- : CRT_colors[PANEL_HEADER_UNFOCUS];
- attrset(attr);
+ attrset(header_attr);
mvhline(y, x, ' ', this->w);
if (scrollH < headerLen) {
RichString_printoffnVal(this->header, y, x, scrollH,
@@ -239,14 +247,15 @@ void Panel_draw(Panel* this, bool focus, bool highlightSelected) {
}
attrset(CRT_colors[RESET_COLOR]);
y++;
+ h--;
}
// ensure scroll area is on screen
if (this->scrollV < 0) {
this->scrollV = 0;
this->needsRedraw = true;
- } else if (this->scrollV >= size) {
- this->scrollV = MAXIMUM(size - 1, 0);
+ } else if (this->scrollV > size - h) {
+ this->scrollV = MAXIMUM(size - h, 0);
this->needsRedraw = true;
}
// ensure selection is on screen
@@ -262,13 +271,13 @@ void Panel_draw(Panel* this, bool focus, bool highlightSelected) {
int upTo = MINIMUM(first + h, size);
int selectionColor = focus
- ? this->selectionColor
+ ? CRT_colors[this->selectionColorId]
: CRT_colors[PANEL_SELECTION_UNFOCUS];
- if (this->needsRedraw) {
+ if (this->needsRedraw || force_redraw) {
int line = 0;
for (int i = first; line < h && i < upTo; i++) {
- Object* itemObj = Vector_get(this->items, i);
+ const Object* itemObj = Vector_get(this->items, i);
RichString_begin(item);
Object_display(itemObj, &item);
int itemLen = RichString_sizeVal(item);
@@ -286,21 +295,20 @@ void Panel_draw(Panel* this, bool focus, bool highlightSelected) {
RichString_printoffnVal(item, y + line, x, scrollH, amt);
if (item.highlightAttr)
attrset(CRT_colors[RESET_COLOR]);
- RichString_end(item);
+ RichString_delete(&item);
line++;
}
while (line < h) {
mvhline(y + line, x, ' ', this->w);
line++;
}
- this->needsRedraw = false;
} else {
- Object* oldObj = Vector_get(this->items, this->oldSelected);
+ const Object* oldObj = Vector_get(this->items, this->oldSelected);
RichString_begin(old);
Object_display(oldObj, &old);
int oldLen = RichString_sizeVal(old);
- Object* newObj = Vector_get(this->items, this->selected);
+ const Object* newObj = Vector_get(this->items, this->selected);
RichString_begin(new);
Object_display(newObj, &new);
int newLen = RichString_sizeVal(new);
@@ -316,20 +324,37 @@ void Panel_draw(Panel* this, bool focus, bool highlightSelected) {
RichString_printoffnVal(new, y + this->selected - first, x,
scrollH, MINIMUM(newLen - scrollH, this->w));
attrset(CRT_colors[RESET_COLOR]);
- RichString_end(new);
- RichString_end(old);
+ RichString_delete(&new);
+ RichString_delete(&old);
+ }
+
+ if (focus && (this->needsRedraw || force_redraw || !this->wasFocus)) {
+ if (Panel_drawFunctionBarFn(this))
+ Panel_drawFunctionBar(this, hideFunctionBar);
+ else if (!hideFunctionBar)
+ FunctionBar_draw(this->currentBar);
}
+
this->oldSelected = this->selected;
- move(0, 0);
+ this->wasFocus = focus;
+ this->needsRedraw = false;
+}
+
+static int Panel_headerHeight(const Panel* this) {
+ return RichString_sizeVal(this->header) > 0 ? 1 : 0;
}
bool Panel_onKey(Panel* this, int key) {
assert (this != NULL);
- int size = Vector_size(this->items);
+ const int size = Vector_size(this->items);
- #define CLAMP_INDEX(var, delta, min, max) \
- CLAMP((var) + (delta), (min), MAXIMUM(0, (max)))
+ #define PANEL_SCROLL(amount) \
+ do { \
+ this->selected += (amount); \
+ this->scrollV = CLAMP(this->scrollV + (amount), 0, MAXIMUM(0, (size - this->h - Panel_headerHeight(this)))); \
+ this->needsRedraw = true; \
+ } while (0)
switch (key) {
case KEY_DOWN:
@@ -363,27 +388,19 @@ bool Panel_onKey(Panel* this, int key) {
break;
case KEY_PPAGE:
- this->selected -= (this->h - 1);
- this->scrollV = CLAMP_INDEX(this->scrollV, -(this->h - 1), 0, size - this->h);
- this->needsRedraw = true;
+ PANEL_SCROLL(-(this->h - Panel_headerHeight(this)));
break;
case KEY_NPAGE:
- this->selected += (this->h - 1);
- this->scrollV = CLAMP_INDEX(this->scrollV, +(this->h - 1), 0, size - this->h);
- this->needsRedraw = true;
+ PANEL_SCROLL(+(this->h - Panel_headerHeight(this)));
break;
case KEY_WHEELUP:
- this->selected -= CRT_scrollWheelVAmount;
- this->scrollV = CLAMP_INDEX(this->scrollV, -CRT_scrollWheelVAmount, 0, size - this->h);
- this->needsRedraw = true;
+ PANEL_SCROLL(-CRT_scrollWheelVAmount);
break;
case KEY_WHEELDOWN:
- this->selected += CRT_scrollWheelVAmount;
- this->scrollV = CLAMP_INDEX(this->scrollV, +CRT_scrollWheelVAmount, 0, size - this->h);
- this->needsRedraw = true;
+ PANEL_SCROLL(+CRT_scrollWheelVAmount);
break;
case KEY_HOME:
@@ -408,7 +425,7 @@ bool Panel_onKey(Panel* this, int key) {
return false;
}
- #undef CLAMP_INDEX
+ #undef PANEL_SCROLL
// ensure selection within bounds
if (this->selected < 0 || size == 0) {
@@ -426,6 +443,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;
@@ -443,15 +463,16 @@ HandlerResult Panel_selectByTyping(Panel* this, int ch) {
}
if (len < 99) {
- buffer[len] = ch;
- buffer[len+1] = '\0';
+ buffer[len] = (char) ch;
+ buffer[len + 1] = '\0';
}
for (int try = 0; try < 2; try++) {
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;
@@ -460,7 +481,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';
}
@@ -475,3 +496,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 86c134e..33d532e 100644
--- a/Panel.h
+++ b/Panel.h
@@ -3,12 +3,16 @@
/*
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>
+#include "CRT.h"
#include "FunctionBar.h"
#include "Object.h"
#include "RichString.h"
@@ -22,9 +26,11 @@ typedef enum HandlerResult_ {
HANDLED = 0x01,
IGNORED = 0x02,
BREAK_LOOP = 0x04,
- REDRAW = 0x08,
- RESCAN = 0x10,
- SYNTH_KEY = 0x20,
+ REFRESH = 0x08,
+ REDRAW = 0x10,
+ RESCAN = 0x20,
+ RESIZE = 0x40,
+ SYNTH_KEY = 0x80,
} HandlerResult;
#define EVENT_SET_SELECTED (-1)
@@ -33,32 +39,47 @@ typedef enum HandlerResult_ {
#define EVENT_IS_HEADER_CLICK(ev_) ((ev_) >= -10000 && (ev_) <= -9000)
#define EVENT_HEADER_CLICK_GET_X(ev_) ((ev_) + 10000)
-typedef HandlerResult(*Panel_EventHandler)(Panel*, int);
+#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*);
typedef struct PanelClass_ {
const ObjectClass super;
const Panel_EventHandler eventHandler;
+ const Panel_DrawFunctionBar drawFunctionBar;
+ const Panel_PrintHeader printHeader;
} PanelClass;
-#define As_Panel(this_) ((const PanelClass*)((this_)->super.klass))
-#define Panel_eventHandlerFn(this_) As_Panel(this_)->eventHandler
-#define Panel_eventHandler(this_, ev_) As_Panel(this_)->eventHandler((Panel*)(this_), ev_)
+#define As_Panel(this_) ((const PanelClass*)((this_)->super.klass))
+#define Panel_eventHandlerFn(this_) As_Panel(this_)->eventHandler
+#define Panel_eventHandler(this_, ev_) (assert(As_Panel(this_)->eventHandler), As_Panel(this_)->eventHandler((Panel*)(this_), ev_))
+#define Panel_drawFunctionBarFn(this_) As_Panel(this_)->drawFunctionBar
+#define Panel_drawFunctionBar(this_, hideFB_) (assert(As_Panel(this_)->drawFunctionBar), As_Panel(this_)->drawFunctionBar((Panel*)(this_), hideFB_))
+#define Panel_printHeaderFn(this_) As_Panel(this_)->printHeader
+#define Panel_printHeader(this_) (assert(As_Panel(this_)->printHeader), As_Panel(this_)->printHeader((Panel*)(this_)))
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;
RichString header;
- int selectionColor;
+ ColorElements selectionColorId;
};
#define Panel_setDefaultBar(this_) do { (this_)->currentBar = (this_)->defaultBar; } while (0)
@@ -67,7 +88,7 @@ struct Panel_ {
extern const PanelClass Panel_class;
-Panel* Panel_new(int x, int y, int w, int h, bool owner, const ObjectClass* type, FunctionBar* fuBar);
+Panel* Panel_new(int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar);
void Panel_delete(Object* cast);
@@ -75,9 +96,9 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type
void Panel_done(Panel* this);
-void Panel_setSelectionColor(Panel* this, int color);
+void Panel_setCursorToSelection(Panel* this);
-RichString* Panel_getHeader(Panel* this);
+void Panel_setSelectionColor(Panel* this, ColorElements colorId);
void Panel_setHeader(Panel* this, const char* header);
@@ -103,13 +124,13 @@ void Panel_moveSelectedUp(Panel* this);
void Panel_moveSelectedDown(Panel* this);
-int Panel_getSelectedIndex(Panel* this);
+int Panel_getSelectedIndex(const Panel* this);
-int Panel_size(Panel* this);
+int Panel_size(const Panel* this);
void Panel_setSelected(Panel* this, int selected);
-void Panel_draw(Panel* this, bool focus, bool highlightSelected);
+void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelected, bool hideFunctionBar);
void Panel_splice(Panel* this, Vector* from);
@@ -117,4 +138,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 8245f86..fcaa3d5 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.
*/
@@ -17,6 +17,7 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/resource.h>
@@ -25,163 +26,248 @@ in the source distribution for its full text.
#include "Macros.h"
#include "Platform.h"
#include "ProcessList.h"
+#include "DynamicColumn.h"
#include "RichString.h"
#include "Settings.h"
#include "XUtils.h"
#if defined(MAJOR_IN_MKDEV)
#include <sys/mkdev.h>
-#elif defined(MAJOR_IN_SYSMACROS)
-#include <sys/sysmacros.h>
#endif
-static uid_t Process_getuid = (uid_t)-1;
+/* Used to identify kernel threads in Comm and Exe columns */
+static const char* const kthreadID = "KTHREAD";
-char Process_pidFormat[20] = "%7d ";
+static uid_t Process_getuid = (uid_t)-1;
-static char Process_titleBuffer[20][20];
+int Process_pidDigits = PROCESS_MIN_PID_DIGITS;
+int Process_uidDigits = PROCESS_MIN_UID_DIGITS;
-void Process_setupColumnWidths() {
+void Process_setupColumnWidths(void) {
int maxPid = Platform_getMaxPid();
if (maxPid == -1)
return;
- int digits = ceil(log10(maxPid));
- assert(digits < 20);
- for (int i = 0; Process_pidColumns[i].label; i++) {
- assert(i < 20);
- xSnprintf(Process_titleBuffer[i], 20, "%*s ", digits, Process_pidColumns[i].label);
- Process_fields[Process_pidColumns[i].id].title = Process_titleBuffer[i];
+ if (maxPid < (int)pow(10, PROCESS_MIN_PID_DIGITS)) {
+ Process_pidDigits = PROCESS_MIN_PID_DIGITS;
+ return;
}
- xSnprintf(Process_pidFormat, sizeof(Process_pidFormat), "%%%dd ", digits);
+
+ Process_pidDigits = (int)log10(maxPid) + 1;
+ assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS);
}
-void Process_humanNumber(RichString* str, unsigned long long number, bool coloring) {
- char buffer[10];
+void Process_setUidColumnWidth(uid_t maxUid) {
+ if (maxUid < (uid_t)pow(10, PROCESS_MIN_UID_DIGITS)) {
+ Process_uidDigits = PROCESS_MIN_UID_DIGITS;
+ return;
+ }
+
+ Process_uidDigits = (int)log10(maxUid) + 1;
+ assert(Process_uidDigits <= PROCESS_MAX_UID_DIGITS);
+}
+
+void Process_printBytes(RichString* str, unsigned long long number, bool coloring) {
+ char buffer[16];
int len;
- int largeNumberColor = CRT_colors[LARGE_NUMBER];
- int processMegabytesColor = CRT_colors[PROCESS_MEGABYTES];
- int processGigabytesColor = CRT_colors[PROCESS_GIGABYTES];
+ int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
+ int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
+ int processGigabytesColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];
+ int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
int processColor = CRT_colors[PROCESS];
- if (!coloring) {
- largeNumberColor = CRT_colors[PROCESS];
- processMegabytesColor = CRT_colors[PROCESS];
- processGigabytesColor = CRT_colors[PROCESS];
+
+ if (number == ULLONG_MAX) {
+ //Invalid number
+ RichString_appendAscii(str, shadowColor, " N/A ");
+ return;
}
+ number /= ONE_K;
+
if (number < 1000) {
//Plain number, no markings
len = xSnprintf(buffer, sizeof(buffer), "%5llu ", number);
- RichString_appendn(str, processColor, buffer, len);
+ 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_appendn(str, processMegabytesColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 1000);
+ RichString_appendnAscii(str, processMegabytesColor, buffer, len);
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03llu ", number);
- RichString_appendn(str, processColor, buffer, len);
+ 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_appendn(str, processMegabytesColor, buffer, len);
+ 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_appendn(str, processGigabytesColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000);
+ RichString_appendnAscii(str, processGigabytesColor, buffer, len);
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03lluM ", number);
- RichString_appendn(str, processMegabytesColor, buffer, len);
+ 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_appendn(str, processGigabytesColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10);
+ RichString_appendnAscii(str, processGigabytesColor, buffer, len);
number %= 10;
len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number);
- RichString_appendn(str, processMegabytesColor, buffer, len);
- RichString_append(str, processGigabytesColor, "G ");
+ 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_appendn(str, processGigabytesColor, buffer, len);
+ 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_appendn(str, largeNumberColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03lluG ", number);
- RichString_appendn(str, processGigabytesColor, buffer, len);
+ RichString_appendnAscii(str, processGigabytesColor, buffer, len);
+ } else if (number < 100000 * ONE_M) {
+ //2 digit TB, 1 digit GB
+ number /= 100 * ONE_M;
+ len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
+ number %= 10;
+ len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number);
+ RichString_appendnAscii(str, processGigabytesColor, buffer, len);
+ RichString_appendAscii(str, largeNumberColor, "T ");
+ } else if (number < 10000ULL * ONE_G) {
+ //3 digit TB or 1 digit PB, 3 digit TB
+ number /= ONE_G;
+ len = xSnprintf(buffer, sizeof(buffer), "%4lluT ", number);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
} else {
- //2 digit TB and above
- len = xSnprintf(buffer, sizeof(buffer), "%4.1lfT ", (double)number/ONE_G);
- RichString_appendn(str, largeNumberColor, buffer, len);
+ //2 digit PB and above
+ len = xSnprintf(buffer, sizeof(buffer), "%4.1lfP ", (double)number / ONE_T);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
}
}
-void Process_colorNumber(RichString* str, unsigned long long number, bool coloring) {
+void Process_printKBytes(RichString* str, unsigned long long number, bool coloring) {
+ if (number == ULLONG_MAX)
+ Process_printBytes(str, ULLONG_MAX, coloring);
+ else
+ Process_printBytes(str, number * ONE_K, coloring);
+}
+
+void Process_printCount(RichString* str, unsigned long long number, bool coloring) {
char buffer[13];
- int largeNumberColor = CRT_colors[LARGE_NUMBER];
- int processMegabytesColor = CRT_colors[PROCESS_MEGABYTES];
+ int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
+ int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
int processColor = CRT_colors[PROCESS];
- int processShadowColor = CRT_colors[PROCESS_SHADOW];
-
- if (!coloring) {
- largeNumberColor = CRT_colors[PROCESS];
- processMegabytesColor = CRT_colors[PROCESS];
- processShadowColor = CRT_colors[PROCESS];
- }
+ int processShadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
if (number == ULLONG_MAX) {
- int len = xSnprintf(buffer, sizeof(buffer), " N/A ");
- RichString_appendn(str, CRT_colors[PROCESS_SHADOW], buffer, len);
+ 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_appendn(str, largeNumberColor, buffer, 12);
+ RichString_appendnAscii(str, largeNumberColor, buffer, 12);
} else if (number >= 100LL * ONE_DECIMAL_T) {
xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M);
- RichString_appendn(str, largeNumberColor, buffer, 8);
- RichString_appendn(str, processMegabytesColor, buffer+8, 4);
+ 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_appendn(str, largeNumberColor, buffer, 5);
- RichString_appendn(str, processMegabytesColor, buffer+5, 3);
- RichString_appendn(str, processColor, buffer+8, 4);
+ 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_appendn(str, largeNumberColor, buffer, 2);
- RichString_appendn(str, processMegabytesColor, buffer+2, 3);
- RichString_appendn(str, processColor, buffer+5, 3);
- RichString_appendn(str, processShadowColor, buffer+8, 4);
+ 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) {
- unsigned long long totalSeconds = totalHundredths / 100;
+void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) {
+ char buffer[10];
+ int len;
+ unsigned long long totalSeconds = totalHundredths / 100;
unsigned long long hours = totalSeconds / 3600;
+ unsigned long long days = totalSeconds / 86400;
int minutes = (totalSeconds / 60) % 60;
int seconds = totalSeconds % 60;
int hundredths = totalHundredths - (totalSeconds * 100);
- char buffer[10];
- if (hours >= 100) {
- xSnprintf(buffer, sizeof(buffer), "%7lluh ", hours);
- RichString_append(str, CRT_colors[LARGE_NUMBER], buffer);
- } else {
- if (hours) {
- xSnprintf(buffer, sizeof(buffer), "%2lluh", hours);
- RichString_append(str, CRT_colors[LARGE_NUMBER], buffer);
- xSnprintf(buffer, sizeof(buffer), "%02d:%02d ", minutes, seconds);
+
+ int yearColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
+ int dayColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];
+ int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
+ int defColor = CRT_colors[PROCESS];
+
+ if (days >= /* Ignore leapyears */365) {
+ int years = days / 365;
+ int daysLeft = days - 365 * years;
+
+ if (years >= 10000000) {
+ RichString_appendnAscii(str, yearColor, "eternity ", 9);
+ } else if (years >= 1000) {
+ len = xSnprintf(buffer, sizeof(buffer), "%7dy ", years);
+ RichString_appendnAscii(str, yearColor, buffer, len);
+ } else if (daysLeft >= 100) {
+ len = xSnprintf(buffer, sizeof(buffer), "%3dy", years);
+ RichString_appendnAscii(str, yearColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft);
+ RichString_appendnAscii(str, dayColor, buffer, len);
+ } else if (daysLeft >= 10) {
+ len = xSnprintf(buffer, sizeof(buffer), "%4dy", years);
+ RichString_appendnAscii(str, yearColor, buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%2dd ", daysLeft);
+ RichString_appendnAscii(str, dayColor, buffer, len);
} else {
- xSnprintf(buffer, sizeof(buffer), "%2d:%02d.%02d ", minutes, seconds, hundredths);
+ 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);
}
- RichString_append(str, CRT_colors[DEFAULT_COLOR], buffer);
+ } 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);
}
}
@@ -191,42 +277,475 @@ void Process_fillStarttimeBuffer(Process* this) {
strftime(this->starttime_show, sizeof(this->starttime_show) - 1, (this->starttime_ctime > (time(NULL) - 86400)) ? "%R " : "%b%d ", &date);
}
-static inline void Process_writeCommand(const Process* this, int attr, int baseattr, RichString* str) {
- int start = RichString_size(str), finish = 0;
- const char* comm = this->comm;
+/*
+ * TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is
+ * not available in an userspace header - so define it.
+ *
+ * Note: This is taken from LINUX headers, but implicitly taken for other platforms
+ * for sake of brevity.
+ *
+ * Note: when colorizing a basename with the comm prefix, the entire basename
+ * (not just the comm prefix) is colorized for better readability, and it is
+ * implicit that only upto (TASK_COMM_LEN - 1) could be comm.
+ */
+#define TASK_COMM_LEN 16
- if (this->settings->highlightBaseName || !this->settings->showProgramPath) {
- int i, basename = 0;
- for (i = 0; i < this->basenameOffset; i++) {
- if (comm[i] == '/') {
- basename = i + 1;
- } else if (comm[i] == ':') {
- finish = i + 1;
- break;
+static bool findCommInCmdline(const char* comm, const char* cmdline, int cmdlineBasenameStart, int* pCommStart, int* pCommEnd) {
+ /* Try to find procComm in tokenized cmdline - this might in rare cases
+ * mis-identify a string or fail, if comm or cmdline had been unsuitably
+ * modified by the process */
+ const char* tokenBase;
+ size_t tokenLen;
+ const size_t commLen = strlen(comm);
+
+ if (cmdlineBasenameStart < 0)
+ return false;
+
+ for (const char* token = cmdline + cmdlineBasenameStart; *token;) {
+ for (tokenBase = token; *token && *token != '\n'; ++token) {
+ if (*token == '/') {
+ tokenBase = token + 1;
}
}
- if (!finish) {
- if (this->settings->showProgramPath) {
- start += basename;
- } else {
- comm += basename;
+ tokenLen = token - tokenBase;
+
+ if ((tokenLen == commLen || (tokenLen > commLen && commLen == (TASK_COMM_LEN - 1))) &&
+ strncmp(tokenBase, comm, commLen) == 0) {
+ *pCommStart = tokenBase - cmdline;
+ *pCommEnd = token - cmdline;
+ return true;
+ }
+
+ if (*token) {
+ do {
+ ++token;
+ } while (*token && '\n' == *token);
+ }
+ }
+ return false;
+}
+
+static int matchCmdlinePrefixWithExeSuffix(const char* cmdline, int cmdlineBaseOffset, const char* exe, int exeBaseOffset, int exeBaseLen) {
+ int matchLen; /* matching length to be returned */
+ char delim; /* delimiter following basename */
+
+ /* cmdline prefix is an absolute path: it must match whole exe. */
+ if (cmdline[0] == '/') {
+ matchLen = exeBaseLen + exeBaseOffset;
+ if (strncmp(cmdline, exe, matchLen) == 0) {
+ delim = cmdline[matchLen];
+ if (delim == 0 || delim == '\n' || delim == ' ') {
+ return matchLen;
+ }
+ }
+ return 0;
+ }
+
+ /* cmdline prefix is a relative path: We need to first match the basename at
+ * cmdlineBaseOffset and then reverse match the cmdline prefix with the exe
+ * suffix. But there is a catch: Some processes modify their cmdline in ways
+ * that make htop's identification of the basename in cmdline unreliable.
+ * For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to
+ * "gdm-session-worker [pam/gdm-autologin]" and htop ends up with
+ * proccmdlineBasenameEnd at "gdm-autologin]". This issue could arise with
+ * chrome as well as it stores in cmdline its concatenated argument vector,
+ * without NUL delimiter between the arguments (which may contain a '/')
+ *
+ * So if needed, we adjust cmdlineBaseOffset to the previous (if any)
+ * component of the cmdline relative path, and retry the procedure. */
+ bool delimFound; /* if valid basename delimiter found */
+ do {
+ /* match basename */
+ matchLen = exeBaseLen + cmdlineBaseOffset;
+ if (cmdlineBaseOffset < exeBaseOffset &&
+ strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) {
+ delim = cmdline[matchLen];
+ if (delim == 0 || delim == '\n' || delim == ' ') {
+ int i, j;
+ /* reverse match the cmdline prefix and exe suffix */
+ for (i = cmdlineBaseOffset - 1, j = exeBaseOffset - 1;
+ i >= 0 && j >= 0 && cmdline[i] == exe[j]; --i, --j)
+ ;
+
+ /* full match, with exe suffix being a valid relative path */
+ if (i < 0 && j >= 0 && exe[j] == '/')
+ return matchLen;
+ }
+ }
+
+ /* Try to find the previous potential cmdlineBaseOffset - it would be
+ * preceded by '/' or nothing, and delimited by ' ' or '\n' */
+ for (delimFound = false, cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) {
+ if (delimFound) {
+ if (cmdline[cmdlineBaseOffset - 1] == '/') {
+ break;
+ }
+ } else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\n') {
+ delimFound = true;
+ }
+ }
+ } while (delimFound);
+
+ return 0;
+}
+
+/* stpcpy, but also converts newlines to spaces */
+static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr) {
+ for (; *srcStr; ++srcStr) {
+ *dstStr++ = (*srcStr == '\n') ? ' ' : *srcStr;
+ }
+ *dstStr = 0;
+ return dstStr;
+}
+
+/*
+ * This function makes the merged Command string. It also stores the offsets of the
+ * basename, comm w.r.t the merged Command string - these offsets will be used by
+ * Process_writeCommand() for coloring. The merged Command string is also
+ * returned by Process_getCommand() for searching, sorting and filtering.
+ */
+void Process_makeCommandStr(Process* this) {
+ ProcessMergedCommand* mc = &this->mergedCommand;
+ const Settings* settings = this->settings;
+
+ bool showMergedCommand = settings->showMergedCommand;
+ bool showProgramPath = settings->showProgramPath;
+ bool searchCommInCmdline = settings->findCommInCmdline;
+ bool stripExeFromCmdline = settings->stripExeFromCmdline;
+ bool showThreadNames = settings->showThreadNames;
+ 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
+ * - a zombie from before being under htop's watch, or
+ * - a user thread and showThreadNames is not set */
+ if (Process_isKernelThread(this))
+ return;
+ if (this->state == 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->lastUpdate >= settingsStamp)
+ return;
+
+ mc->lastUpdate = settingsStamp;
+
+ /* The field separtor "│" has been chosen such that it will not match any
+ * valid string used for searching or filtering */
+ const char* SEPARATOR = CRT_treeStr[TREE_STR_VERT];
+ const int SEPARATOR_LEN = strlen(SEPARATOR);
+
+ /* Accommodate the column text, two field separators and terminating NUL */
+ size_t maxLen = 2 * SEPARATOR_LEN + 1;
+ maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)");
+ maxLen += this->procComm ? strlen(this->procComm) : 0;
+ maxLen += this->procExe ? strlen(this->procExe) : 0;
+
+ free(mc->str);
+ mc->str = xCalloc(1, maxLen);
+
+ /* Reset all locations that need extra handling when actually displaying */
+ mc->highlightCount = 0;
+ memset(mc->highlights, 0, sizeof(mc->highlights));
+
+ size_t mbMismatch = 0;
+ #define WRITE_HIGHLIGHT(_offset, _length, _attr, _flags) \
+ do { \
+ /* Check if we still have capacity */ \
+ assert(mc->highlightCount < ARRAYSIZE(mc->highlights)); \
+ if (mc->highlightCount >= ARRAYSIZE(mc->highlights)) \
+ break; \
+ \
+ mc->highlights[mc->highlightCount].offset = str - strStart + (_offset) - mbMismatch; \
+ mc->highlights[mc->highlightCount].length = _length; \
+ mc->highlights[mc->highlightCount].attr = _attr; \
+ mc->highlights[mc->highlightCount].flags = _flags; \
+ mc->highlightCount++; \
+ } while (0)
+
+ #define WRITE_SEPARATOR \
+ do { \
+ WRITE_HIGHLIGHT(0, 1, CRT_colors[FAILED_READ], CMDLINE_HIGHLIGHT_FLAG_SEPARATOR); \
+ mbMismatch += SEPARATOR_LEN - 1; \
+ str = stpcpy(str, SEPARATOR); \
+ } while (0)
+
+ #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];
+ const int delLibAttr = CRT_colors[PROCESS_TAG];
+
+ /* Establish some shortcuts to data we need */
+ const char* cmdline = this->cmdline;
+ const char* procComm = this->procComm;
+ const char* procExe = this->procExe;
+
+ char* strStart = mc->str;
+ char* str = strStart;
+
+ int cmdlineBasenameStart = this->cmdlineBasenameStart;
+ int cmdlineBasenameEnd = this->cmdlineBasenameEnd;
+
+ if (!cmdline) {
+ cmdlineBasenameStart = 0;
+ cmdlineBasenameEnd = 0;
+ cmdline = "(zombie)";
+ }
+
+ assert(cmdlineBasenameStart >= 0);
+ assert(cmdlineBasenameStart <= (int)strlen(cmdline));
+
+ if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */
+ if ((showMergedCommand || (Process_isUserlandThread(this) && showThreadNames)) && 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;
}
- finish = this->basenameOffset - basename;
}
- finish += start - 1;
+
+ 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;
+ }
+
+ int exeLen = strlen(this->procExe);
+ int exeBasenameOffset = this->procExeBasenameOffset;
+ int exeBasenameLen = exeLen - exeBasenameOffset;
+
+ assert(exeBasenameOffset >= 0);
+ assert(exeBasenameOffset <= (int)strlen(procExe));
+
+ bool haveCommInExe = false;
+ if (procExe && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {
+ haveCommInExe = strncmp(procExe + exeBasenameOffset, procComm, TASK_COMM_LEN - 1) == 0;
}
- RichString_append(str, attr, comm);
+ /* 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);
+ if (this->procExeDeleted)
+ WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ else if (this->usesDeletedLib)
+ WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ str = stpcpy(str, procExe);
+ } else {
+ if (haveCommInExe)
+ WRITE_HIGHLIGHT(0, exeBasenameLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
+ WRITE_HIGHLIGHT(0, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
+ if (this->procExeDeleted)
+ WRITE_HIGHLIGHT(0, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ else if (this->usesDeletedLib)
+ WRITE_HIGHLIGHT(0, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
+ str = stpcpy(str, procExe + exeBasenameOffset);
+ }
+
+ bool haveCommInCmdline = false;
+ int commStart = 0;
+ int commEnd = 0;
+
+ /* Try to match procComm with procExe's basename: This is reliable (predictable) */
+ if (searchCommInCmdline) {
+ /* commStart/commEnd will be adjusted later along with cmdline */
+ haveCommInCmdline = (!Process_isUserlandThread(this) || showThreadNames) && findCommInCmdline(procComm, cmdline, cmdlineBasenameStart, &commStart, &commEnd);
+ }
+
+ int matchLen = matchCmdlinePrefixWithExeSuffix(cmdline, cmdlineBasenameStart, procExe, exeBasenameOffset, exeBasenameLen);
+
+ bool haveCommField = false;
+
+ if (!haveCommInExe && !haveCommInCmdline && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {
+ WRITE_SEPARATOR;
+ WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
+ str = stpcpy(str, procComm);
+ haveCommField = true;
+ }
+
+ if (matchLen) {
+ if (stripExeFromCmdline) {
+ /* strip the matched exe prefix */
+ cmdline += matchLen;
+
+ commStart -= matchLen;
+ commEnd -= matchLen;
+ } else {
+ matchLen = 0;
+ }
+ }
+
+ if (!matchLen || (haveCommField && *cmdline)) {
+ /* cmdline will be a separate field */
+ 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);
+
+ /* Display cmdline if it hasn't been consumed by procExe */
+ if (*cmdline)
+ (void)stpcpyWithNewlineConversion(str, cmdline);
+
+ #undef CHECK_AND_MARK_DIST_PATH_PREFIXES
+ #undef CHECK_AND_MARK
+ #undef WRITE_SEPARATOR
+ #undef WRITE_HIGHLIGHT
+}
+
+void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str) {
+ (void)baseAttr;
+
+ const ProcessMergedCommand* mc = &this->mergedCommand;
+ const char* mergedCommand = mc->str;
+
+ int strStart = RichString_size(str);
+
+ const bool highlightBaseName = this->settings->highlightBaseName;
+ const bool highlightSeparator = true;
+ const bool highlightDeleted = this->settings->highlightDeletedExe;
+
+ if (!mergedCommand) {
+ int len = 0;
+ const char* cmdline = this->cmdline;
+
+ if (highlightBaseName || !this->settings->showProgramPath) {
+ int basename = 0;
+ for (int i = 0; i < this->cmdlineBasenameEnd; i++) {
+ if (cmdline[i] == '/') {
+ basename = i + 1;
+ } else if (cmdline[i] == ':') {
+ len = i + 1;
+ break;
+ }
+ }
+ if (len == 0) {
+ if (this->settings->showProgramPath) {
+ strStart += basename;
+ } else {
+ cmdline += basename;
+ }
+ len = this->cmdlineBasenameEnd - basename;
+ }
+ }
+
+ RichString_appendWide(str, attr, cmdline);
+
+ if (this->settings->highlightBaseName) {
+ RichString_setAttrn(str, baseAttr, strStart, len);
+ }
+
+ return;
+ }
+
+ RichString_appendWide(str, attr, mergedCommand);
+
+ for (size_t i = 0, hlCount = CLAMP(mc->highlightCount, 0, ARRAYSIZE(mc->highlights)); i < hlCount; i++) {
+ const ProcessCmdlineHighlight* hl = &mc->highlights[i];
+
+ if (!hl->length)
+ continue;
+
+ if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_SEPARATOR)
+ if (!highlightSeparator)
+ continue;
+
+ if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_BASENAME)
+ if (!highlightBaseName)
+ continue;
- if (this->settings->highlightBaseName) {
- RichString_setAttrn(str, baseattr, start, finish);
+ if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_DELETED)
+ 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_outputRate(RichString* str, char* buffer, size_t n, double rate, int coloring) {
+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];
@@ -234,165 +753,286 @@ void Process_outputRate(RichString* str, char* buffer, size_t n, double rate, in
}
if (isnan(rate)) {
- int len = xSnprintf(buffer, n, " N/A ");
- RichString_appendn(str, CRT_colors[PROCESS_SHADOW], buffer, len);
+ RichString_appendAscii(str, shadowColor, " N/A ");
+ } else if (rate < 0.005) {
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
+ RichString_appendnAscii(str, shadowColor, buffer, len);
} else if (rate < ONE_K) {
- int len = snprintf(buffer, n, "%7.2f B/s ", rate);
- RichString_appendn(str, processColor, buffer, len);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
+ RichString_appendnAscii(str, processColor, buffer, len);
} else if (rate < ONE_M) {
- int len = snprintf(buffer, n, "%7.2f K/s ", rate / ONE_K);
- RichString_appendn(str, processColor, buffer, len);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K);
+ RichString_appendnAscii(str, processColor, buffer, len);
} else if (rate < ONE_G) {
- int len = snprintf(buffer, n, "%7.2f M/s ", rate / ONE_M);
- RichString_appendn(str, processMegabytesColor, buffer, len);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M);
+ RichString_appendnAscii(str, processMegabytesColor, buffer, len);
} else if (rate < ONE_T) {
- int len = snprintf(buffer, n, "%7.2f G/s ", rate / ONE_G);
- RichString_appendn(str, largeNumberColor, buffer, len);
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f G/s ", rate / ONE_G);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
+ } else if (rate < ONE_P) {
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f T/s ", rate / ONE_T);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
+ } else {
+ int len = snprintf(buffer, sizeof(buffer), "%7.2f P/s ", rate / ONE_P);
+ RichString_appendnAscii(str, largeNumberColor, buffer, len);
+ }
+}
+
+void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) {
+ int columns = width;
+ RichString_appendnWideColumns(str, attr, content, strlen(content), &columns);
+ RichString_appendChr(str, attr, ' ', width + 1 - columns);
+}
+
+void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr) {
+ if (val >= 0) {
+ if (val < 0.05F)
+ *attr = CRT_colors[PROCESS_SHADOW];
+ else if (val >= 99.9F)
+ *attr = CRT_colors[PROCESS_MEGABYTES];
+
+ int precision = 1;
+
+ // Display "val" as "100" for columns like "MEM%".
+ if (width == 4 && val > 99.9F) {
+ precision = 0;
+ val = 100.0F;
+ }
+
+ xSnprintf(buffer, n, "%*.*f ", width, precision, val);
} else {
- int len = snprintf(buffer, n, "%7.2f T/s ", rate / ONE_T);
- RichString_appendn(str, largeNumberColor, buffer, len);
+ *attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
+ }
+}
+
+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_writeField(const Process* this, RichString* str, ProcessField field) {
- char buffer[256]; buffer[255] = '\0';
+ char buffer[256];
+ size_t n = sizeof(buffer);
int attr = CRT_colors[DEFAULT_COLOR];
- int baseattr = CRT_colors[PROCESS_BASENAME];
- size_t n = sizeof(buffer) - 1;
bool coloring = this->settings->highlightMegabytes;
switch (field) {
- case PERCENT_CPU:
- case PERCENT_NORM_CPU: {
- float cpuPercentage = this->percent_cpu;
- if (field == PERCENT_NORM_CPU) {
- cpuPercentage /= this->processList->cpuCount;
- }
- if (cpuPercentage > 999.9) {
- xSnprintf(buffer, n, "%4u ", (unsigned int)cpuPercentage);
- } else if (cpuPercentage > 99.9) {
- xSnprintf(buffer, n, "%3u. ", (unsigned int)cpuPercentage);
- } else {
- xSnprintf(buffer, n, "%4.1f ", cpuPercentage);
- }
- break;
- }
- case PERCENT_MEM: {
- if (this->percent_mem > 99.9) {
- xSnprintf(buffer, n, "100. ");
- } else {
- xSnprintf(buffer, n, "%4.1f ", this->percent_mem);
- }
- break;
- }
case COMM: {
+ int baseattr = CRT_colors[PROCESS_BASENAME];
if (this->settings->highlightThreads && Process_isThread(this)) {
attr = CRT_colors[PROCESS_THREAD];
baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
}
- if (!this->settings->treeView || this->indent == 0) {
+ const ScreenSettings* ss = this->settings->ss;
+ if (!ss->treeView || this->indent == 0) {
Process_writeCommand(this, attr, baseattr, str);
return;
- } else {
- char* buf = buffer;
- int maxIndent = 0;
- bool lastItem = (this->indent < 0);
- int indent = (this->indent < 0 ? -this->indent : this->indent);
-
- for (int i = 0; i < 32; i++) {
- if (indent & (1U << i)) {
- maxIndent = i+1;
- }
+ }
+
+ char* buf = buffer;
+ const bool lastItem = (this->indent < 0);
+
+ for (uint32_t indent = (this->indent < 0 ? -this->indent : this->indent); indent > 1; indent >>= 1) {
+ int written, ret;
+ if (indent & 1U) {
+ ret = xSnprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]);
+ } else {
+ ret = xSnprintf(buf, n, " ");
+ }
+ if (ret < 0 || (size_t)ret >= n) {
+ written = n;
+ } else {
+ written = ret;
}
+ buf += written;
+ n -= written;
+ }
- for (int i = 0; i < maxIndent - 1; i++) {
- int written, ret;
- if (indent & (1 << i)) {
- ret = snprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]);
- } else {
- ret = snprintf(buf, n, " ");
- }
- if (ret < 0 || (size_t)ret >= n) {
- written = n;
- } else {
- written = ret;
- }
- buf += written;
- n -= written;
+ const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE];
+ xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
+ RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer);
+ Process_writeCommand(this, attr, baseattr, str);
+ return;
+ }
+ case PROC_COMM: {
+ const char* procComm;
+ if (this->procComm) {
+ attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
+ procComm = this->procComm;
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ procComm = Process_isKernelThread(this) ? kthreadID : "N/A";
+ }
+
+ Process_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1);
+ return;
+ }
+ case PROC_EXE: {
+ const char* procExe;
+ if (this->procExe) {
+ attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME];
+ if (this->settings->highlightDeletedExe) {
+ if (this->procExeDeleted)
+ attr = CRT_colors[FAILED_READ];
+ else if (this->usesDeletedLib)
+ attr = CRT_colors[PROCESS_TAG];
}
- const char* draw = CRT_treeStr[lastItem ? (this->settings->direction == 1 ? TREE_STR_BEND : TREE_STR_TEND) : TREE_STR_RTEE];
- xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
- RichString_append(str, CRT_colors[PROCESS_TREE], buffer);
- Process_writeCommand(this, attr, baseattr, str);
- return;
+ procExe = this->procExe + this->procExeBasenameOffset;
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ procExe = Process_isKernelThread(this) ? kthreadID : "N/A";
+ }
+
+ Process_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1);
+ return;
+ }
+ case CWD: {
+ const char* cwd;
+ if (!this->procCwd) {
+ attr = CRT_colors[PROCESS_SHADOW];
+ cwd = "N/A";
+ } else if (String_startsWith(this->procCwd, "/proc/") && strstr(this->procCwd, " (deleted)") != NULL) {
+ attr = CRT_colors[PROCESS_SHADOW];
+ cwd = "main thread terminated";
+ } else {
+ cwd = this->procCwd;
}
+ Process_printLeftAlignedField(str, attr, cwd, 25);
+ return;
}
- case MAJFLT: Process_colorNumber(str, this->majflt, coloring); return;
- case MINFLT: Process_colorNumber(str, this->minflt, coloring); return;
- case M_RESIDENT: Process_humanNumber(str, this->m_resident * CRT_pageSizeKB, coloring); return;
- case M_VIRT: Process_humanNumber(str, this->m_virt * CRT_pageSizeKB, coloring); return;
- case NICE: {
+ case ELAPSED: {
+ const uint64_t rt = this->processList->realtimeMs;
+ const uint64_t st = this->starttime_ctime * 1000;
+ const uint64_t dt =
+ rt < st ? 0 :
+ rt - st;
+ Process_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring);
+ return;
+ }
+ case MAJFLT: Process_printCount(str, this->majflt, coloring); return;
+ case MINFLT: Process_printCount(str, this->minflt, coloring); return;
+ case M_RESIDENT: Process_printKBytes(str, this->m_resident, coloring); return;
+ case M_VIRT: Process_printKBytes(str, this->m_virt, coloring); return;
+ case NICE:
xSnprintf(buffer, n, "%3ld ", this->nice);
attr = this->nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]
: this->nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
- : attr;
+ : CRT_colors[PROCESS_SHADOW];
+ break;
+ case NLWP:
+ if (this->nlwp == 1)
+ attr = CRT_colors[PROCESS_SHADOW];
+
+ xSnprintf(buffer, n, "%4ld ", this->nlwp);
+ break;
+ case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break;
+ case PERCENT_NORM_CPU: {
+ float cpuPercentage = this->percent_cpu / this->processList->activeCPUs;
+ Process_printPercentage(cpuPercentage, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr);
break;
}
- case NLWP: xSnprintf(buffer, n, "%4ld ", this->nlwp); break;
- case PGRP: xSnprintf(buffer, n, Process_pidFormat, this->pgrp); break;
- case PID: xSnprintf(buffer, n, Process_pidFormat, this->pid); break;
- case PPID: xSnprintf(buffer, n, Process_pidFormat, this->ppid); break;
- case PRIORITY: {
- if(this->priority <= -100)
+ case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, 4, &attr); break;
+ case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break;
+ case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pid); break;
+ case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->ppid); break;
+ case PRIORITY:
+ if (this->priority <= -100)
xSnprintf(buffer, n, " RT ");
else
xSnprintf(buffer, n, "%3ld ", this->priority);
break;
- }
case PROCESSOR: xSnprintf(buffer, n, "%3d ", Settings_cpuId(this->settings, this->processor)); break;
- case SESSION: xSnprintf(buffer, n, Process_pidFormat, this->session); 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);
- switch(this->state) {
- case 'R':
- attr = CRT_colors[PROCESS_R_STATE];
- break;
- case 'D':
- attr = CRT_colors[PROCESS_D_STATE];
- break;
+ case STATE:
+ xSnprintf(buffer, n, "%c ", processStateChar(this->state));
+ switch (this->state) {
+ case RUNNABLE:
+ case RUNNING:
+ case TRACED:
+ attr = CRT_colors[PROCESS_RUN_STATE];
+ break;
+
+ case BLOCKED:
+ case DEFUNCT:
+ case STOPPED:
+ case UNINTERRUPTIBLE_WAIT:
+ case ZOMBIE:
+ attr = CRT_colors[PROCESS_D_STATE];
+ break;
+
+ case QUEUED:
+ case WAITING:
+ case IDLE:
+ case SLEEPING:
+ attr = CRT_colors[PROCESS_SHADOW];
+ break;
+
+ case UNKNOWN:
+ case PAGING:
+ break;
}
break;
- }
- case ST_UID: xSnprintf(buffer, n, "%5d ", this->st_uid); break;
- case TIME: Process_printTime(str, this->time); return;
- case TGID: xSnprintf(buffer, n, Process_pidFormat, this->tgid); break;
- case TPGID: xSnprintf(buffer, n, Process_pidFormat, this->tpgid); break;
- case TTY_NR: xSnprintf(buffer, n, "%3u:%3u ", major(this->tty_nr), minor(this->tty_nr)); break;
- case USER: {
- if (Process_getuid != this->st_uid)
+ case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break;
+ case TIME: Process_printTime(str, this->time, coloring); return;
+ case TGID:
+ if (this->tgid == this->pid)
attr = CRT_colors[PROCESS_SHADOW];
- if (this->user) {
- xSnprintf(buffer, n, "%-9s ", this->user);
+
+ xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tgid);
+ break;
+ case TPGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tpgid); break;
+ case TTY:
+ if (!this->tty_name) {
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, "(no tty) ");
} else {
- xSnprintf(buffer, n, "%-9d ", this->st_uid);
+ const char* name = String_startsWith(this->tty_name, "/dev/") ? (this->tty_name + strlen("/dev/")) : this->tty_name;
+ xSnprintf(buffer, n, "%-8s ", name);
}
- if (buffer[9] != '\0') {
- buffer[9] = ' ';
- buffer[10] = '\0';
+ break;
+ case USER:
+ if (Process_getuid != this->st_uid)
+ attr = CRT_colors[PROCESS_SHADOW];
+
+ if (this->user) {
+ Process_printLeftAlignedField(str, attr, this->user, 10);
+ return;
}
+
+ xSnprintf(buffer, n, "%-10d ", this->st_uid);
break;
- }
default:
+ if (DynamicColumn_writeField(this, str, field))
+ return;
+ assert(0 && "Process_writeField: default key reached"); /* should never be reached */
xSnprintf(buffer, n, "- ");
+ break;
}
- RichString_append(str, attr, buffer);
+ RichString_appendAscii(str, attr, buffer);
}
void Process_display(const Object* cast, RichString* out) {
const Process* this = (const Process*) cast;
- const ProcessField* fields = this->settings->fields;
- RichString_prune(out);
+ const ProcessField* fields = this->settings->ss->fields;
for (int i = 0; fields[i]; i++)
As_Process(this)->writeField(this, out, fields[i]);
@@ -412,16 +1052,28 @@ void Process_display(const Object* cast, RichString* out) {
}
}
- assert(out->chlen > 0);
+ assert(RichString_size(out) > 0);
}
void Process_done(Process* this) {
assert (this != NULL);
- free(this->comm);
+ free(this->cmdline);
+ free(this->procComm);
+ free(this->procExe);
+ free(this->procCwd);
+ free(this->mergedCommand.str);
+ free(this->tty_name);
}
-static const char* Process_getCommandStr(const Process* p) {
- return p->comm ? p->comm : "";
+/* This function returns the string displayed in Command column, so that sorting
+ * happens on what is displayed - whether comm, full path, basename, etc.. So
+ * this follows Process_writeField(COMM) and Process_writeCommand */
+const char* Process_getCommand(const Process* this) {
+ if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !this->mergedCommand.str) {
+ return this->cmdline;
+ }
+
+ return this->mergedCommand.str;
}
const ProcessClass Process_class = {
@@ -432,16 +1084,16 @@ const ProcessClass Process_class = {
.compare = Process_compare
},
.writeField = Process_writeField,
- .getCommandStr = Process_getCommandStr,
};
-void Process_init(Process* this, const struct Settings_* settings) {
+void Process_init(Process* this, const Settings* settings) {
this->settings = settings;
this->tag = false;
this->showChildren = true;
this->show = true;
this->updated = false;
- this->basenameOffset = -1;
+ this->cmdlineBasenameEnd = -1;
+ this->st_uid = (uid_t)-1;
if (Process_getuid == (uid_t)-1) {
Process_getuid = getuid();
@@ -449,26 +1101,28 @@ void Process_init(Process* this, const struct Settings_* settings) {
}
void Process_toggleTag(Process* this) {
- this->tag = this->tag == true ? false : true;
+ this->tag = !this->tag;
}
bool Process_isNew(const Process* this) {
assert(this->processList);
- if (this->processList->scanTs >= this->seenTs) {
- return this->processList->scanTs - this->seenTs <= this->processList->settings->highlightDelaySecs;
+ if (this->processList->monotonicMs >= this->seenStampMs) {
+ return this->processList->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)this->processList->settings->highlightDelaySecs;
}
return false;
}
bool Process_isTomb(const Process* this) {
- return this->tombTs > 0;
+ return this->tombStampMs > 0;
}
bool Process_setPriority(Process* this, int priority) {
- CRT_dropPrivileges();
+ if (Settings_isReadonly())
+ return false;
+
int old_prio = getpriority(PRIO_PROCESS, this->pid);
int err = setpriority(PRIO_PROCESS, this->pid, priority);
- CRT_restorePrivileges();
+
if (err == 0 && old_prio != getpriority(PRIO_PROCESS, this->pid)) {
this->nice = priority;
}
@@ -480,47 +1134,61 @@ bool Process_changePriorityBy(Process* this, Arg delta) {
}
bool Process_sendSignal(Process* this, Arg sgn) {
- CRT_dropPrivileges();
- bool ok = (kill(this->pid, sgn.i) == 0);
- CRT_restorePrivileges();
- return ok;
+ return kill(this->pid, sgn.i) == 0;
}
-long Process_pidCompare(const void* v1, const void* v2) {
+int Process_compare(const void* v1, const void* v2) {
const Process* p1 = (const Process*)v1;
const Process* p2 = (const Process*)v2;
- return (p1->pid - p2->pid);
+
+ const Settings* settings = p1->settings;
+ const ScreenSettings* ss = settings->ss;
+
+ 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 (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;
}
-long Process_compare(const void* v1, const void* v2) {
- const Process *p1, *p2;
- const Settings *settings = ((const Process*)v1)->settings;
+int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) {
int r;
- if (settings->direction == 1) {
- p1 = (const Process*)v1;
- p2 = (const Process*)v2;
- } else {
- p2 = (const Process*)v1;
- p1 = (const Process*)v2;
- }
-
- switch (settings->sortKey) {
+ switch (key) {
case PERCENT_CPU:
case PERCENT_NORM_CPU:
- return SPACESHIP_NUMBER(p2->percent_cpu, p1->percent_cpu);
+ return SPACESHIP_NUMBER(p1->percent_cpu, p2->percent_cpu);
case PERCENT_MEM:
- return SPACESHIP_NUMBER(p2->m_resident, p1->m_resident);
+ return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
case COMM:
return SPACESHIP_NULLSTR(Process_getCommand(p1), Process_getCommand(p2));
+ case PROC_COMM: {
+ const char* comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : "");
+ const char* comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : "");
+ return SPACESHIP_NULLSTR(comm1, comm2);
+ }
+ case PROC_EXE: {
+ const char* exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : "");
+ const char* exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : "");
+ return SPACESHIP_NULLSTR(exe1, exe2);
+ }
+ case CWD:
+ return SPACESHIP_NULLSTR(p1->procCwd, p2->procCwd);
+ case ELAPSED:
+ r = -SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
+ return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid);
case MAJFLT:
- return SPACESHIP_NUMBER(p2->majflt, p1->majflt);
+ return SPACESHIP_NUMBER(p1->majflt, p2->majflt);
case MINFLT:
- return SPACESHIP_NUMBER(p2->minflt, p1->minflt);
+ return SPACESHIP_NUMBER(p1->minflt, p2->minflt);
case M_RESIDENT:
- return SPACESHIP_NUMBER(p2->m_resident, p1->m_resident);
+ return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
case M_VIRT:
- return SPACESHIP_NUMBER(p2->m_virt, p1->m_virt);
+ return SPACESHIP_NUMBER(p1->m_virt, p2->m_virt);
case NICE:
return SPACESHIP_NUMBER(p1->nice, p2->nice);
case NLWP:
@@ -541,20 +1209,131 @@ long Process_compare(const void* v1, const void* v2) {
r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid);
case STATE:
- return SPACESHIP_NUMBER(Process_sortState(p1->state), Process_sortState(p2->state));
+ return SPACESHIP_NUMBER(p1->state, p2->state);
case ST_UID:
return SPACESHIP_NUMBER(p1->st_uid, p2->st_uid);
case TIME:
- return SPACESHIP_NUMBER(p2->time, p1->time);
+ return SPACESHIP_NUMBER(p1->time, p2->time);
case TGID:
return SPACESHIP_NUMBER(p1->tgid, p2->tgid);
case TPGID:
return SPACESHIP_NUMBER(p1->tpgid, p2->tpgid);
- case TTY_NR:
- return SPACESHIP_NUMBER(p1->tty_nr, p2->tty_nr);
+ case TTY:
+ /* Order no tty last */
+ return SPACESHIP_DEFAULTSTR(p1->tty_name, p2->tty_name, "\x7F");
case USER:
return SPACESHIP_NULLSTR(p1->user, p2->user);
default:
+ 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);
}
}
+
+void Process_updateComm(Process* this, const char* comm) {
+ if (!this->procComm && !comm)
+ return;
+
+ if (this->procComm && comm && String_eq(this->procComm, comm))
+ return;
+
+ free(this->procComm);
+ this->procComm = comm ? xStrdup(comm) : NULL;
+
+ this->mergedCommand.lastUpdate = 0;
+}
+
+static int skipPotentialPath(const char* cmdline, int end) {
+ if (cmdline[0] != '/')
+ return 0;
+
+ int slash = 0;
+ for (int i = 1; i < end; i++) {
+ if (cmdline[i] == '/' && cmdline[i + 1] != '\0') {
+ slash = i + 1;
+ continue;
+ }
+
+ if (cmdline[i] == ' ' && cmdline[i - 1] != '\\')
+ return slash;
+
+ if (cmdline[i] == ':' && cmdline[i + 1] == ' ')
+ return slash;
+ }
+
+ return slash;
+}
+
+void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd) {
+ assert(basenameStart >= 0);
+ assert((cmdline && basenameStart < (int)strlen(cmdline)) || (!cmdline && basenameStart == 0));
+ assert((basenameEnd > basenameStart) || (basenameEnd == 0 && basenameStart == 0));
+ assert((cmdline && basenameEnd <= (int)strlen(cmdline)) || (!cmdline && basenameEnd == 0));
+
+ if (!this->cmdline && !cmdline)
+ return;
+
+ if (this->cmdline && cmdline && String_eq(this->cmdline, cmdline))
+ return;
+
+ free(this->cmdline);
+ this->cmdline = cmdline ? xStrdup(cmdline) : NULL;
+ this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);
+ this->cmdlineBasenameEnd = basenameEnd;
+
+ this->mergedCommand.lastUpdate = 0;
+}
+
+void Process_updateExe(Process* this, const char* exe) {
+ if (!this->procExe && !exe)
+ return;
+
+ if (this->procExe && exe && String_eq(this->procExe, exe))
+ return;
+
+ free(this->procExe);
+ if (exe) {
+ this->procExe = xStrdup(exe);
+ const char* lastSlash = strrchr(exe, '/');
+ this->procExeBasenameOffset = (lastSlash && *(lastSlash + 1) != '\0' && lastSlash != exe) ? (lastSlash - exe + 1) : 0;
+ } else {
+ this->procExe = NULL;
+ this->procExeBasenameOffset = 0;
+ }
+
+ this->mergedCommand.lastUpdate = 0;
+}
+
+uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 };
+
+void Process_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);
+ Process_fieldWidths[i] = (uint8_t)len;
+ }
+}
+
+void Process_updateFieldWidth(ProcessField key, size_t width) {
+ if (width > UINT8_MAX)
+ Process_fieldWidths[key] = UINT8_MAX;
+ else if (width > Process_fieldWidths[key])
+ Process_fieldWidths[key] = (uint8_t)width;
+}
+
+void Process_updateCPUFieldWidths(float percentage) {
+ if (percentage < 99.9F) {
+ Process_updateFieldWidth(PERCENT_CPU, 4);
+ Process_updateFieldWidth(PERCENT_NORM_CPU, 4);
+ return;
+ }
+
+ // Add additional two characters, one for "." and another for precision.
+ uint8_t width = ceil(log10(percentage + 0.1)) + 2;
+
+ Process_updateFieldWidth(PERCENT_CPU, width);
+ Process_updateFieldWidth(PERCENT_NORM_CPU, width);
+}
diff --git a/Process.h b/Process.h
index 48e31b1..eb79470 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,17 +13,16 @@ in the source distribution for its full text.
#include <sys/types.h>
#include "Object.h"
+#include "ProcessField.h"
#include "RichString.h"
-#ifdef __ANDROID__
-#define SYS_ioprio_get __NR_ioprio_get
-#define SYS_ioprio_set __NR_ioprio_set
-#endif
-#define PROCESS_FLAG_IO 0x0001
+#define PROCESS_FLAG_IO 0x00000001
+#define PROCESS_FLAG_CWD 0x00000002
+
#define DEFAULT_HIGHLIGHT_SECS 5
-typedef enum ProcessFields {
+typedef enum ProcessField_ {
NULL_PROCESSFIELD = 0,
PID = 1,
COMM = 2,
@@ -31,7 +30,7 @@ typedef enum ProcessFields {
PPID = 4,
PGRP = 5,
SESSION = 6,
- TTY_NR = 7,
+ TTY = 7,
TPGID = 8,
MINFLT = 10,
MAJFLT = 12,
@@ -49,101 +48,264 @@ typedef enum ProcessFields {
NLWP = 51,
TGID = 52,
PERCENT_NORM_CPU = 53,
+ ELAPSED = 54,
+ PROC_COMM = 124,
+ PROC_EXE = 125,
+ CWD = 126,
+
+ /* Platform specific fields, defined in ${platform}/ProcessField.h */
+ PLATFORM_PROCESS_FIELDS
+
+ /* Do not add new fields after this entry (dynamic entries follow) */
+ LAST_PROCESSFIELD
} ProcessField;
-typedef struct ProcessPidColumn_ {
- int id;
- const char* label;
-} ProcessPidColumn;
+/* 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 Settings_;
+/* Holds information about regions of the cmdline that should be
+ * highlighted (e.g. program basename, delimiter, comm). */
+typedef struct ProcessCmdlineHighlight_ {
+ size_t offset; /* first character to highlight */
+ size_t length; /* How many characters to highlight, zero if unused */
+ int attr; /* The attributes used to highlight */
+ int flags; /* Special flags used for selective highlighting, zero for always */
+} ProcessCmdlineHighlight;
+
+/* ProcessMergedCommand is populated by Process_makeCommandStr: It
+ * contains the merged Command string, and the information needed by
+ * Process_writeCommand to color the string. str will be NULL for kernel
+ * threads and zombies */
+typedef struct ProcessMergedCommand_ {
+ 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 */
+} ProcessMergedCommand;
+
typedef struct Process_ {
+ /* Super object for emulated OOP */
Object super;
+ /* Pointer to quasi-global data structures */
const struct ProcessList_* processList;
const struct Settings_* settings;
- unsigned long long int time;
+ /* Process identifier */
pid_t pid;
+
+ /* Parent process identifier */
pid_t ppid;
+
+ /* Thread group identifier */
pid_t tgid;
- char* comm; /* use Process_getCommand() for Command actually displayed */
- int commLen;
- int indent;
- int basenameOffset;
- bool updated;
+ /* Process group identifier */
+ int pgrp;
- char state;
- bool tag;
- bool showChildren;
- bool show;
- bool wasShown;
- unsigned int pgrp;
- unsigned int session;
- unsigned int tty_nr;
+ /* Session identifier */
+ int session;
+
+ /* Foreground group identifier of the controlling terminal */
int tpgid;
+
+ /* This is a kernel (helper) task */
+ bool isKernelThread;
+
+ /* This is a userland thread / LWP */
+ bool isUserlandThread;
+
+ /* This process is running inside a container */
+ bool isRunningInContainer;
+
+ /* Controlling terminal identifier of the process */
+ unsigned long int tty_nr;
+
+ /* Controlling terminal name of the process */
+ char* tty_name;
+
+ /* User identifier */
uid_t st_uid;
- unsigned long int flags;
+
+ /* User name */
+ const char* user;
+
+ /* Process runtime (in hundredth of a second) */
+ unsigned long long int time;
+
+ /*
+ * Process name including arguments.
+ * Use Process_getCommand() for Command actually displayed.
+ */
+ char* cmdline;
+
+ /* End Offset in cmdline of the process basename */
+ int cmdlineBasenameEnd;
+
+ /* Start Offset in cmdline of the process basename */
+ int cmdlineBasenameStart;
+
+ /* The process' "command" name */
+ char* procComm;
+
+ /* The main process executable */
+ char* procExe;
+
+ /* The process/thread working directory */
+ char* procCwd;
+
+ /* Offset in procExe of the process basename */
+ int procExeBasenameOffset;
+
+ /* Tells if the executable has been replaced in the filesystem since start */
+ bool procExeDeleted;
+
+ /* Tells if the process uses replaced shared libraries since start */
+ bool usesDeletedLib;
+
+ /* CPU number last executed on */
int processor;
+ /* CPU usage during last cycle (in percent) */
float percent_cpu;
+
+ /* Memory usage during last cycle (in percent) */
float percent_mem;
- const char* user;
+ /* Scheduling priority */
long int priority;
+
+ /* Nice value */
long int nice;
+
+ /* Number of threads in this process */
long int nlwp;
- char starttime_show[8];
+
+ /* Process start time (in seconds elapsed since the Epoch) */
time_t starttime_ctime;
- long m_virt;
- long m_resident;
+ /* Process start time (cached formatted string) */
+ char starttime_show[8];
- int exit_signal;
+ /* Total program size (in kilobytes) */
+ long m_virt;
- time_t seenTs;
- time_t tombTs;
+ /* Resident set size (in kilobytes) */
+ long m_resident;
+ /* Number of minor faults the process has made which have not required loading a memory page from disk */
unsigned long int minflt;
+
+ /* Number of major faults the process has made which have required loading a memory page from disk */
unsigned long int majflt;
- unsigned int tree_left;
- unsigned int tree_right;
+ /* Process state enum field (platform dependent) */
+ ProcessState state;
+
+ /* Whether the process was updated during the current scan */
+ bool updated;
+
+ /* Whether the process was tagged by the user */
+ bool tag;
+
+ /* Whether to display this process */
+ bool show;
+
+ /* Whether this process was shown last cycle */
+ bool wasShown;
+
+ /* Whether to show children of this process in tree-mode */
+ bool showChildren;
+
+ /*
+ * Internal time counts for showing new and exited processes.
+ */
+ uint64_t seenStampMs;
+ uint64_t tombStampMs;
+
+ /*
+ * Internal state for tree-mode.
+ */
+ int32_t indent;
unsigned int tree_depth;
- unsigned int tree_index;
+
+ /* Has no known parent process */
+ bool isRoot;
+
+ /*
+ * Internal state for merged Command display
+ */
+ ProcessMergedCommand mergedCommand;
} Process;
typedef struct ProcessFieldData_ {
+ /* Name (displayed in setup menu) */
const char* name;
+
+ /* Title (display in main screen); must have same width as the printed values */
const char* title;
+
+ /* Description (displayed in setup menu) */
const char* description;
+
+ /* Scan flag to enable scan-method otherwise not run */
uint32_t flags;
+
+ /* Whether the values are process identifiers; adjusts the width of title and values if true */
+ bool pidColumn;
+
+ /* Whether the column should be sorted in descending order by default */
+ bool defaultSortDesc;
+
+ /* Whether the column width is dynamically adjusted (the minimum width is determined by the title length) */
+ bool autoWidth;
} ProcessFieldData;
// Implemented in platform-specific code:
void Process_writeField(const Process* this, RichString* str, ProcessField field);
-long Process_compare(const void* v1, const void* v2);
+int Process_compare(const void* v1, const void* v2);
void Process_delete(Object* cast);
-bool Process_isThread(const Process* this);
-extern ProcessFieldData Process_fields[];
-extern ProcessPidColumn Process_pidColumns[];
-extern char Process_pidFormat[20];
-
-typedef Process*(*Process_New)(const struct Settings_*);
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
+extern uint8_t Process_fieldWidths[LAST_PROCESSFIELD];
+#define PROCESS_MIN_PID_DIGITS 5
+#define PROCESS_MAX_PID_DIGITS 19
+#define PROCESS_MIN_UID_DIGITS 5
+#define PROCESS_MAX_UID_DIGITS 20
+extern int Process_pidDigits;
+extern int Process_uidDigits;
+
+typedef Process* (*Process_New)(const struct Settings_*);
typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField);
-typedef const char* (*Process_GetCommandStr)(const Process*);
+typedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField);
typedef struct ProcessClass_ {
const ObjectClass super;
const Process_WriteField writeField;
- const Process_GetCommandStr getCommandStr;
+ const Process_CompareByKey compareByKey;
} ProcessClass;
-#define As_Process(this_) ((const ProcessClass*)((this_)->super.klass))
+#define As_Process(this_) ((const ProcessClass*)((this_)->super.klass))
-#define Process_getCommand(this_) (As_Process(this_)->getCommandStr ? As_Process(this_)->getCommandStr((const Process*)(this_)) : ((const Process*)(this_))->comm)
+#define Process_compareByKey(p1_, p2_, key_) (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_))
static inline pid_t Process_getParentPid(const Process* this) {
return this->tgid == this->pid ? this->ppid : this->tgid;
@@ -153,30 +315,61 @@ static inline bool Process_isChildOf(const Process* this, pid_t pid) {
return pid == Process_getParentPid(this);
}
-#define Process_sortState(state) ((state) == 'I' ? 0x100 : (state))
+static inline bool Process_isKernelThread(const Process* this) {
+ return this->isKernelThread;
+}
+
+static inline bool Process_isUserlandThread(const Process* this) {
+ return this->isUserlandThread;
+}
+static inline bool Process_isThread(const Process* this) {
+ return Process_isUserlandThread(this) || Process_isKernelThread(this);
+}
+
+#define CMDLINE_HIGHLIGHT_FLAG_SEPARATOR 0x00000001
+#define CMDLINE_HIGHLIGHT_FLAG_BASENAME 0x00000002
+#define CMDLINE_HIGHLIGHT_FLAG_COMM 0x00000004
+#define CMDLINE_HIGHLIGHT_FLAG_DELETED 0x00000008
+#define CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR 0x00000010
#define ONE_K 1024UL
#define ONE_M (ONE_K * ONE_K)
#define ONE_G (ONE_M * ONE_K)
#define ONE_T (1ULL * ONE_G * ONE_K)
+#define ONE_P (1ULL * ONE_T * ONE_K)
#define ONE_DECIMAL_K 1000UL
#define ONE_DECIMAL_M (ONE_DECIMAL_K * ONE_DECIMAL_K)
#define ONE_DECIMAL_G (ONE_DECIMAL_M * ONE_DECIMAL_K)
#define ONE_DECIMAL_T (1ULL * ONE_DECIMAL_G * ONE_DECIMAL_K)
+#define ONE_DECIMAL_P (1ULL * ONE_DECIMAL_T * ONE_DECIMAL_K)
void Process_setupColumnWidths(void);
-void Process_humanNumber(RichString* str, unsigned long long number, bool coloring);
+/* Sets the size of the UID column based on the passed UID */
+void Process_setUidColumnWidth(uid_t maxUid);
+
+/* Takes number in bytes (base 1024). Prints 6 columns. */
+void Process_printBytes(RichString* str, unsigned long long number, bool coloring);
-void Process_colorNumber(RichString* str, unsigned long long number, bool coloring);
+/* Takes number in kilo bytes (base 1024). Prints 6 columns. */
+void Process_printKBytes(RichString* str, unsigned long long number, bool coloring);
-void Process_printTime(RichString* str, unsigned long long totalHundredths);
+/* Takes number as count (base 1000). Prints 12 columns. */
+void Process_printCount(RichString* str, unsigned long long number, bool coloring);
+
+/* 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);
void Process_fillStarttimeBuffer(Process* this);
-void Process_outputRate(RichString* str, char* buffer, size_t n, double rate, int coloring);
+void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width);
+
+void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr);
void Process_display(const Object* cast, RichString* out);
@@ -198,6 +391,28 @@ bool Process_changePriorityBy(Process* this, Arg delta);
bool Process_sendSignal(Process* this, Arg sgn);
-long Process_pidCompare(const void* v1, const void* v2);
+static inline int Process_pidEqualCompare(const void* v1, const void* v2) {
+ const pid_t p1 = ((const Process*)v1)->pid;
+ const pid_t p2 = ((const Process*)v2)->pid;
+ return p1 != p2; /* return zero when equal */
+}
+
+int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key);
+
+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_getCommand */
+void Process_makeCommandStr(Process* this);
+
+void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str);
+
+void Process_resetFieldWidths(void);
+void Process_updateFieldWidth(ProcessField key, size_t width);
+void Process_updateCPUFieldWidths(float percentage);
#endif
diff --git a/ProcessList.c b/ProcessList.c
index 2d27339..d115678 100644
--- a/ProcessList.c
+++ b/ProcessList.c
@@ -1,7 +1,7 @@
/*
htop - ProcessList.c
(C) 2004,2005 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,32 +10,37 @@ in the source distribution for its full text.
#include <assert.h>
#include <stdlib.h>
#include <string.h>
-#include <time.h>
#include "CRT.h"
+#include "DynamicColumn.h"
#include "Hashtable.h"
#include "Macros.h"
+#include "Platform.h"
#include "Vector.h"
#include "XUtils.h"
-ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
this->processes = Vector_new(klass, true, DEFAULT_SIZE);
- this->processes2 = Vector_new(klass, true, DEFAULT_SIZE); // tree-view auxiliary buffer
+ this->displayList = Vector_new(klass, false, DEFAULT_SIZE);
this->processTable = Hashtable_new(200, false);
- this->displayTreeSet = Hashtable_new(200, false);
- this->draftingTreeSet = Hashtable_new(200, false);
+ this->needsSort = true;
this->usersTable = usersTable;
this->pidMatchList = pidMatchList;
+ this->dynamicMeters = dynamicMeters;
+ this->dynamicColumns = dynamicColumns;
this->userId = userId;
// set later by platform-specific code
- this->cpuCount = 0;
+ this->activeCPUs = 0;
+ this->existingCPUs = 0;
+ this->monotonicMs = 0;
- this->scanTs = 0;
+ // always maintain valid realtime timestamps
+ Platform_gettime_realtime(&this->realtime, &this->realtimeMs);
#ifdef HAVE_LIBHWLOC
this->topologyOk = false;
@@ -67,11 +72,9 @@ void ProcessList_done(ProcessList* this) {
}
#endif
- Hashtable_delete(this->draftingTreeSet);
- Hashtable_delete(this->displayTreeSet);
Hashtable_delete(this->processTable);
- Vector_delete(this->processes2);
+ Vector_delete(this->displayList);
Vector_delete(this->processes);
}
@@ -79,379 +82,265 @@ void ProcessList_setPanel(ProcessList* this, Panel* panel) {
this->panel = panel;
}
-void ProcessList_printHeader(ProcessList* this, RichString* header) {
- RichString_prune(header);
+static const char* alignedDynamicColumnTitle(const ProcessList* this, int key, char* titleBuffer, size_t titleBufferSize) {
+ const DynamicColumn* column = Hashtable_get(this->dynamicColumns, key);
+ if (column == NULL)
+ return "- ";
+ int width = column->width;
+ if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
+ width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
+ xSnprintf(titleBuffer, titleBufferSize, "%*s", width, column->heading);
+ return titleBuffer;
+}
+
+static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessField field) {
+ static char titleBuffer[UINT8_MAX + sizeof(" ")];
+ assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" "));
+ assert(sizeof(titleBuffer) >= PROCESS_MAX_PID_DIGITS + sizeof(" "));
+ assert(sizeof(titleBuffer) >= PROCESS_MAX_UID_DIGITS + sizeof(" "));
+
+ if (field >= LAST_PROCESSFIELD)
+ return alignedDynamicColumnTitle(this, field, titleBuffer, sizeof(titleBuffer));
+
+ const char* title = Process_fields[field].title;
+ if (!title)
+ return "- ";
+
+ if (Process_fields[field].pidColumn) {
+ xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title);
+ return titleBuffer;
+ }
+
+ if (field == ST_UID) {
+ xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_uidDigits, title);
+ return titleBuffer;
+ }
+
+ if (Process_fields[field].autoWidth) {
+ if (field == PERCENT_CPU)
+ xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_fieldWidths[field], title);
+ else
+ xSnprintf(titleBuffer, sizeof(titleBuffer), "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title);
+ return titleBuffer;
+ }
+
+ return title;
+}
+
+void ProcessList_printHeader(const ProcessList* this, RichString* header) {
+ RichString_rewind(header, RichString_size(header));
- const ProcessField* fields = this->settings->fields;
+ const Settings* settings = this->settings;
+ const ScreenSettings* ss = settings->ss;
+ const ProcessField* fields = ss->fields;
+
+ ProcessField key = ScreenSettings_getActiveSortKey(ss);
for (int i = 0; fields[i]; i++) {
- const char* field = Process_fields[fields[i]].title;
- if (!field) {
- field = "- ";
+ 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];
}
- int color = (this->settings->sortKey == fields[i]) ?
- CRT_colors[PANEL_SELECTION_FOCUS] : CRT_colors[PANEL_HEADER_FOCUS];
- RichString_append(header, color, field);
- if (COMM == fields[i] && this->settings->showMergedCommand) {
- RichString_append(header, color, "(merged)");
+ RichString_appendWide(header, color, alignedProcessFieldTitle(this, fields[i]));
+ if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') {
+ bool ascending = ScreenSettings_getActiveDirection(ss) == 1;
+ RichString_rewind(header, 1); // rewind to override space
+ RichString_appendnWide(header,
+ CRT_colors[PANEL_SELECTION_FOCUS],
+ CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC],
+ 1);
+ }
+ if (COMM == fields[i] && settings->showMergedCommand) {
+ RichString_appendAscii(header, color, "(merged)");
}
}
}
void ProcessList_add(ProcessList* this, Process* p) {
- assert(Vector_indexOf(this->processes, p, Process_pidCompare) == -1);
+ assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) == -1);
assert(Hashtable_get(this->processTable, p->pid) == NULL);
p->processList = this;
// highlighting processes found in first scan by first scan marked "far in the past"
- p->seenTs = this->scanTs;
+ 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(Vector_indexOf(this->processes, p, Process_pidEqualCompare) != -1);
assert(Hashtable_get(this->processTable, p->pid) != NULL);
- assert(Hashtable_count(this->processTable) == Vector_count(this->processes));
+ assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable)));
}
-void ProcessList_remove(ProcessList* this, Process* p) {
- assert(Vector_indexOf(this->processes, p, Process_pidCompare) != -1);
- assert(Hashtable_get(this->processTable, p->pid) != NULL);
+// ProcessList_removeIndex removes Process p from the list's map and soft deletes
+// it from its vector. Vector_compact *must* be called once the caller is done
+// removing items.
+// Should only be called from ProcessList_scan to avoid breaking dying process highlighting.
+static void ProcessList_removeIndex(ProcessList* this, const Process* p, int idx) {
+ pid_t pid = p->pid;
- Process* pp = Hashtable_remove(this->processTable, p->pid);
- assert(pp == p); (void)pp;
+ assert(p == (Process*)Vector_get(this->processes, idx));
+ assert(Hashtable_get(this->processTable, pid) != NULL);
- unsigned int pid = p->pid;
- int idx = Vector_indexOf(this->processes, p, Process_pidCompare);
- assert(idx != -1);
+ Hashtable_remove(this->processTable, pid);
+ Vector_softRemove(this->processes, idx);
- 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); (void)pid;
- assert(Hashtable_count(this->processTable) == Vector_count(this->processes));
+ assert(Hashtable_get(this->processTable, pid) == NULL);
+ assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable)));
}
-Process* ProcessList_get(ProcessList* this, int idx) {
- return (Process*)Vector_get(this->processes, idx);
-}
-
-int ProcessList_size(ProcessList* this) {
- return Vector_size(this->processes);
-}
-
-// ProcessList_updateTreeSetLayer sorts this->displayTreeSet,
-// relying only on itself.
-//
-// Algorithm
-//
-// The algorithm is based on `depth-first search`,
-// even though `breadth-first search` approach may be more efficient on first glance,
-// after comparision 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)
+static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, unsigned int level, int32_t indent, bool show) {
+ // On OpenBSD the kernel thread 'swapper' has pid 0.
+ // Do not treat it as root of any tree.
+ if (pid == 0)
return;
- Vector* layer = Vector_new(this->processes->type, 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;
+ // The vector is sorted by parent PID, find the start of the range by bisection
+ int vsize = Vector_size(this->processes);
+ int l = 0;
+ int r = vsize;
+ while (l < r) {
+ int c = (l + r) / 2;
+ Process* process = (Process*)Vector_get(this->processes, c);
+ pid_t ppid = process->isRoot ? 0 : Process_getParentPid(process);
+ if (ppid < pid) {
+ l = c + 1;
} else {
- proc->indent = currentIndent;
+ r = c;
}
-
- 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) {
- 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);
- }
+ // Find the end to know the last line for indent handling purposes
+ int lastShown = r;
+ while (r < vsize) {
+ Process* process = (Process*)Vector_get(this->processes, r);
+ if (!Process_isChildOf(process, pid))
+ break;
+ if (process->show)
+ lastShown = r;
+ r++;
}
- 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)++;
+ for (int i = l; i < r; i++) {
+ Process* process = (Process*)Vector_get(this->processes, i);
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);
- }
+ Vector_add(this->displayList, 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) {
+ int32_t nextIndent = indent | ((int32_t)1 << MINIMUM(level, sizeof(process->indent) * 8 - 2));
+ ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < lastShown) ? nextIndent : indent, process->show && process->showChildren);
+ if (i == lastShown) {
process->indent = -nextIndent;
} else {
process->indent = nextIndent;
}
- int rht = (*node_counter)++;
-
- process->tree_left = lft;
- process->tree_right = rht;
process->tree_depth = level + 1;
- process->tree_index = index;
- Hashtable_put(this->displayTreeSet, index, process);
}
- Vector_delete(children);
}
-static long ProcessList_treeProcessCompare(const void* v1, const void* v2) {
- const Process *p1 = (const Process*)v1;
- const Process *p2 = (const Process*)v2;
+static int compareProcessByKnownParentThenNatural(const void* v1, const void* v2) {
+ const Process* p1 = (const Process*)v1;
+ const Process* p2 = (const Process*)v2;
- return SPACESHIP_NUMBER(p1->tree_left, p2->tree_left);
-}
+ int result = SPACESHIP_NUMBER(
+ p1->isRoot ? 0 : Process_getParentPid(p1),
+ p2->isRoot ? 0 : Process_getParentPid(p2)
+ );
-static long ProcessList_treeProcessCompareByPID(const void* v1, const void* v2) {
- const Process *p1 = (const Process*)v1;
- const Process *p2 = (const Process*)v2;
+ if (result != 0)
+ return result;
- return SPACESHIP_NUMBER(p1->pid, p2->pid);
+ return Process_compare(v1, v2);
}
// Builds a sorted tree from scratch, without relying on previously gathered information
static void ProcessList_buildTree(ProcessList* this) {
- int node_counter = 1;
- int node_index = 0;
- int direction = this->settings->direction;
+ Vector_prune(this->displayList);
- // Sort by PID
- Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompareByPID);
+ // Mark root processes
int vsize = Vector_size(this->processes);
+ for (int i = 0; i < vsize; i++) {
+ Process* process = (Process*)Vector_get(this->processes, i);
+ pid_t ppid = Process_getParentPid(process);
+ process->isRoot = false;
+
+ // If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0)
+ // on Mac OS X 10.11.6) regard this process as root.
+ if (process->pid == ppid) {
+ process->isRoot = true;
+ continue;
+ }
- // 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;
- }
+ // On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2)
+ // use a ppid of 0. As that PID can't exist, we can skip searching for it.
+ if (!ppid) {
+ process->isRoot = true;
+ continue;
+ }
- 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;
- }
- }
+ // We don't know about its parent for whatever reason
+ if (ProcessList_findProcess(this, ppid) == NULL)
+ process->isRoot = true;
+ }
- // 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;
- }
- }
+ // Sort by known parent PID (roots first), then PID
+ Vector_quickSortCustomCompare(this->processes, compareProcessByKnownParentThenNatural);
- // There should be no loop in the process tree
- assert(i < size);
+ // Find all processes whose parent is not visible
+ for (int i = 0; i < vsize; i++) {
+ Process* process = (Process*)Vector_get(this->processes, i);
+
+ // If parent not found, then construct the tree with this node as root
+ if (process->isRoot) {
+ process = (Process*)Vector_get(this->processes, i);
+ process->indent = 0;
+ process->tree_depth = 0;
+ Vector_add(this->displayList, process);
+ ProcessList_buildTreeBranch(this, process->pid, 0, 0, process->showChildren);
+ continue;
+ }
}
- // Swap listings around
- Vector* t = this->processes;
- this->processes = this->processes2;
- this->processes2 = t;
+ this->needsSort = false;
// Check consistency of the built structures
- assert(Vector_size(this->processes) == vsize); (void)vsize;
- assert(Vector_size(this->processes2) == 0);
+ assert(Vector_size(this->displayList) == vsize); (void)vsize;
}
-void ProcessList_sort(ProcessList* this) {
- if (this->settings->treeView) {
- ProcessList_updateTreeSet(this);
- Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompare);
+void ProcessList_updateDisplayList(ProcessList* this) {
+ if (this->settings->ss->treeView) {
+ if (this->needsSort)
+ ProcessList_buildTree(this);
} else {
- Vector_insertionSort(this->processes);
+ if (this->needsSort)
+ Vector_insertionSort(this->processes);
+ Vector_prune(this->displayList);
+ int size = Vector_size(this->processes);
+ for (int i = 0; i < size; i++)
+ Vector_add(this->displayList, Vector_get(this->processes, i));
}
+ this->needsSort = false;
}
ProcessField ProcessList_keyAt(const ProcessList* this, int at) {
int x = 0;
- const ProcessField* fields = this->settings->fields;
+ const ProcessField* fields = this->settings->ss->fields;
ProcessField field;
for (int i = 0; (field = fields[i]); i++) {
- const char* title = Process_fields[field].title;
- if (!title) {
- title = "- ";
- }
-
- int len = strlen(title);
+ int len = strlen(alignedProcessFieldTitle(this, field));
if (at >= x && at <= x + len) {
return field;
}
@@ -468,54 +357,94 @@ void ProcessList_expandTree(ProcessList* this) {
}
}
+// Called on collapse-all toggle and on startup, possibly in non-tree mode
+void ProcessList_collapseAllBranches(ProcessList* this) {
+ ProcessList_buildTree(this); // Update `tree_depth` fields of the processes
+ this->needsSort = true; // ProcessList is sorted by parent now, force new sort
+ int size = Vector_size(this->processes);
+ for (int i = 0; i < size; i++) {
+ Process* process = (Process*) Vector_get(this->processes, i);
+ // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1
+ if (process->tree_depth > 0 && process->pid > 1)
+ process->showChildren = false;
+ }
+}
+
void ProcessList_rebuildPanel(ProcessList* this) {
+ ProcessList_updateDisplayList(this);
+
const char* incFilter = this->incFilter;
- int currPos = Panel_getSelectedIndex(this->panel);
- pid_t currPid = this->following != -1 ? this->following : 0;
- int currScrollV = this->panel->scrollV;
+ const int currPos = Panel_getSelectedIndex(this->panel);
+ const int currScrollV = this->panel->scrollV;
+ const int currSize = Panel_size(this->panel);
Panel_prune(this->panel);
- int size = ProcessList_size(this);
+
+ /* Follow main process if followed a userland thread and threads are now hidden */
+ const Settings* settings = this->settings;
+ if (this->following != -1 && settings->hideUserlandThreads) {
+ const Process* followedProcess = (const Process*) Hashtable_get(this->processTable, this->following);
+ if (followedProcess && Process_isThread(followedProcess) && Hashtable_get(this->processTable, followedProcess->tgid) != NULL) {
+ this->following = followedProcess->tgid;
+ }
+ }
+
+ const int processCount = Vector_size(this->displayList);
int idx = 0;
- for (int i = 0; i < size; i++) {
- bool hidden = false;
- Process* p = ProcessList_get(this, i);
+ bool foundFollowed = false;
+
+ for (int i = 0; i < processCount; i++) {
+ Process* p = (Process*) Vector_get(this->displayList, i);
if ( (!p->show)
|| (this->userId != (uid_t) -1 && (p->st_uid != this->userId))
- || (incFilter && !(String_contains_i(Process_getCommand(p), incFilter)))
+ || (incFilter && !(String_contains_i(Process_getCommand(p), incFilter, true)))
|| (this->pidMatchList && !Hashtable_get(this->pidMatchList, p->tgid)) )
- hidden = true;
+ continue;
- if (!hidden) {
- Panel_set(this->panel, idx, (Object*)p);
- if ((this->following == -1 && idx == currPos) || (this->following != -1 && p->pid == currPid)) {
- Panel_setSelected(this->panel, idx);
- this->panel->scrollV = currScrollV;
- }
- idx++;
+ 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;
+ *preExisting = proc != NULL;
if (proc) {
- assert(Vector_indexOf(this->processes, proc, Process_pidCompare) != -1);
+ assert(Vector_indexOf(this->processes, proc, Process_pidEqualCompare) != -1);
assert(proc->pid == pid);
} else {
proc = constructor(this->settings);
- assert(proc->comm == NULL);
+ assert(proc->cmdline == NULL);
proc->pid = pid;
}
return proc;
}
void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
- struct timespec now;
-
// in pause mode only gather global data for meters (CPU/memory/...)
if (pauseProcessUpdate) {
ProcessList_goThroughEntries(this, true);
@@ -535,46 +464,48 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
this->kernelThreads = 0;
this->runningTasks = 0;
+ Process_resetFieldWidths();
- // set scanTs
+ // set scan timestamp
static bool firstScanDone = false;
- if (!firstScanDone) {
- this->scanTs = 0;
+ if (firstScanDone) {
+ Platform_gettime_monotonic(&this->monotonicMs);
+ } else {
+ this->monotonicMs = 0;
firstScanDone = true;
- } else if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
- this->scanTs = now.tv_sec;
}
ProcessList_goThroughEntries(this, false);
+ uid_t maxUid = 0;
for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
Process* p = (Process*) Vector_get(this->processes, i);
- if (p->tombTs > 0) {
+ Process_makeCommandStr(p);
+
+ // keep track of the highest UID for column scaling
+ if (p->st_uid > maxUid)
+ maxUid = p->st_uid;
+
+ if (p->tombStampMs > 0) {
// remove tombed process
- if (this->scanTs >= p->tombTs) {
- ProcessList_remove(this, p);
+ if (this->monotonicMs >= p->tombStampMs) {
+ ProcessList_removeIndex(this, p, i);
}
} else if (p->updated == false) {
// process no longer exists
if (this->settings->highlightChanges && p->wasShown) {
// mark tombed
- p->tombTs = this->scanTs + this->settings->highlightDelaySecs;
+ p->tombStampMs = this->monotonicMs + 1000 * this->settings->highlightDelaySecs;
} else {
// immediately remove
- ProcessList_remove(this, p);
+ ProcessList_removeIndex(this, p, i);
}
- } else {
- p->updated = false;
}
}
- 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);
+ // Compact the processes vector in case of any deletions
+ Vector_compact(this->processes);
- ProcessList_buildTree(this);
- }
+ // Set UID column width based on max UID.
+ Process_setUidColumnWidth(maxUid);
}
diff --git a/ProcessList.h b/ProcessList.h
index b7ae400..419dea8 100644
--- a/ProcessList.h
+++ b/ProcessList.h
@@ -3,13 +3,16 @@
/*
htop - ProcessList.h
(C) 2004,2005 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 <limits.h>
#include <stdbool.h>
+#include <stdint.h>
+#include <sys/time.h>
#include <sys/types.h>
#include "Hashtable.h"
@@ -34,16 +37,26 @@ in the source distribution for its full text.
#define MAX_READ 2048
#endif
+typedef unsigned long long int memory_t;
+#define MEMORY_MAX ULLONG_MAX
+
typedef struct ProcessList_ {
const Settings* settings;
- Vector* processes;
- Vector* processes2;
- Hashtable* processTable;
+ Vector* processes; /* all known processes; sort order can vary and differ from display order */
+ Vector* displayList; /* process tree flattened in display order (borrowed);
+ updated in ProcessList_updateDisplayList when rebuilding panel */
+ Hashtable* processTable; /* fast known process lookup by PID */
UsersTable* usersTable;
- Hashtable* displayTreeSet;
- Hashtable* draftingTreeSet;
+ bool needsSort;
+
+ Hashtable* dynamicMeters; /* runtime-discovered meters */
+ Hashtable* dynamicColumns; /* runtime-discovered Columns */
+
+ 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;
@@ -56,55 +69,59 @@ typedef struct ProcessList_ {
bool topologyOk;
#endif
- int totalTasks;
- int runningTasks;
- int userlandThreads;
- int kernelThreads;
+ unsigned int totalTasks;
+ unsigned int runningTasks;
+ unsigned int userlandThreads;
+ unsigned int kernelThreads;
- unsigned long long int totalMem;
- unsigned long long int usedMem;
- unsigned long long int buffersMem;
- unsigned long long int cachedMem;
- unsigned long long int totalSwap;
- unsigned long long int usedSwap;
- unsigned long long int freeSwap;
+ memory_t totalMem;
+ memory_t usedMem;
+ memory_t buffersMem;
+ memory_t cachedMem;
+ memory_t sharedMem;
+ memory_t availableMem;
- int cpuCount;
+ memory_t totalSwap;
+ memory_t usedSwap;
+ memory_t cachedSwap;
- time_t scanTs;
+ unsigned int activeCPUs;
+ unsigned int existingCPUs;
} ProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+/* Implemented by platforms */
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* pl);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
-ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_done(ProcessList* this);
void ProcessList_setPanel(ProcessList* this, Panel* panel);
-void ProcessList_printHeader(ProcessList* this, RichString* header);
+void ProcessList_printHeader(const ProcessList* this, RichString* header);
void ProcessList_add(ProcessList* this, Process* p);
-void ProcessList_remove(ProcessList* this, Process* p);
-
-Process* ProcessList_get(ProcessList* this, int idx);
-
-int ProcessList_size(ProcessList* this);
-
-void ProcessList_sort(ProcessList* this);
+void ProcessList_updateDisplayList(ProcessList* this);
ProcessField ProcessList_keyAt(const ProcessList* this, int at);
void ProcessList_expandTree(ProcessList* this);
+void ProcessList_collapseAllBranches(ProcessList* this);
+
void ProcessList_rebuildPanel(ProcessList* this);
Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process_New constructor);
void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate);
+static inline Process* ProcessList_findProcess(ProcessList* this, pid_t pid) {
+ return (Process*) Hashtable_get(this->processTable, pid);
+}
+
#endif
diff --git a/ProcessLocksScreen.c b/ProcessLocksScreen.c
index 3e7762a..57c9ce7 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.
*/
@@ -27,7 +27,8 @@ ProcessLocksScreen* ProcessLocksScreen_new(const Process* process) {
this->pid = process->tgid;
else
this->pid = process->pid;
- return (ProcessLocksScreen*) InfoScreen_init(&this->super, process, NULL, LINES-3, " ID TYPE EXCLUSION READ/WRITE DEVICE:INODE START END FILENAME");
+
+ 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/ProvideCurses.h b/ProvideCurses.h
index c35d696..06602ff 100644
--- a/ProvideCurses.h
+++ b/ProvideCurses.h
@@ -3,7 +3,7 @@
/*
htop - RichString.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.
*/
@@ -12,7 +12,7 @@ in the source distribution for its full text.
// IWYU pragma: begin_exports
-#ifdef HAVE_NCURSESW_CURSES_H
+#if defined(HAVE_NCURSESW_CURSES_H)
#include <ncursesw/curses.h>
#elif defined(HAVE_NCURSES_NCURSES_H)
#include <ncurses/ncurses.h>
diff --git a/README b/README
index 880597d..7ace7cd 100644
--- a/README
+++ b/README
@@ -1,11 +1,12 @@
-# [![htop](htop.png)](https://htop.dev)
+# [![htop logo](htop.png)](https://htop.dev)
[![CI](https://github.com/htop-dev/htop/workflows/CI/badge.svg)](https://github.com/htop-dev/htop/actions)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/21665/badge.svg)](https://scan.coverity.com/projects/21665)
[![Mailing List](https://img.shields.io/badge/Mailing%20List-htop-blue.svg)](https://groups.io/g/htop)
-[![IRC #htop](https://img.shields.io/badge/IRC-htop-blue.svg)](https://webchat.freenode.net/#htop)
-[![Github Release](https://img.shields.io/github/release/htop-dev/htop.svg)](https://github.com/htop-dev/htop/releases/latest)
-[![Download](https://api.bintray.com/packages/htop/source/htop/images/download.svg)](https://bintray.com/htop/source/htop/_latestVersion)
+[![IRC #htop](https://img.shields.io/badge/IRC-htop-blue.svg)](https://web.libera.chat/#htop)
+[![GitHub Release](https://img.shields.io/github/release/htop-dev/htop.svg)](https://github.com/htop-dev/htop/releases/latest)
+[![Packaging status](https://repology.org/badge/tiny-repos/htop.svg)](https://repology.org/project/htop/versions)
+[![License: GPL v2+](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](COPYING?raw=true)
![Screenshot of htop](docs/images/screenshot.png?raw=true)
@@ -14,57 +15,174 @@
`htop` is a cross-platform interactive process viewer.
`htop` allows scrolling the list of processes vertically and horizontally to see their full command lines and related information like memory and CPU consumption.
+Also system wide information, like load average or swap usage, is shown.
The information displayed is configurable through a graphical setup and can be sorted and filtered interactively.
Tasks related to processes (e.g. killing and renicing) can be done without entering their PIDs.
-Running `htop` requires `ncurses` libraries (typically named libncursesw*).
+Running `htop` requires `ncurses` libraries, typically named libncurses(w).
-For more information and details on how to contribute to `htop` visit [htop.dev](https://htop.dev).
+`htop` is written in C.
-## Build instructions
+For more information and details visit [htop.dev](https://htop.dev).
-This program is distributed as a standard GNU autotools-based package.
+## Build instructions
-Compiling `htop` requires the header files for `ncurses` (libncursesw*-dev). Install these and other required packages for C development from your package manager.
+### Prerequisite
+List of build-time dependencies:
+ * standard GNU autotools-based C toolchain
+ - C99 compliant compiler
+ - `autoconf`
+ - `automake`
+ - `autotools`
+ * `ncurses`
+
+**Note about `ncurses`:**
+> `htop` requires `ncurses` 6.0. Be aware the appropriate package is sometimes still called libncurses5 (on Debian/Ubuntu). Also `ncurses` usually comes in two flavours:
+>* With Unicode support.
+>* Without Unicode support.
+>
+> This is also something that is reflected in the package name on Debian/Ubuntu (via the additional 'w' - 'w'ide character support).
+
+List of additional build-time dependencies (based on feature flags):
+* `sensors`
+* `hwloc`
+* `libcap` (v2.21 or later)
+* `libnl-3`
+
+Install these and other required packages for C development from your package manager.
+
+**Debian/Ubuntu**
+~~~ shell
+sudo apt install libncursesw5-dev autotools-dev autoconf automake build-essential
+~~~
-Then, when compiling from a [release tarball](https://bintray.com/htop/source/htop), run:
+**Fedora/RHEL**
+~~~ shell
+sudo dnf install ncurses-devel automake autoconf gcc
+~~~
+**Archlinux/Manjaro**
~~~ shell
-./configure && make
+sudo pacman -S ncurses automake autoconf gcc
~~~
-Alternatively, for compiling sources downloaded from the Git repository (`git clone` or downloads from [Github releases](https://github.com/htop-dev/htop/releases/)),
-install the header files for `ncurses` (libncursesw*-dev) and other required development packages from your distribution's package manager. Then run:
+**macOS**
+~~~ shell
+brew install ncurses automake autoconf gcc
+~~~
+### Compile from source:
+To compile from source, download from the Git repository (`git clone` or downloads from [GitHub releases](https://github.com/htop-dev/htop/releases/)), then run:
~~~ shell
./autogen.sh && ./configure && make
~~~
-By default `make install` will install into `/usr/local`, for changing the path use `./configure --prefix=/some/path`.
-
-See the manual page (`man htop`) or the on-line help ('F1' or 'h' inside `htop`) for a list of supported key commands.
+### Install
+To install on the local system run `make install`. By default `make install` installs into `/usr/local`. To change this path use `./configure --prefix=/some/path`.
+
+### Build Options
+
+`htop` has several build-time options to enable/disable additional features.
+
+#### Generic
+
+ * `--enable-unicode`:
+ enable Unicode support
+ - dependency: *libncursesw*
+ - default: *yes*
+ * `--enable-affinity`:
+ enable `sched_setaffinity(2)` and `sched_getaffinity(2)` for affinity support; conflicts with hwloc
+ - default: *check*
+ * `--enable-hwloc`:
+ enable hwloc support for CPU affinity; disables affinity support
+ - dependency: *libhwloc*
+ - default: *no*
+ * `--enable-static`:
+ build a static htop binary; hwloc and delay accounting are not supported
+ - default: *no*
+ * `--enable-debug`:
+ Enable asserts and internal sanity checks; implies a performance penalty
+ - default: *no*
+
+#### Performance Co-Pilot
+
+ * `--enable-pcp`:
+ enable Performance Co-Pilot support via a new pcp-htop utility
+ - dependency: *libpcp*
+ - default: *no*
+
+#### Linux
+
+ * `--enable-sensors`:
+ enable libsensors(3) support for reading temperature data
+ - dependencies: *libsensors-dev*(build-time), at runtime *libsensors* is loaded via `dlopen(3)` if available
+ - default: *check*
+ * `--enable-capabilities`:
+ enable Linux capabilities support
+ - dependency: *libcap*
+ - default: *check*
+ * `--with-proc`:
+ location of a Linux-compatible proc filesystem
+ - default: */proc*
+ * `--enable-openvz`:
+ enable OpenVZ support
+ - default: *no*
+ * `--enable-vserver`:
+ enable VServer support
+ - default: *no*
+ * `--enable-ancient-vserver`:
+ enable ancient VServer support (implies `--enable-vserver`)
+ - default: *no*
+ * `--enable-delayacct`:
+ enable Linux delay accounting support
+ - dependencies: *pkg-config*(build-time), *libnl-3* and *libnl-genl-3*
+ - default: *check*
+
+
+## Runtime dependencies:
+`htop` has a set of fixed minimum runtime dependencies, which is kept as minimal as possible:
+* `ncurses` libraries for terminal handling (wide character support).
+
+### Runtime optional dependencies:
+`htop` has a set of fixed optional dependencies, depending on build/configure option used:
+
+#### Linux
+* `libdl`, if not building a static binary, is always required when support for optional dependencies (i.e. `libsensors`, `libsystemd`) is present.
+* `libcap`, user-space interfaces to POSIX 1003.1e capabilities, is always required when `--enable-capabilities` was used to configure `htop`.
+* `libsensors`, readout of temperatures and CPU speeds, is optional even when `--enable-sensors` was used to configure `htop`.
+* `libsystemd` is optional when `--enable-static` was not used to configure `htop`. If building statically and `libsystemd` is not found by `configure`, support for the systemd meter is disabled entirely.
+
+`htop` checks for the availability of the actual runtime libraries as `htop` runs.
+
+#### BSD
+On most BSD systems `kvm` is a requirement to read kernel information.
+
+More information on required and optional dependencies can be found in [configure.ac](configure.ac).
+
+## Usage
+See the manual page (`man htop`) or the help menu (**F1** or **h** inside `htop`) for a list of supported key commands.
## Support
-If you have trouble running `htop` please consult your Operating System / Linux distribution documentation for getting support and filing bugs.
+If you have trouble running `htop` please consult your operating system / Linux distribution documentation for getting support and filing bugs.
## Bugs, development feedback
-We have a [development mailing list](https://htop.dev/mailinglist.html). Feel free to subscribe for release announcements or asking questions on the development of htop.
+We have a [development mailing list](https://htop.dev/mailinglist.html). Feel free to subscribe for release announcements or asking questions on the development of `htop`.
-You can also join our IRC channel #htop on freenode and talk to the developers there.
+You can also join our IRC channel [#htop on Libera.Chat](https://web.libera.chat/#htop) and talk to the developers there.
-If you have found an issue with the source of htop, please check whether this has already been reported in our [Github issue tracker](https://github.com/htop-dev/htop/issues).
-If not, please file a new issue describing the problem you have found, the location in the source code you are referring to and a possible fix.
+If you have found an issue within the source of `htop`, please check whether this has already been reported in our [GitHub issue tracker](https://github.com/htop-dev/htop/issues).
+If not, please file a new issue describing the problem you have found, the potential location in the source code you are referring to and a possible fix if available.
## History
-`htop` was invented, developed and maintained by Hisham Muhammad from 2004 to 2019. His [legacy repository](https://github.com/hishamhm/htop/) has been archived to preserve the history.
+`htop` was invented, developed and maintained by [Hisham Muhammad](https://hisham.hm/) from 2004 to 2019. His [legacy repository](https://github.com/hishamhm/htop/) has been archived to preserve the history.
In 2020 a [team](https://github.com/orgs/htop-dev/people) took over the development amicably and continues to maintain `htop` collaboratively.
## 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 790c15a..daa0c91 100644
--- a/RichString.c
+++ b/RichString.c
@@ -1,12 +1,13 @@
/*
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 "RichString.h"
+#include <ctype.h>
#include <stdlib.h>
#include <string.h>
@@ -45,33 +46,86 @@ static void RichString_setLen(RichString* this, int len) {
}
}
+void RichString_rewind(RichString* this, int count) {
+ RichString_setLen(this, this->chlen - count);
+}
+
#ifdef HAVE_LIBNCURSESW
-static inline void RichString_writeFrom(RichString* this, int attrs, const char* data_c, int from, int len) {
+static inline int RichString_writeFromWide(RichString* this, int attrs, const char* data_c, int from, int len) {
wchar_t data[len + 1];
len = mbstowcs(data, data_c, len);
- if (len < 0)
- return;
+ if (len <= 0)
+ return 0;
int newLen = from + len;
RichString_setLen(this, newLen);
for (int i = from, j = 0; i < newLen; i++, j++) {
- this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (iswprint(data[j]) ? data[j] : '?') } };
+ this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (iswprint(data[j]) ? data[j] : L'\xFFFD') } };
}
+
+ return len;
}
-inline void RichString_setAttrn(RichString* this, int attrs, int start, int finish) {
- cchar_t* ch = this->chptr + start;
- finish = CLAMP(finish, 0, this->chlen - 1);
- for (int i = start; i <= finish; i++) {
- ch->attr = attrs;
- ch++;
+int RichString_appendnWideColumns(RichString* this, int attrs, const char* data_c, int len, int* columns) {
+ wchar_t data[len + 1];
+ len = mbstowcs(data, data_c, len);
+ if (len <= 0)
+ return 0;
+
+ int from = this->chlen;
+ int newLen = from + len;
+ RichString_setLen(this, newLen);
+ int columnsWritten = 0;
+ int pos = from;
+ for (int j = 0; j < len; j++) {
+ wchar_t c = iswprint(data[j]) ? data[j] : L'\xFFFD';
+ int cwidth = wcwidth(c);
+ if (cwidth > *columns)
+ break;
+
+ *columns -= cwidth;
+ columnsWritten += cwidth;
+
+ this->chptr[pos] = (CharType) { .attr = attrs & 0xffffff, .chars = { c, '\0' } };
+ pos++;
+ }
+
+ RichString_setLen(this, pos);
+ *columns = columnsWritten;
+
+ return pos - from;
+}
+
+static inline int RichString_writeFromAscii(RichString* this, int attrs, const char* data, int from, int len) {
+ 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] : L'\xFFFD') } };
+ }
+
+ return len;
+}
+
+inline void RichString_setAttrn(RichString* this, int attrs, int start, int charcount) {
+ int end = CLAMP(start + charcount, 0, this->chlen);
+ for (int i = start; i < end; i++) {
+ this->chptr[i].attr = attrs;
+ }
+}
+
+void RichString_appendChr(RichString* this, int attrs, char c, int count) {
+ int from = this->chlen;
+ int newLen = from + count;
+ RichString_setLen(this, newLen);
+ for (int i = from; i < newLen; i++) {
+ this->chptr[i] = (CharType) { .attr = attrs, .chars = { c, 0 } };
}
}
-int RichString_findChar(RichString* this, char c, int start) {
- wchar_t wc = btowc(c);
- cchar_t* ch = this->chptr + start;
+int RichString_findChar(const RichString* this, char c, int start) {
+ const wchar_t wc = btowc(c);
+ const cchar_t* ch = this->chptr + start;
for (int i = start; i < this->chlen; i++) {
if (ch->chars[0] == wc)
return i;
@@ -82,26 +136,45 @@ int RichString_findChar(RichString* this, char c, int start) {
#else /* HAVE_LIBNCURSESW */
-static inline void RichString_writeFrom(RichString* this, int attrs, const char* data_c, int from, int len) {
+static inline int RichString_writeFromWide(RichString* this, int attrs, const char* data_c, int from, int len) {
int newLen = from + len;
RichString_setLen(this, newLen);
for (int i = from, j = 0; i < newLen; i++, j++) {
this->chptr[i] = (((unsigned char)data_c[j]) >= 32 ? ((unsigned char)data_c[j]) : '?') | attrs;
}
this->chptr[newLen] = 0;
+
+ return len;
}
-void RichString_setAttrn(RichString* this, int attrs, int start, int finish) {
- chtype* ch = this->chptr + start;
- finish = CLAMP(finish, 0, this->chlen - 1);
- for (int i = start; i <= finish; i++) {
- *ch = (*ch & 0xff) | attrs;
- ch++;
+int RichString_appendnWideColumns(RichString* this, int attrs, const char* data_c, int len, int* columns) {
+ int written = RichString_writeFromWide(this, attrs, data_c, this->chlen, MINIMUM(len, *columns));
+ *columns = written;
+ return written;
+}
+
+static inline int RichString_writeFromAscii(RichString* this, int attrs, const char* data_c, int from, int len) {
+ return RichString_writeFromWide(this, attrs, data_c, from, len);
+}
+
+void RichString_setAttrn(RichString* this, int attrs, int start, int charcount) {
+ int end = CLAMP(start + charcount, 0, this->chlen);
+ for (int i = start; i < end; i++) {
+ this->chptr[i] = (this->chptr[i] & 0xff) | attrs;
}
}
-int RichString_findChar(RichString* this, char c, int start) {
- chtype* ch = this->chptr + start;
+void RichString_appendChr(RichString* this, int attrs, char c, int count) {
+ int from = this->chlen;
+ int newLen = from + count;
+ RichString_setLen(this, newLen);
+ for (int i = from; i < newLen; i++) {
+ this->chptr[i] = c | attrs;
+ }
+}
+
+int RichString_findChar(const RichString* this, char c, int start) {
+ const chtype* ch = this->chptr + start;
for (int i = start; i < this->chlen; i++) {
if ((*ch & 0xff) == (chtype) c)
return i;
@@ -112,34 +185,37 @@ int RichString_findChar(RichString* this, char c, int start) {
#endif /* HAVE_LIBNCURSESW */
-void RichString_prune(RichString* this) {
- if (this->chlen > RICHSTRING_MAXLEN)
+void RichString_delete(RichString* this) {
+ if (this->chlen > RICHSTRING_MAXLEN) {
free(this->chptr);
- memset(this, 0, sizeof(RichString));
- this->chptr = this->chstr;
-}
-
-void RichString_appendChr(RichString* this, char c, int count) {
- int from = this->chlen;
- int newLen = from + count;
- RichString_setLen(this, newLen);
- for (int i = from; i < newLen; i++) {
- RichString_setChar(this, i, c);
+ this->chptr = this->chstr;
}
}
void RichString_setAttr(RichString* this, int attrs) {
- RichString_setAttrn(this, attrs, 0, this->chlen - 1);
+ RichString_setAttrn(this, attrs, 0, this->chlen);
+}
+
+int RichString_appendWide(RichString* this, int attrs, const char* data) {
+ return RichString_writeFromWide(this, attrs, data, this->chlen, strlen(data));
+}
+
+int RichString_appendnWide(RichString* this, int attrs, const char* data, int len) {
+ return RichString_writeFromWide(this, attrs, data, this->chlen, len);
+}
+
+int RichString_writeWide(RichString* this, int attrs, const char* data) {
+ return RichString_writeFromWide(this, attrs, data, 0, strlen(data));
}
-void RichString_append(RichString* this, int attrs, const char* data) {
- RichString_writeFrom(this, attrs, data, this->chlen, strlen(data));
+int RichString_appendAscii(RichString* this, int attrs, const char* data) {
+ return RichString_writeFromAscii(this, attrs, data, this->chlen, strlen(data));
}
-void RichString_appendn(RichString* this, int attrs, const char* data, int len) {
- RichString_writeFrom(this, attrs, data, this->chlen, len);
+int RichString_appendnAscii(RichString* this, int attrs, const char* data, int len) {
+ return RichString_writeFromAscii(this, attrs, data, this->chlen, len);
}
-void RichString_write(RichString* this, int attrs, const char* data) {
- RichString_writeFrom(this, attrs, data, 0, strlen(data));
+int RichString_writeAscii(RichString* this, int attrs, const char* data) {
+ return RichString_writeFromAscii(this, attrs, data, 0, strlen(data));
}
diff --git a/RichString.h b/RichString.h
index 262befc..cbcbe48 100644
--- a/RichString.h
+++ b/RichString.h
@@ -3,7 +3,7 @@
/*
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.
*/
@@ -15,20 +15,25 @@ in the source distribution for its full text.
#define RichString_size(this) ((this)->chlen)
#define RichString_sizeVal(this) ((this).chlen)
-#define RichString_begin(this) RichString (this); RichString_beginAllocated(this)
-#define RichString_beginAllocated(this) do { memset(&(this), 0, sizeof(RichString)); (this).chptr = (this).chstr; } while(0)
-#define RichString_end(this) RichString_prune(&(this))
+#define RichString_begin(this) RichString this; RichString_beginAllocated(this)
+#define RichString_beginAllocated(this) \
+ do { \
+ (this).chlen = 0, \
+ (this).chptr = (this).chstr; \
+ RichString_setChar(&(this), 0, 0); \
+ (this).highlightAttr = 0; \
+ } while(0)
#ifdef HAVE_LIBNCURSESW
#define RichString_printVal(this, y, x) mvadd_wchstr(y, x, (this).chptr)
#define RichString_printoffnVal(this, y, x, off, n) mvadd_wchnstr(y, x, (this).chptr + (off), n)
-#define RichString_getCharVal(this, i) ((this).chptr[i].chars[0] & 255)
+#define RichString_getCharVal(this, i) ((this).chptr[i].chars[0])
#define RichString_setChar(this, at, ch) do { (this)->chptr[(at)] = (CharType) { .chars = { ch, 0 } }; } while (0)
#define CharType cchar_t
#else
#define RichString_printVal(this, y, x) mvaddchstr(y, x, (this).chptr)
#define RichString_printoffnVal(this, y, x, off, n) mvaddchnstr(y, x, (this).chptr + (off), n)
-#define RichString_getCharVal(this, i) ((this).chptr[i])
+#define RichString_getCharVal(this, i) ((this).chptr[i] & 0xff)
#define RichString_setChar(this, at, ch) do { (this)->chptr[(at)] = ch; } while (0)
#define CharType chtype
#endif
@@ -42,20 +47,33 @@ typedef struct RichString_ {
int highlightAttr;
} RichString;
-void RichString_setAttrn(RichString* this, int attrs, int start, int finish);
+void RichString_delete(RichString* this);
-int RichString_findChar(RichString* this, char c, int start);
+void RichString_rewind(RichString* this, int count);
-void RichString_prune(RichString* this);
+void RichString_setAttrn(RichString* this, int attrs, int start, int charcount);
+
+int RichString_findChar(const RichString* this, char c, int start);
void RichString_setAttr(RichString* this, int attrs);
-void RichString_appendChr(RichString* this, char c, int count);
+void RichString_appendChr(RichString* this, int attrs, char c, int count);
+
+/* All appending and writing functions return the number of written characters (not columns). */
+
+int RichString_appendWide(RichString* this, int attrs, const char* data);
+
+int RichString_appendnWide(RichString* this, int attrs, const char* data, int len);
+
+/* columns takes the maximum number of columns to write and contains on return the number of columns written. */
+int RichString_appendnWideColumns(RichString* this, int attrs, const char* data, int len, int* columns);
+
+int RichString_writeWide(RichString* this, int attrs, const char* data);
-void RichString_append(RichString* this, int attrs, const char* data);
+int RichString_appendAscii(RichString* this, int attrs, const char* data);
-void RichString_appendn(RichString* this, int attrs, const char* data, int len);
+int RichString_appendnAscii(RichString* this, int attrs, const char* data, int len);
-void RichString_write(RichString* this, int attrs, const char* data);
+int RichString_writeAscii(RichString* this, int attrs, const char* data);
#endif
diff --git a/ScreenManager.c b/ScreenManager.c
index ac93721..55cacd2 100644
--- a/ScreenManager.c
+++ b/ScreenManager.c
@@ -1,10 +1,12 @@
/*
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.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "ScreenManager.h"
#include <assert.h>
@@ -14,17 +16,19 @@ in the source distribution for its full text.
#include "CRT.h"
#include "FunctionBar.h"
+#include "Macros.h"
#include "Object.h"
+#include "Platform.h"
#include "ProcessList.h"
#include "ProvideCurses.h"
#include "XUtils.h"
-ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const State* state, bool owner) {
+ScreenManager* ScreenManager_new(Header* header, const Settings* settings, State* state, bool owner) {
ScreenManager* this;
this = xMalloc(sizeof(ScreenManager));
this->x1 = 0;
- this->y1 = header->height;
+ this->y1 = 0;
this->x2 = 0;
this->y2 = -1;
this->panels = Vector_new(Class(Panel), owner, DEFAULT_SIZE);
@@ -32,7 +36,6 @@ ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const
this->header = header;
this->settings = settings;
this->state = state;
- this->owner = owner;
this->allowFocusChange = true;
return this;
}
@@ -42,59 +45,81 @@ void ScreenManager_delete(ScreenManager* this) {
free(this);
}
-inline int ScreenManager_size(ScreenManager* this) {
+inline int ScreenManager_size(const ScreenManager* this) {
return this->panelCount;
}
void ScreenManager_add(ScreenManager* this, Panel* item, int size) {
+ 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) {
- Panel* last = (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->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);
- 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 x1, int y1, int x2, int y2) {
- this->x1 = x1;
- this->y1 = y1;
- this->x2 = x2;
- this->y2 = y2;
+void ScreenManager_resize(ScreenManager* this) {
+ int y1_header = this->y1 + header_height(this);
int panels = this->panelCount;
int lastX = 0;
for (int i = 0; i < panels - 1; i++) {
Panel* panel = (Panel*) Vector_get(this->panels, i);
- Panel_resize(panel, panel->w, LINES - y1 + y2);
- Panel_move(panel, lastX, y1);
+ Panel_resize(panel, panel->w, LINES - y1_header + this->y2);
+ Panel_move(panel, lastX, y1_header);
lastX = panel->x + panel->w + 1;
}
Panel* panel = (Panel*) Vector_get(this->panels, panels - 1);
- Panel_resize(panel, COLS - x1 + x2 - lastX, LINES - y1 + y2);
- Panel_move(panel, lastX, y1);
+ Panel_resize(panel, COLS - this->x1 + this->x2 - lastX, LINES - y1_header + this->y2);
+ Panel_move(panel, lastX, y1_header);
}
-static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut) {
+static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut, bool* force_redraw) {
ProcessList* pl = this->header->pl;
- struct timeval tv;
- gettimeofday(&tv, NULL);
- double newTime = ((double)tv.tv_sec * 10) + ((double)tv.tv_usec / 100000);
+ Platform_gettime_realtime(&pl->realtime, &pl->realtimeMs);
+ double newTime = ((double)pl->realtime.tv_sec * 10) + ((double)pl->realtime.tv_usec / 100000);
*timedOut = (newTime - *oldTime > this->settings->delay);
*rescan |= *timedOut;
@@ -105,43 +130,93 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi
if (*rescan) {
*oldTime = newTime;
- ProcessList_scan(pl, this->state->pauseProcessUpdate);
- if (*sortTimeout == 0 || this->settings->treeView) {
- ProcessList_sort(pl);
+ int oldUidDigits = Process_uidDigits;
+ if (!this->state->pauseProcessUpdate && (*sortTimeout == 0 || this->settings->ss->treeView)) {
+ pl->needsSort = true;
*sortTimeout = 1;
}
+ // scan processes first - some header values are calculated there
+ ProcessList_scan(pl, this->state->pauseProcessUpdate);
+ // always update header, especially to avoid gaps in graph meters
+ Header_updateData(this->header);
+ // 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);
+ if (!this->state->hideMeters)
+ Header_draw(this->header);
}
*rescan = false;
}
-static void ScreenManager_drawPanels(ScreenManager* this, int focus) {
- const int nPanels = this->panelCount;
- for (int i = 0; i < nPanels; i++) {
- Panel* panel = (Panel*) Vector_get(this->panels, i);
- Panel_draw(panel, i == focus, !((panel == this->state->panel) && this->state->hideProcessSelection));
- mvvline(panel->y, panel->x + panel->w, ' ', panel->h + 1);
- }
+static inline bool drawTab(int* y, int* x, int l, const char* name, bool cur) {
+ attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);
+ mvaddch(*y, *x, '[');
+ (*x)++;
+ if (*x >= l)
+ return false;
+ int nameLen = strlen(name);
+ int n = MINIMUM(l - *x, nameLen);
+ attrset(CRT_colors[cur ? SCREENS_CUR_TEXT : SCREENS_OTH_TEXT]);
+ mvaddnstr(*y, *x, name, n);
+ *x += n;
+ if (*x >= l)
+ return false;
+ attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);
+ mvaddch(*y, *x, ']');
+ *x += 2;
+ if (*x >= l)
+ return false;
+ return true;
}
-static Panel* setCurrentPanel(const ScreenManager* this, Panel* panel) {
- FunctionBar_draw(panel->currentBar);
- if (panel == this->state->panel && this->state->pauseProcessUpdate) {
- FunctionBar_append("PAUSED", CRT_colors[PAUSED]);
+static void ScreenManager_drawScreenTabs(ScreenManager* this) {
+ ScreenSettings** screens = this->settings->screens;
+ int cur = this->settings->ssIndex;
+ int l = COLS;
+ Panel* panel = (Panel*) Vector_get(this->panels, 0);
+ int y = panel->y - 1;
+ int x = 2;
+
+ if (this->name) {
+ drawTab(&y, &x, l, this->name, true);
+ return;
}
- return panel;
+ for (int s = 0; screens[s]; s++) {
+ bool ok = drawTab(&y, &x, l, screens[s]->name, s == cur);
+ if (!ok) {
+ break;
+ }
+ }
+ attrset(CRT_colors[RESET_COLOR]);
+}
+
+static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_redraw) {
+ if (this->settings->screenTabs) {
+ ScreenManager_drawScreenTabs(this);
+ }
+ const int nPanels = this->panelCount;
+ for (int i = 0; i < nPanels; i++) {
+ Panel* panel = (Panel*) Vector_get(this->panels, i);
+ Panel_draw(panel,
+ force_redraw,
+ i == focus,
+ panel != (Panel*)this->state->mainPanel || !this->state->hideProcessSelection,
+ State_hideFunctionBar(this->state));
+ mvvline(panel->y, panel->x + panel->w, ' ', panel->h + (State_hideFunctionBar(this->state) ? 1 : 0));
+ }
}
-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 = setCurrentPanel(this, (Panel*) Vector_get(this->panels, focus));
+ Panel* panelFocus = (Panel*) Vector_get(this->panels, focus);
double oldTime = 0.0;
@@ -150,24 +225,28 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
bool timedOut = true;
bool redraw = true;
+ bool force_redraw = true;
bool rescan = false;
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) {
- ScreenManager_drawPanels(this, focus);
+ if (redraw || force_redraw) {
+ ScreenManager_drawPanels(this, focus, force_redraw);
+ force_redraw = false;
}
int prevCh = ch;
- set_escdelay(25);
- ch = getch();
+ ch = Panel_getCh(panelFocus);
HandlerResult result = IGNORED;
+#ifdef HAVE_GETMOUSE
if (ch == KEY_MOUSE && this->settings->enableMouse) {
ch = ERR;
MEVENT mevent;
@@ -183,12 +262,15 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
if (mevent.y == panel->y) {
ch = EVENT_HEADER_CLICK(mevent.x - panel->x);
break;
+ } else if (this->settings->screenTabs && mevent.y == panel->y - 1) {
+ ch = EVENT_SCREEN_TAB_CLICK(mevent.x);
+ break;
} else if (mevent.y > panel->y && mevent.y <= panel->y + panel->h) {
ch = KEY_MOUSE;
if (panel == panelFocus || this->allowFocusChange) {
focus = i;
- panelFocus = setCurrentPanel(this, panel);
- Object* oldSelection = Panel_getSelected(panel);
+ panelFocus = panel;
+ const Object* oldSelection = Panel_getSelected(panel);
Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV - 1);
if (Panel_getSelected(panel) == oldSelection) {
ch = KEY_RECLICK;
@@ -208,8 +290,10 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
}
}
}
+#endif
if (ch == ERR) {
- sortTimeout--;
+ if (sortTimeout > 0)
+ sortTimeout--;
if (prevCh == ch && !timedOut) {
closeTimeout++;
if (closeTimeout == 100) {
@@ -234,9 +318,16 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
if (result & SYNTH_KEY) {
ch = result >> 16;
}
- if (result & REDRAW) {
+ if (result & REFRESH) {
sortTimeout = 0;
}
+ if (result & REDRAW) {
+ force_redraw = true;
+ }
+ if (result & RESIZE) {
+ ScreenManager_resize(this);
+ force_redraw = true;
+ }
if (result & RESCAN) {
rescan = true;
sortTimeout = 0;
@@ -251,7 +342,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
switch (ch) {
case KEY_RESIZE:
{
- ScreenManager_resize(this, this->x1, this->y1, this->x2, this->y2);
+ ScreenManager_resize(this);
continue;
}
case KEY_LEFT:
@@ -269,7 +360,7 @@ tryLeft:
focus--;
}
- panelFocus = setCurrentPanel(this, (Panel*) Vector_get(this->panels, focus));
+ panelFocus = (Panel*) Vector_get(this->panels, focus);
if (Panel_size(panelFocus) == 0 && focus > 0) {
goto tryLeft;
}
@@ -290,12 +381,17 @@ tryRight:
focus++;
}
- panelFocus = setCurrentPanel(this, (Panel*) Vector_get(this->panels, focus));
+ panelFocus = (Panel*) Vector_get(this->panels, focus);
if (Panel_size(panelFocus) == 0 && focus < this->panelCount - 1) {
goto 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 7dda4fc..d08a941 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.
*/
@@ -22,26 +22,28 @@ typedef struct ScreenManager_ {
int x2;
int y2;
Vector* panels;
+ const char* name;
int panelCount;
Header* header;
const Settings* settings;
- const State* state;
- bool owner;
+ State* state;
bool allowFocusChange;
} ScreenManager;
-ScreenManager* ScreenManager_new(Header* header, const Settings* settings, const State* state, bool owner);
+ScreenManager* ScreenManager_new(Header* header, const Settings* settings, State* state, bool owner);
void ScreenManager_delete(ScreenManager* this);
-int ScreenManager_size(ScreenManager* this);
+int ScreenManager_size(const ScreenManager* this);
void ScreenManager_add(ScreenManager* this, Panel* item, int size);
+void ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx);
+
Panel* ScreenManager_remove(ScreenManager* this, int idx);
-void ScreenManager_resize(ScreenManager* this, int x1, int y1, int x2, int y2);
+void ScreenManager_resize(ScreenManager* this);
-void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey);
+void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name);
#endif
diff --git a/ScreensPanel.c b/ScreensPanel.c
new file mode 100644
index 0000000..cb664ac
--- /dev/null
+++ b/ScreensPanel.c
@@ -0,0 +1,340 @@
+/*
+htop - ScreensPanel.c
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2022 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "ScreensPanel.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "CRT.h"
+#include "FunctionBar.h"
+#include "Hashtable.h"
+#include "ProvideCurses.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+
+static void ScreenListItem_delete(Object* cast) {
+ ScreenListItem* this = (ScreenListItem*)cast;
+ if (this->ss) {
+ ScreenSettings_delete(this->ss);
+ }
+ ListItem_delete(cast);
+}
+
+ObjectClass ScreenListItem_class = {
+ .extends = Class(ListItem),
+ .display = ListItem_display,
+ .delete = ScreenListItem_delete,
+ .compare = ListItem_compare
+};
+
+ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) {
+ ScreenListItem* this = AllocThis(ScreenListItem);
+ ListItem_init((ListItem*)this, value, 0);
+ this->ss = ss;
+ return this;
+}
+
+static const char* const ScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL};
+
+static void ScreensPanel_delete(Object* object) {
+ Panel* super = (Panel*) object;
+ ScreensPanel* this = (ScreensPanel*) object;
+
+ /* do not delete screen settings still in use */
+ int n = Panel_size(super);
+ for (int i = 0; i < n; i++) {
+ ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
+ item->ss = NULL;
+ }
+
+ /* 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 HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) {
+ ScreensPanel* const this = (ScreensPanel*) super;
+
+ if (ch >= 32 && ch < 127 && ch != '=') {
+ if (this->cursor < SCREEN_NAME_LEN - 1) {
+ this->buffer[this->cursor] = (char)ch;
+ this->cursor++;
+ super->selectedLen = strlen(this->buffer);
+ Panel_setCursorToSelection(super);
+ }
+ } else {
+ switch (ch) {
+ case 127:
+ case KEY_BACKSPACE:
+ {
+ if (this->cursor > 0) {
+ this->cursor--;
+ this->buffer[this->cursor] = '\0';
+ super->selectedLen = strlen(this->buffer);
+ Panel_setCursorToSelection(super);
+ }
+ break;
+ }
+ case '\n':
+ case '\r':
+ case KEY_ENTER:
+ {
+ ListItem* item = (ListItem*) Panel_getSelected(super);
+ if (!item)
+ break;
+ 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'):
+ {
+ addNewScreen(super);
+ startRenaming(super);
+ shouldRebuildArray = true;
+ result = HANDLED;
+ break;
+ }
+ case KEY_UP:
+ {
+ if (!this->moving) {
+ Panel_onKey(super, ch);
+ break;
+ }
+ /* else fallthrough */
+ } /* FALLTHRU */
+ case KEY_F(7):
+ case '[':
+ case '-':
+ {
+ Panel_moveSelectedUp(super);
+ shouldRebuildArray = true;
+ result = HANDLED;
+ break;
+ }
+ case KEY_DOWN:
+ {
+ if (!this->moving) {
+ Panel_onKey(super, ch);
+ break;
+ }
+ /* else fallthrough */
+ } /* FALLTHRU */
+ case KEY_F(8):
+ case ']':
+ case '+':
+ {
+ Panel_moveSelectedDown(super);
+ shouldRebuildArray = true;
+ result = HANDLED;
+ break;
+ }
+ case KEY_F(9):
+ //case KEY_DC:
+ {
+ if (Panel_size(super) > 1)
+ Panel_remove(super, selected);
+ shouldRebuildArray = true;
+ result = HANDLED;
+ break;
+ }
+ default:
+ {
+ if (ch < 255 && isalpha(ch))
+ result = Panel_selectByTyping(super, ch);
+ if (result == BREAK_LOOP)
+ result = IGNORED;
+ break;
+ }
+ }
+ ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super);
+ if (newFocus && oldFocus != newFocus) {
+ ColumnsPanel_fill(this->columns, newFocus->ss, this->settings->dynamicColumns);
+ result = HANDLED;
+ }
+ if (shouldRebuildArray)
+ rebuildSettingsArray(super, 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(ScreensFunctions, NULL, NULL);
+ Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
+
+ this->settings = settings;
+ this->columns = ColumnsPanel_new(settings->screens[0], columns, &(settings->changed));
+ this->moving = false;
+ this->renamingItem = NULL;
+ super->cursorOn = false;
+ this->cursor = 0;
+ Panel_setHeader(super, "Screens");
+
+ for (unsigned int i = 0; i < settings->nScreens; i++) {
+ ScreenSettings* ss = settings->screens[i];
+ char* name = ss->name;
+ Panel_add(super, (Object*) ScreenListItem_new(name, ss));
+ }
+ return this;
+}
+
+void ScreensPanel_update(Panel* super) {
+ ScreensPanel* this = (ScreensPanel*) super;
+ int size = Panel_size(super);
+ this->settings->changed = true;
+ this->settings->lastUpdate++;
+ this->settings->screens = xReallocArray(this->settings->screens, size + 1, sizeof(ScreenSettings*));
+ for (int i = 0; i < size; i++) {
+ ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
+ ScreenSettings* ss = item->ss;
+ free(ss->name);
+ this->settings->screens[i] = ss;
+ ss->name = xStrdup(((ListItem*) item)->value);
+ }
+ this->settings->screens[size] = NULL;
+}
diff --git a/ScreensPanel.h b/ScreensPanel.h
new file mode 100644
index 0000000..88d85b5
--- /dev/null
+++ b/ScreensPanel.h
@@ -0,0 +1,53 @@
+#ifndef HEADER_ScreensPanel
+#define HEADER_ScreensPanel
+/*
+htop - ScreensPanel.h
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2022 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "ColumnsPanel.h"
+#include "ListItem.h"
+#include "Object.h"
+#include "Panel.h"
+#include "ScreenManager.h"
+#include "Settings.h"
+
+#ifndef SCREEN_NAME_LEN
+#define SCREEN_NAME_LEN 20
+#endif
+
+typedef struct ScreensPanel_ {
+ Panel super;
+
+ ScreenManager* scr;
+ Settings* settings;
+ ColumnsPanel* columns;
+ char buffer[SCREEN_NAME_LEN + 1];
+ char* saved;
+ int cursor;
+ bool moving;
+ ListItem *renamingItem;
+} ScreensPanel;
+
+typedef struct ScreenListItem_ {
+ ListItem super;
+ ScreenSettings* ss;
+} ScreenListItem;
+
+
+extern ObjectClass ScreenListItem_class;
+
+ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss);
+
+extern PanelClass ScreensPanel_class;
+
+ScreensPanel* ScreensPanel_new(Settings* settings);
+
+void ScreensPanel_update(Panel* super);
+
+#endif
diff --git a/Settings.c b/Settings.c
index 0b4d0ed..8543b9e 100644
--- a/Settings.c
+++ b/Settings.c
@@ -1,42 +1,97 @@
/*
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 "Settings.h"
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "CRT.h"
+#include "DynamicColumn.h"
#include "Macros.h"
#include "Meter.h"
#include "Platform.h"
#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 < ARRAYSIZE(this->columns); i++) {
- String_freeArray(this->columns[i].names);
- free(this->columns[i].modes);
+ for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
+ 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);
}
-static void Settings_readMeters(Settings* this, char* line, int column) {
+static void Settings_readMeters(Settings* this, const char* line, unsigned int column) {
char* trim = String_trim(line);
char** ids = String_split(trim, ' ', NULL);
free(trim);
- this->columns[column].names = ids;
+ column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);
+ this->hColumns[column].names = ids;
}
-static void Settings_readMeterModes(Settings* this, char* line, int column) {
+static void Settings_readMeterModes(Settings* this, const char* line, unsigned int column) {
char* trim = String_trim(line);
char** ids = String_split(trim, ' ', NULL);
free(trim);
@@ -44,86 +99,232 @@ static void Settings_readMeterModes(Settings* this, char* line, int column) {
for (int i = 0; ids[i]; i++) {
len++;
}
- this->columns[column].len = len;
- int* modes = xCalloc(len, sizeof(int));
+ column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);
+ this->hColumns[column].len = len;
+ int* modes = len ? xCalloc(len, sizeof(int)) : NULL;
for (int i = 0; i < len; i++) {
modes[i] = atoi(ids[i]);
}
String_freeArray(ids);
- this->columns[column].modes = modes;
+ this->hColumns[column].modes = modes;
+}
+
+static 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, int initialCpuCount) {
+static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) {
int sizes[] = { 3, 3 };
- if (initialCpuCount > 4) {
+
+ if (initialCpuCount > 4 && initialCpuCount <= 128) {
sizes[1]++;
}
- for (int i = 0; i < 2; i++) {
- this->columns[i].names = xCalloc(sizes[i] + 1, sizeof(char*));
- this->columns[i].modes = xCalloc(sizes[i], sizeof(int));
- this->columns[i].len = sizes[i];
+
+ // 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 > 8) {
- this->columns[0].names[0] = xStrdup("LeftCPUs2");
- this->columns[0].modes[0] = BAR_METERMODE;
- this->columns[1].names[r] = xStrdup("RightCPUs2");
- this->columns[1].modes[r++] = BAR_METERMODE;
+
+ if (initialCpuCount > 128) {
+ // Just show the average, ricers need to config for impressive screenshots
+ this->hColumns[0].names[0] = xStrdup("CPU");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ } else if (initialCpuCount > 32) {
+ this->hColumns[0].names[0] = xStrdup("LeftCPUs8");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("RightCPUs8");
+ this->hColumns[1].modes[r++] = BAR_METERMODE;
+ } else if (initialCpuCount > 16) {
+ this->hColumns[0].names[0] = xStrdup("LeftCPUs4");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("RightCPUs4");
+ this->hColumns[1].modes[r++] = BAR_METERMODE;
+ } else if (initialCpuCount > 8) {
+ this->hColumns[0].names[0] = xStrdup("LeftCPUs2");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("RightCPUs2");
+ this->hColumns[1].modes[r++] = BAR_METERMODE;
} else if (initialCpuCount > 4) {
- this->columns[0].names[0] = xStrdup("LeftCPUs");
- this->columns[0].modes[0] = BAR_METERMODE;
- this->columns[1].names[r] = xStrdup("RightCPUs");
- this->columns[1].modes[r++] = BAR_METERMODE;
+ this->hColumns[0].names[0] = xStrdup("LeftCPUs");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("RightCPUs");
+ this->hColumns[1].modes[r++] = BAR_METERMODE;
} else {
- this->columns[0].names[0] = xStrdup("AllCPUs");
- this->columns[0].modes[0] = BAR_METERMODE;
+ this->hColumns[0].names[0] = xStrdup("AllCPUs");
+ this->hColumns[0].modes[0] = BAR_METERMODE;
}
- this->columns[0].names[1] = xStrdup("Memory");
- this->columns[0].modes[1] = BAR_METERMODE;
- this->columns[0].names[2] = xStrdup("Swap");
- this->columns[0].modes[2] = BAR_METERMODE;
- this->columns[1].names[r] = xStrdup("Tasks");
- this->columns[1].modes[r++] = TEXT_METERMODE;
- this->columns[1].names[r] = xStrdup("LoadAverage");
- this->columns[1].modes[r++] = TEXT_METERMODE;
- this->columns[1].names[r] = xStrdup("Uptime");
- this->columns[1].modes[r++] = TEXT_METERMODE;
+ this->hColumns[0].names[1] = xStrdup("Memory");
+ this->hColumns[0].modes[1] = BAR_METERMODE;
+ this->hColumns[0].names[2] = xStrdup("Swap");
+ this->hColumns[0].modes[2] = BAR_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("Tasks");
+ this->hColumns[1].modes[r++] = TEXT_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("LoadAverage");
+ this->hColumns[1].modes[r++] = TEXT_METERMODE;
+ this->hColumns[1].names[r] = xStrdup("Uptime");
+ this->hColumns[1].modes[r++] = TEXT_METERMODE;
}
-static void readFields(ProcessField* fields, uint32_t* flags, const char* line) {
+static const char* toFieldName(Hashtable* columns, int id) {
+ if (id < 0)
+ return NULL;
+ if (id >= LAST_PROCESSFIELD) {
+ const DynamicColumn* column = DynamicColumn_lookup(columns, id);
+ return column->name;
+ }
+ 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)) {
+ 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);
+ 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);
- int i, j;
- *flags = 0;
- for (j = 0, i = 0; i < Platform_numberOfFields && ids[i]; i++) {
- // This "+1" is for compatibility with the older enum format.
- int id = atoi(ids[i]) + 1;
- if (id > 0 && id < Platform_numberOfFields && Process_fields[id].name) {
- fields[j] = id;
- *flags |= Process_fields[id].flags;
- j++;
+
+ /* reset default fields */
+ memset(ss->fields, '\0', LAST_PROCESSFIELD * sizeof(ProcessField));
+
+ for (size_t j = 0, i = 0; ids[i]; i++) {
+ if (j >= UINT_MAX / sizeof(ProcessField))
+ continue;
+ if (j >= LAST_PROCESSFIELD) {
+ 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;
}
- fields[j] = NULL_PROCESSFIELD;
String_freeArray(ids);
}
-static bool Settings_read(Settings* this, const char* fileName, int initialCpuCount) {
- FILE* fd;
- CRT_dropPrivileges();
- fd = fopen(fileName, "r");
- CRT_restorePrivileges();
+ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults) {
+ int sortKey = defaults->sortKey ? toFieldIndex(this->dynamicColumns, defaults->sortKey) : PID;
+ int sortDesc = (sortKey >= 0 && sortKey < LAST_PROCESSFIELD) ? Process_fields[sortKey].defaultSortDesc : 1;
+
+ ScreenSettings* ss = xMalloc(sizeof(ScreenSettings));
+ *ss = (ScreenSettings) {
+ .name = xStrdup(defaults->name),
+ .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)),
+ .flags = 0,
+ .direction = sortDesc ? -1 : 1,
+ .treeDirection = 1,
+ .sortKey = sortKey,
+ .treeSortKey = PID,
+ .treeView = false,
+ .treeViewAlwaysByPID = false,
+ .allBranchesCollapsed = false,
+ };
+
+ ScreenSettings_readFields(ss, this->dynamicColumns, defaults->columns);
+ this->screens[this->nScreens] = ss;
+ this->nScreens++;
+ this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1));
+ this->screens[this->nScreens] = NULL;
+ return ss;
+}
+
+void ScreenSettings_delete(ScreenSettings* this) {
+ free(this->name);
+ 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);
+ }
+ 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 didReadFields = false;
+ bool didReadAny = false;
for (;;) {
char* line = String_readLine(fd);
if (!line) {
break;
}
+ didReadAny = true;
size_t nOptions;
char** option = String_split(line, '=', &nOptions);
free (line);
@@ -131,20 +332,57 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
String_freeArray(option);
continue;
}
- if (String_eq(option[0], "fields")) {
- readFields(this->fields, &(this->flags), option[1]);
- didReadFields = true;
- } else if (String_eq(option[0], "sort_key")) {
+ if (String_eq(option[0], "config_reader_min_version")) {
+ this->config_version = atoi(option[1]);
+ if (this->config_version > CONFIG_READER_MIN_VERSION) {
+ // the version of the config file on disk is newer than what we can read
+ fprintf(stderr, "WARNING: %s specifies configuration format\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") && 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], "sort_direction")) {
- this->direction = atoi(option[1]);
- } else if (String_eq(option[0], "tree_view")) {
- this->treeView = atoi(option[1]);
+ 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.
+ 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")) {
@@ -153,6 +391,10 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
this->showProgramPath = atoi(option[1]);
} else if (String_eq(option[0], "highlight_base_name")) {
this->highlightBaseName = atoi(option[1]);
+ } else if (String_eq(option[0], "highlight_deleted_exe")) {
+ this->highlightDeletedExe = atoi(option[1]);
+ } else if (String_eq(option[0], "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")) {
@@ -160,7 +402,7 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
} else if (String_eq(option[0], "highlight_changes")) {
this->highlightChanges = atoi(option[1]);
} else if (String_eq(option[0], "highlight_changes_delay_secs")) {
- this->highlightDelaySecs = CLAMP(atoi(option[1]), 1, 24*60*60);
+ this->highlightDelaySecs = CLAMP(atoi(option[1]), 1, 24 * 60 * 60);
} else if (String_eq(option[0], "find_comm_in_cmdline")) {
this->findCommInCmdline = atoi(option[1]);
} else if (String_eq(option[0], "strip_exe_from_cmdline")) {
@@ -169,6 +411,8 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
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]);
@@ -183,7 +427,7 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
this->showCPUUsage = atoi(option[1]);
} else if (String_eq(option[0], "show_cpu_frequency")) {
this->showCPUFrequency = atoi(option[1]);
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
} else if (String_eq(option[0], "show_cpu_temperature")) {
this->showCPUTemperature = atoi(option[1]);
} else if (String_eq(option[0], "degree_fahrenheit")) {
@@ -200,8 +444,16 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
if (this->colorScheme < 0 || this->colorScheme >= LAST_COLORSCHEME) {
this->colorScheme = 0;
}
+ #ifdef HAVE_GETMOUSE
} else if (String_eq(option[0], "enable_mouse")) {
this->enableMouse = atoi(option[1]);
+ #endif
+ } else if (String_eq(option[0], "header_layout")) {
+ this->hLayout = isdigit((unsigned char)option[1][0]) ? ((HeaderLayout) atoi(option[1])) : HeaderLayout_fromName(option[1]);
+ if (this->hLayout < 0 || this->hLayout >= LAST_HEADER_LAYOUT)
+ this->hLayout = HF_TWO_50_50;
+ free(this->hColumns);
+ this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
} else if (String_eq(option[0], "left_meters")) {
Settings_readMeters(this, option[1], 0);
didReadMeters = true;
@@ -214,121 +466,228 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo
} else if (String_eq(option[0], "right_meter_modes")) {
Settings_readMeterModes(this, option[1], 1);
didReadMeters = true;
+ } else if (String_startsWith(option[0], "column_meters_")) {
+ Settings_readMeters(this, option[1], atoi(option[0] + strlen("column_meters_")));
+ didReadMeters = true;
+ } else if (String_startsWith(option[0], "column_meter_modes_")) {
+ Settings_readMeterModes(this, option[1], atoi(option[0] + strlen("column_meter_modes_")));
+ didReadMeters = true;
+ } else if (String_eq(option[0], "hide_function_bar")) {
+ this->hideFunctionBar = atoi(option[1]);
#ifdef HAVE_LIBHWLOC
} 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]);
}
String_freeArray(option);
}
fclose(fd);
- if (!didReadMeters) {
+ if (!didReadMeters || !Settings_validateMeters(this))
Settings_defaultMeters(this, initialCpuCount);
- }
- return didReadFields;
+ if (!this->nScreens)
+ Settings_defaultScreens(this);
+ return didReadAny;
}
-static void writeFields(FILE* fd, ProcessField* fields, const char* name) {
- fprintf(fd, "%s=", name);
+static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, bool byName, char separator) {
const char* sep = "";
- for (int i = 0; fields[i]; i++) {
- // This "-1" is for compatibility with the older enum format.
- fprintf(fd, "%s%d", sep, (int) fields[i] - 1);
+ for (unsigned int i = 0; fields[i]; i++) {
+ if (fields[i] < LAST_PROCESSFIELD && byName) {
+ const char* pName = toFieldName(columns, fields[i]);
+ fprintf(fd, "%s%s", sep, pName);
+ } else if (fields[i] >= LAST_PROCESSFIELD && byName) {
+ const char* pName = toFieldName(columns, fields[i]);
+ fprintf(fd, " Dynamic(%s)", pName);
+ } else {
+ // This "-1" is for compatibility with the older enum format.
+ fprintf(fd, "%s%d", sep, (int) fields[i] - 1);
+ }
sep = " ";
}
- fprintf(fd, "\n");
+ fputc(separator, fd);
}
-static void writeMeters(Settings* this, FILE* fd, int column) {
+static void writeList(FILE* fd, char** list, int len, char separator) {
const char* sep = "";
- for (int i = 0; i < this->columns[column].len; i++) {
- fprintf(fd, "%s%s", sep, this->columns[column].names[i]);
+ for (int i = 0; i < len; i++) {
+ fprintf(fd, "%s%s", sep, list[i]);
sep = " ";
}
- fprintf(fd, "\n");
+ fputc(separator, fd);
+}
+
+static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) {
+ writeList(fd, this->hColumns[column].names, this->hColumns[column].len, separator);
}
-static void writeMeterModes(Settings* this, FILE* fd, int column) {
+static void writeMeterModes(const Settings* this, FILE* fd, char separator, unsigned int column) {
const char* sep = "";
- for (int i = 0; i < this->columns[column].len; i++) {
- fprintf(fd, "%s%d", sep, this->columns[column].modes[i]);
+ for (size_t i = 0; i < this->hColumns[column].len; i++) {
+ fprintf(fd, "%s%d", sep, this->hColumns[column].modes[i]);
sep = " ";
}
- fprintf(fd, "\n");
+ fputc(separator, fd);
}
-bool Settings_write(Settings* this) {
+int Settings_write(const Settings* this, bool onCrash) {
FILE* fd;
+ char separator;
+ if (onCrash) {
+ fd = stderr;
+ separator = ';';
+ } else {
+ fd = fopen(this->filename, "w");
+ if (fd == NULL)
+ return -errno;
+ separator = '\n';
+ }
- CRT_dropPrivileges();
- fd = fopen(this->filename, "w");
- CRT_restorePrivileges();
+ #define printSettingInteger(setting_, value_) \
+ fprintf(fd, setting_ "=%d%c", (int) (value_), separator)
+ #define printSettingString(setting_, value_) \
+ fprintf(fd, setting_ "=%s%c", value_, separator)
- if (fd == NULL) {
- return false;
+ if (!onCrash) {
+ fprintf(fd, "# Beware! This file is rewritten by htop when settings are changed in the interface.\n");
+ fprintf(fd, "# The parser is also very primitive, and not human-friendly.\n");
}
- fprintf(fd, "# Beware! This file is rewritten by htop when settings are changed in the interface.\n");
- fprintf(fd, "# The parser is also very primitive, and not human-friendly.\n");
- writeFields(fd, this->fields, "fields");
- // This "-1" is for compatibility with the older enum format.
- fprintf(fd, "sort_key=%d\n", (int) this->sortKey - 1);
- fprintf(fd, "sort_direction=%d\n", (int) this->direction);
- fprintf(fd, "hide_kernel_threads=%d\n", (int) this->hideKernelThreads);
- fprintf(fd, "hide_userland_threads=%d\n", (int) this->hideUserlandThreads);
- fprintf(fd, "shadow_other_users=%d\n", (int) this->shadowOtherUsers);
- fprintf(fd, "show_thread_names=%d\n", (int) this->showThreadNames);
- fprintf(fd, "show_program_path=%d\n", (int) this->showProgramPath);
- fprintf(fd, "highlight_base_name=%d\n", (int) this->highlightBaseName);
- fprintf(fd, "highlight_megabytes=%d\n", (int) this->highlightMegabytes);
- fprintf(fd, "highlight_threads=%d\n", (int) this->highlightThreads);
- fprintf(fd, "highlight_changes=%d\n", (int) this->highlightChanges);
- fprintf(fd, "highlight_changes_delay_secs=%d\n", (int) this->highlightDelaySecs);
- fprintf(fd, "find_comm_in_cmdline=%d\n", (int) this->findCommInCmdline);
- fprintf(fd, "strip_exe_from_cmdline=%d\n", (int) this->stripExeFromCmdline);
- fprintf(fd, "show_merged_command=%d\n", (int) this->showMergedCommand);
- fprintf(fd, "tree_view=%d\n", (int) this->treeView);
- fprintf(fd, "header_margin=%d\n", (int) this->headerMargin);
- fprintf(fd, "detailed_cpu_time=%d\n", (int) this->detailedCPUTime);
- fprintf(fd, "cpu_count_from_one=%d\n", (int) this->countCPUsFromOne);
- fprintf(fd, "show_cpu_usage=%d\n", (int) this->showCPUUsage);
- fprintf(fd, "show_cpu_frequency=%d\n", (int) this->showCPUFrequency);
- #ifdef HAVE_SENSORS_SENSORS_H
- fprintf(fd, "show_cpu_temperature=%d\n", (int) this->showCPUTemperature);
- fprintf(fd, "degree_fahrenheit=%d\n", (int) this->degreeFahrenheit);
+ printSettingString("htop_version", VERSION);
+ printSettingInteger("config_reader_min_version", CONFIG_READER_MIN_VERSION);
+ fprintf(fd, "fields="); writeFields(fd, this->screens[0]->fields, this->dynamicColumns, false, separator);
+ 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);
+ printSettingInteger("highlight_changes_delay_secs", this->highlightDelaySecs);
+ printSettingInteger("find_comm_in_cmdline", this->findCommInCmdline);
+ printSettingInteger("strip_exe_from_cmdline", this->stripExeFromCmdline);
+ printSettingInteger("show_merged_command", this->showMergedCommand);
+ printSettingInteger("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);
+ printSettingInteger("show_cpu_frequency", this->showCPUFrequency);
+ #ifdef BUILD_WITH_CPU_TEMP
+ printSettingInteger("show_cpu_temperature", this->showCPUTemperature);
+ printSettingInteger("degree_fahrenheit", this->degreeFahrenheit);
+ #endif
+ printSettingInteger("update_process_names", this->updateProcessNames);
+ printSettingInteger("account_guest_in_cpu_meter", this->accountGuestInCPUMeter);
+ printSettingInteger("color_scheme", this->colorScheme);
+ #ifdef HAVE_GETMOUSE
+ printSettingInteger("enable_mouse", this->enableMouse);
#endif
- fprintf(fd, "update_process_names=%d\n", (int) this->updateProcessNames);
- fprintf(fd, "account_guest_in_cpu_meter=%d\n", (int) this->accountGuestInCPUMeter);
- fprintf(fd, "color_scheme=%d\n", (int) this->colorScheme);
- fprintf(fd, "enable_mouse=%d\n", (int) this->enableMouse);
- fprintf(fd, "delay=%d\n", (int) this->delay);
- fprintf(fd, "left_meters="); writeMeters(this, fd, 0);
- fprintf(fd, "left_meter_modes="); writeMeterModes(this, fd, 0);
- fprintf(fd, "right_meters="); writeMeters(this, fd, 1);
- fprintf(fd, "right_meter_modes="); writeMeterModes(this, fd, 1);
+ printSettingInteger("delay", (int) this->delay);
+ printSettingInteger("hide_function_bar", (int) this->hideFunctionBar);
#ifdef HAVE_LIBHWLOC
- fprintf(fd, "topology_affinity=%d\n", (int) this->topologyAffinity);
+ printSettingInteger("topology_affinity", this->topologyAffinity);
#endif
- fclose(fd);
- return true;
+
+ printSettingString("header_layout", HeaderLayout_getName(this->hLayout));
+ for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
+ fprintf(fd, "column_meters_%u=", i);
+ writeMeters(this, fd, separator, i);
+ fprintf(fd, "column_meter_modes_%u=", i);
+ writeMeterModes(this, fd, separator, i);
+ }
+
+ // 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];
+ fprintf(fd, "screen:%s=", ss->name);
+ writeFields(fd, ss->fields, this->dynamicColumns, true, separator);
+ printSettingString(".sort_key", toFieldName(this->dynamicColumns, ss->sortKey));
+ printSettingString(".tree_sort_key", toFieldName(this->dynamicColumns, ss->treeSortKey));
+ printSettingInteger(".tree_view", ss->treeView);
+ printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID);
+ printSettingInteger(".sort_direction", ss->direction);
+ printSettingInteger(".tree_sort_direction", ss->treeDirection);
+ printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed);
+ }
+
+ #undef printSettingString
+ #undef printSettingInteger
+
+ if (onCrash)
+ return 0;
+
+ int r = 0;
+
+ if (ferror(fd) != 0)
+ r = (errno != 0) ? -errno : -EBADF;
+
+ if (fclose(fd) != 0)
+ r = r ? r : -errno;
+
+ return r;
}
-Settings* Settings_new(int initialCpuCount) {
+Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns) {
Settings* this = xCalloc(1, sizeof(Settings));
- this->sortKey = PERCENT_CPU;
- this->direction = 1;
+ this->dynamicColumns = dynamicColumns;
+ this->hLayout = HF_TWO_50_50;
+ this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
+
this->shadowOtherUsers = false;
this->showThreadNames = false;
- this->hideKernelThreads = false;
+ this->hideKernelThreads = true;
this->hideUserlandThreads = false;
- this->treeView = false;
+ this->hideRunningInContainer = false;
this->highlightBaseName = false;
- this->highlightMegabytes = false;
+ this->highlightDeletedExe = true;
+ this->shadowDistPathPrefix = false;
+ this->highlightMegabytes = true;
this->detailedCPUTime = false;
this->countCPUsFromOne = false;
this->showCPUUsage = true;
this->showCPUFrequency = false;
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
this->showCPUTemperature = false;
this->degreeFahrenheit = false;
#endif
@@ -340,28 +699,25 @@ Settings* Settings_new(int initialCpuCount) {
this->findCommInCmdline = true;
this->stripExeFromCmdline = true;
this->showMergedCommand = false;
+ this->hideFunctionBar = 0;
+ this->headerMargin = true;
#ifdef HAVE_LIBHWLOC
this->topologyAffinity = false;
#endif
- this->fields = xCalloc(Platform_numberOfFields + 1, sizeof(ProcessField));
- // TODO: turn 'fields' into a Vector,
- // (and ProcessFields into proper objects).
- this->flags = 0;
- 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;
- char* rcfile = getenv("HTOPRC");
+ const char* rcfile = getenv("HTOPRC");
if (rcfile) {
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;
@@ -375,7 +731,6 @@ Settings* Settings_new(int initialCpuCount) {
htopDir = String_cat(home, "/.config/htop");
}
legacyDotfile = String_cat(home, "/.htoprc");
- CRT_dropPrivileges();
(void) mkdir(configDir, 0700);
(void) mkdir(htopDir, 0700);
free(htopDir);
@@ -386,10 +741,11 @@ Settings* Settings_new(int initialCpuCount) {
free(legacyDotfile);
legacyDotfile = NULL;
}
- CRT_restorePrivileges();
}
this->colorScheme = 0;
+#ifdef HAVE_GETMOUSE
this->enableMouse = true;
+#endif
this->changed = false;
this->delay = DEFAULT_DELAY;
bool ok = false;
@@ -397,7 +753,7 @@ Settings* Settings_new(int initialCpuCount) {
ok = Settings_read(this, legacyDotfile, initialCpuCount);
if (ok) {
// Transition to new location and delete old configuration file
- if (Settings_write(this)) {
+ if (Settings_write(this, false) == 0) {
unlink(legacyDotfile);
}
}
@@ -407,29 +763,68 @@ Settings* Settings_new(int initialCpuCount) {
ok = Settings_read(this, this->filename, initialCpuCount);
}
if (!ok) {
+ this->screenTabs = true;
this->changed = true;
- // TODO: how to get SYSCONFDIR correctly through Autoconf?
- char* systemSettings = String_cat(SYSCONFDIR, "/htoprc");
- ok = Settings_read(this, systemSettings, initialCpuCount);
- free(systemSettings);
+ ok = Settings_read(this, SYSCONFDIR "/htoprc", initialCpuCount);
}
if (!ok) {
Settings_defaultMeters(this, initialCpuCount);
- this->hideKernelThreads = true;
- this->highlightMegabytes = true;
- this->highlightThreads = true;
- this->findCommInCmdline = true;
- this->stripExeFromCmdline = true;
- this->showMergedCommand = false;
- this->headerMargin = true;
+ Settings_defaultScreens(this);
}
+
+ this->ssIndex = 0;
+ this->ss = this->screens[this->ssIndex];
+
+ this->lastUpdate = 1;
+
return this;
}
-void Settings_invertSortOrder(Settings* this) {
- if (this->direction == 1) {
- this->direction = -1;
+void ScreenSettings_invertSortOrder(ScreenSettings* this) {
+ int* attr = (this->treeView) ? &(this->treeDirection) : &(this->direction);
+ *attr = (*attr == 1) ? -1 : 1;
+}
+
+void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) {
+ if (this->treeViewAlwaysByPID || !this->treeView) {
+ this->sortKey = sortKey;
+ this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
+ this->treeView = false;
} else {
- this->direction = 1;
+ this->treeSortKey = sortKey;
+ this->treeDirection = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
}
}
+
+static bool readonly = false;
+
+void Settings_enableReadonly(void) {
+ readonly = true;
+}
+
+bool Settings_isReadonly(void) {
+ return readonly;
+}
+
+void Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout) {
+ unsigned int oldColumns = HeaderLayout_getColumns(this->hLayout);
+ unsigned int newColumns = HeaderLayout_getColumns(hLayout);
+
+ if (newColumns > oldColumns) {
+ this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));
+ memset(this->hColumns + oldColumns, 0, (newColumns - oldColumns) * sizeof(MeterColumnSetting));
+ } else if (newColumns < oldColumns) {
+ for (unsigned int i = newColumns; i < oldColumns; i++) {
+ if (this->hColumns[i].names) {
+ for (size_t j = 0; j < this->hColumns[i].len; j++)
+ free(this->hColumns[i].names[j]);
+ free(this->hColumns[i].names);
+ }
+ free(this->hColumns[i].modes);
+ }
+ this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));
+ }
+
+ this->hLayout = hLayout;
+ this->changed = true;
+}
diff --git a/Settings.h b/Settings.h
index 752970a..baf05da 100644
--- a/Settings.h
+++ b/Settings.h
@@ -3,7 +3,7 @@
/*
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.
*/
@@ -12,44 +12,72 @@ in the source distribution for its full text.
#include <stdbool.h>
#include <stdint.h>
+#include "Hashtable.h"
+#include "HeaderLayout.h"
#include "Process.h"
#define DEFAULT_DELAY 15
+#define CONFIG_READER_MIN_VERSION 3
+
+typedef struct {
+ const char* name;
+ const char* columns;
+ const char* sortKey;
+} ScreenDefaults;
+
typedef struct {
- int len;
+ size_t len;
char** names;
int* modes;
-} MeterColumnSettings;
+} MeterColumnSetting;
+
+typedef struct {
+ char* name;
+ ProcessField* fields;
+ uint32_t flags;
+ int direction;
+ int treeDirection;
+ ProcessField sortKey;
+ ProcessField treeSortKey;
+ bool treeView;
+ bool treeViewAlwaysByPID;
+ bool allBranchesCollapsed;
+} ScreenSettings;
typedef struct Settings_ {
char* filename;
- MeterColumnSettings columns[2];
+ int config_version;
+ HeaderLayout hLayout;
+ MeterColumnSetting* hColumns;
+ Hashtable* dynamicColumns;
+
+ ScreenSettings** screens;
+ unsigned int nScreens;
+ unsigned int ssIndex;
+ ScreenSettings* ss;
- ProcessField* fields;
- uint32_t flags;
int colorScheme;
int delay;
- int direction;
- ProcessField sortKey;
-
bool countCPUsFromOne;
bool detailedCPUTime;
bool showCPUUsage;
bool showCPUFrequency;
- #ifdef HAVE_SENSORS_SENSORS_H
+ #ifdef BUILD_WITH_CPU_TEMP
bool showCPUTemperature;
bool degreeFahrenheit;
#endif
- bool treeView;
bool showProgramPath;
bool shadowOtherUsers;
bool showThreadNames;
bool hideKernelThreads;
+ bool hideRunningInContainer;
bool hideUserlandThreads;
bool highlightBaseName;
+ bool highlightDeletedExe;
+ bool shadowDistPathPrefix;
bool highlightMegabytes;
bool highlightThreads;
bool highlightChanges;
@@ -60,22 +88,49 @@ typedef struct Settings_ {
bool updateProcessNames;
bool accountGuestInCPUMeter;
bool headerMargin;
+ bool screenTabs;
+ #ifdef HAVE_GETMOUSE
bool enableMouse;
+ #endif
+ int hideFunctionBar; // 0 - off, 1 - on ESC until next input, 2 - permanently
#ifdef HAVE_LIBHWLOC
bool topologyAffinity;
#endif
bool changed;
+ uint64_t lastUpdate;
} Settings;
#define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu))
+static inline ProcessField ScreenSettings_getActiveSortKey(const ScreenSettings* this) {
+ return (this->treeView)
+ ? (this->treeViewAlwaysByPID ? PID : this->treeSortKey)
+ : this->sortKey;
+}
+
+static inline int ScreenSettings_getActiveDirection(const ScreenSettings* this) {
+ return this->treeView ? this->treeDirection : this->direction;
+}
+
void Settings_delete(Settings* this);
-bool Settings_write(Settings* this);
+int Settings_write(const Settings* this, bool onCrash);
+
+Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns);
+
+ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults);
+
+void ScreenSettings_delete(ScreenSettings* this);
+
+void ScreenSettings_invertSortOrder(ScreenSettings* this);
+
+void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey);
+
+void Settings_enableReadonly(void);
-Settings* Settings_new(int initialCpuCount);
+bool Settings_isReadonly(void);
-void Settings_invertSortOrder(Settings* this);
+void Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout);
#endif
diff --git a/SignalsPanel.c b/SignalsPanel.c
index 436fc57..7e21ce9 100644
--- a/SignalsPanel.c
+++ b/SignalsPanel.c
@@ -1,13 +1,13 @@
/*
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 "SignalsPanel.h"
+// the above contains #include <signal.h> so do not add that here again (breaks Solaris build)
-#include <signal.h>
#include <stdbool.h>
#include "FunctionBar.h"
@@ -18,15 +18,14 @@ in the source distribution for its full text.
#include "XUtils.h"
-Panel* SignalsPanel_new() {
- Panel* this = Panel_new(1, 1, 1, 1, true, Class(ListItem), FunctionBar_newEnterEsc("Send ", "Cancel "));
- const int defaultSignal = SIGTERM;
+Panel* SignalsPanel_new(int preSelectedSignal) {
+ Panel* this = Panel_new(1, 1, 1, 1, Class(ListItem), true, FunctionBar_newEnterEsc("Send ", "Cancel "));
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 0a90b08..529043a 100644
--- a/SignalsPanel.h
+++ b/SignalsPanel.h
@@ -3,17 +3,26 @@
/*
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 "config.h" // IWYU pragma: keep
+
+#ifndef HTOP_SOLARIS
+#include <signal.h>
+#endif
+
#include "Panel.h"
+
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 8e39c75..c0f4820 100644
--- a/SwapMeter.c
+++ b/SwapMeter.c
@@ -1,12 +1,17 @@
/*
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.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "SwapMeter.h"
+#include <math.h>
+#include <stddef.h>
+
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
@@ -14,11 +19,16 @@ in the source distribution for its full text.
static const int SwapMeter_attributes[] = {
- SWAP
+ SWAP,
+ SWAP_CACHE
};
-static void SwapMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void SwapMeter_updateValues(Meter* this) {
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
int written;
+
+ this->values[1] = NAN; /* 'cached' not present on all platforms */
Platform_setSwapValues(this);
written = Meter_humanUnit(buffer, this->values[0], size);
@@ -32,12 +42,18 @@ static void SwapMeter_updateValues(Meter* this, char* buffer, size_t size) {
static void SwapMeter_display(const Object* cast, RichString* out) {
char buffer[50];
const Meter* this = (const Meter*)cast;
- RichString_write(out, CRT_colors[METER_TEXT], ":");
- Meter_humanUnit(buffer, this->total, 50);
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
- Meter_humanUnit(buffer, this->values[0], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " used:");
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
+ 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[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[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);
+ }
}
const MeterClass SwapMeter_class = {
@@ -48,7 +64,7 @@ const MeterClass SwapMeter_class = {
},
.updateValues = SwapMeter_updateValues,
.defaultMode = BAR_METERMODE,
- .maxItems = 1,
+ .maxItems = 2,
.total = 100.0,
.attributes = SwapMeter_attributes,
.name = "Swap",
diff --git a/SwapMeter.h b/SwapMeter.h
index 623a036..b71e83f 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_ITEMCOUNT = 2, // number of entries in this enum
+} SwapMeterValues;
+
extern const MeterClass SwapMeter_class;
#endif
diff --git a/SysArchMeter.c b/SysArchMeter.c
new file mode 100644
index 0000000..1985caa
--- /dev/null
+++ b/SysArchMeter.c
@@ -0,0 +1,44 @@
+/*
+htop - SysArchMeter.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "SysArchMeter.h"
+
+#include <stddef.h>
+
+#include "CRT.h"
+#include "Object.h"
+#include "Platform.h"
+#include "XUtils.h"
+
+
+static const int SysArchMeter_attributes[] = {HOSTNAME};
+
+static void SysArchMeter_updateValues(Meter* this) {
+ static char* string;
+
+ if (string == NULL)
+ Platform_getRelease(&string);
+
+ String_safeStrncpy(this->txtBuffer, string, sizeof(this->txtBuffer));
+}
+
+const MeterClass SysArchMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete
+ },
+ .updateValues = SysArchMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 0,
+ .total = 100.0,
+ .attributes = SysArchMeter_attributes,
+ .name = "System",
+ .uiName = "System",
+ .caption = "System: ",
+};
diff --git a/SysArchMeter.h b/SysArchMeter.h
new file mode 100644
index 0000000..50b3869
--- /dev/null
+++ b/SysArchMeter.h
@@ -0,0 +1,14 @@
+#ifndef HEADER_SysArchMeter
+#define HEADER_SysArchMeter
+/*
+htop - SysArchMeter.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+#include "Meter.h"
+
+
+extern const MeterClass SysArchMeter_class;
+
+#endif
diff --git a/TESTPLAN b/TESTPLAN
index 7669485..88fe039 100644
--- a/TESTPLAN
+++ b/TESTPLAN
@@ -55,7 +55,9 @@ Main screen:
<l> - enter LSOF screen.
- <m>, <n>, <o>, <p> - do nothing.
+ <m>, <n>, <o> - do nothing.
+
+ <p> - hide/show program path.
<F10>, <q> - quit program.
diff --git a/TasksMeter.c b/TasksMeter.c
index 7696988..64c9837 100644
--- a/TasksMeter.c
+++ b/TasksMeter.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -23,52 +23,40 @@ static const int TasksMeter_attributes[] = {
TASKS_RUNNING
};
-static void TasksMeter_updateValues(Meter* this, char* buffer, size_t len) {
+static void TasksMeter_updateValues(Meter* this) {
const ProcessList* pl = this->pl;
this->values[0] = pl->kernelThreads;
this->values[1] = pl->userlandThreads;
this->values[2] = pl->totalTasks - pl->kernelThreads - pl->userlandThreads;
- this->values[3] = MINIMUM(pl->runningTasks, pl->cpuCount);
- if (pl->totalTasks > this->total) {
- this->total = pl->totalTasks;
- }
- if (pl->settings->hideKernelThreads) {
- this->values[0] = 0;
- }
- xSnprintf(buffer, len, "%d/%d", (int) this->values[3], (int) this->total);
+ this->values[3] = MINIMUM(pl->runningTasks, pl->activeCPUs);
+ this->total = pl->totalTasks;
+
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%u/%u", MINIMUM(pl->runningTasks, pl->activeCPUs), pl->totalTasks);
}
static void TasksMeter_display(const Object* cast, RichString* out) {
const Meter* this = (const Meter*)cast;
const Settings* settings = this->pl->settings;
char buffer[20];
+ int len;
+
+ len = xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[2]);
+ RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, len);
+
+ RichString_appendAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], ", ");
+ len = xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[1]);
+ RichString_appendnAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[TASKS_RUNNING], buffer, len);
+ RichString_appendAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], " thr");
- int processes = (int) this->values[2];
+ RichString_appendAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], ", ");
+ len = xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[0]);
+ RichString_appendnAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[TASKS_RUNNING], buffer, len);
+ RichString_appendAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], " kthr");
- xSnprintf(buffer, sizeof(buffer), "%d", processes);
- RichString_write(out, CRT_colors[METER_VALUE], buffer);
- int threadValueColor = CRT_colors[METER_VALUE];
- int threadCaptionColor = CRT_colors[METER_TEXT];
- if (settings->highlightThreads) {
- threadValueColor = CRT_colors[PROCESS_THREAD_BASENAME];
- threadCaptionColor = CRT_colors[PROCESS_THREAD];
- }
- if (!settings->hideUserlandThreads) {
- RichString_append(out, CRT_colors[METER_TEXT], ", ");
- xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[1]);
- RichString_append(out, threadValueColor, buffer);
- RichString_append(out, threadCaptionColor, " thr");
- }
- if (!settings->hideKernelThreads) {
- RichString_append(out, CRT_colors[METER_TEXT], ", ");
- xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[0]);
- RichString_append(out, threadValueColor, buffer);
- RichString_append(out, threadCaptionColor, " kthr");
- }
- RichString_append(out, CRT_colors[METER_TEXT], "; ");
- xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[3]);
- RichString_append(out, CRT_colors[TASKS_RUNNING], buffer);
- RichString_append(out, CRT_colors[METER_TEXT], " running");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "; ");
+ len = xSnprintf(buffer, sizeof(buffer), "%d", (int)this->values[3]);
+ RichString_appendnAscii(out, CRT_colors[TASKS_RUNNING], buffer, len);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " running");
}
const MeterClass TasksMeter_class = {
diff --git a/TasksMeter.h b/TasksMeter.h
index cecb401..9f9ba63 100644
--- a/TasksMeter.h
+++ b/TasksMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass TasksMeter_class;
#endif
diff --git a/TraceScreen.c b/TraceScreen.c
index 47cf0ab..2aa0781 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>
@@ -18,12 +19,10 @@ in the source distribution for its full text.
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
-#include <sys/time.h>
#include <sys/wait.h>
#include "CRT.h"
#include "FunctionBar.h"
-#include "IncSet.h"
#include "Panel.h"
#include "ProvideCurses.h"
#include "XUtils.h"
@@ -33,33 +32,25 @@ static const char* const TraceScreenFunctions[] = {"Search ", "Filter ", "AutoSc
static const char* const TraceScreenKeys[] = {"F3", "F4", "F8", "F9", "Esc"};
-static int TraceScreenEvents[] = {KEY_F(3), KEY_F(4), KEY_F(8), KEY_F(9), 27};
+static const int TraceScreenEvents[] = {KEY_F(3), KEY_F(4), KEY_F(8), KEY_F(9), 27};
-const InfoScreenClass TraceScreen_class = {
- .super = {
- .extends = Class(Object),
- .delete = TraceScreen_delete
- },
- .draw = TraceScreen_draw,
- .onErr = TraceScreen_updateTrace,
- .onKey = TraceScreen_onKey,
-};
-
-TraceScreen* TraceScreen_new(Process* process) {
+TraceScreen* TraceScreen_new(const Process* process) {
// This initializes all TraceScreen variables to "false" so only default = true ones need to be set below
TraceScreen* this = xCalloc(1, sizeof(TraceScreen));
Object_setClass(this, Class(TraceScreen));
this->tracing = true;
FunctionBar* fuBar = FunctionBar_new(TraceScreenFunctions, TraceScreenKeys, TraceScreenEvents);
CRT_disableDelay();
- return (TraceScreen*) InfoScreen_init(&this->super, process, fuBar, LINES - 2, "");
+ return (TraceScreen*) InfoScreen_init(&this->super, process, fuBar, LINES - 2, " ");
}
void TraceScreen_delete(Object* cast) {
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) {
@@ -70,12 +61,8 @@ void TraceScreen_delete(Object* cast) {
free(InfoScreen_done((InfoScreen*)this));
}
-void TraceScreen_draw(InfoScreen* this) {
- attrset(CRT_colors[PANEL_HEADER_FOCUS]);
- mvhline(0, 0, ' ', COLS);
- mvprintw(0, 0, "Trace of process %d - %s", this->process->pid, Process_getCommand(this->process));
- attrset(CRT_colors[DEFAULT_COLOR]);
- IncSet_drawBar(this->inc);
+static void TraceScreen_draw(InfoScreen* this) {
+ InfoScreen_drawTitled(this, "Trace of process %d - %s", this->process->pid, Process_getCommand(this->process));
}
bool TraceScreen_forkTracer(TraceScreen* this) {
@@ -101,16 +88,15 @@ bool TraceScreen_forkTracer(TraceScreen* this) {
dup2(fdpair[1], STDERR_FILENO);
close(fdpair[1]);
- CRT_dropPrivileges();
-
char buffer[32] = {0};
xSnprintf(buffer, sizeof(buffer), "%d", this->super.process->pid);
- execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, NULL);
+ // Use of NULL in variadic functions must have a pointer cast.
+ // The NULL constant is not required by standard to have a pointer type.
+ execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (char *)NULL);
// Should never reach here, unless execlp fails ...
const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH.";
- ssize_t written = write(STDERR_FILENO, message, strlen(message));
- (void) written;
+ (void)! write(STDERR_FILENO, message, strlen(message));
exit(127);
}
@@ -131,7 +117,7 @@ err:
return false;
}
-void TraceScreen_updateTrace(InfoScreen* super) {
+static void TraceScreen_updateTrace(InfoScreen* super) {
TraceScreen* this = (TraceScreen*) super;
char buffer[1025];
@@ -176,22 +162,32 @@ void TraceScreen_updateTrace(InfoScreen* super) {
}
}
-bool TraceScreen_onKey(InfoScreen* super, int ch) {
+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;
}
+
+const InfoScreenClass TraceScreen_class = {
+ .super = {
+ .extends = Class(Object),
+ .delete = TraceScreen_delete
+ },
+ .draw = TraceScreen_draw,
+ .onErr = TraceScreen_updateTrace,
+ .onKey = TraceScreen_onKey,
+};
diff --git a/TraceScreen.h b/TraceScreen.h
index 8192047..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.
*/
@@ -28,16 +28,10 @@ typedef struct TraceScreen_ {
extern const InfoScreenClass TraceScreen_class;
-TraceScreen* TraceScreen_new(Process* process);
+TraceScreen* TraceScreen_new(const Process* process);
void TraceScreen_delete(Object* cast);
-void TraceScreen_draw(InfoScreen* this);
-
bool TraceScreen_forkTracer(TraceScreen* this);
-void TraceScreen_updateTrace(InfoScreen* super);
-
-bool TraceScreen_onKey(InfoScreen* super, int ch);
-
#endif
diff --git a/UptimeMeter.c b/UptimeMeter.c
index 37740c2..d4b3175 100644
--- a/UptimeMeter.c
+++ b/UptimeMeter.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -17,10 +17,10 @@ static const int UptimeMeter_attributes[] = {
UPTIME
};
-static void UptimeMeter_updateValues(Meter* this, char* buffer, size_t len) {
+static void UptimeMeter_updateValues(Meter* this) {
int totalseconds = Platform_getUptime();
- if (totalseconds == -1) {
- xSnprintf(buffer, len, "(unknown)");
+ if (totalseconds <= 0) {
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "(unknown)");
return;
}
int seconds = totalseconds % 60;
@@ -41,7 +41,7 @@ static void UptimeMeter_updateValues(Meter* this, char* buffer, size_t len) {
} else {
daysbuf[0] = '\0';
}
- xSnprintf(buffer, len, "%s%02d:%02d:%02d", daysbuf, hours, minutes, seconds);
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s%02d:%02d:%02d", daysbuf, hours, minutes, seconds);
}
const MeterClass UptimeMeter_class = {
diff --git a/UptimeMeter.h b/UptimeMeter.h
index 49300bb..5a852ad 100644
--- a/UptimeMeter.h
+++ b/UptimeMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass UptimeMeter_class;
#endif
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 0cac7dc..ecfd214 100644
--- a/UsersTable.h
+++ b/UsersTable.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Hashtable.h"
+
typedef struct UsersTable_ {
Hashtable* users;
} UsersTable;
diff --git a/Vector.c b/Vector.c
index 40e6203..eddbc9a 100644
--- a/Vector.c
+++ b/Vector.c
@@ -1,7 +1,7 @@
/*
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.
*/
@@ -29,6 +29,8 @@ Vector* Vector_new(const ObjectClass* type, bool owner, int size) {
this->items = 0;
this->type = type;
this->owner = owner;
+ this->dirty_index = -1;
+ this->dirty_count = 0;
return this;
}
@@ -44,10 +46,21 @@ void Vector_delete(Vector* this) {
free(this);
}
+static inline bool Vector_isDirty(const Vector* this) {
+ if (this->dirty_count > 0) {
+ assert(0 <= this->dirty_index && this->dirty_index < this->items);
+ assert(this->dirty_count <= this->items);
+ return true;
+ }
+ assert(this->dirty_index == -1);
+ return false;
+}
+
#ifndef NDEBUG
static bool Vector_isConsistent(const Vector* this) {
assert(this->items <= this->arraySize);
+ assert(!Vector_isDirty(this));
if (this->owner) {
for (int i = 0; i < this->items; i++) {
@@ -60,15 +73,14 @@ static bool Vector_isConsistent(const Vector* this) {
return true;
}
-unsigned int Vector_count(const Vector* this) {
- unsigned int items = 0;
+bool Vector_countEquals(const Vector* this, unsigned int expectedCount) {
+ unsigned int n = 0;
for (int i = 0; i < this->items; i++) {
if (this->array[i]) {
- items++;
+ n++;
}
}
- assert(items == (unsigned int)this->items);
- return items;
+ return n == expectedCount;
}
Object* Vector_get(const Vector* this, int idx) {
@@ -88,13 +100,16 @@ int Vector_size(const Vector* this) {
void Vector_prune(Vector* this) {
assert(Vector_isConsistent(this));
if (this->owner) {
- for (int i = 0; i < this->items; i++)
+ for (int i = 0; i < this->items; i++) {
if (this->array[i]) {
Object_delete(this->array[i]);
- //this->array[i] = NULL;
+ this->array[i] = NULL;
}
+ }
}
this->items = 0;
+ this->dirty_index = -1;
+ this->dirty_count = 0;
}
//static int comparisons = 0;
@@ -242,6 +257,58 @@ Object* Vector_remove(Vector* this, int idx) {
}
}
+Object* Vector_softRemove(Vector* this, int idx) {
+ assert(idx >= 0 && idx < this->items);
+
+ Object* removed = this->array[idx];
+ assert(removed);
+ if (removed) {
+ this->array[idx] = NULL;
+
+ this->dirty_count++;
+ if (this->dirty_index < 0 || idx < this->dirty_index) {
+ this->dirty_index = idx;
+ }
+
+ if (this->owner) {
+ Object_delete(removed);
+ return NULL;
+ }
+ }
+
+ return removed;
+}
+
+void Vector_compact(Vector* this) {
+ if (!Vector_isDirty(this)) {
+ return;
+ }
+
+ const int size = this->items;
+ assert(0 <= this->dirty_index && this->dirty_index < size);
+ assert(this->array[this->dirty_index] == NULL);
+
+ int idx = this->dirty_index;
+
+ /* one deletion: use memmove, which should be faster */
+ if (this->dirty_count == 1) {
+ memmove(&this->array[idx], &this->array[idx + 1], (this->items - idx - 1) * sizeof(this->array[0]));
+ } else {
+ /* multiple deletions */
+ for (int i = idx + 1; i < size; i++) {
+ if (this->array[i]) {
+ this->array[idx++] = this->array[i];
+ }
+ }
+ }
+
+ this->items -= this->dirty_count;
+ this->dirty_index = -1;
+ this->dirty_count = 0;
+
+ assert(Vector_isConsistent(this));
+}
+
void Vector_moveUp(Vector* this, int idx) {
assert(idx >= 0 && idx < this->items);
assert(Vector_isConsistent(this));
diff --git a/Vector.h b/Vector.h
index ee51413..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,11 +68,15 @@ 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 */
-static inline Object* Vector_get(Vector* this, int idx) {
+static inline Object* Vector_get(const Vector* this, int idx) {
return this->array[idx];
}
@@ -68,6 +86,10 @@ static inline int Vector_size(const Vector* this) {
#endif /* NDEBUG */
+static inline const ObjectClass* Vector_type(const Vector* this) {
+ return this->type;
+}
+
void Vector_add(Vector* this, void* data_);
int Vector_indexOf(const Vector* this, const void* search_, Object_Compare compare);
diff --git a/XUtils.c b/XUtils.c
index cd5edb9..6a021f3 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.
*/
@@ -13,6 +13,7 @@ in the source distribution for its full text.
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -20,7 +21,7 @@ in the source distribution for its full text.
#include "CRT.h"
-void fail() {
+void fail(void) {
CRT_done();
abort();
@@ -36,9 +37,21 @@ void* xMalloc(size_t size) {
return data;
}
+void* xMallocArray(size_t nmemb, size_t size) {
+ assert(nmemb > 0);
+ assert(size > 0);
+ if (SIZE_MAX / nmemb < size) {
+ fail();
+ }
+ return xMalloc(nmemb * size);
+}
+
void* xCalloc(size_t nmemb, size_t size) {
assert(nmemb > 0);
assert(size > 0);
+ if (SIZE_MAX / nmemb < size) {
+ fail();
+ }
void* data = calloc(nmemb, size);
if (!data) {
fail();
@@ -56,9 +69,55 @@ void* xRealloc(void* ptr, size_t size) {
return data;
}
+void* xReallocArray(void* ptr, size_t nmemb, size_t size) {
+ assert(nmemb > 0);
+ assert(size > 0);
+ if (SIZE_MAX / nmemb < size) {
+ fail();
+ }
+ return xRealloc(ptr, nmemb * size);
+}
+
+void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size) {
+ assert((ptr == NULL) == (prevmemb == 0));
+
+ if (prevmemb == newmemb) {
+ return ptr;
+ }
+
+ void* ret = xReallocArray(ptr, newmemb, size);
+
+ if (newmemb > prevmemb) {
+ memset((unsigned char*)ret + prevmemb * size, '\0', (newmemb - prevmemb) * size);
+ }
+
+ return ret;
+}
+
+inline bool String_contains_i(const char* s1, const char* s2, 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);
@@ -80,10 +139,10 @@ char* String_trim(const char* in) {
}
char** String_split(const char* s, char sep, size_t* n) {
- const unsigned int rate = 10;
+ const size_t rate = 10;
char** out = xCalloc(rate, sizeof(char*));
size_t ctr = 0;
- unsigned int blocks = rate;
+ size_t blocks = rate;
const char* where;
while ((where = strchr(s, sep)) != NULL) {
size_t size = (size_t)(where - s);
@@ -118,40 +177,13 @@ 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 (;;) {
- char* ok = fgets(at, step + 1, fd);
+ const char* ok = fgets(at, step + 1, fd);
if (!ok) {
free(buffer);
return NULL;
@@ -171,6 +203,18 @@ char* String_readLine(FILE* fd) {
}
}
+size_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size) {
+ assert(size > 0);
+
+ size_t i = 0;
+ for (; i < size - 1 && src[i]; i++)
+ dest[i] = src[i];
+
+ dest[i] = '\0';
+
+ return i;
+}
+
int xAsprintf(char** strp, const char* fmt, ...) {
va_list vl;
va_start(vl, fmt);
@@ -205,6 +249,14 @@ char* xStrdup(const char* str) {
return data;
}
+void free_and_xStrdup(char** ptr, const char* str) {
+ if (*ptr && String_eq(*ptr, str))
+ return;
+
+ free(*ptr);
+ *ptr = xStrdup(str);
+}
+
char* xStrndup(const char* str, size_t len) {
char* data = strndup(str, len);
if (!data) {
@@ -261,3 +313,26 @@ 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;
+}
diff --git a/XUtils.h b/XUtils.h
index 19cfadb..e1c50be 100644
--- a/XUtils.h
+++ b/XUtils.h
@@ -3,7 +3,7 @@
/*
htop - StringUtils.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,7 +13,6 @@ in the source distribution for its full text.
#include <stdio.h>
#include <stdlib.h> // IWYU pragma: keep
#include <string.h> // IWYU pragma: keep
-#include <sys/types.h>
#include "Compat.h"
#include "Macros.h"
@@ -21,11 +20,17 @@ in the source distribution for its full text.
void fail(void) ATTR_NORETURN;
-void* xMalloc(size_t size);
+void* xMalloc(size_t size) ATTR_ALLOC_SIZE1(1) ATTR_MALLOC;
-void* xCalloc(size_t nmemb, size_t size);
+void* xMallocArray(size_t nmemb, size_t size) ATTR_ALLOC_SIZE2(1, 2) ATTR_MALLOC;
-void* xRealloc(void* ptr, size_t size);
+void* xCalloc(size_t nmemb, size_t size) ATTR_ALLOC_SIZE2(1, 2) ATTR_MALLOC;
+
+void* xRealloc(void* ptr, size_t size) ATTR_ALLOC_SIZE1(2);
+
+void* xReallocArray(void* ptr, size_t nmemb, size_t size) ATTR_ALLOC_SIZE2(2, 3);
+
+void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size) ATTR_ALLOC_SIZE2(3, 4);
/*
* String_startsWith gives better performance if strlen(match) can be computed
@@ -35,25 +40,24 @@ static inline bool String_startsWith(const char* s, const char* match) {
return strncmp(s, match, strlen(match)) == 0;
}
-static inline bool String_contains_i(const char* s1, const char* s2) {
- return strcasestr(s1, s2) != NULL;
-}
+bool String_contains_i(const char* s1, const char* s2, bool multi);
static inline bool String_eq(const char* s1, const char* s2) {
return strcmp(s1, s2) == 0;
}
-char* String_cat(const char* s1, const char* s2);
+char* String_cat(const char* s1, const char* s2) ATTR_MALLOC;
-char* String_trim(const char* in);
+char* String_trim(const char* in) ATTR_MALLOC;
char** String_split(const char* s, char sep, size_t* n);
void String_freeArray(char** s);
-char* String_getToken(const char* line, unsigned short int numMatch);
+char* String_readLine(FILE* fd) ATTR_MALLOC;
-char* String_readLine(FILE* fd);
+/* Always null-terminates dest. Caller must pass a strictly positive size. */
+size_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size);
ATTR_FORMAT(printf, 2, 3)
int xAsprintf(char** strp, const char* fmt, ...);
@@ -61,11 +65,14 @@ int xAsprintf(char** strp, const char* fmt, ...);
ATTR_FORMAT(printf, 3, 4)
int xSnprintf(char* buf, size_t len, const char* fmt, ...);
-char* xStrdup(const char* str) ATTR_NONNULL;
+char* xStrdup(const char* str) ATTR_NONNULL ATTR_MALLOC;
+void free_and_xStrdup(char** ptr, const char* str);
-char* xStrndup(const char* str, size_t len) ATTR_NONNULL;
+char* xStrndup(const char* str, size_t len) ATTR_NONNULL ATTR_MALLOC;
ssize_t xReadfile(const char* pathname, void* buffer, size_t count);
ssize_t xReadfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size_t count);
+ssize_t full_write(int fd, const void* buf, size_t count);
+
#endif
diff --git a/configure.ac b/configure.ac
index f624664..bbb042a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,71 +1,146 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.65)
-AC_INIT([htop],[3.0.3],[htop@groups.io])
+# ----------------------------------------------------------------------
+# Autoconf initialization.
+# ----------------------------------------------------------------------
+
+AC_PREREQ([2.69])
+AC_INIT([htop], [3.2.2], [htop@groups.io], [], [https://htop.dev/])
AC_CONFIG_SRCDIR([htop.c])
-AC_CONFIG_AUX_DIR([.])
+AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_HEADERS([config.h])
-# Required by hwloc scripts
-AC_CANONICAL_TARGET
+AC_CANONICAL_HOST
+AM_INIT_AUTOMAKE([-Wall std-options subdir-objects])
-AM_INIT_AUTOMAKE([1.11])
-
-# Checks for programs.
# ----------------------------------------------------------------------
-AC_PROG_CC
-AM_PROG_CC_C_O
-# Required by hwloc scripts
-AC_USE_SYSTEM_EXTENSIONS
+# ----------------------------------------------------------------------
# Checks for platform.
# ----------------------------------------------------------------------
-case "$target_os" in
+
+case "$host_os" in
linux*|gnu*)
my_htop_platform=linux
- AC_DEFINE([HTOP_LINUX], [], [Building for Linux])
+ AC_DEFINE([HTOP_LINUX], [], [Building for Linux.])
;;
freebsd*|kfreebsd*)
my_htop_platform=freebsd
- AC_DEFINE([HTOP_FREEBSD], [], [Building for FreeBSD])
+ AC_DEFINE([HTOP_FREEBSD], [], [Building for FreeBSD.])
+ ;;
+netbsd*)
+ my_htop_platform=netbsd
+ AC_DEFINE([HTOP_NETBSD], [], [Building for NetBSD.])
;;
openbsd*)
my_htop_platform=openbsd
- AC_DEFINE([HTOP_OPENBSD], [], [Building for OpenBSD])
+ AC_DEFINE([HTOP_OPENBSD], [], [Building for OpenBSD.])
;;
dragonfly*)
my_htop_platform=dragonflybsd
- AC_DEFINE([HTOP_DRAGONFLYBSD], [], [Building for DragonFlyBSD])
+ AC_DEFINE([HTOP_DRAGONFLYBSD], [], [Building for DragonFlyBSD.])
;;
darwin*)
my_htop_platform=darwin
- AC_DEFINE([HTOP_DARWIN], [], [Building for Darwin])
+ AC_DEFINE([HTOP_DARWIN], [], [Building for Darwin.])
;;
solaris*)
my_htop_platform=solaris
- AC_DEFINE([HTOP_SOLARIS], [], [Building for Solaris])
+ AC_DEFINE([HTOP_SOLARIS], [], [Building for Solaris.])
;;
*)
my_htop_platform=unsupported
- AC_DEFINE([HTOP_UNSUPPORTED], [], [Building for an unsupported platform])
+ AC_DEFINE([HTOP_UNSUPPORTED], [], [Building for an unsupported platform.])
;;
esac
-# Checks for libraries.
+# Enable extensions, required by hwloc scripts
+AC_USE_SYSTEM_EXTENSIONS
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for compiler.
# ----------------------------------------------------------------------
-AC_CHECK_LIB([m], [ceil], [], [missing_libraries="$missing_libraries libm"])
-# Checks for header files.
+AC_PROG_CC
+AM_PROG_CC_C_O
+m4_version_prereq([2.70], [], [AC_PROG_CC_C99])
+AS_IF([test "x$ac_cv_prog_cc_c99" = xno], [AC_MSG_ERROR([htop is written in C99. A newer compiler is required.])])
+
# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for static build.
+# ----------------------------------------------------------------------
+
+AC_ARG_ENABLE([static],
+ [AS_HELP_STRING([--enable-static],
+ [build a static htop binary @<:@default=no@:>@])],
+ [],
+ [enable_static=no])
+case "$enable_static" in
+ no)
+ ;;
+ yes)
+ AC_DEFINE([BUILD_STATIC], [1], [Define if building static binary.])
+ CFLAGS="$CFLAGS -static"
+ LDFLAGS="$LDFLAGS -static"
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_static' for --enable-static option])
+ ;;
+esac
+
+# ----------------------------------------------------------------------
+
+# ----------------------------------------------------------------------
+# Checks for a PCP-based htop build. (https://pcp.io)
+# ----------------------------------------------------------------------
+
+AC_ARG_ENABLE([pcp],
+ [AS_HELP_STRING([--enable-pcp],
+ [build a pcp-htop binary @<:@default=no@:>@])],
+ [],
+ [enable_pcp=no])
+case "$enable_pcp" in
+ no)
+ ;;
+ yes)
+ AC_CHECK_HEADERS([pcp/pmapi.h], [my_htop_platform=pcp],
+ [AC_MSG_ERROR([can not find PCP header file])])
+ AC_SEARCH_LIBS([pmNewContext], [pcp], [], [AC_MSG_ERROR([can not find PCP library])])
+ AC_DEFINE([HTOP_PCP], [1], [Define if building pcp-htop binary.])
+ AC_CONFIG_FILES([pcp-htop.5])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_pcp' for --enable-pcp option])
+ ;;
+esac
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for generic header files.
+# ----------------------------------------------------------------------
+
AC_HEADER_DIRENT
-AC_HEADER_STDC
-AC_CHECK_HEADERS([stdlib.h string.h strings.h sys/param.h sys/time.h unistd.h],[:],[
- missing_headers="$missing_headers $ac_header"
-])
-AC_CHECK_HEADERS([execinfo.h],[:],[:])
+m4_version_prereq([2.70], [AC_CHECK_INCLUDES_DEFAULT], [AC_HEADER_STDC])
+AC_CHECK_HEADERS([ \
+ stdlib.h \
+ string.h \
+ strings.h \
+ sys/param.h \
+ sys/time.h \
+ sys/utsname.h \
+ unistd.h
+ ], [], [AC_MSG_ERROR([can not find required generic header files])])
AC_HEADER_MAJOR
dnl glibc 2.25 deprecates 'major' and 'minor' in <sys/types.h> and requires to
@@ -73,14 +148,25 @@ dnl include <sys/sysmacros.h>. However the logic in AC_HEADER_MAJOR has not yet
dnl been updated in Autoconf 2.69, so use a workaround:
m4_version_prereq([2.70], [],
[if test "x$ac_cv_header_sys_mkdev_h" != xyes; then
- AC_CHECK_HEADER(sys/sysmacros.h, [AC_DEFINE(MAJOR_IN_SYSMACROS, 1,
+ AC_CHECK_HEADER([sys/sysmacros.h], [AC_DEFINE([MAJOR_IN_SYSMACROS], [1],
[Define to 1 if `major', `minor', and `makedev' are declared in <sys/sysmacros.h>.])])
fi])
+# Optional Section
+
+AC_CHECK_HEADERS([execinfo.h])
+
+if test "$my_htop_platform" = darwin; then
+ AC_CHECK_HEADERS([mach/mach_time.h])
+fi
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
# Checks for typedefs, structures, and compiler characteristics.
# ----------------------------------------------------------------------
-AC_HEADER_STDBOOL
-AC_C_CONST
+
AC_TYPE_PID_T
AC_TYPE_UID_T
AC_TYPE_UINT8_T
@@ -88,61 +174,113 @@ AC_TYPE_UINT16_T
AC_TYPE_UINT32_T
AC_TYPE_UINT64_T
-# Checks for library functions and compiler features.
+AC_MSG_CHECKING(for alloc_size)
+old_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -Wno-error -Werror=attributes"
+AC_COMPILE_IFELSE([
+ AC_LANG_SOURCE(
+ [
+ __attribute__((alloc_size(1))) char* my_alloc(int size) { return 0; }
+ ],[]
+ )],
+ AC_DEFINE([HAVE_ATTR_ALLOC_SIZE], 1, [The alloc_size attribute is supported.])
+ AC_MSG_RESULT(yes),
+ AC_MSG_RESULT(no))
+CFLAGS="$old_CFLAGS"
+
+AC_MSG_CHECKING(for NaN support)
+AC_RUN_IFELSE([
+ AC_LANG_PROGRAM(
+ [[
+ #include <math.h>
+ ]],
+ [[
+ double x = NAN; return !isnan(x);
+ ]]
+ )],
+ [AC_MSG_RESULT(yes)],
+ [
+ AC_MSG_RESULT(no)
+ AC_MSG_WARN([Compiler does not respect NaN, some functionality might break; consider using '-fno-finite-math-only'])
+ ],
+ [AC_MSG_RESULT(skipped)])
+
# ----------------------------------------------------------------------
-AC_FUNC_CLOSEDIR_VOID
-AC_FUNC_STAT
-AC_CHECK_FUNCS([\
- faccessat\
- fstatat\
- openat\
- readlinkat\
-])
-AC_SEARCH_LIBS([dlopen], [dl dld])
-save_cflags="${CFLAGS}"
-CFLAGS="${CFLAGS} -std=c99"
-AC_MSG_CHECKING([whether cc -std=c99 option works])
-AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
- [AC_INCLUDES_DEFAULT], [[char *a; a = strdup("foo"); int i = 0; i++; // C99]])],
- [AC_MSG_RESULT([yes])],
- [AC_MSG_ERROR([htop is written in C99. A newer compiler is required.])])
-CFLAGS="$save_cflags"
+# ----------------------------------------------------------------------
+# Checks for generic library functions.
+# ----------------------------------------------------------------------
-# Add -lexecinfo if needed
-AC_SEARCH_LIBS([backtrace], [execinfo])
+AC_SEARCH_LIBS([ceil], [m], [], [AC_MSG_ERROR([can not find required function ceil()])])
-# Add -ldevstat if needed
-AC_SEARCH_LIBS([devstat_checkversion], [devstat])
+if test "$my_htop_platform" = dragonflybsd; then
+ AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
+fi
-# Checks for features and flags.
-# ----------------------------------------------------------------------
-PROCDIR=/proc
+if test "$my_htop_platform" = freebsd; then
+ if test "$enable_static" = yes; then
+ AC_SEARCH_LIBS([elf_version], [elf], [], [AC_MSG_ERROR([can not find required function elf_version()])])
+ fi
+ AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
+ AC_SEARCH_LIBS([devstat_checkversion], [devstat], [], [AC_MSG_ERROR([can not find required function devstat_checkversion()])])
+fi
-AC_ARG_WITH(proc, [AS_HELP_STRING([--with-proc=DIR], [Location of a Linux-compatible proc filesystem (default=/proc).])],
- if test -n "$withval"; then
- AC_DEFINE_UNQUOTED(PROCDIR, "$withval", [Path of proc filesystem])
- PROCDIR="$withval"
- fi,
- AC_DEFINE(PROCDIR, "/proc", [Path of proc filesystem]))
+if test "$my_htop_platform" = linux; then
+ if test "$enable_static" != yes; then
+ AC_SEARCH_LIBS([dlopen], [dl dld], [], [AC_MSG_ERROR([can not find required function dlopen()])])
+ fi
+fi
-AC_ARG_ENABLE(openvz, [AS_HELP_STRING([--enable-openvz], [enable OpenVZ support])], ,enable_openvz="no")
-if test "x$enable_openvz" = xyes; then
- AC_DEFINE(HAVE_OPENVZ, 1, [Define if openvz support enabled.])
+if test "$my_htop_platform" = netbsd; then
+ AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
+ AC_SEARCH_LIBS([prop_dictionary_get], [prop], [], [AC_MSG_ERROR([can not find required function prop_dictionary_get()])])
fi
-AC_ARG_ENABLE(vserver, [AS_HELP_STRING([--enable-vserver], [enable VServer support])], ,enable_vserver="no")
-if test "x$enable_vserver" = xyes; then
- AC_DEFINE(HAVE_VSERVER, 1, [Define if vserver support enabled.])
+if test "$my_htop_platform" = openbsd; then
+ AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([can not find required function kvm_open()])])
fi
-AC_ARG_ENABLE(ancient_vserver, [AS_HELP_STRING([--enable-ancient-vserver], [enable ancient VServer support (implies --enable-vserver)])], ,enable_ancient_vserver="no")
-if test "x$enable_ancient_vserver" = xyes; then
- AC_DEFINE(HAVE_VSERVER, 1, [Define if vserver support enabled.])
- AC_DEFINE(HAVE_ANCIENT_VSERVER, 1, [Define if ancient vserver support enabled.])
+if test "$my_htop_platform" = solaris; then
+ AC_SEARCH_LIBS([kstat_open], [kstat], [], [AC_MSG_ERROR([can not find required function kstat_open()])])
+ AC_SEARCH_LIBS([Pgrab_error], [proc], [], [AC_MSG_ERROR([can not find required function Pgrab_error()])])
+ AC_SEARCH_LIBS([free], [malloc], [], [AC_MSG_ERROR([can not find required function free()])])
+fi
+
+# Optional Section
+
+AC_SEARCH_LIBS([clock_gettime], [rt])
+
+AC_CHECK_FUNCS([ \
+ clock_gettime \
+ dladdr \
+ faccessat \
+ fstatat \
+ host_get_clock_service \
+ memfd_create\
+ openat \
+ readlinkat \
+ ])
+
+if test "$my_htop_platform" = darwin; then
+ AC_CHECK_FUNCS([mach_timebase_info])
+fi
+
+if test "$my_htop_platform" = pcp; then
+ AC_CHECK_FUNCS([pmLookupDescs])
+fi
+
+if test "$my_htop_platform" = linux && test "x$enable_static" = xyes; then
+ AC_CHECK_LIB([systemd], [sd_bus_open_system])
fi
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for cross-platform features and flags.
+# ----------------------------------------------------------------------
+
# HTOP_CHECK_SCRIPT(LIBNAME, FUNCTION, DEFINE, CONFIG_SCRIPT, ELSE_PART)
m4_define([HTOP_CHECK_SCRIPT],
[
@@ -155,19 +293,18 @@ m4_define([HTOP_CHECK_SCRIPT],
htop_config_script_cflags=$([$4] --cflags 2> /dev/null)
fi
htop_script_success=no
- htop_save_LDFLAGS="$LDFLAGS"
htop_save_CFLAGS="$CFLAGS"
if test ! "x$htop_config_script_libs" = x; then
- LDFLAGS="$htop_config_script_libs $LDFLAGS"
CFLAGS="$htop_config_script_cflags $CFLAGS"
AC_CHECK_LIB([$1], [$2], [
AC_DEFINE([$3], 1, [The library is present.])
LIBS="$htop_config_script_libs $LIBS "
htop_script_success=yes
], [
- CFLAGS="$htop_save_CFLAGS"
+ CFLAGS="$htop_save_CFLAGS"
+ ], [
+ $htop_config_script_libs
])
- LDFLAGS="$htop_save_LDFLAGS"
fi
if test "x$htop_script_success" = xno; then
[$5]
@@ -178,84 +315,75 @@ m4_define([HTOP_CHECK_SCRIPT],
m4_define([HTOP_CHECK_LIB],
[
AC_CHECK_LIB([$1], [$2], [
- AC_DEFINE([$3], 1, [The library is present.])
+ AC_DEFINE([$3], [1], [The library is present.])
LIBS="-l[$1] $LIBS "
], [$4])
])
-dnl https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
-AC_DEFUN([AX_CHECK_COMPILE_FLAG],
-[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
-AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
-AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
- ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
- _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
- AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
- [AS_VAR_SET(CACHEVAR,[yes])],
- [AS_VAR_SET(CACHEVAR,[no])])
- _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
-AS_VAR_IF(CACHEVAR,yes,
- [m4_default([$2], :)],
- [m4_default([$3], :)])
-AS_VAR_POPDEF([CACHEVAR])dnl
-])dnl AX_CHECK_COMPILE_FLAGS
-
-AC_ARG_ENABLE(unicode, [AS_HELP_STRING([--enable-unicode], [enable Unicode support])], ,enable_unicode="yes")
+AC_ARG_ENABLE([unicode],
+ [AS_HELP_STRING([--enable-unicode],
+ [enable Unicode support @<:@default=yes@:>@])],
+ [],
+ [enable_unicode=yes])
if test "x$enable_unicode" = xyes; then
- HTOP_CHECK_SCRIPT([ncursesw6], [addnwstr], [HAVE_LIBNCURSESW], "ncursesw6-config",
- HTOP_CHECK_SCRIPT([ncursesw], [addnwstr], [HAVE_LIBNCURSESW], "ncursesw6-config",
- HTOP_CHECK_SCRIPT([ncursesw], [addnwstr], [HAVE_LIBNCURSESW], "ncursesw5-config",
- HTOP_CHECK_SCRIPT([ncurses], [addnwstr], [HAVE_LIBNCURSESW], "ncurses5-config",
+ HTOP_CHECK_SCRIPT([ncursesw6], [waddwstr], [HAVE_LIBNCURSESW], "ncursesw6-config",
+ HTOP_CHECK_SCRIPT([ncursesw], [waddwstr], [HAVE_LIBNCURSESW], "ncursesw6-config",
+ HTOP_CHECK_SCRIPT([ncursesw], [wadd_wch], [HAVE_LIBNCURSESW], "ncursesw5-config",
+ HTOP_CHECK_SCRIPT([ncurses], [wadd_wch], [HAVE_LIBNCURSESW], "ncurses5-config",
HTOP_CHECK_LIB([ncursesw6], [addnwstr], [HAVE_LIBNCURSESW],
HTOP_CHECK_LIB([ncursesw], [addnwstr], [HAVE_LIBNCURSESW],
HTOP_CHECK_LIB([ncurses], [addnwstr], [HAVE_LIBNCURSESW],
- missing_libraries="$missing_libraries libncursesw"
- AC_MSG_ERROR([You may want to use --disable-unicode or install libncursesw.])
+ AC_MSG_ERROR([can not find required library libncursesw; you may want to use --disable-unicode])
)))))))
- AC_CHECK_HEADERS([ncursesw/curses.h],[:],
- [AC_CHECK_HEADERS([ncurses/ncurses.h],[:],
- [AC_CHECK_HEADERS([ncurses/curses.h],[:],
- [AC_CHECK_HEADERS([ncurses.h],[:],[missing_headers="$missing_headers $ac_header"])])])])
-else
- HTOP_CHECK_SCRIPT([ncurses6], [refresh], [HAVE_LIBNCURSES], "ncurses6-config",
- HTOP_CHECK_SCRIPT([ncurses], [refresh], [HAVE_LIBNCURSES], "ncurses5-config",
- HTOP_CHECK_LIB([ncurses6], [refresh], [HAVE_LIBNCURSES],
- HTOP_CHECK_LIB([ncurses], [refresh], [HAVE_LIBNCURSES],
- missing_libraries="$missing_libraries libncurses"
- ))))
-
- AC_CHECK_HEADERS([curses.h],[:],
- [AC_CHECK_HEADERS([ncurses/curses.h],[:],
- [AC_CHECK_HEADERS([ncurses/ncurses.h],[:],
- [AC_CHECK_HEADERS([ncurses.h],[:],[missing_headers="$missing_headers $ac_header"])])])])
-fi
+ AC_CHECK_HEADERS([ncursesw/curses.h], [],
+ [AC_CHECK_HEADERS([ncurses/ncurses.h], [],
+ [AC_CHECK_HEADERS([ncurses/curses.h], [],
+ [AC_CHECK_HEADERS([ncurses.h], [],
+ [AC_MSG_ERROR([can not find required ncurses header file])])])])])
-if test "$my_htop_platform" = "freebsd"; then
- AC_CHECK_LIB([kvm], [kvm_open], [], [missing_libraries="$missing_libraries libkvm"])
+ # check if additional linker flags are needed for keypad(3)
+ # (at this point we already link against a working ncurses library with wide character support)
+ AC_SEARCH_LIBS([keypad], [tinfow tinfo])
+else
+ HTOP_CHECK_SCRIPT([ncurses6], [wnoutrefresh], [HAVE_LIBNCURSES], [ncurses6-config],
+ HTOP_CHECK_SCRIPT([ncurses], [wnoutrefresh], [HAVE_LIBNCURSES], [ncurses5-config],
+ HTOP_CHECK_LIB([ncurses6], [doupdate], [HAVE_LIBNCURSES],
+ HTOP_CHECK_LIB([ncurses], [doupdate], [HAVE_LIBNCURSES],
+ HTOP_CHECK_LIB([curses], [doupdate], [HAVE_LIBNCURSES],
+ AC_MSG_ERROR([can not find required curses/ncurses library])
+ )))))
+
+ AC_CHECK_HEADERS([curses.h], [],
+ [AC_CHECK_HEADERS([ncurses/curses.h], [],
+ [AC_CHECK_HEADERS([ncurses/ncurses.h], [],
+ [AC_CHECK_HEADERS([ncurses.h] ,[],
+ [AC_MSG_ERROR([can not find required ncurses header file])])])])])
+
+ # check if additional linker flags are needed for keypad(3)
+ # (at this point we already link against a working ncurses library)
+ AC_SEARCH_LIBS([keypad], [tinfo])
fi
-
-if test "$my_htop_platform" = "openbsd"; then
- AC_CHECK_LIB([kvm], [kvm_open], [], [missing_libraries="$missing_libraries libkvm"])
+if test "$enable_static" = yes; then
+ AC_SEARCH_LIBS([Gpm_GetEvent], [gpm])
fi
-
if test "$my_htop_platform" = "solaris"; then
- AC_CHECK_LIB([kstat], [kstat_open], [], [missing_libraries="$missing_libraries libkstat"])
- AC_CHECK_LIB([proc], [Pgrab_error], [], [missing_libraries="$missing_libraries libproc"])
- AC_CHECK_LIB([malloc], [free], [], [missing_libraries="$missing_libraries libmalloc"])
+ # On OmniOS /usr/include/sys/regset.h redefines ERR to 13 - \r, breaking the Enter key.
+ # Since ncurses macros use the ERR macro, we can not use another name.
+ AC_DEFINE([ERR], [(-1)], [Predefine ncurses macro.])
fi
+AC_CHECK_FUNCS( [set_escdelay] )
+AC_CHECK_FUNCS( [getmouse] )
-AC_ARG_ENABLE(hwloc, [AS_HELP_STRING([--enable-hwloc], [enable hwloc support for CPU affinity, disables Linux affinity])],, enable_hwloc="no")
-if test "x$enable_hwloc" = xyes
-then
- AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [missing_libraries="$missing_libraries libhwloc"])
- AC_CHECK_HEADERS([hwloc.h],[:], [missing_headers="$missing_headers $ac_header"])
-fi
-AC_ARG_ENABLE(linux_affinity, [AS_HELP_STRING([--enable-linux-affinity], [enable Linux sched_setaffinity and sched_getaffinity for affinity support, conflicts with hwloc])], ,enable_linux_affinity="check")
-if test "x$enable_linux_affinity" = xcheck; then
+AC_ARG_ENABLE([affinity],
+ [AS_HELP_STRING([--enable-affinity],
+ [enable sched_setaffinity and sched_getaffinity for affinity support, conflicts with hwloc @<:@default=check@:>@])],
+ [],
+ [enable_affinity=check])
+if test "x$enable_affinity" = xcheck; then
if test "x$enable_hwloc" = xyes; then
- enable_linux_affinity=no
+ enable_affinity=no
else
AC_MSG_CHECKING([for usable sched_setaffinity])
AC_RUN_IFELSE([
@@ -268,59 +396,270 @@ if test "x$enable_linux_affinity" = xcheck; then
sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
if (errno == ENOSYS) return 1;
]])],
- [enable_linux_affinity=yes
- AC_MSG_RESULT([yes])],
- [enable_linux_affinity=no
- AC_MSG_RESULT([no])],
+ [enable_affinity=yes
+ AC_MSG_RESULT([yes])],
+ [enable_affinity=no
+ AC_MSG_RESULT([no])],
[AC_MSG_RESULT([yes (assumed while cross compiling)])])
fi
fi
-if test "x$enable_linux_affinity" = xyes; then
- AC_DEFINE(HAVE_LINUX_AFFINITY, 1, [Define if Linux sched_setaffinity and sched_getaffinity are to be used.])
+if test "x$enable_affinity" = xyes; then
+ if test "x$enable_hwloc" = xyes; then
+ AC_MSG_ERROR([--enable-hwloc and --enable-affinity are mutual exclusive. Specify at most one of them.])
+ fi
+ AC_DEFINE([HAVE_AFFINITY], [1], [Define if sched_setaffinity and sched_getaffinity are to be used.])
+fi
+
+
+AC_ARG_ENABLE([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], [], [
+ CFLAGS="$CFLAGS -I/usr/include/libunwind"
+ AC_CHECK_HEADERS([libunwind/libunwind.h], [], [AC_MSG_ERROR([can not find required header file libunwind.h])])
+ ])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_unwind' for --enable-unwind])
+ ;;
+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@:>@])],
+ [],
+ [enable_hwloc=no])
+case "$enable_hwloc" in
+ no)
+ ;;
+ yes)
+ m4_ifdef([PKG_PROG_PKG_CONFIG], [
+ PKG_PROG_PKG_CONFIG()
+ PKG_CHECK_MODULES(HWLOC, hwloc, [
+ CFLAGS="$CFLAGS $HWLOC_CFLAGS" LIBS="$LIBS $HWLOC_LIBS"
+ ], [
+ AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])])
+ AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])])
+ ])
+ ], [
+ AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])])
+ AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])])
+ ])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_hwloc' for --enable-hwloc])
+ ;;
+esac
+
+
+AC_ARG_WITH([os-release],
+ [AS_HELP_STRING([--with-os-release=FILE],
+ [location of an os-release file @<:@default=/etc/os-release@:>@])],
+ [],
+ [with_os_release=/etc/os-release])
+if test -n "$with_os_release" && test ! -f "$with_os_release"; then
+ if test -f "/usr/lib/os-release"; then
+ with_os_release="/usr/lib/os-release"
+ fi
+fi
+AC_DEFINE_UNQUOTED([OSRELEASEFILE], ["$with_os_release"], [File with OS release details.])
+
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for Linux features and flags.
+# ----------------------------------------------------------------------
+
+AC_ARG_WITH([proc],
+ [AS_HELP_STRING([--with-proc=DIR],
+ [location of a Linux-compatible proc filesystem @<:@default=/proc@:>@])],
+ [],
+ [with_proc=/proc])
+if test -z "$with_proc"; then
+ AC_MSG_ERROR([bad empty value for --with-proc option])
fi
+AC_DEFINE_UNQUOTED([PROCDIR], ["$with_proc"], [Path of proc filesystem.])
-if test "x$enable_linux_affinity" = xyes -a "x$enable_hwloc" = xyes
-then
- AC_MSG_ERROR([--enable-hwloc and --enable-linux-affinity are mutual exclusive. Specify at most one of them.])
+
+AC_ARG_ENABLE([openvz],
+ [AS_HELP_STRING([--enable-openvz],
+ [enable OpenVZ support @<:@default=no@:>@])],
+ [],
+ [enable_openvz=no])
+if test "x$enable_openvz" = xyes; then
+ AC_DEFINE([HAVE_OPENVZ], [1], [Define if openvz support enabled.])
fi
-AC_ARG_ENABLE(setuid, [AS_HELP_STRING([--enable-setuid], [enable setuid support for platforms that need it])],, enable_setuid="no")
-if test "x$enable_setuid" = xyes
-then
- AC_DEFINE(HAVE_SETUID_ENABLED, 1, [Define if setuid support should be enabled.])
+
+AC_ARG_ENABLE([vserver],
+ [AS_HELP_STRING([--enable-vserver],
+ [enable VServer support @<:@default=no@:>@])],
+ [],
+ [enable_vserver=no])
+if test "x$enable_vserver" = xyes; then
+ AC_DEFINE([HAVE_VSERVER], [1], [Define if VServer support enabled.])
fi
-AC_ARG_ENABLE(delayacct, [AS_HELP_STRING([--enable-delayacct], [enable Linux delay accounting])],, enable_delayacct="no")
-if test "x$enable_delayacct" = xyes
-then
- m4_ifdef([PKG_PROG_PKG_CONFIG], [
- PKG_PROG_PKG_CONFIG()
- PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [missing_libraries="$missing_libraries libnl-3"])
- PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [missing_libraries="$missing_libraries libnl-genl-3"])
- CFLAGS="$CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
- LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
- AC_DEFINE(HAVE_DELAYACCT, 1, [Define if delay accounting support should be enabled.])
- ], [
- pkg_m4_absent=1
- m4_warning([configure is generated without pkg.m4. 'make dist' target will be disabled.])
- AC_MSG_ERROR([htop on Linux requires pkg-config for checking delayacct requirements. Please install pkg-config and run ./autogen.sh to rebuild the configure script.])
- ])
+
+AC_ARG_ENABLE([ancient_vserver],
+ [AS_HELP_STRING([--enable-ancient-vserver],
+ [enable ancient VServer support (implies --enable-vserver) @<:@default=no@:>@])],
+ [],
+ [enable_ancient_vserver=no])
+if test "x$enable_ancient_vserver" = xyes; then
+ if test "x$enable_vserver" != xyes; then
+ enable_vserver=implied
+ fi
+ AC_DEFINE([HAVE_VSERVER], [1], [Define if VServer support enabled.])
+ AC_DEFINE([HAVE_ANCIENT_VSERVER], [1], [Define if ancient vserver support enabled.])
fi
-AC_ARG_WITH(sensors, [AS_HELP_STRING([--with-sensors], [Compile with libsensors support for reading temperature data. Only requires libsensors headers at compile time, at runtime libsensors is loaded via dlopen.])],, with_sensors="check")
-if test "x$with_sensors" = xyes; then
- AC_CHECK_HEADERS([sensors/sensors.h], [], [missing_headers="$missing_headers $ac_header"])
-elif test "x$with_sensors" = xcheck; then
- with_sensors=yes
- AC_CHECK_HEADERS([sensors/sensors.h], [], [with_sensors=no])
+
+AC_ARG_ENABLE([capabilities],
+ [AS_HELP_STRING([--enable-capabilities],
+ [enable Linux capabilities support; requires libcap @<:@default=check@:>@])],
+ [],
+ [enable_capabilities=check])
+case "$enable_capabilities" in
+ no)
+ ;;
+ check)
+ enable_capabilities=yes
+ AC_CHECK_LIB([cap], [cap_init], [], [enable_capabilities=no])
+ AC_CHECK_HEADERS([sys/capability.h], [], [enable_capabilities=no])
+ ;;
+ yes)
+ AC_CHECK_LIB([cap], [cap_init], [], [AC_MSG_ERROR([can not find required library libcap])])
+ AC_CHECK_HEADERS([sys/capability.h], [], [AC_MSG_ERROR([can not find required header file sys/capability.h])])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_capabilities' for --enable-capabilities])
+ ;;
+esac
+
+
+AC_ARG_ENABLE([delayacct],
+ [AS_HELP_STRING([--enable-delayacct],
+ [enable Linux delay accounting support; requires pkg-config, libnl-3 and libnl-genl-3 @<:@default=check@:>@])],
+ [],
+ [enable_delayacct=check])
+case "$enable_delayacct" in
+ no)
+ ;;
+ check)
+ if test "$my_htop_platform" != linux; then
+ enable_delayacct=no
+ elif test "$enable_static" = yes; then
+ enable_delayacct=no
+ else
+ m4_ifdef([PKG_PROG_PKG_CONFIG], [
+ enable_delayacct=yes
+ PKG_PROG_PKG_CONFIG()
+ PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [enable_delayacct=no])
+ PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [enable_delayacct=no])
+ if test "$enable_delayacct" = yes; then
+ CFLAGS="$CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
+ LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
+ AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
+ fi
+ ], [
+ enable_delayacct=no
+ AC_MSG_NOTICE([Linux delay accounting support can not be enabled, cause pkg-config is required for checking its availability])
+ ])
+ fi
+ ;;
+ yes)
+ m4_ifdef([PKG_PROG_PKG_CONFIG], [
+ PKG_PROG_PKG_CONFIG()
+ PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [AC_MSG_ERROR([can not find required library libnl3])])
+ PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [AC_MSG_ERROR([can not find required library libnl3genl])])
+ CFLAGS="$CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
+ LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
+ AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
+ ], [
+ pkg_m4_absent=1
+ m4_warning([configure is generated without pkg.m4. 'make dist' target will be disabled.])
+ AC_MSG_ERROR([htop on Linux requires pkg-config for checking delayacct requirements. Please install pkg-config and run ./autogen.sh to rebuild the configure script.])
+ ])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_delayacct' for --enable-delayacct])
+ ;;
+esac
+
+
+AC_ARG_ENABLE([sensors],
+ [AS_HELP_STRING([--enable-sensors],
+ [enable libsensors support for reading temperature data; requires only libsensors headers at compile time, at runtime libsensors is loaded via dlopen @<:@default=check@:>@])],
+ [],
+ [enable_sensors=check])
+case "$enable_sensors" in
+ no)
+ ;;
+ check)
+ enable_sensors=yes
+ if test "$enable_static" = yes; then
+ AC_CHECK_LIB([sensors], [sensors_init], [], [enable_sensors=no])
+ fi
+ AC_CHECK_HEADERS([sensors/sensors.h], [], [enable_sensors=no])
+ ;;
+ yes)
+ if test "$enable_static" = yes; then
+ AC_CHECK_LIB([sensors], [sensors_init], [], [AC_MSG_ERROR([can not find required library libsensors])])
+ fi
+ AC_CHECK_HEADERS([sensors/sensors.h], [], [AC_MSG_ERROR([can not find required header file sensors/sensors.h])])
+ ;;
+ *)
+ AC_MSG_ERROR([bad value '$enable_sensors' for --enable-sensors])
+ ;;
+esac
+if test "$enable_sensors" = yes || test "$my_htop_platform" = freebsd; then
+ AC_DEFINE([BUILD_WITH_CPU_TEMP], [1], [Define if CPU temperature option should be enabled.])
fi
+# ----------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------
+# Checks for compiler warnings.
+# ----------------------------------------------------------------------
+
AM_CFLAGS="\
-Wall\
-Wcast-align\
-Wcast-qual\
-Wextra\
-Wfloat-equal\
+ -Wformat=2\
+ -Winit-self\
-Wmissing-format-attribute\
-Wmissing-noreturn\
-Wmissing-prototypes\
@@ -331,44 +670,80 @@ AM_CFLAGS="\
-Wunused\
-Wwrite-strings"
-AX_CHECK_COMPILE_FLAG([-Wnull-dereference], [AM_CFLAGS="$AM_CFLAGS -Wnull-dereference"], , [-Werror])
+# FreeBSD uses C11 _Generic in its isnan implementation, even with -std=c99
+if test "$my_htop_platform" = freebsd; then
+ AM_CFLAGS="$AM_CFLAGS -Wno-c11-extensions"
+fi
+
+dnl https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+ _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+ AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+ [AS_VAR_SET(CACHEVAR,[yes])],
+ [AS_VAR_SET(CACHEVAR,[no])])
+ _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
-AC_ARG_ENABLE([werror], [AS_HELP_STRING([--enable-werror], [Treat warnings as errors (default: warnings are not errors)])], [enable_werror="$enableval"], [enable_werror=no])
-AS_IF([test "x$enable_werror" = "xyes"], [AM_CFLAGS="$AM_CFLAGS -Werror"])
+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],
+ [Treat warnings as errors @<:@default=no@:>@])],
+ [],
+ [enable_werror=no])
+if test "x$enable_werror" = xyes; then
+ AM_CFLAGS="$AM_CFLAGS -Werror"
+fi
-AC_SUBST([AM_CFLAGS])
+AC_ARG_ENABLE([debug],
+ [AS_HELP_STRING([--enable-debug],
+ [Enable compiling with maximum debug info, asserts and internal sanity checks @<:@default=no@:>@])],
+ [],
+ [enable_debug=no])
+if test "x$enable_debug" != xyes; then
+ AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG"
+else
+ AM_CPPFLAGS="$AM_CPPFLAGS -ggdb3"
+fi
-AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [Enable asserts (default: asserts are disabled)])], [enable_debug="$enableval"], [enable_debug=no])
-AS_IF([test "x$enable_debug" = "xyes"], , [AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG"])
+AC_SUBST([AM_CFLAGS])
AC_SUBST([AM_CPPFLAGS])
-# Bail out on errors.
# ----------------------------------------------------------------------
-if test ! -z "$missing_libraries"; then
- AC_MSG_ERROR([missing libraries: $missing_libraries])
-fi
-if test ! -z "$missing_headers"; then
- AC_MSG_ERROR([missing headers: $missing_headers])
-fi
-AC_DEFINE_UNQUOTED(COPYRIGHT, "(C) 2004-2019 Hisham Muhammad. (C) 2020 htop dev team.", [Copyright message.])
+# ----------------------------------------------------------------------
# We're done, let's go!
# ----------------------------------------------------------------------
+
+AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2023 htop dev team."], [Copyright message.])
+
AM_CONDITIONAL([HTOP_LINUX], [test "$my_htop_platform" = linux])
AM_CONDITIONAL([HTOP_FREEBSD], [test "$my_htop_platform" = freebsd])
AM_CONDITIONAL([HTOP_DRAGONFLYBSD], [test "$my_htop_platform" = dragonflybsd])
+AM_CONDITIONAL([HTOP_NETBSD], [test "$my_htop_platform" = netbsd])
AM_CONDITIONAL([HTOP_OPENBSD], [test "$my_htop_platform" = openbsd])
AM_CONDITIONAL([HTOP_DARWIN], [test "$my_htop_platform" = darwin])
AM_CONDITIONAL([HTOP_SOLARIS], [test "$my_htop_platform" = solaris])
+AM_CONDITIONAL([HTOP_PCP], [test "$my_htop_platform" = pcp])
AM_CONDITIONAL([HTOP_UNSUPPORTED], [test "$my_htop_platform" = unsupported])
+
AC_SUBST(my_htop_platform)
AC_CONFIG_FILES([Makefile htop.1])
AC_OUTPUT
-if test "$my_htop_platform" = "unsupported"
-then
+if test "$my_htop_platform" = unsupported; then
echo ""
echo "****************************************************************"
echo "WARNING! This platform is not currently supported by htop."
@@ -385,15 +760,18 @@ AC_MSG_RESULT([
${PACKAGE_NAME} ${VERSION}
platform: $my_htop_platform
- (Linux) proc directory: $PROCDIR
+ os-release file: $with_os_release
+ (Linux) proc directory: $with_proc
(Linux) openvz: $enable_openvz
(Linux) vserver: $enable_vserver
(Linux) ancient vserver: $enable_ancient_vserver
- (Linux) affinity: $enable_linux_affinity
(Linux) delay accounting: $enable_delayacct
- (Linux) sensors: $with_sensors
+ (Linux) sensors: $enable_sensors
+ (Linux) capabilities: $enable_capabilities
unicode: $enable_unicode
+ affinity: $enable_affinity
+ unwind: $enable_unwind
hwloc: $enable_hwloc
- setuid: $enable_setuid
debug: $enable_debug
+ static: $enable_static
])
diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c
index f3c3425..6027c25 100644
--- a/darwin/DarwinProcess.c
+++ b/darwin/DarwinProcess.c
@@ -1,30 +1,54 @@
/*
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.
*/
-#include "DarwinProcess.h"
+#include "darwin/DarwinProcess.h"
#include <libproc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mach/mach.h>
+#include <sys/dirent.h>
#include "CRT.h"
#include "Process.h"
-
-
-const ProcessClass DarwinProcess_class = {
- .super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = Process_compare
- },
- .writeField = Process_writeField,
+#include "darwin/Platform.h"
+
+
+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, },
+ [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
+ [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)", .flags = 0, },
+ [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 = 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, },
+ [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
+ [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
+ [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
+ [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
+ [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
+ [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
+ [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
+ [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
+ [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
+ [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, },
+ [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
+ [TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, },
};
Process* DarwinProcess_new(const Settings* settings) {
@@ -35,6 +59,7 @@ Process* DarwinProcess_new(const Settings* settings) {
this->utime = 0;
this->stime = 0;
this->taskAccess = true;
+ this->translated = false;
return &this->super;
}
@@ -46,16 +71,70 @@ void Process_delete(Object* cast) {
free(this);
}
-bool Process_isThread(const Process* this) {
- (void) this;
- return false;
+static void DarwinProcess_writeField(const Process* this, RichString* str, ProcessField field) {
+ const DarwinProcess* dp = (const DarwinProcess*) this;
+ char buffer[256]; buffer[255] = '\0';
+ int attr = CRT_colors[DEFAULT_COLOR];
+ int 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);
+ return;
+ }
+ RichString_appendWide(str, attr, buffer);
+}
+
+static int DarwinProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const DarwinProcess* p1 = (const DarwinProcess*)v1;
+ const DarwinProcess* p2 = (const DarwinProcess*)v2;
+
+ switch (key) {
+ // add Platform-specific fields here
+ case TRANSLATED:
+ return SPACESHIP_NUMBER(p1->translated, p2->translated);
+ default:
+ return Process_compareByKey_Base(v1, v2, key);
+ }
+}
+
+static void DarwinProcess_updateExe(pid_t pid, Process* proc) {
+ char path[PROC_PIDPATHINFO_MAXSIZE];
+
+ int r = proc_pidpath(pid, path, sizeof(path));
+ if (r <= 0)
+ return;
+
+ Process_updateExe(proc, path);
+}
+
+static void DarwinProcess_updateCwd(pid_t pid, Process* proc) {
+ struct proc_vnodepathinfo vpi;
+
+ int r = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
+ if (r <= 0) {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ if (!vpi.pvi_cdir.vip_path[0]) {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ free_and_xStrdup(&proc->procCwd, vpi.pvi_cdir.vip_path);
}
-static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameOffset) {
+static void DarwinProcess_updateCmdLine(const struct kinfo_proc* k, Process* proc) {
+ Process_updateComm(proc, k->kp_proc.p_comm);
+
/* This function is from the old Mac version of htop. Originally from ps? */
int mib[3], argmax, nargs, c = 0;
size_t size;
- char *procargs, *sp, *np, *cp, *retval;
+ char *procargs, *sp, *np, *cp;
/* Get the maximum process arguments size. */
mib[0] = CTL_KERN;
@@ -67,7 +146,7 @@ static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameO
}
/* Allocate space for the arguments. */
- procargs = (char*)xMalloc(argmax);
+ procargs = (char*)malloc(argmax);
if ( procargs == NULL ) {
goto ERROR_A;
}
@@ -149,7 +228,7 @@ static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameO
/* Save where the argv[0] string starts. */
sp = cp;
- *basenameOffset = 0;
+ int end = 0;
for ( np = NULL; c < nargs && cp < &procargs[size]; cp++ ) {
if ( *cp == '\0' ) {
c++;
@@ -159,8 +238,8 @@ static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameO
}
/* Note location of current '\0'. */
np = cp;
- if (*basenameOffset == 0) {
- *basenameOffset = cp - sp;
+ if (end == 0) {
+ end = cp - sp;
}
}
}
@@ -173,28 +252,46 @@ static char* DarwinProcess_getCmdLine(const struct kinfo_proc* k, int* basenameO
/* Empty or unterminated string. */
goto ERROR_B;
}
- if (*basenameOffset == 0) {
- *basenameOffset = np - sp;
+ if (end == 0) {
+ end = np - sp;
}
- /* Make a copy of the string. */
- retval = xStrdup(sp);
+ Process_updateCmdline(proc, sp, 0, end);
/* Clean up. */
free( procargs );
- return retval;
+ return;
ERROR_B:
free( procargs );
+
ERROR_A:
- retval = xStrdup(k->kp_proc.p_comm);
- *basenameOffset = strlen(retval);
+ Process_updateCmdline(proc, k->kp_proc.p_comm, 0, strlen(k->kp_proc.p_comm));
+}
+
+// Converts nanoseconds to hundredths of a second (centiseconds) as needed by the "time" field of the Process struct.
+static long long int nanosecondsToCentiseconds(uint64_t nanoseconds) {
+ const uint64_t centiseconds_per_second = 100;
+ const uint64_t nanoseconds_per_second = 1e9;
+ return nanoseconds / nanoseconds_per_second * centiseconds_per_second;
+}
- return retval;
+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 struct extern_proc* ep = &ps->kp_proc;
/* UNSET HERE :
@@ -219,53 +316,79 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
proc->session = 0; /* TODO Get the session id */
proc->tpgid = ps->kp_eproc.e_tpgid;
proc->tgid = proc->pid;
- proc->st_uid = ps->kp_eproc.e_ucred.cr_uid;
- /* e_tdev = (major << 24) | (minor & 0xffffff) */
- /* e_tdev == -1 for "no device" */
- proc->tty_nr = ps->kp_eproc.e_tdev & 0xff; /* TODO tty_nr is unsigned */
+ proc->isKernelThread = false;
+ proc->isUserlandThread = false;
+ dp->translated = ps->kp_proc.p_flag & P_TRANSLATED;
+ proc->tty_nr = ps->kp_eproc.e_tdev;
+ proc->tty_name = NULL;
proc->starttime_ctime = ep->p_starttime.tv_sec;
Process_fillStarttimeBuffer(proc);
- proc->comm = DarwinProcess_getCmdLine(ps, &(proc->basenameOffset));
+ DarwinProcess_updateExe(ep->p_pid, proc);
+ DarwinProcess_updateCmdLine(ps, proc);
+
+ if (proc->settings->ss->flags & PROCESS_FLAG_CWD) {
+ DarwinProcess_updateCwd(ep->p_pid, proc);
+ }
+ }
+
+ if (proc->tty_name == NULL && (dev_t)proc->tty_nr != NODEV) {
+ /* The call to devname() is extremely expensive (due to lstat)
+ * and represents ~95% of htop's CPU usage when there is high
+ * process turnover.
+ *
+ * To mitigate this we only fetch TTY information if the TTY
+ * field is enabled in the settings.
+ */
+ if (proc->settings->ss->flags & PROCESS_FLAG_TTY) {
+ proc->tty_name = DarwinProcess_getDevname(proc->tty_nr);
+ if (!proc->tty_name) {
+ /* devname failed: prevent us from calling it again */
+ proc->tty_nr = NODEV;
+ }
+ }
}
/* Mutable information */
proc->nice = ep->p_nice;
proc->priority = ep->p_priority;
- 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;
}
-void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl) {
+void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS) {
struct proc_taskinfo pti;
if (sizeof(pti) == proc_pidinfo(proc->super.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) {
- if (0 != proc->utime || 0 != proc->stime) {
- uint64_t diff = (pti.pti_total_system - proc->stime)
- + (pti.pti_total_user - proc->utime);
+ uint64_t total_existing_time_ns = proc->stime + proc->utime;
- proc->super.percent_cpu = (double)diff * (double)dpl->super.cpuCount
- / ((double)dpl->global_diff * 100000.0);
+ uint64_t user_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_user);
+ uint64_t system_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_system);
-// fprintf(stderr, "%f %llu %llu %llu %llu %llu\n", proc->super.percent_cpu,
-// proc->stime, proc->utime, pti.pti_total_system, pti.pti_total_user, dpl->global_diff);
-// exit(7);
+ uint64_t total_current_time_ns = user_time_ns + system_time_ns;
+
+ if (total_existing_time_ns && 1E-6 < timeIntervalNS) {
+ uint64_t total_time_diff_ns = total_current_time_ns - total_existing_time_ns;
+ proc->super.percent_cpu = ((double)total_time_diff_ns / timeIntervalNS) * 100.0;
+ } else {
+ proc->super.percent_cpu = 0.0;
}
+ Process_updateCPUFieldWidths(proc->super.percent_cpu);
- proc->super.time = (pti.pti_total_system + pti.pti_total_user) / 10000000;
+ proc->super.time = nanosecondsToCentiseconds(total_current_time_ns);
proc->super.nlwp = pti.pti_threadnum;
- proc->super.m_virt = pti.pti_virtual_size / CRT_pageSize;
- proc->super.m_resident = pti.pti_resident_size / CRT_pageSize;
+ proc->super.m_virt = pti.pti_virtual_size / ONE_K;
+ 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;
- proc->stime = pti.pti_total_system;
- proc->utime = pti.pti_total_user;
+ proc->stime = system_time_ns;
+ proc->utime = user_time_ns;
dpl->super.kernelThreads += 0; /*pti.pti_threads_system;*/
dpl->super.userlandThreads += pti.pti_threadnum; /*pti.pti_threads_user;*/
@@ -287,7 +410,7 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) {
return;
}
- if (proc->state == 'Z') {
+ if (proc->state == ZOMBIE) {
return;
}
@@ -331,13 +454,25 @@ 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
+ },
+ .writeField = DarwinProcess_writeField,
+ .compareByKey = DarwinProcess_compareByKey,
+};
diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h
index 98897c9..bd17974 100644
--- a/darwin/DarwinProcess.h
+++ b/darwin/DarwinProcess.h
@@ -3,35 +3,38 @@
/*
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 "DarwinProcessList.h"
#include "Settings.h"
+#include "darwin/DarwinProcessList.h"
+#define PROCESS_FLAG_TTY 0x00000100
+
typedef struct DarwinProcess_ {
Process super;
uint64_t utime;
uint64_t stime;
bool taskAccess;
+ bool translated;
} DarwinProcess;
extern const ProcessClass DarwinProcess_class;
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
+
Process* DarwinProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-bool Process_isThread(const Process* this);
-
void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists);
-void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl);
+void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS);
/*
* Scan threads for process state information.
diff --git a/darwin/DarwinProcessList.c b/darwin/DarwinProcessList.c
index ae1efb1..dae588b 100644
--- a/darwin/DarwinProcessList.c
+++ b/darwin/DarwinProcessList.c
@@ -1,13 +1,12 @@
/*
htop - DarwinProcessList.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 "DarwinProcessList.h"
+#include "darwin/DarwinProcessList.h"
-#include <err.h>
#include <errno.h>
#include <libproc.h>
#include <stdbool.h>
@@ -20,65 +19,26 @@ in the source distribution for its full text.
#include <sys/sysctl.h>
#include "CRT.h"
-#include "DarwinProcess.h"
#include "ProcessList.h"
-#include "zfs/openzfs_sysctl.h"
+#include "darwin/DarwinProcess.h"
+#include "darwin/Platform.h"
+#include "darwin/PlatformHelpers.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\n");
+ 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\n");
+ CRT_fatalError("Unable to free old CPU load information");
}
*p = NULL;
}
@@ -90,7 +50,7 @@ static unsigned ProcessList_allocateCPULoadInfo(processor_cpu_load_info_t* p) {
// 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\n");
+ CRT_fatalError("Unable to retrieve CPU info");
}
return cpu_count;
@@ -100,7 +60,7 @@ 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\n");
+ CRT_fatalError("Unable to retrieve VM statistics");
}
}
@@ -108,12 +68,13 @@ 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--) {
+ 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) {
@@ -128,13 +89,15 @@ static struct kinfo_proc* ProcessList_getKInfoProcs(size_t* count) {
CRT_fatalError("Unable to get kinfo_procs");
}
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
DarwinProcessList* this = xCalloc(1, sizeof(DarwinProcessList));
- ProcessList_init(&this->super, Class(Process), usersTable, pidMatchList, userId);
+ ProcessList_init(&this->super, Class(DarwinProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
/* Initialize the CPU information */
- this->super.cpuCount = ProcessList_allocateCPULoadInfo(&this->prev_load);
+ this->super.activeCPUs = ProcessList_allocateCPULoadInfo(&this->prev_load);
+ // TODO: support offline CPUs and hot swapping
+ this->super.existingCPUs = this->super.activeCPUs;
ProcessList_getHostInfo(&this->host_info);
ProcessList_allocateCPULoadInfo(&this->curr_load);
@@ -179,12 +142,14 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
/* Get the time difference */
dpl->global_diff = 0;
- for (int i = 0; i < dpl->super.cpuCount; ++i) {
+ for (unsigned int i = 0; i < dpl->super.existingCPUs; ++i) {
for (size_t j = 0; j < CPU_STATE_MAX; ++j) {
dpl->global_diff += dpl->curr_load[i].cpu_ticks[j] - dpl->prev_load[i].cpu_ticks[j];
}
}
+ const double time_interval_ns = Platform_schedulerTicksToNanoseconds(dpl->global_diff) / (double) dpl->super.activeCPUs;
+
/* Clear the thread counts */
super->kernelThreads = 0;
super->userlandThreads = 0;
@@ -204,10 +169,15 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc = (DarwinProcess*)ProcessList_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new);
DarwinProcess_setFromKInfoProc(&proc->super, &ps[i], preExisting);
- DarwinProcess_setFromLibprocPidinfo(proc, dpl);
+ 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);
+ bool isScanThreadSupported = !Platform_KernelVersionIsBetween((KernelVersion) {17, 0, 0}, (KernelVersion) {17, 5, 0});
if (isScanThreadSupported) {
DarwinProcess_scanThreads(proc);
@@ -216,11 +186,18 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
super->totalTasks += 1;
if (!preExisting) {
- proc->super.user = UsersTable_getRef(super->usersTable, proc->super.st_uid);
-
ProcessList_add(super, &proc->super);
}
}
free(ps);
}
+
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ // TODO: support offline CPUs and hot swapping
+ (void) super; (void) id;
+
+ return true;
+}
diff --git a/darwin/DarwinProcessList.h b/darwin/DarwinProcessList.h
index 1ae2f2b..393e656 100644
--- a/darwin/DarwinProcessList.h
+++ b/darwin/DarwinProcessList.h
@@ -3,7 +3,7 @@
/*
htop - DarwinProcessList.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.
*/
@@ -28,10 +28,12 @@ typedef struct DarwinProcessList_ {
ZfsArcStats zfs;
} DarwinProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* this);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/darwin/Platform.c b/darwin/Platform.c
index e24b67b..332752b 100644
--- a/darwin/Platform.c
+++ b/darwin/Platform.c
@@ -2,36 +2,62 @@
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.
*/
-#include "Platform.h"
-#include "Macros.h"
-#include "CPUMeter.h"
-#include "MemoryMeter.h"
-#include "SwapMeter.h"
-#include "TasksMeter.h"
-#include "LoadAverageMeter.h"
+#include "config.h" // IWYU pragma: keep
+
+#include "darwin/Platform.h"
+
+#include <errno.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <CoreFoundation/CFString.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/ps/IOPowerSources.h>
+#include <IOKit/ps/IOPSKeys.h>
+
#include "ClockMeter.h"
+#include "CPUMeter.h"
+#include "CRT.h"
#include "DateMeter.h"
#include "DateTimeMeter.h"
#include "HostnameMeter.h"
+#include "LoadAverageMeter.h"
+#include "Macros.h"
+#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
#include "ProcessLocksScreen.h"
+#include "SwapMeter.h"
+#include "SysArchMeter.h"
+#include "TasksMeter.h"
#include "UptimeMeter.h"
+#include "darwin/DarwinProcessList.h"
+#include "darwin/PlatformHelpers.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsCompressedArcMeter.h"
-#include "DarwinProcessList.h"
-#include <math.h>
-#include <stdlib.h>
+#ifdef HAVE_HOST_GET_CLOCK_SERVICE
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
-#include <CoreFoundation/CoreFoundation.h>
-#include <CoreFoundation/CFString.h>
-#include <IOKit/ps/IOPowerSources.h>
-#include <IOKit/ps/IOPSKeys.h>
+#ifdef HAVE_MACH_MACH_TIME_H
+#include <mach/mach_time.h>
+#endif
-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 },
@@ -71,35 +97,6 @@ const SignalItem Platform_signals[] = {
const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
-ProcessFieldData Process_fields[] = {
- [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, },
- [PID] = { .name = "PID", .title = " PID ", .description = "Process/thread ID", .flags = 0, },
- [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
- [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)", .flags = 0, },
- [PPID] = { .name = "PPID", .title = " PPID ", .description = "Parent process ID", .flags = 0, },
- [PGRP] = { .name = "PGRP", .title = " PGRP ", .description = "Process group ID", .flags = 0, },
- [SESSION] = { .name = "SESSION", .title = " SID ", .description = "Process's session ID", .flags = 0, },
- [TTY_NR] = { .name = "TTY_NR", .title = " TTY ", .description = "Controlling terminal", .flags = 0, },
- [TPGID] = { .name = "TPGID", .title = " TPGID ", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, },
- [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
- [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
- [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
- [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
-
- [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, },
- [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
- [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, },
- [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
- [TGID] = { .name = "TGID", .title = " TGID ", .description = "Thread group ID (i.e. process ID)", .flags = 0, },
- [100] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, },
-};
-
const MeterClass* const Platform_meterTypes[] = {
&CPUMeter_class,
&ClockMeter_class,
@@ -109,9 +106,11 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
+ &MemorySwapMeter_class,
&TasksMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&UptimeMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
@@ -131,10 +130,37 @@ const MeterClass* const Platform_meterTypes[] = {
NULL
};
-int Platform_numberOfFields = 100;
+static double Platform_nanosecondsPerMachTick = 1.0;
-void Platform_init(void) {
- /* no platform-specific setup needed */
+static double Platform_nanosecondsPerSchedulerTick = -1;
+
+bool Platform_init(void) {
+ Platform_nanosecondsPerMachTick = Platform_calculateNanosecondsPerMachTick();
+
+ // Determine the number of scheduler clock ticks per second
+ errno = 0;
+ long scheduler_ticks_per_sec = sysconf(_SC_CLK_TCK);
+
+ if (errno || scheduler_ticks_per_sec < 1) {
+ CRT_fatalError("Unable to retrieve clock tick rate");
+ }
+
+ const double nanos_per_sec = 1e9;
+ Platform_nanosecondsPerSchedulerTick = nanos_per_sec / scheduler_ticks_per_sec;
+
+ return true;
+}
+
+// Converts ticks in the Mach "timebase" to nanoseconds.
+// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant.
+uint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks) {
+ return (uint64_t) ((double) mach_ticks * Platform_nanosecondsPerMachTick);
+}
+
+// Converts "scheduler ticks" to nanoseconds.
+// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant.
+double Platform_schedulerTicksToNanoseconds(const double scheduler_ticks) {
+ return scheduler_ticks * Platform_nanosecondsPerSchedulerTick;
}
void Platform_done(void) {
@@ -146,7 +172,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);
@@ -174,41 +200,31 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
}
}
-int Platform_getMaxPid() {
+int Platform_getMaxPid(void) {
/* http://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/proc_internal.hh */
return 99999;
}
-ProcessPidColumn Process_pidColumns[] = {
- { .id = PID, .label = "PID" },
- { .id = PPID, .label = "PPID" },
- { .id = TPGID, .label = "TPGID" },
- { .id = TGID, .label = "TGID" },
- { .id = PGRP, .label = "PGRP" },
- { .id = SESSION, .label = "SID" },
- { .id = 0, .label = NULL },
-};
-
static double Platform_setCPUAverageValues(Meter* mtr) {
const ProcessList* dpl = mtr->pl;
- int cpus = dpl->cpuCount;
+ unsigned int activeCPUs = dpl->activeCPUs;
double sumNice = 0.0;
double sumNormal = 0.0;
double sumKernel = 0.0;
double sumPercent = 0.0;
- for (int i = 1; i <= cpus; i++) {
+ for (unsigned int i = 1; i <= dpl->existingCPUs; i++) {
sumPercent += Platform_setCPUValues(mtr, i);
sumNice += mtr->values[CPU_METER_NICE];
sumNormal += mtr->values[CPU_METER_NORMAL];
sumKernel += mtr->values[CPU_METER_KERNEL];
}
- mtr->values[CPU_METER_NICE] = sumNice / cpus;
- mtr->values[CPU_METER_NORMAL] = sumNormal / cpus;
- mtr->values[CPU_METER_KERNEL] = sumKernel / cpus;
- return sumPercent / cpus;
+ mtr->values[CPU_METER_NICE] = sumNice / activeCPUs;
+ mtr->values[CPU_METER_NORMAL] = sumNormal / activeCPUs;
+ mtr->values[CPU_METER_KERNEL] = sumKernel / activeCPUs;
+ return sumPercent / activeCPUs;
}
-double Platform_setCPUValues(Meter* mtr, int cpu) {
+double Platform_setCPUValues(Meter* mtr, unsigned int cpu) {
if (cpu == 0) {
return Platform_setCPUAverageValues(mtr);
@@ -248,9 +264,11 @@ void Platform_setMemoryValues(Meter* mtr) {
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] = (double)vm->inactive_count * page_K;
+ mtr->values[MEMORY_METER_USED] = (double)(vm->active_count + vm->wire_count) * page_K;
+ mtr->values[MEMORY_METER_BUFFERS] = (double)vm->purgeable_count * page_K;
+ // mtr->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ mtr->values[MEMORY_METER_CACHE] = (double)vm->inactive_count * page_K;
+ // mtr->values[MEMORY_METER_AVAILABLE] = "available memory"
}
void Platform_setSwapValues(Meter* mtr) {
@@ -260,7 +278,7 @@ 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;
}
void Platform_setZfsArcValues(Meter* this) {
@@ -326,15 +344,9 @@ char* Platform_getProcessEnv(pid_t pid) {
return env;
}
-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;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -343,78 +355,83 @@ bool Platform_getDiskIO(DiskIOData* data) {
return false;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
+bool Platform_getNetworkIO(NetworkIOData* data) {
// TODO
- *bytesReceived = 0;
- *packetsReceived = 0;
- *bytesTransmitted = 0;
- *packetsTransmitted = 0;
+ (void)data;
return false;
}
void Platform_getBattery(double* percent, ACPresence* isOnAC) {
- CFTypeRef power_sources = IOPSCopyPowerSourcesInfo();
-
*percent = NAN;
*isOnAC = AC_ERROR;
- if (NULL == power_sources)
- return;
-
- CFArrayRef list = IOPSCopyPowerSourcesList(power_sources);
- CFDictionaryRef battery = NULL;
- int len;
+ CFArrayRef list = NULL;
- if (NULL == list) {
- CFRelease(power_sources);
+ CFTypeRef power_sources = IOPSCopyPowerSourcesInfo();
+ if (!power_sources)
+ goto cleanup;
- return;
- }
+ list = IOPSCopyPowerSourcesList(power_sources);
+ if (!list)
+ goto cleanup;
- len = CFArrayGetCount(list);
+ double cap_current = 0.0;
+ double cap_max = 0.0;
/* Get the battery */
- for (int i = 0; i < len && battery == NULL; ++i) {
- CFDictionaryRef candidate = IOPSGetPowerSourceDescription(power_sources,
- CFArrayGetValueAtIndex(list, i)); /* GET rule */
- CFStringRef type;
-
- if (NULL != candidate) {
- type = (CFStringRef) CFDictionaryGetValue(candidate,
- CFSTR(kIOPSTransportTypeKey)); /* GET rule */
-
- if (kCFCompareEqualTo == CFStringCompare(type, CFSTR(kIOPSInternalType), 0)) {
- CFRetain(candidate);
- battery = candidate;
- }
- }
- }
+ for (int i = 0, len = CFArrayGetCount(list); i < len; ++i) {
+ CFDictionaryRef power_source = IOPSGetPowerSourceDescription(power_sources, CFArrayGetValueAtIndex(list, i)); /* GET rule */
+
+ if (!power_source)
+ continue;
+
+ CFStringRef power_type = CFDictionaryGetValue(power_source, CFSTR(kIOPSTransportTypeKey)); /* GET rule */
+
+ if (kCFCompareEqualTo != CFStringCompare(power_type, CFSTR(kIOPSInternalType), 0))
+ continue;
- if (NULL != battery) {
/* Determine the AC state */
- CFStringRef power_state = CFDictionaryGetValue(battery, CFSTR(kIOPSPowerSourceStateKey));
+ CFStringRef power_state = CFDictionaryGetValue(power_source, CFSTR(kIOPSPowerSourceStateKey));
- *isOnAC = (kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0))
- ? AC_PRESENT
- : AC_ABSENT;
+ if (*isOnAC != AC_PRESENT)
+ *isOnAC = (kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0)) ? AC_PRESENT : AC_ABSENT;
/* Get the percentage remaining */
- double current;
- double max;
+ double tmp;
+ CFNumberGetValue(CFDictionaryGetValue(power_source, CFSTR(kIOPSCurrentCapacityKey)), kCFNumberDoubleType, &tmp);
+ cap_current += tmp;
+ CFNumberGetValue(CFDictionaryGetValue(power_source, CFSTR(kIOPSMaxCapacityKey)), kCFNumberDoubleType, &tmp);
+ cap_max += tmp;
+ }
- CFNumberGetValue(CFDictionaryGetValue(battery, CFSTR(kIOPSCurrentCapacityKey)),
- kCFNumberDoubleType, &current);
- CFNumberGetValue(CFDictionaryGetValue(battery, CFSTR(kIOPSMaxCapacityKey)),
- kCFNumberDoubleType, &max);
+ if (cap_max > 0.0)
+ *percent = 100.0 * cap_current / cap_max;
- *percent = (current * 100.0) / max;
+cleanup:
+ if (list)
+ CFRelease(list);
- CFRelease(battery);
- }
+ if (power_sources)
+ CFRelease(power_sources);
+}
+
+void Platform_gettime_monotonic(uint64_t* msec) {
+
+#ifdef HAVE_HOST_GET_CLOCK_SERVICE
+
+ clock_serv_t cclock;
+ mach_timespec_t mts;
+
+ host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
+ clock_get_time(cclock, &mts);
+ mach_port_deallocate(mach_task_self(), cclock);
+
+ *msec = ((uint64_t)mts.tv_sec * 1000) + ((uint64_t)mts.tv_nsec / 1000000);
+
+#else
+
+ Generic_gettime_monotonic(msec);
+
+#endif
- CFRelease(list);
- CFRelease(power_sources);
}
diff --git a/darwin/Platform.h b/darwin/Platform.h
index e1f8355..6636207 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.
*/
@@ -14,16 +14,21 @@ in the source distribution for its full text.
#include "Action.h"
#include "BatteryMeter.h"
#include "CPUMeter.h"
-#include "DarwinProcess.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "NetworkIOMeter.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
+#include "darwin/DarwinProcess.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
-extern ProcessFieldData Process_fields[];
-extern ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
-extern int Platform_numberOfFields;
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -31,7 +36,15 @@ 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.
+uint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks);
+
+// Converts "scheduler ticks" to nanoseconds.
+// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant.
+double Platform_schedulerTicksToNanoseconds(const double scheduler_ticks);
void Platform_done(void);
@@ -43,9 +56,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-extern ProcessPidColumn Process_pidColumns[];
-
-double Platform_setCPUValues(Meter* mtr, int cpu);
+double Platform_setCPUValues(Meter* mtr, unsigned int cpu);
void Platform_setMemoryValues(Meter* mtr);
@@ -57,17 +68,60 @@ 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);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC);
+
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+static inline 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) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+void Platform_gettime_monotonic(uint64_t* msec);
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
-void Platform_getBattery(double *percent, ACPresence *isOnAC);
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
#endif
diff --git a/darwin/PlatformHelpers.c b/darwin/PlatformHelpers.c
new file mode 100644
index 0000000..97f0741
--- /dev/null
+++ b/darwin/PlatformHelpers.c
@@ -0,0 +1,126 @@
+/*
+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.
+ */
+
+ size_t cpuBrandStringSize = 1024;
+ char cpuBrandString[cpuBrandStringSize];
+ Platform_getCPUBrandString(cpuBrandString, cpuBrandStringSize);
+
+ 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
new file mode 100644
index 0000000..05fbc3b
--- /dev/null
+++ b/darwin/ProcessField.h
@@ -0,0 +1,18 @@
+#ifndef HEADER_DarwinProcessField
+#define HEADER_DarwinProcessField
+/*
+htop - darwin/ProcessField.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+#define PLATFORM_PROCESS_FIELDS \
+ TRANSLATED = 100, \
+ \
+ DUMMY_BUMP_FIELD = CWD, \
+ // End of list
+
+
+#endif /* HEADER_DarwinProcessField */
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 a0856c4..977ee38 100644
--- a/docs/styleguide.md
+++ b/docs/styleguide.md
@@ -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.
@@ -194,7 +196,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,8 +219,25 @@ 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.
Documentation files reside in the `docs/` directory and have a `.md` extension.
+
+Writing pull-requests (PRs)
+---------------------------
+
+When writing your PR or patch, the set of patches should contain the minimal changes required.
+Each patch in itself should ideally be self-contained and runable.
+
+A PR should not contain any merge commits.
+To follow the upstream branch of your PR rebase your work instead.
+
+Avoid small commits that just fix typos that another of your commits introduced.
+Instead squash those changes in the appropriate commit that introduced that mistake.
+Git offers `git commit --fixup=<commit>` and `git rebase -i --autosquash` to help you with this.
+
+Your final PR should contain a minimal set of reasonably sized commits that by themselves are easy to review.
+
+Rebase early. Rebase often.
diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c
index 9311f03..7ff9245 100644
--- a/dragonflybsd/DragonFlyBSDProcess.c
+++ b/dragonflybsd/DragonFlyBSDProcess.c
@@ -2,72 +2,54 @@
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.
*/
-#include "Process.h"
-#include "ProcessList.h"
-#include "DragonFlyBSDProcess.h"
-#include "Platform.h"
-#include "CRT.h"
+#include "dragonflybsd/DragonFlyBSDProcess.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
+#include "CRT.h"
+
+#include "dragonflybsd/Platform.h"
-const ProcessClass DragonFlyBSDProcess_class = {
- .super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = DragonFlyBSDProcess_compare
- },
- .writeField = DragonFlyBSDProcess_writeField,
-};
-ProcessFieldData Process_fields[] = {
+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, },
+ [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, },
[COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
[STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping (<20s), I Idle, Q Queued for Run, R running, D disk, Z zombie, T traced, W paging, B Blocked, A AskedPage, C Core, J Jailed)", .flags = 0, },
- [PPID] = { .name = "PPID", .title = " PPID ", .description = "Parent process ID", .flags = 0, },
- [PGRP] = { .name = "PGRP", .title = " PGRP ", .description = "Process group ID", .flags = 0, },
- [SESSION] = { .name = "SESSION", .title = " SID ", .description = "Process's session ID", .flags = 0, },
- [TTY_NR] = { .name = "TTY_NR", .title = " TTY ", .description = "Controlling terminal", .flags = 0, },
- [TPGID] = { .name = "TPGID", .title = " TPGID ", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, },
- [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
+ [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, },
+ [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, },
[PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
[NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
[STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, },
- [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, },
+ [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
+ [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
+ [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[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, },
- [JID] = { .name = "JID", .title = " JID ", .description = "Jail prison ID", .flags = 0, },
+ [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
+ [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process", .flags = 0, },
+ [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, },
+ [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
+ [JID] = { .name = "JID", .title = "JID", .description = "Jail prison ID", .flags = 0, .pidColumn = true, },
[JAIL] = { .name = "JAIL", .title = "JAIL ", .description = "Jail prison name", .flags = 0, },
- [LAST_PROCESSFIELD] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, },
-};
-
-ProcessPidColumn Process_pidColumns[] = {
- { .id = JID, .label = "JID" },
- { .id = PID, .label = "PID" },
- { .id = PPID, .label = "PPID" },
- { .id = TPGID, .label = "TPGID" },
- { .id = TGID, .label = "TGID" },
- { .id = PGRP, .label = "PGRP" },
- { .id = SESSION, .label = "SID" },
- { .id = 0, .label = NULL },
};
Process* DragonFlyBSDProcess_new(const Settings* settings) {
@@ -84,59 +66,45 @@ void Process_delete(Object* cast) {
free(this);
}
-void DragonFlyBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
+static void DragonFlyBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) this;
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
- int n = sizeof(buffer) - 1;
- switch ((int) field) {
+ size_t n = sizeof(buffer) - 1;
+ switch (field) {
// add Platform-specific fields here
- case PID: xSnprintf(buffer, n, Process_pidFormat, (fp->kernel ? -1 : this->pid)); break;
- case JID: xSnprintf(buffer, n, Process_pidFormat, fp->jid); break;
- case JAIL: {
- xSnprintf(buffer, n, "%-11s ", fp->jname);
- if (buffer[11] != '\0') {
- buffer[11] = ' ';
- buffer[12] = '\0';
- }
- break;
- }
+ case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_isKernelThread(this) ? -1 : this->pid); break;
+ case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break;
+ case JAIL: Process_printLeftAlignedField(str, attr, fp->jname, 11); return;
default:
Process_writeField(this, str, field);
return;
}
- RichString_append(str, attr, buffer);
+ RichString_appendWide(str, attr, buffer);
}
-long DragonFlyBSDProcess_compare(const void* v1, const void* v2) {
- const DragonFlyBSDProcess *p1, *p2;
- const Settings *settings = ((const Process*)v1)->settings;
+static int DragonFlyBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const DragonFlyBSDProcess* p1 = (const DragonFlyBSDProcess*)v1;
+ const DragonFlyBSDProcess* p2 = (const DragonFlyBSDProcess*)v2;
- if (settings->direction == 1) {
- p1 = (const DragonFlyBSDProcess*)v1;
- p2 = (const DragonFlyBSDProcess*)v2;
- } else {
- p2 = (const DragonFlyBSDProcess*)v1;
- p1 = (const DragonFlyBSDProcess*)v2;
- }
-
- switch ((int) settings->sortKey) {
+ switch (key) {
// add Platform-specific fields here
case JID:
return SPACESHIP_NUMBER(p1->jid, p2->jid);
case JAIL:
return SPACESHIP_NULLSTR(p1->jname, p2->jname);
default:
- return Process_compare(v1, v2);
+ return Process_compareByKey_Base(v1, v2, key);
}
}
-bool Process_isThread(const Process* this) {
- const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) this;
-
- if (fp->kernel == 1 ) {
- return 1;
- } else {
- return (Process_isUserlandThread(this));
- }
-}
+const ProcessClass DragonFlyBSDProcess_class = {
+ .super = {
+ .extends = Class(Process),
+ .display = Process_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .writeField = DragonFlyBSDProcess_writeField,
+ .compareByKey = DragonFlyBSDProcess_compareByKey
+};
diff --git a/dragonflybsd/DragonFlyBSDProcess.h b/dragonflybsd/DragonFlyBSDProcess.h
index 1befd94..e0a77ef 100644
--- a/dragonflybsd/DragonFlyBSDProcess.h
+++ b/dragonflybsd/DragonFlyBSDProcess.h
@@ -4,43 +4,29 @@
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.
*/
-typedef enum DragonFlyBSDProcessFields {
- // Add platform-specific fields here, with ids >= 100
- JID = 100,
- JAIL = 101,
- LAST_PROCESSFIELD = 102,
-} DragonFlyBSDProcessField;
+#include <stdbool.h>
+
+#include "Object.h"
+#include "Process.h"
+#include "Settings.h"
+
typedef struct DragonFlyBSDProcess_ {
Process super;
- int kernel;
int jid;
char* jname;
} DragonFlyBSDProcess;
-#define Process_isKernelThread(_process) (_process->kernel == 1)
-
-//#define Process_isUserlandThread(_process) (_process->pid != _process->tgid)
-#define Process_isUserlandThread(_process) (_process->nlwp > 1)
-
extern const ProcessClass DragonFlyBSDProcess_class;
-extern ProcessFieldData Process_fields[];
-
-extern ProcessPidColumn Process_pidColumns[];
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
Process* DragonFlyBSDProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-void DragonFlyBSDProcess_writeField(const Process* this, RichString* str, ProcessField field);
-
-long DragonFlyBSDProcess_compare(const void* v1, const void* v2);
-
-bool Process_isThread(const Process* this);
-
#endif
diff --git a/dragonflybsd/DragonFlyBSDProcessList.c b/dragonflybsd/DragonFlyBSDProcessList.c
index 940ec03..f46d6ce 100644
--- a/dragonflybsd/DragonFlyBSDProcessList.c
+++ b/dragonflybsd/DragonFlyBSDProcessList.c
@@ -2,28 +2,28 @@
htop - DragonFlyBSDProcessList.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 "ProcessList.h"
-#include "DragonFlyBSDProcessList.h"
-#include "DragonFlyBSDProcess.h"
+#include "dragonflybsd/DragonFlyBSDProcessList.h"
-#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
-#include <err.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <string.h>
#include <sys/param.h>
#include "CRT.h"
#include "Macros.h"
+#include "dragonflybsd/DragonFlyBSDProcess.h"
+
static int MIB_hw_physmem[2];
static int MIB_vm_stats_vm_v_page_count[4];
@@ -42,12 +42,12 @@ static int MIB_kern_cp_time[2];
static int MIB_kern_cp_times[2];
static int kernelFScale;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
size_t len;
char errbuf[_POSIX2_LINE_MAX];
DragonFlyBSDProcessList* dfpl = xCalloc(1, sizeof(DragonFlyBSDProcessList));
ProcessList* pl = (ProcessList*) dfpl;
- ProcessList_init(pl, Class(DragonFlyBSDProcess), usersTable, pidMatchList, userId);
+ ProcessList_init(pl, Class(DragonFlyBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
// physical memory in system: hw.physmem
// physical page size: hw.pagesize
@@ -55,12 +55,9 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
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) {
- pageSize = CRT_pageSize;
- pageSizeKb = CRT_pageSizeKB;
- } else {
- pageSizeKb = pageSize / ONE_K;
- }
+ 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
@@ -82,8 +79,8 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
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);
+ dfpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
+ dfpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
len = sizeof_cp_time_array;
// fetch initial single (or average) CPU clicks from kernel
@@ -98,13 +95,15 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
sysctl(MIB_kern_cp_times, 2, dfpl->cp_times_o, &len, NULL, 0);
}
- pl->cpuCount = MAXIMUM(cpus, 1);
+ pl->existingCPUs = MAXIMUM(cpus, 1);
+ // TODO: support offline CPUs and hot swapping
+ pl->activeCPUs = pl->existingCPUs;
if (cpus == 1 ) {
dfpl->cpus = xRealloc(dfpl->cpus, sizeof(CPUData));
} else {
// on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)
- dfpl->cpus = xRealloc(dfpl->cpus, (pl->cpuCount + 1) * sizeof(CPUData));
+ dfpl->cpus = xRealloc(dfpl->cpus, (pl->existingCPUs + 1) * sizeof(CPUData));
}
len = sizeof(kernelFScale);
@@ -115,7 +114,7 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
dfpl->kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf);
if (dfpl->kd == NULL) {
- errx(1, "kvm_open: %s", errbuf);
+ CRT_fatalError("kvm_openfiles() failed");
}
return pl;
@@ -143,8 +142,8 @@ void ProcessList_delete(ProcessList* this) {
static inline void DragonFlyBSDProcessList_scanCPUTime(ProcessList* pl) {
const DragonFlyBSDProcessList* dfpl = (DragonFlyBSDProcessList*) pl;
- int cpus = pl->cpuCount; // actual CPU count
- int maxcpu = cpus; // max iteration (in case we have average + smp)
+ unsigned int cpus = pl->existingCPUs; // actual CPU count
+ unsigned int maxcpu = cpus; // max iteration (in case we have average + smp)
int cp_times_offset;
assert(cpus > 0);
@@ -171,7 +170,7 @@ static inline void DragonFlyBSDProcessList_scanCPUTime(ProcessList* pl) {
sysctl(MIB_kern_cp_times, 2, dfpl->cp_times_n, &sizeof_cp_time_array, NULL, 0);
}
- for (int i = 0; i < maxcpu; i++) {
+ for (unsigned int i = 0; i < maxcpu; i++) {
if (cpus == 1) {
// single CPU box
cp_time_n = dfpl->cp_time_n;
@@ -265,29 +264,91 @@ static inline void DragonFlyBSDProcessList_scanMemoryInfo(ProcessList* pl) {
pl->usedSwap *= pageSizeKb;
}
-char* DragonFlyBSDProcessList_readProcessName(kvm_t* kd, struct kinfo_proc* kproc, int* basenameEnd) {
+//static void DragonFlyBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) {
+// const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, kproc->kp_pid };
+// char buffer[2048];
+// size_t size = sizeof(buffer);
+// if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+// Process_updateExe(proc, NULL);
+// return;
+// }
+//
+// /* Kernel threads return an empty buffer */
+// if (buffer[0] == '\0') {
+// Process_updateExe(proc, NULL);
+// return;
+// }
+//
+// Process_updateExe(proc, buffer);
+//}
+
+static void DragonFlyBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) {
+ if (Process_isKernelThread(proc))
+ return;
+
+ char path[32];
+ xSnprintf(path, sizeof(path), "/proc/%d/file", kproc->kp_pid);
+
+ char target[PATH_MAX];
+ ssize_t ret = readlink(path, target, sizeof(target) - 1);
+ if (ret <= 0)
+ return;
+
+ target[ret] = '\0';
+ Process_updateExe(proc, target);
+}
+
+static void DragonFlyBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
+ const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, kproc->kp_pid };
+ char buffer[2048];
+ size_t size = sizeof(buffer);
+ if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ /* Kernel threads return an empty buffer */
+ if (buffer[0] == '\0') {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ free_and_xStrdup(&proc->procCwd, buffer);
+}
+
+static void DragonFlyBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
+ Process_updateComm(proc, kproc->kp_comm);
+
char** argv = kvm_getargv(kd, kproc, 0);
- if (!argv) {
- return xStrdup(kproc->kp_comm);
+ if (!argv || !argv[0]) {
+ Process_updateCmdline(proc, kproc->kp_comm, 0, strlen(kproc->kp_comm));
+ return;
}
- int len = 0;
+
+ size_t len = 0;
for (int i = 0; argv[i]; i++) {
len += strlen(argv[i]) + 1;
}
- char* comm = xMalloc(len);
- char* at = comm;
- *basenameEnd = 0;
+
+ char* cmdline = xMalloc(len);
+
+ char* at = cmdline;
+ int end = 0;
for (int i = 0; argv[i]; i++) {
at = stpcpy(at, argv[i]);
- if (!*basenameEnd) {
- *basenameEnd = at - comm;
+ if (end == 0) {
+ end = at - cmdline;
}
- *at = ' ';
- at++;
+ *at++ = ' ';
}
at--;
*at = '\0';
- return comm;
+
+ Process_updateCmdline(proc, cmdline, 0, end);
+
+ free(cmdline);
}
static inline void DragonFlyBSDProcessList_scanJails(DragonFlyBSDProcessList* dfpl) {
@@ -297,35 +358,33 @@ static inline void DragonFlyBSDProcessList_scanJails(DragonFlyBSDProcessList* df
char* nextpos;
if (sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1) {
- fprintf(stderr, "initial sysctlbyname / jail.list failed\n");
- exit(3);
+ CRT_fatalError("initial sysctlbyname / jail.list failed");
}
+
retry:
if (len == 0)
return;
jls = xMalloc(len);
- if (jls == NULL) {
- fprintf(stderr, "xMalloc failed\n");
- exit(4);
- }
+
if (sysctlbyname("jail.list", jls, &len, NULL, 0) == -1) {
if (errno == ENOMEM) {
free(jls);
goto retry;
}
- fprintf(stderr, "sysctlbyname / jail.list failed\n");
- exit(5);
+ 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;
@@ -346,7 +405,7 @@ retry:
free(jls);
}
-char* DragonFlyBSDProcessList_readJailName(DragonFlyBSDProcessList* dfpl, int jailid) {
+static char* DragonFlyBSDProcessList_readJailName(DragonFlyBSDProcessList* dfpl, int jailid) {
char* hostname;
char* jname;
@@ -355,6 +414,7 @@ char* DragonFlyBSDProcessList_readJailName(DragonFlyBSDProcessList* dfpl, int ja
} else {
jname = xStrdup("-");
}
+
return jname;
}
@@ -375,11 +435,10 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
int count = 0;
- // TODO Kernel Threads seem to be skipped, need to figure out the correct flag
- struct kinfo_proc* kprocs = kvm_getprocs(dfpl->kd, KERN_PROC_ALL | (!hideUserlandThreads ? KERN_PROC_FLAG_LWP : 0), 0, &count);
+ 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++) {
- struct kinfo_proc* kproc = &kprocs[i];
+ const struct kinfo_proc* kproc = &kprocs[i];
bool preExisting = false;
bool ATTR_UNUSED isIdleProcess = false;
@@ -387,32 +446,47 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
Process* proc = ProcessList_getProcess(super, kproc->kp_ktaddr ? (pid_t)kproc->kp_ktaddr : kproc->kp_pid, &preExisting, DragonFlyBSDProcess_new);
DragonFlyBSDProcess* dfp = (DragonFlyBSDProcess*) proc;
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(dfp)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
if (!preExisting) {
dfp->jid = kproc->kp_jailid;
if (kproc->kp_ktaddr && kproc->kp_flags & P_SYSTEM) {
// dfb kernel threads all have the same pid, so we misuse the kernel thread address to give them a unique identifier
proc->pid = (pid_t)kproc->kp_ktaddr;
- dfp->kernel = 1;
+ proc->isKernelThread = true;
} else {
proc->pid = kproc->kp_pid; // process ID
- dfp->kernel = 0;
+ proc->isKernelThread = false;
}
- proc->ppid = kproc->kp_ppid; // parent process id
+ proc->isUserlandThread = kproc->kp_nthreads > 1;
+ proc->ppid = kproc->kp_ppid; // parent process id
proc->tpgid = kproc->kp_tpgid; // tty process group id
//proc->tgid = kproc->kp_lwp.kl_tid; // thread group id
proc->tgid = kproc->kp_pid; // thread group id
proc->pgrp = kproc->kp_pgid; // process group id
proc->session = kproc->kp_sid;
- proc->tty_nr = kproc->kp_tdev; // control terminal device number
proc->st_uid = kproc->kp_uid; // user ID
proc->processor = kproc->kp_lwp.kl_origcpu;
proc->starttime_ctime = kproc->kp_start.tv_sec;
+ Process_fillStarttimeBuffer(proc);
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
+ proc->tty_nr = kproc->kp_tdev; // control terminal device number
+ const char* name = (kproc->kp_tdev != NODEV) ? devname(kproc->kp_tdev, S_IFCHR) : NULL;
+ if (!name) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
+
+ DragonFlyBSDProcessList_updateExe(kproc, proc);
+ DragonFlyBSDProcessList_updateProcessName(dfpl->kd, kproc, proc);
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ DragonFlyBSDProcessList_updateCwd(kproc, proc);
+ }
+
ProcessList_add(super, proc);
- proc->comm = DragonFlyBSDProcessList_readProcessName(dfpl->kd, kproc, &proc->basenameOffset);
+
dfp->jname = DragonFlyBSDProcessList_readJailName(dfpl, kproc->kp_jailid);
} else {
proc->processor = kproc->kp_lwp.kl_cpuid;
@@ -428,22 +502,22 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
}
if (settings->updateProcessNames) {
- free(proc->comm);
- proc->comm = DragonFlyBSDProcessList_readProcessName(dfpl->kd, kproc, &proc->basenameOffset);
+ DragonFlyBSDProcessList_updateProcessName(dfpl->kd, kproc, proc);
}
}
- proc->m_virt = kproc->kp_vm_map_size / pageSize;
- proc->m_resident = kproc->kp_vm_rssize;
+ 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->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)kernelFScale);
- proc->percent_mem = 100.0 * (proc->m_resident * pageSizeKb) / (double)(super->totalMem);
+ 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 ( strcmp("idle", kproc->kp_comm) == 0 ) {
+ if (String_eq("idle", kproc->kp_comm)) {
isIdleProcess = true;
}
}
@@ -453,80 +527,89 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
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;
+ 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 = 'I'; isIdleProcess = true; break;
+ 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 = 'I';
+ proc->state = IDLE;
isIdleProcess = true;
} else {
- proc->state = 'S';
+ proc->state = SLEEPING;
}
else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) // interruptible lwkt wait
- proc->state = 'S';
+ proc->state = SLEEPING;
else if (kproc->kp_paddr) // uninterruptible wait
- proc->state = 'D';
+ proc->state = UNINTERRUPTIBLE_WAIT;
else // uninterruptible lwkt wait
- proc->state = 'B';
+ 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 = 'Q';
+ proc->state = QUEUED;
else
- proc->state = 'R';
+ proc->state = RUNNING;
}
break;
case LSSTOP:
- proc->state = 'T';
+ proc->state = STOPPED;
break;
default:
- proc->state = 'A';
+ proc->state = PAGING;
break;
}
break;
- case SSTOP: proc->state = 'T'; break;
- case SZOMB: proc->state = 'Z'; break;
- case SCORE: proc->state = 'C'; break;
- default: proc->state = '?';
+ 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 = 'W';
- }
- if (kproc->kp_flags & P_TRACED) {
- proc->state = 'T';
- }
- if (kproc->kp_flags & P_JAILED) {
- proc->state = 'J';
- }
+ 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(dfp)) {
+ if (Process_isKernelThread(proc))
super->kernelThreads++;
- }
super->totalTasks++;
- if (proc->state == 'R')
+
+ if (proc->state == RUNNING)
super->runningTasks++;
+
+ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
proc->updated = true;
}
}
+
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ // TODO: support offline CPUs and hot swapping
+ (void) super; (void) id;
+
+ return true;
+}
diff --git a/dragonflybsd/DragonFlyBSDProcessList.h b/dragonflybsd/DragonFlyBSDProcessList.h
index 682971e..c1bf2d1 100644
--- a/dragonflybsd/DragonFlyBSDProcessList.h
+++ b/dragonflybsd/DragonFlyBSDProcessList.h
@@ -4,23 +4,25 @@
htop - DragonFlyBSDProcessList.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.
*/
+#include <sys/types.h> // required for kvm.h
#include <kvm.h>
-#include <sys/param.h>
#include <osreldate.h>
-#include <sys/kinfo.h>
-#include <kinfo.h>
+#include <stdbool.h>
#include <sys/jail.h>
-#include <sys/uio.h>
+#include <sys/param.h>
#include <sys/resource.h>
+#include <sys/uio.h>
+
#include "Hashtable.h"
-#include "DragonFlyBSDProcess.h"
+#include "ProcessList.h"
+#include "UsersTable.h"
+
+#include "dragonflybsd/DragonFlyBSDProcess.h"
-#define JAIL_ERRMSGLEN 1024
-extern char jail_errmsg[JAIL_ERRMSGLEN];
typedef struct CPUData_ {
double userPercent;
@@ -51,14 +53,12 @@ typedef struct DragonFlyBSDProcessList_ {
Hashtable* jails;
} DragonFlyBSDProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* this);
-char* DragonFlyBSDProcessList_readProcessName(kvm_t* kd, struct kinfo_proc* kproc, int* basenameEnd);
-
-char* DragonFlyBSDProcessList_readJailName(DragonFlyBSDProcessList* dfpl, int jailid);
-
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c
index 40a7348..8a684d8 100644
--- a/dragonflybsd/Platform.c
+++ b/dragonflybsd/Platform.c
@@ -2,38 +2,45 @@
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 "Platform.h"
-#include "Macros.h"
-#include "Meter.h"
-#include "CPUMeter.h"
-#include "MemoryMeter.h"
-#include "SwapMeter.h"
-#include "TasksMeter.h"
-#include "LoadAverageMeter.h"
-#include "UptimeMeter.h"
-#include "ClockMeter.h"
-#include "DateMeter.h"
-#include "DateTimeMeter.h"
-#include "HostnameMeter.h"
-#include "DragonFlyBSDProcess.h"
-#include "DragonFlyBSDProcessList.h"
+#include "dragonflybsd/Platform.h"
-#include <sys/types.h>
+#include <math.h>
+#include <time.h>
+#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/time.h>
-#include <sys/resource.h>
+#include <sys/types.h>
#include <vm/vm_param.h>
-#include <time.h>
-#include <math.h>
-
-ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+#include "ClockMeter.h"
+#include "CPUMeter.h"
+#include "DateMeter.h"
+#include "DateTimeMeter.h"
+#include "HostnameMeter.h"
+#include "LoadAverageMeter.h"
+#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
+#include "ProcessList.h"
+#include "SwapMeter.h"
+#include "SysArchMeter.h"
+#include "TasksMeter.h"
+#include "UptimeMeter.h"
+#include "dragonflybsd/DragonFlyBSDProcess.h"
+#include "dragonflybsd/DragonFlyBSDProcessList.h"
+
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+};
-int Platform_numberOfFields = LAST_PROCESSFIELD;
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
@@ -82,11 +89,13 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadAverageMeter_class,
&LoadMeter_class,
&MemoryMeter_class,
+ &MemorySwapMeter_class,
&SwapMeter_class,
&TasksMeter_class,
&UptimeMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
&AllCPUs4Meter_class,
@@ -103,8 +112,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) {
@@ -116,7 +126,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);
@@ -147,7 +157,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;
}
-int Platform_getMaxPid() {
+int Platform_getMaxPid(void) {
int maxPid;
size_t size = sizeof(maxPid);
int err = sysctlbyname("kern.pid_max", &maxPid, &size, NULL, 0);
@@ -157,9 +167,9 @@ int Platform_getMaxPid() {
return maxPid;
}
-double Platform_setCPUValues(Meter* this, int cpu) {
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
const DragonFlyBSDProcessList* fpl = (const DragonFlyBSDProcessList*) this->pl;
- int cpus = this->pl->cpuCount;
+ unsigned int cpus = this->pl->activeCPUs;
const CPUData* cpuData;
if (cpus == 1) {
@@ -198,15 +208,18 @@ 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] = pl->cachedMem;
+ this->values[MEMORY_METER_USED] = pl->usedMem;
+ this->values[MEMORY_METER_BUFFERS] = pl->buffersMem;
+ // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ this->values[MEMORY_METER_CACHE] = pl->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[SWAP_METER_USED] = pl->usedSwap;
+ this->values[SWAP_METER_CACHE] = NAN;
}
char* Platform_getProcessEnv(pid_t pid) {
@@ -215,15 +228,9 @@ char* Platform_getProcessEnv(pid_t pid) {
return NULL;
}
-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;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -232,15 +239,9 @@ bool Platform_getDiskIO(DiskIOData* data) {
return false;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
+bool Platform_getNetworkIO(NetworkIOData* data) {
// TODO
- *bytesReceived = 0;
- *packetsReceived = 0;
- *bytesTransmitted = 0;
- *packetsTransmitted = 0;
+ (void)data;
return false;
}
diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h
index 5456539..cf69356 100644
--- a/dragonflybsd/Platform.h
+++ b/dragonflybsd/Platform.h
@@ -4,24 +4,34 @@
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.
*/
#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
#include <sys/types.h>
#include "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "Macros.h"
+#include "Meter.h"
+#include "NetworkIOMeter.h"
+#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
-extern ProcessFieldData Process_fields[];
-extern ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
-extern int Platform_numberOfFields;
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -29,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);
@@ -41,7 +51,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* this, int cpu);
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
void Platform_setMemoryValues(Meter* this);
@@ -49,17 +59,62 @@ 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);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
void Platform_getBattery(double* percent, ACPresence* isOnAC);
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+static inline 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) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+static inline void Platform_gettime_monotonic(uint64_t* msec) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
+
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
+
#endif
diff --git a/dragonflybsd/ProcessField.h b/dragonflybsd/ProcessField.h
new file mode 100644
index 0000000..1409675
--- /dev/null
+++ b/dragonflybsd/ProcessField.h
@@ -0,0 +1,19 @@
+#ifndef HEADER_DragonFlyBSDProcessField
+#define HEADER_DragonFlyBSDProcessField
+/*
+htop - dragonflybsd/ProcessField.h
+(C) 2020 htop dev team
+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, \
+ \
+ DUMMY_BUMP_FIELD = CWD, \
+ // End of list
+
+
+#endif /* HEADER_DragonFlyBSDProcessField */
diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c
index d6d67b7..4eccfe7 100644
--- a/freebsd/FreeBSDProcess.c
+++ b/freebsd/FreeBSDProcess.c
@@ -1,11 +1,11 @@
/*
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 "FreeBSDProcess.h"
+#include "freebsd/FreeBSDProcess.h"
#include <stdlib.h>
@@ -18,47 +18,38 @@ in the source distribution for its full text.
const char* const nodevStr = "nodev";
-ProcessFieldData Process_fields[] = {
+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, },
+ [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, },
[COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
[STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)", .flags = 0, },
- [PPID] = { .name = "PPID", .title = " PPID ", .description = "Parent process ID", .flags = 0, },
- [PGRP] = { .name = "PGRP", .title = " PGRP ", .description = "Process group ID", .flags = 0, },
- [SESSION] = { .name = "SESSION", .title = " SID ", .description = "Process's session ID", .flags = 0, },
- [TTY_NR] = { .name = "TTY_NR", .title = " TTY ", .description = "Controlling terminal", .flags = PROCESS_FLAG_FREEBSD_TTY, },
- [TPGID] = { .name = "TPGID", .title = " TPGID ", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, },
- [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
+ [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, },
+ [TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, .pidColumn = true, },
+ [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of copy-on-write faults", .flags = 0, .defaultSortDesc = true, },
[PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
[NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
[STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
-
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, },
- [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
- [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, },
- [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
- [TGID] = { .name = "TGID", .title = " TGID ", .description = "Thread group ID (i.e. process ID)", .flags = 0, },
- [JID] = { .name = "JID", .title = " JID ", .description = "Jail prison ID", .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, .autoWidth = true, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
+ [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
+ [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
+ [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process", .flags = 0, },
+ [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, },
+ [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
+ [JID] = { .name = "JID", .title = "JID", .description = "Jail prison ID", .flags = 0, .pidColumn = true, },
[JAIL] = { .name = "JAIL", .title = "JAIL ", .description = "Jail prison name", .flags = 0, },
- [LAST_PROCESSFIELD] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, },
-};
-
-ProcessPidColumn Process_pidColumns[] = {
- { .id = JID, .label = "JID" },
- { .id = PID, .label = "PID" },
- { .id = PPID, .label = "PPID" },
- { .id = TPGID, .label = "TPGID" },
- { .id = TGID, .label = "TGID" },
- { .id = PGRP, .label = "PGRP" },
- { .id = SESSION, .label = "SID" },
- { .id = 0, .label = NULL },
+ [EMULATION] = { .name = "EMULATION", .title = "EMULATION ", .description = "System call emulation environment (ABI)", .flags = 0, },
};
Process* FreeBSDProcess_new(const Settings* settings) {
@@ -71,75 +62,47 @@ Process* FreeBSDProcess_new(const Settings* settings) {
void Process_delete(Object* cast) {
FreeBSDProcess* this = (FreeBSDProcess*) cast;
Process_done((Process*)cast);
+ free(this->emul);
free(this->jname);
free(this);
}
static void FreeBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
const FreeBSDProcess* fp = (const FreeBSDProcess*) this;
- char buffer[256]; buffer[255] = '\0';
+ char buffer[256];
+ size_t n = sizeof(buffer);
int attr = CRT_colors[DEFAULT_COLOR];
- int n = sizeof(buffer) - 1;
- switch ((int) field) {
+
+ switch (field) {
// add FreeBSD-specific fields here
- case JID: xSnprintf(buffer, n, Process_pidFormat, fp->jid); break;
- case JAIL: {
- xSnprintf(buffer, n, "%-11s ", fp->jname);
- if (buffer[11] != '\0') {
- buffer[11] = ' ';
- buffer[12] = '\0';
- }
- break;
- }
- case TTY_NR:
- if (fp->ttyPath) {
- if (fp->ttyPath == nodevStr)
- attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "%-8s", fp->ttyPath);
- } else {
- attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "? ");
- }
- break;
+ case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break;
+ case JAIL:
+ Process_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11);
+ return;
+ case EMULATION:
+ Process_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16);
+ return;
default:
Process_writeField(this, str, field);
return;
}
- RichString_append(str, attr, buffer);
+ RichString_appendWide(str, attr, buffer);
}
-static long FreeBSDProcess_compare(const void* v1, const void* v2) {
- const FreeBSDProcess *p1, *p2;
- const Settings *settings = ((const Process*)v1)->settings;
+static int FreeBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const FreeBSDProcess* p1 = (const FreeBSDProcess*)v1;
+ const FreeBSDProcess* p2 = (const FreeBSDProcess*)v2;
- if (settings->direction == 1) {
- p1 = (const FreeBSDProcess*)v1;
- p2 = (const FreeBSDProcess*)v2;
- } else {
- p2 = (const FreeBSDProcess*)v1;
- p1 = (const FreeBSDProcess*)v2;
- }
-
- switch ((int) settings->sortKey) {
+ switch (key) {
// add FreeBSD-specific fields here
case JID:
return SPACESHIP_NUMBER(p1->jid, p2->jid);
case JAIL:
return SPACESHIP_NULLSTR(p1->jname, p2->jname);
- case TTY_NR:
- return SPACESHIP_NULLSTR(p1->ttyPath, p2->ttyPath);
+ case EMULATION:
+ return SPACESHIP_NULLSTR(p1->emul, p2->emul);
default:
- return Process_compare(v1, v2);
- }
-}
-
-bool Process_isThread(const Process* this) {
- const FreeBSDProcess* fp = (const FreeBSDProcess*) this;
-
- if (fp->kernel == 1 ) {
- return 1;
- } else {
- return Process_isUserlandThread(this);
+ return Process_compareByKey_Base(v1, v2, key);
}
}
@@ -148,7 +111,8 @@ const ProcessClass FreeBSDProcess_class = {
.extends = Class(Process),
.display = Process_display,
.delete = Process_delete,
- .compare = FreeBSDProcess_compare
+ .compare = Process_compare
},
.writeField = FreeBSDProcess_writeField,
+ .compareByKey = FreeBSDProcess_compareByKey
};
diff --git a/freebsd/FreeBSDProcess.h b/freebsd/FreeBSDProcess.h
index 8911976..654f9cf 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.
*/
@@ -14,43 +14,19 @@ in the source distribution for its full text.
#include "Settings.h"
-#define PROCESS_FLAG_FREEBSD_TTY 0x0100
-
-extern const char* const nodevStr;
-
-typedef enum FreeBSDProcessFields_ {
- // Add platform-specific fields here, with ids >= 100
- JID = 100,
- JAIL = 101,
- LAST_PROCESSFIELD = 102,
-} FreeBSDProcessField;
-
typedef struct FreeBSDProcess_ {
Process super;
- int kernel;
int jid;
char* jname;
- const char* ttyPath;
+ char* emul;
} FreeBSDProcess;
-static inline bool Process_isKernelThread(const Process* this) {
- return ((const FreeBSDProcess*)this)->kernel == 1;
-}
-
-static inline bool Process_isUserlandThread(const Process* this) {
- return this->pid != this->tgid;
-}
-
extern const ProcessClass FreeBSDProcess_class;
-extern ProcessFieldData Process_fields[];
-
-extern ProcessPidColumn Process_pidColumns[];
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
Process* FreeBSDProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-bool Process_isThread(const Process* this);
-
#endif
diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessList.c
index 9aaab5d..2c7e20b 100644
--- a/freebsd/FreeBSDProcessList.c
+++ b/freebsd/FreeBSDProcessList.c
@@ -1,31 +1,31 @@
/*
htop - FreeBSDProcessList.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 "FreeBSDProcessList.h"
+#include "config.h" // IWYU pragma: keep
+
+#include "freebsd/FreeBSDProcessList.h"
#include <assert.h>
-#include <dirent.h>
-#include <err.h>
#include <limits.h>
+#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <sys/_iovec.h>
-#include <sys/dirent.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/stat.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"
@@ -36,11 +36,9 @@ in the source distribution for its full text.
#include "ProcessList.h"
#include "Settings.h"
#include "XUtils.h"
+#include "generic/openzfs_sysctl.h"
#include "zfs/ZfsArcStats.h"
-#include "zfs/openzfs_sysctl.h"
-
-char jail_errmsg[JAIL_ERRMSGLEN];
static int MIB_hw_physmem[2];
static int MIB_vm_stats_vm_v_page_count[4];
@@ -52,6 +50,7 @@ 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];
@@ -59,12 +58,12 @@ static int MIB_kern_cp_time[2];
static int MIB_kern_cp_times[2];
static int kernelFScale;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* DynamicColumns, Hashtable* pidMatchList, uid_t userId) {
size_t len;
char errbuf[_POSIX2_LINE_MAX];
FreeBSDProcessList* fpl = xCalloc(1, sizeof(FreeBSDProcessList));
ProcessList* pl = (ProcessList*) fpl;
- ProcessList_init(pl, Class(FreeBSDProcess), usersTable, pidMatchList, userId);
+ ProcessList_init(pl, Class(FreeBSDProcess), usersTable, dynamicMeters, DynamicColumns, pidMatchList, userId);
// physical memory in system: hw.physmem
// physical page size: hw.pagesize
@@ -72,12 +71,9 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
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) {
- pageSize = CRT_pageSize;
- pageSizeKb = CRT_pageSize;
- } else {
- pageSizeKb = pageSize / ONE_K;
- }
+ 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
@@ -88,6 +84,7 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
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);
@@ -115,8 +112,8 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
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);
+ fpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
+ fpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
len = sizeof_cp_time_array;
// fetch initial single (or average) CPU clicks from kernel
@@ -131,13 +128,15 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
sysctl(MIB_kern_cp_times, 2, fpl->cp_times_o, &len, NULL, 0);
}
- pl->cpuCount = MAXIMUM(cpus, 1);
+ pl->existingCPUs = MAXIMUM(cpus, 1);
+ // TODO: support offline CPUs and hot swapping
+ pl->activeCPUs = pl->existingCPUs;
if (cpus == 1 ) {
fpl->cpus = xRealloc(fpl->cpus, sizeof(CPUData));
} else {
// on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)
- fpl->cpus = xRealloc(fpl->cpus, (pl->cpuCount + 1) * sizeof(CPUData));
+ fpl->cpus = xRealloc(fpl->cpus, (pl->existingCPUs + 1) * sizeof(CPUData));
}
@@ -149,19 +148,15 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
fpl->kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf);
if (fpl->kd == NULL) {
- errx(1, "kvm_open: %s", errbuf);
+ CRT_fatalError("kvm_openfiles() failed");
}
- fpl->ttys = Hashtable_new(20, true);
-
return pl;
}
void ProcessList_delete(ProcessList* this) {
const FreeBSDProcessList* fpl = (FreeBSDProcessList*) this;
- Hashtable_delete(fpl->ttys);
-
if (fpl->kd) {
kvm_close(fpl->kd);
}
@@ -176,11 +171,11 @@ void ProcessList_delete(ProcessList* this) {
free(this);
}
-static inline void FreeBSDProcessList_scanCPUTime(ProcessList* pl) {
+static inline void FreeBSDProcessList_scanCPU(ProcessList* pl) {
const FreeBSDProcessList* fpl = (FreeBSDProcessList*) pl;
- int cpus = pl->cpuCount; // actual CPU count
- int maxcpu = cpus; // max iteration (in case we have average + smp)
+ unsigned int cpus = pl->existingCPUs; // actual CPU count
+ unsigned int maxcpu = cpus; // max iteration (in case we have average + smp)
int cp_times_offset;
assert(cpus > 0);
@@ -207,7 +202,7 @@ static inline void FreeBSDProcessList_scanCPUTime(ProcessList* pl) {
sysctl(MIB_kern_cp_times, 2, fpl->cp_times_n, &sizeof_cp_time_array, NULL, 0);
}
- for (int i = 0; i < maxcpu; i++) {
+ for (unsigned int i = 0; i < maxcpu; i++) {
if (cpus == 1) {
// single CPU box
cp_time_n = fpl->cp_time_n;
@@ -253,8 +248,69 @@ static inline void FreeBSDProcessList_scanCPUTime(ProcessList* pl) {
cpuData->systemPercent = cp_time_p[CP_SYS];
cpuData->irqPercent = cp_time_p[CP_INTR];
cpuData->systemAllPercent = cp_time_p[CP_SYS] + cp_time_p[CP_INTR];
- // this one is not really used, but we store it anyway
- cpuData->idlePercent = cp_time_p[CP_IDLE];
+ // this one is not really used
+ //cpuData->idlePercent = cp_time_p[CP_IDLE];
+
+ cpuData->temperature = NAN;
+ cpuData->frequency = NAN;
+
+ const int coreId = (cpus == 1) ? 0 : ((int)i - 1);
+ if (coreId < 0)
+ continue;
+
+ // TODO: test with hyperthreading and multi-cpu systems
+ if (pl->settings->showCPUTemperature) {
+ int temperature;
+ size_t len = sizeof(temperature);
+ char mibBuffer[32];
+ xSnprintf(mibBuffer, sizeof(mibBuffer), "dev.cpu.%d.temperature", coreId);
+ int r = sysctlbyname(mibBuffer, &temperature, &len, NULL, 0);
+ if (r == 0)
+ cpuData->temperature = (double)(temperature - 2732) / 10.0; // convert from deci-Kelvin to Celsius
+ }
+
+ // TODO: test with hyperthreading and multi-cpu systems
+ if (pl->settings->showCPUFrequency) {
+ int frequency;
+ size_t len = sizeof(frequency);
+ char mibBuffer[32];
+ xSnprintf(mibBuffer, sizeof(mibBuffer), "dev.cpu.%d.freq", coreId);
+ int r = sysctlbyname(mibBuffer, &frequency, &len, NULL, 0);
+ if (r == 0)
+ cpuData->frequency = frequency; // keep in MHz
+ }
+ }
+
+ // calculate max temperature and avg frequency for average meter and
+ // propagate frequency to all cores if only supplied for CPU 0
+ if (cpus > 1) {
+ if (pl->settings->showCPUTemperature) {
+ double maxTemp = NAN;
+ for (unsigned int i = 1; i < maxcpu; i++) {
+ const double coreTemp = fpl->cpus[i].temperature;
+ if (isnan(coreTemp))
+ continue;
+
+ maxTemp = MAXIMUM(maxTemp, coreTemp);
+ }
+
+ fpl->cpus[0].temperature = maxTemp;
+ }
+
+ if (pl->settings->showCPUFrequency) {
+ const double coreZeroFreq = fpl->cpus[1].frequency;
+ double freqSum = coreZeroFreq;
+ if (!isnan(coreZeroFreq)) {
+ for (unsigned int i = 2; i < maxcpu; i++) {
+ if (isnan(fpl->cpus[i].frequency))
+ fpl->cpus[i].frequency = coreZeroFreq;
+
+ freqSum += fpl->cpus[i].frequency;
+ }
+
+ fpl->cpus[0].frequency = freqSum / (maxcpu - 1);
+ }
+ }
}
}
@@ -277,6 +333,7 @@ static inline void FreeBSDProcessList_scanMemoryInfo(ProcessList* pl) {
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
@@ -307,6 +364,10 @@ static inline void FreeBSDProcessList_scanMemoryInfo(ProcessList* pl) {
cachedMem *= pageSizeKb;
pl->cachedMem = cachedMem;
+ len = sizeof(vmtotal);
+ sysctl(MIB_vm_vmtotal, 2, &(vmtotal), &len, NULL, 0);
+ pl->sharedMem = vmtotal.t_vmshr * pageSizeKb;
+
if (fpl->zfs.enabled) {
fpl->memWire -= fpl->zfs.size;
pl->cachedMem += fpl->zfs.size;
@@ -326,132 +387,102 @@ static inline void FreeBSDProcessList_scanMemoryInfo(ProcessList* pl) {
pl->usedSwap *= pageSizeKb;
}
-static void FreeBSDProcessList_scanTTYs(ProcessList* pl) {
- FreeBSDProcessList* fpl = (FreeBSDProcessList*) pl;
-
- // scan /dev/tty*
- {
- DIR* dirPtr = opendir("/dev");
- if (!dirPtr)
- return;
-
- int dirFd = dirfd(dirPtr);
- if (dirFd < 0)
- goto err1;
-
- const struct dirent* entry;
- while ((entry = readdir(dirPtr))) {
- if (!String_startsWith(entry->d_name, "tty"))
- continue;
-
- struct stat info;
- if (Compat_fstatat(dirFd, "/dev", entry->d_name, &info, 0) < 0)
- continue;
+static void FreeBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) {
+ if (Process_isKernelThread(proc)) {
+ Process_updateExe(proc, NULL);
+ return;
+ }
- if (!S_ISCHR(info.st_mode))
- continue;
+ const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, kproc->ki_pid };
+ char buffer[2048];
+ size_t size = sizeof(buffer);
+ if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+ Process_updateExe(proc, NULL);
+ return;
+ }
- if (!Hashtable_get(fpl->ttys, info.st_rdev))
- Hashtable_put(fpl->ttys, info.st_rdev, xStrdup(entry->d_name));
- }
+ Process_updateExe(proc, buffer);
+}
-err1:
- closedir(dirPtr);
+static void FreeBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
+#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;
}
- // scan /dev/pts/*
- {
- DIR* dirPtr = opendir("/dev/pts");
- if (!dirPtr)
- return;
-
- int dirFd = dirfd(dirPtr);
- if (dirFd < 0)
- goto err2;
-
- const struct dirent* entry;
- while ((entry = readdir(dirPtr))) {
- struct stat info;
- if (Compat_fstatat(dirFd, "/dev/pts", entry->d_name, &info, 0) < 0)
- continue;
-
- if (!S_ISCHR(info.st_mode))
- continue;
-
- if (!Hashtable_get(fpl->ttys, info.st_rdev)) {
- char* path;
- xAsprintf(&path, "pts/%s", entry->d_name);
- Hashtable_put(fpl->ttys, info.st_rdev, path);
- }
- }
-
-err2:
- closedir(dirPtr);
+ /* Kernel threads return an empty buffer */
+ if (buffer[0] == '\0') {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
}
+
+ free_and_xStrdup(&proc->procCwd, buffer);
+#else
+ proc->procCwd = NULL;
+#endif
}
-static char* FreeBSDProcessList_readProcessName(kvm_t* kd, const struct kinfo_proc* kproc, int* basenameEnd) {
+static void FreeBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
+ Process_updateComm(proc, kproc->ki_comm);
+
char** argv = kvm_getargv(kd, kproc, 0);
- if (!argv) {
- return xStrdup(kproc->ki_comm);
+ if (!argv || !argv[0]) {
+ Process_updateCmdline(proc, kproc->ki_comm, 0, strlen(kproc->ki_comm));
+ return;
}
- int len = 0;
+
+ size_t len = 0;
for (int i = 0; argv[i]; i++) {
len += strlen(argv[i]) + 1;
}
- char* comm = xMalloc(len);
- char* at = comm;
- *basenameEnd = 0;
+
+ char* cmdline = xMalloc(len);
+ char* at = cmdline;
+ int end = 0;
for (int i = 0; argv[i]; i++) {
at = stpcpy(at, argv[i]);
- if (!*basenameEnd) {
- *basenameEnd = at - comm;
+ if (end == 0) {
+ end = at - cmdline;
}
- *at = ' ';
- at++;
+ *at++ = ' ';
}
at--;
*at = '\0';
- return comm;
+
+ Process_updateCmdline(proc, cmdline, 0, end);
+
+ free(cmdline);
}
static char* FreeBSDProcessList_readJailName(const struct kinfo_proc* kproc) {
- char* jname = NULL;
- char jnamebuf[MAXHOSTNAMELEN];
+ if (kproc->ki_jid == 0)
+ return xStrdup("-");
- if (kproc->ki_jid != 0 ) {
- struct iovec jiov[6];
+ char jnamebuf[MAXHOSTNAMELEN] = {0};
+ struct iovec jiov[4];
- memset(jnamebuf, 0, sizeof(jnamebuf));
IGNORE_WCASTQUAL_BEGIN
- *(const void**)&jiov[0].iov_base = "jid";
- jiov[0].iov_len = sizeof("jid");
- jiov[1].iov_base = (void*) &kproc->ki_jid;
- jiov[1].iov_len = sizeof(kproc->ki_jid);
- *(const void**)&jiov[2].iov_base = "name";
- jiov[2].iov_len = sizeof("name");
- jiov[3].iov_base = jnamebuf;
- jiov[3].iov_len = sizeof(jnamebuf);
- *(const void**)&jiov[4].iov_base = "errmsg";
- jiov[4].iov_len = sizeof("errmsg");
- jiov[5].iov_base = jail_errmsg;
- jiov[5].iov_len = JAIL_ERRMSGLEN;
+ *(const void**)&jiov[0].iov_base = "jid";
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = (void*) &kproc->ki_jid;
+ jiov[1].iov_len = sizeof(kproc->ki_jid);
+ *(const void**)&jiov[2].iov_base = "name";
+ jiov[2].iov_len = sizeof("name");
+ jiov[3].iov_base = jnamebuf;
+ jiov[3].iov_len = sizeof(jnamebuf);
IGNORE_WCASTQUAL_END
- jail_errmsg[0] = 0;
- int jid = jail_get(jiov, 6, 0);
- if (jid < 0) {
- if (!jail_errmsg[0]) {
- xSnprintf(jail_errmsg, JAIL_ERRMSGLEN, "jail_get: %s", strerror(errno));
- }
- } else if (jid == kproc->ki_jid) {
- jname = xStrdup(jnamebuf);
- }
- } else {
- jname = xStrdup("-");
- }
+ int jid = jail_get(jiov, 4, 0);
+ if (jid == kproc->ki_jid)
+ return xStrdup(jnamebuf);
- return jname;
+ return NULL;
}
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
@@ -462,37 +493,27 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
openzfs_sysctl_updateArcStats(&fpl->zfs);
FreeBSDProcessList_scanMemoryInfo(super);
- FreeBSDProcessList_scanCPUTime(super);
+ FreeBSDProcessList_scanCPU(super);
// in pause mode only gather global data for meters (CPU/memory/...)
if (pauseProcessUpdate) {
return;
}
- if (settings->flags & PROCESS_FLAG_FREEBSD_TTY) {
- FreeBSDProcessList_scanTTYs(super);
- }
-
int count = 0;
- struct kinfo_proc* kprocs = kvm_getprocs(fpl->kd, KERN_PROC_PROC, 0, &count);
+ const struct kinfo_proc* kprocs = kvm_getprocs(fpl->kd, KERN_PROC_PROC, 0, &count);
for (int i = 0; i < count; i++) {
- struct kinfo_proc* kproc = &kprocs[i];
+ const struct kinfo_proc* kproc = &kprocs[i];
bool preExisting = false;
- // TODO: bool isIdleProcess = false;
Process* proc = ProcessList_getProcess(super, kproc->ki_pid, &preExisting, FreeBSDProcess_new);
FreeBSDProcess* fp = (FreeBSDProcess*) proc;
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
if (!preExisting) {
fp->jid = kproc->ki_jid;
proc->pid = kproc->ki_pid;
- if ( ! ((kproc->ki_pid == 0) || (kproc->ki_pid == 1) ) && kproc->ki_flag & P_SYSTEM) {
- fp->kernel = 1;
- } else {
- fp->kernel = 0;
- }
+ proc->isKernelThread = kproc->ki_pid != 1 && (kproc->ki_flag & P_SYSTEM);
+ proc->isUserlandThread = false;
proc->ppid = kproc->ki_ppid;
proc->tpgid = kproc->ki_tpgid;
proc->tgid = kproc->ki_pid;
@@ -500,11 +521,30 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc->pgrp = kproc->ki_pgid;
proc->st_uid = kproc->ki_uid;
proc->starttime_ctime = kproc->ki_start.tv_sec;
+ if (proc->starttime_ctime < 0) {
+ proc->starttime_ctime = super->realtimeMs / 1000;
+ }
Process_fillStarttimeBuffer(proc);
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
ProcessList_add(super, proc);
- proc->comm = FreeBSDProcessList_readProcessName(fpl->kd, kproc, &proc->basenameOffset);
+
+ FreeBSDProcessList_updateExe(kproc, proc);
+ FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ FreeBSDProcessList_updateCwd(kproc, proc);
+ }
+
fp->jname = FreeBSDProcessList_readJailName(kproc);
+
+ proc->tty_nr = kproc->ki_tdev;
+ const char* name = (kproc->ki_tdev != NODEV) ? devname(kproc->ki_tdev, S_IFCHR) : NULL;
+ if (!name) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
} else {
if (fp->jid != kproc->ki_jid) {
// process can enter jail anytime
@@ -520,33 +560,33 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
}
if (settings->updateProcessNames) {
- free(proc->comm);
- proc->comm = FreeBSDProcessList_readProcessName(fpl->kd, kproc, &proc->basenameOffset);
+ FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
}
}
+ free_and_xStrdup(&fp->emul, kproc->ki_emul);
+
// from FreeBSD source /src/usr.bin/top/machine.c
- proc->m_virt = kproc->ki_size / pageSize;
- proc->m_resident = kproc->ki_rssize;
+ 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 * pageSizeKb) / (double)(super->totalMem);
-
- /*
- * TODO
- * if (proc->percent_cpu > 0.1) {
- * // system idle process should own all CPU time left regardless of CPU count
- * if ( strcmp("idle", kproc->ki_comm) == 0 ) {
- * isIdleProcess = true;
- * }
- * }
- */
+ proc->percent_mem = 100.0 * proc->m_resident / (double)(super->totalMem);
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ if (kproc->ki_stat == SRUN && kproc->ki_oncpu != NOCPU) {
+ proc->processor = kproc->ki_oncpu;
+ } else {
+ proc->processor = kproc->ki_lastcpu;
+ }
+
+ proc->majflt = kproc->ki_cow;
proc->priority = kproc->ki_pri.pri_level - PZERO;
- if (strcmp("intr", kproc->ki_comm) == 0 && kproc->ki_flag & P_SYSTEM) {
+ if (String_eq("intr", kproc->ki_comm) && (kproc->ki_flag & P_SYSTEM)) {
proc->nice = 0; //@etosan: intr kernel process (not thread) has weird nice value
} else if (kproc->ki_pri.pri_class == PRI_TIMESHARE) {
proc->nice = kproc->ki_nice - NZERO;
@@ -556,27 +596,35 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
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 = '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 (settings->flags & PROCESS_FLAG_FREEBSD_TTY) {
- fp->ttyPath = (kproc->ki_tdev == NODEV) ? nodevStr : Hashtable_get(fpl->ttys, kproc->ki_tdev);
+ case SIDL: proc->state = IDLE; break;
+ case SRUN: proc->state = RUNNING; break;
+ case SSLEEP: proc->state = SLEEPING; break;
+ case SSTOP: proc->state = STOPPED; break;
+ case SZOMB: proc->state = ZOMBIE; break;
+ case SWAIT: proc->state = WAITING; break;
+ case SLOCK: proc->state = BLOCKED; break;
+ default: proc->state = UNKNOWN;
}
if (Process_isKernelThread(proc))
super->kernelThreads++;
+ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+
super->totalTasks++;
- if (proc->state == 'R')
+ if (proc->state == RUNNING)
super->runningTasks++;
proc->updated = true;
}
}
+
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ // TODO: support offline CPUs and hot swapping
+ (void) super; (void) id;
+
+ return true;
+}
diff --git a/freebsd/FreeBSDProcessList.h b/freebsd/FreeBSDProcessList.h
index f18275d..adc70e4 100644
--- a/freebsd/FreeBSDProcessList.h
+++ b/freebsd/FreeBSDProcessList.h
@@ -3,7 +3,7 @@
/*
htop - FreeBSDProcessList.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.
*/
@@ -17,16 +17,15 @@ in the source distribution for its full text.
#include "zfs/ZfsArcStats.h"
-#define JAIL_ERRMSGLEN 1024
-extern char jail_errmsg[JAIL_ERRMSGLEN];
-
typedef struct CPUData_ {
double userPercent;
double nicePercent;
double systemPercent;
double irqPercent;
- double idlePercent;
double systemAllPercent;
+
+ double frequency;
+ double temperature;
} CPUData;
typedef struct FreeBSDProcessList_ {
@@ -35,15 +34,11 @@ typedef struct FreeBSDProcessList_ {
unsigned long long int memWire;
unsigned long long int memActive;
- unsigned long long int memInactive;
- unsigned long long int memFree;
ZfsArcStats zfs;
CPUData* cpus;
- Hashtable* ttys;
-
unsigned long* cp_time_o;
unsigned long* cp_time_n;
@@ -52,10 +47,12 @@ typedef struct FreeBSDProcessList_ {
} FreeBSDProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* this);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/freebsd/Platform.c b/freebsd/Platform.c
index bc77cf4..646163a 100644
--- a/freebsd/Platform.c
+++ b/freebsd/Platform.c
@@ -1,11 +1,13 @@
/*
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.
*/
-#include "Platform.h"
+#include "config.h" // IWYU pragma: keep
+
+#include "freebsd/Platform.h"
#include <devstat.h>
#include <math.h>
@@ -29,27 +31,34 @@ in the source distribution for its full text.
#include "DateMeter.h"
#include "DateTimeMeter.h"
#include "DiskIOMeter.h"
-#include "FreeBSDProcess.h"
-#include "FreeBSDProcessList.h"
#include "HostnameMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
#include "Meter.h"
#include "NetworkIOMeter.h"
#include "ProcessList.h"
#include "Settings.h"
#include "SwapMeter.h"
+#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
#include "XUtils.h"
+#include "freebsd/FreeBSDProcess.h"
+#include "freebsd/FreeBSDProcessList.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsCompressedArcMeter.h"
+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",
+ },
+};
-ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
-
-int Platform_numberOfFields = LAST_PROCESSFIELD;
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
@@ -99,10 +108,12 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
+ &MemorySwapMeter_class,
&TasksMeter_class,
&UptimeMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
&AllCPUs4Meter_class,
@@ -123,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) {
@@ -136,9 +148,9 @@ 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 };
+ const int mib[2] = { CTL_KERN, KERN_BOOTTIME };
size_t size = sizeof(bootTime);
int err = sysctl(mib, 2, &bootTime, &size, NULL, 0);
@@ -152,7 +164,7 @@ int Platform_getUptime() {
void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
struct loadavg loadAverage;
- int mib[2] = { CTL_VM, VM_LOADAVG };
+ const int mib[2] = { CTL_VM, VM_LOADAVG };
size_t size = sizeof(loadAverage);
int err = sysctl(mib, 2, &loadAverage, &size, NULL, 0);
@@ -167,7 +179,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;
}
-int Platform_getMaxPid() {
+int Platform_getMaxPid(void) {
int maxPid;
size_t size = sizeof(maxPid);
int err = sysctlbyname("kern.pid_max", &maxPid, &size, NULL, 0);
@@ -177,9 +189,9 @@ int Platform_getMaxPid() {
return maxPid;
}
-double Platform_setCPUValues(Meter* this, int cpu) {
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) this->pl;
- int cpus = this->pl->cpuCount;
+ unsigned int cpus = this->pl->activeCPUs;
const CPUData* cpuData;
if (cpus == 1) {
@@ -207,26 +219,39 @@ double Platform_setCPUValues(Meter* this, int cpu) {
percent = CLAMP(percent, 0.0, 100.0);
- v[CPU_METER_FREQUENCY] = NAN;
- v[CPU_METER_TEMPERATURE] = NAN;
+ v[CPU_METER_FREQUENCY] = cpuData->frequency;
+ v[CPU_METER_TEMPERATURE] = cpuData->temperature;
return percent;
}
void Platform_setMemoryValues(Meter* this) {
- // TODO
const ProcessList* pl = this->pl;
+ const FreeBSDProcessList* fpl = (const FreeBSDProcessList*) pl;
this->total = pl->totalMem;
- this->values[0] = pl->usedMem;
- this->values[1] = pl->buffersMem;
- this->values[2] = pl->cachedMem;
+ this->values[MEMORY_METER_USED] = pl->usedMem;
+ this->values[MEMORY_METER_BUFFERS] = pl->buffersMem;
+ this->values[MEMORY_METER_SHARED] = pl->sharedMem;
+ this->values[MEMORY_METER_CACHE] = pl->cachedMem;
+ // this->values[MEMORY_METER_AVAILABLE] = "available memory"
+
+ if (fpl->zfs.enabled) {
+ // ZFS does not shrink below the value of zfs_arc_min.
+ unsigned long long int shrinkableSize = 0;
+ if (fpl->zfs.size > fpl->zfs.min)
+ shrinkableSize = fpl->zfs.size - fpl->zfs.min;
+ this->values[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[SWAP_METER_USED] = pl->usedSwap;
+ this->values[SWAP_METER_CACHE] = NAN;
}
void Platform_setZfsArcValues(Meter* this) {
@@ -242,7 +267,7 @@ void Platform_setZfsCompressedArcValues(Meter* this) {
}
char* Platform_getProcessEnv(pid_t pid) {
- int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ENV, pid };
+ const int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ENV, pid };
size_t capacity = ARG_MAX;
char* env = xMalloc(capacity);
@@ -262,15 +287,9 @@ char* Platform_getProcessEnv(pid_t pid) {
return env;
}
-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;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -278,7 +297,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
@@ -287,7 +307,7 @@ bool Platform_getDiskIO(DiskIOData* data) {
int count = current.dinfo->numdevs;
- unsigned long int bytesReadSum = 0, bytesWriteSum = 0, timeSpendSum = 0;
+ unsigned long long int bytesReadSum = 0, bytesWriteSum = 0, timeSpendSum = 0;
// get data
for (int i = 0; i < count; i++) {
@@ -313,24 +333,17 @@ bool Platform_getDiskIO(DiskIOData* data) {
return true;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
- int r;
-
+bool Platform_getNetworkIO(NetworkIOData* data) {
// get number of interfaces
int count;
size_t countLen = sizeof(count);
const int countMib[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_SYSTEM, IFMIB_IFCOUNT };
- r = sysctl(countMib, ARRAYSIZE(countMib), &count, &countLen, NULL, 0);
+ int r = sysctl(countMib, ARRAYSIZE(countMib), &count, &countLen, NULL, 0);
if (r < 0)
return false;
-
- unsigned long int bytesReceivedSum = 0, packetsReceivedSum = 0, bytesTransmittedSum = 0, packetsTransmittedSum = 0;
-
+ memset(data, 0, sizeof(NetworkIOData));
for (int i = 1; i <= count; i++) {
struct ifmibdata ifmd;
size_t ifmdLen = sizeof(ifmd);
@@ -344,16 +357,12 @@ bool Platform_getNetworkIO(unsigned long int* bytesReceived,
if (ifmd.ifmd_flags & IFF_LOOPBACK)
continue;
- bytesReceivedSum += ifmd.ifmd_data.ifi_ibytes;
- packetsReceivedSum += ifmd.ifmd_data.ifi_ipackets;
- bytesTransmittedSum += ifmd.ifmd_data.ifi_obytes;
- packetsTransmittedSum += ifmd.ifmd_data.ifi_opackets;
+ data->bytesReceived += ifmd.ifmd_data.ifi_ibytes;
+ data->packetsReceived += ifmd.ifmd_data.ifi_ipackets;
+ data->bytesTransmitted += ifmd.ifmd_data.ifi_obytes;
+ data->packetsTransmitted += ifmd.ifmd_data.ifi_opackets;
}
- *bytesReceived = bytesReceivedSum;
- *packetsReceived = packetsReceivedSum;
- *bytesTransmitted = bytesTransmittedSum;
- *packetsTransmitted = packetsTransmittedSum;
return true;
}
diff --git a/freebsd/Platform.h b/freebsd/Platform.h
index 5b3b019..51269c0 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.
*/
@@ -13,17 +13,21 @@ in the source distribution for its full text.
#include "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
#include "Meter.h"
+#include "NetworkIOMeter.h"
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
-extern ProcessFieldData Process_fields[];
+extern const ScreenDefaults Platform_defaultScreens[];
-extern ProcessField Platform_defaultFields[];
-
-extern int Platform_numberOfFields;
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -31,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);
@@ -43,7 +47,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* this, int cpu);
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
void Platform_setMemoryValues(Meter* this);
@@ -55,17 +59,62 @@ 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);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
void Platform_getBattery(double* percent, ACPresence* isOnAC);
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+static inline 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) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+static inline void Platform_gettime_monotonic(uint64_t* msec) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
+
#endif
diff --git a/freebsd/ProcessField.h b/freebsd/ProcessField.h
new file mode 100644
index 0000000..d20900d
--- /dev/null
+++ b/freebsd/ProcessField.h
@@ -0,0 +1,20 @@
+#ifndef HEADER_FreeBSDProcessField
+#define HEADER_FreeBSDProcessField
+/*
+htop - freebsd/ProcessField.h
+(C) 2020 htop dev team
+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, \
+ EMULATION = 102, \
+ \
+ DUMMY_BUMP_FIELD = CWD, \
+ // End of list
+
+
+#endif /* HEADER_FreeBSDProcessField */
diff --git a/generic/gettime.c b/generic/gettime.c
new file mode 100644
index 0000000..b7c4885
--- /dev/null
+++ b/generic/gettime.c
@@ -0,0 +1,61 @@
+/*
+htop - generic/gettime.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+#include "config.h" // IWYU pragma: keep
+
+#include "generic/gettime.h"
+
+#include <string.h>
+#include <time.h>
+
+
+void Generic_gettime_realtime(struct timeval* tvp, uint64_t* msec) {
+
+#if defined(HAVE_CLOCK_GETTIME)
+
+ struct timespec ts;
+ if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
+ tvp->tv_sec = ts.tv_sec;
+ tvp->tv_usec = ts.tv_nsec / 1000;
+ *msec = ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000);
+ } else {
+ memset(tvp, 0, sizeof(struct timeval));
+ *msec = 0;
+ }
+
+#else /* lower resolution gettimeofday(2) is always available */
+
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) == 0) {
+ *tvp = tv; /* struct copy */
+ *msec = ((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000);
+ } else {
+ memset(tvp, 0, sizeof(struct timeval));
+ *msec = 0;
+ }
+
+#endif
+}
+
+void Generic_gettime_monotonic(uint64_t* msec) {
+#if defined(HAVE_CLOCK_GETTIME)
+
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
+ *msec = ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000);
+ else
+ *msec = 0;
+
+#else /* lower resolution gettimeofday() should be always 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
new file mode 100644
index 0000000..91fe43d
--- /dev/null
+++ b/generic/gettime.h
@@ -0,0 +1,18 @@
+#ifndef HEADER_gettime
+#define HEADER_gettime
+/*
+htop - generic/gettime.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdint.h>
+#include <sys/time.h>
+
+
+void Generic_gettime_realtime(struct timeval* tvp, uint64_t* msec);
+
+void Generic_gettime_monotonic(uint64_t* msec);
+
+#endif
diff --git a/generic/hostname.c b/generic/hostname.c
new file mode 100644
index 0000000..69a4146
--- /dev/null
+++ b/generic/hostname.c
@@ -0,0 +1,17 @@
+/*
+htop - generic/hostname.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+#include "config.h" // IWYU pragma: keep
+
+#include "generic/hostname.h"
+
+#include <unistd.h>
+
+
+void Generic_hostname(char* buffer, size_t size) {
+ gethostname(buffer, size - 1);
+ buffer[size - 1] = '\0';
+}
diff --git a/generic/hostname.h b/generic/hostname.h
new file mode 100644
index 0000000..1e6c52d
--- /dev/null
+++ b/generic/hostname.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_hostname
+#define HEADER_hostname
+/*
+htop - generic/hostname.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stddef.h>
+
+
+void Generic_hostname(char* buffer, size_t size);
+
+#endif
diff --git a/zfs/openzfs_sysctl.c b/generic/openzfs_sysctl.c
index fd00d61..bcd37dc 100644
--- a/zfs/openzfs_sysctl.c
+++ b/generic/openzfs_sysctl.c
@@ -1,11 +1,11 @@
/*
-htop - zfs/openzfs_sysctl.c
+htop - generic/openzfs_sysctl.c
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
-#include "zfs/openzfs_sysctl.h"
+#include "generic/openzfs_sysctl.h"
#include <stdlib.h>
#include <sys/types.h> // IWYU pragma: keep
@@ -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/zfs/openzfs_sysctl.h b/generic/openzfs_sysctl.h
index b49128e..27fa720 100644
--- a/zfs/openzfs_sysctl.h
+++ b/generic/openzfs_sysctl.h
@@ -1,14 +1,15 @@
#ifndef HEADER_openzfs_sysctl
#define HEADER_openzfs_sysctl
/*
-htop - zfs/openzfs_sysctl.h
+htop - generic/openzfs_sysctl.h
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#include "zfs/ZfsArcStats.h"
+
void openzfs_sysctl_init(ZfsArcStats* stats);
void openzfs_sysctl_updateArcStats(ZfsArcStats* stats);
diff --git a/generic/uname.c b/generic/uname.c
new file mode 100644
index 0000000..2a734dc
--- /dev/null
+++ b/generic/uname.c
@@ -0,0 +1,98 @@
+/*
+htop - generic/uname.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+#include "config.h" // IWYU pragma: keep
+
+#include "generic/uname.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "Macros.h"
+#include "XUtils.h"
+
+#ifdef HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+
+
+#ifndef OSRELEASEFILE
+#define OSRELEASEFILE "/etc/os-release"
+#endif
+
+static void parseOSRelease(char* buffer, size_t bufferLen) {
+ FILE* stream = fopen(OSRELEASEFILE, "r");
+ if (!stream) {
+ xSnprintf(buffer, bufferLen, "No OS Release");
+ return;
+ }
+
+ char name[64] = {'\0'};
+ char version[64] = {'\0'};
+ char lineBuffer[256];
+ while (fgets(lineBuffer, sizeof(lineBuffer), stream)) {
+ if (String_startsWith(lineBuffer, "PRETTY_NAME=\"")) {
+ const char* start = lineBuffer + strlen("PRETTY_NAME=\"");
+ const char* stop = strrchr(lineBuffer, '"');
+ if (!stop || stop <= start)
+ continue;
+ String_safeStrncpy(buffer, start, MINIMUM(bufferLen, (size_t)(stop - start + 1)));
+ fclose(stream);
+ return;
+ }
+ if (String_startsWith(lineBuffer, "NAME=\"")) {
+ const char* start = lineBuffer + strlen("NAME=\"");
+ const char* stop = strrchr(lineBuffer, '"');
+ if (!stop || stop <= start)
+ continue;
+ String_safeStrncpy(name, start, MINIMUM(sizeof(name), (size_t)(stop - start + 1)));
+ continue;
+ }
+ if (String_startsWith(lineBuffer, "VERSION=\"")) {
+ const char* start = lineBuffer + strlen("VERSION=\"");
+ const char* stop = strrchr(lineBuffer, '"');
+ if (!stop || stop <= start)
+ continue;
+ String_safeStrncpy(version, start, MINIMUM(sizeof(version), (size_t)(stop - start + 1)));
+ continue;
+ }
+ }
+ fclose(stream);
+
+ snprintf(buffer, bufferLen, "%s%s%s", name[0] ? name : "", name[0] && version[0] ? " " : "", version);
+}
+
+char* Generic_uname(void) {
+ static char savedString[
+ /* uname structure fields - manpages recommend sizeof */
+ sizeof(((struct utsname*)0)->sysname) +
+ sizeof(((struct utsname*)0)->release) +
+ sizeof(((struct utsname*)0)->machine) +
+ 16/*markup*/ +
+ 128/*distro*/] = {'\0'};
+ static bool loaded_data = false;
+
+ if (!loaded_data) {
+ struct utsname uname_info;
+ int uname_result = uname(&uname_info);
+
+ char distro[128];
+ parseOSRelease(distro, sizeof(distro));
+
+ if (uname_result == 0) {
+ size_t written = xSnprintf(savedString, sizeof(savedString), "%s %s [%s]", uname_info.sysname, uname_info.release, uname_info.machine);
+ if (!String_contains_i(savedString, distro, false) && sizeof(savedString) > written)
+ snprintf(savedString + written, sizeof(savedString) - written, " @ %s", distro);
+ } else {
+ snprintf(savedString, sizeof(savedString), "%s", distro);
+ }
+
+ loaded_data = true;
+ }
+
+ return savedString;
+}
diff --git a/generic/uname.h b/generic/uname.h
new file mode 100644
index 0000000..940d64c
--- /dev/null
+++ b/generic/uname.h
@@ -0,0 +1,12 @@
+#ifndef HEADER_uname
+#define HEADER_uname
+/*
+htop - generic/uname.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+char* Generic_uname(void);
+
+#endif
diff --git a/htop.1.in b/htop.1.in
index 0205a99..eefc37f 100644
--- a/htop.1.in
+++ b/htop.1.in
@@ -1,12 +1,14 @@
-.TH "HTOP" "1" "2020" "@PACKAGE_STRING@" "User Commands"
+.TH "HTOP" "1" "2023" "@PACKAGE_STRING@" "User Commands"
.SH "NAME"
-htop \- interactive process viewer
+htop, pcp-htop \- interactive process viewer
.SH "SYNOPSIS"
-.LP
.B htop
.RB [ \-dCFhpustvH ]
+.br
+.B pcp\ htop
+.RB [ \-dCFhpustvH ]
+.RB [ \-\-host/-h\ host ]
.SH "DESCRIPTION"
-.LP
.B htop
is a cross-platform ncurses-based process viewer.
.LP
@@ -16,19 +18,30 @@ but allows you to scroll vertically and horizontally, and interact using
a pointing device (mouse).
You can observe all processes running on the system, along with their
command line arguments, as well as view them in a tree format, select
-multiple processes and acting on them all at once.
+multiple processes and act on them all at once.
.LP
Tasks related to processes (killing, renicing) can be done without
entering their PIDs.
+.LP
+.B pcp-htop
+is a version of
+.B htop
+built using the Performance Co-Pilot (PCP) Metrics API (see \c
+.BR PCPIntro (1),
+.BR PMAPI (3)),
+allowing to extend
+.B htop
+to display values from arbitrary metrics.
+See the section below titled
+.B "CONFIG FILES"
+for further details.
.br
.SH "COMMAND-LINE OPTIONS"
-.LP
Mandatory arguments to long options are mandatory for short options too.
-.LP
.TP
\fB\-d \-\-delay=DELAY\fR
-Delay between updates, in tenths of seconds. If the delay value is
-less than 1 it is increased to 1, i.e. 1/10 second. If the delay value
+Delay between updates, in tenths of a second. If the delay value is
+less than 1, it is increased to 1, i.e. 1/10 second. If the delay value
is greater than 100, it is decreased to 100, i.e. 10 seconds.
.TP
\fB\-C \-\-no-color \-\-no-colour\fR
@@ -37,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
@@ -46,9 +60,10 @@ Display a help message and exit
Show only the given PIDs
.TP
\fB\-s \-\-sort\-key COLUMN\fR
-Sort by this column (use \-\-sort\-key help for a column list)
+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
@@ -57,20 +72,34 @@ Do not use unicode but ASCII characters for graph meters
\fB\-M \-\-no-mouse\fR
Disable support of mouse control
.TP
+\fB\-\-readonly\fR
+Disable all system and process changing features
+.TP
\fB\-V \-\-version
Output version information and exit
.TP
\fB\-t \-\-tree
-Show processes in tree view
+Show processes in tree view. This can be used to force a tree view when
+requesting a sort order with -s.
.TP
\fB\-H \-\-highlight-changes=DELAY\fR
Highlight new and old processes
+.TP
+\fB \-\-drop-capabilities[=off|basic|strict]\fR
+Linux only; requires libcap support.
+.br
+Drop unneeded Linux capabilities.
+In strict mode features like killing, changing process priorities, and reading
+process delay accounting information will not work, due to less capabilities
+held.
.SH "INTERACTIVE COMMANDS"
-.LP
The following commands are supported while in
.BR htop :
-.LP
.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.
@@ -141,6 +170,7 @@ select which columns are displayed, in which order.
Incrementally search the command lines of all the displayed processes. The
currently selected (highlighted) command will update as you type. While in
search mode, pressing F3 will cycle through matching occurrences.
+Pressing Shift-F3 will cycle backwards.
Alternatively the search can be started by simply typing the command
you are looking for, although for the first character normal key
@@ -150,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
@@ -157,11 +189,9 @@ between them as a tree. Toggling the key will switch between tree and
your previously selected sort view. Selecting a sort view will exit
tree view.
.TP
-.B F6
-On sorted view, select a field for sorting, also accessible through < and >.
+.B F6, <, >
+Selects a field for sorting, also accessible through < and >.
The current sort field is indicated by a highlight in the header.
-On tree view, expand or collapse the current subtree. A "+" indicator in the
-tree node indicates that it is collapsed.
.TP
.B F7, ]
Increase the selected process's priority (subtract from 'nice' value).
@@ -170,6 +200,13 @@ This can only be done by the superuser.
.B F8, [
Decrease the selected process's priority (add to 'nice' value)
.TP
+.B Shift-F7, }
+Increase the selected process's autogroup priority (subtract from autogroup 'nice' value).
+This can only be done by the superuser.
+.TP
+.B Shift-F8, {
+Decrease the selected process's autogroup priority (add to autogroup 'nice' value)
+.TP
.B F9, k
"Kill" process: sends a signal which is selected in a menu, to one or a group
of processes. If processes were tagged, sends the signal to all tagged processes.
@@ -182,9 +219,11 @@ Quit
Invert the sort order: if sort order is increasing, switch to decreasing, and
vice-versa.
.TP
-.B +, \-
+.B +, \-, *
When in tree view mode, expand or collapse subtree. When a subtree is collapsed
a "+" sign shows to the left of the process name.
+Pressing "*" will expand or collapse all children of PIDs without parents, so
+typically PID 1 (init) and PID 2 (kthreadd on Linux, if kernel threads are shown).
.TP
.B a (on multiprocessor machines)
Set CPU affinity: mark which CPUs a process is allowed to use.
@@ -192,6 +231,9 @@ Set CPU affinity: mark which CPUs a process is allowed to use.
.B u
Show only processes owned by a specified user.
.TP
+.B N
+Sort by PID.
+.TP
.B M
Sort by memory usage (top compatibility key).
.TP
@@ -232,7 +274,6 @@ Refresh: redraw screen and recalculate values.
PID search: type in process ID and the selection highlight will be moved to it.
.PD
.SH "COLUMNS"
-.LP
The following columns can display data about each process. A value of '\-' in
all the rows indicates that a column is unsupported on your system, or
currently unimplemented in
@@ -242,28 +283,52 @@ The names below are the ones used in the
shown in
.BR htop 's
main screen, it is shown below in parenthesis.
-.LP
.TP 5
.B Command
-The full command line of the process (i.e. program name and arguments). If the
-option 'Merge exe, comm and cmdline in Command' (toggled by the 'm' key) is set,
-and if readable, the executable path (/proc/[pid]/exe) and the command name
-(/proc/[pid]/comm) are also shown merged with the command line.
+The full command line of the process (i.e. program name and arguments).
+
+If the option 'Merge exe, comm and cmdline in Command' (toggled by the 'm' key)
+is active, the executable path (/proc/[pid]/exe) and the command name
+(/proc/[pid]/comm) are also shown merged with the command line, if available.
+
+The program basename is highlighted if set in the configuration. Additional
+highlighting can be configured for stale executables (cf. EXE column below).
.TP
-.B Comm
+.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. 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)
@@ -279,7 +344,7 @@ The process's group ID.
.B SESSION (SID)
The process's session ID.
.TP
-.B TTY_NR (TTY)
+.B TTY
The controlling terminal of the process.
.TP
.B TPGID
@@ -351,9 +416,6 @@ except the process's executable instructions).
.B M_LRS (LIB)
The library size of the process.
.TP
-.B M_DT (DIRTY)
-The size of the dirty pages of the process.
-.TP
.B M_SWAP (SWAP)
The size of the process's swapped pages.
.TP
@@ -370,6 +432,15 @@ The user ID of the process owner.
.TP
.B PERCENT_CPU (CPU%)
The percentage of the CPU time that the process is currently using.
+This is the default way to represent CPU usage in Linux. Each process can
+consume up to 100% which means the full capacity of the core it is running
+on. This is sometimes called "Irix mode" e.g. in
+.BR top (1).
+.TP
+.B PERCENT_NORM_CPU (NCPU%)
+The percentage of the CPU time that the process is currently using normalized
+by CPU count. This is sometimes called "Solaris mode" e.g. in
+.BR top (1).
.TP
.B PERCENT_MEM (MEM%)
The percentage of memory the process is currently using (based on the process's
@@ -384,7 +455,7 @@ The time, measured in clock ticks that the process has spent in user and system
time (see UTIME, STIME above).
.TP
.B NLWP
-The number of threads in the process.
+The number of Light-Weight Processes (=threads) in the process.
.TP
.B TGID
The thread group ID.
@@ -429,7 +500,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.
@@ -452,16 +539,56 @@ 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.
+.B AGRP
+The autogroup identifier for the process. Requires Linux CFS to be enabled.
.TP
-.B EXE
-The executable file of the process as reported by the kernel. Requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED.
+.B ANI
+The autogroup nice value for the process autogroup. Requires Linux CFS to be enabled.
.TP
.B All other flags
Currently unsupported (always displays '-').
-.SH "CONFIG FILE"
-.LP
+.SH "EXTERNAL LIBRARIES"
+While
+.B htop
+depends on most of the libraries it uses at build time there are two
+noteworthy exceptions to this rule. These exceptions both relate to
+data displayed in meters displayed in the header of
+.B htop
+and were intentionally created as optional runtime dependencies instead.
+These exceptions are described below:
+.TP
+.B libsystemd
+The bindings for libsystemd are used in the SystemD meter to determine
+the number of active services and the overall system state. Looking for
+the functions to determine these information at runtime allows for
+builds to support these meters without forcing the package manager
+to install these libraries on systems that otherwise don't use systemd.
+
+Summary: no build time dependency, optional runtime dependency on
+.B libsystemd
+via dynamic loading, with
+.B systemctl(1)
+fallback.
+.TP
+.B libsensors
+The bindings for libsensors are used for the CPU temperature readings
+in the CPU usage meters if displaying the temperature is enabled through
+the setup screen. In order for
+.B htop
+to show these temperatures correctly though, a proper configuration
+of libsensors through its usual configuration files is assumed and that
+all CPU cores correspond to temperature sensors from the
+.B coretemp
+driver with core 0 corresponding to a sensor labelled "Core 0". The
+package temperature may be given as "Package id 0". If missing it is
+inferred as the maximum value from the available per-core readings.
+
+Summary: build time dependency on
+.B libsensors(3)
+C header files, optional runtime dependency on
+.B libsensors(3)
+via dynamic loading.
+.SH "CONFIG FILES"
By default
.B htop
reads its configuration from the XDG-compliant path
@@ -478,8 +605,37 @@ and as a last resort, falls back to its hard coded defaults.
You may override the location of the configuration file using the $HTOPRC
environment variable (so you can have multiple configurations for different
machines that share the same home directory, for example).
-.SH "MEMORY SIZES"
.LP
+The
+.B pcp-htop
+utility makes use of
+.I htoprc
+in exactly the same way.
+In addition, it supports additional configuration files allowing
+new meters and columns to be added to the display via the usual
+Setup function, which will display additional Available Meters
+and Available Column entries for each runtime configured meter
+or column.
+.LP
+These
+.B pcp-htop
+configuration files are read once at startup.
+The format of these files is described in detail in the
+.BR pcp-htop (5)
+manual page.
+.LP
+This functionality makes available many thousands of Performance
+Co-Pilot metrics for display by
+.BR pcp-htop ,
+as well as the ability to display custom metrics added at individual sites.
+Applications and services instrumented using the OpenMetrics format
+.B https://openmetrics.io
+can also be displayed by
+.B pcp-htop
+if the
+.BR pmdaopenmetrics (1)
+component is configured.
+.SH "MEMORY SIZES"
Memory sizes in
.B htop
are displayed in a human-readable form.
@@ -496,8 +652,26 @@ space and make memory size representations consistent throughout
.BR uptime (1)
and
.BR limits.conf (5).
+.SH "SEE ALSO FOR PCP"
+.BR pmdaopenmetrics (1),
+.BR PCPIntro (1),
+.BR PMAPI (3),
+and
+.BR pcp-htop (5).
.SH "AUTHORS"
-.LP
.B htop
was originally developed by Hisham Muhammad.
Nowadays it is maintained by the community at <htop@groups.io>.
+.LP
+.B pcp-htop
+is maintained as a collaboration between the <htop@groups.io> and <pcp@groups.io>
+communities, and forms part of the Performance Co-Pilot suite of tools.
+.SH "COPYRIGHT"
+Copyright \(co 2004-2019 Hisham Muhammad.
+.br
+Copyright \(co 2020-2023 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 23da081..6b9ea48 100644
--- a/htop.c
+++ b/htop.c
@@ -1,361 +1,16 @@
/*
htop - htop.c
(C) 2004-2011 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
+(C) 2020-2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/
#include "config.h" // IWYU pragma: keep
-#include <assert.h>
-#include <getopt.h>
-#include <locale.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
+#include "CommandLine.h"
-#include "Action.h"
-#include "CRT.h"
-#include "Hashtable.h"
-#include "Header.h"
-#include "IncSet.h"
-#include "MainPanel.h"
-#include "MetersPanel.h"
-#include "Panel.h"
-#include "Platform.h"
-#include "Process.h"
-#include "ProcessList.h"
-#include "ProvideCurses.h"
-#include "ScreenManager.h"
-#include "Settings.h"
-#include "UsersTable.h"
-#include "XUtils.h"
-
-static void printVersionFlag(void) {
- fputs(PACKAGE " " VERSION "\n", stdout);
-}
-
-static void printHelpFlag(void) {
- fputs(PACKAGE " " VERSION "\n"
- COPYRIGHT "\n"
- "Released under the GNU GPLv2.\n\n"
- "-C --no-color Use a monochrome color scheme\n"
- "-d --delay=DELAY Set the delay between updates, in tenths of seconds\n"
- "-F --filter=FILTER Show only the commands matching the given filter\n"
- "-h --help Print this help screen\n"
- "-H --highlight-changes[=DELAY] Highlight new and old processes\n"
- "-M --no-mouse Disable the mouse\n"
- "-p --pid=PID[,PID,PID...] Show only the given PIDs\n"
- "-s --sort-key=COLUMN Sort by COLUMN (try --sort-key=help for a list)\n"
- "-t --tree Show the tree view by default\n"
- "-u --user[=USERNAME] Show only processes for a given user (or $USER)\n"
- "-U --no-unicode Do not use unicode but plain ASCII\n"
- "-V --version Print version info\n"
- "\n"
- "Long options may be passed with a single dash.\n\n"
- "Press F1 inside " PACKAGE " for online help.\n"
- "See 'man " PACKAGE "' for more information.\n",
- stdout);
-}
-
-// ----------------------------------------
-
-typedef struct CommandLineSettings_ {
- Hashtable* pidMatchList;
- char* commFilter;
- uid_t userId;
- int sortKey;
- int delay;
- bool useColors;
- bool enableMouse;
- bool treeView;
- bool allowUnicode;
- bool highlightChanges;
- int highlightDelaySecs;
-} CommandLineSettings;
-
-static CommandLineSettings parseArguments(int argc, char** argv) {
-
- CommandLineSettings flags = {
- .pidMatchList = NULL,
- .commFilter = NULL,
- .userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2))
- .sortKey = 0,
- .delay = -1,
- .useColors = true,
- .enableMouse = true,
- .treeView = false,
- .allowUnicode = true,
- .highlightChanges = false,
- .highlightDelaySecs = -1,
- };
-
- static struct option long_opts[] =
- {
- {"help", no_argument, 0, 'h'},
- {"version", no_argument, 0, 'V'},
- {"delay", required_argument, 0, 'd'},
- {"sort-key", required_argument, 0, 's'},
- {"user", optional_argument, 0, 'u'},
- {"no-color", no_argument, 0, 'C'},
- {"no-colour", no_argument, 0, 'C'},
- {"no-mouse", no_argument, 0, 'M'},
- {"no-unicode", no_argument, 0, 'U'},
- {"tree", no_argument, 0, 't'},
- {"pid", required_argument, 0, 'p'},
- {"filter", required_argument, 0, 'F'},
- {"highlight-changes", optional_argument, 0, 'H'},
- {0,0,0,0}
- };
-
- int opt, opti=0;
- /* Parse arguments */
- while ((opt = getopt_long(argc, argv, "hVMCs:td:u::Up:F:H::", long_opts, &opti))) {
- if (opt == EOF) break;
- switch (opt) {
- case 'h':
- printHelpFlag();
- exit(0);
- case 'V':
- printVersionFlag();
- exit(0);
- case 's':
- assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
- if (String_eq(optarg, "help")) {
- for (int j = 1; j < Platform_numberOfFields; j++) {
- const char* name = Process_fields[j].name;
- if (name) printf ("%s\n", name);
- }
- exit(0);
- }
- flags.sortKey = 0;
- for (int j = 1; j < Platform_numberOfFields; j++) {
- if (Process_fields[j].name == NULL)
- continue;
- if (String_eq(optarg, Process_fields[j].name)) {
- flags.sortKey = j;
- break;
- }
- }
- if (flags.sortKey == 0) {
- fprintf(stderr, "Error: invalid column \"%s\".\n", optarg);
- exit(1);
- }
- break;
- case 'd':
- if (sscanf(optarg, "%16d", &(flags.delay)) == 1) {
- if (flags.delay < 1) flags.delay = 1;
- if (flags.delay > 100) flags.delay = 100;
- } else {
- fprintf(stderr, "Error: invalid delay value \"%s\".\n", optarg);
- exit(1);
- }
- break;
- case 'u':
- {
- const char *username = optarg;
- if (!username && optind < argc && argv[optind] != NULL &&
- (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
- username = argv[optind++];
- }
-
- if (!username) {
- flags.userId = geteuid();
- } else if (!Action_setUserOnly(username, &(flags.userId))) {
- fprintf(stderr, "Error: invalid user \"%s\".\n", username);
- exit(1);
- }
- break;
- }
- case 'C':
- flags.useColors = false;
- break;
- case 'M':
- flags.enableMouse = false;
- break;
- case 'U':
- flags.allowUnicode = false;
- break;
- case 't':
- flags.treeView = true;
- break;
- case 'p': {
- assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
- char* argCopy = xStrdup(optarg);
- char* saveptr;
- char* pid = strtok_r(argCopy, ",", &saveptr);
-
- if(!flags.pidMatchList) {
- flags.pidMatchList = Hashtable_new(8, false);
- }
-
- while(pid) {
- unsigned int num_pid = atoi(pid);
- // deepcode ignore CastIntegerToAddress: we just want a non-NUll pointer here
- Hashtable_put(flags.pidMatchList, num_pid, (void *) 1);
- pid = strtok_r(NULL, ",", &saveptr);
- }
- free(argCopy);
-
- break;
- }
- case 'F': {
- assert(optarg);
- flags.commFilter = xStrdup(optarg);
-
- break;
- }
- case 'H': {
- const char *delay = optarg;
- if (!delay && optind < argc && argv[optind] != NULL &&
- (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
- delay = argv[optind++];
- }
- if (delay) {
- if (sscanf(delay, "%16d", &(flags.highlightDelaySecs)) == 1) {
- if (flags.highlightDelaySecs < 1)
- flags.highlightDelaySecs = 1;
- } else {
- fprintf(stderr, "Error: invalid highlight delay value \"%s\".\n", delay);
- exit(1);
- }
- }
- flags.highlightChanges = true;
- break;
- }
- default:
- exit(1);
- }
- }
- return flags;
-}
-
-static void millisleep(unsigned long millisec) {
- struct timespec req = {
- .tv_sec = 0,
- .tv_nsec = millisec * 1000000L
- };
- while(nanosleep(&req,&req)==-1) {
- continue;
- }
-}
-
-static void setCommFilter(State* state, char** commFilter) {
- MainPanel* panel = (MainPanel*)state->panel;
- ProcessList* pl = state->pl;
- IncSet* inc = panel->inc;
- size_t maxlen = sizeof(inc->modes[INC_FILTER].buffer) - 1;
- char* buffer = inc->modes[INC_FILTER].buffer;
-
- strncpy(buffer, *commFilter, maxlen);
- buffer[maxlen] = 0;
- inc->modes[INC_FILTER].index = strlen(buffer);
- inc->filtering = true;
- pl->incFilter = IncSet_filter(inc);
-
- free(*commFilter);
- *commFilter = NULL;
-}
int main(int argc, char** argv) {
-
- /* initialize locale */
- const char* lc_ctype;
- if ((lc_ctype = getenv("LC_CTYPE")) || (lc_ctype = getenv("LC_ALL")))
- setlocale(LC_CTYPE, lc_ctype);
- else
- setlocale(LC_CTYPE, "");
-
- CommandLineSettings flags = parseArguments(argc, argv);
-
- Platform_init();
-
- Process_setupColumnWidths();
-
- UsersTable* ut = UsersTable_new();
- ProcessList* pl = ProcessList_new(ut, flags.pidMatchList, flags.userId);
-
- Settings* settings = Settings_new(pl->cpuCount);
- pl->settings = settings;
-
- Header* header = Header_new(pl, settings, 2);
-
- Header_populateFromSettings(header);
-
- if (flags.delay != -1)
- settings->delay = flags.delay;
- if (!flags.useColors)
- settings->colorScheme = COLORSCHEME_MONOCHROME;
- if (!flags.enableMouse)
- settings->enableMouse = false;
- if (flags.treeView)
- settings->treeView = true;
- if (flags.highlightChanges)
- settings->highlightChanges = true;
- if (flags.highlightDelaySecs != -1)
- settings->highlightDelaySecs = flags.highlightDelaySecs;
- if (flags.sortKey > 0) {
- settings->sortKey = flags.sortKey;
- settings->treeView = false;
- settings->direction = 1;
- }
-
- CRT_init(&(settings->delay), settings->colorScheme, flags.allowUnicode);
-
- MainPanel* panel = MainPanel_new();
- ProcessList_setPanel(pl, (Panel*) panel);
-
- MainPanel_updateTreeFunctions(panel, settings->treeView);
-
- ProcessList_printHeader(pl, Panel_getHeader((Panel*)panel));
-
- State state = {
- .settings = settings,
- .ut = ut,
- .pl = pl,
- .panel = (Panel*) panel,
- .header = header,
- .pauseProcessUpdate = false,
- .hideProcessSelection = false,
- };
-
- MainPanel_setState(panel, &state);
- if (flags.commFilter)
- setCommFilter(&state, &(flags.commFilter));
-
- ScreenManager* scr = ScreenManager_new(header, settings, &state, true);
- ScreenManager_add(scr, (Panel*) panel, -1);
-
- ProcessList_scan(pl, false);
- millisleep(75);
- ProcessList_scan(pl, false);
-
- ScreenManager_run(scr, NULL, NULL);
-
- attron(CRT_colors[RESET_COLOR]);
- mvhline(LINES-1, 0, ' ', COLS);
- attroff(CRT_colors[RESET_COLOR]);
- refresh();
-
- Platform_done();
-
- CRT_done();
- if (settings->changed)
- Settings_write(settings);
- Header_delete(header);
- ProcessList_delete(pl);
-
- ScreenManager_delete(scr);
- MetersPanel_cleanup();
-
- UsersTable_delete(ut);
- Settings_delete(settings);
-
- if (flags.pidMatchList)
- Hashtable_delete(flags.pidMatchList);
-
- return 0;
+ return CommandLine_run(PACKAGE, 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 7fe432c..1416d74 100644
--- a/iwyu/htop.imp
+++ b/iwyu/htop.imp
@@ -8,6 +8,8 @@
{ include: ["<bits/getopt_core.h>", "private", "<unistd.h>", "public"] },
+ { include: ["<sys/dirent.h>", "private", "<dirent.h>", "public"] },
+
{ include: ["<sys/signal.h>", "private", "<signal.h>", "public"] },
{ include: ["<sys/_stdarg.h>", "private", "<stdarg.h>", "public"] },
@@ -15,4 +17,8 @@
{ include: ["<sys/limits.h>", "private", "<limits.h>", "public"] },
{ include: ["<x86/_inttypes.h>", "private", "<inttypes.h>", "public"] },
+
+ { include: ["<linux/capability.h>", "private", "<sys/capability.h>", "public"] },
+
+ { include: ["<bits/mman-shared.h>", "private", "<sys/mman.h>", "public"] },
]
diff --git a/linux/CGroupUtils.c b/linux/CGroupUtils.c
new file mode 100644
index 0000000..c11c460
--- /dev/null
+++ b/linux/CGroupUtils.c
@@ -0,0 +1,341 @@
+/*
+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.
+*/
+
+#include "linux/CGroupUtils.h"
+
+#include "XUtils.h"
+
+
+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) {
+ const char* str_slice_suffix = ".slice";
+ const char* str_system_slice = "system.slice";
+ const char* str_user_slice = "user.slice";
+ const char* str_machine_slice = "machine.slice";
+ const char* str_user_slice_prefix = "/user-";
+ const char* str_system_slice_prefix = "/system-";
+
+ const char* str_lxc_monitor_legacy = "lxc.monitor";
+ const char* str_lxc_payload_legacy = "lxc.payload";
+ const char* str_lxc_monitor_prefix = "lxc.monitor.";
+ const char* str_lxc_payload_prefix = "lxc.payload.";
+
+ const char* str_nspawn_scope_prefix = "machine-";
+ const char* str_nspawn_monitor_label = "/supervisor";
+ const char* str_nspawn_payload_label = "/payload";
+
+ const char* str_snap_scope_prefix = "snap.";
+
+ const char* str_service_suffix = ".service";
+ const char* str_scope_suffix = ".scope";
+
+ while (*cgroup) {
+ if ('/' == *cgroup) {
+ while ('/' == *cgroup)
+ cgroup++;
+
+ if (!w(s, '/'))
+ return false;
+
+ continue;
+ }
+
+ const char* labelStart = cgroup;
+ const char* nextSlash = 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 = 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 = 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 = 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 = 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;
+ }
+
+ 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;
+}
diff --git a/linux/CGroupUtils.h b/linux/CGroupUtils.h
new file mode 100644
index 0000000..ff13437
--- /dev/null
+++ b/linux/CGroupUtils.h
@@ -0,0 +1,16 @@
+#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.
+*/
+
+#include <stdbool.h>
+#include <stddef.h>
+
+
+char* CGroup_filterName(const char* cgroup);
+
+#endif /* HEADER_CGroupUtils */
diff --git a/linux/HugePageMeter.c b/linux/HugePageMeter.c
new file mode 100644
index 0000000..1efde2f
--- /dev/null
+++ b/linux/HugePageMeter.c
@@ -0,0 +1,106 @@
+/*
+htop - HugePageMeter.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "linux/HugePageMeter.h"
+
+#include <assert.h>
+#include <math.h>
+#include <stddef.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Object.h"
+#include "ProcessList.h"
+#include "RichString.h"
+#include "linux/LinuxProcessList.h"
+
+
+static const char* HugePageMeter_active_labels[4] = { NULL, NULL, NULL, NULL };
+
+static const int HugePageMeter_attributes[] = {
+ HUGEPAGE_1,
+ HUGEPAGE_2,
+ HUGEPAGE_3,
+ HUGEPAGE_4,
+};
+
+static const char* const HugePageMeter_labels[] = {
+ " 64K:", " 128K:", " 256K:", " 512K:",
+ " 1M:", " 2M:", " 4M:", " 8M:", " 16M:", " 32M:", " 64M:", " 128M:", " 256M:", " 512M:",
+ " 1G:", " 2G:", " 4G:", " 8G:", " 16G:", " 32G:", " 64G:", " 128G:", " 256G:", " 512G:",
+};
+
+static void HugePageMeter_updateValues(Meter* this) {
+ assert(ARRAYSIZE(HugePageMeter_labels) == HTOP_HUGEPAGE_COUNT);
+
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
+ int written;
+ memory_t usedTotal = 0;
+ unsigned nextUsed = 0;
+
+ const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl;
+ this->total = lpl->totalHugePageMem;
+ this->values[0] = 0;
+ HugePageMeter_active_labels[0] = " used:";
+ for (unsigned i = 1; i < ARRAYSIZE(HugePageMeter_active_labels); i++) {
+ this->values[i] = NAN;
+ HugePageMeter_active_labels[i] = NULL;
+ }
+ for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {
+ memory_t value = lpl->usedHugePageMem[i];
+ if (value != MEMORY_MAX) {
+ this->values[nextUsed] = value;
+ usedTotal += value;
+ HugePageMeter_active_labels[nextUsed] = HugePageMeter_labels[i];
+ if (++nextUsed == ARRAYSIZE(HugePageMeter_active_labels)) {
+ break;
+ }
+ }
+ }
+
+ written = Meter_humanUnit(buffer, usedTotal, size);
+ METER_BUFFER_CHECK(buffer, size, written);
+
+ METER_BUFFER_APPEND_CHR(buffer, size, '/');
+
+ Meter_humanUnit(buffer, this->total, size);
+}
+
+static void HugePageMeter_display(const Object* cast, RichString* out) {
+ char buffer[50];
+ const Meter* this = (const Meter*)cast;
+
+ RichString_writeAscii(out, CRT_colors[METER_TEXT], ":");
+ Meter_humanUnit(buffer, this->total, sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+
+ for (unsigned i = 0; i < ARRAYSIZE(HugePageMeter_active_labels); i++) {
+ if (isnan(this->values[i])) {
+ break;
+ }
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], HugePageMeter_active_labels[i]);
+ Meter_humanUnit(buffer, this->values[i], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[HUGEPAGE_1 + i], buffer);
+ }
+}
+
+const MeterClass HugePageMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = HugePageMeter_display,
+ },
+ .updateValues = HugePageMeter_updateValues,
+ .defaultMode = BAR_METERMODE,
+ .maxItems = ARRAYSIZE(HugePageMeter_active_labels),
+ .total = 100.0,
+ .attributes = HugePageMeter_attributes,
+ .name = "HugePages",
+ .uiName = "HugePages",
+ .caption = "HP"
+};
diff --git a/linux/HugePageMeter.h b/linux/HugePageMeter.h
new file mode 100644
index 0000000..d74a19e
--- /dev/null
+++ b/linux/HugePageMeter.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_HugePageMeter
+#define HEADER_HugePageMeter
+/*
+htop - HugePageMeter.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Meter.h"
+
+
+extern const MeterClass HugePageMeter_class;
+
+#endif /* HEADER_HugePageMeter */
diff --git a/linux/IOPriority.h b/linux/IOPriority.h
index 551a7d5..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,
@@ -30,8 +30,6 @@ typedef int IOPriority;
#define IOPriority_tuple(class_, data_) (((class_) << IOPRIO_CLASS_SHIFT) | (data_))
-#define IOPriority_error 0xffffffff
-
#define IOPriority_None IOPriority_tuple(IOPRIO_CLASS_NONE, 0)
#define IOPriority_Idle IOPriority_tuple(IOPRIO_CLASS_IDLE, 7)
diff --git a/linux/IOPriorityPanel.c b/linux/IOPriorityPanel.c
index c5a4a4c..3e91bc4 100644
--- a/linux/IOPriorityPanel.c
+++ b/linux/IOPriorityPanel.c
@@ -1,11 +1,11 @@
/*
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 "IOPriorityPanel.h"
+#include "linux/IOPriorityPanel.h"
#include <stdbool.h>
#include <stddef.h>
@@ -14,10 +14,11 @@ in the source distribution for its full text.
#include "ListItem.h"
#include "Object.h"
#include "XUtils.h"
+#include "IOPriority.h"
Panel* IOPriorityPanel_new(IOPriority currPrio) {
- Panel* this = Panel_new(1, 1, 1, 1, true, Class(ListItem), FunctionBar_newEnterEsc("Set ", "Cancel "));
+ Panel* this = Panel_new(1, 1, 1, 1, Class(ListItem), true, FunctionBar_newEnterEsc("Set ", "Cancel "));
Panel_setHeader(this, "IO Priority:");
Panel_add(this, (Object*) ListItem_new("None (based on nice)", IOPriority_None));
diff --git a/linux/IOPriorityPanel.h b/linux/IOPriorityPanel.h
index 2ac4b31..cb5b338 100644
--- a/linux/IOPriorityPanel.h
+++ b/linux/IOPriorityPanel.h
@@ -3,12 +3,12 @@
/*
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.
*/
#include "Panel.h"
-#include "IOPriority.h"
+#include "linux/IOPriority.h"
Panel* IOPriorityPanel_new(IOPriority currPrio);
diff --git a/linux/LibSensors.c b/linux/LibSensors.c
index a30e21b..9a27fe5 100644
--- a/linux/LibSensors.c
+++ b/linux/LibSensors.c
@@ -1,28 +1,62 @@
-#include "LibSensors.h"
+#include "linux/LibSensors.h"
+
+#include "config.h"
#ifdef HAVE_SENSORS_SENSORS_H
+#include <assert.h>
#include <dlfcn.h>
#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
#include <sensors/sensors.h>
+#include "Macros.h"
#include "XUtils.h"
+#include "linux/LinuxProcessList.h"
+
+
+#ifdef BUILD_STATIC
+
+#define sym_sensors_init sensors_init
+#define sym_sensors_cleanup sensors_cleanup
+#define sym_sensors_get_detected_chips sensors_get_detected_chips
+#define sym_sensors_get_features sensors_get_features
+#define sym_sensors_get_subfeature sensors_get_subfeature
+#define sym_sensors_get_value sensors_get_value
+#else
static int (*sym_sensors_init)(FILE*);
static void (*sym_sensors_cleanup)(void);
static const sensors_chip_name* (*sym_sensors_get_detected_chips)(const sensors_chip_name*, int*);
-static int (*sym_sensors_snprintf_chip_name)(char*, size_t, const sensors_chip_name*);
static const sensors_feature* (*sym_sensors_get_features)(const sensors_chip_name*, int*);
static const sensors_subfeature* (*sym_sensors_get_subfeature)(const sensors_chip_name*, const sensors_feature*, sensors_subfeature_type);
static int (*sym_sensors_get_value)(const sensors_chip_name*, int, double*);
static void* dlopenHandle = NULL;
-int LibSensors_init(FILE* input) {
+#endif /* BUILD_STATIC */
+
+int LibSensors_init(void) {
+#ifdef BUILD_STATIC
+
+ return sym_sensors_init(NULL);
+
+#else
+
if (!dlopenHandle) {
+ /* Find the unversioned libsensors.so (symlink) and prefer that, but Debian has .so.5 and Fedora .so.4 without
+ matching symlinks (unless people install the -dev packages) */
dlopenHandle = dlopen("libsensors.so", RTLD_LAZY);
if (!dlopenHandle)
+ dlopenHandle = dlopen("libsensors.so.5", RTLD_LAZY);
+ if (!dlopenHandle)
+ dlopenHandle = dlopen("libsensors.so.4", RTLD_LAZY);
+ if (!dlopenHandle)
goto dlfailure;
/* Clear any errors */
@@ -37,7 +71,6 @@ int LibSensors_init(FILE* input) {
resolve(sensors_init);
resolve(sensors_cleanup);
resolve(sensors_get_detected_chips);
- resolve(sensors_snprintf_chip_name);
resolve(sensors_get_features);
resolve(sensors_get_subfeature);
resolve(sensors_get_value);
@@ -45,7 +78,8 @@ int LibSensors_init(FILE* input) {
#undef resolve
}
- return sym_sensors_init(input);
+ return sym_sensors_init(NULL);
+
dlfailure:
if (dlopenHandle) {
@@ -53,52 +87,183 @@ dlfailure:
dlopenHandle = NULL;
}
return -1;
+
+#endif /* BUILD_STATIC */
}
void LibSensors_cleanup(void) {
+#ifdef BUILD_STATIC
+
+ sym_sensors_cleanup();
+
+#else
+
if (dlopenHandle) {
sym_sensors_cleanup();
dlclose(dlopenHandle);
dlopenHandle = NULL;
}
+
+#endif /* BUILD_STATIC */
+}
+
+int LibSensors_reload(void) {
+#ifndef BUILD_STATIC
+ if (!dlopenHandle) {
+ errno = ENOTSUP;
+ return -1;
+ }
+#endif /* !BUILD_STATIC */
+
+ sym_sensors_cleanup();
+ return sym_sensors_init(NULL);
}
-int LibSensors_getCPUTemperatures(CPUData* cpus, int cpuCount) {
+static int tempDriverPriority(const sensors_chip_name* chip) {
+ static const struct TempDriverDefs {
+ const char* prefix;
+ int priority;
+ } tempDrivers[] = {
+ { "coretemp", 0 },
+ { "via_cputemp", 0 },
+ { "cpu_thermal", 0 },
+ { "k10temp", 0 },
+ { "zenpower", 0 },
+ /* Low priority drivers */
+ { "acpitz", 1 },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE(tempDrivers); i++)
+ if (String_eq(chip->prefix, tempDrivers[i].prefix))
+ return tempDrivers[i].priority;
+
+ return -1;
+}
+
+void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs) {
+ assert(existingCPUs > 0 && existingCPUs < 16384);
+ double data[existingCPUs + 1];
+ for (size_t i = 0; i < existingCPUs + 1; i++)
+ data[i] = NAN;
+
+#ifndef BUILD_STATIC
if (!dlopenHandle)
- return -ENOTSUP;
+ goto out;
+#endif /* !BUILD_STATIC */
- int tempCount = 0;
+ unsigned int coreTempCount = 0;
+ int topPriority = 99;
int n = 0;
- for (const sensors_chip_name *chip = sym_sensors_get_detected_chips(NULL, &n); chip; chip = sym_sensors_get_detected_chips(NULL, &n)) {
- char buffer[32];
- sym_sensors_snprintf_chip_name(buffer, sizeof(buffer), chip);
- if (!String_startsWith(buffer, "coretemp") && !String_startsWith(buffer, "cpu_thermal"))
+ for (const sensors_chip_name* chip = sym_sensors_get_detected_chips(NULL, &n); chip; chip = sym_sensors_get_detected_chips(NULL, &n)) {
+ const int priority = tempDriverPriority(chip);
+ if (priority < 0)
continue;
+ if (priority > topPriority)
+ continue;
+
+ if (priority < topPriority) {
+ /* Clear data from lower priority sensor */
+ for (size_t i = 0; i < existingCPUs + 1; i++)
+ data[i] = NAN;
+ }
+
+ topPriority = priority;
+
int m = 0;
- for (const sensors_feature *feature = sym_sensors_get_features(chip, &m); feature; feature = sym_sensors_get_features(chip, &m)) {
+ for (const sensors_feature* feature = sym_sensors_get_features(chip, &m); feature; feature = sym_sensors_get_features(chip, &m)) {
if (feature->type != SENSORS_FEATURE_TEMP)
continue;
- if (feature->number > cpuCount)
+ if (!feature->name || !String_startsWith(feature->name, "temp"))
+ continue;
+
+ unsigned long int tempID = strtoul(feature->name + strlen("temp"), NULL, 10);
+ if (tempID == 0 || tempID == ULONG_MAX)
continue;
- const sensors_subfeature *sub_feature = sym_sensors_get_subfeature(chip, feature, SENSORS_SUBFEATURE_TEMP_INPUT);
- if (sub_feature) {
- double temp;
- int r = sym_sensors_get_value(chip, sub_feature->number, &temp);
- if (r != 0)
- continue;
+ /* Feature name IDs start at 1, adjust to start at 0 to match data indices */
+ tempID--;
+
+ if (tempID > existingCPUs)
+ continue;
+
+ const sensors_subfeature* subFeature = sym_sensors_get_subfeature(chip, feature, SENSORS_SUBFEATURE_TEMP_INPUT);
+ if (!subFeature)
+ continue;
- cpus[feature->number].temperature = temp;
- tempCount++;
+ double temp;
+ int r = sym_sensors_get_value(chip, subFeature->number, &temp);
+ if (r != 0)
+ continue;
+
+ /* If already set, e.g. Ryzen reporting platform temperature for each die, use the bigger one */
+ if (isnan(data[tempID])) {
+ data[tempID] = temp;
+ if (tempID > 0)
+ coreTempCount++;
+ } else {
+ data[tempID] = MAXIMUM(data[tempID], temp);
}
}
}
- return tempCount;
+ /* Adjust data for chips not providing a platform temperature */
+ if (coreTempCount + 1 == activeCPUs || coreTempCount + 1 == activeCPUs / 2) {
+ memmove(&data[1], &data[0], existingCPUs * sizeof(*data));
+ data[0] = NAN;
+ coreTempCount++;
+
+ /* Check for further adjustments */
+ }
+
+ /* Only package temperature - copy to all cores */
+ if (coreTempCount == 0 && !isnan(data[0])) {
+ for (unsigned int i = 1; i <= existingCPUs; i++)
+ data[i] = data[0];
+
+ /* No further adjustments */
+ goto out;
+ }
+
+ /* No package temperature - set to max core temperature */
+ if (isnan(data[0]) && coreTempCount != 0) {
+ double maxTemp = NAN;
+ for (unsigned int i = 1; i <= existingCPUs; i++) {
+ if (isnan(data[i]))
+ continue;
+
+ maxTemp = MAXIMUM(maxTemp, data[i]);
+ }
+
+ data[0] = maxTemp;
+
+ /* Check for further adjustments */
+ }
+
+ /* Only temperature for core 0, maybe Ryzen - copy to all other cores */
+ if (coreTempCount == 1 && !isnan(data[1])) {
+ for (unsigned int i = 2; i <= existingCPUs; i++)
+ data[i] = data[1];
+
+ /* No further adjustments */
+ goto out;
+ }
+
+ /* Half the temperatures, probably HT/SMT - copy to second half */
+ const unsigned int delta = activeCPUs / 2;
+ if (coreTempCount == delta) {
+ memcpy(&data[delta + 1], &data[1], delta * sizeof(*data));
+
+ /* No further adjustments */
+ goto out;
+ }
+
+out:
+ for (unsigned int i = 0; i <= existingCPUs; i++)
+ cpus[i].temperature = data[i];
}
#endif /* HAVE_SENSORS_SENSORS_H */
diff --git a/linux/LibSensors.h b/linux/LibSensors.h
index ed9be7b..aa89979 100644
--- a/linux/LibSensors.h
+++ b/linux/LibSensors.h
@@ -1,16 +1,13 @@
#ifndef HEADER_LibSensors
#define HEADER_LibSensors
-#include "config.h" // IWYU pragma: keep
+#include "linux/LinuxProcessList.h"
-#include <stdio.h>
-#include "LinuxProcessList.h"
-
-
-int LibSensors_init(FILE* input);
+int LibSensors_init(void);
void LibSensors_cleanup(void);
+int LibSensors_reload(void);
-int LibSensors_getCPUTemperatures(CPUData* cpus, int cpuCount);
+void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs);
#endif /* HEADER_LibSensors */
diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c
index 8298000..381b7cf 100644
--- a/linux/LinuxProcess.c
+++ b/linux/LinuxProcess.c
@@ -2,16 +2,15 @@
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 "LinuxProcess.h"
+#include "linux/LinuxProcess.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
#include <syscall.h>
#include <unistd.h>
@@ -21,129 +20,88 @@ in the source distribution for its full text.
#include "ProvideCurses.h"
#include "RichString.h"
#include "XUtils.h"
+#include "linux/IOPriority.h"
/* semi-global */
-long long btime;
+int pageSize;
+int pageSizeKB;
-/* Used to identify kernel threads in Comm and Exe columns */
-static const char *const kthreadID = "KTHREAD";
-
-ProcessFieldData Process_fields[] = {
+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, },
+ [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, },
[COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
[STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging, I idle)", .flags = 0, },
- [PPID] = { .name = "PPID", .title = " PPID ", .description = "Parent process ID", .flags = 0, },
- [PGRP] = { .name = "PGRP", .title = " PGRP ", .description = "Process group ID", .flags = 0, },
- [SESSION] = { .name = "SESSION", .title = " SID ", .description = "Process's session ID", .flags = 0, },
- [TTY_NR] = { .name = "TTY_NR", .title = "TTY ", .description = "Controlling terminal", .flags = 0, },
- [TPGID] = { .name = "TPGID", .title = " TPGID ", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, },
- [FLAGS] = { .name = "FLAGS", .title = NULL, .description = NULL, .flags = 0, },
- [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, },
- [CMINFLT] = { .name = "CMINFLT", .title = " CMINFLT ", .description = "Children processes' minor faults", .flags = 0, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
- [CMAJFLT] = { .name = "CMAJFLT", .title = " CMAJFLT ", .description = "Children processes' major faults", .flags = 0, },
- [UTIME] = { .name = "UTIME", .title = " UTIME+ ", .description = "User CPU time - time the process spent executing in user mode", .flags = 0, },
- [STIME] = { .name = "STIME", .title = " STIME+ ", .description = "System CPU time - time the kernel spent running system calls for this process", .flags = 0, },
- [CUTIME] = { .name = "CUTIME", .title = " CUTIME+ ", .description = "Children processes' user CPU time", .flags = 0, },
- [CSTIME] = { .name = "CSTIME", .title = " CSTIME+ ", .description = "Children processes' system CPU time", .flags = 0, },
+ [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, },
+ [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, },
+ [CMINFLT] = { .name = "CMINFLT", .title = " CMINFLT ", .description = "Children processes' minor faults", .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, },
+ [CMAJFLT] = { .name = "CMAJFLT", .title = " CMAJFLT ", .description = "Children processes' major faults", .flags = 0, .defaultSortDesc = true, },
+ [UTIME] = { .name = "UTIME", .title = " UTIME+ ", .description = "User CPU time - time the process spent executing in user mode", .flags = 0, .defaultSortDesc = true, },
+ [STIME] = { .name = "STIME", .title = " STIME+ ", .description = "System CPU time - time the kernel spent running system calls for this process", .flags = 0, .defaultSortDesc = true, },
+ [CUTIME] = { .name = "CUTIME", .title = " CUTIME+ ", .description = "Children processes' user CPU time", .flags = 0, .defaultSortDesc = true, },
+ [CSTIME] = { .name = "CSTIME", .title = " CSTIME+ ", .description = "Children processes' system CPU time", .flags = 0, .defaultSortDesc = true, },
[PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
[NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
- [ITREALVALUE] = { .name = "ITREALVALUE", .title = NULL, .description = NULL, .flags = 0, },
[STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
- [VSIZE] = { .name = "VSIZE", .title = NULL, .description = NULL, .flags = 0, },
- [RSS] = { .name = "RSS", .title = NULL, .description = NULL, .flags = 0, },
- [RLIM] = { .name = "RLIM", .title = NULL, .description = NULL, .flags = 0, },
- [STARTCODE] = { .name = "STARTCODE", .title = NULL, .description = NULL, .flags = 0, },
- [ENDCODE] = { .name = "ENDCODE", .title = NULL, .description = NULL, .flags = 0, },
- [STARTSTACK] = { .name = "STARTSTACK", .title = NULL, .description = NULL, .flags = 0, },
- [KSTKESP] = { .name = "KSTKESP", .title = NULL, .description = NULL, .flags = 0, },
- [KSTKEIP] = { .name = "KSTKEIP", .title = NULL, .description = NULL, .flags = 0, },
- [SIGNAL] = { .name = "SIGNAL", .title = NULL, .description = NULL, .flags = 0, },
- [BLOCKED] = { .name = "BLOCKED", .title = NULL, .description = NULL, .flags = 0, },
- [SSIGIGNORE] = { .name = "SIGIGNORE", .title = NULL, .description = NULL, .flags = 0, },
- [SIGCATCH] = { .name = "SIGCATCH", .title = NULL, .description = NULL, .flags = 0, },
- [WCHAN] = { .name = "WCHAN", .title = NULL, .description = NULL, .flags = 0, },
- [NSWAP] = { .name = "NSWAP", .title = NULL, .description = NULL, .flags = 0, },
- [CNSWAP] = { .name = "CNSWAP", .title = NULL, .description = NULL, .flags = 0, },
- [EXIT_SIGNAL] = { .name = "EXIT_SIGNAL", .title = NULL, .description = NULL, .flags = 0, },
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
- [M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, },
- [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, },
- [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, },
- [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, },
- [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, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, },
- [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
- [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, },
- [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
- [TGID] = { .name = "TGID", .title = " TGID ", .description = "Thread group ID (i.e. process ID)", .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, },
+ [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 (CODE)", .flags = 0, .defaultSortDesc = true, },
+ [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the .data segment plus stack usage of the process (DATA)", .flags = 0, .defaultSortDesc = true, },
+ [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, .defaultSortDesc = true, },
+ [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
+ [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, },
#ifdef HAVE_OPENVZ
[CTID] = { .name = "CTID", .title = " CTID ", .description = "OpenVZ container ID (a.k.a. virtual environment ID)", .flags = PROCESS_FLAG_LINUX_OPENVZ, },
- [VPID] = { .name = "VPID", .title = " VPID ", .description = "OpenVZ process ID", .flags = PROCESS_FLAG_LINUX_OPENVZ, },
+ [VPID] = { .name = "VPID", .title = "VPID", .description = "OpenVZ process ID", .flags = PROCESS_FLAG_LINUX_OPENVZ, .pidColumn = true, },
#endif
#ifdef HAVE_VSERVER
[VXID] = { .name = "VXID", .title = " VXID ", .description = "VServer process ID", .flags = PROCESS_FLAG_LINUX_VSERVER, },
#endif
- [RCHAR] = { .name = "RCHAR", .title = " RD_CHAR ", .description = "Number of bytes the process has read", .flags = PROCESS_FLAG_IO, },
- [WCHAR] = { .name = "WCHAR", .title = " WR_CHAR ", .description = "Number of bytes the process has written", .flags = PROCESS_FLAG_IO, },
- [SYSCR] = { .name = "SYSCR", .title = " RD_SYSC ", .description = "Number of read(2) syscalls for the process", .flags = PROCESS_FLAG_IO, },
- [SYSCW] = { .name = "SYSCW", .title = " WR_SYSC ", .description = "Number of write(2) syscalls for the process", .flags = PROCESS_FLAG_IO, },
- [RBYTES] = { .name = "RBYTES", .title = " IO_RBYTES ", .description = "Bytes of read(2) I/O for the process", .flags = PROCESS_FLAG_IO, },
- [WBYTES] = { .name = "WBYTES", .title = " IO_WBYTES ", .description = "Bytes of write(2) I/O for the process", .flags = PROCESS_FLAG_IO, },
- [CNCLWB] = { .name = "CNCLWB", .title = " IO_CANCEL ", .description = "Bytes of cancelled write(2) I/O", .flags = PROCESS_FLAG_IO, },
- [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, },
- [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, },
- [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, },
- [CGROUP] = { .name = "CGROUP", .title = " CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, },
- [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, },
+ [RCHAR] = { .name = "RCHAR", .title = "RCHAR ", .description = "Number of bytes the process has read", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [WCHAR] = { .name = "WCHAR", .title = "WCHAR ", .description = "Number of bytes the process has written", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [SYSCR] = { .name = "SYSCR", .title = " READ_SYSC ", .description = "Number of read(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [SYSCW] = { .name = "SYSCW", .title = " WRITE_SYSC ", .description = "Number of write(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [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_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw)", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
+ [CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed)", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
+ [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 = 0, },
- [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = 0, },
- [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWAPD% ", .description = "Swapin delay %", .flags = 0, },
+ [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, },
- [M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, },
- [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, },
- [CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, },
- [SECATTR] = { .name = "SECATTR", .title = " Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, },
+ [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, .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_LINUX_CWD, },
- [LAST_PROCESSFIELD] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, },
-};
-
-ProcessPidColumn Process_pidColumns[] = {
- { .id = PID, .label = "PID" },
- { .id = PPID, .label = "PPID" },
- #ifdef HAVE_OPENVZ
- { .id = VPID, .label = "VPID" },
- #endif
- { .id = TPGID, .label = "TPGID" },
- { .id = TGID, .label = "TGID" },
- { .id = PGRP, .label = "PGRP" },
- { .id = SESSION, .label = "SID" },
- { .id = 0, .label = NULL },
+ [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, },
};
-/* 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 LinuxProcess_writeField(COMM) and LinuxProcess_writeCommand */
-static const char* LinuxProcess_getCommandStr(const Process *this) {
- const LinuxProcess *lp = (const LinuxProcess *)this;
- if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !lp->mergedCommand.str) {
- return this->comm;
- }
- return lp->mergedCommand.str;
-}
-
Process* LinuxProcess_new(const Settings* settings) {
LinuxProcess* this = xCalloc(1, sizeof(LinuxProcess));
Object_setClass(this, Class(LinuxProcess));
@@ -154,16 +112,12 @@ Process* LinuxProcess_new(const Settings* settings) {
void Process_delete(Object* cast) {
LinuxProcess* this = (LinuxProcess*) cast;
Process_done((Process*)cast);
+ free(this->cgroup_short);
free(this->cgroup);
#ifdef HAVE_OPENVZ
free(this->ctid);
#endif
- free(this->cwd);
free(this->secattr);
- free(this->ttyDevice);
- free(this->procExe);
- free(this->procComm);
- free(this->mergedCommand.str);
free(this);
}
@@ -183,6 +137,11 @@ static int LinuxProcess_effectiveIOPriority(const LinuxProcess* this) {
return this->ioPriority;
}
+#ifdef __ANDROID__
+#define SYS_ioprio_get __NR_ioprio_get
+#define SYS_ioprio_set __NR_ioprio_set
+#endif
+
IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this) {
IOPriority ioprio = 0;
// Other OSes masquerading as Linux (NetBSD?) don't have this syscall
@@ -201,416 +160,35 @@ bool LinuxProcess_setIOPriority(Process* this, Arg ioprio) {
return (LinuxProcess_updateIOPriority((LinuxProcess*)this) == ioprio.i);
}
-#ifdef HAVE_DELAYACCT
-static void LinuxProcess_printDelay(float delay_percent, char* buffer, int n) {
- if (isnan(delay_percent)) {
- xSnprintf(buffer, n, " N/A ");
- } else {
- xSnprintf(buffer, n, "%4.1f ", delay_percent);
- }
-}
-#endif
-
-/*
-TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is
-not available in an userspace header - so define it. Note: when colorizing a
-basename with the comm prefix, the entire basename (not just the comm prefix) is
-colorized for better readability, and it is implicit that only upto
-(TASK_COMM_LEN - 1) could be comm
-*/
-#define TASK_COMM_LEN 16
-
-static bool findCommInCmdline(const char *comm, const char *cmdline, int cmdlineBasenameOffset, int *pCommStart, int *pCommEnd) {
- /* Try to find procComm in tokenized cmdline - this might in rare cases
- * mis-identify a string or fail, if comm or cmdline had been unsuitably
- * modified by the process */
- const char *token;
- const char *tokenBase;
- size_t tokenLen;
- const size_t commLen = strlen(comm);
-
- for (token = cmdline + cmdlineBasenameOffset; *token; ) {
- for (tokenBase = token; *token && *token != '\n'; ++token) {
- if (*token == '/') {
- tokenBase = token + 1;
- }
- }
- tokenLen = token - tokenBase;
-
- if ((tokenLen == commLen || (tokenLen > commLen && commLen == (TASK_COMM_LEN - 1))) &&
- strncmp(tokenBase, comm, commLen) == 0) {
- *pCommStart = tokenBase - cmdline;
- *pCommEnd = token - cmdline;
- return true;
- }
- if (*token) {
- do {
- ++token;
- } while ('\n' == *token);
- }
- }
- return false;
-}
-
-static int matchCmdlinePrefixWithExeSuffix(const char *cmdline, int cmdlineBaseOffset, const char *exe, int exeBaseOffset, int exeBaseLen) {
- int matchLen; /* matching length to be returned */
- char delim; /* delimiter following basename */
-
- /* cmdline prefix is an absolute path: it must match whole exe. */
- if (cmdline[0] == '/') {
- matchLen = exeBaseLen + exeBaseOffset;
- if (strncmp(cmdline, exe, matchLen) == 0) {
- delim = cmdline[matchLen];
- if (delim == 0 || delim == '\n' || delim == ' ') {
- return matchLen;
- }
- }
- return 0;
- }
-
- /* cmdline prefix is a relative path: We need to first match the basename at
- * cmdlineBaseOffset and then reverse match the cmdline prefix with the exe
- * suffix. But there is a catch: Some processes modify their cmdline in ways
- * that make htop's identification of the basename in cmdline unreliable.
- * For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to
- * "gdm-session-worker [pam/gdm-autologin]" and htop ends up with
- * procCmdlineBasenameOffset at "gdm-autologin]". This issue could arise with
- * chrome as well as it stores in cmdline its concatenated argument vector,
- * without NUL delimiter between the arguments (which may contain a '/')
- *
- * So if needed, we adjust cmdlineBaseOffset to the previous (if any)
- * component of the cmdline relative path, and retry the procedure. */
- bool delimFound; /* if valid basename delimiter found */
- do {
- /* match basename */
- matchLen = exeBaseLen + cmdlineBaseOffset;
- if (cmdlineBaseOffset < exeBaseOffset &&
- strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) {
- delim = cmdline[matchLen];
- if (delim == 0 || delim == '\n' || delim == ' ') {
- int i, j;
- /* reverse match the cmdline prefix and exe suffix */
- for (i = cmdlineBaseOffset - 1, j = exeBaseOffset - 1;
- i >= 0 && cmdline[i] == exe[j]; --i, --j)
- ;
- /* full match, with exe suffix being a valid relative path */
- if (i < 0 && exe[j] == '/') {
- return matchLen;
- }
- }
- }
- /* Try to find the previous potential cmdlineBaseOffset - it would be
- * preceded by '/' or nothing, and delimited by ' ' or '\n' */
- for (delimFound = false, cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) {
- if (delimFound) {
- if (cmdline[cmdlineBaseOffset - 1] == '/') {
- break;
- }
- } else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\n') {
- delimFound = true;
- }
- }
- } while (delimFound);
-
- return 0;
-}
-
-/* stpcpy, but also converts newlines to spaces */
-static inline char *stpcpyWithNewlineConversion(char *dstStr, const char *srcStr) {
- for (; *srcStr; ++srcStr) {
- *dstStr++ = (*srcStr == '\n') ? ' ' : *srcStr;
- }
- *dstStr = 0;
- return dstStr;
+bool LinuxProcess_isAutogroupEnabled(void) {
+ char buf[16];
+ if (xReadfile(PROCDIR "/sys/kernel/sched_autogroup_enabled", buf, sizeof(buf)) < 0)
+ return false;
+ return buf[0] == '1';
}
-/*
-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
-LinuxProcess_writeCommand() for coloring. The merged Command string is also
-returned by LinuxProcess_getCommandStr() for searching, sorting and filtering.
-*/
-void LinuxProcess_makeCommandStr(Process* this) {
- LinuxProcess *lp = (LinuxProcess *)this;
- LinuxProcessMergedCommand *mc = &lp->mergedCommand;
-
- bool showMergedCommand = this->settings->showMergedCommand;
- bool showProgramPath = this->settings->showProgramPath;
- bool searchCommInCmdline = this->settings->findCommInCmdline;
- bool stripExeFromCmdline = this->settings->stripExeFromCmdline;
-
- /* lp->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->cmdlineChanged &&
- !mc->commChanged &&
- !mc->exeChanged
- ) {
- return;
- }
-
- /* The field separtor "│" has been chosen such that it will not match any
- * valid string used for searching or filtering */
- const char *SEPARATOR = CRT_treeStr[TREE_STR_VERT];
- const int SEPARATOR_LEN = strlen(SEPARATOR);
-
- /* Check for any changed fields since we last built this string */
- if (mc->cmdlineChanged || mc->commChanged || mc->exeChanged) {
- free(mc->str);
- /* Accommodate the column text, two field separators and terminating NUL */
- mc->str = xCalloc(1, mc->maxLen + 2*SEPARATOR_LEN + 1);
- }
-
- /* Preserve the settings used in this run */
- mc->prevMergeSet = showMergedCommand;
- mc->prevPathSet = showProgramPath;
- mc->prevCommSet = searchCommInCmdline;
- mc->prevCmdlineSet = stripExeFromCmdline;
-
- /* Mark everything as unchanged */
- mc->cmdlineChanged = false;
- mc->commChanged = false;
- mc->exeChanged = false;
-
- /* Clear any separators */
- mc->sep1 = 0;
- mc->sep2 = 0;
-
- /* Clear any highlighting locations */
- mc->baseStart = 0;
- mc->baseEnd = 0;
- mc->commStart = 0;
- mc->commEnd = 0;
-
- const char *cmdline = this->comm;
- const char *procExe = lp->procExe;
- const char *procComm = lp->procComm;
-
- char *strStart = mc->str;
- char *str = strStart;
-
- int cmdlineBasenameOffset = lp->procCmdlineBasenameOffset;
-
- if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */
- if (showMergedCommand && !procExe && procComm && strlen(procComm)) { /* Prefix column with comm */
- if (strncmp(cmdline + cmdlineBasenameOffset, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) {
- mc->commStart = 0;
- mc->commEnd = strlen(procComm);
-
- str = stpcpy(str, procComm);
-
- mc->sep1 = str - strStart;
- str = stpcpy(str, SEPARATOR);
- }
- }
-
- if (showProgramPath) {
- (void) stpcpyWithNewlineConversion(str, cmdline);
- mc->baseStart = cmdlineBasenameOffset;
- mc->baseEnd = lp->procCmdlineBasenameEnd;
- } else {
- (void) stpcpyWithNewlineConversion(str, cmdline + cmdlineBasenameOffset);
- mc->baseStart = 0;
- mc->baseEnd = lp->procCmdlineBasenameEnd - cmdlineBasenameOffset;
- }
-
- if (mc->sep1) {
- mc->baseStart += str - strStart - SEPARATOR_LEN + 1;
- mc->baseEnd += str - strStart - SEPARATOR_LEN + 1;
- }
-
- return;
- }
-
- int exeLen = lp->procExeLen;
- int exeBasenameOffset = lp->procExeBasenameOffset;
- int exeBasenameLen = exeLen - exeBasenameOffset;
-
- /* Start with copying exe */
- if (showProgramPath) {
- str = stpcpy(str, procExe);
- mc->baseStart = exeBasenameOffset;
- mc->baseEnd = exeLen;
+bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) {
+ char buffer[256];
+ xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", this->pid);
+
+ FILE* file = fopen(buffer, "r+");
+ if (!file)
+ return false;
+
+ long int identity;
+ int nice;
+ int ok = fscanf(file, "/autogroup-%ld nice %d", &identity, &nice);
+ bool success;
+ if (ok == 2) {
+ rewind(file);
+ xSnprintf(buffer, sizeof(buffer), "%d", nice + delta.i);
+ success = fputs(buffer, file) > 0;
} else {
- str = stpcpy(str, procExe + exeBasenameOffset);
- mc->baseStart = 0;
- mc->baseEnd = exeBasenameLen;
+ success = false;
}
- mc->sep1 = 0;
- mc->sep2 = 0;
-
- int commStart = 0;
- int commEnd = 0;
- bool commInCmdline = false;
-
- /* Try to match procComm with procExe's basename: This is reliable (predictable) */
- if (strncmp(procExe + exeBasenameOffset, procComm, TASK_COMM_LEN - 1) == 0) {
- commStart = mc->baseStart;
- commEnd = mc->baseEnd;
- } else if (searchCommInCmdline) {
- /* commStart/commEnd will be adjusted later along with cmdline */
- commInCmdline = findCommInCmdline(procComm, cmdline, cmdlineBasenameOffset, &commStart, &commEnd);
- }
-
- int matchLen = matchCmdlinePrefixWithExeSuffix(cmdline, cmdlineBasenameOffset, procExe, exeBasenameOffset, exeBasenameLen);
-
- /* Note: commStart, commEnd are offsets into RichString. But the multibyte
- * separator (with size SEPARATOR_LEN) has size 1 in RichString. The offset
- * adjustments below reflect this. */
- if (commEnd) {
- mc->unmatchedExe = !matchLen;
-
- if (matchLen) {
- /* strip the matched exe prefix */
- cmdline += matchLen;
-
- if (commInCmdline) {
- commStart += str - strStart - matchLen;
- commEnd += str - strStart - matchLen;
- }
- } else {
- /* cmdline will be a separate field */
- mc->sep1 = str - strStart;
- str = stpcpy(str, SEPARATOR);
-
- if (commInCmdline) {
- commStart += str - strStart - SEPARATOR_LEN + 1;
- commEnd += str - strStart - SEPARATOR_LEN + 1;
- }
- }
-
- mc->separateComm = false; /* procComm merged */
- } else {
- mc->sep1 = str - strStart;
- str = stpcpy(str, SEPARATOR);
-
- commStart = str - strStart - SEPARATOR_LEN + 1;
- str = stpcpy(str, procComm);
- commEnd = str - strStart - SEPARATOR_LEN + 1; /* or commStart + strlen(procComm) */
-
- mc->unmatchedExe = !matchLen;
-
- if (matchLen) {
- if (stripExeFromCmdline) {
- cmdline += matchLen;
- }
- }
-
- if (*cmdline) {
- mc->sep2 = str - strStart - SEPARATOR_LEN + 1;
- str = stpcpy(str, SEPARATOR);
- }
-
- mc->separateComm = true; /* procComm a separate field */
- }
-
- /* Display cmdline if it hasn't been consumed by procExe */
- if (*cmdline) {
- (void) stpcpyWithNewlineConversion(str, cmdline);
- }
-
- mc->commStart = commStart;
- mc->commEnd = commEnd;
-}
-
-static void LinuxProcess_writeCommand(const Process* this, int attr, int baseAttr, RichString* str) {
- const LinuxProcess *lp = (const LinuxProcess *)this;
- const LinuxProcessMergedCommand *mc = &lp->mergedCommand;
-
- int strStart = RichString_size(str);
-
- int baseStart = strStart + lp->mergedCommand.baseStart;
- int baseEnd = strStart + lp->mergedCommand.baseEnd;
- int commStart = strStart + lp->mergedCommand.commStart;
- int commEnd = strStart + lp->mergedCommand.commEnd;
-
- int commAttr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
-
- bool highlightBaseName = this->settings->highlightBaseName;
-
- if(lp->procExeDeleted)
- baseAttr = CRT_colors[FAILED_READ];
-
- RichString_append(str, attr, lp->mergedCommand.str);
-
- if (lp->mergedCommand.commEnd) {
- if (!lp->mergedCommand.separateComm && commStart == baseStart && highlightBaseName) {
- /* If it was matched with procExe's basename, make it bold if needed */
- if (commEnd > baseEnd) {
- RichString_setAttrn(str, A_BOLD | baseAttr, baseStart, baseEnd - 1);
- RichString_setAttrn(str, A_BOLD | commAttr, baseEnd, commEnd - 1);
- } else if (commEnd < baseEnd) {
- RichString_setAttrn(str, A_BOLD | commAttr, commStart, commEnd - 1);
- RichString_setAttrn(str, A_BOLD | baseAttr, commEnd, baseEnd - 1);
- } else {
- // Actually should be highlighted commAttr, but marked baseAttr to reduce visual noise
- RichString_setAttrn(str, A_BOLD | baseAttr, commStart, commEnd - 1);
- }
-
- baseStart = baseEnd;
- } else {
- RichString_setAttrn(str, commAttr, commStart, commEnd - 1);
- }
- }
-
- if (baseStart < baseEnd && highlightBaseName) {
- RichString_setAttrn(str, baseAttr, baseStart, baseEnd - 1);
- }
-
- if (mc->sep1)
- RichString_setAttrn(str, CRT_colors[FAILED_READ], strStart + mc->sep1, strStart + mc->sep1);
- if (mc->sep2)
- RichString_setAttrn(str, CRT_colors[FAILED_READ], strStart + mc->sep2, strStart + mc->sep2);
-}
-
-static void LinuxProcess_writeCommandField(const Process *this, RichString *str, char *buffer, int n, int attr) {
- /* This code is from Process_writeField for COMM, but we invoke
- * LinuxProcess_writeCommand to display
- * /proc/pid/exe (or its basename)│/proc/pid/comm│/proc/pid/cmdline */
- int baseattr = CRT_colors[PROCESS_BASENAME];
- if (this->settings->highlightThreads && Process_isThread(this)) {
- attr = CRT_colors[PROCESS_THREAD];
- baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
- }
- if (!this->settings->treeView || this->indent == 0) {
- LinuxProcess_writeCommand(this, attr, baseattr, str);
- } else {
- char* buf = buffer;
- int maxIndent = 0;
- bool lastItem = (this->indent < 0);
- int indent = (this->indent < 0 ? -this->indent : this->indent);
- int vertLen = strlen(CRT_treeStr[TREE_STR_VERT]);
-
- for (int i = 0; i < 32; i++) {
- if (indent & (1U << i)) {
- maxIndent = i+1;
- }
- }
- for (int i = 0; i < maxIndent - 1; i++) {
- if (indent & (1 << i)) {
- if (buf - buffer + (vertLen + 3) > n) {
- break;
- }
- buf = stpcpy(buf, CRT_treeStr[TREE_STR_VERT]);
- buf = stpcpy(buf, " ");
- } else {
- if (buf - buffer + 4 > n) {
- break;
- }
- buf = stpcpy(buf, " ");
- }
- }
- n -= (buf - buffer);
- const char* draw = CRT_treeStr[lastItem ? (this->settings->direction == 1 ? TREE_STR_BEND : TREE_STR_TEND) : TREE_STR_RTEE];
- xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
- RichString_append(str, CRT_colors[PROCESS_TREE], buffer);
- LinuxProcess_writeCommand(this, attr, baseattr, str);
- }
+ fclose(file);
+ return success;
}
static void LinuxProcess_writeField(const Process* this, RichString* str, ProcessField field) {
@@ -619,49 +197,39 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
size_t n = sizeof(buffer) - 1;
- switch ((int)field) {
- case TTY_NR: {
- if (lp->ttyDevice) {
- xSnprintf(buffer, n, "%-9s", lp->ttyDevice + 5 /* skip "/dev/" */);
- } else {
- attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "? ");
- }
- break;
- }
- case CMINFLT: Process_colorNumber(str, lp->cminflt, coloring); return;
- case CMAJFLT: Process_colorNumber(str, lp->cmajflt, coloring); return;
- case M_DRS: Process_humanNumber(str, lp->m_drs * CRT_pageSizeKB, coloring); return;
- case M_DT: Process_humanNumber(str, lp->m_dt * CRT_pageSizeKB, coloring); return;
+ 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 M_LRS:
if (lp->m_lrs) {
- Process_humanNumber(str, lp->m_lrs * CRT_pageSizeKB, coloring);
+ Process_printBytes(str, lp->m_lrs * pageSize, coloring);
return;
}
attr = CRT_colors[PROCESS_SHADOW];
xSnprintf(buffer, n, " N/A ");
break;
- case M_TRS: Process_humanNumber(str, lp->m_trs * CRT_pageSizeKB, coloring); return;
- case M_SHARE: Process_humanNumber(str, lp->m_share * CRT_pageSizeKB, coloring); return;
- case M_PSS: Process_humanNumber(str, lp->m_pss, coloring); return;
- case M_SWAP: Process_humanNumber(str, lp->m_swap, coloring); return;
- case M_PSSWP: Process_humanNumber(str, lp->m_psswp, coloring); return;
- case UTIME: Process_printTime(str, lp->utime); return;
- case STIME: Process_printTime(str, lp->stime); return;
- case CUTIME: Process_printTime(str, lp->cutime); return;
- case CSTIME: Process_printTime(str, lp->cstime); return;
- case RCHAR: Process_colorNumber(str, lp->io_rchar, coloring); return;
- case WCHAR: Process_colorNumber(str, lp->io_wchar, coloring); return;
- case SYSCR: Process_colorNumber(str, lp->io_syscr, coloring); return;
- case SYSCW: Process_colorNumber(str, lp->io_syscw, coloring); return;
- case RBYTES: Process_colorNumber(str, lp->io_read_bytes, coloring); return;
- case WBYTES: Process_colorNumber(str, lp->io_write_bytes, coloring); return;
- case CNCLWB: Process_colorNumber(str, lp->io_cancelled_write_bytes, coloring); return;
- case IO_READ_RATE: Process_outputRate(str, buffer, n, lp->io_rate_read_bps, coloring); return;
- case IO_WRITE_RATE: Process_outputRate(str, buffer, n, lp->io_rate_write_bps, coloring); return;
+ 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 = NAN;
+ 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))
@@ -670,16 +238,18 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
totalRate = lp->io_rate_write_bps;
else
totalRate = NAN;
- Process_outputRate(str, buffer, n, totalRate, coloring); return;
+ Process_printRate(str, totalRate, coloring);
+ return;
}
#ifdef HAVE_OPENVZ
case CTID: xSnprintf(buffer, n, "%-8s ", lp->ctid ? lp->ctid : ""); break;
- case VPID: xSnprintf(buffer, n, Process_pidFormat, lp->vpid); break;
+ case VPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, lp->vpid); break;
#endif
#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 ", Process_fieldWidths[CGROUP], Process_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break;
+ case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CCGROUP], Process_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break;
case OOM: xSnprintf(buffer, n, "%4u ", lp->oom); break;
case IO_PRIORITY: {
int klass = IOPriority_class(lp->ioPriority);
@@ -700,9 +270,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
break;
}
#ifdef HAVE_DELAYACCT
- case PERCENT_CPU_DELAY: LinuxProcess_printDelay(lp->cpu_delay_percent, buffer, n); break;
- case PERCENT_IO_DELAY: LinuxProcess_printDelay(lp->blkio_delay_percent, buffer, n); break;
- case PERCENT_SWAP_DELAY: LinuxProcess_printDelay(lp->swapin_delay_percent, buffer, n); break;
+ case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break;
+ case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break;
+ case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break;
#endif
case CTXT:
if (lp->ctxt_diff > 1000) {
@@ -710,169 +280,133 @@ 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 COMM: {
- if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !lp->mergedCommand.str) {
- Process_writeField(this, str, field);
- } else {
- LinuxProcess_writeCommandField(this, str, buffer, n, attr);
- }
- return;
- }
- case PROC_COMM: {
- if (lp->procComm) {
- attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
- /* 15 being (TASK_COMM_LEN - 1) */
- xSnprintf(buffer, n, "%-15.15s ", lp->procComm);
+ case SECATTR: snprintf(buffer, n, "%-*.*s ", Process_fieldWidths[SECATTR], Process_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break;
+ case AUTOGROUP_ID:
+ if (lp->autogroup_id != -1) {
+ xSnprintf(buffer, n, "%4ld ", lp->autogroup_id);
} else {
attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "%-15.15s ", Process_isKernelThread(lp) ? kthreadID : "N/A");
+ xSnprintf(buffer, n, " N/A ");
}
break;
- }
- case PROC_EXE: {
- if (lp->procExe) {
- attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME];
- if (lp->procExeDeleted)
- attr = CRT_colors[FAILED_READ];
- xSnprintf(buffer, n, "%-15.15s ", lp->procExe + lp->procExeBasenameOffset);
+ case AUTOGROUP_NICE:
+ 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];
} else {
attr = CRT_colors[PROCESS_SHADOW];
- xSnprintf(buffer, n, "%-15.15s ", Process_isKernelThread(lp) ? kthreadID : "N/A");
- }
- break;
- }
- case CWD:
- if (!lp->cwd) {
- xSnprintf(buffer, n, "%-25s ", "N/A");
- attr = CRT_colors[PROCESS_SHADOW];
- } else if (String_startsWith(lp->cwd, "/proc/") && strstr(lp->cwd, " (deleted)") != NULL) {
- xSnprintf(buffer, n, "%-25s ", "main thread terminated");
- attr = CRT_colors[PROCESS_SHADOW];
- } else {
- xSnprintf(buffer, n, "%-25.25s ", lp->cwd);
+ xSnprintf(buffer, n, "N/A ");
}
break;
default:
Process_writeField(this, str, field);
return;
}
- RichString_append(str, attr, buffer);
+ RichString_appendAscii(str, attr, buffer);
}
-static long LinuxProcess_compare(const void* v1, const void* v2) {
- const LinuxProcess *p1, *p2;
- const Settings *settings = ((const Process*)v1)->settings;
+static double adjustNaN(double num) {
+ if (isnan(num))
+ return -0.0005;
- if (settings->direction == 1) {
- p1 = (const LinuxProcess*)v1;
- p2 = (const LinuxProcess*)v2;
- } else {
- p2 = (const LinuxProcess*)v1;
- p1 = (const LinuxProcess*)v2;
- }
+ return num;
+}
+
+static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const LinuxProcess* p1 = (const LinuxProcess*)v1;
+ const LinuxProcess* p2 = (const LinuxProcess*)v2;
- switch ((int)settings->sortKey) {
+ switch (key) {
case M_DRS:
- return SPACESHIP_NUMBER(p2->m_drs, p1->m_drs);
- case M_DT:
- return SPACESHIP_NUMBER(p2->m_dt, p1->m_dt);
+ return SPACESHIP_NUMBER(p1->m_drs, p2->m_drs);
case M_LRS:
- return SPACESHIP_NUMBER(p2->m_lrs, p1->m_lrs);
+ return SPACESHIP_NUMBER(p1->m_lrs, p2->m_lrs);
case M_TRS:
- return SPACESHIP_NUMBER(p2->m_trs, p1->m_trs);
+ return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs);
case M_SHARE:
- return SPACESHIP_NUMBER(p2->m_share, p1->m_share);
+ return SPACESHIP_NUMBER(p1->m_share, p2->m_share);
case M_PSS:
- return SPACESHIP_NUMBER(p2->m_pss, p1->m_pss);
+ return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss);
case M_SWAP:
- return SPACESHIP_NUMBER(p2->m_swap, p1->m_swap);
+ return SPACESHIP_NUMBER(p1->m_swap, p2->m_swap);
case M_PSSWP:
- return SPACESHIP_NUMBER(p2->m_psswp, p1->m_psswp);
+ return SPACESHIP_NUMBER(p1->m_psswp, p2->m_psswp);
case UTIME:
- return SPACESHIP_NUMBER(p2->utime, p1->utime);
+ return SPACESHIP_NUMBER(p1->utime, p2->utime);
case CUTIME:
- return SPACESHIP_NUMBER(p2->cutime, p1->cutime);
+ return SPACESHIP_NUMBER(p1->cutime, p2->cutime);
case STIME:
- return SPACESHIP_NUMBER(p2->stime, p1->stime);
+ return SPACESHIP_NUMBER(p1->stime, p2->stime);
case CSTIME:
- return SPACESHIP_NUMBER(p2->cstime, p1->cstime);
+ return SPACESHIP_NUMBER(p1->cstime, p2->cstime);
case RCHAR:
- return SPACESHIP_NUMBER(p2->io_rchar, p1->io_rchar);
+ return SPACESHIP_NUMBER(p1->io_rchar, p2->io_rchar);
case WCHAR:
- return SPACESHIP_NUMBER(p2->io_wchar, p1->io_wchar);
+ return SPACESHIP_NUMBER(p1->io_wchar, p2->io_wchar);
case SYSCR:
- return SPACESHIP_NUMBER(p2->io_syscr, p1->io_syscr);
+ return SPACESHIP_NUMBER(p1->io_syscr, p2->io_syscr);
case SYSCW:
- return SPACESHIP_NUMBER(p2->io_syscw, p1->io_syscw);
+ return SPACESHIP_NUMBER(p1->io_syscw, p2->io_syscw);
case RBYTES:
- return SPACESHIP_NUMBER(p2->io_read_bytes, p1->io_read_bytes);
+ return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes);
case WBYTES:
- return SPACESHIP_NUMBER(p2->io_write_bytes, p1->io_write_bytes);
+ return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes);
case CNCLWB:
- return SPACESHIP_NUMBER(p2->io_cancelled_write_bytes, p1->io_cancelled_write_bytes);
+ return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes);
case IO_READ_RATE:
- return SPACESHIP_NUMBER(p2->io_rate_read_bps, p1->io_rate_read_bps);
+ return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps), adjustNaN(p2->io_rate_read_bps));
case IO_WRITE_RATE:
- return SPACESHIP_NUMBER(p2->io_rate_write_bps, p1->io_rate_write_bps);
+ return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_write_bps));
case IO_RATE:
- return SPACESHIP_NUMBER(p2->io_rate_read_bps + p2->io_rate_write_bps, p1->io_rate_read_bps + p1->io_rate_write_bps);
+ 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));
#ifdef HAVE_OPENVZ
case CTID:
return SPACESHIP_NULLSTR(p1->ctid, p2->ctid);
case VPID:
- return SPACESHIP_NUMBER(p2->vpid, p1->vpid);
+ return SPACESHIP_NUMBER(p1->vpid, p2->vpid);
#endif
#ifdef HAVE_VSERVER
case VXID:
- return SPACESHIP_NUMBER(p2->vxid, p1->vxid);
+ return SPACESHIP_NUMBER(p1->vxid, p2->vxid);
#endif
case CGROUP:
return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);
+ case CCGROUP:
+ return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short);
case OOM:
- return SPACESHIP_NUMBER(p2->oom, p1->oom);
+ return SPACESHIP_NUMBER(p1->oom, p2->oom);
#ifdef HAVE_DELAYACCT
case PERCENT_CPU_DELAY:
- return SPACESHIP_NUMBER(p2->cpu_delay_percent, p1->cpu_delay_percent);
+ return SPACESHIP_NUMBER(p1->cpu_delay_percent, p2->cpu_delay_percent);
case PERCENT_IO_DELAY:
- return SPACESHIP_NUMBER(p2->blkio_delay_percent, p1->blkio_delay_percent);
+ return SPACESHIP_NUMBER(p1->blkio_delay_percent, p2->blkio_delay_percent);
case PERCENT_SWAP_DELAY:
- return SPACESHIP_NUMBER(p2->swapin_delay_percent, p1->swapin_delay_percent);
+ return SPACESHIP_NUMBER(p1->swapin_delay_percent, p2->swapin_delay_percent);
#endif
case IO_PRIORITY:
return SPACESHIP_NUMBER(LinuxProcess_effectiveIOPriority(p1), LinuxProcess_effectiveIOPriority(p2));
case CTXT:
- return SPACESHIP_NUMBER(p2->ctxt_diff, p1->ctxt_diff);
+ return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff);
case SECATTR:
return SPACESHIP_NULLSTR(p1->secattr, p2->secattr);
- case PROC_COMM: {
- const char *comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : "");
- const char *comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : "");
- return strcmp(comm1, comm2);
- }
- case PROC_EXE: {
- const char *exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : "");
- const char *exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : "");
- return strcmp(exe1, exe2);
- }
- case CWD:
- return SPACESHIP_NULLSTR(p1->cwd, p2->cwd);
+ case AUTOGROUP_ID:
+ return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id);
+ case AUTOGROUP_NICE:
+ return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice);
default:
- return Process_compare(v1, v2);
+ return Process_compareByKey_Base(v1, v2, key);
}
}
-bool Process_isThread(const Process* this) {
- return (Process_isUserlandThread(this) || Process_isKernelThread(this));
-}
-
const ProcessClass LinuxProcess_class = {
.super = {
.extends = Class(Process),
.display = Process_display,
.delete = Process_delete,
- .compare = LinuxProcess_compare
+ .compare = Process_compare
},
.writeField = LinuxProcess_writeField,
- .getCommandStr = LinuxProcess_getCommandStr
+ .compareByKey = LinuxProcess_compareByKey
};
diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h
index ad396fb..3e5d380 100644
--- a/linux/LinuxProcess.h
+++ b/linux/LinuxProcess.h
@@ -4,135 +4,35 @@
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 "IOPriority.h"
+#include "linux/IOPriority.h"
#include "Object.h"
#include "Process.h"
#include "Settings.h"
-#define PROCESS_FLAG_LINUX_IOPRIO 0x00000100
-#define PROCESS_FLAG_LINUX_OPENVZ 0x00000200
-#define PROCESS_FLAG_LINUX_VSERVER 0x00000400
-#define PROCESS_FLAG_LINUX_CGROUP 0x00000800
-#define PROCESS_FLAG_LINUX_OOM 0x00001000
-#define PROCESS_FLAG_LINUX_SMAPS 0x00002000
-#define PROCESS_FLAG_LINUX_CTXT 0x00004000
-#define PROCESS_FLAG_LINUX_SECATTR 0x00008000
-#define PROCESS_FLAG_LINUX_LRS_FIX 0x00010000
-#define PROCESS_FLAG_LINUX_CWD 0x00020000
-
-typedef enum UnsupportedProcessFields {
- FLAGS = 9,
- ITREALVALUE = 20,
- VSIZE = 22,
- RSS = 23,
- RLIM = 24,
- STARTCODE = 25,
- ENDCODE = 26,
- STARTSTACK = 27,
- KSTKESP = 28,
- KSTKEIP = 29,
- SIGNAL = 30,
- BLOCKED = 31,
- SSIGIGNORE = 32,
- SIGCATCH = 33,
- WCHAN = 34,
- NSWAP = 35,
- CNSWAP = 36,
- EXIT_SIGNAL = 37,
-} UnsupportedProcessField;
-
-typedef enum LinuxProcessFields {
- CMINFLT = 11,
- CMAJFLT = 13,
- UTIME = 14,
- STIME = 15,
- CUTIME = 16,
- CSTIME = 17,
- M_SHARE = 41,
- M_TRS = 42,
- M_DRS = 43,
- M_LRS = 44,
- M_DT = 45,
- #ifdef HAVE_OPENVZ
- CTID = 100,
- VPID = 101,
- #endif
- #ifdef HAVE_VSERVER
- VXID = 102,
- #endif
- RCHAR = 103,
- WCHAR = 104,
- SYSCR = 105,
- SYSCW = 106,
- RBYTES = 107,
- WBYTES = 108,
- CNCLWB = 109,
- IO_READ_RATE = 110,
- IO_WRITE_RATE = 111,
- IO_RATE = 112,
- CGROUP = 113,
- OOM = 114,
- IO_PRIORITY = 115,
- #ifdef HAVE_DELAYACCT
- PERCENT_CPU_DELAY = 116,
- PERCENT_IO_DELAY = 117,
- PERCENT_SWAP_DELAY = 118,
- #endif
- M_PSS = 119,
- M_SWAP = 120,
- M_PSSWP = 121,
- CTXT = 122,
- SECATTR = 123,
- PROC_COMM = 124,
- PROC_EXE = 125,
- CWD = 126,
- LAST_PROCESSFIELD = 127,
-} LinuxProcessField;
-
-/* LinuxProcessMergedCommand is populated by LinuxProcess_makeCommandStr: It
- * contains the merged Command string, and the information needed by
- * LinuxProcess_writeCommand to color the string. str will be NULL for kernel
- * threads and zombies */
-typedef struct LinuxProcessMergedCommand_ {
- char *str; /* merged Command string */
- int maxLen; /* maximum expected length of Command string */
- int baseStart; /* basename's start offset */
- int baseEnd; /* basename's end offset */
- int commStart; /* comm's start offset */
- int commEnd; /* comm's end offset */
- int sep1; /* first field separator, used if non-zero */
- int sep2; /* second field separator, used if non-zero */
- bool separateComm; /* whether comm is a separate field */
- bool unmatchedExe; /* whether exe matched with cmdline */
- bool cmdlineChanged; /* whether cmdline changed */
- bool exeChanged; /* whether exe changed */
- bool commChanged; /* whether comm changed */
- bool prevMergeSet; /* whether showMergedCommand was set */
- bool prevPathSet; /* whether showProgramPath was set */
- bool prevCommSet; /* whether findCommInCmdline was set */
- bool prevCmdlineSet; /* whether findCommInCmdline was set */
-} LinuxProcessMergedCommand;
+#define PROCESS_FLAG_LINUX_IOPRIO 0x00000100
+#define PROCESS_FLAG_LINUX_OPENVZ 0x00000200
+#define PROCESS_FLAG_LINUX_VSERVER 0x00000400
+#define PROCESS_FLAG_LINUX_CGROUP 0x00000800
+#define PROCESS_FLAG_LINUX_OOM 0x00001000
+#define PROCESS_FLAG_LINUX_SMAPS 0x00002000
+#define PROCESS_FLAG_LINUX_CTXT 0x00004000
+#define PROCESS_FLAG_LINUX_SECATTR 0x00008000
+#define PROCESS_FLAG_LINUX_LRS_FIX 0x00010000
+#define PROCESS_FLAG_LINUX_DELAYACCT 0x00040000
+#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000
typedef struct LinuxProcess_ {
Process super;
- char *procComm;
- char *procExe;
- int procExeLen;
- int procExeBasenameOffset;
- bool procExeDeleted;
- int procCmdlineBasenameOffset;
- int procCmdlineBasenameEnd;
- LinuxProcessMergedCommand mergedCommand;
- bool isKernelThread;
IOPriority ioPriority;
unsigned long int cminflt;
unsigned long int cmajflt;
@@ -147,18 +47,40 @@ typedef struct LinuxProcess_ {
long m_trs;
long m_drs;
long m_lrs;
- long m_dt;
+
+ /* Process flags */
+ unsigned long int flags;
+
+ /* Data read (in bytes) */
unsigned long long io_rchar;
+
+ /* Data written (in bytes) */
unsigned long long io_wchar;
+
+ /* Number of read(2) syscalls */
unsigned long long io_syscr;
+
+ /* Number of write(2) syscalls */
unsigned long long io_syscw;
+
+ /* Storage data read (in bytes) */
unsigned long long io_read_bytes;
+
+ /* Storage data written (in bytes) */
unsigned long long io_write_bytes;
+
+ /* Storage data cancelled (in bytes) */
unsigned long long io_cancelled_write_bytes;
- unsigned long long io_rate_read_time;
- unsigned long long io_rate_write_time;
+
+ /* Point in time of last io scan (in milliseconds elapsed since the Epoch) */
+ unsigned long long io_last_scan_time_ms;
+
+ /* Storage data read (in bytes per second) */
double io_rate_read_bps;
+
+ /* Storage data written (in bytes per second) */
double io_rate_write_bps;
+
#ifdef HAVE_OPENVZ
char* ctid;
pid_t vpid;
@@ -167,8 +89,8 @@ typedef struct LinuxProcess_ {
unsigned int vxid;
#endif
char* cgroup;
+ char* cgroup_short;
unsigned int oom;
- char* ttyDevice;
#ifdef HAVE_DELAYACCT
unsigned long long int delay_read_time;
unsigned long long cpu_delay_total;
@@ -182,20 +104,17 @@ typedef struct LinuxProcess_ {
unsigned long ctxt_diff;
char* secattr;
unsigned long long int last_mlrs_calctime;
- char* cwd;
-} LinuxProcess;
-
-#define Process_isKernelThread(_process) (((const LinuxProcess*)(_process))->isKernelThread)
-static inline bool Process_isUserlandThread(const Process* this) {
- return this->pid != this->tgid;
-}
+ /* Autogroup scheduling (CFS) information */
+ long int autogroup_id;
+ int autogroup_nice;
+} LinuxProcess;
-extern long long btime;
+extern int pageSize;
-extern ProcessFieldData Process_fields[];
+extern int pageSizeKB;
-extern ProcessPidColumn Process_pidColumns[];
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
extern const ProcessClass LinuxProcess_class;
@@ -207,9 +126,9 @@ IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this);
bool LinuxProcess_setIOPriority(Process* this, Arg ioprio);
-/* This function constructs the string that is displayed by
- * LinuxProcess_writeCommand and also returned by LinuxProcess_getCommandStr */
-void LinuxProcess_makeCommandStr(Process *this);
+bool LinuxProcess_isAutogroupEnabled(void);
+
+bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta);
bool Process_isThread(const Process* this);
diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c
index b9ba247..eca9459 100644
--- a/linux/LinuxProcessList.c
+++ b/linux/LinuxProcessList.c
@@ -1,15 +1,16 @@
/*
htop - LinuxProcessList.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" // IWYU pragma: keep
-#include "LinuxProcessList.h"
+#include "linux/LinuxProcessList.h"
#include <assert.h>
+#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
@@ -20,9 +21,10 @@ in the source distribution for its full text.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <strings.h>
+#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
-#include <sys/time.h>
#include <sys/types.h>
#ifdef HAVE_DELAYACCT
@@ -39,14 +41,16 @@ in the source distribution for its full text.
#include "Compat.h"
#include "CRT.h"
-#include "LinuxProcess.h"
#include "Macros.h"
#include "Object.h"
#include "Process.h"
#include "Settings.h"
#include "XUtils.h"
+#include "linux/CGroupUtils.h"
+#include "linux/LinuxProcess.h"
+#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep
-#ifdef MAJOR_IN_MKDEV
+#if defined(MAJOR_IN_MKDEV)
#include <sys/mkdev.h>
#elif defined(MAJOR_IN_SYSMACROS)
#include <sys/sysmacros.h>
@@ -56,6 +60,18 @@ 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
+
+/* Not exposed yet. Defined at include/linux/sched.h */
+#ifndef PF_KTHREAD
+#define PF_KTHREAD 0x00200000
+#endif
+
+static long long btime = -1;
+
+static long jiffy;
static FILE* fopenat(openat_arg_t openatArg, const char* pathname, const char* mode) {
assert(String_eq(mode, "r")); /* only currently supported mode */
@@ -92,12 +108,12 @@ static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) {
int numDrivers = 0;
int allocd = 10;
- ttyDrivers = xMalloc(sizeof(TtyDriver) * allocd);
+ ttyDrivers = xMallocArray(allocd, sizeof(TtyDriver));
char* at = buf;
while (*at != '\0') {
at = strchr(at, ' '); // skip first token
while (*at == ' ') at++; // skip spaces
- char* token = at; // mark beginning of path
+ 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
@@ -126,7 +142,7 @@ static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) {
numDrivers++;
if (numDrivers == allocd) {
allocd += 10;
- ttyDrivers = xRealloc(ttyDrivers, sizeof(TtyDriver) * allocd);
+ ttyDrivers = xReallocArray(ttyDrivers, allocd, sizeof(TtyDriver));
}
}
numDrivers++;
@@ -151,99 +167,163 @@ static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) {
#endif
-static int LinuxProcessList_computeCPUcount(void) {
- FILE* file = fopen(PROCSTATFILE, "r");
- if (file == NULL) {
- CRT_fatalError("Cannot open " PROCSTATFILE);
- }
+static unsigned int scanAvailableCPUsFromCPUinfo(LinuxProcessList* this) {
+ FILE* file = fopen(PROCCPUINFOFILE, "r");
+ if (file == NULL)
+ return this->super.existingCPUs;
- int cpus = 0;
- char buffer[PROC_LINE_LENGTH + 1];
- while (fgets(buffer, sizeof(buffer), file)) {
- if (String_startsWith(buffer, "cpu")) {
- cpus++;
- }
- }
+ unsigned int availableCPUs = 0;
- fclose(file);
+ while (!feof(file)) {
+ char buffer[PROC_LINE_LENGTH];
+
+ if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
+ break;
- /* subtract raw cpu entry */
- if (cpus > 0) {
- cpus--;
+ if (String_startsWith(buffer, "processor"))
+ availableCPUs++;
}
- return cpus;
+ fclose(file);
+
+ return availableCPUs ? availableCPUs : 1;
}
-static void LinuxProcessList_updateCPUcount(LinuxProcessList* this) {
- ProcessList* pl = &(this->super);
- int cpus = LinuxProcessList_computeCPUcount();
- if (cpus == 0 || cpus == pl->cpuCount)
+static void LinuxProcessList_updateCPUcount(ProcessList* super) {
+ /* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF
+ * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD
+ */
+
+ LinuxProcessList* this = (LinuxProcessList*) super;
+ unsigned int existing = 0, active = 0;
+
+ // Initialize the cpuData array before anything else.
+ if (!this->cpuData) {
+ this->cpuData = xCalloc(2, sizeof(CPUData));
+ this->cpuData[0].online = true; /* average is always "online" */
+ this->cpuData[1].online = true;
+ super->activeCPUs = 1;
+ super->existingCPUs = 1;
+ }
+
+ DIR* dir = opendir("/sys/devices/system/cpu");
+ if (!dir)
return;
- pl->cpuCount = cpus;
- free(this->cpus);
- this->cpus = xCalloc(cpus + 1, sizeof(CPUData));
+ 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++;
- for (int i = 0; i <= cpus; i++) {
- this->cpus[i].totalTime = 1;
- this->cpus[i].totalPeriod = 1;
+ /* readdir() iterates with no specific order */
+ unsigned int max = MAXIMUM(existing, id + 1);
+ if (max > currExisting) {
+ this->cpuData = xReallocArrayZero(this->cpuData, currExisting ? (currExisting + 1) : 0, max + /* aggregate */ 1, sizeof(CPUData));
+ this->cpuData[0].online = true; /* average is always "online" */
+ currExisting = max;
+ }
+
+ char buffer[8];
+ ssize_t res = xReadfileat(cpuDirFd, "online", buffer, sizeof(buffer));
+ /* If the file "online" does not exist or on failure count as active */
+ if (res < 1 || buffer[0] != '0') {
+ active++;
+ this->cpuData[id + 1].online = true;
+ } else {
+ this->cpuData[id + 1].online = false;
+ }
+
+ Compat_openatArgClose(cpuDirFd);
}
+
+ closedir(dir);
+
+ // return if no CPU is found
+ if (existing < 1)
+ return;
+
+ if (Running_containerized) {
+ /* LXC munges /proc/cpuinfo but not the /sys/devices/system/cpu/ files,
+ * so limit the visible CPUs to what the guest has been configured to see: */
+ currExisting = active = scanAvailableCPUsFromCPUinfo(this);
+ }
+
+#ifdef HAVE_SENSORS_SENSORS_H
+ /* When started with offline CPUs, libsensors does not monitor those,
+ * even when they become online. */
+ if (super->existingCPUs != 0 && (active > super->activeCPUs || currExisting > super->existingCPUs))
+ LibSensors_reload();
+#endif
+
+ super->activeCPUs = active;
+ assert(Running_containerized || (existing == currExisting));
+ super->existingCPUs = currExisting;
}
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+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, pidMatchList, userId);
+ ProcessList_init(pl, Class(LinuxProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
LinuxProcessList_initTtyDrivers(this);
- #ifdef HAVE_DELAYACCT
- LinuxProcessList_initNetlinkSocket(this);
- #endif
-
- // Check for /proc/*/smaps_rollup availability (improves smaps parsing speed, Linux 4.14+)
- FILE* file = fopen(PROCDIR "/self/smaps_rollup", "r");
- if (file != NULL) {
- this->haveSmapsRollup = true;
- fclose(file);
- } else {
- this->haveSmapsRollup = false;
- }
+ // Initialize page size
+ pageSize = sysconf(_SC_PAGESIZE);
+ if (pageSize == -1)
+ CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)");
+ pageSizeKB = pageSize / ONE_K;
- // Read btime
- {
- FILE* statfile = fopen(PROCSTATFILE, "r");
- if (statfile == NULL) {
- CRT_fatalError("Cannot open " PROCSTATFILE);
- }
+ // Initialize clock ticks
+ jiffy = sysconf(_SC_CLK_TCK);
+ if (jiffy == -1)
+ CRT_fatalError("Cannot get clock ticks by sysconf(_SC_CLK_TCK)");
- while (true) {
- char buffer[PROC_LINE_LENGTH + 1];
- if (fgets(buffer, sizeof(buffer), statfile) == NULL) {
- CRT_fatalError("No btime in " PROCSTATFILE);
- } else if (String_startsWith(buffer, "btime ")) {
- if (sscanf(buffer, "btime %lld\n", &btime) != 1) {
- CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
- }
- break;
- }
- }
+ // Test /proc/PID/smaps_rollup availability (faster to parse, Linux 4.14+)
+ this->haveSmapsRollup = (access(PROCDIR "/self/smaps_rollup", R_OK) == 0);
- fclose(statfile);
+ // 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);
}
- // Initialize CPU count
- {
- int cpus = LinuxProcessList_computeCPUcount();
- pl->cpuCount = MAXIMUM(cpus, 1);
- this->cpus = xCalloc(cpus + 1, sizeof(CPUData));
+ fclose(statfile);
- for (int i = 0; i <= cpus; i++) {
- this->cpus[i].totalTime = 1;
- this->cpus[i].totalPeriod = 1;
- }
- }
+ if (btime == -1)
+ CRT_fatalError("No btime in " PROCSTATFILE);
+
+ // Initialize CPU count
+ LinuxProcessList_updateCPUcount(pl);
return pl;
}
@@ -251,7 +331,7 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
void ProcessList_delete(ProcessList* pl) {
LinuxProcessList* this = (LinuxProcessList*) pl;
ProcessList_done(pl);
- free(this->cpus);
+ free(this->cpuData);
if (this->ttyDrivers) {
for (int i = 0; this->ttyDrivers[i].path; i++) {
free(this->ttyDrivers[i].path);
@@ -267,104 +347,152 @@ void ProcessList_delete(ProcessList* pl) {
free(this);
}
-static inline unsigned long long LinuxProcess_adjustTime(unsigned long long t) {
- static long jiffy = -1;
- if (jiffy == -1) {
- errno = 0;
- jiffy = sysconf(_SC_CLK_TCK);
- if (errno || -1 == jiffy) {
- jiffy = -1;
- return t; // Assume 100Hz clock
- }
- }
+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, int* commLen) {
+/* Taken from: https://github.com/torvalds/linux/blob/64570fbc14f8d7cb3fe3995f20e26bc25ce4b2cc/fs/proc/array.c#L120 */
+static inline ProcessState LinuxProcessList_getProcessState(char state) {
+ switch (state) {
+ case 'S': return SLEEPING;
+ case 'X': return DEFUNCT;
+ case 'Z': return ZOMBIE;
+ case 't': return TRACED;
+ case 'T': return STOPPED;
+ case 'D': return UNINTERRUPTIBLE_WAIT;
+ case 'R': return RUNNING;
+ case 'P': return BLOCKED;
+ case 'I': return IDLE;
+ default: return UNKNOWN;
+ }
+}
+
+static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, char* command, size_t commLen) {
LinuxProcess* lp = (LinuxProcess*) process;
- const int commLenIn = *commLen;
- *commLen = 0;
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;
- int commsize = MINIMUM(end - location, commLenIn - 1);
- // deepcode ignore BufferOverflow: commsize is bounded by the allocated length passed in by commLen, saved into commLenIn
- memcpy(command, location, commsize);
- command[commsize] = '\0';
- *commLen = commsize;
+ String_safeStrncpy(command, location, MINIMUM((size_t)(end - location + 1), commLen));
+
location = end + 2;
- process->state = location[0];
+ /* (3) state - %c */
+ process->state = LinuxProcessList_getProcessState(location[0]);
location += 2;
+
+ /* (4) ppid - %d */
process->ppid = strtol(location, &location, 10);
location += 1;
- process->pgrp = strtoul(location, &location, 10);
+
+ /* (5) pgrp - %d */
+ process->pgrp = strtol(location, &location, 10);
location += 1;
- process->session = strtoul(location, &location, 10);
+
+ /* (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;
- process->flags = strtoul(location, &location, 10);
+
+ /* (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;
- lp->utime = LinuxProcess_adjustTime(strtoull(location, &location, 10));
+
+ /* (14) utime - %lu */
+ lp->utime = LinuxProcessList_adjustTime(strtoull(location, &location, 10));
location += 1;
- lp->stime = LinuxProcess_adjustTime(strtoull(location, &location, 10));
+
+ /* (15) stime - %lu */
+ lp->stime = LinuxProcessList_adjustTime(strtoull(location, &location, 10));
location += 1;
- lp->cutime = LinuxProcess_adjustTime(strtoull(location, &location, 10));
+
+ /* (16) cutime - %ld */
+ lp->cutime = LinuxProcessList_adjustTime(strtoull(location, &location, 10));
location += 1;
- lp->cstime = LinuxProcess_adjustTime(strtoull(location, &location, 10));
+
+ /* (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 + LinuxProcess_adjustTime(strtoll(location, &location, 10)) / 100;
+ process->starttime_ctime = btime + LinuxProcessList_adjustTime(strtoll(location, &location, 10)) / 100;
} else {
- location = strchr(location, ' ') + 1;
+ location = strchr(location, ' ');
}
location += 1;
- for (int i = 0; i < 15; i++) {
+
+ /* Skip (23) - (38) */
+ for (int i = 0; i < 16; i++) {
location = strchr(location, ' ') + 1;
}
- process->exit_signal = strtol(location, &location, 10);
- 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_statProcessDir(Process* process, openat_arg_t procFd) {
+static bool LinuxProcessList_updateUser(ProcessList* processList, Process* process, openat_arg_t procFd) {
struct stat sstat;
#ifdef HAVE_OPENAT
int statok = fstat(procFd, &sstat);
@@ -373,32 +501,38 @@ static bool LinuxProcessList_statProcessDir(Process* process, openat_arg_t procF
#endif
if (statok == -1)
return false;
- process->st_uid = sstat.st_uid;
+
+ 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 now) {
+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 = -1LL;
- process->io_wchar = -1LL;
- process->io_syscr = -1LL;
- process->io_syscw = -1LL;
- process->io_read_bytes = -1LL;
- process->io_write_bytes = -1LL;
- process->io_cancelled_write_bytes = -1LL;
- process->io_rate_read_time = -1LL;
- process->io_rate_write_time = -1LL;
+ process->io_rchar = ULLONG_MAX;
+ process->io_wchar = ULLONG_MAX;
+ process->io_syscr = ULLONG_MAX;
+ process->io_syscw = ULLONG_MAX;
+ process->io_read_bytes = ULLONG_MAX;
+ process->io_write_bytes = ULLONG_MAX;
+ process->io_cancelled_write_bytes = ULLONG_MAX;
+ process->io_last_scan_time_ms = realtimeMs;
return;
}
unsigned long long last_read = process->io_read_bytes;
unsigned long long last_write = process->io_write_bytes;
+ unsigned long long time_delta = realtimeMs > process->io_last_scan_time_ms ? realtimeMs - process->io_last_scan_time_ms : 0;
+
char* buf = buffer;
- char* line = NULL;
+ const char* line;
while ((line = strsep(&buf, "\n")) != NULL) {
switch (line[0]) {
case 'r':
@@ -406,9 +540,7 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc
process->io_rchar = strtoull(line + 7, NULL, 10);
} else if (String_startsWith(line + 1, "ead_bytes: ")) {
process->io_read_bytes = strtoull(line + 12, NULL, 10);
- process->io_rate_read_bps =
- ((double)(process->io_read_bytes - last_read)) / (((double)(now - process->io_rate_read_time)) / 1000);
- process->io_rate_read_time = now;
+ process->io_rate_read_bps = time_delta ? (process->io_read_bytes - last_read) * /*ms to s*/1000. / time_delta : NAN;
}
break;
case 'w':
@@ -416,9 +548,7 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc
process->io_wchar = strtoull(line + 7, NULL, 10);
} else if (String_startsWith(line + 1, "rite_bytes: ")) {
process->io_write_bytes = strtoull(line + 13, NULL, 10);
- process->io_rate_write_bps =
- ((double)(process->io_write_bytes - last_write)) / (((double)(now - process->io_rate_write_time)) / 1000);
- process->io_rate_write_time = now;
+ process->io_rate_write_bps = time_delta ? (process->io_write_bytes - last_write) * /*ms to s*/1000. / time_delta : NAN;
}
break;
case 's':
@@ -434,14 +564,16 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc
}
}
}
+
+ process->io_last_scan_time_ms = realtimeMs;
}
typedef struct LibraryData_ {
- uint64_t size;
- bool exec;
+ uint64_t size;
+ bool exec;
} LibraryData;
-static inline uint64_t fast_strtoull_dec(char **str, int maxlen) {
+static inline uint64_t fast_strtoull_dec(char** str, int maxlen) {
register uint64_t result = 0;
if (!maxlen)
@@ -456,7 +588,7 @@ static inline uint64_t fast_strtoull_dec(char **str, int maxlen) {
return result;
}
-static inline uint64_t fast_strtoull_hex(char **str, int maxlen) {
+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;
@@ -483,33 +615,39 @@ static inline uint64_t fast_strtoull_hex(char **str, int maxlen) {
return result;
}
-static void LinuxProcessList_calcLibSize_helper(ATTR_UNUSED hkey_t key, void* value, void* data) {
+static void LinuxProcessList_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* value, void* data) {
if (!data)
return;
if (!value)
return;
- LibraryData* v = (LibraryData *)value;
- uint64_t* d = (uint64_t *)data;
+ const LibraryData* v = (const LibraryData*)value;
+ uint64_t* d = (uint64_t*)data;
if (!v->exec)
return;
*d += v->size;
}
-static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) {
+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 0;
+ return;
- Hashtable* ht = Hashtable_new(64, true);
+ 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;
- char map_perm[5];
+ bool map_execute;
unsigned int map_devmaj;
unsigned int map_devmin;
uint64_t map_inode;
@@ -519,7 +657,7 @@ static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) {
continue;
// Parse format: "%Lx-%Lx %4s %x %2x:%2x %Ld"
- char *readptr = buffer;
+ char* readptr = buffer;
map_start = fast_strtoull_hex(&readptr, 16);
if ('-' != *readptr++)
@@ -529,13 +667,12 @@ static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) {
if (' ' != *readptr++)
continue;
- memcpy(map_perm, readptr, 4);
- map_perm[4] = '\0';
+ map_execute = (readptr[2] == 'x');
readptr += 4;
if (' ' != *readptr++)
continue;
- while(*readptr > ' ')
+ while (*readptr > ' ')
readptr++; // Skip parsing this hex value
if (' ' != *readptr++)
continue;
@@ -556,61 +693,112 @@ static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) {
if (!map_inode)
continue;
- LibraryData* libdata = Hashtable_get(ht, map_inode);
- if (!libdata) {
- libdata = xCalloc(1, sizeof(LibraryData));
- Hashtable_put(ht, map_inode, libdata);
+ 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;
}
- libdata->size += map_end - map_start;
- libdata->exec |= 'x' == map_perm[2];
+ 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);
- uint64_t total_size = 0;
- Hashtable_foreach(ht, LinuxProcessList_calcLibSize_helper, &total_size);
+ if (calcSize) {
+ uint64_t total_size = 0;
+ Hashtable_foreach(ht, LinuxProcessList_calcLibSize_helper, &total_size);
- Hashtable_delete(ht);
+ Hashtable_delete(ht);
- return total_size / CRT_pageSize;
+ process->m_lrs = total_size / pageSize;
+ }
}
-static bool LinuxProcessList_readStatmFile(LinuxProcess* process, openat_arg_t procFd, bool performLookup, unsigned long long now) {
+static bool LinuxProcessList_readStatmFile(LinuxProcess* process, openat_arg_t procFd) {
FILE* statmfile = fopenat(procFd, "statm", "r");
if (!statmfile)
return false;
- long tmp_m_lrs = 0;
+ 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,
- &tmp_m_lrs,
+ &dummy, /* unused since Linux 2.6; always 0 */
&process->m_drs,
- &process->m_dt);
+ &dummy2); /* unused since Linux 2.6; always 0 */
fclose(statmfile);
if (r == 7) {
- if (tmp_m_lrs) {
- process->m_lrs = tmp_m_lrs;
- } else if (performLookup) {
- // Check if we really should recalculate the M_LRS value for this process
- uint64_t passedTimeInMs = now - process->last_mlrs_calctime;
+ process->super.m_virt *= pageSizeKB;
+ process->super.m_resident *= pageSizeKB;
+ }
+
+ return r == 7;
+}
- uint64_t recheck = ((uint64_t)rand()) % 2048;
+static bool LinuxProcessList_checkPidNamespace(Process* process, openat_arg_t procFd) {
+ FILE* statusfile = fopenat(procFd, "status", "r");
+ if (!statusfile)
+ return false;
- if(passedTimeInMs > 2000 || passedTimeInMs > recheck) {
- process->last_mlrs_calctime = now;
- process->m_lrs = LinuxProcessList_calcLibSize(procFd);
- }
- } else {
- // Keep previous value
+ while (true) {
+ char buffer[PROC_LINE_LENGTH + 1];
+ if (fgets(buffer, sizeof(buffer), statusfile) == NULL)
+ break;
+
+ if (!String_startsWith(buffer, "NSpid:"))
+ continue;
+
+ char* ptr = buffer;
+ int pid_ns_count = 0;
+ while (*ptr && *ptr != '\n' && !isdigit(*ptr))
+ ++ptr;
+
+ while (*ptr && *ptr != '\n') {
+ if (isdigit(*ptr))
+ pid_ns_count++;
+ while (isdigit(*ptr))
+ ++ptr;
+ while (*ptr && *ptr != '\n' && !isdigit(*ptr))
+ ++ptr;
}
+
+ if (pid_ns_count > 1)
+ process->isRunningInContainer = true;
+
+ break;
}
- return r == 7;
+ fclose(statusfile);
+ return true;
}
static bool LinuxProcessList_readSmapsFile(LinuxProcess* process, openat_arg_t procFd, bool haveSmapsRollup) {
@@ -652,7 +840,7 @@ static bool LinuxProcessList_readSmapsFile(LinuxProcess* process, openat_arg_t p
#ifdef HAVE_OPENVZ
static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) {
- if ( (access(PROCDIR "/vz", R_OK) != 0)) {
+ if (access(PROCDIR "/vz", R_OK) != 0) {
free(process->ctid);
process->ctid = NULL;
process->vpid = process->super.pid;
@@ -701,7 +889,7 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t
char* value_end = name_value_sep;
- while(*value_end > 32) {
+ while (*value_end > 32) {
value_end++;
}
@@ -711,13 +899,11 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t
*value_end = '\0';
- switch(field) {
+ switch (field) {
case 1:
foundEnvID = true;
- if (!String_eq(name_value_sep, process->ctid ? process->ctid : "")) {
- free(process->ctid);
- process->ctid = xStrdup(name_value_sep);
- }
+ if (!String_eq(name_value_sep, process->ctid ? process->ctid : ""))
+ free_and_xStrdup(&process->ctid, name_value_sep);
break;
case 2:
foundVPid = true;
@@ -743,6 +929,13 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t
#endif
+static bool isContainerOrVMSlice(char* cgroup) {
+ if (String_startsWith(cgroup, "/user") || String_startsWith(cgroup, "/system"))
+ return false;
+
+ return true;
+}
+
static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t procFd) {
FILE* file = fopenat(procFd, "cgroup", "r");
if (!file) {
@@ -750,6 +943,10 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t
free(process->cgroup);
process->cgroup = NULL;
}
+ if (process->cgroup_short) {
+ free(process->cgroup_short);
+ process->cgroup_short = NULL;
+ }
return;
}
char output[PROC_LINE_LENGTH + 1];
@@ -758,13 +955,20 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t
int left = PROC_LINE_LENGTH;
while (!feof(file) && left > 0) {
char buffer[PROC_LINE_LENGTH + 1];
- char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
+ const char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
if (!ok)
break;
- char* group = strchr(buffer, ':');
- if (!group)
- break;
+ char* group = buffer;
+ for (size_t i = 0; i < 2; i++) {
+ group = strchrnul(group, ':');
+ if (!*group)
+ break;
+ group++;
+ }
+
+ char* eol = strchrnul(group, '\n');
+ *eol = '\0';
if (at != output) {
*at = ';';
@@ -775,8 +979,33 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t
left -= wrote;
}
fclose(file);
- free(process->cgroup);
- process->cgroup = xStrdup(output);
+
+ bool changed = !process->cgroup || !String_eq(process->cgroup, output);
+
+ Process_updateFieldWidth(CGROUP, strlen(output));
+ free_and_xStrdup(&process->cgroup, output);
+
+ if (!changed) {
+ if (process->cgroup_short) {
+ Process_updateFieldWidth(CCGROUP, strlen(process->cgroup_short));
+ } else {
+ //CCGROUP is alias to normal CGROUP if shortening fails
+ Process_updateFieldWidth(CCGROUP, strlen(process->cgroup));
+ }
+ return;
+ }
+
+ char* cgroup_short = CGroup_filterName(process->cgroup);
+ if (cgroup_short) {
+ Process_updateFieldWidth(CCGROUP, strlen(cgroup_short));
+ free_and_xStrdup(&process->cgroup_short, cgroup_short);
+ free(cgroup_short);
+ } else {
+ //CCGROUP is alias to normal CGROUP if shortening fails
+ Process_updateFieldWidth(CCGROUP, strlen(process->cgroup));
+ free(process->cgroup_short);
+ process->cgroup_short = NULL;
+ }
}
#ifdef HAVE_VSERVER
@@ -827,6 +1056,23 @@ static void LinuxProcessList_readOomData(LinuxProcess* process, openat_arg_t pro
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)
@@ -863,7 +1109,7 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t
}
char buffer[PROC_LINE_LENGTH + 1];
- char* res = fgets(buffer, sizeof(buffer), file);
+ const char* res = fgets(buffer, sizeof(buffer), file);
fclose(file);
if (!res) {
free(process->secattr);
@@ -874,11 +1120,13 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t
if (newline) {
*newline = '\0';
}
+
+ Process_updateFieldWidth(SECATTR, strlen(buffer));
+
if (process->secattr && String_eq(process->secattr, buffer)) {
return;
}
- free(process->secattr);
- process->secattr = xStrdup(buffer);
+ free_and_xStrdup(&process->secattr, buffer);
}
static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd) {
@@ -887,24 +1135,21 @@ static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd)
#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);
+ ssize_t r = Compat_readlink(procFd, "cwd", pathBuffer, sizeof(pathBuffer) - 1);
#endif
if (r < 0) {
- free(process->cwd);
- process->cwd = NULL;
+ free(process->super.procCwd);
+ process->super.procCwd = NULL;
return;
}
pathBuffer[r] = '\0';
- if (process->cwd && String_eq(process->cwd, pathBuffer))
+ if (process->super.procCwd && String_eq(process->super.procCwd, pathBuffer))
return;
- free(process->cwd);
- process->cwd = xStrdup(pathBuffer);
+ free_and_xStrdup(&process->super.procCwd, pathBuffer);
}
#ifdef HAVE_DELAYACCT
@@ -912,7 +1157,7 @@ static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd)
static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {
struct nlmsghdr* nlhdr;
struct nlattr* nlattrs[TASKSTATS_TYPE_MAX + 1];
- struct nlattr* nlattr;
+ const struct nlattr* nlattr;
struct taskstats stats;
int rem;
LinuxProcess* lp = (LinuxProcess*) linuxProcess;
@@ -947,12 +1192,19 @@ static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {
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) {
- return;
+ goto delayacct_failure;
}
if (! (msg = nlmsg_alloc())) {
- return;
+ 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)) {
@@ -964,44 +1216,29 @@ static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProc
}
if (nl_send_sync(this->netlink_socket, msg) < 0) {
- process->swapin_delay_percent = NAN;
- process->blkio_delay_percent = NAN;
- process->cpu_delay_percent = NAN;
- return;
+ goto delayacct_failure;
}
if (nl_recvmsgs_default(this->netlink_socket) < 0) {
- return;
+ goto delayacct_failure;
}
-}
-#endif
+ return;
-static void setCommand(Process* process, const char* command, int len) {
- if (process->comm && process->commLen >= len) {
- strncpy(process->comm, command, len + 1);
- } else {
- free(process->comm);
- process->comm = xStrdup(command);
- }
- process->commLen = len;
+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)
+ if (amtRead <= 0)
return false;
- if (amtRead == 0) {
- if (process->state == 'Z') {
- process->basenameOffset = 0;
- } else {
- ((LinuxProcess*)process)->isKernelThread = true;
- }
- return true;
- }
-
int tokenEnd = 0;
int tokenStart = 0;
int lastChar = 0;
@@ -1010,7 +1247,7 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc
for (int i = 0; i < amtRead; i++) {
/* newline used as delimiter - when forming the mergedCommand, newline is
- * converted to space by LinuxProcess_makeCommandStr */
+ * converted to space by Process_makeCommandStr */
if (command[i] == '\0') {
command[i] = '\n';
} else {
@@ -1106,35 +1343,27 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc
}
}
}
+
+ /* 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;
}
- LinuxProcess *lp = (LinuxProcess *)process;
- lp->mergedCommand.maxLen = lastChar + 1; /* accommodate cmdline */
- if (!process->comm || !String_eq(command, process->comm)) {
- process->basenameOffset = tokenEnd;
- setCommand(process, command, lastChar + 1);
- lp->procCmdlineBasenameOffset = tokenStart;
- lp->procCmdlineBasenameEnd = tokenEnd;
- lp->mergedCommand.cmdlineChanged = true;
- }
+ 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';
- lp->mergedCommand.maxLen += amtRead - 1; /* accommodate comm */
- if (!lp->procComm || !String_eq(command, lp->procComm)) {
- free(lp->procComm);
- lp->procComm = xStrdup(command);
- lp->mergedCommand.commChanged = true;
- }
- } else if (lp->procComm) {
- free(lp->procComm);
- lp->procComm = NULL;
- lp->mergedCommand.commChanged = true;
+ Process_updateComm(process, command);
+ } else {
+ Process_updateComm(process, NULL);
}
char filename[MAX_NAME + 1];
@@ -1143,45 +1372,41 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc
#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);
+ amtRead = Compat_readlink(procFd, "exe", filename, sizeof(filename) - 1);
#endif
if (amtRead > 0) {
filename[amtRead] = 0;
- lp->mergedCommand.maxLen += amtRead; /* accommodate exe */
- if (!lp->procExe || !String_eq(filename, lp->procExe)) {
- free(lp->procExe);
- lp->procExe = xStrdup(filename);
- lp->procExeLen = amtRead;
- /* exe is guaranteed to contain at least one /, but validate anyway */
- while (amtRead && filename[--amtRead] != '/')
- ;
- lp->procExeBasenameOffset = amtRead + 1;
- lp->mergedCommand.exeChanged = true;
+ if (!process->procExe ||
+ (!process->procExeDeleted && !String_eq(filename, process->procExe)) ||
+ process->procExeDeleted) {
const char* deletedMarker = " (deleted)";
- if (strlen(lp->procExe) > strlen(deletedMarker)) {
- lp->procExeDeleted = String_eq(lp->procExe + strlen(lp->procExe) - strlen(deletedMarker), deletedMarker);
+ const size_t markerLen = strlen(deletedMarker);
+ const size_t filenameLen = strlen(filename);
- if (lp->procExeDeleted && strlen(lp->procExe) - strlen(deletedMarker) == 1 && lp->procExe[0] == '/') {
- lp->procExeBasenameOffset = 0;
- }
+ 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 (lp->procExe) {
- free(lp->procExe);
- lp->procExe = NULL;
- lp->procExeLen = 0;
- lp->procExeBasenameOffset = 0;
- lp->procExeDeleted = false;
- lp->mergedCommand.exeChanged = true;
+ } else if (process->procExe) {
+ Process_updateExe(process, NULL);
+ process->procExeDeleted = false;
}
return true;
}
-static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned int tty_nr) {
+static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned long int tty_nr) {
unsigned int maj = major(tty_nr);
unsigned int min = minor(tty_nr);
@@ -1234,10 +1459,26 @@ static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned in
return out;
}
-static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const char* dirname, const Process* parent, double period, unsigned long long now) {
+static bool isOlderThan(const ProcessList* pl, const Process* proc, unsigned int seconds) {
+ assert(pl->realtimeMs > 0);
+
+ /* Starttime might not yet be parsed */
+ if (proc->starttime_ctime <= 0)
+ return false;
+
+ uint64_t realtime = pl->realtimeMs / 1000;
+
+ if (realtime < (uint64_t)proc->starttime_ctime)
+ return false;
+
+ return realtime - proc->starttime_ctime > seconds;
+}
+
+static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const char* dirname, const Process* parent, double period) {
ProcessList* pl = (ProcessList*) this;
const struct dirent* entry;
const Settings* settings = pl->settings;
+ const ScreenSettings* ss = settings->ss;
#ifdef HAVE_OPENAT
int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
@@ -1254,9 +1495,10 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
return false;
}
- int cpus = pl->cpuCount;
- bool hideKernelThreads = settings->hideKernelThreads;
- bool hideUserlandThreads = settings->hideUserlandThreads;
+ const unsigned int activeCPUs = pl->activeCPUs;
+ 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;
@@ -1277,30 +1519,45 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
}
// filename is a number: process directory
- int pid = atoi(name);
+ int pid;
+ {
+ char* endptr;
+ unsigned long parsedPid = strtoul(name, &endptr, 10);
+ if (parsedPid == 0 || parsedPid == ULONG_MAX || *endptr != '\0')
+ continue;
+ pid = parsedPid;
+ }
- if (pid <= 0)
+ // Skip task directory of main thread
+ if (parent && pid == parent->pid)
continue;
- if (parent && pid == parent->pid)
+#ifdef HAVE_OPENAT
+ int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (procFd < 0)
continue;
+#else
+ char procFd[4096];
+ xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name);
+#endif
bool preExisting;
Process* proc = ProcessList_getProcess(pl, pid, &preExisting, LinuxProcess_new);
LinuxProcess* lp = (LinuxProcess*) proc;
proc->tgid = parent ? parent->pid : pid;
+ proc->isUserlandThread = proc->pid != proc->tgid;
-#ifdef HAVE_OPENAT
- int procFd = openat(dirFd, entry->d_name, O_PATH | 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);
- LinuxProcessList_recurseProcTree(this, procFd, "task", proc, period, now);
+ if (ss->flags & PROCESS_FLAG_LINUX_CGROUP || hideRunningInContainer) {
+ LinuxProcessList_readCGroupFile(lp, procFd);
+ if (hideRunningInContainer && lp->cgroup && isContainerOrVMSlice(lp->cgroup)) {
+ if (!LinuxProcessList_checkPidNamespace(proc, procFd)) {
+ goto errorReadingProcess;
+ }
+ }
+ }
/*
* These conditions will not trigger on first occurrence, cause we need to
@@ -1324,14 +1581,45 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
Compat_openatArgClose(procFd);
continue;
}
+ if (preExisting && hideRunningInContainer && proc->isRunningInContainer) {
+ proc->updated = true;
+ proc->show = false;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
- if (settings->flags & PROCESS_FLAG_IO)
- LinuxProcessList_readIoFile(lp, procFd, now);
+ if (ss->flags & PROCESS_FLAG_IO)
+ LinuxProcessList_readIoFile(lp, procFd, pl->realtimeMs);
- if (!LinuxProcessList_readStatmFile(lp, procFd, !!(settings->flags & PROCESS_FLAG_LINUX_LRS_FIX), now))
+ if (!LinuxProcessList_readStatmFile(lp, procFd))
goto errorReadingProcess;
- if ((settings->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
+ {
+ bool prev = proc->usesDeletedLib;
+
+ if (!proc->isKernelThread && !proc->isUserlandThread &&
+ ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(pl, proc, 10)))) {
+
+ // Check if we really should recalculate the M_LRS value for this process
+ uint64_t passedTimeInMs = pl->realtimeMs - lp->last_mlrs_calctime;
+
+ uint64_t recheck = ((uint64_t)rand()) % 2048;
+
+ if (passedTimeInMs > recheck) {
+ lp->last_mlrs_calctime = pl->realtimeMs;
+ LinuxProcessList_readMaps(lp, procFd, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe);
+ }
+ } else {
+ /* Copy from process structure in threads and reset if setting got disabled */
+ proc->usesDeletedLib = (proc->isUserlandThread && parent) ? parent->usesDeletedLib : false;
+ lp->m_lrs = (proc->isUserlandThread && parent) ? ((const LinuxProcess*)parent)->m_lrs : 0;
+ }
+
+ if (prev != proc->usesDeletedLib)
+ proc->mergedCommand.lastUpdate = 0;
+ }
+
+ if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
if (!parent) {
// Read smaps file of each process only every second pass to improve performance
static int smaps_flag = 0;
@@ -1346,119 +1634,109 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
}
}
- char command[MAX_NAME + 1];
+ char statCommand[MAX_NAME + 1];
unsigned long long int lasttimes = (lp->utime + lp->stime);
- int commLen = sizeof(command);
- unsigned int tty_nr = proc->tty_nr;
- if (! LinuxProcessList_readStatFile(proc, procFd, command, &commLen))
+ unsigned long int tty_nr = proc->tty_nr;
+ if (!LinuxProcessList_readStatFile(proc, procFd, statCommand, sizeof(statCommand)))
goto errorReadingProcess;
+ if (lp->flags & PF_KTHREAD) {
+ proc->isKernelThread = true;
+ }
+
if (tty_nr != proc->tty_nr && this->ttyDrivers) {
- free(lp->ttyDevice);
- lp->ttyDevice = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
+ free(proc->tty_name);
+ proc->tty_name = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
}
- if (settings->flags & PROCESS_FLAG_LINUX_IOPRIO) {
+ if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) {
LinuxProcess_updateIOPriority(lp);
}
/* 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, cpus * 100.0f);
- proc->percent_mem = (proc->m_resident * CRT_pageSizeKB) / (double)(pl->totalMem) * 100.0;
-
- if (!preExisting) {
+ float percent_cpu = (period < 1E-6) ? 0.0F : ((lp->utime + lp->stime - lasttimes) / period * 100.0);
+ proc->percent_cpu = CLAMP(percent_cpu, 0.0F, activeCPUs * 100.0F);
+ proc->percent_mem = proc->m_resident / (double)(pl->totalMem) * 100.0;
+ Process_updateCPUFieldWidths(proc->percent_cpu);
- if (! LinuxProcessList_statProcessDir(proc, procFd))
- goto errorReadingProcess;
+ if (! LinuxProcessList_updateUser(pl, proc, procFd))
+ goto errorReadingProcess;
- proc->user = UsersTable_getRef(pl->usersTable, proc->st_uid);
+ if (!preExisting) {
#ifdef HAVE_OPENVZ
- if (settings->flags & PROCESS_FLAG_LINUX_OPENVZ) {
+ if (ss->flags & PROCESS_FLAG_LINUX_OPENVZ) {
LinuxProcessList_readOpenVZData(lp, procFd);
}
#endif
#ifdef HAVE_VSERVER
- if (settings->flags & PROCESS_FLAG_LINUX_VSERVER) {
+ if (ss->flags & PROCESS_FLAG_LINUX_VSERVER) {
LinuxProcessList_readVServerData(lp, procFd);
}
#endif
- if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
- goto errorReadingProcess;
+ if (proc->isKernelThread) {
+ Process_updateCmdline(proc, NULL, 0, 0);
+ } else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
}
Process_fillStarttimeBuffer(proc);
ProcessList_add(pl, proc);
} else {
- if (settings->updateProcessNames && proc->state != 'Z') {
- if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
- goto errorReadingProcess;
+ if (settings->updateProcessNames && proc->state != ZOMBIE) {
+ if (proc->isKernelThread) {
+ Process_updateCmdline(proc, NULL, 0, 0);
+ } else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
}
}
}
- /* (Re)Generate the Command string, but only if the process is:
- * - not a kernel thread, and
- * - not a zombie or it became zombie under htop's watch, and
- * - not a user thread or if showThreadNames is not set */
- if (!Process_isKernelThread(proc) &&
- (proc->state != 'Z' || lp->mergedCommand.str) &&
- (!Process_isUserlandThread(proc) || !settings->showThreadNames)) {
- LinuxProcess_makeCommandStr(proc);
- }
#ifdef HAVE_DELAYACCT
- LinuxProcessList_readDelayAcctData(this, lp);
- #endif
-
- if (settings->flags & PROCESS_FLAG_LINUX_CGROUP) {
- LinuxProcessList_readCGroupFile(lp, procFd);
+ if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) {
+ LinuxProcessList_readDelayAcctData(this, lp);
}
+ #endif
- if (settings->flags & PROCESS_FLAG_LINUX_OOM) {
+ if (ss->flags & PROCESS_FLAG_LINUX_OOM) {
LinuxProcessList_readOomData(lp, procFd);
}
- if (settings->flags & PROCESS_FLAG_LINUX_CTXT) {
+ if (ss->flags & PROCESS_FLAG_LINUX_CTXT) {
LinuxProcessList_readCtxtData(lp, procFd);
}
- if (settings->flags & PROCESS_FLAG_LINUX_SECATTR) {
+ if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) {
LinuxProcessList_readSecattrData(lp, procFd);
}
- if (settings->flags & PROCESS_FLAG_LINUX_CWD) {
+ if (ss->flags & PROCESS_FLAG_CWD) {
LinuxProcessList_readCwd(lp, procFd);
}
- if (proc->state == 'Z' && (proc->basenameOffset == 0)) {
- proc->basenameOffset = -1;
- setCommand(proc, command, commLen);
- } else if (Process_isThread(proc)) {
- if (settings->showThreadNames || Process_isKernelThread(proc) || (proc->state == 'Z' && proc->basenameOffset == 0)) {
- proc->basenameOffset = -1;
- setCommand(proc, command, commLen);
- } else if (settings->showThreadNames) {
- if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
- goto errorReadingProcess;
- }
- }
- if (Process_isKernelThread(proc)) {
- pl->kernelThreads++;
- } else {
- pl->userlandThreads++;
- }
+ if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {
+ LinuxProcessList_readAutogroup(lp, procFd);
+ }
+
+ if (!proc->cmdline && statCommand[0] &&
+ (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+
+ if (Process_isKernelThread(proc)) {
+ pl->kernelThreads++;
+ } else if (Process_isUserlandThread(proc)) {
+ pl->userlandThreads++;
}
/* Set at the end when we know if a new entry is a thread */
proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
pl->totalTasks++;
- if (proc->state == 'R')
- pl->runningTasks++;
+ /* runningTasks is set in LinuxProcessList_scanCPUTime() from /proc/stat */
proc->updated = true;
Compat_openatArgClose(procFd);
continue;
@@ -1473,8 +1751,13 @@ errorReadingProcess:
#endif
if (preExisting) {
- ProcessList_remove(pl, proc);
+ /*
+ * The only real reason for coming here (apart from Linux violating the /proc API)
+ * would be the process going away with its /proc files disappearing (!HAVE_OPENAT).
+ * However, we want to keep in the process list for now for the "highlight dying" mode.
+ */
} else {
+ /* A really short-lived process that we don't have full info about */
Process_delete((Object*)proc);
}
}
@@ -1484,63 +1767,147 @@ errorReadingProcess:
}
static inline void LinuxProcessList_scanMemoryInfo(ProcessList* this) {
- unsigned long long int freeMem = 0;
- unsigned long long int swapFree = 0;
- unsigned long long int shmem = 0;
- unsigned long long int sreclaimable = 0;
+ 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 == NULL) {
+ if (!file)
CRT_fatalError("Cannot open " PROCMEMINFOFILE);
- }
+
char buffer[128];
- while (fgets(buffer, 128, file)) {
+ while (fgets(buffer, sizeof(buffer), file)) {
- #define tryRead(label, variable) \
- if (String_startsWith(buffer, label)) { \
- sscanf(buffer + strlen(label), " %32llu kB", variable); \
- break; \
- }
+ #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("MemTotal:", &this->totalMem);
- tryRead("MemFree:", &freeMem);
+ tryRead("MemAvailable:", availableMem);
+ tryRead("MemFree:", freeMem);
+ tryRead("MemTotal:", totalMem);
break;
case 'B':
- tryRead("Buffers:", &this->buffersMem);
+ tryRead("Buffers:", buffersMem);
break;
case 'C':
- tryRead("Cached:", &this->cachedMem);
+ tryRead("Cached:", cachedMem);
break;
case 'S':
switch (buffer[1]) {
- case 'w':
- tryRead("SwapTotal:", &this->totalSwap);
- tryRead("SwapFree:", &swapFree);
- break;
case 'h':
- tryRead("Shmem:", &shmem);
+ tryRead("Shmem:", sharedMem);
+ break;
+ case 'w':
+ tryRead("SwapTotal:", swapTotalMem);
+ tryRead("SwapCached:", swapCacheMem);
+ tryRead("SwapFree:", swapFreeMem);
break;
case 'R':
- tryRead("SReclaimable:", &sreclaimable);
+ tryRead("SReclaimable:", sreclaimableMem);
break;
}
break;
}
+
#undef tryRead
}
- this->usedMem = this->totalMem - freeMem;
- this->cachedMem = this->cachedMem + sreclaimable - shmem;
- this->usedSwap = this->totalSwap - swapFree;
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) {
- unsigned long long int totalZram = 0;
- unsigned long long int usedZramComp = 0;
- unsigned long long int usedZramOrig = 0;
+ memory_t totalZram = 0;
+ memory_t usedZramComp = 0;
+ memory_t usedZramOrig = 0;
char mm_stat[34];
char disksize[34];
@@ -1561,9 +1928,9 @@ static inline void LinuxProcessList_scanZramInfo(LinuxProcessList* this) {
}
break;
}
- unsigned long long int size = 0;
- unsigned long long int orig_data_size = 0;
- unsigned long long int compr_data_size = 0;
+ 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)) {
@@ -1586,9 +1953,9 @@ static inline void LinuxProcessList_scanZramInfo(LinuxProcessList* this) {
}
static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
- unsigned long long int dbufSize = 0;
- unsigned long long int dnodeSize = 0;
- unsigned long long int bonusSize = 0;
+ memory_t dbufSize = 0;
+ memory_t dnodeSize = 0;
+ memory_t bonusSize = 0;
FILE* file = fopen(PROCARCSTATSFILE, "r");
if (file == NULL) {
@@ -1601,15 +1968,16 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
if (String_startsWith(buffer, label)) { \
sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
break; \
- }
+ } else (void) 0 /* Require a ";" after the macro use. */
#define tryReadFlag(label, variable, flag) \
if (String_startsWith(buffer, label)) { \
(flag) = sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
break; \
- }
+ } else (void) 0 /* Require a ";" after the macro use. */
switch (buffer[0]) {
case 'c':
+ tryRead("c_min", &lpl->zfs.min);
tryRead("c_max", &lpl->zfs.max);
tryReadFlag("compressed_size", &lpl->zfs.compressed, lpl->zfs.isCompressed);
break;
@@ -1644,6 +2012,7 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
lpl->zfs.enabled = (lpl->zfs.size > 0 ? 1 : 0);
lpl->zfs.size /= 1024;
+ lpl->zfs.min /= 1024;
lpl->zfs.max /= 1024;
lpl->zfs.MFU /= 1024;
lpl->zfs.MRU /= 1024;
@@ -1656,61 +2025,78 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
}
}
-static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) {
+static inline double LinuxProcessList_scanCPUTime(ProcessList* super) {
+ LinuxProcessList* this = (LinuxProcessList*) super;
+
+ LinuxProcessList_updateCPUcount(super);
FILE* file = fopen(PROCSTATFILE, "r");
- if (file == NULL) {
+ if (!file)
CRT_fatalError("Cannot open " PROCSTATFILE);
- }
- int cpus = this->super.cpuCount;
- assert(cpus > 0);
- for (int i = 0; i <= cpus; i++) {
+
+ 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, irq, softIrq, steal, guest, guestnice;
- ioWait = irq = softIrq = steal = guest = guestnice = 0;
+ 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.
- char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
- if (!ok) {
- buffer[0] = '\0';
- }
-
+ 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 {
- int cpuid;
- (void) sscanf(buffer, "cpu%4d %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
- assert(cpuid == i - 1);
+ 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 = usertime - guest;
- nicetime = nicetime - guestnice;
+ 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->cpus[i]);
+ 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.
- #define WRAP_SUBTRACT(a,b) (((a) > (b)) ? (a) - (b) : 0)
- cpuData->userPeriod = WRAP_SUBTRACT(usertime, cpuData->userTime);
- cpuData->nicePeriod = WRAP_SUBTRACT(nicetime, cpuData->niceTime);
- cpuData->systemPeriod = WRAP_SUBTRACT(systemtime, cpuData->systemTime);
- cpuData->systemAllPeriod = WRAP_SUBTRACT(systemalltime, cpuData->systemAllTime);
- cpuData->idleAllPeriod = WRAP_SUBTRACT(idlealltime, cpuData->idleAllTime);
- cpuData->idlePeriod = WRAP_SUBTRACT(idletime, cpuData->idleTime);
- cpuData->ioWaitPeriod = WRAP_SUBTRACT(ioWait, cpuData->ioWaitTime);
- cpuData->irqPeriod = WRAP_SUBTRACT(irq, cpuData->irqTime);
- cpuData->softIrqPeriod = WRAP_SUBTRACT(softIrq, cpuData->softIrqTime);
- cpuData->stealPeriod = WRAP_SUBTRACT(steal, cpuData->stealTime);
- cpuData->guestPeriod = WRAP_SUBTRACT(virtalltime, cpuData->guestTime);
- cpuData->totalPeriod = WRAP_SUBTRACT(totaltime, cpuData->totalTime);
- #undef WRAP_SUBTRACT
+ 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;
@@ -1725,19 +2111,50 @@ static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) {
cpuData->totalTime = totaltime;
}
- double period = (double)this->cpus[0].totalPeriod / cpus;
+ 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) {
- int cpus = this->super.cpuCount;
+static int scanCPUFrequencyFromSysCPUFreq(LinuxProcessList* this) {
+ unsigned int existingCPUs = this->super.existingCPUs;
int numCPUsWithFrequency = 0;
unsigned long totalFrequency = 0;
- for (int i = 0; i < cpus; ++i) {
+ /*
+ * 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%d/cpufreq/scaling_cur_freq", i);
+ 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)
@@ -1747,26 +2164,37 @@ static int scanCPUFreqencyFromSysCPUFreq(LinuxProcessList* this) {
if (fscanf(file, "%lu", &frequency) == 1) {
/* convert kHz to MHz */
frequency = frequency / 1000;
- this->cpus[i + 1].frequency = frequency;
+ 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->cpus[0].frequency = (double)totalFrequency / numCPUsWithFrequency;
+ this->cpuData[0].frequency = (double)totalFrequency / numCPUsWithFrequency;
return 0;
}
-static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) {
+static void scanCPUFrequencyFromCPUinfo(LinuxProcessList* this) {
FILE* file = fopen(PROCCPUINFOFILE, "r");
if (file == NULL)
return;
- int cpus = this->super.cpuCount;
+ unsigned int existingCPUs = this->super.existingCPUs;
int numCPUsWithFrequency = 0;
double totalFrequency = 0;
int cpuid = -1;
@@ -1778,20 +2206,17 @@ static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) {
if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
break;
- if (
- (sscanf(buffer, "processor : %d", &cpuid) == 1) ||
- (sscanf(buffer, "processor: %d", &cpuid) == 1)
- ) {
+ if (sscanf(buffer, "processor : %d", &cpuid) == 1) {
continue;
} else if (
(sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
- (sscanf(buffer, "cpu MHz: %lf", &frequency) == 1)
+ (sscanf(buffer, "clock : %lfMHz", &frequency) == 1)
) {
- if (cpuid < 0 || cpuid > (cpus - 1)) {
+ if (cpuid < 0 || (unsigned int)cpuid > (existingCPUs - 1)) {
continue;
}
- CPUData* cpuData = &(this->cpus[cpuid + 1]);
+ CPUData* cpuData = &(this->cpuData[cpuid + 1]);
/* do not override sysfs data */
if (isnan(cpuData->frequency)) {
cpuData->frequency = frequency;
@@ -1805,70 +2230,34 @@ static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) {
fclose(file);
if (numCPUsWithFrequency > 0) {
- this->cpus[0].frequency = totalFrequency / numCPUsWithFrequency;
+ this->cpuData[0].frequency = totalFrequency / numCPUsWithFrequency;
}
}
static void LinuxProcessList_scanCPUFrequency(LinuxProcessList* this) {
- int cpus = this->super.cpuCount;
- assert(cpus > 0);
+ unsigned int existingCPUs = this->super.existingCPUs;
- for (int i = 0; i <= cpus; i++) {
- this->cpus[i].frequency = NAN;
+ for (unsigned int i = 0; i <= existingCPUs; i++) {
+ this->cpuData[i].frequency = NAN;
}
- if (scanCPUFreqencyFromSysCPUFreq(this) == 0) {
+ if (scanCPUFrequencyFromSysCPUFreq(this) == 0) {
return;
}
- scanCPUFreqencyFromCPUinfo(this);
+ scanCPUFrequencyFromCPUinfo(this);
}
-#ifdef HAVE_SENSORS_SENSORS_H
-static void LinuxProcessList_scanCPUTemperature(LinuxProcessList* this) {
- const int cpuCount = this->super.cpuCount;
-
- for (int i = 0; i <= cpuCount; i++) {
- this->cpus[i].temperature = NAN;
- }
-
- int r = LibSensors_getCPUTemperatures(this->cpus, cpuCount);
-
- /* No temperature - nothing to do */
- if (r <= 0)
- return;
-
- /* Only package temperature - copy to all cpus */
- if (r == 1 && !isnan(this->cpus[0].temperature)) {
- double packageTemp = this->cpus[0].temperature;
- for (int i = 1; i <= cpuCount; i++) {
- this->cpus[i].temperature = packageTemp;
- }
-
- return;
- }
-
- /* Half the temperatures, probably HT/SMT - copy to second half */
- if (r >= 2 && (r - 1) == (cpuCount / 2)) {
- for (int i = cpuCount / 2 + 1; i <= cpuCount; i++) {
- this->cpus[i].temperature = this->cpus[i/2].temperature;
- }
-
- return;
- }
-}
-#endif
-
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_updateCPUcount(this);
LinuxProcessList_scanZramInfo(this);
- double period = LinuxProcessList_scanCPUTime(this);
+ double period = LinuxProcessList_scanCPUTime(super);
if (settings->showCPUFrequency) {
LinuxProcessList_scanCPUFrequency(this);
@@ -1876,7 +2265,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
#ifdef HAVE_SENSORS_SENSORS_H
if (settings->showCPUTemperature)
- LinuxProcessList_scanCPUTemperature(this);
+ LibSensors_getCPUTemperatures(this->cpuData, this->super.existingCPUs, this->super.activeCPUs);
#endif
// in pause mode only gather global data for meters (CPU/memory/...)
@@ -1884,9 +2273,15 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
return;
}
- struct timeval tv;
- gettimeofday(&tv, NULL);
- unsigned long long now = tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL;
+ 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] == '/');
@@ -1896,5 +2291,12 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
openat_arg_t rootFd = "";
#endif
- LinuxProcessList_recurseProcTree(this, rootFd, PROCDIR, NULL, period, now);
+ 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/LinuxProcessList.h b/linux/LinuxProcessList.h
index 09b84af..6c2f7db 100644
--- a/linux/LinuxProcessList.h
+++ b/linux/LinuxProcessList.h
@@ -3,7 +3,7 @@
/*
htop - LinuxProcessList.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.
*/
@@ -18,6 +18,8 @@ in the source distribution for its full text.
#include "ZramStats.h"
#include "zfs/ZfsArcStats.h"
+#define HTOP_HUGEPAGE_BASE_SHIFT 16
+#define HTOP_HUGEPAGE_COUNT 24
typedef struct CPUData_ {
unsigned long long int totalTime;
@@ -51,6 +53,8 @@ typedef struct CPUData_ {
#ifdef HAVE_SENSORS_SENSORS_H
double temperature;
#endif
+
+ bool online;
} CPUData;
typedef struct TtyDriver_ {
@@ -63,15 +67,22 @@ typedef struct TtyDriver_ {
typedef struct LinuxProcessList_ {
ProcessList super;
- CPUData* cpus;
+ CPUData* cpuData;
+
TtyDriver* ttyDrivers;
bool haveSmapsRollup;
+ bool haveAutogroup;
#ifdef HAVE_DELAYACCT
struct nl_sock* netlink_socket;
int netlink_family;
#endif
+ memory_t totalHugePageMem;
+ memory_t usedHugePageMem[HTOP_HUGEPAGE_COUNT];
+
+ memory_t availableMem;
+
ZfsArcStats zfs;
ZramStats zram;
} LinuxProcessList;
@@ -104,10 +115,12 @@ typedef struct LinuxProcessList_ {
#define PROC_LINE_LENGTH 4096
#endif
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* pl);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/linux/Platform.c b/linux/Platform.c
index 590fc7a..64f25c4 100644
--- a/linux/Platform.c
+++ b/linux/Platform.c
@@ -1,20 +1,20 @@
/*
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 "Platform.h"
+#include "linux/Platform.h"
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
+#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
-#include <limits.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
@@ -22,6 +22,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"
@@ -31,43 +32,75 @@ in the source distribution for its full text.
#include "DateTimeMeter.h"
#include "DiskIOMeter.h"
#include "HostnameMeter.h"
-#include "IOPriority.h"
-#include "IOPriorityPanel.h"
-#include "LinuxProcess.h"
-#include "LinuxProcessList.h"
+#include "HugePageMeter.h"
#include "LoadAverageMeter.h"
#include "Macros.h"
#include "MainPanel.h"
#include "Meter.h"
#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
#include "NetworkIOMeter.h"
#include "Object.h"
#include "Panel.h"
#include "PressureStallMeter.h"
#include "ProcessList.h"
#include "ProvideCurses.h"
-#include "SELinuxMeter.h"
+#include "linux/SELinuxMeter.h"
#include "Settings.h"
#include "SwapMeter.h"
-#include "SystemdMeter.h"
+#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
#include "XUtils.h"
-#include "ZramMeter.h"
-#include "ZramStats.h"
-
+#include "linux/IOPriority.h"
+#include "linux/IOPriorityPanel.h"
+#include "linux/LinuxProcess.h"
+#include "linux/LinuxProcessList.h"
+#include "linux/SystemdMeter.h"
+#include "linux/ZramMeter.h"
+#include "linux/ZramStats.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsArcStats.h"
#include "zfs/ZfsCompressedArcMeter.h"
+#ifdef HAVE_LIBCAP
+#include <errno.h>
+#include <sys/capability.h>
+#endif
+
#ifdef HAVE_SENSORS_SENSORS_H
#include "LibSensors.h"
#endif
+#ifndef O_PATH
+#define O_PATH 010000000 // declare for ancient glibc versions
+#endif
+
-ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, (int)M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
+#ifdef HAVE_LIBCAP
+enum CapMode {
+ CAP_MODE_OFF,
+ CAP_MODE_BASIC,
+ CAP_MODE_STRICT
+};
+#endif
-int Platform_numberOfFields = LAST_PROCESSFIELD;
+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 },
@@ -113,36 +146,24 @@ static time_t Platform_Battery_cacheTime;
static double Platform_Battery_cachePercent = NAN;
static ACPresence Platform_Battery_cacheIsOnAC;
-void Platform_init(void) {
- if (access(PROCDIR, R_OK) != 0) {
- fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR);
- exit(1);
- }
-
-#ifdef HAVE_SENSORS_SENSORS_H
- LibSensors_init(NULL);
-#endif
-}
-
-void Platform_done(void) {
-#ifdef HAVE_SENSORS_SENSORS_H
- LibSensors_cleanup();
+#ifdef HAVE_LIBCAP
+static enum CapMode Platform_capabilitiesMode = CAP_MODE_BASIC;
#endif
-}
static Htop_Reaction Platform_actionSetIOPriority(State* st) {
- Panel* panel = st->panel;
+ if (Settings_isReadonly())
+ return HTOP_OK;
- LinuxProcess* p = (LinuxProcess*) Panel_getSelected(panel);
+ const LinuxProcess* p = (const LinuxProcess*) Panel_getSelected((Panel*)st->mainPanel);
if (!p)
return HTOP_OK;
IOPriority ioprio1 = p->ioPriority;
Panel* ioprioPanel = IOPriorityPanel_new(ioprio1);
- void* set = Action_pickFromVector(st, ioprioPanel, 21, true);
+ const void* set = Action_pickFromVector(st, ioprioPanel, 20, true);
if (set) {
IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel);
- bool ok = MainPanel_foreachProcess((MainPanel*)panel, LinuxProcess_setIOPriority, (Arg) { .i = ioprio2 }, NULL);
+ bool ok = MainPanel_foreachProcess(st->mainPanel, LinuxProcess_setIOPriority, (Arg) { .i = ioprio2 }, NULL);
if (!ok) {
beep();
}
@@ -151,8 +172,40 @@ static Htop_Reaction Platform_actionSetIOPriority(State* st) {
return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
}
+static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) {
+ if (LinuxProcess_isAutogroupEnabled() == false) {
+ beep();
+ return false;
+ }
+ bool anyTagged;
+ bool ok = MainPanel_foreachProcess(panel, LinuxProcess_changeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged);
+ if (!ok)
+ beep();
+ return anyTagged;
+}
+
+static Htop_Reaction Platform_actionHigherAutogroupPriority(State* st) {
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ bool changed = Platform_changeAutogroupPriority(st->mainPanel, -1);
+ return changed ? HTOP_REFRESH : HTOP_OK;
+}
+
+static Htop_Reaction Platform_actionLowerAutogroupPriority(State* st) {
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ bool changed = Platform_changeAutogroupPriority(st->mainPanel, 1);
+ return changed ? HTOP_REFRESH : HTOP_OK;
+}
+
void Platform_setBindings(Htop_Action* keys) {
keys['i'] = Platform_actionSetIOPriority;
+ keys['{'] = Platform_actionLowerAutogroupPriority;
+ keys['}'] = Platform_actionHigherAutogroupPriority;
+ keys[KEY_F(19)] = Platform_actionLowerAutogroupPriority; // Shift-F7
+ keys[KEY_F(20)] = Platform_actionHigherAutogroupPriority; // Shift-F8
}
const MeterClass* const Platform_meterTypes[] = {
@@ -164,6 +217,9 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
+ &MemorySwapMeter_class,
+ &SysArchMeter_class,
+ &HugePageMeter_class,
&TasksMeter_class,
&UptimeMeter_class,
&BatteryMeter_class,
@@ -196,7 +252,7 @@ const MeterClass* const Platform_meterTypes[] = {
NULL
};
-int Platform_getUptime() {
+int Platform_getUptime(void) {
double uptime = 0;
FILE* fd = fopen(PROCDIR "/uptime", "r");
if (fd) {
@@ -210,22 +266,28 @@ int Platform_getUptime() {
}
void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
- int activeProcs, totalProcs, lastProc;
- *one = 0;
- *five = 0;
- *fifteen = 0;
-
FILE* fd = fopen(PROCDIR "/loadavg", "r");
- if (fd) {
- int total = fscanf(fd, "%32lf %32lf %32lf %32d/%32d %32d", one, five, fifteen,
- &activeProcs, &totalProcs, &lastProc);
- (void) total;
- assert(total == 6);
- fclose(fd);
- }
+ if (!fd)
+ goto err;
+
+ double scanOne, scanFive, scanFifteen;
+ int r = fscanf(fd, "%lf %lf %lf", &scanOne, &scanFive, &scanFifteen);
+ fclose(fd);
+ if (r != 3)
+ goto err;
+
+ *one = scanOne;
+ *five = scanFive;
+ *fifteen = scanFifteen;
+ return;
+
+err:
+ *one = NAN;
+ *five = NAN;
+ *fifteen = NAN;
}
-int Platform_getMaxPid() {
+int Platform_getMaxPid(void) {
FILE* file = fopen(PROCDIR "/sys/kernel/pid_max", "r");
if (!file)
return -1;
@@ -237,12 +299,18 @@ int Platform_getMaxPid() {
return maxPid;
}
-double Platform_setCPUValues(Meter* this, int cpu) {
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
const LinuxProcessList* pl = (const LinuxProcessList*) this->pl;
- const CPUData* cpuData = &(pl->cpus[cpu]);
+ const CPUData* cpuData = &(pl->cpuData[cpu]);
double total = (double) ( cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod);
double percent;
double* v = this->values;
+
+ if (!cpuData->online) {
+ this->curItems = 0;
+ return NAN;
+ }
+
v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;
v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;
if (this->pl->settings->detailedCPUTime) {
@@ -284,18 +352,21 @@ void Platform_setMemoryValues(Meter* this) {
const ProcessList* pl = this->pl;
const LinuxProcessList* lpl = (const LinuxProcessList*) pl;
- long int usedMem = pl->usedMem;
- long int buffersMem = pl->buffersMem;
- long int cachedMem = pl->cachedMem;
- usedMem -= buffersMem + cachedMem;
- this->total = pl->totalMem;
- this->values[0] = usedMem;
- this->values[1] = buffersMem;
- this->values[2] = cachedMem;
-
- if (lpl->zfs.enabled != 0) {
- this->values[0] -= lpl->zfs.size;
- this->values[2] += lpl->zfs.size;
+ this->total = pl->totalMem;
+ this->values[MEMORY_METER_USED] = pl->usedMem;
+ this->values[MEMORY_METER_BUFFERS] = pl->buffersMem;
+ this->values[MEMORY_METER_SHARED] = pl->sharedMem;
+ this->values[MEMORY_METER_CACHE] = pl->cachedMem;
+ this->values[MEMORY_METER_AVAILABLE] = pl->availableMem;
+
+ if (lpl->zfs.enabled != 0 && !Running_containerized) {
+ // ZFS does not shrink below the value of zfs_arc_min.
+ unsigned long long int shrinkableSize = 0;
+ if (lpl->zfs.size > lpl->zfs.min)
+ shrinkableSize = lpl->zfs.size - lpl->zfs.min;
+ this->values[MEMORY_METER_USED] -= shrinkableSize;
+ this->values[MEMORY_METER_CACHE] += shrinkableSize;
+ this->values[MEMORY_METER_AVAILABLE] += shrinkableSize;
}
}
@@ -303,6 +374,7 @@ void Platform_setSwapValues(Meter* this) {
const ProcessList* pl = this->pl;
this->total = pl->totalSwap;
this->values[0] = pl->usedSwap;
+ this->values[1] = pl->cachedSwap;
}
void Platform_setZramValues(Meter* this) {
@@ -360,117 +432,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;
- struct dirent *de;
- DIR *dirp;
- ssize_t len;
- int fd;
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
+ FileLocks_ProcessData* pdata = xCalloc(1, sizeof(FileLocks_ProcessData));
+ DIR* dirp;
+ 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 (strncmp(buffer, "lock:\t", strlen("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;
}
@@ -497,33 +542,27 @@ bool Platform_getDiskIO(DiskIOData* data) {
if (!fd)
return false;
- unsigned long int read_sum = 0, write_sum = 0, timeSpend_sum = 0;
+ char lastTopDisk[32] = { '\0' };
+
+ unsigned long long int read_sum = 0, write_sum = 0, timeSpend_sum = 0;
char lineBuffer[256];
while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
char diskname[32];
- unsigned long int read_tmp, write_tmp, timeSpend_tmp;
- if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %lu %*u %*u %*u %lu %*u %*u %lu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) {
+ unsigned long long int read_tmp, write_tmp, timeSpend_tmp;
+ if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu %*u %*u %llu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) {
if (String_startsWith(diskname, "dm-"))
continue;
- /* only count root disks, e.g. do not count IO from sda and sda1 twice */
- if ((diskname[0] == 's' || diskname[0] == 'h')
- && diskname[1] == 'd'
- && isalpha((unsigned char)diskname[2])
- && isdigit((unsigned char)diskname[3]))
+ if (String_startsWith(diskname, "zram"))
continue;
- /* only count root disks, e.g. do not count IO from mmcblk0 and mmcblk0p1 twice */
- if (diskname[0] == 'm'
- && diskname[1] == 'm'
- && diskname[2] == 'c'
- && diskname[3] == 'b'
- && diskname[4] == 'l'
- && diskname[5] == 'k'
- && isdigit((unsigned char)diskname[6])
- && diskname[7] == 'p')
+ /* only count root disks, e.g. do not count IO from sda and sda1 twice */
+ if (lastTopDisk[0] && String_startsWith(diskname, lastTopDisk))
continue;
+ /* This assumes disks are listed directly before any of their partitions */
+ String_safeStrncpy(lastTopDisk, diskname, sizeof(lastTopDisk));
+
read_sum += read_tmp;
write_sum += write_tmp;
timeSpend_sum += timeSpend_tmp;
@@ -537,175 +576,119 @@ bool Platform_getDiskIO(DiskIOData* data) {
return true;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
+bool Platform_getNetworkIO(NetworkIOData* data) {
FILE* fd = fopen(PROCDIR "/net/dev", "r");
if (!fd)
return false;
- unsigned long int bytesReceivedSum = 0, packetsReceivedSum = 0, bytesTransmittedSum = 0, packetsTransmittedSum = 0;
+ memset(data, 0, sizeof(NetworkIOData));
char lineBuffer[512];
while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
char interfaceName[32];
- unsigned long int bytesReceivedParsed, packetsReceivedParsed, bytesTransmittedParsed, packetsTransmittedParsed;
- if (sscanf(lineBuffer, "%31s %lu %lu %*u %*u %*u %*u %*u %*u %lu %lu",
+ unsigned long long int bytesReceived, packetsReceived, bytesTransmitted, packetsTransmitted;
+ if (sscanf(lineBuffer, "%31s %llu %llu %*u %*u %*u %*u %*u %*u %llu %llu",
interfaceName,
- &bytesReceivedParsed,
- &packetsReceivedParsed,
- &bytesTransmittedParsed,
- &packetsTransmittedParsed) != 5)
+ &bytesReceived,
+ &packetsReceived,
+ &bytesTransmitted,
+ &packetsTransmitted) != 5)
continue;
if (String_eq(interfaceName, "lo:"))
continue;
- bytesReceivedSum += bytesReceivedParsed;
- packetsReceivedSum += packetsReceivedParsed;
- bytesTransmittedSum += bytesTransmittedParsed;
- packetsTransmittedSum += packetsTransmittedParsed;
+ data->bytesReceived += bytesReceived;
+ data->packetsReceived += packetsReceived;
+ data->bytesTransmitted += bytesTransmitted;
+ data->packetsTransmitted += packetsTransmitted;
}
fclose(fd);
- *bytesReceived = bytesReceivedSum;
- *packetsReceived = packetsReceivedSum;
- *bytesTransmitted = bytesTransmittedSum;
- *packetsTransmitted = packetsTransmittedSum;
return true;
}
// 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) {
- struct dirent* dirEntry = readdir(batteryDir);
- if (!dirEntry)
- break;
+ uint64_t totalFull = 0;
+ uint64_t totalRemain = 0;
- char* entryName = dirEntry->d_name;
+ 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 (;;) {
- 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;
- }
-
- if (dir)
- 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) {
@@ -718,7 +701,6 @@ static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {
// ----------------------------------------
static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
-
*percent = NAN;
*isOnAC = AC_ERROR;
@@ -726,68 +708,81 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
if (!dir)
return;
- unsigned long int totalFull = 0;
- unsigned long int totalRemain = 0;
-
- for (;;) {
- 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;
- char* line = NULL;
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;
@@ -795,23 +790,18 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
}
}
- #undef match
-
if (!now && full && !isnan(capacityLevel))
- totalRemain += (capacityLevel * fullSize);
+ 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')
@@ -819,7 +809,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;
@@ -854,3 +848,186 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) {
Platform_Battery_cacheIsOnAC = *isOnAC;
Platform_Battery_cacheTime = now;
}
+
+void Platform_longOptionsUsage(const char* name)
+{
+#ifdef HAVE_LIBCAP
+ printf(
+" --drop-capabilities[=off|basic|strict] Drop Linux capabilities when running as root\n"
+" off - do not drop any capabilities\n"
+" basic (default) - drop all capabilities not needed by %s\n"
+" strict - drop all capabilities except those needed for\n"
+" core functionality\n", name);
+#else
+ (void) name;
+#endif
+}
+
+CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) {
+#ifndef HAVE_LIBCAP
+ (void) argc;
+ (void) argv;
+#endif
+
+ switch (opt) {
+#ifdef HAVE_LIBCAP
+ case 160: {
+ const char* mode = optarg;
+ if (!mode && optind < argc && argv[optind] != NULL &&
+ (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
+ mode = argv[optind++];
+ }
+
+ if (!mode || String_eq(mode, "basic")) {
+ Platform_capabilitiesMode = CAP_MODE_BASIC;
+ } else if (String_eq(mode, "off")) {
+ Platform_capabilitiesMode = CAP_MODE_OFF;
+ } else if (String_eq(mode, "strict")) {
+ Platform_capabilitiesMode = CAP_MODE_STRICT;
+ } else {
+ fprintf(stderr, "Error: invalid capabilities mode \"%s\".\n", mode);
+ return STATUS_ERROR_EXIT;
+ }
+ return STATUS_OK;
+ }
+#endif
+
+ default:
+ break;
+ }
+ return STATUS_ERROR_EXIT;
+}
+
+#ifdef HAVE_LIBCAP
+static int dropCapabilities(enum CapMode mode) {
+
+ if (mode == CAP_MODE_OFF)
+ return 0;
+
+ /* capabilities we keep to operate */
+ const cap_value_t keepcapsStrict[] = {
+ CAP_DAC_READ_SEARCH,
+ CAP_SYS_PTRACE,
+ };
+ const cap_value_t keepcapsBasic[] = {
+ CAP_DAC_READ_SEARCH, /* read non world-readable process files of other users, like /proc/[pid]/io */
+ CAP_KILL, /* send signals to processes of other users */
+ CAP_SYS_NICE, /* lower process nice value / change nice value for arbitrary processes */
+ CAP_SYS_PTRACE, /* read /proc/[pid]/exe */
+#ifdef HAVE_DELAYACCT
+ CAP_NET_ADMIN, /* communicate over netlink socket for delay accounting */
+#endif
+ };
+ const cap_value_t* const keepcaps = (mode == CAP_MODE_BASIC) ? keepcapsBasic : keepcapsStrict;
+ const size_t ncap = (mode == CAP_MODE_BASIC) ? ARRAYSIZE(keepcapsBasic) : ARRAYSIZE(keepcapsStrict);
+
+ cap_t caps = cap_init();
+ if (caps == NULL) {
+ fprintf(stderr, "Error: can not initialize capabilities: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (cap_clear(caps) < 0) {
+ fprintf(stderr, "Error: can not clear capabilities: %s\n", strerror(errno));
+ cap_free(caps);
+ return -1;
+ }
+
+ cap_t currCaps = cap_get_proc();
+ if (currCaps == NULL) {
+ fprintf(stderr, "Error: can not get current process capabilities: %s\n", strerror(errno));
+ cap_free(caps);
+ return -1;
+ }
+
+ for (size_t i = 0; i < ncap; i++) {
+ if (!CAP_IS_SUPPORTED(keepcaps[i]))
+ continue;
+
+ cap_flag_value_t current;
+ if (cap_get_flag(currCaps, keepcaps[i], CAP_PERMITTED, &current) < 0) {
+ fprintf(stderr, "Error: can not get current value of capability %d: %s\n", keepcaps[i], strerror(errno));
+ cap_free(currCaps);
+ cap_free(caps);
+ return -1;
+ }
+
+ if (current != CAP_SET)
+ continue;
+
+ if (cap_set_flag(caps, CAP_PERMITTED, 1, &keepcaps[i], CAP_SET) < 0) {
+ fprintf(stderr, "Error: can not set permitted capability %d: %s\n", keepcaps[i], strerror(errno));
+ cap_free(currCaps);
+ cap_free(caps);
+ return -1;
+ }
+
+ if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &keepcaps[i], CAP_SET) < 0) {
+ fprintf(stderr, "Error: can not set effective capability %d: %s\n", keepcaps[i], strerror(errno));
+ cap_free(currCaps);
+ cap_free(caps);
+ return -1;
+ }
+ }
+
+ if (cap_set_proc(caps) < 0) {
+ fprintf(stderr, "Error: can not set process capabilities: %s\n", strerror(errno));
+ cap_free(currCaps);
+ cap_free(caps);
+ return -1;
+ }
+
+ cap_free(currCaps);
+ cap_free(caps);
+
+ return 0;
+}
+#endif
+
+bool Platform_init(void) {
+#ifdef HAVE_LIBCAP
+ if (dropCapabilities(Platform_capabilitiesMode) < 0)
+ return false;
+#endif
+
+ if (access(PROCDIR, R_OK) != 0) {
+ fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR);
+ 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 ")) {
+ Running_containerized = true;
+ break;
+ }
+ }
+ fclose(fd);
+ } // if (fd)
+
+ return true;
+}
+
+void Platform_done(void) {
+#ifdef HAVE_SENSORS_SENSORS_H
+ LibSensors_cleanup();
+#endif
+}
diff --git a/linux/Platform.h b/linux/Platform.h
index 280b997..f6ac188 100644
--- a/linux/Platform.h
+++ b/linux/Platform.h
@@ -3,24 +3,44 @@
/*
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>
+#include <stdint.h>
+#include <sys/time.h>
#include <sys/types.h>
#include "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "Macros.h"
#include "Meter.h"
+#include "NetworkIOMeter.h"
#include "Process.h"
#include "ProcessLocksScreen.h"
+#include "RichString.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 ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
-extern int Platform_numberOfFields;
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
@@ -28,10 +48,11 @@ 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);
@@ -40,7 +61,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* this, int cpu);
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
void Platform_setMemoryValues(Meter* this);
@@ -54,19 +75,67 @@ 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_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC);
+
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#ifdef HAVE_LIBCAP
+ #define PLATFORM_LONG_OPTIONS \
+ {"drop-capabilities", optional_argument, 0, 160},
+#else
+ #define PLATFORM_LONG_OPTIONS
+#endif
+
+void Platform_longOptionsUsage(const char* name);
+
+CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);
+
+static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+static inline void Platform_gettime_monotonic(uint64_t* msec) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
-void Platform_getBattery(double *percent, ACPresence *isOnAC);
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
#endif
diff --git a/linux/PressureStallMeter.c b/linux/PressureStallMeter.c
index 745068c..e5089fc 100644
--- a/linux/PressureStallMeter.c
+++ b/linux/PressureStallMeter.c
@@ -2,11 +2,11 @@
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 "PressureStallMeter.h"
+#include "linux/PressureStallMeter.h"
#include <stdbool.h>
#include <string.h>
@@ -25,7 +25,7 @@ static const int PressureStallMeter_attributes[] = {
PRESSURE_STALL_THREEHUNDRED
};
-static void PressureStallMeter_updateValues(Meter* this, char* buffer, size_t len) {
+static void PressureStallMeter_updateValues(Meter* this) {
const char* file;
if (strstr(Meter_name(this), "CPU")) {
file = "cpu";
@@ -43,18 +43,24 @@ static void PressureStallMeter_updateValues(Meter* this, char* buffer, size_t le
}
Platform_getPressureStall(file, some, &this->values[0], &this->values[1], &this->values[2]);
- xSnprintf(buffer, len, "%s %s %.2lf%% %.2lf%% %.2lf%%", some ? "some" : "full", file, this->values[0], this->values[1], this->values[2]);
+
+ /* only print bar for ten (not sixty and threehundred), cause the sum is meaningless */
+ this->curItems = 1;
+
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s %s %5.2lf%% %5.2lf%% %5.2lf%%", some ? "some" : "full", file, this->values[0], this->values[1], this->values[2]);
}
static void PressureStallMeter_display(const Object* cast, RichString* out) {
const Meter* this = (const Meter*)cast;
char buffer[20];
- xSnprintf(buffer, sizeof(buffer), "%.2lf%% ", this->values[0]);
- RichString_write(out, CRT_colors[PRESSURE_STALL_TEN], buffer);
- xSnprintf(buffer, sizeof(buffer), "%.2lf%% ", this->values[1]);
- RichString_append(out, CRT_colors[PRESSURE_STALL_SIXTY], buffer);
- xSnprintf(buffer, sizeof(buffer), "%.2lf%% ", this->values[2]);
- RichString_append(out, CRT_colors[PRESSURE_STALL_THREEHUNDRED], buffer);
+ int len;
+
+ len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[0]);
+ RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_TEN], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[1]);
+ RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_SIXTY], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[2]);
+ RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_THREEHUNDRED], buffer, len);
}
const MeterClass PressureStallCPUSomeMeter_class = {
diff --git a/linux/PressureStallMeter.h b/linux/PressureStallMeter.h
index 1a0ad58..8acf46b 100644
--- a/linux/PressureStallMeter.h
+++ b/linux/PressureStallMeter.h
@@ -6,12 +6,13 @@
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.
*/
#include "Meter.h"
+
extern const MeterClass PressureStallCPUSomeMeter_class;
extern const MeterClass PressureStallIOSomeMeter_class;
diff --git a/linux/ProcessField.h b/linux/ProcessField.h
new file mode 100644
index 0000000..17cafa9
--- /dev/null
+++ b/linux/ProcessField.h
@@ -0,0 +1,52 @@
+#ifndef HEADER_LinuxProcessField
+#define HEADER_LinuxProcessField
+/*
+htop - linux/ProcessField.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+#define PLATFORM_PROCESS_FIELDS \
+ CMINFLT = 11, \
+ CMAJFLT = 13, \
+ UTIME = 14, \
+ STIME = 15, \
+ CUTIME = 16, \
+ CSTIME = 17, \
+ M_SHARE = 41, \
+ M_TRS = 42, \
+ M_DRS = 43, \
+ M_LRS = 44, \
+ CTID = 100, \
+ VPID = 101, \
+ VXID = 102, \
+ RCHAR = 103, \
+ WCHAR = 104, \
+ SYSCR = 105, \
+ SYSCW = 106, \
+ RBYTES = 107, \
+ WBYTES = 108, \
+ CNCLWB = 109, \
+ IO_READ_RATE = 110, \
+ IO_WRITE_RATE = 111, \
+ IO_RATE = 112, \
+ CGROUP = 113, \
+ OOM = 114, \
+ IO_PRIORITY = 115, \
+ PERCENT_CPU_DELAY = 116, \
+ PERCENT_IO_DELAY = 117, \
+ PERCENT_SWAP_DELAY = 118, \
+ M_PSS = 119, \
+ M_SWAP = 120, \
+ M_PSSWP = 121, \
+ CTXT = 122, \
+ SECATTR = 123, \
+ AUTOGROUP_ID = 127, \
+ AUTOGROUP_NICE = 128, \
+ CCGROUP = 129, \
+ // End of list
+
+
+#endif /* HEADER_LinuxProcessField */
diff --git a/linux/SELinuxMeter.c b/linux/SELinuxMeter.c
index 64a3f2a..c35cb68 100644
--- a/linux/SELinuxMeter.c
+++ b/linux/SELinuxMeter.c
@@ -1,22 +1,21 @@
/*
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 "SELinuxMeter.h"
+#include "linux/SELinuxMeter.h"
#include "CRT.h"
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
-#include <linux/magic.h>
#include <sys/statfs.h>
#include <sys/statvfs.h>
-#include "Macros.h"
#include "Object.h"
#include "XUtils.h"
@@ -35,7 +34,7 @@ static bool hasSELinuxMount(void) {
return false;
}
- if (sfbuf.f_type != SELINUX_MAGIC) {
+ if ((uint32_t)sfbuf.f_type != /* SELINUX_MAGIC */ 0xf97cff8cU) {
return false;
}
@@ -70,11 +69,11 @@ static bool isSelinuxEnforcing(void) {
return !!enforce;
}
-static void SELinuxMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t len) {
+static void SELinuxMeter_updateValues(Meter* this) {
enabled = isSelinuxEnabled();
enforcing = isSelinuxEnforcing();
- xSnprintf(buffer, len, "%s%s", enabled ? "enabled" : "disabled", enabled ? (enforcing ? "; mode: enforcing" : "; mode: permissive") : "");
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s%s", enabled ? "enabled" : "disabled", enabled ? (enforcing ? "; mode: enforcing" : "; mode: permissive") : "");
}
const MeterClass SELinuxMeter_class = {
diff --git a/linux/SELinuxMeter.h b/linux/SELinuxMeter.h
index d79ad01..d8a04db 100644
--- a/linux/SELinuxMeter.h
+++ b/linux/SELinuxMeter.h
@@ -3,12 +3,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass SELinuxMeter_class;
#endif /* HEADER_SELinuxMeter */
diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c
index 4350d26..cee3231 100644
--- a/linux/SystemdMeter.c
+++ b/linux/SystemdMeter.c
@@ -1,11 +1,11 @@
/*
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 "SystemdMeter.h"
+#include "linux/SystemdMeter.h"
#include <dlfcn.h>
#include <fcntl.h>
@@ -19,10 +19,22 @@ in the source distribution for its full text.
#include "Macros.h"
#include "Object.h"
#include "RichString.h"
+#include "Settings.h"
#include "XUtils.h"
+#if defined(BUILD_STATIC) && defined(HAVE_LIBSYSTEMD)
+#include <systemd/sd-bus.h>
+#endif
-#define INVALID_VALUE ((unsigned int)-1)
+
+#ifdef BUILD_STATIC
+
+#define sym_sd_bus_open_system sd_bus_open_system
+#define sym_sd_bus_get_property_string sd_bus_get_property_string
+#define sym_sd_bus_get_property_trivial sd_bus_get_property_trivial
+#define sym_sd_bus_unref sd_bus_unref
+
+#else
typedef void sd_bus;
typedef void sd_bus_error;
@@ -30,19 +42,35 @@ static int (*sym_sd_bus_open_system)(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*);
+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;
-static void* dlopenHandle = NULL;
-static sd_bus* bus = NULL;
static void SystemdMeter_done(ATTR_UNUSED Meter* this) {
free(systemState);
systemState = NULL;
+#ifdef BUILD_STATIC
+# ifdef HAVE_LIBSYSTEMD
+ if (bus) {
+ sym_sd_bus_unref(bus);
+ }
+ bus = NULL;
+# endif /* HAVE_LIBSYSTEMD */
+#else /* BUILD_STATIC */
if (bus && dlopenHandle) {
sym_sd_bus_unref(bus);
}
@@ -52,9 +80,12 @@ static void SystemdMeter_done(ATTR_UNUSED Meter* this) {
dlclose(dlopenHandle);
dlopenHandle = NULL;
}
+#endif /* BUILD_STATIC */
}
+#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
static int updateViaLib(void) {
+#ifndef BUILD_STATIC
if (!dlopenHandle) {
dlopenHandle = dlopen("libsystemd.so.0", RTLD_LAZY);
if (!dlopenHandle)
@@ -76,6 +107,7 @@ static int updateViaLib(void) {
#undef resolve
}
+#endif /* !BUILD_STATIC */
int r;
@@ -152,15 +184,21 @@ busfailure:
bus = NULL;
return -2;
+#ifndef BUILD_STATIC
dlfailure:
if (dlopenHandle) {
dlclose(dlopenHandle);
dlopenHandle = NULL;
}
return -1;
+#endif /* !BUILD_STATIC */
}
+#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
static void updateViaExec(void) {
+ if (Settings_isReadonly())
+ return;
+
int fdpair[2];
if (pipe(fdpair) < 0)
return;
@@ -181,15 +219,18 @@ static void updateViaExec(void) {
exit(1);
dup2(fdnull, STDERR_FILENO);
close(fdnull);
- execl("/bin/systemctl",
- "/bin/systemctl",
- "show",
- "--property=SystemState",
- "--property=NFailedUnits",
- "--property=NNames",
- "--property=NJobs",
- "--property=NInstalledJobs",
- NULL);
+ // Use of NULL in variadic functions must have a pointer cast.
+ // The NULL constant is not required by standard to have a pointer type.
+ execlp(
+ "systemctl",
+ "systemctl",
+ "show",
+ "--property=SystemState",
+ "--property=NFailedUnits",
+ "--property=NNames",
+ "--property=NJobs",
+ "--property=NInstalledJobs",
+ (char *)NULL);
exit(127);
}
close(fdpair[1]);
@@ -213,8 +254,7 @@ static void updateViaExec(void) {
if (newline) {
*newline = '\0';
}
- free(systemState);
- systemState = xStrdup(lineBuffer + strlen("SystemState="));
+ free_and_xStrdup(&systemState, lineBuffer + strlen("SystemState="));
} else if (String_startsWith(lineBuffer, "NFailedUnits=")) {
nFailedUnits = strtoul(lineBuffer + strlen("NFailedUnits="), NULL, 10);
} else if (String_startsWith(lineBuffer, "NNames=")) {
@@ -229,15 +269,19 @@ static void updateViaExec(void) {
fclose(commandOutput);
}
-static void SystemdMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t size) {
+static void SystemdMeter_updateValues(Meter* this) {
free(systemState);
systemState = NULL;
nFailedUnits = nInstalledJobs = nNames = nJobs = INVALID_VALUE;
+#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
if (updateViaLib() < 0)
updateViaExec();
+#else
+ updateViaExec();
+#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
- xSnprintf(buffer, size, "%s", systemState ? systemState : "???");
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s", systemState ? systemState : "???");
}
static int zeroDigitColor(unsigned int value) {
@@ -265,51 +309,60 @@ static int valueDigitColor(unsigned int value) {
static void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
char buffer[16];
+ int len;
+ int color = METER_VALUE_ERROR;
- int color = (systemState && 0 == strcmp(systemState, "running")) ? METER_VALUE_OK : METER_VALUE_ERROR;
- RichString_write(out, CRT_colors[color], systemState ? systemState : "???");
+ if (systemState) {
+ color = String_eq(systemState, "running") ? METER_VALUE_OK :
+ String_eq(systemState, "degraded") ? METER_VALUE_ERROR : METER_VALUE_WARN;
+ }
+ RichString_writeAscii(out, CRT_colors[color], systemState ? systemState : "N/A");
- RichString_append(out, CRT_colors[METER_TEXT], " (");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " (");
if (nFailedUnits == INVALID_VALUE) {
buffer[0] = '?';
buffer[1] = '\0';
+ len = 1;
} else {
- xSnprintf(buffer, sizeof(buffer), "%u", nFailedUnits);
+ len = xSnprintf(buffer, sizeof(buffer), "%u", nFailedUnits);
}
- RichString_append(out, zeroDigitColor(nFailedUnits), buffer);
+ RichString_appendnAscii(out, zeroDigitColor(nFailedUnits), buffer, len);
- RichString_append(out, CRT_colors[METER_TEXT], "/");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "/");
if (nNames == INVALID_VALUE) {
buffer[0] = '?';
buffer[1] = '\0';
+ len = 1;
} else {
- xSnprintf(buffer, sizeof(buffer), "%u", nNames);
+ len = xSnprintf(buffer, sizeof(buffer), "%u", nNames);
}
- RichString_append(out, valueDigitColor(nNames), buffer);
+ RichString_appendnAscii(out, valueDigitColor(nNames), buffer, len);
- RichString_append(out, CRT_colors[METER_TEXT], " failed) (");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " failed) (");
if (nJobs == INVALID_VALUE) {
buffer[0] = '?';
buffer[1] = '\0';
+ len = 1;
} else {
- xSnprintf(buffer, sizeof(buffer), "%u", nJobs);
+ len = xSnprintf(buffer, sizeof(buffer), "%u", nJobs);
}
- RichString_append(out, zeroDigitColor(nJobs), buffer);
+ RichString_appendnAscii(out, zeroDigitColor(nJobs), buffer, len);
- RichString_append(out, CRT_colors[METER_TEXT], "/");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "/");
if (nInstalledJobs == INVALID_VALUE) {
buffer[0] = '?';
buffer[1] = '\0';
+ len = 1;
} else {
- xSnprintf(buffer, sizeof(buffer), "%u", nInstalledJobs);
+ len = xSnprintf(buffer, sizeof(buffer), "%u", nInstalledJobs);
}
- RichString_append(out, valueDigitColor(nInstalledJobs), buffer);
+ RichString_appendnAscii(out, valueDigitColor(nInstalledJobs), buffer, len);
- RichString_append(out, CRT_colors[METER_TEXT], " jobs)");
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " jobs)");
}
static const int SystemdMeter_attributes[] = {
diff --git a/linux/SystemdMeter.h b/linux/SystemdMeter.h
index 0f226d6..a05e087 100644
--- a/linux/SystemdMeter.h
+++ b/linux/SystemdMeter.h
@@ -4,12 +4,13 @@
/*
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.
*/
#include "Meter.h"
+
extern const MeterClass SystemdMeter_class;
#endif /* HEADER_SystemdMeter */
diff --git a/linux/ZramMeter.c b/linux/ZramMeter.c
index e6b6937..e1e27b7 100644
--- a/linux/ZramMeter.c
+++ b/linux/ZramMeter.c
@@ -1,4 +1,6 @@
-#include "ZramMeter.h"
+#include "linux/ZramMeter.h"
+
+#include <stddef.h>
#include "CRT.h"
#include "Meter.h"
@@ -11,7 +13,9 @@ static const int ZramMeter_attributes[] = {
ZRAM
};
-static void ZramMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void ZramMeter_updateValues(Meter* this) {
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
int written;
Platform_setZramValues(this);
@@ -37,17 +41,19 @@ static void ZramMeter_updateValues(Meter* this, char* buffer, size_t size) {
static void ZramMeter_display(const Object* cast, RichString* out) {
char buffer[50];
const Meter* this = (const Meter*)cast;
- RichString_write(out, CRT_colors[METER_TEXT], ":");
+
+ RichString_writeAscii(out, CRT_colors[METER_TEXT], ":");
+
Meter_humanUnit(buffer, this->total, sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
Meter_humanUnit(buffer, this->values[0], sizeof(buffer));
- RichString_append(out, CRT_colors[METER_TEXT], " used:");
- RichString_append(out, CRT_colors[METER_VALUE], 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));
- RichString_append(out, CRT_colors[METER_TEXT], " uncompressed:");
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " uncompressed:");
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
}
const MeterClass ZramMeter_class = {
diff --git a/linux/ZramMeter.h b/linux/ZramMeter.h
index 7cf7861..ddba1ba 100644
--- a/linux/ZramMeter.h
+++ b/linux/ZramMeter.h
@@ -3,6 +3,7 @@
#include "Meter.h"
+
extern const MeterClass ZramMeter_class;
#endif
diff --git a/linux/ZramStats.h b/linux/ZramStats.h
index 2305cfd..67aadcc 100644
--- a/linux/ZramStats.h
+++ b/linux/ZramStats.h
@@ -2,9 +2,9 @@
#define HEADER_ZramStats
typedef struct ZramStats_ {
- unsigned long long int totalZram;
- unsigned long long int usedZramComp;
- unsigned long long int usedZramOrig;
+ memory_t totalZram;
+ memory_t usedZramComp;
+ memory_t usedZramOrig;
} ZramStats;
#endif
diff --git a/netbsd/NetBSDProcess.c b/netbsd/NetBSDProcess.c
new file mode 100644
index 0000000..4d4ac4e
--- /dev/null
+++ b/netbsd/NetBSDProcess.c
@@ -0,0 +1,264 @@
+/*
+htop - NetBSDProcess.c
+(C) 2015 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/NetBSDProcess.h"
+
+#include <stdlib.h>
+
+#include "CRT.h"
+#include "Process.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+
+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,
+ },
+ [COMM] = {
+ .name = "Command",
+ .title = "Command ",
+ .description = "Command line",
+ .flags = 0,
+ },
+ [STATE] = {
+ .name = "STATE",
+ .title = "S ",
+ .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)",
+ .flags = 0,
+ },
+ [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 = "SESN",
+ .description = "Process's session ID",
+ .flags = 0,
+ .pidColumn = true,
+ },
+ [TTY] = {
+ .name = "TTY",
+ .title = "TTY ",
+ .description = "Controlling terminal",
+ .flags = 0,
+ },
+ [TPGID] = {
+ .name = "TPGID",
+ .title = "TPGID",
+ .description = "Process ID of the fg process group of the controlling terminal",
+ .flags = 0,
+ .pidColumn = true,
+ },
+ [MINFLT] = {
+ .name = "MINFLT",
+ .title = " MINFLT ",
+ .description = "Number of minor faults which have not required loading a memory page from disk",
+ .flags = 0,
+ .defaultSortDesc = true,
+ },
+ [MAJFLT] = {
+ .name = "MAJFLT",
+ .title = " MAJFLT ",
+ .description = "Number of major faults which have required loading a memory page from disk",
+ .flags = 0,
+ .defaultSortDesc = true,
+ },
+ [PRIORITY] = {
+ .name = "PRIORITY",
+ .title = "PRI ",
+ .description = "Kernel's internal priority for the process",
+ .flags = 0,
+ },
+ [NICE] = {
+ .name = "NICE",
+ .title = " NI ",
+ .description = "Nice value (the higher the value, the more it lets other processes take priority)",
+ .flags = 0,
+ },
+ [STARTTIME] = {
+ .name = "STARTTIME",
+ .title = "START ",
+ .description = "Time the process was started",
+ .flags = 0,
+ },
+ [ELAPSED] = {
+ .name = "ELAPSED",
+ .title = "ELAPSED ",
+ .description = "Time since the process was started",
+ .flags = 0,
+ },
+ [PROCESSOR] = {
+ .name = "PROCESSOR",
+ .title = "CPU ",
+ .description = "Id of the CPU the process last executed on",
+ .flags = 0,
+ },
+ [M_VIRT] = {
+ .name = "M_VIRT",
+ .title = " VIRT ",
+ .description = "Total program size in virtual memory",
+ .flags = 0,
+ .defaultSortDesc = true,
+ },
+ [M_RESIDENT] = {
+ .name = "M_RESIDENT",
+ .title = " RES ",
+ .description = "Resident set size, size of the text and data sections, plus stack usage",
+ .flags = 0,
+ .defaultSortDesc = true,
+ },
+ [ST_UID] = {
+ .name = "ST_UID",
+ .title = "UID",
+ .description = "User ID of the process owner",
+ .flags = 0,
+ },
+ [PERCENT_CPU] = {
+ .name = "PERCENT_CPU",
+ .title = " CPU%",
+ .description = "Percentage of the CPU time the process used in the last sampling",
+ .flags = 0,
+ .defaultSortDesc = true,
+ .autoWidth = true,
+ },
+ [PERCENT_NORM_CPU] = {
+ .name = "PERCENT_NORM_CPU",
+ .title = "NCPU%",
+ .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)",
+ .flags = 0,
+ .defaultSortDesc = true,
+ .autoWidth = true,
+ },
+ [PERCENT_MEM] = {
+ .name = "PERCENT_MEM",
+ .title = "MEM% ",
+ .description = "Percentage of the memory the process is using, based on resident memory size",
+ .flags = 0,
+ .defaultSortDesc = true,
+ },
+ [USER] = {
+ .name = "USER",
+ .title = "USER ",
+ .description = "Username of the process owner (or user ID if name cannot be determined)",
+ .flags = 0,
+ },
+ [TIME] = {
+ .name = "TIME",
+ .title = " TIME+ ",
+ .description = "Total time the process has spent in user and system time",
+ .flags = 0,
+ .defaultSortDesc = true,
+ },
+ [NLWP] = {
+ .name = "NLWP",
+ .title = "NLWP ",
+ .description = "Number of threads in the process",
+ .flags = 0,
+ },
+ [TGID] = {
+ .name = "TGID",
+ .title = "TGID",
+ .description = "Thread group ID (i.e. process ID)",
+ .flags = 0,
+ .pidColumn = true,
+ },
+ [PROC_COMM] = {
+ .name = "COMM",
+ .title = "COMM ",
+ .description = "comm string of the process",
+ .flags = 0,
+ },
+ [PROC_EXE] = {
+ .name = "EXE",
+ .title = "EXE ",
+ .description = "Basename of exe of the process",
+ .flags = 0,
+ },
+ [CWD] = {
+ .name = "CWD",
+ .title = "CWD ",
+ .description = "The current working directory of the process",
+ .flags = PROCESS_FLAG_CWD,
+ },
+
+};
+
+Process* NetBSDProcess_new(const Settings* settings) {
+ NetBSDProcess* this = xCalloc(1, sizeof(NetBSDProcess));
+ Object_setClass(this, Class(NetBSDProcess));
+ Process_init(&this->super, settings);
+ return &this->super;
+}
+
+void Process_delete(Object* cast) {
+ NetBSDProcess* this = (NetBSDProcess*) cast;
+ Process_done((Process*)cast);
+ free(this);
+}
+
+static void NetBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) {
+ char buffer[256]; buffer[255] = '\0';
+ int attr = CRT_colors[DEFAULT_COLOR];
+
+ switch (field) {
+ // add NetBSD-specific fields here
+ default:
+ Process_writeField(this, str, field);
+ return;
+ }
+ RichString_appendWide(str, attr, buffer);
+}
+
+static int NetBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const NetBSDProcess* p1 = (const NetBSDProcess*)v1;
+ const NetBSDProcess* p2 = (const NetBSDProcess*)v2;
+
+ // remove if actually used
+ (void)p1; (void)p2;
+
+ switch (key) {
+ // add NetBSD-specific fields here
+ default:
+ return Process_compareByKey_Base(v1, v2, key);
+ }
+}
+
+const ProcessClass NetBSDProcess_class = {
+ .super = {
+ .extends = Class(Process),
+ .display = Process_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .writeField = NetBSDProcess_writeField,
+ .compareByKey = NetBSDProcess_compareByKey
+};
diff --git a/netbsd/NetBSDProcess.h b/netbsd/NetBSDProcess.h
new file mode 100644
index 0000000..b9e6b26
--- /dev/null
+++ b/netbsd/NetBSDProcess.h
@@ -0,0 +1,32 @@
+#ifndef HEADER_NetBSDProcess
+#define HEADER_NetBSDProcess
+/*
+htop - NetBSDProcess.h
+(C) 2015 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 "Object.h"
+#include "Process.h"
+#include "Settings.h"
+
+
+typedef struct NetBSDProcess_ {
+ Process super;
+} NetBSDProcess;
+
+extern const ProcessClass NetBSDProcess_class;
+
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
+
+Process* NetBSDProcess_new(const Settings* settings);
+
+void Process_delete(Object* cast);
+
+#endif
diff --git a/netbsd/NetBSDProcessList.c b/netbsd/NetBSDProcessList.c
new file mode 100644
index 0000000..197a150
--- /dev/null
+++ b/netbsd/NetBSDProcessList.c
@@ -0,0 +1,504 @@
+/*
+htop - NetBSDProcessList.c
+(C) 2014 Hisham H. Muhammad
+(C) 2015 Michael McConville
+(C) 2021 Santhosh Raju
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "netbsd/NetBSDProcessList.h"
+
+#include <kvm.h>
+#include <math.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <sys/sched.h>
+#include <sys/swap.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <uvm/uvm_extern.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Process.h"
+#include "ProcessList.h"
+#include "Settings.h"
+#include "XUtils.h"
+#include "netbsd/NetBSDProcess.h"
+
+
+static long fscale;
+static int pageSize;
+static int pageSizeKB;
+
+static const struct {
+ const char* name;
+ long int scale;
+} freqSysctls[] = {
+ { "machdep.est.frequency.current", 1 },
+ { "machdep.powernow.frequency.current", 1 },
+ { "machdep.intrepid.frequency.current", 1 },
+ { "machdep.loongson.frequency.current", 1 },
+ { "machdep.cpu.frequency.current", 1 },
+ { "machdep.frequency.current", 1 },
+ { "machdep.tsc_freq", 1000000 },
+};
+
+static void NetBSDProcessList_updateCPUcount(ProcessList* super) {
+ NetBSDProcessList* opl = (NetBSDProcessList*) super;
+
+ // Definitions for sysctl(3), cf. https://nxr.netbsd.org/xref/src/sys/sys/sysctl.h#813
+ const int mib_ncpu_existing[] = { CTL_HW, HW_NCPU }; // Number of existing CPUs
+ const int mib_ncpu_online[] = { CTL_HW, HW_NCPUONLINE }; // Number of online/active CPUs
+
+ int r;
+ unsigned int value;
+ size_t size;
+
+ bool change = false;
+
+ // Query the number of active/online CPUs.
+ size = sizeof(value);
+ r = sysctl(mib_ncpu_online, 2, &value, &size, NULL, 0);
+ if (r < 0 || value < 1) {
+ value = 1;
+ }
+
+ if (value != super->activeCPUs) {
+ super->activeCPUs = value;
+ change = true;
+ }
+
+ // Query the total number of CPUs.
+ size = sizeof(value);
+ r = sysctl(mib_ncpu_existing, 2, &value, &size, NULL, 0);
+ if (r < 0 || value < 1) {
+ value = super->activeCPUs;
+ }
+
+ if (value != super->existingCPUs) {
+ opl->cpuData = xReallocArray(opl->cpuData, value + 1, sizeof(CPUData));
+ super->existingCPUs = value;
+ change = true;
+ }
+
+ // Reset CPU stats when number of online/existing CPU cores changed
+ if (change) {
+ CPUData* dAvg = &opl->cpuData[0];
+ memset(dAvg, '\0', sizeof(CPUData));
+ dAvg->totalTime = 1;
+ dAvg->totalPeriod = 1;
+
+ for (unsigned int i = 0; i < super->existingCPUs; i++) {
+ CPUData* d = &opl->cpuData[i + 1];
+ memset(d, '\0', sizeof(CPUData));
+ d->totalTime = 1;
+ d->totalPeriod = 1;
+ }
+ }
+}
+
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
+ const int fmib[] = { CTL_KERN, KERN_FSCALE };
+ size_t size;
+ char errbuf[_POSIX2_LINE_MAX];
+
+ NetBSDProcessList* npl = xCalloc(1, sizeof(NetBSDProcessList));
+ ProcessList* pl = (ProcessList*) npl;
+ ProcessList_init(pl, Class(NetBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
+
+ NetBSDProcessList_updateCPUcount(pl);
+
+ size = sizeof(fscale);
+ if (sysctl(fmib, 2, &fscale, &size, NULL, 0) < 0) {
+ CRT_fatalError("fscale sysctl call failed");
+ }
+
+ if ((pageSize = sysconf(_SC_PAGESIZE)) == -1)
+ CRT_fatalError("pagesize sysconf call failed");
+ pageSizeKB = pageSize / ONE_K;
+
+ npl->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
+ if (npl->kd == NULL) {
+ CRT_fatalError("kvm_openfiles() failed");
+ }
+
+ return pl;
+}
+
+void ProcessList_delete(ProcessList* this) {
+ NetBSDProcessList* npl = (NetBSDProcessList*) this;
+
+ if (npl->kd) {
+ kvm_close(npl->kd);
+ }
+
+ free(npl->cpuData);
+
+ ProcessList_done(this);
+ free(this);
+}
+
+static void NetBSDProcessList_scanMemoryInfo(ProcessList* pl) {
+ static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2};
+ struct uvmexp_sysctl uvmexp;
+ size_t size_uvmexp = sizeof(uvmexp);
+
+ if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {
+ CRT_fatalError("uvmexp sysctl call failed");
+ }
+
+ pl->totalMem = uvmexp.npages * pageSizeKB;
+ pl->buffersMem = 0;
+ pl->cachedMem = (uvmexp.filepages + uvmexp.execpages) * pageSizeKB;
+ pl->usedMem = (uvmexp.active + uvmexp.wired) * pageSizeKB;
+ pl->totalSwap = uvmexp.swpages * pageSizeKB;
+ pl->usedSwap = uvmexp.swpginuse * pageSizeKB;
+}
+
+static void NetBSDProcessList_updateExe(const struct kinfo_proc2* kproc, Process* proc) {
+ const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_PATHNAME };
+ char buffer[2048];
+ size_t size = sizeof(buffer);
+ if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+ Process_updateExe(proc, NULL);
+ return;
+ }
+
+ /* Kernel threads return an empty buffer */
+ if (buffer[0] == '\0') {
+ Process_updateExe(proc, NULL);
+ return;
+ }
+
+ Process_updateExe(proc, buffer);
+}
+
+static void NetBSDProcessList_updateCwd(const struct kinfo_proc2* kproc, Process* proc) {
+ const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_CWD };
+ char buffer[2048];
+ size_t size = sizeof(buffer);
+ if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ /* Kernel threads return an empty buffer */
+ if (buffer[0] == '\0') {
+ free(proc->procCwd);
+ proc->procCwd = NULL;
+ return;
+ }
+
+ free_and_xStrdup(&proc->procCwd, buffer);
+}
+
+static void NetBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc2* kproc, Process* proc) {
+ Process_updateComm(proc, kproc->p_comm);
+
+ /*
+ * Like NetBSD's top(1), we try to fall back to the command name
+ * (argv[0]) if we fail to construct the full command.
+ */
+ char** arg = kvm_getargv2(kd, kproc, 500);
+ if (arg == NULL || *arg == NULL) {
+ Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
+ return;
+ }
+
+ size_t len = 0;
+ for (int i = 0; arg[i] != NULL; i++) {
+ len += strlen(arg[i]) + 1; /* room for arg and trailing space or NUL */
+ }
+
+ /* don't use xMalloc here - we want to handle huge argv's gracefully */
+ char* s;
+ if ((s = malloc(len)) == NULL) {
+ Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
+ return;
+ }
+
+ *s = '\0';
+
+ int start = 0;
+ int end = 0;
+ for (int i = 0; arg[i] != NULL; i++) {
+ size_t n = strlcat(s, arg[i], len);
+ if (i == 0) {
+ end = MINIMUM(n, len - 1);
+ /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */
+ for (int j = end; j > 0; j--) {
+ if (arg[0][j] == ' ' && arg[0][j - 1] != '\\') {
+ end = (arg[0][j - 1] == ':') ? (j - 1) : j;
+ }
+ }
+ }
+ /* the trailing space should get truncated anyway */
+ strlcat(s, " ", len);
+ }
+
+ Process_updateCmdline(proc, s, start, end);
+
+ free(s);
+}
+
+/*
+ * Borrowed with modifications from NetBSD's top(1).
+ */
+static double getpcpu(const struct kinfo_proc2* kp) {
+ if (fscale == 0)
+ return 0.0;
+
+ return 100.0 * (double)kp->p_pctcpu / fscale;
+}
+
+static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) {
+ const Settings* settings = this->super.settings;
+ bool hideKernelThreads = settings->hideKernelThreads;
+ bool hideUserlandThreads = settings->hideUserlandThreads;
+ int count = 0;
+
+ const struct kinfo_proc2* kprocs = kvm_getproc2(this->kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), &count);
+
+ for (int i = 0; i < count; i++) {
+ const struct kinfo_proc2* kproc = &kprocs[i];
+
+ bool preExisting = false;
+ Process* proc = ProcessList_getProcess(&this->super, kproc->p_pid, &preExisting, NetBSDProcess_new);
+
+ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+
+ if (!preExisting) {
+ proc->pid = kproc->p_pid;
+ proc->ppid = kproc->p_ppid;
+ proc->tpgid = kproc->p_tpgid;
+ proc->tgid = kproc->p_pid;
+ proc->session = kproc->p_sid;
+ proc->pgrp = kproc->p__pgid;
+ proc->isKernelThread = !!(kproc->p_flag & P_SYSTEM);
+ proc->isUserlandThread = proc->pid != proc->tgid;
+ proc->starttime_ctime = kproc->p_ustart_sec;
+ Process_fillStarttimeBuffer(proc);
+ ProcessList_add(&this->super, proc);
+
+ proc->tty_nr = kproc->p_tdev;
+ const char* name = ((dev_t)kproc->p_tdev != KERN_PROC_TTY_NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL;
+ if (!name) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
+
+ NetBSDProcessList_updateExe(kproc, proc);
+ NetBSDProcessList_updateProcessName(this->kd, kproc, proc);
+ } else {
+ if (settings->updateProcessNames) {
+ NetBSDProcessList_updateProcessName(this->kd, kproc, proc);
+ }
+ }
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ NetBSDProcessList_updateCwd(kproc, proc);
+ }
+
+ if (proc->st_uid != kproc->p_uid) {
+ proc->st_uid = kproc->p_uid;
+ proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid);
+ }
+
+ proc->m_virt = kproc->p_vm_vsize;
+ proc->m_resident = kproc->p_vm_rssize;
+
+ proc->percent_mem = (proc->m_resident * pageSizeKB) / (double)(this->super.totalMem) * 100.0;
+ proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0, this->super.activeCPUs * 100.0);
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ proc->nlwp = kproc->p_nlwps;
+ proc->nice = kproc->p_nice - 20;
+ proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
+ proc->priority = kproc->p_priority - PZERO;
+ proc->processor = kproc->p_cpuid;
+ proc->minflt = kproc->p_uru_minflt;
+ proc->majflt = kproc->p_uru_majflt;
+
+ int nlwps = 0;
+ const struct kinfo_lwp* klwps = kvm_getlwps(this->kd, kproc->p_pid, kproc->p_paddr, sizeof(struct kinfo_lwp), &nlwps);
+
+ /* TODO: According to the link below, SDYING should be a regarded state */
+ /* Taken from: https://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/sys/proc.h */
+ switch (kproc->p_realstat) {
+ case SIDL: proc->state = IDLE; break;
+ case SACTIVE:
+ // We only consider the first LWP with a one of the below states.
+ for (int j = 0; j < nlwps; j++) {
+ if (klwps) {
+ switch (klwps[j].l_stat) {
+ case LSONPROC: proc->state = RUNNING; break;
+ case LSRUN: proc->state = RUNNABLE; break;
+ case LSSLEEP: proc->state = SLEEPING; break;
+ case LSSTOP: proc->state = STOPPED; break;
+ default: proc->state = UNKNOWN;
+ }
+ if (proc->state != UNKNOWN)
+ break;
+ } else {
+ proc->state = UNKNOWN;
+ break;
+ }
+ }
+ break;
+ case SSTOP: proc->state = STOPPED; break;
+ case SZOMB: proc->state = ZOMBIE; break;
+ case SDEAD: proc->state = DEFUNCT; break;
+ default: proc->state = UNKNOWN;
+ }
+
+ if (Process_isKernelThread(proc)) {
+ this->super.kernelThreads++;
+ } else if (Process_isUserlandThread(proc)) {
+ this->super.userlandThreads++;
+ }
+
+ this->super.totalTasks++;
+ if (proc->state == RUNNING) {
+ this->super.runningTasks++;
+ }
+ proc->updated = true;
+ }
+}
+
+static void getKernelCPUTimes(int cpuId, u_int64_t* times) {
+ const int mib[] = { CTL_KERN, KERN_CP_TIME, cpuId };
+ size_t length = sizeof(*times) * CPUSTATES;
+ if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {
+ CRT_fatalError("sysctl kern.cp_time2 failed");
+ }
+}
+
+static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
+ unsigned long long totalTime = 0;
+ for (int i = 0; i < CPUSTATES; i++) {
+ totalTime += times[i];
+ }
+
+ unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
+
+ cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);
+ cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);
+ cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);
+ cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);
+ cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);
+ cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);
+ cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);
+
+ cpu->totalTime = totalTime;
+ cpu->userTime = times[CP_USER];
+ cpu->niceTime = times[CP_NICE];
+ cpu->sysTime = times[CP_SYS];
+ cpu->sysAllTime = sysAllTime;
+ cpu->intrTime = times[CP_INTR];
+ cpu->idleTime = times[CP_IDLE];
+}
+
+static void NetBSDProcessList_scanCPUTime(NetBSDProcessList* this) {
+ u_int64_t kernelTimes[CPUSTATES] = {0};
+ u_int64_t avg[CPUSTATES] = {0};
+
+ for (unsigned int i = 0; i < this->super.existingCPUs; i++) {
+ getKernelCPUTimes(i, kernelTimes);
+ CPUData* cpu = &this->cpuData[i + 1];
+ kernelCPUTimesToHtop(kernelTimes, cpu);
+
+ avg[CP_USER] += cpu->userTime;
+ avg[CP_NICE] += cpu->niceTime;
+ avg[CP_SYS] += cpu->sysTime;
+ avg[CP_INTR] += cpu->intrTime;
+ avg[CP_IDLE] += cpu->idleTime;
+ }
+
+ for (int i = 0; i < CPUSTATES; i++) {
+ avg[i] /= this->super.activeCPUs;
+ }
+
+ kernelCPUTimesToHtop(avg, &this->cpuData[0]);
+}
+
+static void NetBSDProcessList_scanCPUFrequency(NetBSDProcessList* this) {
+ unsigned int cpus = this->super.existingCPUs;
+ bool match = false;
+ char name[64];
+ long int freq = 0;
+ size_t freqSize;
+
+ for (unsigned int i = 0; i < cpus; i++) {
+ this->cpuData[i + 1].frequency = NAN;
+ }
+
+ /* newer hardware supports per-core frequency, for e.g. ARM big.LITTLE */
+ for (unsigned int i = 0; i < cpus; i++) {
+ xSnprintf(name, sizeof(name), "machdep.cpufreq.cpu%u.current", i);
+ freqSize = sizeof(freq);
+ if (sysctlbyname(name, &freq, &freqSize, NULL, 0) != -1) {
+ this->cpuData[i + 1].frequency = freq; /* already in MHz */
+ match = true;
+ }
+ }
+
+ if (match) {
+ return;
+ }
+
+ /*
+ * Iterate through legacy sysctl nodes for single-core frequency until
+ * we find a match...
+ */
+ for (size_t i = 0; i < ARRAYSIZE(freqSysctls); i++) {
+ freqSize = sizeof(freq);
+ if (sysctlbyname(freqSysctls[i].name, &freq, &freqSize, NULL, 0) != -1) {
+ freq /= freqSysctls[i].scale; /* scale to MHz */
+ match = true;
+ break;
+ }
+ }
+
+ if (match) {
+ for (unsigned int i = 0; i < cpus; i++) {
+ this->cpuData[i + 1].frequency = freq;
+ }
+ }
+}
+
+void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
+ NetBSDProcessList* npl = (NetBSDProcessList*) super;
+
+ NetBSDProcessList_scanMemoryInfo(super);
+ NetBSDProcessList_scanCPUTime(npl);
+
+ if (super->settings->showCPUFrequency) {
+ NetBSDProcessList_scanCPUFrequency(npl);
+ }
+
+ // in pause mode only gather global data for meters (CPU/memory/...)
+ if (pauseProcessUpdate) {
+ return;
+ }
+
+ NetBSDProcessList_scanProcs(npl);
+}
+
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+
+ // TODO: Support detecting online / offline CPUs.
+ return true;
+}
diff --git a/netbsd/NetBSDProcessList.h b/netbsd/NetBSDProcessList.h
new file mode 100644
index 0000000..d228f48
--- /dev/null
+++ b/netbsd/NetBSDProcessList.h
@@ -0,0 +1,58 @@
+#ifndef HEADER_NetBSDProcessList
+#define HEADER_NetBSDProcessList
+/*
+htop - NetBSDProcessList.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 <kvm.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "Hashtable.h"
+#include "ProcessList.h"
+#include "UsersTable.h"
+
+
+typedef struct CPUData_ {
+ unsigned long long int totalTime;
+ unsigned long long int userTime;
+ unsigned long long int niceTime;
+ unsigned long long int sysTime;
+ unsigned long long int sysAllTime;
+ unsigned long long int spinTime;
+ unsigned long long int intrTime;
+ unsigned long long int idleTime;
+
+ unsigned long long int totalPeriod;
+ unsigned long long int userPeriod;
+ unsigned long long int nicePeriod;
+ unsigned long long int sysPeriod;
+ unsigned long long int sysAllPeriod;
+ unsigned long long int spinPeriod;
+ unsigned long long int intrPeriod;
+ unsigned long long int idlePeriod;
+
+ double frequency;
+} CPUData;
+
+typedef struct NetBSDProcessList_ {
+ ProcessList super;
+ kvm_t* kd;
+
+ CPUData* cpuData;
+} NetBSDProcessList;
+
+
+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);
+
+#endif
diff --git a/netbsd/Platform.c b/netbsd/Platform.c
new file mode 100644
index 0000000..ad6050c
--- /dev/null
+++ b/netbsd/Platform.c
@@ -0,0 +1,509 @@
+/*
+htop - netbsd/Platform.c
+(C) 2014 Hisham H. Muhammad
+(C) 2015 Michael McConville
+(C) 2021 Santhosh Raju
+(C) 2021 Nia Alarie
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "netbsd/Platform.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <paths.h>
+#include <unistd.h>
+#include <kvm.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <net/if.h>
+#include <prop/proplib.h>
+#include <sys/envsys.h>
+#include <sys/iostat.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "CPUMeter.h"
+#include "ClockMeter.h"
+#include "DateMeter.h"
+#include "DateTimeMeter.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"
+#include "SysArchMeter.h"
+#include "TasksMeter.h"
+#include "UptimeMeter.h"
+#include "XUtils.h"
+#include "netbsd/NetBSDProcess.h"
+#include "netbsd/NetBSDProcessList.h"
+
+/*
+ * The older proplib APIs will be deprecated in NetBSD 10, but we still
+ * want to support the 9.x stable branch.
+ *
+ * Create aliases for the newer functions that are missing from 9.x.
+ */
+#if !__NetBSD_Prereq__(9,99,65)
+#define prop_string_equals_string prop_string_equals_cstring
+#define prop_number_signed_value prop_number_integer_value
+#endif
+
+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
+ */
+const SignalItem Platform_signals[] = {
+ { .name = " 0 Cancel", .number = 0 },
+ { .name = " 1 SIGHUP", .number = 1 },
+ { .name = " 2 SIGINT", .number = 2 },
+ { .name = " 3 SIGQUIT", .number = 3 },
+ { .name = " 4 SIGILL", .number = 4 },
+ { .name = " 5 SIGTRAP", .number = 5 },
+ { .name = " 6 SIGABRT", .number = 6 },
+ { .name = " 6 SIGIOT", .number = 6 },
+ { .name = " 7 SIGEMT", .number = 7 },
+ { .name = " 8 SIGFPE", .number = 8 },
+ { .name = " 9 SIGKILL", .number = 9 },
+ { .name = "10 SIGBUS", .number = 10 },
+ { .name = "11 SIGSEGV", .number = 11 },
+ { .name = "12 SIGSYS", .number = 12 },
+ { .name = "13 SIGPIPE", .number = 13 },
+ { .name = "14 SIGALRM", .number = 14 },
+ { .name = "15 SIGTERM", .number = 15 },
+ { .name = "16 SIGURG", .number = 16 },
+ { .name = "17 SIGSTOP", .number = 17 },
+ { .name = "18 SIGTSTP", .number = 18 },
+ { .name = "19 SIGCONT", .number = 19 },
+ { .name = "20 SIGCHLD", .number = 20 },
+ { .name = "21 SIGTTIN", .number = 21 },
+ { .name = "22 SIGTTOU", .number = 22 },
+ { .name = "23 SIGIO", .number = 23 },
+ { .name = "24 SIGXCPU", .number = 24 },
+ { .name = "25 SIGXFSZ", .number = 25 },
+ { .name = "26 SIGVTALRM", .number = 26 },
+ { .name = "27 SIGPROF", .number = 27 },
+ { .name = "28 SIGWINCH", .number = 28 },
+ { .name = "29 SIGINFO", .number = 29 },
+ { .name = "30 SIGUSR1", .number = 30 },
+ { .name = "31 SIGUSR2", .number = 31 },
+ { .name = "32 SIGPWR", .number = 32 },
+ { .name = "33 SIGRTMIN", .number = 33 },
+ { .name = "34 SIGRTMIN+1", .number = 34 },
+ { .name = "35 SIGRTMIN+2", .number = 35 },
+ { .name = "36 SIGRTMIN+3", .number = 36 },
+ { .name = "37 SIGRTMIN+4", .number = 37 },
+ { .name = "38 SIGRTMIN+5", .number = 38 },
+ { .name = "39 SIGRTMIN+6", .number = 39 },
+ { .name = "40 SIGRTMIN+7", .number = 40 },
+ { .name = "41 SIGRTMIN+8", .number = 41 },
+ { .name = "42 SIGRTMIN+9", .number = 42 },
+ { .name = "43 SIGRTMIN+10", .number = 43 },
+ { .name = "44 SIGRTMIN+11", .number = 44 },
+ { .name = "45 SIGRTMIN+12", .number = 45 },
+ { .name = "46 SIGRTMIN+13", .number = 46 },
+ { .name = "47 SIGRTMIN+14", .number = 47 },
+ { .name = "48 SIGRTMIN+15", .number = 48 },
+ { .name = "49 SIGRTMIN+16", .number = 49 },
+ { .name = "50 SIGRTMIN+17", .number = 50 },
+ { .name = "51 SIGRTMIN+18", .number = 51 },
+ { .name = "52 SIGRTMIN+19", .number = 52 },
+ { .name = "53 SIGRTMIN+20", .number = 53 },
+ { .name = "54 SIGRTMIN+21", .number = 54 },
+ { .name = "55 SIGRTMIN+22", .number = 55 },
+ { .name = "56 SIGRTMIN+23", .number = 56 },
+ { .name = "57 SIGRTMIN+24", .number = 57 },
+ { .name = "58 SIGRTMIN+25", .number = 58 },
+ { .name = "59 SIGRTMIN+26", .number = 59 },
+ { .name = "60 SIGRTMIN+27", .number = 60 },
+ { .name = "61 SIGRTMIN+28", .number = 61 },
+ { .name = "62 SIGRTMIN+29", .number = 62 },
+ { .name = "63 SIGRTMAX", .number = 63 },
+};
+
+const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
+
+const MeterClass* const Platform_meterTypes[] = {
+ &CPUMeter_class,
+ &ClockMeter_class,
+ &DateMeter_class,
+ &DateTimeMeter_class,
+ &LoadAverageMeter_class,
+ &LoadMeter_class,
+ &MemoryMeter_class,
+ &SwapMeter_class,
+ &MemorySwapMeter_class,
+ &TasksMeter_class,
+ &UptimeMeter_class,
+ &BatteryMeter_class,
+ &HostnameMeter_class,
+ &SysArchMeter_class,
+ &AllCPUsMeter_class,
+ &AllCPUs2Meter_class,
+ &AllCPUs4Meter_class,
+ &AllCPUs8Meter_class,
+ &LeftCPUsMeter_class,
+ &RightCPUsMeter_class,
+ &LeftCPUs2Meter_class,
+ &RightCPUs2Meter_class,
+ &LeftCPUs4Meter_class,
+ &RightCPUs4Meter_class,
+ &LeftCPUs8Meter_class,
+ &RightCPUs8Meter_class,
+ &BlankMeter_class,
+ &DiskIOMeter_class,
+ &NetworkIOMeter_class,
+ NULL
+};
+
+bool Platform_init(void) {
+ /* no platform-specific setup needed */
+ return true;
+}
+
+void Platform_done(void) {
+ /* no platform-specific cleanup needed */
+}
+
+void Platform_setBindings(Htop_Action* keys) {
+ /* no platform-specific key bindings */
+ (void) keys;
+}
+
+int Platform_getUptime(void) {
+ struct timeval bootTime, currTime;
+ const int mib[2] = { CTL_KERN, KERN_BOOTTIME };
+ size_t size = sizeof(bootTime);
+
+ int err = sysctl(mib, 2, &bootTime, &size, NULL, 0);
+ if (err) {
+ return -1;
+ }
+ gettimeofday(&currTime, NULL);
+
+ return (int) difftime(currTime.tv_sec, bootTime.tv_sec);
+}
+
+void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
+ struct loadavg loadAverage;
+ const int mib[2] = { CTL_VM, VM_LOADAVG };
+ size_t size = sizeof(loadAverage);
+
+ int err = sysctl(mib, 2, &loadAverage, &size, NULL, 0);
+ if (err) {
+ *one = 0;
+ *five = 0;
+ *fifteen = 0;
+ return;
+ }
+ *one = (double) loadAverage.ldavg[0] / loadAverage.fscale;
+ *five = (double) loadAverage.ldavg[1] / loadAverage.fscale;
+ *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;
+}
+
+int 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];
+ 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) {
+ v[CPU_METER_KERNEL] = cpuData->sysPeriod / total * 100.0;
+ v[CPU_METER_IRQ] = cpuData->intrPeriod / total * 100.0;
+ v[CPU_METER_SOFTIRQ] = 0.0;
+ v[CPU_METER_STEAL] = 0.0;
+ v[CPU_METER_GUEST] = 0.0;
+ 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];
+ this->curItems = 4;
+ }
+
+ totalPercent = CLAMP(totalPercent, 0.0, 100.0);
+
+ v[CPU_METER_FREQUENCY] = cpuData->frequency;
+ v[CPU_METER_TEMPERATURE] = NAN;
+
+ return totalPercent;
+}
+
+void Platform_setMemoryValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+ this->total = pl->totalMem;
+ this->values[MEMORY_METER_USED] = pl->usedMem;
+ this->values[MEMORY_METER_BUFFERS] = pl->buffersMem;
+ // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ this->values[MEMORY_METER_CACHE] = pl->cachedMem;
+ // this->values[MEMORY_METER_AVAILABLE] = "available memory"
+}
+
+void Platform_setSwapValues(Meter* this) {
+ const ProcessList* pl = this->pl;
+ this->total = pl->totalSwap;
+ this->values[SWAP_METER_USED] = pl->usedSwap;
+ this->values[SWAP_METER_CACHE] = NAN;
+}
+
+char* Platform_getProcessEnv(pid_t pid) {
+ char errbuf[_POSIX2_LINE_MAX];
+ char* env;
+ char** ptr;
+ int count;
+ kvm_t* kt;
+ const struct kinfo_proc2* kproc;
+ size_t capacity = 4096, size = 0;
+
+ if ((kt = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf)) == NULL) {
+ return NULL;
+ }
+
+ if ((kproc = kvm_getproc2(kt, KERN_PROC_PID, pid, sizeof(struct kinfo_proc2), &count)) == NULL) {
+ (void) kvm_close(kt);
+ return NULL;
+ }
+
+ if ((ptr = kvm_getenvv2(kt, kproc, 0)) == NULL) {
+ (void) kvm_close(kt);
+ return NULL;
+ }
+
+ env = xMalloc(capacity);
+ for (char** p = ptr; *p; p++) {
+ size_t len = strlen(*p) + 1;
+
+ while (size + len > capacity) {
+ if (capacity > (SIZE_MAX / 2)) {
+ free(env);
+ env = NULL;
+ goto end;
+ }
+
+ capacity *= 2;
+ env = xRealloc(env, capacity);
+ }
+
+ String_safeStrncpy(env + size, *p, len);
+ size += len;
+ }
+
+ if (size < 2 || env[size - 1] || env[size - 2]) {
+ if (size + 2 < capacity)
+ env = xRealloc(env, capacity + 2);
+ env[size] = 0;
+ env[size + 1] = 0;
+ }
+
+end:
+ (void) kvm_close(kt);
+ return env;
+}
+
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
+ (void)pid;
+ return NULL;
+}
+
+bool Platform_getDiskIO(DiskIOData* data) {
+ const int mib[] = { CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl) };
+ struct io_sysctl* iostats = NULL;
+ size_t size = 0;
+
+ for (int retry = 3; retry > 0; retry--) {
+ /* get the size of the IO statistic array */
+ if (sysctl(mib, __arraycount(mib), iostats, &size, NULL, 0) < 0)
+ CRT_fatalError("Unable to get size of io_sysctl");
+
+ if (size == 0) {
+ free(iostats);
+ return false;
+ }
+
+ iostats = xRealloc(iostats, size);
+
+ errno = 0;
+
+ if (sysctl(mib, __arraycount(mib), iostats, &size, NULL, 0) == 0)
+ break;
+
+ if (errno != ENOMEM)
+ CRT_fatalError("Unable to get disk IO statistics");
+ }
+
+ if (errno == ENOMEM)
+ CRT_fatalError("Unable to get disk IO statistics");
+
+ uint64_t bytesReadSum = 0;
+ uint64_t bytesWriteSum = 0;
+ uint64_t busyTimeSum = 0;
+
+ for (size_t i = 0, count = size / sizeof(struct io_sysctl); i < count; i++) {
+ /* ignore NFS activity */
+ if (iostats[i].type != IOSTAT_DISK)
+ continue;
+
+ bytesReadSum += iostats[i].rbytes;
+ bytesWriteSum += iostats[i].wbytes;
+ busyTimeSum += iostats[i].busysum_usec;
+ }
+
+ data->totalBytesRead = bytesReadSum;
+ data->totalBytesWritten = bytesWriteSum;
+ data->totalMsTimeSpend = busyTimeSum / 1000;
+
+ free(iostats);
+ return true;
+}
+
+bool Platform_getNetworkIO(NetworkIOData* data) {
+ struct ifaddrs* ifaddrs = NULL;
+
+ if (getifaddrs(&ifaddrs) != 0)
+ return false;
+
+ for (const struct ifaddrs* ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
+ if (!ifa->ifa_addr)
+ continue;
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+ if (ifa->ifa_flags & IFF_LOOPBACK)
+ continue;
+
+ const struct if_data* ifd = (const struct if_data *)ifa->ifa_data;
+
+ data->bytesReceived += ifd->ifi_ibytes;
+ data->packetsReceived += ifd->ifi_ipackets;
+ data->bytesTransmitted += ifd->ifi_obytes;
+ data->packetsTransmitted += ifd->ifi_opackets;
+ }
+
+ freeifaddrs(ifaddrs);
+ return true;
+}
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC) {
+ prop_dictionary_t dict, fields, props;
+ prop_object_t device, class;
+
+ intmax_t totalCharge = 0;
+ intmax_t totalCapacity = 0;
+
+ *percent = NAN;
+ *isOnAC = AC_ERROR;
+
+ int fd = open(_PATH_SYSMON, O_RDONLY);
+ if (fd == -1)
+ goto error;
+
+ if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict) != 0)
+ goto error;
+
+ prop_object_iterator_t devIter = prop_dictionary_iterator(dict);
+ if (devIter == NULL)
+ goto error;
+
+ while ((device = prop_object_iterator_next(devIter)) != NULL) {
+ prop_object_t fieldsArray = prop_dictionary_get_keysym(dict, device);
+ if (fieldsArray == NULL)
+ goto error;
+
+ prop_object_iterator_t fieldsIter = prop_array_iterator(fieldsArray);
+ if (fieldsIter == NULL)
+ goto error;
+
+ bool isACAdapter = false;
+ bool isBattery = false;
+
+ /* only assume battery is not present if explicitly stated */
+ intmax_t isPresent = 1;
+ intmax_t isConnected = 0;
+ intmax_t curCharge = 0;
+ intmax_t maxCharge = 0;
+
+ while ((fields = prop_object_iterator_next(fieldsIter)) != NULL) {
+ props = prop_dictionary_get(fields, "device-properties");
+ if (props != NULL) {
+ class = prop_dictionary_get(props, "device-class");
+
+ if (prop_string_equals_string(class, "ac-adapter")) {
+ isACAdapter = true;
+ } else if (prop_string_equals_string(class, "battery")) {
+ isBattery = true;
+ }
+ continue;
+ }
+
+ prop_object_t curValue = prop_dictionary_get(fields, "cur-value");
+ prop_object_t maxValue = prop_dictionary_get(fields, "max-value");
+ prop_object_t descField = prop_dictionary_get(fields, "description");
+
+ if (descField == NULL || curValue == NULL)
+ continue;
+
+ if (prop_string_equals_string(descField, "connected")) {
+ isConnected = prop_number_signed_value(curValue);
+ } else if (prop_string_equals_string(descField, "present")) {
+ isPresent = prop_number_signed_value(curValue);
+ } else if (prop_string_equals_string(descField, "charge")) {
+ if (maxValue == NULL)
+ continue;
+ curCharge = prop_number_signed_value(curValue);
+ maxCharge = prop_number_signed_value(maxValue);
+ }
+ }
+
+ if (isBattery && isPresent) {
+ totalCharge += curCharge;
+ totalCapacity += maxCharge;
+ }
+
+ if (isACAdapter && *isOnAC != AC_PRESENT) {
+ *isOnAC = isConnected ? AC_PRESENT : AC_ABSENT;
+ }
+ }
+
+ *percent = ((double)totalCharge / (double)totalCapacity) * 100.0;
+
+error:
+ if (fd != -1)
+ close(fd);
+}
diff --git a/netbsd/Platform.h b/netbsd/Platform.h
new file mode 100644
index 0000000..0e53b45
--- /dev/null
+++ b/netbsd/Platform.h
@@ -0,0 +1,124 @@
+#ifndef HEADER_Platform
+#define HEADER_Platform
+/*
+htop - netbsd/Platform.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 <stddef.h>
+#include <stdint.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "Action.h"
+#include "BatteryMeter.h"
+#include "DiskIOMeter.h"
+#include "Meter.h"
+#include "NetworkIOMeter.h"
+#include "Process.h"
+#include "ProcessLocksScreen.h"
+#include "SignalsPanel.h"
+#include "CommandLine.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
+
+
+/* There are no Long Options for NetBSD as of now. */
+#define PLATFORM_LONG_OPTIONS \
+ // End of list
+
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
+
+/* see /usr/include/sys/signal.h */
+extern const SignalItem Platform_signals[];
+
+extern const unsigned int Platform_numberOfSignals;
+
+extern const MeterClass* const Platform_meterTypes[];
+
+bool Platform_init(void);
+
+void Platform_done(void);
+
+void Platform_setBindings(Htop_Action* keys);
+
+int Platform_getUptime(void);
+
+void Platform_getLoadAverage(double* one, double* five, double* fifteen);
+
+int Platform_getMaxPid(void);
+
+double Platform_setCPUValues(Meter* this, int cpu);
+
+void Platform_setMemoryValues(Meter* this);
+
+void Platform_setSwapValues(Meter* this);
+
+char* Platform_getProcessEnv(pid_t pid);
+
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+
+bool Platform_getDiskIO(DiskIOData* data);
+
+bool Platform_getNetworkIO(NetworkIOData* data);
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC);
+
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+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) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+static inline void Platform_gettime_monotonic(uint64_t* msec) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
+
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
+
+#endif
diff --git a/netbsd/ProcessField.h b/netbsd/ProcessField.h
new file mode 100644
index 0000000..87d4a69
--- /dev/null
+++ b/netbsd/ProcessField.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_NetBSDProcessField
+#define HEADER_NetBSDProcessField
+/*
+htop - netbsd/ProcessField.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+#define PLATFORM_PROCESS_FIELDS \
+ // End of list
+
+
+#endif /* HEADER_NetBSDProcessField */
diff --git a/netbsd/README.md b/netbsd/README.md
new file mode 100644
index 0000000..ed7be0a
--- /dev/null
+++ b/netbsd/README.md
@@ -0,0 +1,32 @@
+NetBSD support in htop(1)
+===
+
+This implementation utilizes kvm_getprocs(3), sysctl(3), etc, eliminating the
+need for mount_procfs(8) with Linux compatibility enabled.
+
+The implementation was initially based on the OpenBSD support in htop(1).
+
+Notes on NetBSD curses
+---
+
+NetBSD is one of the last operating systems to use and maintain its own
+implementation of Curses.
+
+htop(1) can be compiled against either ncurses or NetBSD's curses(3).
+In order for NetBSD's libcurses to be used, htop(1) must be configured with
+`--disable-unicode`. This is necessary because htop(1) with Unicode enabled
+directly accesses ncurses's cchar_t struct, which has different contents
+in NetBSD's curses.
+
+Versions of libcurses in NetBSD 9 and prior have no mouse support
+(this is an ncurses extension). Newer versions contain no-op mouse functions
+for compatibility with ncurses.
+
+What needs improvement
+---
+
+* Kernel and userspace threads are not displayed or counted -
+ maybe look at NetBSD top(1).
+* Support for compiling using libcurses's Unicode support.
+* Support for fstat(1) (view open files, like lsof(8) on Linux).
+* Support for ktrace(1) (like strace(1) on Linux).
diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c
index df5002a..c2f2ed4 100644
--- a/openbsd/OpenBSDProcess.c
+++ b/openbsd/OpenBSDProcess.c
@@ -2,11 +2,11 @@
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 "OpenBSDProcess.h"
+#include "openbsd/OpenBSDProcess.h"
#include <stdlib.h>
@@ -16,7 +16,7 @@ in the source distribution for its full text.
#include "XUtils.h"
-ProcessFieldData Process_fields[] = {
+const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[0] = {
.name = "",
.title = NULL,
@@ -25,9 +25,10 @@ ProcessFieldData Process_fields[] = {
},
[PID] = {
.name = "PID",
- .title = " PID ",
+ .title = "PID",
.description = "Process/thread ID",
.flags = 0,
+ .pidColumn = true,
},
[COMM] = {
.name = "Command",
@@ -43,45 +44,51 @@ ProcessFieldData Process_fields[] = {
},
[PPID] = {
.name = "PPID",
- .title = " PPID ",
+ .title = "PPID",
.description = "Parent process ID",
.flags = 0,
+ .pidColumn = true,
},
[PGRP] = {
.name = "PGRP",
- .title = " PGRP ",
+ .title = "PGRP",
.description = "Process group ID",
.flags = 0,
+ .pidColumn = true,
},
[SESSION] = {
.name = "SESSION",
- .title = " SESN ",
+ .title = "SESN",
.description = "Process's session ID",
.flags = 0,
+ .pidColumn = true,
},
- [TTY_NR] = {
- .name = "TTY_NR",
- .title = " TTY ",
+ [TTY] = {
+ .name = "TTY",
+ .title = "TTY ",
.description = "Controlling terminal",
.flags = 0,
},
[TPGID] = {
.name = "TPGID",
- .title = " 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,
},
[PRIORITY] = {
.name = "PRIORITY",
@@ -101,6 +108,12 @@ ProcessFieldData Process_fields[] = {
.description = "Time the process was started",
.flags = 0,
},
+ [ELAPSED] = {
+ .name = "ELAPSED",
+ .title = "ELAPSED ",
+ .description = "Time since the process was started",
+ .flags = 0,
+ },
[PROCESSOR] = {
.name = "PROCESSOR",
.title = "CPU ",
@@ -112,40 +125,47 @@ ProcessFieldData Process_fields[] = {
.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 ",
+ .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",
.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 ",
+ .title = "USER ",
.description = "Username of the process owner (or user ID if name cannot be determined)",
.flags = 0,
},
@@ -154,6 +174,7 @@ ProcessFieldData Process_fields[] = {
.title = " TIME+ ",
.description = "Total time the process has spent in user and system time",
.flags = 0,
+ .defaultSortDesc = true,
},
[NLWP] = {
.name = "NLWP",
@@ -163,30 +184,28 @@ ProcessFieldData Process_fields[] = {
},
[TGID] = {
.name = "TGID",
- .title = " TGID ",
+ .title = "TGID",
.description = "Thread group ID (i.e. process ID)",
.flags = 0,
+ .pidColumn = true,
},
- [LAST_PROCESSFIELD] = {
- .name = "*** report bug! ***",
- .title = NULL,
- .description = NULL,
+ [PROC_COMM] = {
+ .name = "COMM",
+ .title = "COMM ",
+ .description = "comm string of the process",
.flags = 0,
},
-};
+ [CWD] = {
+ .name = "CWD",
+ .title = "CWD ",
+ .description = "The current working directory of the process",
+ .flags = PROCESS_FLAG_CWD,
+ },
-ProcessPidColumn Process_pidColumns[] = {
- { .id = PID, .label = "PID" },
- { .id = PPID, .label = "PPID" },
- { .id = TPGID, .label = "TPGID" },
- { .id = TGID, .label = "TGID" },
- { .id = PGRP, .label = "PGRP" },
- { .id = SESSION, .label = "SESN" },
- { .id = 0, .label = NULL },
};
Process* OpenBSDProcess_new(const Settings* settings) {
- OpenBSDProcess* this = xCalloc(sizeof(OpenBSDProcess), 1);
+ OpenBSDProcess* this = xCalloc(1, sizeof(OpenBSDProcess));
Object_setClass(this, Class(OpenBSDProcess));
Process_init(&this->super, settings);
return &this->super;
@@ -209,28 +228,20 @@ static void OpenBSDProcess_writeField(const Process* this, RichString* str, Proc
Process_writeField(this, str, field);
return;
}
- RichString_append(str, attr, buffer);
+ RichString_appendWide(str, attr, buffer);
}
-static long OpenBSDProcess_compare(const void* v1, const void* v2) {
- const OpenBSDProcess *p1, *p2;
- const Settings *settings = ((const Process*)v1)->settings;
-
- if (settings->direction == 1) {
- p1 = (const OpenBSDProcess*)v1;
- p2 = (const OpenBSDProcess*)v2;
- } else {
- p2 = (const OpenBSDProcess*)v1;
- p1 = (const OpenBSDProcess*)v2;
- }
+static int OpenBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const OpenBSDProcess* p1 = (const OpenBSDProcess*)v1;
+ const OpenBSDProcess* p2 = (const OpenBSDProcess*)v2;
// remove if actually used
(void)p1; (void)p2;
- switch (settings->sortKey) {
+ switch (key) {
// add OpenBSD-specific fields here
default:
- return Process_compare(v1, v2);
+ return Process_compareByKey_Base(v1, v2, key);
}
}
@@ -239,11 +250,8 @@ const ProcessClass OpenBSDProcess_class = {
.extends = Class(Process),
.display = Process_display,
.delete = Process_delete,
- .compare = OpenBSDProcess_compare
+ .compare = Process_compare
},
.writeField = OpenBSDProcess_writeField,
+ .compareByKey = OpenBSDProcess_compareByKey
};
-
-bool Process_isThread(const Process* this) {
- return Process_isKernelThread(this) || Process_isUserlandThread(this);
-}
diff --git a/openbsd/OpenBSDProcess.h b/openbsd/OpenBSDProcess.h
index 2d01513..898c537 100644
--- a/openbsd/OpenBSDProcess.h
+++ b/openbsd/OpenBSDProcess.h
@@ -4,7 +4,7 @@
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.
*/
@@ -15,29 +15,19 @@ in the source distribution for its full text.
#include "Settings.h"
-typedef enum OpenBSDProcessFields_ {
- // Add platform-specific fields here, with ids >= 100
- LAST_PROCESSFIELD = 100,
-} OpenBSDProcessField;
-
typedef struct OpenBSDProcess_ {
Process super;
-} OpenBSDProcess;
-
-#define Process_isKernelThread(_process) (_process->pgrp == 0)
-#define Process_isUserlandThread(_process) (_process->pid != _process->tgid)
+ /* 'Kernel virtual addr of u-area' to detect main threads */
+ uint64_t addr;
+} OpenBSDProcess;
extern const ProcessClass OpenBSDProcess_class;
-extern ProcessFieldData Process_fields[];
-
-extern ProcessPidColumn Process_pidColumns[];
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
Process* OpenBSDProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-bool Process_isThread(const Process* this);
-
#endif
diff --git a/openbsd/OpenBSDProcessList.c b/openbsd/OpenBSDProcessList.c
index 5412030..d070e5e 100644
--- a/openbsd/OpenBSDProcessList.c
+++ b/openbsd/OpenBSDProcessList.c
@@ -2,13 +2,12 @@
htop - OpenBSDProcessList.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 "OpenBSDProcessList.h"
+#include "openbsd/OpenBSDProcessList.h"
-#include <err.h>
#include <kvm.h>
#include <limits.h>
#include <stdlib.h>
@@ -26,49 +25,102 @@ in the source distribution for its full text.
#include "CRT.h"
#include "Macros.h"
#include "Object.h"
-#include "OpenBSDProcess.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;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
- const int mib[] = { CTL_HW, HW_NCPU };
- const int fmib[] = { CTL_KERN, KERN_FSCALE };
+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, pidMatchList, userId);
+ ProcessList_init(pl, Class(OpenBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
- size = sizeof(pl->cpuCount);
- r = sysctl(mib, 2, &pl->cpuCount, &size, NULL, 0);
- if (r < 0 || pl->cpuCount < 1) {
- pl->cpuCount = 1;
- }
- opl->cpus = xCalloc(pl->cpuCount + 1, sizeof(CPUData));
+ OpenBSDProcessList_updateCPUcount(pl);
size = sizeof(fscale);
if (sysctl(fmib, 2, &fscale, &size, NULL, 0) < 0) {
- err(1, "fscale sysctl call failed");
+ CRT_fatalError("fscale sysctl call failed");
}
- for (int i = 0; i <= pl->cpuCount; i++) {
- CPUData* d = opl->cpus + i;
- d->totalTime = 1;
- d->totalPeriod = 1;
- }
+ 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) {
- errx(1, "kvm_open: %s", errbuf);
+ CRT_fatalError("kvm_openfiles() failed");
}
+ opl->cpuSpeed = -1;
+
return pl;
}
@@ -79,7 +131,7 @@ void ProcessList_delete(ProcessList* this) {
kvm_close(opl->kd);
}
- free(opl->cpus);
+ free(opl->cpuData);
ProcessList_done(this);
free(this);
@@ -91,11 +143,11 @@ static void OpenBSDProcessList_scanMemoryInfo(ProcessList* pl) {
size_t size_uvmexp = sizeof(uvmexp);
if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {
- err(1, "uvmexp sysctl call failed");
+ CRT_fatalError("uvmexp sysctl call failed");
}
- pl->totalMem = uvmexp.npages * CRT_pageSizeKB;
- pl->usedMem = (uvmexp.npages - uvmexp.free - uvmexp.paging) * CRT_pageSizeKB;
+ 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 };
@@ -103,10 +155,10 @@ static void OpenBSDProcessList_scanMemoryInfo(ProcessList* pl) {
size_t size_bcstats = sizeof(bcstats);
if (sysctl(bcache_mib, 3, &bcstats, &size_bcstats, NULL, 0) < 0) {
- err(1, "cannot get vfs.bcachestat");
+ CRT_fatalError("cannot get vfs.bcachestat");
}
- pl->cachedMem = bcstats.numbufpages * CRT_pageSizeKB;
+ pl->cachedMem = bcstats.numbufpages * pageSizeKB;
/*
* Copyright (c) 1994 Thorsten Lockert <tholo@sigmasoft.com>
@@ -138,15 +190,37 @@ static void OpenBSDProcessList_scanMemoryInfo(ProcessList* pl) {
}
}
-static char* OpenBSDProcessList_readProcessName(kvm_t* kd, const struct kinfo_proc* kproc, int* basenameEnd) {
+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) {
- *basenameEnd = strlen(kproc->p_comm);
- return xStrdup(kproc->p_comm);
+ Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
+ return;
}
size_t len = 0;
@@ -157,22 +231,32 @@ static char* OpenBSDProcessList_readProcessName(kvm_t* kd, const struct kinfo_pr
/* don't use xMalloc here - we want to handle huge argv's gracefully */
char* s;
if ((s = malloc(len)) == NULL) {
- *basenameEnd = strlen(kproc->p_comm);
- return xStrdup(kproc->p_comm);
+ 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) {
- *basenameEnd = MINIMUM(n, len - 1);
+ 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);
}
- return s;
+ Process_updateCmdline(proc, s, start, end);
+
+ free(s);
}
/*
@@ -187,79 +271,112 @@ static double getpcpu(const struct kinfo_proc* kp) {
static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) {
const Settings* settings = this->super.settings;
- bool hideKernelThreads = settings->hideKernelThreads;
- bool hideUserlandThreads = settings->hideUserlandThreads;
+ 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, 0, sizeof(struct kinfo_proc), &count);
+ 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];
- bool preExisting = false;
- Process* proc = ProcessList_getProcess(&this->super, kproc->p_pid, &preExisting, OpenBSDProcess_new);
- //OpenBSDProcess* fp = (OpenBSDProcess*) proc;
+ /* 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;
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+ 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->tty_nr = kproc->p_tdev;
proc->pgrp = kproc->p__pgid;
- proc->st_uid = kproc->p_uid;
+ proc->isKernelThread = proc->pgrp == 0;
+ proc->isUserlandThread = kproc->p_tid != -1;
proc->starttime_ctime = kproc->p_ustart_sec;
Process_fillStarttimeBuffer(proc);
- proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid);
ProcessList_add(&this->super, proc);
- proc->comm = OpenBSDProcessList_readProcessName(this->kd, kproc, &proc->basenameOffset);
+
+ OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD) {
+ OpenBSDProcessList_updateCwd(kproc, proc);
+ }
+
+ proc->tty_nr = kproc->p_tdev;
+ const char* name = ((dev_t)kproc->p_tdev != NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL;
+ if (!name || String_eq(name, "??")) {
+ free(proc->tty_name);
+ proc->tty_name = NULL;
+ } else {
+ free_and_xStrdup(&proc->tty_name, name);
+ }
} else {
if (settings->updateProcessNames) {
- free(proc->comm);
- proc->comm = OpenBSDProcessList_readProcessName(this->kd, kproc, &proc->basenameOffset);
+ OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
}
}
- proc->m_virt = kproc->p_vm_dsize;
- proc->m_resident = kproc->p_vm_rssize;
- proc->percent_mem = (proc->m_resident * CRT_pageSizeKB) / (double)(this->super.totalMem) * 100.0;
- proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0, this->super.cpuCount * 100.0);
- //proc->nlwp = kproc->p_numthreads;
+ fp->addr = kproc->p_addr;
+ proc->m_virt = kproc->p_vm_dsize * pageSizeKB;
+ proc->m_resident = kproc->p_vm_rssize * pageSizeKB;
+
+ proc->percent_mem = proc->m_resident / (float)this->super.totalMem * 100.0F;
+ proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0F, this->super.activeCPUs * 100.0F);
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
proc->nice = kproc->p_nice - 20;
proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
proc->priority = kproc->p_priority - PZERO;
+ proc->processor = kproc->p_cpuid;
+ proc->minflt = kproc->p_uru_minflt;
+ proc->majflt = kproc->p_uru_majflt;
+ proc->nlwp = 1;
+ if (proc->st_uid != kproc->p_uid) {
+ proc->st_uid = kproc->p_uid;
+ proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid);
+ }
+
+ /* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */
switch (kproc->p_stat) {
- case SIDL: proc->state = '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 SDEAD: proc->state = 'D'; break;
- case SONPROC: proc->state = 'P'; break;
- default: proc->state = '?';
+ 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++;
- // SRUN ('R') means runnable, not running
- if (proc->state == 'P') {
+ if (proc->state == RUNNING) {
this->super.runningTasks++;
}
+
+ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
proc->updated = true;
}
}
-static unsigned long long saturatingSub(unsigned long long a, unsigned long long b) {
- return a > b ? a - b : 0;
-}
-
-static void getKernelCPUTimes(int cpuId, u_int64_t* times) {
+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) {
@@ -275,7 +392,7 @@ static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
- // XXX Not sure if CP_SPIN should be added to sysAllTime.
+ // 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];
@@ -308,9 +425,14 @@ static void OpenBSDProcessList_scanCPUTime(OpenBSDProcessList* this) {
u_int64_t kernelTimes[CPUSTATES] = {0};
u_int64_t avg[CPUSTATES] = {0};
- for (int i = 0; i < this->super.cpuCount; i++) {
+ for (unsigned int i = 0; i < this->super.existingCPUs; i++) {
+ CPUData* cpu = &this->cpuData[i + 1];
+
+ if (!cpu->online) {
+ continue;
+ }
+
getKernelCPUTimes(i, kernelTimes);
- CPUData* cpu = this->cpus + i + 1;
kernelCPUTimesToHtop(kernelTimes, cpu);
avg[CP_USER] += cpu->userTime;
@@ -324,15 +446,27 @@ static void OpenBSDProcessList_scanCPUTime(OpenBSDProcessList* this) {
}
for (int i = 0; i < CPUSTATES; i++) {
- avg[i] /= this->super.cpuCount;
+ avg[i] /= this->super.activeCPUs;
}
- kernelCPUTimesToHtop(avg, this->cpus);
+ 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);
@@ -343,3 +477,10 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
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/OpenBSDProcessList.h b/openbsd/OpenBSDProcessList.h
index a6195a5..89fdb09 100644
--- a/openbsd/OpenBSDProcessList.h
+++ b/openbsd/OpenBSDProcessList.h
@@ -4,7 +4,7 @@
htop - OpenBSDProcessList.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.
*/
@@ -35,21 +35,26 @@ typedef struct CPUData_ {
unsigned long long int spinPeriod;
unsigned long long int intrPeriod;
unsigned long long int idlePeriod;
+
+ bool online;
} CPUData;
typedef struct OpenBSDProcessList_ {
ProcessList super;
kvm_t* kd;
- CPUData* cpus;
+ CPUData* cpuData;
+ int cpuSpeed;
} OpenBSDProcessList;
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* this);
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/openbsd/Platform.c b/openbsd/Platform.c
index dae8072..1ce5ba1 100644
--- a/openbsd/Platform.c
+++ b/openbsd/Platform.c
@@ -2,11 +2,11 @@
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 "Platform.h"
+#include "openbsd/Platform.h"
#include <errno.h>
#include <kvm.h>
@@ -15,6 +15,8 @@ in the source distribution for its full text.
#include <stdlib.h>
#include <string.h>
#include <time.h>
+#include <sys/signal.h> // needs to be included before <sys/proc.h> for 'struct sigaltstack'
+#include <sys/proc.h>
#include <sys/resource.h>
#include <sys/sensors.h>
#include <sys/sysctl.h>
@@ -30,21 +32,29 @@ in the source distribution for its full text.
#include "LoadAverageMeter.h"
#include "Macros.h"
#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
#include "Meter.h"
-#include "OpenBSDProcess.h"
-#include "OpenBSDProcessList.h"
#include "ProcessList.h"
#include "Settings.h"
#include "SignalsPanel.h"
#include "SwapMeter.h"
+#include "SysArchMeter.h"
#include "TasksMeter.h"
#include "UptimeMeter.h"
#include "XUtils.h"
+#include "openbsd/OpenBSDProcess.h"
+#include "openbsd/OpenBSDProcessList.h"
-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",
+ },
+};
-int Platform_numberOfFields = LAST_PROCESSFIELD;
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
/*
* See /usr/include/sys/signal.h
@@ -97,10 +107,12 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
+ &MemorySwapMeter_class,
&TasksMeter_class,
&UptimeMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
&AllCPUs4Meter_class,
@@ -117,8 +129,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) {
@@ -130,7 +143,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);
@@ -161,18 +174,24 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;
}
-int Platform_getMaxPid() {
- // this is hard-coded in sys/proc.h - no sysctl exists
- return 99999;
+int Platform_getMaxPid(void) {
+ return 2 * THREAD_PID_OFFSET;
}
-double Platform_setCPUValues(Meter* this, int cpu) {
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
const OpenBSDProcessList* pl = (const OpenBSDProcessList*) this->pl;
- const CPUData* cpuData = &(pl->cpus[cpu]);
- double total = cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod;
+ const CPUData* cpuData = &(pl->cpuData[cpu]);
+ double total;
double totalPercent;
double* v = this->values;
+ if (!cpuData->online) {
+ this->curItems = 0;
+ return NAN;
+ }
+
+ total = cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod;
+
v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;
v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;
if (this->pl->settings->detailedCPUTime) {
@@ -196,6 +215,8 @@ double Platform_setCPUValues(Meter* this, int cpu) {
v[CPU_METER_TEMPERATURE] = NAN;
+ v[CPU_METER_FREQUENCY] = (pl->cpuSpeed != -1) ? pl->cpuSpeed : NAN;
+
return totalPercent;
}
@@ -206,15 +227,18 @@ void Platform_setMemoryValues(Meter* this) {
long int cachedMem = pl->cachedMem;
usedMem -= buffersMem + cachedMem;
this->total = pl->totalMem;
- this->values[0] = usedMem;
- this->values[1] = buffersMem;
- this->values[2] = cachedMem;
+ this->values[MEMORY_METER_USED] = usedMem;
+ this->values[MEMORY_METER_BUFFERS] = buffersMem;
+ // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ 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[SWAP_METER_USED] = pl->usedSwap;
+ this->values[SWAP_METER_CACHE] = NAN;
}
char* Platform_getProcessEnv(pid_t pid) {
@@ -245,7 +269,13 @@ char* Platform_getProcessEnv(pid_t pid) {
for (char** p = ptr; *p; p++) {
size_t len = strlen(*p) + 1;
- if (size + len > capacity) {
+ while (size + len > capacity) {
+ if (capacity > (SIZE_MAX / 2)) {
+ free(env);
+ env = NULL;
+ goto end;
+ }
+
capacity *= 2;
env = xRealloc(env, capacity);
}
@@ -261,19 +291,14 @@ 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) {
- (void)pid;
- (void)inode;
- return NULL;
-}
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
- (void)pid;
- return NULL;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -282,15 +307,9 @@ bool Platform_getDiskIO(DiskIOData* data) {
return false;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
+bool Platform_getNetworkIO(NetworkIOData* data) {
// TODO
- *bytesReceived = 0;
- *packetsReceived = 0;
- *bytesTransmitted = 0;
- *packetsTransmitted = 0;
+ (void)data;
return false;
}
@@ -303,7 +322,7 @@ static bool findDevice(const char* name, int* mib, struct sensordev* snsrdev, si
if (errno == ENOENT)
return false;
}
- if (strcmp(name, snsrdev->xname) == 0) {
+ if (String_eq(name, snsrdev->xname)) {
return true;
}
}
diff --git a/openbsd/Platform.h b/openbsd/Platform.h
index 0e2d435..27d792e 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.
*/
@@ -14,17 +14,21 @@ in the source distribution for its full text.
#include "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
#include "Meter.h"
+#include "NetworkIOMeter.h"
#include "Process.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
-extern ProcessFieldData Process_fields[];
+extern const ScreenDefaults Platform_defaultScreens[];
-extern ProcessField Platform_defaultFields[];
-
-extern int Platform_numberOfFields;
+extern const unsigned int Platform_numberOfDefaultScreens;
/* see /usr/include/sys/signal.h */
extern const SignalItem Platform_signals[];
@@ -33,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);
@@ -45,7 +49,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* this, int cpu);
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
void Platform_setMemoryValues(Meter* this);
@@ -53,17 +57,62 @@ 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);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
void Platform_getBattery(double* percent, ACPresence* isOnAC);
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+static inline 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) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+static inline void Platform_gettime_monotonic(uint64_t* msec) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
+
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
+
#endif
diff --git a/openbsd/ProcessField.h b/openbsd/ProcessField.h
new file mode 100644
index 0000000..b8e8d6b
--- /dev/null
+++ b/openbsd/ProcessField.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_OpenBSDProcessField
+#define HEADER_OpenBSDProcessField
+/*
+htop - openbsd/ProcessField.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+#define PLATFORM_PROCESS_FIELDS \
+ // End of list
+
+
+#endif /* HEADER_OpenBSDProcessField */
diff --git a/pcp-htop.5.in b/pcp-htop.5.in
new file mode 100644
index 0000000..dbc0dfb
--- /dev/null
+++ b/pcp-htop.5.in
@@ -0,0 +1,237 @@
+.TH "PCP-HTOP" "5" "2023" "@PACKAGE_STRING@" "File Formats"
+.SH "NAME"
+\f3pcp-htop\f1 \- pcp-htop configuration file
+.SH "DESCRIPTION"
+.B pcp-htop
+is a customizable performance metrics reporting tool.
+It has a dynamic architecture, where a set of configuration files
+provide additional, optional meters and columns to extend the fixed
+set of display options provided by regular
+.BR htop .
+.LP
+These configuration files can be provided from both system-wide
+locations (first
+.I @sysconfdir@/pcp/htop
+then
+.IR @datarootdir@/pcp/htop )
+and below the user's home directory (usually
+.IR ~/.config/htop ).
+Within these locations the
+.I meters
+and
+.I columns
+are scanned for dynamic Meter and Column specifications.
+.LP
+Meters are displayed in the top part of the
+.B pcp-htop
+window, and columns are displayed in the lower part.
+Meters tend to display system-wide metrics, and Columns
+display metrics about individual processes.
+.LP
+The formats are similar but have slightly different requirements.
+Both formats follow the common ini-style. Blank lines are ignored.
+Lines starting with the "#" character are treated as comments.
+.SH "METERS"
+The following is an example configuration for a new Redis meter:
+.LP
+.ft CW
+.nf
+.in +0.5i
+[redisclient]
+caption = Redis clients
+type = bar
+blocked.metric = redis.blocked_clients
+blocked.color = blue
+blocked.label = blk
+clients.metric = redis.connected_clients
+clients.color = green
+clients.label = conn
+.in
+.fi
+.ft 1
+.LP
+A configuration file can contain multiple meter definitions.
+Each definition begins with a identifying name enclosed by
+square brackets \-
+.I redisclient
+in this example.
+The name is used internally within
+.B pcp-htop
+and must be unique, must begin with an alphabetic character,
+and may subsequently only contain alphanumeric characters or
+the underscore character.
+No whitespace or other characters are allowed.
+.LP
+There are several parameters that define the way the meter
+will be displayed to the user.
+.TP 5
+.B caption
+This value is displayed on the Setup screen once the meter
+has been selected.
+A truncated version of the
+.I caption
+will also be displayed (followed by a colon) on the primary
+display while the meter is updating.
+.TP
+.B description
+This can be used to provide more detail during the meter
+selection process on the Setup screen, and if present it is
+displayed in the "Available Meters" column.
+If not present, the
+.B caption
+will be used for this.
+If neither is present, the internal (mandatory)
+.B name
+will be used.
+.TP
+.B type
+This setting allows a preferred default meter type to be specified.
+The associated value must be one of
+.IR bar ,
+.IR text ,
+.IR graph ,
+or
+.IR led .
+If no value is provided for a dynamic meter, the default value of
+.IR text
+will be used.
+.TP
+.B maximum
+A numeric value can also be set to size the meter, such that
+values (e.g. for a
+.I bar
+type meter display) will be scaled within range zero to
+.IR maximum .
+.LP
+The remaining definition syntax describes the individual
+metric(s) which will be used to animate the meter.
+One or more metrics must be specified for each meter and
+there are several properties associated with each.
+Once again, these metrics must be named (the same rules
+described above for meters apply here) and the following
+properties can be configured:
+.TP 5
+.B name.metric
+This is the only mandatory field and associates a PCP metric
+with the meter.
+Values sampled for each metric at runtime provide the
+animation visible in the
+.B pcp-htop
+display.
+The metric specification can be either a PCP metric name as
+listed by
+.BR pminfo (1)
+or a "derived" metric expression.
+The format for derived metric expressions is described on the
+.BR pmRegisterDerived (3)
+manual page.
+.TP
+.B name.color
+Setting color to be used when rendering metric values.
+Possible values are
+.IR red ,
+.IR green ,
+.IR blue ,
+.IR cyan ,
+.IR magenta ,
+.IR yellow ,
+.IR gray ,
+.I darkgray
+or
+.IR white .
+.TP
+.B name.label
+An optional, short label to display before the metric value.
+The ":" character will be appended to the
+.I label
+before the metric value part of the display.
+.TP
+.B name.suffix
+An optional, short suffix to display after the metric value.
+Commonly used to indicate values as a percentage using a "%"
+.I suffix
+value and to provide the base unit of measurement.
+Note that since PCP maintains units for metrics, for those
+metrics that have dimension in "space" (bytes, kilobytes,
+megabytes, etc), a suffix will be automatically appended.
+.SH "COLUMNS"
+The following is an example configuration for a new column
+showing open file descriptors for each process:
+.LP
+.ft CW
+.nf
+.in +0.5i
+[openfds]
+heading = FDS
+caption = FDCOUNT
+description = Open file descriptors
+metric = proc.fd.count
+width = 3
+.in
+.fi
+.ft 1
+.LP
+A configuration file can contain multiple column definitions.
+Each definition begins with a identifying name enclosed
+by square brackets \-
+.I openfds
+in this example, and the same rules apply as described above
+for meter names.
+.LP
+Each column must specify a metric.
+Optional parameters can also be set.
+.TP 5
+.B metric
+As with meters, the metric value must be either a PCP metric
+name as listed by
+.BR pminfo (1)
+or a derived metric.
+The metric must have an instance domain (set of values) and
+that instance domain must map to the set of processes with
+the instance identifier being PIDs (process identifiers).
+Typically this will be metrics from the
+.I proc
+or
+.I hotproc
+namespace (\c
+.BR pmdaproc (1)),
+but metrics from other domains (\c
+.BR pmdabcc (1),
+.BR pmdabpf (1),
+etc) that have per-process values are equally applicable.
+.TP
+.B width
+Column width to use when displaying values for the metric.
+A negative value can be used to specify left alignment.
+An upper column limit of 28 characters is enforced.
+The default column width is 5 characters.
+.TP
+.B heading
+The short title that will be displayed at the head of the
+column \- usually a short, cryptic, all uppercase string.
+.TP
+.B caption
+A short identifying word presented to users on the Setup
+screen under both the Available and Active Columns lists.
+.TP
+.B description
+Text that assists users to understand the meaning of this
+column when it is being presented via the Setup screen in
+the Available Columns list.
+.SH "SEE ALSO"
+.BR pcp-htop (1),
+.BR pminfo (1),
+.BR pmcd (1),
+.BR pmdaproc (1),
+.BR pmdabcc (1),
+.BR pmdabpf (1)
+and
+.BR pmRegisterDerived (3).
+.SH "AUTHORS"
+.B htop
+was originally developed by Hisham Muhammad.
+Nowadays it is maintained by the community at <htop@groups.io>.
+.LP
+.B pcp-htop
+is maintained as a collaboration between the <htop@groups.io> and <pcp@groups.io>
+communities, and forms part of the Performance Co-Pilot suite of tools.
diff --git a/pcp-htop.c b/pcp-htop.c
new file mode 100644
index 0000000..2713c89
--- /dev/null
+++ b/pcp-htop.c
@@ -0,0 +1,26 @@
+/*
+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
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include <pcp/pmapi.h>
+
+#include "CommandLine.h"
+#include "Platform.h"
+
+
+int main(int argc, char** argv) {
+ const char* name = "pcp-htop";
+ pmSetProgname(name);
+
+ /* extract environment variables */
+ opts.flags |= PM_OPTFLAG_ENV_ONLY;
+ (void)pmGetOptions(argc, argv, &opts);
+
+ return CommandLine_run(name, argc, argv);
+}
diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c
new file mode 100644
index 0000000..33c6d72
--- /dev/null
+++ b/pcp/PCPDynamicColumn.c
@@ -0,0 +1,348 @@
+/*
+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
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "pcp/PCPDynamicColumn.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Platform.h"
+#include "Process.h"
+#include "ProcessList.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+#include "pcp/PCPProcess.h"
+#include "pcp/PCPMetric.h"
+
+
+static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) {
+ if (!column->super.name[0])
+ return false;
+
+ size_t bytes = 16 + strlen(column->super.name);
+ char* metricName = xMalloc(bytes);
+ xSnprintf(metricName, bytes, "htop.column.%s", column->super.name);
+
+ column->metricName = metricName;
+ column->id = columns->offset + columns->cursor;
+ columns->cursor++;
+
+ Platform_addMetric(column->id, metricName);
+ return true;
+}
+
+static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) {
+ /* lookup a dynamic metric with this name, else create */
+ if (PCPDynamicColumn_addMetric(columns, column) == false)
+ return;
+
+ /* derived metrics in all dynamic columns for simplicity */
+ 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);
+ }
+}
+
+// Ensure a valid name for use in a PCP metric name and in htoprc
+static bool PCPDynamicColumn_validateColumnName(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 column 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 column name at %s line %u\n\"%s\"",
+ pmGetProgname(), path, line, key);
+ return false;
+ }
+ return true;
+}
+
+// Ensure a column name has not been defined previously
+static bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) {
+ return DynamicColumn_search(columns->table, key, NULL) == NULL;
+}
+
+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));
+
+ size_t id = columns->count + LAST_PROCESSFIELD;
+ Hashtable_put(columns->table, id, column);
+ columns->count++;
+
+ return column;
+}
+
+static void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* path) {
+ FILE* file = fopen(path, "r");
+ if (!file)
+ return;
+
+ PCPDynamicColumn* column = 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 heading - i.e. new column */
+ ok = PCPDynamicColumn_validateColumnName(key + 1, path, lineno);
+ if (ok)
+ ok = PCPDynamicColumn_uniqueName(key + 1, columns);
+ if (ok)
+ column = PCPDynamicColumn_new(columns, key + 1);
+ } else if (value && column && String_eq(key, "caption")) {
+ free_and_xStrdup(&column->super.caption, value);
+ } else if (value && column && String_eq(key, "heading")) {
+ free_and_xStrdup(&column->super.heading, value);
+ } else if (value && column && String_eq(key, "description")) {
+ 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, "metric")) {
+ PCPDynamicColumn_parseMetric(columns, column, path, lineno, value);
+ }
+ String_freeArray(config);
+ free(value);
+ free(key);
+ }
+ fclose(file);
+}
+
+static void PCPDynamicColumn_scanDir(PCPDynamicColumns* columns, 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);
+ PCPDynamicColumn_parseFile(columns, file);
+ free(file);
+ }
+ closedir(dir);
+}
+
+void PCPDynamicColumns_init(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;
+
+ 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 */
+ if (override) {
+ path = String_cat(override, "/columns/");
+ PCPDynamicColumn_scanDir(columns, path);
+ free(path);
+ }
+
+ /* next, search in home directory alongside htoprc */
+ if (xdgConfigHome)
+ path = String_cat(xdgConfigHome, "/htop/columns/");
+ else if (home)
+ path = String_cat(home, "/.config/htop/columns/");
+ else
+ path = NULL;
+ if (path) {
+ PCPDynamicColumn_scanDir(columns, path);
+ free(path);
+ }
+
+ /* next, search in the system columns directory */
+ path = String_cat(sysconf, "/htop/columns/");
+ PCPDynamicColumn_scanDir(columns, path);
+ free(path);
+
+ /* next, try the readonly system columns directory */
+ path = String_cat(share, "/htop/columns/");
+ PCPDynamicColumn_scanDir(columns, path);
+ free(path);
+}
+
+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);
+}
+
+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);
+
+ pmAtomValue atom;
+ if (!PCPMetric_instance(this->id, proc->pid, pp->offset, &atom, type)) {
+ RichString_appendAscii(str, CRT_colors[METER_VALUE_ERROR], "no data");
+ 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;
+ }
+
+ char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1];
+ int attr = CRT_colors[DEFAULT_COLOR];
+ 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);
+ break;
+ case PM_TYPE_U32:
+ xSnprintf(buffer, sizeof(buffer), "%*u ", width, atom.ul);
+ RichString_appendAscii(str, attr, buffer);
+ break;
+ case PM_TYPE_64:
+ xSnprintf(buffer, sizeof(buffer), "%*lld ", width, (long long) atom.ll);
+ RichString_appendAscii(str, attr, buffer);
+ break;
+ case PM_TYPE_U64:
+ xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long) atom.ull);
+ RichString_appendAscii(str, attr, buffer);
+ break;
+ case PM_TYPE_FLOAT:
+ xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, (double) atom.f);
+ RichString_appendAscii(str, attr, buffer);
+ break;
+ case PM_TYPE_DOUBLE:
+ xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, atom.d);
+ RichString_appendAscii(str, attr, buffer);
+ break;
+ default:
+ attr = CRT_colors[METER_VALUE_ERROR];
+ RichString_appendAscii(str, attr, "no type");
+ break;
+ }
+}
+
+int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) {
+ const PCPDynamicColumn* column = Hashtable_get(p1->super.processList->dynamicColumns, key);
+
+ if (!column)
+ return -1;
+
+ size_t metric = column->id;
+ unsigned int type = PCPMetric_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 (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 -1;
+}
diff --git a/pcp/PCPDynamicColumn.h b/pcp/PCPDynamicColumn.h
new file mode 100644
index 0000000..d0ffe71
--- /dev/null
+++ b/pcp/PCPDynamicColumn.h
@@ -0,0 +1,35 @@
+#ifndef HEADER_PCPDynamicColumn
+#define HEADER_PCPDynamicColumn
+
+#include <stddef.h>
+
+#include "DynamicColumn.h"
+#include "Hashtable.h"
+#include "Process.h"
+#include "RichString.h"
+
+#include "pcp/PCPProcess.h"
+
+
+typedef struct PCPDynamicColumn_ {
+ DynamicColumn super;
+ char* metricName;
+ size_t id; /* identifier for metric array lookups */
+} PCPDynamicColumn;
+
+typedef struct PCPDynamicColumns_ {
+ Hashtable* table;
+ size_t count; /* count of dynamic meters discovered by scan */
+ size_t offset; /* start offset into the Platform metric array */
+ size_t cursor; /* identifier allocator for each new metric used */
+} PCPDynamicColumns;
+
+void PCPDynamicColumns_init(PCPDynamicColumns* columns);
+
+void PCPDynamicColumns_done(Hashtable* table);
+
+void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str);
+
+int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key);
+
+#endif
diff --git a/pcp/PCPDynamicMeter.c b/pcp/PCPDynamicMeter.c
new file mode 100644
index 0000000..e899988
--- /dev/null
+++ b/pcp/PCPDynamicMeter.c
@@ -0,0 +1,468 @@
+/*
+htop - PCPDynamicMeter.c
+(C) 2021 htop dev team
+(C) 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/PCPDynamicMeter.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.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"
+
+
+static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) {
+ size_t bytes = 16 + strlen(meter->super.name) + strlen(name);
+ char* metricName = xMalloc(bytes);
+ xSnprintf(metricName, bytes, "htop.meter.%s.%s", meter->super.name, name);
+
+ PCPDynamicMetric* metric;
+ for (size_t i = 0; i < meter->totalMetrics; i++) {
+ metric = &meter->metrics[i];
+ if (String_eq(metric->name, metricName)) {
+ free(metricName);
+ return metric;
+ }
+ }
+
+ /* not an existing metric in this meter - add it */
+ size_t n = meter->totalMetrics + 1;
+ meter->metrics = xReallocArray(meter->metrics, n, sizeof(PCPDynamicMetric));
+ meter->totalMetrics = n;
+ metric = &meter->metrics[n - 1];
+ memset(metric, 0, sizeof(PCPDynamicMetric));
+ metric->name = metricName;
+ metric->label = String_cat(name, ": ");
+ metric->id = meters->offset + meters->cursor;
+ meters->cursor++;
+
+ Platform_addMetric(metric->id, metricName);
+
+ return metric;
+}
+
+static void PCPDynamicMeter_parseMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* path, unsigned int line, char* key, char* value) {
+ PCPDynamicMetric* metric;
+ char* p;
+
+ if ((p = strchr(key, '.')) == NULL)
+ return;
+ *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */
+
+ if (String_eq(p, "metric")) {
+ /* lookup a dynamic metric with this name, else create */
+ metric = PCPDynamicMeter_lookupMetric(meters, meter, key);
+
+ /* use derived metrics in dynamic meters for simplicity */
+ char* error;
+ if (pmRegisterDerivedMetric(metric->name, value, &error) < 0) {
+ char* note;
+ xAsprintf(&note,
+ "%s: failed to parse expression in %s at line %u\n%s\n%s",
+ pmGetProgname(), path, line, error, pmGetProgname());
+ free(error);
+ errno = EINVAL;
+ CRT_fatalError(note);
+ free(note);
+ }
+ } else {
+ /* this is a property of a dynamic metric - the metric expression */
+ /* may not have been observed yet - i.e. we allow for any ordering */
+ metric = PCPDynamicMeter_lookupMetric(meters, meter, key);
+ if (String_eq(p, "color")) {
+ if (String_eq(value, "gray"))
+ metric->color = DYNAMIC_GRAY;
+ else if (String_eq(value, "darkgray"))
+ metric->color = DYNAMIC_DARKGRAY;
+ else if (String_eq(value, "red"))
+ metric->color = DYNAMIC_RED;
+ else if (String_eq(value, "green"))
+ metric->color = DYNAMIC_GREEN;
+ else if (String_eq(value, "blue"))
+ metric->color = DYNAMIC_BLUE;
+ else if (String_eq(value, "cyan"))
+ metric->color = DYNAMIC_CYAN;
+ else if (String_eq(value, "magenta"))
+ metric->color = DYNAMIC_MAGENTA;
+ else if (String_eq(value, "yellow"))
+ metric->color = DYNAMIC_YELLOW;
+ else if (String_eq(value, "white"))
+ metric->color = DYNAMIC_WHITE;
+ } else if (String_eq(p, "label")) {
+ char* label = String_cat(value, ": ");
+ free_and_xStrdup(&metric->label, label);
+ free(label);
+ } else if (String_eq(p, "suffix")) {
+ free_and_xStrdup(&metric->suffix, value);
+ }
+ }
+}
+
+// Ensure a valid name for use in a PCP metric name and in htoprc
+static bool PCPDynamicMeter_validateMeterName(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 meter name at %s line %u\n\"%s\"\n",
+ 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 meter name at %s line %u\n\"%s\"\n",
+ pmGetProgname(), path, line, key);
+ return false;
+ }
+ return true;
+}
+
+// Ensure a meter name has not been defined previously
+static bool PCPDynamicMeter_uniqueName(char* key, PCPDynamicMeters* meters) {
+ return !DynamicMeter_search(meters->table, key, NULL);
+}
+
+static PCPDynamicMeter* PCPDynamicMeter_new(PCPDynamicMeters* meters, const char* name) {
+ PCPDynamicMeter* meter = xCalloc(1, sizeof(*meter));
+ String_safeStrncpy(meter->super.name, name, sizeof(meter->super.name));
+ Hashtable_put(meters->table, ++meters->count, meter);
+ return meter;
+}
+
+static void PCPDynamicMeter_parseFile(PCPDynamicMeters* meters, const char* path) {
+ FILE* file = fopen(path, "r");
+ if (!file)
+ return;
+
+ PCPDynamicMeter* meter = 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[0] == '#' || trimmed[0] == '\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 heading - i.e. new meter */
+ ok = PCPDynamicMeter_validateMeterName(key + 1, path, lineno);
+ if (ok)
+ ok = PCPDynamicMeter_uniqueName(key + 1, meters);
+ if (ok)
+ meter = PCPDynamicMeter_new(meters, key + 1);
+ } else if (!ok) {
+ ; /* skip this one, we're looking for a new header */
+ } else if (value && meter && String_eq(key, "caption")) {
+ char* caption = String_cat(value, ": ");
+ if (caption) {
+ free_and_xStrdup(&meter->super.caption, caption);
+ free(caption);
+ caption = NULL;
+ }
+ } else if (value && meter && String_eq(key, "description")) {
+ free_and_xStrdup(&meter->super.description, value);
+ } else if (value && meter && String_eq(key, "type")) {
+ if (String_eq(config[1], "bar"))
+ meter->super.type = BAR_METERMODE;
+ else if (String_eq(config[1], "text"))
+ meter->super.type = TEXT_METERMODE;
+ else if (String_eq(config[1], "graph"))
+ meter->super.type = GRAPH_METERMODE;
+ else if (String_eq(config[1], "led"))
+ meter->super.type = LED_METERMODE;
+ } else if (value && meter && String_eq(key, "maximum")) {
+ meter->super.maximum = strtod(value, NULL);
+ } else if (value && meter) {
+ PCPDynamicMeter_parseMetric(meters, meter, path, lineno, key, value);
+ }
+ String_freeArray(config);
+ free(value);
+ free(key);
+ }
+ fclose(file);
+}
+
+static void PCPDynamicMeter_scanDir(PCPDynamicMeters* meters, 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);
+ PCPDynamicMeter_parseFile(meters, file);
+ free(file);
+ }
+ closedir(dir);
+}
+
+void PCPDynamicMeters_init(PCPDynamicMeters* meters) {
+ 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;
+
+ 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 */
+ if (override) {
+ path = String_cat(override, "/meters/");
+ PCPDynamicMeter_scanDir(meters, path);
+ free(path);
+ }
+
+ /* next, search in home directory alongside htoprc */
+ if (xdgConfigHome)
+ path = String_cat(xdgConfigHome, "/htop/meters/");
+ else if (home)
+ path = String_cat(home, "/.config/htop/meters/");
+ else
+ path = NULL;
+ if (path) {
+ PCPDynamicMeter_scanDir(meters, path);
+ free(path);
+ }
+
+ /* next, search in the system meters directory */
+ path = String_cat(sysconf, "/htop/meters/");
+ PCPDynamicMeter_scanDir(meters, path);
+ free(path);
+
+ /* next, try the readonly system meters directory */
+ path = String_cat(share, "/htop/meters/");
+ PCPDynamicMeter_scanDir(meters, path);
+ free(path);
+}
+
+static void PCPDynamicMeter_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {
+ PCPDynamicMeter* meter = (PCPDynamicMeter*) value;
+ for (size_t i = 0; i < meter->totalMetrics; i++) {
+ free(meter->metrics[i].name);
+ free(meter->metrics[i].label);
+ free(meter->metrics[i].suffix);
+ }
+ free(meter->metrics);
+ free(meter->super.caption);
+ free(meter->super.description);
+}
+
+void PCPDynamicMeters_done(Hashtable* table) {
+ Hashtable_foreach(table, PCPDynamicMeter_free, NULL);
+}
+
+void PCPDynamicMeter_enable(PCPDynamicMeter* this) {
+ for (size_t i = 0; i < this->totalMetrics; i++)
+ PCPMetric_enable(this->metrics[i].id, true);
+}
+
+void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) {
+ char* buffer = meter->txtBuffer;
+ size_t size = sizeof(meter->txtBuffer);
+ size_t bytes = 0;
+
+ for (size_t i = 0; i < this->totalMetrics; i++) {
+ if (i > 0 && bytes < size - 1)
+ buffer[bytes++] = '/'; /* separator */
+
+ PCPDynamicMetric* metric = &this->metrics[i];
+ const pmDesc* desc = PCPMetric_desc(metric->id);
+ pmAtomValue atom, raw;
+
+ if (!PCPMetric_values(metric->id, &raw, 1, desc->type)) {
+ bytes--; /* clear the separator */
+ continue;
+ }
+
+ pmUnits conv = desc->units; /* convert to canonical units */
+ if (conv.dimSpace)
+ conv.scaleSpace = PM_SPACE_KBYTE;
+ if (conv.dimTime)
+ conv.scaleTime = PM_TIME_SEC;
+ if (desc->type == PM_TYPE_STRING)
+ atom = raw;
+ else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0) {
+ bytes--; /* clear the separator */
+ continue;
+ }
+
+ size_t saved = bytes;
+ switch (desc->type) {
+ case PM_TYPE_STRING:
+ bytes += xSnprintf(buffer + bytes, size - bytes, "%s", atom.cp);
+ free(atom.cp);
+ break;
+ case PM_TYPE_32:
+ bytes += conv.dimSpace ?
+ Meter_humanUnit(buffer + bytes, 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) :
+ xSnprintf(buffer + bytes, size - bytes, "%u", atom.ul);
+ break;
+ case PM_TYPE_64:
+ bytes += conv.dimSpace ?
+ Meter_humanUnit(buffer + bytes, 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) :
+ 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) :
+ xSnprintf(buffer + bytes, size - bytes, "%.2f", (double) atom.f);
+ break;
+ case PM_TYPE_DOUBLE:
+ bytes += conv.dimSpace ?
+ Meter_humanUnit(buffer + bytes, atom.d, size - bytes) :
+ xSnprintf(buffer + bytes, size - bytes, "%.2f", atom.d);
+ break;
+ default:
+ break;
+ }
+ if (saved != bytes && metric->suffix)
+ bytes += xSnprintf(buffer + bytes, size - bytes, "%s", metric->suffix);
+ }
+ if (!bytes)
+ xSnprintf(buffer, size, "no data");
+}
+
+void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* meter, RichString* out) {
+ int nodata = 1;
+
+ for (size_t i = 0; i < this->totalMetrics; i++) {
+ PCPDynamicMetric* metric = &this->metrics[i];
+ const pmDesc* desc = PCPMetric_desc(metric->id);
+ pmAtomValue atom, raw;
+ char buffer[64];
+
+ if (!PCPMetric_values(metric->id, &raw, 1, desc->type))
+ continue;
+
+ pmUnits conv = desc->units; /* convert to canonical units */
+ if (conv.dimSpace)
+ conv.scaleSpace = PM_SPACE_KBYTE;
+ if (conv.dimTime)
+ conv.scaleTime = PM_TIME_SEC;
+ if (desc->type == PM_TYPE_STRING)
+ atom = raw;
+ else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0)
+ continue;
+
+ nodata = 0; /* we will use this metric so *some* data will be added */
+
+ if (i > 0)
+ RichString_appendnAscii(out, CRT_colors[metric->color], " ", 1);
+
+ if (metric->label)
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->label);
+
+ int len = 0;
+ switch (desc->type) {
+ case PM_TYPE_STRING:
+ len = xSnprintf(buffer, sizeof(buffer), "%s", atom.cp);
+ free(atom.cp);
+ break;
+ case PM_TYPE_32:
+ len = conv.dimSpace ?
+ Meter_humanUnit(buffer, 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)) :
+ xSnprintf(buffer, sizeof(buffer), "%u", atom.ul);
+ break;
+ case PM_TYPE_64:
+ len = conv.dimSpace ?
+ Meter_humanUnit(buffer, 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)) :
+ 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)) :
+ xSnprintf(buffer, sizeof(buffer), "%.2f", (double) atom.f);
+ break;
+ case PM_TYPE_DOUBLE:
+ len = conv.dimSpace ?
+ Meter_humanUnit(buffer, atom.d, sizeof(buffer)) :
+ xSnprintf(buffer, sizeof(buffer), "%.2f", atom.d);
+ break;
+ 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
new file mode 100644
index 0000000..0e5ddd2
--- /dev/null
+++ b/pcp/PCPDynamicMeter.h
@@ -0,0 +1,44 @@
+#ifndef HEADER_PCPDynamicMeter
+#define HEADER_PCPDynamicMeter
+
+#include <stddef.h>
+
+#include "CRT.h"
+#include "DynamicMeter.h"
+#include "Hashtable.h"
+#include "Meter.h"
+#include "RichString.h"
+
+
+typedef struct PCPDynamicMetric_ {
+ size_t id; /* index into metric array */
+ ColorElements color;
+ char* name; /* derived metric name */
+ char* label;
+ char* suffix;
+} PCPDynamicMetric;
+
+typedef struct PCPDynamicMeter_ {
+ DynamicMeter super;
+ PCPDynamicMetric* metrics;
+ size_t totalMetrics;
+} PCPDynamicMeter;
+
+typedef struct PCPDynamicMeters_ {
+ Hashtable* table;
+ size_t count; /* count of dynamic meters discovered by scan */
+ size_t offset; /* start offset into the Platform metric array */
+ size_t cursor; /* identifier allocator for each new metric used */
+} PCPDynamicMeters;
+
+void PCPDynamicMeters_init(PCPDynamicMeters* meters);
+
+void PCPDynamicMeters_done(Hashtable* table);
+
+void PCPDynamicMeter_enable(PCPDynamicMeter* this);
+
+void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter);
+
+void PCPDynamicMeter_display(PCPDynamicMeter* this, const Meter* meter, RichString* out);
+
+#endif
diff --git a/pcp/PCPMetric.c b/pcp/PCPMetric.c
new file mode 100644
index 0000000..606a5df
--- /dev/null
+++ b/pcp/PCPMetric.c
@@ -0,0 +1,180 @@
+/*
+htop - PCPMetric.c
+(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/PCPMetric.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "XUtils.h"
+
+#include "pcp/Platform.h"
+
+
+extern Platform* pcp;
+
+const pmDesc* PCPMetric_desc(PCPMetric metric) {
+ return &pcp->descs[metric];
+}
+
+int PCPMetric_type(PCPMetric metric) {
+ return pcp->descs[metric].type;
+}
+
+pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type) {
+ if (pcp->result == NULL)
+ return NULL;
+
+ pmValueSet* vset = pcp->result->vset[metric];
+ if (!vset || vset->numval <= 0)
+ return NULL;
+
+ /* extract requested number of values as requested type */
+ const pmDesc* desc = &pcp->descs[metric];
+ for (int i = 0; i < vset->numval; i++) {
+ if (i == count)
+ break;
+ const pmValue* value = &vset->vlist[i];
+ int sts = pmExtractValue(vset->valfmt, value, desc->type, &atom[i], type);
+ if (sts < 0) {
+ if (pmDebugOptions.appl0)
+ fprintf(stderr, "Error: cannot extract metric value: %s\n",
+ pmErrStr(sts));
+ memset(&atom[i], 0, sizeof(pmAtomValue));
+ }
+ }
+ return atom;
+}
+
+int PCPMetric_instanceCount(PCPMetric metric) {
+ pmValueSet* vset = pcp->result->vset[metric];
+ if (vset)
+ return vset->numval;
+ return 0;
+}
+
+int PCPMetric_instanceOffset(PCPMetric metric, int inst) {
+ pmValueSet* vset = pcp->result->vset[metric];
+ if (!vset || vset->numval <= 0)
+ return 0;
+
+ /* search for optimal offset for subsequent inst lookups to begin */
+ for (int i = 0; i < vset->numval; i++) {
+ if (inst == vset->vlist[i].inst)
+ return i;
+ }
+ return 0;
+}
+
+static pmAtomValue* PCPMetric_extract(PCPMetric 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];
+ const pmValue* value = &vset->vlist[offset];
+ int sts = pmExtractValue(vset->valfmt, value, desc->type, atom, type);
+ if (sts < 0) {
+ if (pmDebugOptions.appl0)
+ fprintf(stderr, "Error: cannot extract %s instance %d value: %s\n",
+ pcp->names[metric], inst, pmErrStr(sts));
+ memset(atom, 0, sizeof(pmAtomValue));
+ }
+ return atom;
+}
+
+pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type) {
+
+ pmValueSet* vset = pcp->result->vset[metric];
+ if (!vset || vset->numval <= 0)
+ return NULL;
+
+ /* 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);
+
+ /* 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 NULL;
+}
+
+/*
+ * Iterate over a set of instances (incl PM_IN_NULL)
+ * returning the next instance identifier and offset.
+ *
+ * Start it off by passing offset -1 into the routine.
+ */
+bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp) {
+ if (!pcp->result)
+ return false;
+
+ pmValueSet* vset = pcp->result->vset[metric];
+ if (!vset || vset->numval <= 0)
+ return false;
+
+ int offset = *offsetp;
+ offset = (offset < 0) ? 0 : offset + 1;
+ if (offset > vset->numval - 1)
+ return false;
+
+ *offsetp = offset;
+ *instp = vset->vlist[offset].inst;
+ return true;
+}
+
+/* Switch on/off a metric for value fetching (sampling) */
+void PCPMetric_enable(PCPMetric metric, bool enable) {
+ pcp->fetch[metric] = enable ? pcp->pmids[metric] : PM_ID_NULL;
+}
+
+bool PCPMetric_enabled(PCPMetric metric) {
+ return pcp->fetch[metric] != PM_ID_NULL;
+}
+
+void PCPMetric_enableThreads(void) {
+ pmValueSet* vset = xCalloc(1, sizeof(pmValueSet));
+ vset->vlist[0].inst = PM_IN_NULL;
+ vset->vlist[0].value.lval = 1;
+ vset->valfmt = PM_VAL_INSITU;
+ vset->numval = 1;
+ vset->pmid = pcp->pmids[PCP_CONTROL_THREADS];
+
+ pmResult* result = xCalloc(1, sizeof(pmResult));
+ result->vset[0] = vset;
+ result->numpmid = 1;
+
+ int sts = pmStore(result);
+ if (sts < 0 && pmDebugOptions.appl0)
+ fprintf(stderr, "Error: cannot enable threads: %s\n", pmErrStr(sts));
+
+ pmFreeResult(result);
+}
+
+bool PCPMetric_fetch(struct timeval* timestamp) {
+ if (pcp->result) {
+ pmFreeResult(pcp->result);
+ pcp->result = NULL;
+ }
+ 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",
+ pmErrStr(sts));
+ return false;
+ }
+ if (timestamp)
+ *timestamp = pcp->result->timestamp;
+ return true;
+}
diff --git a/pcp/PCPMetric.h b/pcp/PCPMetric.h
new file mode 100644
index 0000000..84ccbb9
--- /dev/null
+++ b/pcp/PCPMetric.h
@@ -0,0 +1,180 @@
+#ifndef HEADER_PCPMetric
+#define HEADER_PCPMetric
+/*
+htop - PCPMetric.h
+(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 <ctype.h>
+#include <stdbool.h>
+#include <pcp/pmapi.h>
+#include <sys/time.h>
+
+/* use htop config.h values for these macros, not pcp values */
+#undef PACKAGE_URL
+#undef PACKAGE_NAME
+#undef PACKAGE_STRING
+#undef PACKAGE_TARNAME
+#undef PACKAGE_VERSION
+#undef PACKAGE_BUGREPORT
+
+
+typedef enum PCPMetric_ {
+ PCP_CONTROL_THREADS, /* proc.control.perclient.threads */
+
+ PCP_HINV_NCPU, /* hinv.ncpu */
+ PCP_HINV_CPUCLOCK, /* hinv.cpu.clock */
+ PCP_UNAME_SYSNAME, /* kernel.uname.sysname */
+ PCP_UNAME_RELEASE, /* kernel.uname.release */
+ PCP_UNAME_MACHINE, /* kernel.uname.machine */
+ PCP_UNAME_DISTRO, /* kernel.uname.distro */
+ PCP_LOAD_AVERAGE, /* kernel.all.load */
+ PCP_PID_MAX, /* kernel.all.pid_max */
+ PCP_UPTIME, /* kernel.all.uptime */
+ PCP_BOOTTIME, /* kernel.all.boottime */
+ PCP_CPU_USER, /* kernel.all.cpu.user */
+ PCP_CPU_NICE, /* kernel.all.cpu.nice */
+ PCP_CPU_SYSTEM, /* kernel.all.cpu.sys */
+ PCP_CPU_IDLE, /* kernel.all.cpu.idle */
+ PCP_CPU_IOWAIT, /* kernel.all.cpu.wait.total */
+ PCP_CPU_IRQ, /* kernel.all.cpu.intr */
+ PCP_CPU_SOFTIRQ, /* kernel.all.cpu.irq.soft */
+ PCP_CPU_STEAL, /* kernel.all.cpu.steal */
+ PCP_CPU_GUEST, /* kernel.all.cpu.guest */
+ PCP_CPU_GUESTNICE, /* kernel.all.cpu.guest_nice */
+ PCP_PERCPU_USER, /* kernel.percpu.cpu.user */
+ PCP_PERCPU_NICE, /* kernel.percpu.cpu.nice */
+ PCP_PERCPU_SYSTEM, /* kernel.percpu.cpu.sys */
+ PCP_PERCPU_IDLE, /* kernel.percpu.cpu.idle */
+ PCP_PERCPU_IOWAIT, /* kernel.percpu.cpu.wait.total */
+ PCP_PERCPU_IRQ, /* kernel.percpu.cpu.intr */
+ PCP_PERCPU_SOFTIRQ, /* kernel.percpu.cpu.irq.soft */
+ PCP_PERCPU_STEAL, /* kernel.percpu.cpu.steal */
+ PCP_PERCPU_GUEST, /* kernel.percpu.cpu.guest */
+ PCP_PERCPU_GUESTNICE, /* kernel.percpu.cpu.guest_nice */
+ PCP_MEM_TOTAL, /* mem.physmem */
+ PCP_MEM_FREE, /* mem.util.free */
+ PCP_MEM_BUFFERS, /* mem.util.bufmem */
+ PCP_MEM_CACHED, /* mem.util.cached */
+ PCP_MEM_SHARED, /* mem.util.shared */
+ PCP_MEM_AVAILABLE, /* mem.util.available */
+ PCP_MEM_SRECLAIM, /* mem.util.slabReclaimable */
+ PCP_MEM_SWAPCACHED, /* mem.util.swapCached */
+ PCP_MEM_SWAPTOTAL, /* mem.util.swapTotal */
+ PCP_MEM_SWAPFREE, /* mem.util.swapFree */
+ PCP_DISK_READB, /* disk.all.read_bytes */
+ PCP_DISK_WRITEB, /* disk.all.write_bytes */
+ PCP_DISK_ACTIVE, /* disk.all.avactive */
+ PCP_NET_RECVB, /* network.all.in.bytes */
+ PCP_NET_SENDB, /* network.all.out.bytes */
+ PCP_NET_RECVP, /* network.all.in.packets */
+ PCP_NET_SENDP, /* network.all.out.packets */
+ 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_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 */
+ PCP_ZFS_ARC_HDR_SIZE, /* zfs.arc.hdr_size */
+ PCP_ZFS_ARC_MFU_SIZE, /* zfs.arc.mfu_size */
+ PCP_ZFS_ARC_MRU_SIZE, /* zfs.arc.mru_size */
+ PCP_ZFS_ARC_SIZE, /* zfs.arc.size */
+ PCP_ZRAM_CAPACITY, /* zram.capacity */
+ PCP_ZRAM_ORIGINAL, /* zram.mm_stat.data_size.original */
+ PCP_ZRAM_COMPRESSED, /* zram.mm_stat.data_size.compressed */
+
+ PCP_PROC_PID, /* proc.psinfo.pid */
+ PCP_PROC_PPID, /* proc.psinfo.ppid */
+ PCP_PROC_TGID, /* proc.psinfo.tgid */
+ PCP_PROC_PGRP, /* proc.psinfo.pgrp */
+ PCP_PROC_SESSION, /* proc.psinfo.session */
+ PCP_PROC_STATE, /* proc.psinfo.sname */
+ PCP_PROC_TTY, /* proc.psinfo.tty */
+ PCP_PROC_TTYPGRP, /* proc.psinfo.tty_pgrp */
+ PCP_PROC_MINFLT, /* proc.psinfo.minflt */
+ PCP_PROC_MAJFLT, /* proc.psinfo.maj_flt */
+ PCP_PROC_CMINFLT, /* proc.psinfo.cmin_flt */
+ PCP_PROC_CMAJFLT, /* proc.psinfo.cmaj_flt */
+ PCP_PROC_UTIME, /* proc.psinfo.utime */
+ PCP_PROC_STIME, /* proc.psinfo.stime */
+ PCP_PROC_CUTIME, /* proc.psinfo.cutime */
+ PCP_PROC_CSTIME, /* proc.psinfo.cstime */
+ PCP_PROC_PRIORITY, /* proc.psinfo.priority */
+ PCP_PROC_NICE, /* proc.psinfo.nice */
+ PCP_PROC_THREADS, /* proc.psinfo.threads */
+ PCP_PROC_STARTTIME, /* proc.psinfo.start_time */
+ PCP_PROC_PROCESSOR, /* proc.psinfo.processor */
+ PCP_PROC_CMD, /* proc.psinfo.cmd */
+ PCP_PROC_PSARGS, /* proc.psinfo.psargs */
+ PCP_PROC_CGROUPS, /* proc.psinfo.cgroups */
+ PCP_PROC_OOMSCORE, /* proc.psinfo.oom_score */
+ PCP_PROC_VCTXSW, /* proc.psinfo.vctxsw */
+ PCP_PROC_NVCTXSW, /* proc.psinfo.nvctxsw */
+ PCP_PROC_LABELS, /* proc.psinfo.labels */
+ PCP_PROC_ENVIRON, /* proc.psinfo.environ */
+ PCP_PROC_TTYNAME, /* proc.psinfo.ttyname */
+ PCP_PROC_EXE, /* proc.psinfo.exe */
+ PCP_PROC_CWD, /* proc.psinfo.cwd */
+
+ PCP_PROC_AUTOGROUP_ID, /* proc.autogroup.id */
+ PCP_PROC_AUTOGROUP_NICE, /* proc.autogroup.nice */
+
+ PCP_PROC_ID_UID, /* proc.id.uid */
+ PCP_PROC_ID_USER, /* proc.id.uid_nm */
+
+ PCP_PROC_IO_RCHAR, /* proc.io.rchar */
+ PCP_PROC_IO_WCHAR, /* proc.io.wchar */
+ PCP_PROC_IO_SYSCR, /* proc.io.syscr */
+ PCP_PROC_IO_SYSCW, /* proc.io.syscw */
+ PCP_PROC_IO_READB, /* proc.io.read_bytes */
+ PCP_PROC_IO_WRITEB, /* proc.io.write_bytes */
+ PCP_PROC_IO_CANCELLED, /* proc.io.cancelled_write_bytes */
+
+ PCP_PROC_MEM_SIZE, /* proc.memory.size */
+ PCP_PROC_MEM_RSS, /* proc.memory.rss */
+ PCP_PROC_MEM_SHARE, /* proc.memory.share */
+ PCP_PROC_MEM_TEXTRS, /* proc.memory.textrss */
+ PCP_PROC_MEM_LIBRS, /* proc.memory.librss */
+ PCP_PROC_MEM_DATRS, /* proc.memory.datrss */
+ PCP_PROC_MEM_DIRTY, /* proc.memory.dirty */
+
+ PCP_PROC_SMAPS_PSS, /* proc.smaps.pss */
+ PCP_PROC_SMAPS_SWAP, /* proc.smaps.swap */
+ PCP_PROC_SMAPS_SWAPPSS, /* proc.smaps.swappss */
+
+ PCP_METRIC_COUNT /* total metric count */
+} PCPMetric;
+
+void PCPMetric_enable(PCPMetric metric, bool enable);
+
+bool PCPMetric_enabled(PCPMetric metric);
+
+void PCPMetric_enableThreads(void);
+
+bool PCPMetric_fetch(struct timeval* timestamp);
+
+bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp);
+
+pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type);
+
+const pmDesc* PCPMetric_desc(PCPMetric metric);
+
+int PCPMetric_type(PCPMetric metric);
+
+int PCPMetric_instanceCount(PCPMetric metric);
+
+int PCPMetric_instanceOffset(PCPMetric metric, int inst);
+
+pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type);
+
+#endif
diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c
new file mode 100644
index 0000000..b8b87ca
--- /dev/null
+++ b/pcp/PCPProcess.c
@@ -0,0 +1,291 @@
+/*
+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
+in the source distribution for its full text.
+*/
+
+#include "pcp/PCPProcess.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Process.h"
+#include "ProvideCurses.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+#include "pcp/PCPDynamicColumn.h"
+
+
+const ProcessFieldData Process_fields[] = {
+ [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, },
+ [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, },
+ [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
+ [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging, I idle)", .flags = 0, },
+ [PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, },
+ [PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, },
+ [SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .flags = 0, },
+ [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, },
+ [TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, },
+ [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, },
+ [CMINFLT] = { .name = "CMINFLT", .title = " CMINFLT ", .description = "Children processes' minor faults", .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, },
+ [CMAJFLT] = { .name = "CMAJFLT", .title = " CMAJFLT ", .description = "Children processes' major faults", .flags = 0, .defaultSortDesc = true, },
+ [UTIME] = { .name = "UTIME", .title = " UTIME+ ", .description = "User CPU time - time the process spent executing in user mode", .flags = 0, .defaultSortDesc = true, },
+ [STIME] = { .name = "STIME", .title = " STIME+ ", .description = "System CPU time - time the kernel spent running system calls for this process", .flags = 0, .defaultSortDesc = true, },
+ [CUTIME] = { .name = "CUTIME", .title = " CUTIME+ ", .description = "Children processes' user CPU time", .flags = 0, .defaultSortDesc = true, },
+ [CSTIME] = { .name = "CSTIME", .title = " CSTIME+ ", .description = "Children processes' system CPU time", .flags = 0, .defaultSortDesc = true, },
+ [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
+ [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
+ [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
+ [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "If 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, },
+ [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_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, },
+ [M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, },
+ [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
+ [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, },
+ [RCHAR] = { .name = "RCHAR", .title = "RCHAR ", .description = "Number of bytes the process has read", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [WCHAR] = { .name = "WCHAR", .title = "WCHAR ", .description = "Number of bytes the process has written", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [SYSCR] = { .name = "SYSCR", .title = " READ_SYSC ", .description = "Number of read(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [SYSCW] = { .name = "SYSCW", .title = " WRITE_SYSC ", .description = "Number of write(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [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_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, },
+ [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_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, },
+ [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, },
+ [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, },
+ [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, },
+};
+
+Process* PCPProcess_new(const Settings* settings) {
+ PCPProcess* this = xCalloc(1, sizeof(PCPProcess));
+ Object_setClass(this, Class(PCPProcess));
+ Process_init(&this->super, settings);
+ return &this->super;
+}
+
+void Process_delete(Object* cast) {
+ PCPProcess* this = (PCPProcess*) cast;
+ Process_done((Process*)cast);
+ 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 {
+ xSnprintf(buffer, n, "%4.1f ", delay_percent);
+ }
+}
+
+static void PCPProcess_writeField(const Process* this, RichString* str, ProcessField field) {
+ const PCPProcess* pp = (const PCPProcess*) this;
+ bool coloring = this->settings->highlightMegabytes;
+ char buffer[256]; buffer[255] = '\0';
+ int attr = CRT_colors[DEFAULT_COLOR];
+ int 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 OOM: xSnprintf(buffer, n, "%4u ", pp->oom); break;
+ case PERCENT_CPU_DELAY:
+ PCPProcess_printDelay(pp->cpu_delay_percent, buffer, n);
+ break;
+ case PERCENT_IO_DELAY:
+ PCPProcess_printDelay(pp->blkio_delay_percent, buffer, n);
+ break;
+ case PERCENT_SWAP_DELAY:
+ PCPProcess_printDelay(pp->swapin_delay_percent, buffer, n);
+ break;
+ case CTXT:
+ if (pp->ctxt_diff > 1000) {
+ attr |= A_BOLD;
+ }
+ xSnprintf(buffer, n, "%5lu ", pp->ctxt_diff);
+ break;
+ case SECATTR: snprintf(buffer, n, "%-30s ", pp->secattr ? pp->secattr : "?"); break;
+ case AUTOGROUP_ID:
+ if (pp->autogroup_id != -1) {
+ xSnprintf(buffer, n, "%4ld ", pp->autogroup_id);
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, " N/A ");
+ }
+ break;
+ case AUTOGROUP_NICE:
+ if (pp->autogroup_id != -1) {
+ xSnprintf(buffer, n, "%3d ", pp->autogroup_nice);
+ attr = pp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]
+ : pp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
+ : CRT_colors[PROCESS_SHADOW];
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, "N/A ");
+ }
+ break;
+ default:
+ Process_writeField(this, str, field);
+ return;
+ }
+ RichString_appendWide(str, attr, buffer);
+}
+
+static double adjustNaN(double num) {
+ if (isnan(num))
+ return -0.0005;
+
+ return num;
+}
+
+static int PCPProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const PCPProcess* p1 = (const PCPProcess*)v1;
+ const PCPProcess* p2 = (const PCPProcess*)v2;
+
+ switch (key) {
+ case M_DRS:
+ return SPACESHIP_NUMBER(p1->m_drs, p2->m_drs);
+ case M_DT:
+ return SPACESHIP_NUMBER(p1->m_dt, p2->m_dt);
+ case M_LRS:
+ return SPACESHIP_NUMBER(p1->m_lrs, p2->m_lrs);
+ case M_TRS:
+ return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs);
+ case M_SHARE:
+ return SPACESHIP_NUMBER(p1->m_share, p2->m_share);
+ case M_PSS:
+ return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss);
+ case M_SWAP:
+ return SPACESHIP_NUMBER(p1->m_swap, p2->m_swap);
+ case M_PSSWP:
+ return SPACESHIP_NUMBER(p1->m_psswp, p2->m_psswp);
+ case UTIME:
+ return SPACESHIP_NUMBER(p1->utime, p2->utime);
+ case CUTIME:
+ return SPACESHIP_NUMBER(p1->cutime, p2->cutime);
+ case STIME:
+ return SPACESHIP_NUMBER(p1->stime, p2->stime);
+ case CSTIME:
+ return SPACESHIP_NUMBER(p1->cstime, p2->cstime);
+ case RCHAR:
+ return SPACESHIP_NUMBER(p1->io_rchar, p2->io_rchar);
+ case WCHAR:
+ return SPACESHIP_NUMBER(p1->io_wchar, p2->io_wchar);
+ case SYSCR:
+ return SPACESHIP_NUMBER(p1->io_syscr, p2->io_syscr);
+ case SYSCW:
+ return SPACESHIP_NUMBER(p1->io_syscw, p2->io_syscw);
+ case RBYTES:
+ return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes);
+ case WBYTES:
+ return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes);
+ 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));
+ case IO_WRITE_RATE:
+ return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(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));
+ case CGROUP:
+ return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);
+ case OOM:
+ return SPACESHIP_NUMBER(p1->oom, p2->oom);
+ case PERCENT_CPU_DELAY:
+ return SPACESHIP_NUMBER(p1->cpu_delay_percent, p2->cpu_delay_percent);
+ case PERCENT_IO_DELAY:
+ return SPACESHIP_NUMBER(p1->blkio_delay_percent, p2->blkio_delay_percent);
+ case PERCENT_SWAP_DELAY:
+ return SPACESHIP_NUMBER(p1->swapin_delay_percent, p2->swapin_delay_percent);
+ case CTXT:
+ return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff);
+ case SECATTR:
+ return SPACESHIP_NULLSTR(p1->secattr, p2->secattr);
+ case AUTOGROUP_ID:
+ return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id);
+ case AUTOGROUP_NICE:
+ return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice);
+ default:
+ if (key < LAST_PROCESSFIELD)
+ return Process_compareByKey_Base(v1, v2, key);
+ return PCPDynamicColumn_compareByKey(p1, p2, key);
+ }
+}
+
+const ProcessClass PCPProcess_class = {
+ .super = {
+ .extends = Class(Process),
+ .display = Process_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .writeField = PCPProcess_writeField,
+ .compareByKey = PCPProcess_compareByKey
+};
diff --git a/pcp/PCPProcess.h b/pcp/PCPProcess.h
new file mode 100644
index 0000000..46ba07f
--- /dev/null
+++ b/pcp/PCPProcess.h
@@ -0,0 +1,102 @@
+#ifndef HEADER_PCPProcess
+#define HEADER_PCPProcess
+/*
+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
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include <stdbool.h>
+
+#include "Object.h"
+#include "Process.h"
+#include "Settings.h"
+
+
+#define PROCESS_FLAG_LINUX_CGROUP 0x00000800
+#define PROCESS_FLAG_LINUX_OOM 0x00001000
+#define PROCESS_FLAG_LINUX_SMAPS 0x00002000
+#define PROCESS_FLAG_LINUX_CTXT 0x00004000
+#define PROCESS_FLAG_LINUX_SECATTR 0x00008000
+#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000
+
+typedef struct PCPProcess_ {
+ Process super;
+
+ /* default result offset to use for searching proc metrics */
+ unsigned int offset;
+
+ unsigned long int cminflt;
+ unsigned long int cmajflt;
+ unsigned long long int utime;
+ unsigned long long int stime;
+ unsigned long long int cutime;
+ unsigned long long int cstime;
+ long m_share;
+ long m_pss;
+ long m_swap;
+ long m_psswp;
+ long m_trs;
+ long m_drs;
+ long m_lrs;
+ long m_dt;
+
+ /* Data read (in kilobytes) */
+ unsigned long long io_rchar;
+
+ /* Data written (in kilobytes) */
+ unsigned long long io_wchar;
+
+ /* Number of read(2) syscalls */
+ unsigned long long io_syscr;
+
+ /* Number of write(2) syscalls */
+ unsigned long long io_syscw;
+
+ /* Storage data read (in kilobytes) */
+ unsigned long long io_read_bytes;
+
+ /* Storage data written (in kilobytes) */
+ unsigned long long io_write_bytes;
+
+ /* Storage data cancelled (in kilobytes) */
+ unsigned long long io_cancelled_write_bytes;
+
+ /* Point in time of last io scan (in seconds elapsed since the Epoch) */
+ unsigned long long io_last_scan_time;
+
+ double io_rate_read_bps;
+ double io_rate_write_bps;
+ char* cgroup;
+ long int autogroup_id;
+ int autogroup_nice;
+ unsigned int oom;
+ unsigned long long int delay_read_time;
+ unsigned long long cpu_delay_total;
+ unsigned long long blkio_delay_total;
+ unsigned long long swapin_delay_total;
+ float cpu_delay_percent;
+ float blkio_delay_percent;
+ float swapin_delay_percent;
+ unsigned long ctxt_total;
+ unsigned long ctxt_diff;
+ char* secattr;
+ unsigned long long int last_mlrs_calctime;
+} PCPProcess;
+
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
+
+extern const ProcessClass PCPProcess_class;
+
+Process* PCPProcess_new(const Settings* settings);
+
+void Process_delete(Object* cast);
+
+bool Process_isThread(const Process* this);
+
+#endif
diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c
new file mode 100644
index 0000000..f893689
--- /dev/null
+++ b/pcp/PCPProcessList.c
@@ -0,0 +1,727 @@
+/*
+htop - PCPProcessList.c
+(C) 2014 Hisham H. Muhammad
+(C) 2020-2021 htop dev team
+(C) 2020-2021 Red Hat, Inc.
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "pcp/PCPProcessList.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "Macros.h"
+#include "Object.h"
+#include "Platform.h"
+#include "Process.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+#include "pcp/PCPMetric.h"
+#include "pcp/PCPProcess.h"
+
+
+static void PCPProcessList_updateCPUcount(PCPProcessList* this) {
+ ProcessList* pl = &(this->super);
+ pl->activeCPUs = PCPMetric_instanceCount(PCP_PERCPU_SYSTEM);
+ unsigned int cpus = Platform_getMaxCPU();
+ if (cpus == pl->existingCPUs)
+ return;
+ if (cpus == 0)
+ cpus = pl->activeCPUs;
+ if (cpus <= 1)
+ cpus = pl->activeCPUs = 1;
+ pl->existingCPUs = cpus;
+
+ free(this->percpu);
+ free(this->values);
+
+ this->percpu = xCalloc(cpus, sizeof(pmAtomValue *));
+ for (unsigned int i = 0; i < cpus; i++)
+ this->percpu[i] = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue));
+ this->values = xCalloc(cpus, sizeof(pmAtomValue));
+}
+
+static char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) {
+ char* name = Hashtable_get(this->users, uid);
+ if (name)
+ return name;
+
+ pmAtomValue value;
+ if (PCPMetric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) {
+ Hashtable_put(this->users, uid, value.cp);
+ name = value.cp;
+ }
+ return name;
+}
+
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
+ PCPProcessList* this = xCalloc(1, sizeof(PCPProcessList));
+ ProcessList* super = &(this->super);
+
+ ProcessList_init(super, Class(PCPProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
+
+ struct timeval timestamp;
+ gettimeofday(&timestamp, NULL);
+ this->timestamp = pmtimevalToReal(&timestamp);
+
+ this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue));
+ PCPProcessList_updateCPUcount(this);
+
+ return super;
+}
+
+void ProcessList_delete(ProcessList* pl) {
+ PCPProcessList* this = (PCPProcessList*) pl;
+ ProcessList_done(pl);
+ free(this->values);
+ for (unsigned int i = 0; i < pl->existingCPUs; i++)
+ free(this->percpu[i]);
+ free(this->percpu);
+ free(this->cpu);
+ free(this);
+}
+
+static inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) {
+ pmAtomValue value;
+ if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_32))
+ return value.l;
+ return fallback;
+}
+
+static inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) {
+ pmAtomValue value;
+ if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_64))
+ return value.l;
+ return fallback;
+}
+
+static inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) {
+ pmAtomValue value;
+ if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U32))
+ return value.ul;
+ return fallback;
+}
+
+static inline unsigned long long Metric_instance_u64(int metric, int pid, int offset, unsigned long long fallback) {
+ pmAtomValue value;
+ if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64))
+ return value.ull;
+ return fallback;
+}
+
+static inline unsigned long long Metric_instance_time(int metric, int pid, int offset) {
+ pmAtomValue value;
+ if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64))
+ return value.ull / 10;
+ return 0;
+}
+
+static inline unsigned long long Metric_instance_ONE_K(int metric, int pid, int offset) {
+ pmAtomValue value;
+ if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64))
+ return value.ull / ONE_K;
+ return ULLONG_MAX;
+}
+
+static inline char Metric_instance_char(int metric, int pid, int offset, char fallback) {
+ pmAtomValue value;
+ if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) {
+ char uchar = value.cp[0];
+ free(value.cp);
+ return uchar;
+ }
+ return fallback;
+}
+
+static inline ProcessState PCPProcessList_getProcessState(char state) {
+ switch (state) {
+ case '?': return UNKNOWN;
+ case 'R': return RUNNING;
+ case 'W': return WAITING;
+ case 'D': return UNINTERRUPTIBLE_WAIT;
+ case 'P': return PAGING;
+ case 'T': return STOPPED;
+ case 't': return TRACED;
+ case 'Z': return ZOMBIE;
+ case 'X': return DEFUNCT;
+ case 'I': return IDLE;
+ case 'S': return SLEEPING;
+ default: return UNKNOWN;
+ }
+}
+
+static void PCPProcessList_updateID(Process* process, int pid, int offset) {
+ process->tgid = Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1);
+ process->ppid = Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1);
+ process->state = PCPProcessList_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?'));
+}
+
+static void PCPProcessList_updateInfo(Process* process, int pid, int offset, char* command, size_t commLen) {
+ PCPProcess* pp = (PCPProcess*) process;
+ pmAtomValue value;
+
+ if (!PCPMetric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING))
+ value.cp = xStrdup("<unknown>");
+ String_safeStrncpy(command, value.cp, commLen);
+ free(value.cp);
+
+ process->pgrp = Metric_instance_u32(PCP_PROC_PGRP, pid, offset, 0);
+ process->session = Metric_instance_u32(PCP_PROC_SESSION, pid, offset, 0);
+ process->tty_nr = Metric_instance_u32(PCP_PROC_TTY, pid, offset, 0);
+ process->tpgid = Metric_instance_u32(PCP_PROC_TTYPGRP, pid, offset, 0);
+ process->minflt = Metric_instance_u32(PCP_PROC_MINFLT, pid, offset, 0);
+ pp->cminflt = Metric_instance_u32(PCP_PROC_CMINFLT, pid, offset, 0);
+ process->majflt = Metric_instance_u32(PCP_PROC_MAJFLT, pid, offset, 0);
+ pp->cmajflt = Metric_instance_u32(PCP_PROC_CMAJFLT, pid, offset, 0);
+ pp->utime = Metric_instance_time(PCP_PROC_UTIME, pid, offset);
+ pp->stime = Metric_instance_time(PCP_PROC_STIME, pid, offset);
+ pp->cutime = Metric_instance_time(PCP_PROC_CUTIME, pid, offset);
+ pp->cstime = Metric_instance_time(PCP_PROC_CSTIME, pid, offset);
+ process->priority = Metric_instance_u32(PCP_PROC_PRIORITY, pid, offset, 0);
+ process->nice = Metric_instance_s32(PCP_PROC_NICE, pid, offset, 0);
+ process->nlwp = Metric_instance_u32(PCP_PROC_THREADS, pid, offset, 0);
+ process->starttime_ctime = Metric_instance_time(PCP_PROC_STARTTIME, pid, offset);
+ process->processor = Metric_instance_u32(PCP_PROC_PROCESSOR, pid, offset, 0);
+
+ process->time = pp->utime + pp->stime;
+}
+
+static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigned long long now) {
+ pmAtomValue value;
+
+ pp->io_rchar = Metric_instance_ONE_K(PCP_PROC_IO_RCHAR, pid, offset);
+ pp->io_wchar = Metric_instance_ONE_K(PCP_PROC_IO_WCHAR, pid, offset);
+ pp->io_syscr = Metric_instance_u64(PCP_PROC_IO_SYSCR, pid, offset, ULLONG_MAX);
+ pp->io_syscw = Metric_instance_u64(PCP_PROC_IO_SYSCW, pid, offset, ULLONG_MAX);
+ pp->io_cancelled_write_bytes = Metric_instance_ONE_K(PCP_PROC_IO_CANCELLED, pid, offset);
+
+ if (PCPMetric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) {
+ unsigned long long last_read = pp->io_read_bytes;
+ pp->io_read_bytes = value.ull / ONE_K;
+ pp->io_rate_read_bps = ONE_K * (pp->io_read_bytes - last_read) /
+ (now - pp->io_last_scan_time);
+ } else {
+ pp->io_read_bytes = ULLONG_MAX;
+ pp->io_rate_read_bps = NAN;
+ }
+
+ if (PCPMetric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) {
+ unsigned long long last_write = pp->io_write_bytes;
+ pp->io_write_bytes = value.ull;
+ pp->io_rate_write_bps = ONE_K * (pp->io_write_bytes - last_write) /
+ (now - pp->io_last_scan_time);
+ } else {
+ pp->io_write_bytes = ULLONG_MAX;
+ pp->io_rate_write_bps = NAN;
+ }
+
+ pp->io_last_scan_time = now;
+}
+
+static void PCPProcessList_updateMemory(PCPProcess* pp, int pid, int offset) {
+ pp->super.m_virt = Metric_instance_u32(PCP_PROC_MEM_SIZE, pid, offset, 0);
+ pp->super.m_resident = Metric_instance_u32(PCP_PROC_MEM_RSS, pid, offset, 0);
+ pp->m_share = Metric_instance_u32(PCP_PROC_MEM_SHARE, pid, offset, 0);
+ pp->m_trs = Metric_instance_u32(PCP_PROC_MEM_TEXTRS, pid, offset, 0);
+ pp->m_lrs = Metric_instance_u32(PCP_PROC_MEM_LIBRS, pid, offset, 0);
+ pp->m_drs = Metric_instance_u32(PCP_PROC_MEM_DATRS, pid, offset, 0);
+ pp->m_dt = Metric_instance_u32(PCP_PROC_MEM_DIRTY, pid, offset, 0);
+}
+
+static void PCPProcessList_updateSmaps(PCPProcess* pp, pid_t pid, int offset) {
+ pp->m_pss = Metric_instance_u64(PCP_PROC_SMAPS_PSS, pid, offset, 0);
+ pp->m_swap = Metric_instance_u64(PCP_PROC_SMAPS_SWAP, pid, offset, 0);
+ pp->m_psswp = Metric_instance_u64(PCP_PROC_SMAPS_SWAPPSS, pid, offset, 0);
+}
+
+static void PCPProcessList_readOomData(PCPProcess* pp, int pid, int offset) {
+ pp->oom = Metric_instance_u32(PCP_PROC_OOMSCORE, pid, offset, 0);
+}
+
+static void PCPProcessList_readAutogroup(PCPProcess* pp, int pid, int offset) {
+ pp->autogroup_id = Metric_instance_s64(PCP_PROC_AUTOGROUP_ID, pid, offset, -1);
+ pp->autogroup_nice = Metric_instance_s32(PCP_PROC_AUTOGROUP_NICE, pid, offset, 0);
+}
+
+static void PCPProcessList_readCtxtData(PCPProcess* pp, int pid, int offset) {
+ pmAtomValue value;
+ unsigned long ctxt = 0;
+
+ if (PCPMetric_instance(PCP_PROC_VCTXSW, pid, offset, &value, PM_TYPE_U32))
+ ctxt += value.ul;
+ if (PCPMetric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32))
+ ctxt += value.ul;
+
+ pp->ctxt_diff = ctxt > pp->ctxt_total ? ctxt - pp->ctxt_total : 0;
+ pp->ctxt_total = ctxt;
+}
+
+static char* setString(PCPMetric metric, int pid, int offset, char* string) {
+ if (string)
+ free(string);
+ pmAtomValue value;
+ if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING))
+ string = value.cp;
+ else
+ string = NULL;
+ return string;
+}
+
+static void PCPProcessList_updateTTY(Process* process, int pid, int offset) {
+ process->tty_name = setString(PCP_PROC_TTYNAME, pid, offset, process->tty_name);
+}
+
+static void PCPProcessList_readCGroups(PCPProcess* pp, int pid, int offset) {
+ pp->cgroup = setString(PCP_PROC_CGROUPS, pid, offset, pp->cgroup);
+}
+
+static void PCPProcessList_readSecattrData(PCPProcess* pp, int pid, int offset) {
+ pp->secattr = setString(PCP_PROC_LABELS, pid, offset, pp->secattr);
+}
+
+static void PCPProcessList_readCwd(PCPProcess* pp, int pid, int offset) {
+ pp->super.procCwd = setString(PCP_PROC_CWD, pid, offset, pp->super.procCwd);
+}
+
+static void PCPProcessList_updateUsername(Process* process, int pid, int offset, UsersTable* users) {
+ process->st_uid = Metric_instance_u32(PCP_PROC_ID_UID, pid, offset, 0);
+ process->user = setUser(users, process->st_uid, pid, offset);
+}
+
+static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, const char* comm) {
+ pmAtomValue value;
+ if (!PCPMetric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) {
+ if (process->state != ZOMBIE)
+ process->isKernelThread = true;
+ Process_updateCmdline(process, NULL, 0, 0);
+ return;
+ }
+
+ char* command = value.cp;
+ int length = strlen(command);
+ if (command[0] != '(') {
+ process->isKernelThread = false;
+ } else {
+ ++command;
+ --length;
+ if (command[length - 1] == ')')
+ command[--length] = '\0';
+ process->isKernelThread = true;
+ }
+
+ int tokenStart = 0;
+ for (int i = 0; i < length; i++) {
+ /* htop considers the next character after the last / that is before
+ * basenameOffset, as the start of the basename in cmdline - see
+ * Process_writeCommand */
+ if (command[i] == '/')
+ tokenStart = i + 1;
+ }
+ int tokenEnd = length;
+
+ Process_updateCmdline(process, command, tokenStart, tokenEnd);
+ free(value.cp);
+
+ Process_updateComm(process, comm);
+
+ if (PCPMetric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) {
+ Process_updateExe(process, value.cp[0] ? value.cp : NULL);
+ free(value.cp);
+ }
+}
+
+static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, struct timeval* tv) {
+ ProcessList* pl = (ProcessList*) this;
+ const Settings* settings = pl->settings;
+
+ bool hideKernelThreads = settings->hideKernelThreads;
+ bool hideUserlandThreads = settings->hideUserlandThreads;
+
+ unsigned long long now = tv->tv_sec * 1000LL + tv->tv_usec / 1000LL;
+ int pid = -1, offset = -1;
+
+ /* for every process ... */
+ while (PCPMetric_iterate(PCP_PROC_PID, &pid, &offset)) {
+
+ bool preExisting;
+ Process* proc = ProcessList_getProcess(pl, pid, &preExisting, PCPProcess_new);
+ PCPProcess* pp = (PCPProcess*) proc;
+ PCPProcessList_updateID(proc, pid, offset);
+ proc->isUserlandThread = proc->pid != proc->tgid;
+ pp->offset = offset >= 0 ? offset : 0;
+
+ /*
+ * These conditions will not trigger on first occurrence, cause we need to
+ * add the process to the ProcessList and do all one time scans
+ * (e.g. parsing the cmdline to detect a kernel thread)
+ * But it will short-circuit subsequent scans.
+ */
+ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {
+ proc->updated = true;
+ proc->show = false;
+ if (proc->state == RUNNING)
+ pl->runningTasks++;
+ pl->kernelThreads++;
+ pl->totalTasks++;
+ continue;
+ }
+ if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
+ proc->updated = true;
+ proc->show = false;
+ if (proc->state == RUNNING)
+ pl->runningTasks++;
+ pl->userlandThreads++;
+ pl->totalTasks++;
+ continue;
+ }
+
+ if (settings->ss->flags & PROCESS_FLAG_IO)
+ PCPProcessList_updateIO(pp, pid, offset, now);
+
+ PCPProcessList_updateMemory(pp, pid, offset);
+
+ if ((settings->ss->flags & PROCESS_FLAG_LINUX_SMAPS) &&
+ (Process_isKernelThread(proc) == false)) {
+ if (PCPMetric_enabled(PCP_PROC_SMAPS_PSS))
+ PCPProcessList_updateSmaps(pp, pid, offset);
+ }
+
+ char command[MAX_NAME + 1];
+ unsigned int tty_nr = proc->tty_nr;
+ unsigned long long int lasttimes = pp->utime + pp->stime;
+
+ PCPProcessList_updateInfo(proc, pid, offset, command, sizeof(command));
+ proc->starttime_ctime += Platform_getBootTime();
+ if (tty_nr != proc->tty_nr)
+ PCPProcessList_updateTTY(proc, pid, offset);
+
+ float percent_cpu = (pp->utime + pp->stime - lasttimes) / period * 100.0;
+ proc->percent_cpu = isnan(percent_cpu) ?
+ 0.0 : CLAMP(percent_cpu, 0.0, pl->activeCPUs * 100.0);
+ proc->percent_mem = proc->m_resident / (double)pl->totalMem * 100.0;
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ PCPProcessList_updateUsername(proc, pid, offset, pl->usersTable);
+
+ if (!preExisting) {
+ PCPProcessList_updateCmdline(proc, pid, offset, command);
+ Process_fillStarttimeBuffer(proc);
+ ProcessList_add(pl, proc);
+ } else if (settings->updateProcessNames && proc->state != ZOMBIE) {
+ PCPProcessList_updateCmdline(proc, pid, offset, command);
+ }
+
+ if (settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP)
+ PCPProcessList_readCGroups(pp, pid, offset);
+
+ if (settings->ss->flags & PROCESS_FLAG_LINUX_OOM)
+ PCPProcessList_readOomData(pp, pid, offset);
+
+ if (settings->ss->flags & PROCESS_FLAG_LINUX_CTXT)
+ PCPProcessList_readCtxtData(pp, pid, offset);
+
+ if (settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR)
+ PCPProcessList_readSecattrData(pp, pid, offset);
+
+ if (settings->ss->flags & PROCESS_FLAG_CWD)
+ PCPProcessList_readCwd(pp, pid, offset);
+
+ if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP)
+ PCPProcessList_readAutogroup(pp, pid, offset);
+
+ if (proc->state == ZOMBIE && !proc->cmdline && command[0]) {
+ Process_updateCmdline(proc, command, 0, strlen(command));
+ } else if (Process_isThread(proc)) {
+ if ((settings->showThreadNames || Process_isKernelThread(proc)) && command[0]) {
+ Process_updateCmdline(proc, command, 0, strlen(command));
+ }
+
+ if (Process_isKernelThread(proc)) {
+ pl->kernelThreads++;
+ } else {
+ pl->userlandThreads++;
+ }
+ }
+
+ /* Set at the end when we know if a new entry is a thread */
+ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) ||
+ (hideUserlandThreads && Process_isUserlandThread(proc)));
+
+ pl->totalTasks++;
+ if (proc->state == RUNNING)
+ pl->runningTasks++;
+ proc->updated = true;
+ }
+ return true;
+}
+
+static void PCPProcessList_updateMemoryInfo(ProcessList* super) {
+ unsigned long long int freeMem = 0;
+ unsigned long long int swapFreeMem = 0;
+ unsigned long long int sreclaimableMem = 0;
+ super->totalMem = super->usedMem = super->cachedMem = 0;
+ super->usedSwap = super->totalSwap = super->sharedMem = 0;
+
+ pmAtomValue value;
+ if (PCPMetric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL)
+ super->totalMem = value.ull;
+ if (PCPMetric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL)
+ freeMem = value.ull;
+ if (PCPMetric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL)
+ super->buffersMem = value.ull;
+ if (PCPMetric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL)
+ sreclaimableMem = value.ull;
+ if (PCPMetric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL)
+ super->sharedMem = value.ull;
+ if (PCPMetric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL)
+ super->cachedMem = value.ull + sreclaimableMem - super->sharedMem;
+ const memory_t usedDiff = freeMem + super->cachedMem + sreclaimableMem + super->buffersMem;
+ super->usedMem = (super->totalMem >= usedDiff) ?
+ super->totalMem - usedDiff : super->totalMem - freeMem;
+ if (PCPMetric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL)
+ super->availableMem = MINIMUM(value.ull, super->totalMem);
+ else
+ super->availableMem = freeMem;
+ if (PCPMetric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL)
+ swapFreeMem = value.ull;
+ if (PCPMetric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL)
+ super->totalSwap = value.ull;
+ if (PCPMetric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL)
+ super->cachedSwap = value.ull;
+ super->usedSwap = super->totalSwap - swapFreeMem - super->cachedSwap;
+}
+
+/* make copies of previously sampled values to avoid overwrite */
+static inline void PCPProcessList_backupCPUTime(pmAtomValue* values) {
+ /* the PERIOD fields (must) mirror the TIME fields */
+ for (int metric = CPU_TOTAL_TIME; metric < CPU_TOTAL_PERIOD; metric++) {
+ values[metric + CPU_TOTAL_PERIOD] = values[metric];
+ }
+}
+
+static inline void PCPProcessList_saveCPUTimePeriod(pmAtomValue* values, CPUMetric previous, pmAtomValue* latest) {
+ pmAtomValue* value;
+
+ /* new value for period */
+ value = &values[previous];
+ if (latest->ull > value->ull)
+ value->ull = latest->ull - value->ull;
+ else
+ value->ull = 0;
+
+ /* new value for time */
+ value = &values[previous - CPU_TOTAL_PERIOD];
+ value->ull = latest->ull;
+}
+
+/* using copied sampled values and new values, calculate derivations */
+static void PCPProcessList_deriveCPUTime(pmAtomValue* values) {
+
+ pmAtomValue* usertime = &values[CPU_USER_TIME];
+ pmAtomValue* guesttime = &values[CPU_GUEST_TIME];
+ usertime->ull -= guesttime->ull;
+
+ pmAtomValue* nicetime = &values[CPU_NICE_TIME];
+ pmAtomValue* guestnicetime = &values[CPU_GUESTNICE_TIME];
+ nicetime->ull -= guestnicetime->ull;
+
+ pmAtomValue* idletime = &values[CPU_IDLE_TIME];
+ pmAtomValue* iowaittime = &values[CPU_IOWAIT_TIME];
+ pmAtomValue* idlealltime = &values[CPU_IDLE_ALL_TIME];
+ idlealltime->ull = idletime->ull + iowaittime->ull;
+
+ pmAtomValue* systemtime = &values[CPU_SYSTEM_TIME];
+ pmAtomValue* irqtime = &values[CPU_IRQ_TIME];
+ pmAtomValue* softirqtime = &values[CPU_SOFTIRQ_TIME];
+ pmAtomValue* systalltime = &values[CPU_SYSTEM_ALL_TIME];
+ systalltime->ull = systemtime->ull + irqtime->ull + softirqtime->ull;
+
+ pmAtomValue* virtalltime = &values[CPU_GUEST_TIME];
+ virtalltime->ull = guesttime->ull + guestnicetime->ull;
+
+ pmAtomValue* stealtime = &values[CPU_STEAL_TIME];
+ pmAtomValue* totaltime = &values[CPU_TOTAL_TIME];
+ totaltime->ull = usertime->ull + nicetime->ull + systalltime->ull +
+ idlealltime->ull + stealtime->ull + virtalltime->ull;
+
+ PCPProcessList_saveCPUTimePeriod(values, CPU_USER_PERIOD, usertime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_NICE_PERIOD, nicetime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_SYSTEM_PERIOD, systemtime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_SYSTEM_ALL_PERIOD, systalltime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_IDLE_ALL_PERIOD, idlealltime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_IDLE_PERIOD, idletime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_IOWAIT_PERIOD, iowaittime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_IRQ_PERIOD, irqtime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_SOFTIRQ_PERIOD, softirqtime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_STEAL_PERIOD, stealtime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_GUEST_PERIOD, virtalltime);
+ PCPProcessList_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime);
+}
+
+static void PCPProcessList_updateAllCPUTime(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric)
+{
+ pmAtomValue* value = &this->cpu[cpumetric];
+ if (PCPMetric_values(metric, value, 1, PM_TYPE_U64) == NULL)
+ memset(value, 0, sizeof(pmAtomValue));
+}
+
+static void PCPProcessList_updatePerCPUTime(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric)
+{
+ int cpus = this->super.existingCPUs;
+ if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL)
+ memset(this->values, 0, cpus * sizeof(pmAtomValue));
+ for (int i = 0; i < cpus; i++)
+ this->percpu[i][cpumetric].ull = this->values[i].ull;
+}
+
+static void PCPProcessList_updatePerCPUReal(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric)
+{
+ int cpus = this->super.existingCPUs;
+ if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL)
+ memset(this->values, 0, cpus * sizeof(pmAtomValue));
+ for (int i = 0; i < cpus; i++)
+ this->percpu[i][cpumetric].d = this->values[i].d;
+}
+
+static inline void PCPProcessList_scanZfsArcstats(PCPProcessList* this) {
+ unsigned long long int dbufSize = 0;
+ unsigned long long int dnodeSize = 0;
+ unsigned long long int bonusSize = 0;
+ pmAtomValue value;
+
+ memset(&this->zfs, 0, sizeof(ZfsArcStats));
+ if (PCPMetric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.anon = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64))
+ this->zfs.min = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64))
+ this->zfs.max = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64))
+ bonusSize = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_DBUF_SIZE, &value, 1, PM_TYPE_U64))
+ dbufSize = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_DNODE_SIZE, &value, 1, PM_TYPE_U64))
+ dnodeSize = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_COMPRESSED_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.compressed = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_UNCOMPRESSED_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.uncompressed = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_HDR_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.header = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_MFU_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.MFU = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_MRU_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.MRU = value.ull / ONE_K;
+ if (PCPMetric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64))
+ this->zfs.size = value.ull / ONE_K;
+
+ this->zfs.other = (dbufSize + dnodeSize + bonusSize) / ONE_K;
+ this->zfs.enabled = (this->zfs.size > 0);
+ this->zfs.isCompressed = (this->zfs.compressed > 0);
+}
+
+static void PCPProcessList_updateHeader(ProcessList* super, const Settings* settings) {
+ PCPProcessList_updateMemoryInfo(super);
+
+ PCPProcessList* this = (PCPProcessList*) super;
+ PCPProcessList_updateCPUcount(this);
+
+ PCPProcessList_backupCPUTime(this->cpu);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_USER, CPU_USER_TIME);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_NICE, CPU_NICE_TIME);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_SYSTEM, CPU_SYSTEM_TIME);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_IDLE, CPU_IDLE_TIME);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_IOWAIT, CPU_IOWAIT_TIME);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_IRQ, CPU_IRQ_TIME);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_SOFTIRQ, CPU_SOFTIRQ_TIME);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_STEAL, CPU_STEAL_TIME);
+ PCPProcessList_updateAllCPUTime(this, PCP_CPU_GUEST, CPU_GUEST_TIME);
+ PCPProcessList_deriveCPUTime(this->cpu);
+
+ for (unsigned int i = 0; i < super->existingCPUs; i++)
+ PCPProcessList_backupCPUTime(this->percpu[i]);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_USER, CPU_USER_TIME);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_NICE, CPU_NICE_TIME);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_SYSTEM, CPU_SYSTEM_TIME);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IDLE, CPU_IDLE_TIME);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IOWAIT, CPU_IOWAIT_TIME);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IRQ, CPU_IRQ_TIME);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_SOFTIRQ, CPU_SOFTIRQ_TIME);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_STEAL, CPU_STEAL_TIME);
+ PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_GUEST, CPU_GUEST_TIME);
+ for (unsigned int i = 0; i < super->existingCPUs; i++)
+ PCPProcessList_deriveCPUTime(this->percpu[i]);
+
+ if (settings->showCPUFrequency)
+ PCPProcessList_updatePerCPUReal(this, PCP_HINV_CPUCLOCK, CPU_FREQUENCY);
+
+ PCPProcessList_scanZfsArcstats(this);
+}
+
+void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
+ PCPProcessList* this = (PCPProcessList*) super;
+ const Settings* settings = super->settings;
+ bool enabled = !pauseProcessUpdate;
+
+ bool flagged = settings->showCPUFrequency;
+ PCPMetric_enable(PCP_HINV_CPUCLOCK, flagged);
+
+ /* In pause mode do not sample per-process metric values at all */
+ for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++)
+ PCPMetric_enable(metric, enabled);
+
+ flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP;
+ PCPMetric_enable(PCP_PROC_CGROUPS, flagged && enabled);
+ flagged = settings->ss->flags & PROCESS_FLAG_LINUX_OOM;
+ PCPMetric_enable(PCP_PROC_OOMSCORE, flagged && enabled);
+ flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CTXT;
+ PCPMetric_enable(PCP_PROC_VCTXSW, flagged && enabled);
+ PCPMetric_enable(PCP_PROC_NVCTXSW, flagged && enabled);
+ flagged = settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR;
+ PCPMetric_enable(PCP_PROC_LABELS, flagged && enabled);
+ flagged = settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP;
+ PCPMetric_enable(PCP_PROC_AUTOGROUP_ID, flagged && enabled);
+ PCPMetric_enable(PCP_PROC_AUTOGROUP_NICE, flagged && enabled);
+
+ /* Sample smaps metrics on every second pass to improve performance */
+ static int smaps_flag;
+ smaps_flag = !!smaps_flag;
+ PCPMetric_enable(PCP_PROC_SMAPS_PSS, smaps_flag && enabled);
+ PCPMetric_enable(PCP_PROC_SMAPS_SWAP, smaps_flag && enabled);
+ PCPMetric_enable(PCP_PROC_SMAPS_SWAPPSS, smaps_flag && enabled);
+
+ struct timeval timestamp;
+ if (PCPMetric_fetch(&timestamp) != true)
+ return;
+
+ double sample = this->timestamp;
+ this->timestamp = pmtimevalToReal(&timestamp);
+
+ PCPProcessList_updateHeader(super, settings);
+
+ /* In pause mode only update global data for meters (CPU, memory, etc) */
+ if (pauseProcessUpdate)
+ return;
+
+ double period = (this->timestamp - sample) * 100;
+ PCPProcessList_updateProcesses(this, period, &timestamp);
+}
+
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
+ assert(id < super->existingCPUs);
+ (void) super;
+
+ pmAtomValue value;
+ if (PCPMetric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32))
+ return true;
+ return false;
+}
diff --git a/pcp/PCPProcessList.h b/pcp/PCPProcessList.h
new file mode 100644
index 0000000..a3a7372
--- /dev/null
+++ b/pcp/PCPProcessList.h
@@ -0,0 +1,74 @@
+#ifndef HEADER_PCPProcessList
+#define HEADER_PCPProcessList
+/*
+htop - PCPProcessList.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "Hashtable.h"
+#include "ProcessList.h"
+#include "UsersTable.h"
+
+#include "pcp/Platform.h"
+#include "zfs/ZfsArcStats.h"
+
+
+typedef enum CPUMetric_ {
+ CPU_TOTAL_TIME,
+ CPU_USER_TIME,
+ CPU_SYSTEM_TIME,
+ CPU_SYSTEM_ALL_TIME,
+ CPU_IDLE_ALL_TIME,
+ CPU_IDLE_TIME,
+ CPU_NICE_TIME,
+ CPU_IOWAIT_TIME,
+ CPU_IRQ_TIME,
+ CPU_SOFTIRQ_TIME,
+ CPU_STEAL_TIME,
+ CPU_GUEST_TIME,
+ CPU_GUESTNICE_TIME,
+
+ CPU_TOTAL_PERIOD,
+ CPU_USER_PERIOD,
+ CPU_SYSTEM_PERIOD,
+ CPU_SYSTEM_ALL_PERIOD,
+ CPU_IDLE_ALL_PERIOD,
+ CPU_IDLE_PERIOD,
+ CPU_NICE_PERIOD,
+ CPU_IOWAIT_PERIOD,
+ CPU_IRQ_PERIOD,
+ CPU_SOFTIRQ_PERIOD,
+ CPU_STEAL_PERIOD,
+ CPU_GUEST_PERIOD,
+ CPU_GUESTNICE_PERIOD,
+
+ CPU_FREQUENCY,
+
+ CPU_METRIC_COUNT
+} CPUMetric;
+
+typedef struct PCPProcessList_ {
+ ProcessList super;
+ 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);
+
+#endif
diff --git a/pcp/Platform.c b/pcp/Platform.c
new file mode 100644
index 0000000..994cef3
--- /dev/null
+++ b/pcp/Platform.c
@@ -0,0 +1,842 @@
+/*
+htop - linux/Platform.c
+(C) 2014 Hisham H. Muhammad
+(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.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "pcp/Platform.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "BatteryMeter.h"
+#include "CPUMeter.h"
+#include "ClockMeter.h"
+#include "DateMeter.h"
+#include "DateTimeMeter.h"
+#include "DiskIOMeter.h"
+#include "DynamicColumn.h"
+#include "DynamicMeter.h"
+#include "HostnameMeter.h"
+#include "LoadAverageMeter.h"
+#include "Macros.h"
+#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
+#include "Meter.h"
+#include "NetworkIOMeter.h"
+#include "ProcessList.h"
+#include "Settings.h"
+#include "SwapMeter.h"
+#include "SysArchMeter.h"
+#include "TasksMeter.h"
+#include "UptimeMeter.h"
+#include "XUtils.h"
+
+#include "linux/PressureStallMeter.h"
+#include "linux/ZramMeter.h"
+#include "linux/ZramStats.h"
+#include "pcp/PCPDynamicColumn.h"
+#include "pcp/PCPDynamicMeter.h"
+#include "pcp/PCPMetric.h"
+#include "pcp/PCPProcessList.h"
+#include "zfs/ZfsArcMeter.h"
+#include "zfs/ZfsArcStats.h"
+#include "zfs/ZfsCompressedArcMeter.h"
+
+
+Platform* pcp;
+
+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 },
+};
+
+const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
+
+const MeterClass* const Platform_meterTypes[] = {
+ &CPUMeter_class,
+ &DynamicMeter_class,
+ &ClockMeter_class,
+ &DateMeter_class,
+ &DateTimeMeter_class,
+ &LoadAverageMeter_class,
+ &LoadMeter_class,
+ &MemoryMeter_class,
+ &SwapMeter_class,
+ &MemorySwapMeter_class,
+ &TasksMeter_class,
+ &UptimeMeter_class,
+ &BatteryMeter_class,
+ &HostnameMeter_class,
+ &AllCPUsMeter_class,
+ &AllCPUs2Meter_class,
+ &AllCPUs4Meter_class,
+ &AllCPUs8Meter_class,
+ &LeftCPUsMeter_class,
+ &RightCPUsMeter_class,
+ &LeftCPUs2Meter_class,
+ &RightCPUs2Meter_class,
+ &LeftCPUs4Meter_class,
+ &RightCPUs4Meter_class,
+ &LeftCPUs8Meter_class,
+ &RightCPUs8Meter_class,
+ &BlankMeter_class,
+ &PressureStallCPUSomeMeter_class,
+ &PressureStallIOSomeMeter_class,
+ &PressureStallIOFullMeter_class,
+ &PressureStallMemorySomeMeter_class,
+ &PressureStallMemoryFullMeter_class,
+ &ZfsArcMeter_class,
+ &ZfsCompressedArcMeter_class,
+ &ZramMeter_class,
+ &DiskIOMeter_class,
+ &NetworkIOMeter_class,
+ &SysArchMeter_class,
+ NULL
+};
+
+static const char* Platform_metricNames[] = {
+ [PCP_CONTROL_THREADS] = "proc.control.perclient.threads",
+
+ [PCP_HINV_NCPU] = "hinv.ncpu",
+ [PCP_HINV_CPUCLOCK] = "hinv.cpu.clock",
+ [PCP_UNAME_SYSNAME] = "kernel.uname.sysname",
+ [PCP_UNAME_RELEASE] = "kernel.uname.release",
+ [PCP_UNAME_MACHINE] = "kernel.uname.machine",
+ [PCP_UNAME_DISTRO] = "kernel.uname.distro",
+ [PCP_LOAD_AVERAGE] = "kernel.all.load",
+ [PCP_PID_MAX] = "kernel.all.pid_max",
+ [PCP_UPTIME] = "kernel.all.uptime",
+ [PCP_BOOTTIME] = "kernel.all.boottime",
+ [PCP_CPU_USER] = "kernel.all.cpu.user",
+ [PCP_CPU_NICE] = "kernel.all.cpu.nice",
+ [PCP_CPU_SYSTEM] = "kernel.all.cpu.sys",
+ [PCP_CPU_IDLE] = "kernel.all.cpu.idle",
+ [PCP_CPU_IOWAIT] = "kernel.all.cpu.wait.total",
+ [PCP_CPU_IRQ] = "kernel.all.cpu.intr",
+ [PCP_CPU_SOFTIRQ] = "kernel.all.cpu.irq.soft",
+ [PCP_CPU_STEAL] = "kernel.all.cpu.steal",
+ [PCP_CPU_GUEST] = "kernel.all.cpu.guest",
+ [PCP_CPU_GUESTNICE] = "kernel.all.cpu.guest_nice",
+ [PCP_PERCPU_USER] = "kernel.percpu.cpu.user",
+ [PCP_PERCPU_NICE] = "kernel.percpu.cpu.nice",
+ [PCP_PERCPU_SYSTEM] = "kernel.percpu.cpu.sys",
+ [PCP_PERCPU_IDLE] = "kernel.percpu.cpu.idle",
+ [PCP_PERCPU_IOWAIT] = "kernel.percpu.cpu.wait.total",
+ [PCP_PERCPU_IRQ] = "kernel.percpu.cpu.intr",
+ [PCP_PERCPU_SOFTIRQ] = "kernel.percpu.cpu.irq.soft",
+ [PCP_PERCPU_STEAL] = "kernel.percpu.cpu.steal",
+ [PCP_PERCPU_GUEST] = "kernel.percpu.cpu.guest",
+ [PCP_PERCPU_GUESTNICE] = "kernel.percpu.cpu.guest_nice",
+ [PCP_MEM_TOTAL] = "mem.physmem",
+ [PCP_MEM_FREE] = "mem.util.free",
+ [PCP_MEM_AVAILABLE] = "mem.util.available",
+ [PCP_MEM_BUFFERS] = "mem.util.bufmem",
+ [PCP_MEM_CACHED] = "mem.util.cached",
+ [PCP_MEM_SHARED] = "mem.util.shmem",
+ [PCP_MEM_SRECLAIM] = "mem.util.slabReclaimable",
+ [PCP_MEM_SWAPCACHED] = "mem.util.swapCached",
+ [PCP_MEM_SWAPTOTAL] = "mem.util.swapTotal",
+ [PCP_MEM_SWAPFREE] = "mem.util.swapFree",
+ [PCP_DISK_READB] = "disk.all.read_bytes",
+ [PCP_DISK_WRITEB] = "disk.all.write_bytes",
+ [PCP_DISK_ACTIVE] = "disk.all.avactive",
+ [PCP_NET_RECVB] = "network.all.in.bytes",
+ [PCP_NET_SENDB] = "network.all.out.bytes",
+ [PCP_NET_RECVP] = "network.all.in.packets",
+ [PCP_NET_SENDP] = "network.all.out.packets",
+
+ [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_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",
+ [PCP_ZFS_ARC_HDR_SIZE] = "zfs.arc.hdr_size",
+ [PCP_ZFS_ARC_MFU_SIZE] = "zfs.arc.mfu.size",
+ [PCP_ZFS_ARC_MRU_SIZE] = "zfs.arc.mru.size",
+ [PCP_ZFS_ARC_SIZE] = "zfs.arc.size",
+
+ [PCP_ZRAM_CAPACITY] = "zram.capacity",
+ [PCP_ZRAM_ORIGINAL] = "zram.mm_stat.data_size.original",
+ [PCP_ZRAM_COMPRESSED] = "zram.mm_stat.data_size.compressed",
+
+ [PCP_PROC_PID] = "proc.psinfo.pid",
+ [PCP_PROC_PPID] = "proc.psinfo.ppid",
+ [PCP_PROC_TGID] = "proc.psinfo.tgid",
+ [PCP_PROC_PGRP] = "proc.psinfo.pgrp",
+ [PCP_PROC_SESSION] = "proc.psinfo.session",
+ [PCP_PROC_STATE] = "proc.psinfo.sname",
+ [PCP_PROC_TTY] = "proc.psinfo.tty",
+ [PCP_PROC_TTYPGRP] = "proc.psinfo.tty_pgrp",
+ [PCP_PROC_MINFLT] = "proc.psinfo.minflt",
+ [PCP_PROC_MAJFLT] = "proc.psinfo.maj_flt",
+ [PCP_PROC_CMINFLT] = "proc.psinfo.cmin_flt",
+ [PCP_PROC_CMAJFLT] = "proc.psinfo.cmaj_flt",
+ [PCP_PROC_UTIME] = "proc.psinfo.utime",
+ [PCP_PROC_STIME] = "proc.psinfo.stime",
+ [PCP_PROC_CUTIME] = "proc.psinfo.cutime",
+ [PCP_PROC_CSTIME] = "proc.psinfo.cstime",
+ [PCP_PROC_PRIORITY] = "proc.psinfo.priority",
+ [PCP_PROC_NICE] = "proc.psinfo.nice",
+ [PCP_PROC_THREADS] = "proc.psinfo.threads",
+ [PCP_PROC_STARTTIME] = "proc.psinfo.start_time",
+ [PCP_PROC_PROCESSOR] = "proc.psinfo.processor",
+ [PCP_PROC_CMD] = "proc.psinfo.cmd",
+ [PCP_PROC_PSARGS] = "proc.psinfo.psargs",
+ [PCP_PROC_CGROUPS] = "proc.psinfo.cgroups",
+ [PCP_PROC_OOMSCORE] = "proc.psinfo.oom_score",
+ [PCP_PROC_VCTXSW] = "proc.psinfo.vctxsw",
+ [PCP_PROC_NVCTXSW] = "proc.psinfo.nvctxsw",
+ [PCP_PROC_LABELS] = "proc.psinfo.labels",
+ [PCP_PROC_ENVIRON] = "proc.psinfo.environ",
+ [PCP_PROC_TTYNAME] = "proc.psinfo.ttyname",
+ [PCP_PROC_EXE] = "proc.psinfo.exe",
+ [PCP_PROC_CWD] = "proc.psinfo.cwd",
+ [PCP_PROC_AUTOGROUP_ID] = "proc.autogroup.id",
+ [PCP_PROC_AUTOGROUP_NICE] = "proc.autogroup.nice",
+ [PCP_PROC_ID_UID] = "proc.id.uid",
+ [PCP_PROC_ID_USER] = "proc.id.uid_nm",
+ [PCP_PROC_IO_RCHAR] = "proc.io.rchar",
+ [PCP_PROC_IO_WCHAR] = "proc.io.wchar",
+ [PCP_PROC_IO_SYSCR] = "proc.io.syscr",
+ [PCP_PROC_IO_SYSCW] = "proc.io.syscw",
+ [PCP_PROC_IO_READB] = "proc.io.read_bytes",
+ [PCP_PROC_IO_WRITEB] = "proc.io.write_bytes",
+ [PCP_PROC_IO_CANCELLED] = "proc.io.cancelled_write_bytes",
+ [PCP_PROC_MEM_SIZE] = "proc.memory.size",
+ [PCP_PROC_MEM_RSS] = "proc.memory.rss",
+ [PCP_PROC_MEM_SHARE] = "proc.memory.share",
+ [PCP_PROC_MEM_TEXTRS] = "proc.memory.textrss",
+ [PCP_PROC_MEM_LIBRS] = "proc.memory.librss",
+ [PCP_PROC_MEM_DATRS] = "proc.memory.datrss",
+ [PCP_PROC_MEM_DIRTY] = "proc.memory.dirty",
+ [PCP_PROC_SMAPS_PSS] = "proc.smaps.pss",
+ [PCP_PROC_SMAPS_SWAP] = "proc.smaps.swap",
+ [PCP_PROC_SMAPS_SWAPPSS] = "proc.smaps.swappss",
+
+ [PCP_METRIC_COUNT] = NULL
+};
+
+#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(PCPMetric id, const char* name) {
+ unsigned int i = (unsigned int)id;
+
+ if (i >= PCP_METRIC_COUNT && i >= pcp->totalMetrics) {
+ /* added via configuration files */
+ size_t j = pcp->totalMetrics + 1;
+ pcp->fetch = xRealloc(pcp->fetch, j * sizeof(pmID));
+ pcp->pmids = xRealloc(pcp->pmids, j * sizeof(pmID));
+ pcp->names = xRealloc(pcp->names, j * sizeof(char*));
+ pcp->descs = xRealloc(pcp->descs, j * sizeof(pmDesc));
+ memset(&pcp->descs[i], 0, sizeof(pmDesc));
+ }
+
+ pcp->pmids[i] = pcp->fetch[i] = PM_ID_NULL;
+ pcp->names[i] = name;
+ return ++pcp->totalMetrics;
+}
+
+/* global state from the environment and command line arguments */
+pmOptions opts;
+
+bool Platform_init(void) {
+ const char* source;
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ source = opts.archives[0];
+ } else if (opts.context == PM_CONTEXT_HOST) {
+ source = opts.nhosts > 0 ? opts.hosts[0] : "local:";
+ } else {
+ opts.context = PM_CONTEXT_HOST;
+ source = "local:";
+ }
+
+ int sts;
+ sts = pmNewContext(opts.context, source);
+ /* with no host requested, fallback to PM_CONTEXT_LOCAL shared libraries */
+ if (sts < 0 && opts.context == PM_CONTEXT_HOST && opts.nhosts == 0) {
+ opts.context = PM_CONTEXT_LOCAL;
+ sts = pmNewContext(opts.context, NULL);
+ }
+ if (sts < 0) {
+ fprintf(stderr, "Cannot setup PCP metric source: %s\n", pmErrStr(sts));
+ return false;
+ }
+ /* setup timezones and other general startup preparation completion */
+ if (pmGetContextOptions(sts, &opts) < 0 || opts.errors) {
+ pmflush();
+ return false;
+ }
+
+ pcp = xCalloc(1, sizeof(Platform));
+ pcp->context = sts;
+ pcp->fetch = xCalloc(PCP_METRIC_COUNT, sizeof(pmID));
+ pcp->pmids = xCalloc(PCP_METRIC_COUNT, sizeof(pmID));
+ pcp->names = xCalloc(PCP_METRIC_COUNT, sizeof(char*));
+ pcp->descs = xCalloc(PCP_METRIC_COUNT, sizeof(pmDesc));
+
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ gettimeofday(&pcp->offset, NULL);
+ pmtimevalDec(&pcp->offset, &opts.start);
+ }
+
+ for (unsigned int i = 0; i < PCP_METRIC_COUNT; i++)
+ Platform_addMetric(i, Platform_metricNames[i]);
+ pcp->meters.offset = PCP_METRIC_COUNT;
+
+ PCPDynamicMeters_init(&pcp->meters);
+
+ pcp->columns.offset = PCP_METRIC_COUNT + pcp->meters.cursor;
+ PCPDynamicColumns_init(&pcp->columns);
+
+ sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids);
+ if (sts < 0) {
+ fprintf(stderr, "Error: cannot lookup metric names: %s\n", pmErrStr(sts));
+ Platform_done();
+ return false;
+ }
+
+ 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();
+
+ /* 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);
+
+ for (size_t i = pcp->columns.offset; i < pcp->columns.offset + pcp->columns.count; i++)
+ PCPMetric_enable(i, true);
+
+ PCPMetric_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);
+
+ /* first sample (fetch) performed above, save constants */
+ Platform_getBootTime();
+ Platform_getRelease(0);
+ Platform_getMaxCPU();
+ Platform_getMaxPid();
+
+ return true;
+}
+
+void Platform_dynamicColumnsDone(Hashtable* columns) {
+ PCPDynamicColumns_done(columns);
+}
+
+void Platform_dynamicMetersDone(Hashtable* meters) {
+ PCPDynamicMeters_done(meters);
+}
+
+void Platform_done(void) {
+ pmDestroyContext(pcp->context);
+ if (pcp->result)
+ pmFreeResult(pcp->result);
+ free(pcp->release);
+ free(pcp->fetch);
+ free(pcp->pmids);
+ free(pcp->names);
+ free(pcp->descs);
+ free(pcp);
+}
+
+void Platform_setBindings(Htop_Action* keys) {
+ /* no platform-specific key bindings */
+ (void)keys;
+}
+
+int Platform_getUptime(void) {
+ pmAtomValue value;
+ if (PCPMetric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL)
+ return 0;
+ return value.l;
+}
+
+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) {
+ *one = values[0].d;
+ *five = values[1].d;
+ *fifteen = values[2].d;
+ }
+}
+
+unsigned int Platform_getMaxCPU(void) {
+ if (pcp->ncpu)
+ return pcp->ncpu;
+
+ pmAtomValue value;
+ if (PCPMetric_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) {
+ if (pcp->pidmax)
+ return pcp->pidmax;
+
+ pmAtomValue value;
+ if (PCPMetric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL)
+ return -1;
+ pcp->pidmax = value.l;
+ return pcp->pidmax;
+}
+
+long long Platform_getBootTime(void) {
+ if (pcp->btime)
+ return pcp->btime;
+
+ pmAtomValue value;
+ if (PCPMetric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL)
+ pcp->btime = value.ll;
+ return pcp->btime;
+}
+
+static double Platform_setOneCPUValues(Meter* this, pmAtomValue* values) {
+
+ unsigned long long value = values[CPU_TOTAL_PERIOD].ull;
+ double total = (double) (value == 0 ? 1 : value);
+ double percent;
+
+ 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) {
+ 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;
+ v[CPU_METER_STEAL] = values[CPU_STEAL_PERIOD].ull / total * 100.0;
+ v[CPU_METER_GUEST] = values[CPU_GUEST_PERIOD].ull / total * 100.0;
+ 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;
+ value = values[CPU_STEAL_PERIOD].ull + values[CPU_GUEST_PERIOD].ull;
+ v[3] = 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;
+
+ v[CPU_METER_FREQUENCY] = values[CPU_FREQUENCY].d;
+ v[CPU_METER_TEMPERATURE] = NAN;
+
+ return percent;
+}
+
+double Platform_setCPUValues(Meter* this, int cpu) {
+ const PCPProcessList* pl = (const PCPProcessList*) this->pl;
+ if (cpu <= 0) /* use aggregate values */
+ return Platform_setOneCPUValues(this, pl->cpu);
+ return Platform_setOneCPUValues(this, pl->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[MEMORY_METER_USED] = pl->usedMem;
+ this->values[MEMORY_METER_BUFFERS] = pl->buffersMem;
+ this->values[MEMORY_METER_SHARED] = pl->sharedMem;
+ this->values[MEMORY_METER_CACHE] = pl->cachedMem;
+ this->values[MEMORY_METER_AVAILABLE] = pl->availableMem;
+
+ if (ppl->zfs.enabled != 0) {
+ // ZFS does not shrink below the value of zfs_arc_min.
+ unsigned long long int shrinkableSize = 0;
+ if (ppl->zfs.size > ppl->zfs.min)
+ shrinkableSize = ppl->zfs.size - ppl->zfs.min;
+ this->values[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[SWAP_METER_USED] = pl->usedSwap;
+ this->values[SWAP_METER_CACHE] = pl->cachedSwap;
+}
+
+void Platform_setZramValues(Meter* this) {
+ int i, count = PCPMetric_instanceCount(PCP_ZRAM_CAPACITY);
+ if (!count) {
+ this->total = 0;
+ this->values[0] = 0;
+ this->values[1] = 0;
+ return;
+ }
+
+ pmAtomValue* values = xCalloc(count, sizeof(pmAtomValue));
+ ZramStats stats = {0};
+
+ if (PCPMetric_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)) {
+ for (i = 0; i < count; i++)
+ stats.usedZramOrig += values[i].ull;
+ }
+ if (PCPMetric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) {
+ for (i = 0; i < count; i++)
+ stats.usedZramComp += values[i].ull;
+ }
+
+ free(values);
+
+ this->total = stats.totalZram;
+ this->values[0] = stats.usedZramComp;
+ this->values[1] = stats.usedZramOrig;
+}
+
+void Platform_setZfsArcValues(Meter* this) {
+ const PCPProcessList* ppl = (const PCPProcessList*) this->pl;
+
+ ZfsArcMeter_readStats(this, &(ppl->zfs));
+}
+
+void Platform_setZfsCompressedArcValues(Meter* this) {
+ const PCPProcessList* ppl = (const PCPProcessList*) this->pl;
+
+ ZfsCompressedArcMeter_readStats(this, &(ppl->zfs));
+}
+
+void Platform_getHostname(char* buffer, size_t size) {
+ const char* hostname = pmGetContextHostName(pcp->context);
+ String_safeStrncpy(buffer, hostname, size);
+}
+
+void Platform_getRelease(char** string) {
+ /* fast-path - previously-formatted string */
+ if (string) {
+ *string = pcp->release;
+ return;
+ }
+
+ /* first call, extract just-sampled values */
+ pmAtomValue sysname, release, machine, distro;
+ if (!PCPMetric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING))
+ sysname.cp = NULL;
+ if (!PCPMetric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING))
+ release.cp = NULL;
+ if (!PCPMetric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING))
+ machine.cp = NULL;
+ if (!PCPMetric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING))
+ distro.cp = NULL;
+
+ size_t length = 16; /* padded for formatting characters */
+ if (sysname.cp)
+ length += strlen(sysname.cp);
+ if (release.cp)
+ length += strlen(release.cp);
+ if (machine.cp)
+ length += strlen(machine.cp);
+ if (distro.cp)
+ length += strlen(distro.cp);
+ pcp->release = xCalloc(1, length);
+
+ if (sysname.cp) {
+ strcat(pcp->release, sysname.cp);
+ strcat(pcp->release, " ");
+ }
+ if (release.cp) {
+ strcat(pcp->release, release.cp);
+ strcat(pcp->release, " ");
+ }
+ if (machine.cp) {
+ strcat(pcp->release, "[");
+ strcat(pcp->release, machine.cp);
+ strcat(pcp->release, "] ");
+ }
+ if (distro.cp) {
+ if (pcp->release[0] != '\0') {
+ strcat(pcp->release, "@ ");
+ strcat(pcp->release, distro.cp);
+ } else {
+ strcat(pcp->release, distro.cp);
+ }
+ strcat(pcp->release, " ");
+ }
+
+ if (pcp->release) /* cull trailing space */
+ pcp->release[strlen(pcp->release)] = '\0';
+
+ free(distro.cp);
+ free(machine.cp);
+ free(release.cp);
+ free(sysname.cp);
+}
+
+char* Platform_getProcessEnv(pid_t pid) {
+ pmAtomValue value;
+ if (!PCPMetric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING))
+ return NULL;
+ return value.cp;
+}
+
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
+ (void)pid;
+ return NULL;
+}
+
+void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) {
+ *ten = *sixty = *threehundred = 0;
+
+ PCPMetric 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, "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) {
+ *ten = values[0].d;
+ *sixty = values[1].d;
+ *threehundred = values[2].d;
+ }
+}
+
+bool Platform_getDiskIO(DiskIOData* data) {
+ memset(data, 0, sizeof(*data));
+
+ pmAtomValue value;
+ if (PCPMetric_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)
+ data->totalBytesWritten = value.ull;
+ if (PCPMetric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL)
+ data->totalMsTimeSpend = value.ull;
+ return true;
+}
+
+bool Platform_getNetworkIO(NetworkIOData* data) {
+ memset(data, 0, sizeof(*data));
+
+ pmAtomValue value;
+ if (PCPMetric_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)
+ data->bytesTransmitted = value.ull;
+ if (PCPMetric_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)
+ data->packetsTransmitted = value.ull;
+ return true;
+}
+
+void Platform_getBattery(double* level, ACPresence* isOnAC) {
+ *level = NAN;
+ *isOnAC = AC_ERROR;
+}
+
+void Platform_longOptionsUsage(ATTR_UNUSED const char* name) {
+ printf(
+" --host=HOSTSPEC metrics source is PMCD at HOSTSPEC [see PCPIntro(1)]\n"
+" --hostzone set reporting timezone to local time of metrics source\n"
+" --timezone=TZ set reporting timezone\n");
+}
+
+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 STATUS_ERROR_EXIT;
+ __pmAddOptHost(&opts, optarg);
+ return STATUS_OK;
+
+ case PLATFORM_LONGOPT_HOSTZONE: /* --hostzone */
+ if (opts.timezone) {
+ pmprintf("%s: at most one of -Z and -z allowed\n", pmGetProgname());
+ opts.errors++;
+ } else {
+ opts.tzflag = 1;
+ }
+ return STATUS_OK;
+
+ case PLATFORM_LONGOPT_TIMEZONE: /* --timezone=TZ */
+ if (argv[optind][0] == '\0')
+ 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 STATUS_OK;
+
+ default:
+ break;
+ }
+ return STATUS_ERROR_EXIT;
+}
+
+void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
+ if (gettimeofday(tv, NULL) == 0) {
+ /* shift by start offset to stay in lock-step with realtime (archives) */
+ if (pcp->offset.tv_sec || pcp->offset.tv_usec)
+ pmtimevalDec(tv, &pcp->offset);
+ *msec = ((uint64_t)tv->tv_sec * 1000) + ((uint64_t)tv->tv_usec / 1000);
+ } else {
+ memset(tv, 0, sizeof(struct timeval));
+ *msec = 0;
+ }
+}
+
+void Platform_gettime_monotonic(uint64_t* msec) {
+ if (pcp->result) {
+ struct timeval* tv = &pcp->result->timestamp;
+ *msec = ((uint64_t)tv->tv_sec * 1000) + ((uint64_t)tv->tv_usec / 1000);
+ } else {
+ *msec = 0;
+ }
+}
+
+Hashtable* Platform_dynamicMeters(void) {
+ return pcp->meters.table;
+}
+
+void Platform_dynamicMeterInit(Meter* meter) {
+ PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param);
+ if (this)
+ PCPDynamicMeter_enable(this);
+}
+
+void Platform_dynamicMeterUpdateValues(Meter* meter) {
+ PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param);
+ if (this)
+ PCPDynamicMeter_updateValues(this, meter);
+}
+
+void Platform_dynamicMeterDisplay(const Meter* meter, RichString* out) {
+ PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param);
+ if (this)
+ PCPDynamicMeter_display(this, meter, out);
+}
+
+Hashtable* Platform_dynamicColumns(void) {
+ return pcp->columns.table;
+}
+
+const char* Platform_dynamicColumnInit(unsigned int key) {
+ PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key);
+ if (this) {
+ PCPMetric_enable(this->id, true);
+ if (this->super.caption)
+ return this->super.caption;
+ if (this->super.heading)
+ return this->super.heading;
+ return this->super.name;
+ }
+ return NULL;
+}
+
+bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key) {
+ PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key);
+ if (this) {
+ PCPDynamicColumn_writeField(this, proc, str);
+ return true;
+ }
+ return false;
+}
diff --git a/pcp/Platform.h b/pcp/Platform.h
new file mode 100644
index 0000000..f06f226
--- /dev/null
+++ b/pcp/Platform.h
@@ -0,0 +1,156 @@
+#ifndef HEADER_Platform
+#define HEADER_Platform
+/*
+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
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <pcp/pmapi.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+/* use htop config.h values for these macros, not pcp values */
+#undef PACKAGE_URL
+#undef PACKAGE_NAME
+#undef PACKAGE_STRING
+#undef PACKAGE_TARNAME
+#undef PACKAGE_VERSION
+#undef PACKAGE_BUGREPORT
+
+#include "Action.h"
+#include "BatteryMeter.h"
+#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "Meter.h"
+#include "NetworkIOMeter.h"
+#include "Process.h"
+#include "ProcessLocksScreen.h"
+#include "RichString.h"
+#include "SignalsPanel.h"
+#include "CommandLine.h"
+
+#include "pcp/PCPDynamicColumn.h"
+#include "pcp/PCPDynamicMeter.h"
+#include "pcp/PCPMetric.h"
+
+
+typedef struct Platform_ {
+ int context; /* PMAPI(3) context identifier */
+ size_t totalMetrics; /* total number of all metrics */
+ const char** names; /* name array indexed by Metric */
+ pmID* pmids; /* all known metric identifiers */
+ pmID* fetch; /* enabled identifiers for sampling */
+ pmDesc* descs; /* metric desc array indexed by Metric */
+ pmResult* result; /* sample values result indexed by Metric */
+ PCPDynamicMeters meters; /* dynamic meters via configuration files */
+ PCPDynamicColumns columns; /* dynamic columns 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 */
+ int pidmax; /* maximum platform process identifier */
+ unsigned int ncpu; /* maximum processor count configured */
+} Platform;
+
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
+
+extern const SignalItem Platform_signals[];
+
+extern const unsigned int Platform_numberOfSignals;
+
+extern const MeterClass* const Platform_meterTypes[];
+
+bool Platform_init(void);
+
+void Platform_done(void);
+
+void Platform_setBindings(Htop_Action* keys);
+
+int Platform_getUptime(void);
+
+void Platform_getLoadAverage(double* one, double* five, double* fifteen);
+
+long long Platform_getBootTime(void);
+
+unsigned int Platform_getMaxCPU(void);
+
+int Platform_getMaxPid(void);
+
+double Platform_setCPUValues(Meter* this, int cpu);
+
+void Platform_setMemoryValues(Meter* this);
+
+void Platform_setSwapValues(Meter* this);
+
+void Platform_setZramValues(Meter* this);
+
+void Platform_setZfsArcValues(Meter* this);
+
+void Platform_setZfsCompressedArcValues(Meter* this);
+
+char* Platform_getProcessEnv(pid_t pid);
+
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+
+void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred);
+
+bool Platform_getDiskIO(DiskIOData* data);
+
+bool Platform_getNetworkIO(NetworkIOData* data);
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC);
+
+void Platform_getHostname(char* buffer, size_t size);
+
+void Platform_getRelease(char** string);
+
+enum {
+ PLATFORM_LONGOPT_HOST = 128,
+ PLATFORM_LONGOPT_TIMEZONE,
+ PLATFORM_LONGOPT_HOSTZONE,
+};
+
+#define PLATFORM_LONG_OPTIONS \
+ {PMLONGOPT_HOST, optional_argument, 0, PLATFORM_LONGOPT_HOST}, \
+ {PMLONGOPT_TIMEZONE, optional_argument, 0, PLATFORM_LONGOPT_TIMEZONE}, \
+ {PMLONGOPT_HOSTZONE, optional_argument, 0, PLATFORM_LONGOPT_HOSTZONE}, \
+
+void Platform_longOptionsUsage(const char* name);
+
+CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);
+
+extern pmOptions opts;
+
+size_t Platform_addMetric(PCPMetric id, const char* name);
+
+void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec);
+
+void Platform_gettime_monotonic(uint64_t* msec);
+
+Hashtable* Platform_dynamicMeters(void);
+
+void Platform_dynamicMetersDone(Hashtable* meters);
+
+void Platform_dynamicMeterInit(Meter* meter);
+
+void Platform_dynamicMeterUpdateValues(Meter* meter);
+
+void Platform_dynamicMeterDisplay(const Meter* meter, RichString* out);
+
+Hashtable* Platform_dynamicColumns(void);
+
+void Platform_dynamicColumnsDone(Hashtable* columns);
+
+const char* Platform_dynamicColumnInit(unsigned int key);
+
+bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key);
+
+#endif
diff --git a/pcp/ProcessField.h b/pcp/ProcessField.h
new file mode 100644
index 0000000..b3ba265
--- /dev/null
+++ b/pcp/ProcessField.h
@@ -0,0 +1,51 @@
+#ifndef HEADER_PCPProcessField
+#define HEADER_PCPProcessField
+/*
+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
+in the source distribution for its full text.
+*/
+
+
+#define PLATFORM_PROCESS_FIELDS \
+ CMINFLT = 11, \
+ CMAJFLT = 13, \
+ UTIME = 14, \
+ STIME = 15, \
+ CUTIME = 16, \
+ CSTIME = 17, \
+ M_SHARE = 41, \
+ M_TRS = 42, \
+ M_DRS = 43, \
+ M_LRS = 44, \
+ M_DT = 45, \
+ CTID = 100, \
+ RCHAR = 103, \
+ WCHAR = 104, \
+ SYSCR = 105, \
+ SYSCW = 106, \
+ RBYTES = 107, \
+ WBYTES = 108, \
+ CNCLWB = 109, \
+ IO_READ_RATE = 110, \
+ IO_WRITE_RATE = 111, \
+ IO_RATE = 112, \
+ CGROUP = 113, \
+ OOM = 114, \
+ PERCENT_CPU_DELAY = 116, \
+ PERCENT_IO_DELAY = 117, \
+ PERCENT_SWAP_DELAY = 118, \
+ M_PSS = 119, \
+ M_SWAP = 120, \
+ M_PSSWP = 121, \
+ CTXT = 122, \
+ SECATTR = 123, \
+ AUTOGROUP_ID = 127, \
+ AUTOGROUP_NICE = 128, \
+ // End of list
+
+
+#endif /* HEADER_PCPProcessField */
diff --git a/pcp/columns/container b/pcp/columns/container
new file mode 100644
index 0000000..519288f
--- /dev/null
+++ b/pcp/columns/container
@@ -0,0 +1,10 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[container]
+heading = Container
+caption = CONTAINER
+width = -12
+metric = proc.id.container
+description = Name of processes container via cgroup heuristics
diff --git a/pcp/columns/delayacct b/pcp/columns/delayacct
new file mode 100644
index 0000000..016904c
--- /dev/null
+++ b/pcp/columns/delayacct
@@ -0,0 +1,10 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[blkio]
+heading = BLKIOD
+caption = BLKIO_TIME
+width = 6
+metric = proc.psinfo.delayacct_blkio_time
+description = Aggregated block I/O delays
diff --git a/pcp/columns/fdcount b/pcp/columns/fdcount
new file mode 100644
index 0000000..e679480
--- /dev/null
+++ b/pcp/columns/fdcount
@@ -0,0 +1,10 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[fds]
+heading = FDS
+caption = FDCOUNT
+width = 4
+metric = proc.fd.count
+description = Open file descriptors
diff --git a/pcp/columns/guest b/pcp/columns/guest
new file mode 100644
index 0000000..89bb926
--- /dev/null
+++ b/pcp/columns/guest
@@ -0,0 +1,17 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[guest]
+heading = GUEST
+caption = GUEST_TIME
+width = 6
+metric = proc.psinfo.guest_time
+description = Guest time for the process
+
+[cguest]
+heading = CGUEST
+caption = CGUEST_TIME
+width = 6
+metric = proc.psinfo.guest_time + proc.psinfo.cguest_time
+description = Cumulative guest time for the process and its children
diff --git a/pcp/columns/memory b/pcp/columns/memory
new file mode 100644
index 0000000..305a654
--- /dev/null
+++ b/pcp/columns/memory
@@ -0,0 +1,39 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[vmdata]
+heading = VDATA
+width = 6
+metric = proc.memory.vmdata
+description = Virtual memory used for data
+
+[vmstack]
+heading = VSTACK
+width = -6
+metric = proc.memory.vmstack
+description = Virtual memory used for stack
+
+[vmexe]
+heading = VEXEC
+width = 6
+metric = proc.memory.vmexe
+description = Virtual memory used for non-library executable code
+
+[vmlib]
+heading = VLIBS
+width = 6
+metric = proc.memory.vmlib
+description = Virtual memory used for libraries
+
+[vmswap]
+heading = VSWAP
+width = 6
+metric = proc.memory.vmswap
+description = Virtual memory size currently swapped out
+
+[vmlock]
+heading = VLOCK
+width = 6
+metric = proc.memory.vmlock
+description = Locked virtual memory
diff --git a/pcp/columns/sched b/pcp/columns/sched
new file mode 100644
index 0000000..36b8b55
--- /dev/null
+++ b/pcp/columns/sched
@@ -0,0 +1,10 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[rundelay]
+heading = RUNQ
+caption = RUN_DELAY
+width = 4
+metric = proc.schedstat.run_delay
+description = Run queue time
diff --git a/pcp/columns/swap b/pcp/columns/swap
new file mode 100644
index 0000000..234b3db
--- /dev/null
+++ b/pcp/columns/swap
@@ -0,0 +1,15 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[swap]
+heading = SWAP
+width = 5
+metric = proc.psinfo.nswap
+description = Count of swap operations for the process
+
+[cswap]
+heading = CSWAP
+width = 5
+metric = proc.psinfo.nswap + proc.psinfo.cnswap
+description = Cumulative swap operations for the process and its children
diff --git a/pcp/columns/tcp b/pcp/columns/tcp
new file mode 100644
index 0000000..f9a1819
--- /dev/null
+++ b/pcp/columns/tcp
@@ -0,0 +1,31 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[tcp_send_calls]
+heading = TCPS
+caption = TCP_SEND
+width = 6
+metric = bcc.proc.net.tcp.send.calls
+description = Count of TCP send calls
+
+[tcp_send_bytes]
+heading = TCPSB
+caption = TCP_SEND_BYTES
+width = 6
+metric = bcc.proc.net.tcp.send.bytes
+description = Cumulative bytes sent via TCP
+
+[tcp_recv_calls]
+heading = TCPR
+caption = TCP_RECV
+width = 6
+metric = bcc.proc.net.tcp.recv.calls
+description = Count of TCP recv calls
+
+[tcp_recv_bytes]
+heading = TCPRB
+caption = TCP_RECV_BYTES
+width = 6
+metric = bcc.proc.net.tcp.recv.bytes
+description = Cumulative bytes received via TCP
diff --git a/pcp/columns/udp b/pcp/columns/udp
new file mode 100644
index 0000000..060f048
--- /dev/null
+++ b/pcp/columns/udp
@@ -0,0 +1,31 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[udp_send_calls]
+heading = UDPS
+caption = UDP_SEND
+width = 6
+metric = bcc.proc.net.udp.send.calls
+description = Count of UDP send calls
+
+[udp_send_bytes]
+heading = UDPSB
+caption = UDP_SEND_BYTES
+width = 6
+metric = bcc.proc.net.udp.send.bytes
+description = Cumulative bytes sent via UDP
+
+[udp_recv_calls]
+heading = UDPR
+caption = UDP_RECV
+width = 6
+metric = bcc.proc.net.udp.recv.calls
+description = Count of UDP recv calls
+
+[udp_recv_bytes]
+heading = UDPRB
+caption = UDP_RECV_BYTES
+width = 6
+metric = bcc.proc.net.udp.recv.bytes
+description = Cumulative bytes received via UDP
diff --git a/pcp/columns/wchan b/pcp/columns/wchan
new file mode 100644
index 0000000..893de58
--- /dev/null
+++ b/pcp/columns/wchan
@@ -0,0 +1,17 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[wchan]
+heading = WCHAN
+caption = WCHAN_ADDRESS
+width = 8
+metric = proc.psinfo.wchan
+description = Wait channel, kernel address process is blocked or sleeping on
+
+[wchans]
+heading = WCHANS
+caption = WCHAN_SYMBOL
+width = -12
+metric = proc.psinfo.wchan_s
+description = Wait channel, kernel symbol process is blocked or sleeping on
diff --git a/pcp/meters/entropy b/pcp/meters/entropy
new file mode 100644
index 0000000..0bef0cf
--- /dev/null
+++ b/pcp/meters/entropy
@@ -0,0 +1,9 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[entropy]
+caption = Entropy
+avail.metric = kernel.all.entropy.avail / kernel.all.entropy.poolsize * 100
+avail.label = avail
+avail.suffix = %
diff --git a/pcp/meters/freespace b/pcp/meters/freespace
new file mode 100644
index 0000000..074af6d
--- /dev/null
+++ b/pcp/meters/freespace
@@ -0,0 +1,11 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[freespace]
+caption = Freespace
+description = Filesystem space
+used.metric = sum(filesys.used)
+used.color = blue
+free.metric = sum(filesys.free)
+free.color = green
diff --git a/pcp/meters/ipc b/pcp/meters/ipc
new file mode 100644
index 0000000..4162f39
--- /dev/null
+++ b/pcp/meters/ipc
@@ -0,0 +1,13 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[ipc]
+caption = SysV IPC
+description = SysV IPC counts
+msg.metric = ipc.msg.used_queues
+msg.color = blue
+sem.metric = ipc.sem.used_sem
+sem.color = green
+shm.metric = ipc.shm.used_ids
+shm.color = cyan
diff --git a/pcp/meters/locks b/pcp/meters/locks
new file mode 100644
index 0000000..5d09510
--- /dev/null
+++ b/pcp/meters/locks
@@ -0,0 +1,15 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[locks]
+caption = File locks
+description = VFS file locks
+posix.metric = vfs.locks.posix.count
+posix.color = blue
+flock.metric = vfs.locks.flock.count
+flock.color = green
+readlock.metric = vfs.locks.posix.read + vfs.locks.flock.read
+readlock.color = red
+writelock.metric = vfs.locks.posix.write + vfs.locks.flock.write
+writelock.color = yellow
diff --git a/pcp/meters/memcache b/pcp/meters/memcache
new file mode 100644
index 0000000..0f2fac2
--- /dev/null
+++ b/pcp/meters/memcache
@@ -0,0 +1,11 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[memcache]
+caption = Memcache
+description = Memcache Hits
+hit.metric = sum(memcache.hits)
+hit.color = green
+miss.metric = sum(memcache.misses)
+miss.color = blue
diff --git a/pcp/meters/mysql b/pcp/meters/mysql
new file mode 100644
index 0000000..a9e75e4
--- /dev/null
+++ b/pcp/meters/mysql
@@ -0,0 +1,71 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[mysql_io]
+caption = MySQL I/O
+recv.metric = mysql.status.bytes_received
+recv.color = green
+sent.metric = mysql.status.bytes_sent
+sent.color = blue
+
+[mysql_keys]
+caption = MySQL keys
+description = MySQL key status
+key_blocks_used.metric = mysql.status.key_blocks_used
+key_blocks_used.label = color
+key_blocks_used.label = used
+key_reads.metric = mysql.status.key_reads
+key_reads.label = read
+key_reads.color = green
+key_writes.metric = mysql.status.key_writes
+key_writes.label = writ
+key_writes.color = blue
+key_read_requests.metric = mysql.status.key_read_requests
+key_read_requests.label = rreq
+key_read_requests.color = green
+key_write_requests.metric = mysql.status.key_write_requests
+key_write_requests.label = wreq
+key_write_requests.color = blue
+
+[innodb_buffer]
+caption = InnoDB pool
+description = InnoDB buffer pool
+created.metric = mysql.status.innodb_pages_created
+created.label = cr
+created.color = yellow
+read.metric = mysql.status.innodb_pages_read
+read.label = rd
+read.color = greed
+written.metric = mysql.status.innodb_pages_written
+written.label = wr
+written.color = red
+
+[innodb_io]
+caption = InnoDB I/O
+description = InnoDB I/O operations
+read.metric = mysql.status.innodb_data_read
+read.label = rd
+read.color = green
+written.metric = mysql.status.innodb_data.writes
+written.label = wr
+written.color = blue
+sync.metric = mysql.status.innodb_data_fsyncs
+sync.label = sync
+sync.color = cyan
+
+[innodb_ops]
+caption = InnoDB ops
+description = InnoDB operations
+inserted.metric = mysql.status.innodb_rows_inserted
+inserted.label = ins
+inserted.color = blue
+updated.metric = mysql.status.innodb_rows_updated
+updated.label = upd
+updated.color = cyan
+deleted.metric = mysql.status.innodb_rows_deleted
+deleted.label = del
+deleted.color = red
+read.metric = mysql.status.innodb_rows_read
+read.label = rd
+read.color = green
diff --git a/pcp/meters/postfix b/pcp/meters/postfix
new file mode 100644
index 0000000..cda6837
--- /dev/null
+++ b/pcp/meters/postfix
@@ -0,0 +1,20 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[postfix]
+caption = Postfix
+incoming.metric = sum(postfix.queues.incoming)
+incoming.color = green
+incoming.label = in
+active.metric = sum(postfix.queues.active)
+active.color = blue
+active.label = act
+deferred.metric = sum(postfix.queues.deferred)
+deferred.color = cyan
+deferred.label = dfr
+bounce.metric = sum(postfix.queues.maildrop)
+bounce.color = red
+bounce.label = bnc
+hold.metric = sum(postfix.queues.hold)
+hold.color = yellow
diff --git a/pcp/meters/redis b/pcp/meters/redis
new file mode 100644
index 0000000..a72f727
--- /dev/null
+++ b/pcp/meters/redis
@@ -0,0 +1,39 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[redisxact]
+caption = Redis xact
+description = Redis transactions
+tps.metric = redis.instantaneous_ops_per_sec
+tps.color = green
+
+[redismem]
+caption = Redis mem
+description = Redis memory
+lua.metric = redis.used_memory_lua
+lua.color = magenta
+used.metric = redis.used_memory
+used.color = blue
+
+[redisclient]
+caption = Redis clients
+description = Redis clients
+type = bar
+blocked.metric = redis.blocked_clients
+blocked.color = blue
+blocked.label = blk
+clients.metric = redis.connected_clients
+clients.color = green
+clients.label = conn
+
+[redisconn]
+caption = Redis conn
+description = Redis connections
+type = bar
+reject.metric = redis.rejected_connections
+reject.color = magenta
+reject.label = fail/s
+total.metric = redis.total_connections_received
+total.color = blue
+total.label = conn/s
diff --git a/pcp/meters/tcp b/pcp/meters/tcp
new file mode 100644
index 0000000..c95736f
--- /dev/null
+++ b/pcp/meters/tcp
@@ -0,0 +1,21 @@
+#
+# pcp-htop(1) configuration file - see pcp-htop(5)
+#
+
+[tcp]
+caption = TCP
+description = TCP sockets
+listen.metric = network.tcpconn.listen
+listen.color = green
+listen.label = lis
+active.metric = network.tcpconn.established
+active.color = blue
+active.label = act
+syn.metric = network.tcpconn.syn_sent + network.tcpconn.syn_recv + network.tcpconn.last_ack
+syn.color = cyan
+wait.metric = network.tcpconn.time_wait
+wait.color = red
+wait.label = tim
+close.metric = network.tcpconn.fin_wait1 + network.tcpconn.fin_wait2 + network.tcpconn.close + network.tcpconn.close_wait + network.tcpconn.closing
+close.color = yellow
+close.label = clo
diff --git a/scripts/htop_suppressions.valgrind b/scripts/htop_suppressions.valgrind
index a7afb0f..0ba4539 100644
--- a/scripts/htop_suppressions.valgrind
+++ b/scripts/htop_suppressions.valgrind
@@ -1,17 +1,20 @@
{
- <ncurses internal memory allocated at startup>
+ <ncurses internal memory>
Memcheck:Leak
- match-leak-kinds: reachable
+ match-leak-kinds: possible,reachable
...
- fun:CRT_init
- fun:main
+ fun:doupdate_sp
+ fun:wrefresh
}
{
- <ncurses internal memory allocated at startup>
+ <ncurses internal memory>
Memcheck:Leak
- match-leak-kinds: reachable
+ match-leak-kinds: possible,reachable
...
+ fun:newterm_sp
+ fun:newterm
+ fun:initscr
fun:CRT_init
}
@@ -20,13 +23,8 @@
Memcheck:Leak
match-leak-kinds: reachable
...
- fun:wgetch
- fun:ScreenManager_run
- fun:Action_runSetup
- fun:actionSetup
- fun:MainPanel_eventHandler
- fun:ScreenManager_run
- fun:main
+ obj:*/libtinfo*
+ fun:CRT_init
}
{
@@ -34,30 +32,26 @@
Memcheck:Leak
match-leak-kinds: reachable
...
- fun:wgetch
- fun:ScreenManager_run
- fun:main
+ obj:*/libncurses*
+ fun:CRT_init
}
{
<ncurses internal memory>
Memcheck:Leak
- match-leak-kinds: reachable
+ match-leak-kinds: possible,reachable
...
- fun:wrefresh
- fun:main
+ obj:*/libncurses*
+ fun:CRT_setColors
+ fun:CRT_init
}
{
- <ncurses internal memory>
+ <devstat internal memory>
Memcheck:Leak
- match-leak-kinds: reachable
- fun:realloc
- fun:_nc_doalloc
- fun:_nc_tparm_analyze
- fun:tparm
+ match-leak-kinds: possible,reachable
...
- fun:doupdate_sp
- fun:wrefresh
- obj:*
+ obj:*/libdevstat*
+ ...
+ fun:Platform_getDiskIO
}
diff --git a/scripts/run_valgrind.sh b/scripts/run_valgrind.sh
index c7c17ff..a03468c 100755
--- a/scripts/run_valgrind.sh
+++ b/scripts/run_valgrind.sh
@@ -3,4 +3,4 @@
SCRIPT=$(readlink -f "$0")
SCRIPTDIR=$(dirname "$SCRIPT")
-valgrind --leak-check=full --show-reachable=yes --show-leak-kinds=all --track-fds=yes --errors-for-leak-kinds=all --suppressions="${SCRIPTDIR}/htop_suppressions.valgrind" "${SCRIPTDIR}/../htop"
+valgrind --leak-check=full --show-reachable=yes --show-leak-kinds=all --track-fds=yes --errors-for-leak-kinds=all --track-origins=yes --suppressions="${SCRIPTDIR}/htop_suppressions.valgrind" "${SCRIPTDIR}/../htop"
diff --git a/solaris/Platform.c b/solaris/Platform.c
index 014eaf5..96f3526 100644
--- a/solaris/Platform.c
+++ b/solaris/Platform.c
@@ -3,15 +3,28 @@ 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 "Platform.h"
+#include "solaris/Platform.h"
+
+#include <kstat.h>
+#include <math.h>
+#include <string.h>
+#include <time.h>
+#include <utmpx.h>
+#include <sys/loadavg.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/var.h>
+
#include "Macros.h"
#include "Meter.h"
#include "CPUMeter.h"
#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
#include "SwapMeter.h"
#include "TasksMeter.h"
#include "LoadAverageMeter.h"
@@ -19,25 +32,23 @@ in the source distribution for its full text.
#include "DateMeter.h"
#include "DateTimeMeter.h"
#include "HostnameMeter.h"
+#include "SysArchMeter.h"
#include "UptimeMeter.h"
#include "zfs/ZfsArcMeter.h"
#include "zfs/ZfsCompressedArcMeter.h"
#include "SolarisProcess.h"
#include "SolarisProcessList.h"
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <utmpx.h>
-#include <sys/loadavg.h>
-#include <string.h>
-#include <kstat.h>
-#include <time.h>
-#include <math.h>
-#include <sys/var.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",
+ },
+};
-double plat_loadavg[3] = {0};
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
const SignalItem Platform_signals[] = {
{ .name = " 0 Cancel", .number = 0 },
@@ -86,8 +97,6 @@ const SignalItem Platform_signals[] = {
const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
-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,
@@ -97,9 +106,11 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
+ &MemorySwapMeter_class,
&TasksMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&UptimeMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
@@ -119,12 +130,9 @@ const MeterClass* const Platform_meterTypes[] = {
NULL
};
-int Platform_numberOfFields = LAST_PROCESSFIELD;
-
-extern char Process_pidFormat[20];
-
-void Platform_init(void) {
+bool Platform_init(void) {
/* no platform-specific setup needed */
+ return true;
}
void Platform_done(void) {
@@ -136,13 +144,13 @@ 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;
while (( ent = getutxent() )) {
- if ( !strcmp("system boot", ent->ut_line )) {
+ if ( String_eq("system boot", ent->ut_line )) {
boot_time = ent->ut_tv.tv_sec;
}
}
@@ -153,18 +161,24 @@ int Platform_getUptime() {
}
void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
- getloadavg( plat_loadavg, 3 );
+ double plat_loadavg[3];
+ if (getloadavg( plat_loadavg, 3 ) < 0) {
+ *one = NAN;
+ *five = NAN;
+ *fifteen = NAN;
+ return;
+ }
*one = plat_loadavg[LOADAVG_1MIN];
*five = plat_loadavg[LOADAVG_5MIN];
*fifteen = plat_loadavg[LOADAVG_15MIN];
}
-int Platform_getMaxPid() {
+int Platform_getMaxPid(void) {
int vproc = 32778; // Reasonable Solaris default
kstat_ctl_t* kc = kstat_open();
if (kc != NULL) {
- kstat_t* kshandle = kstat_lookup(kc, "unix", 0, "var");
+ kstat_t* kshandle = kstat_lookup_wrapper(kc, "unix", 0, "var");
if (kshandle != NULL) {
kstat_read(kc, kshandle, NULL);
@@ -179,9 +193,9 @@ int Platform_getMaxPid() {
return vproc;
}
-double Platform_setCPUValues(Meter* this, int cpu) {
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
const SolarisProcessList* spl = (const SolarisProcessList*) this->pl;
- int cpus = this->pl->cpuCount;
+ unsigned int cpus = this->pl->existingCPUs;
const CPUData* cpuData = NULL;
if (cpus == 1) {
@@ -191,6 +205,11 @@ double Platform_setCPUValues(Meter* this, int cpu) {
cpuData = &(spl->cpus[cpu]);
}
+ if (!cpuData->online) {
+ this->curItems = 0;
+ return NAN;
+ }
+
double percent;
double* v = this->values;
@@ -209,7 +228,7 @@ double Platform_setCPUValues(Meter* this, int cpu) {
percent = isnan(percent) ? 0.0 : CLAMP(percent, 0.0, 100.0);
- v[CPU_METER_FREQUENCY] = NAN;
+ v[CPU_METER_FREQUENCY] = cpuData->frequency;
v[CPU_METER_TEMPERATURE] = NAN;
return percent;
@@ -218,15 +237,18 @@ 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] = pl->cachedMem;
+ this->values[MEMORY_METER_USED] = pl->usedMem;
+ this->values[MEMORY_METER_BUFFERS] = pl->buffersMem;
+ // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
+ this->values[MEMORY_METER_CACHE] = pl->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[SWAP_METER_USED] = pl->usedSwap;
+ this->values[SWAP_METER_CACHE] = NAN;
}
void Platform_setZfsArcValues(Meter* this) {
@@ -245,16 +267,21 @@ static int Platform_buildenv(void* accum, struct ps_prochandle* Phandle, uintptr
envAccum* accump = accum;
(void) Phandle;
(void) addr;
+
size_t thissz = strlen(str);
- if ((thissz + 2) > (accump->capacity - accump->size)) {
- accump->env = xRealloc(accump->env, accump->capacity *= 2);
- }
- if ((thissz + 2) > (accump->capacity - accump->size)) {
- 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));
- strncpy( accump->env + accump->size + thissz + 1, "\n", 1);
- accump->size = accump->size + thissz + 1;
+
+ strlcpy( accump->env + accump->size, str, accump->capacity - accump->size);
+ strncpy( accump->env + accump->size + thissz + 1, "\n", 2);
+
+ accump->size += thissz + 1;
return 0;
}
@@ -265,7 +292,7 @@ char* Platform_getProcessEnv(pid_t pid) {
struct ps_prochandle* Phandle;
if ((Phandle = Pgrab(realpid, PGRAB_RDONLY, &graberr)) == NULL) {
- return "Unable to read process environment.";
+ return NULL;
}
envBuilder.capacity = 4096;
@@ -277,18 +304,13 @@ 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) {
- (void)pid;
- return NULL;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -297,15 +319,9 @@ bool Platform_getDiskIO(DiskIOData* data) {
return false;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
+bool Platform_getNetworkIO(NetworkIOData* data) {
// TODO
- *bytesReceived = 0;
- *packetsReceived = 0;
- *bytesTransmitted = 0;
- *packetsTransmitted = 0;
+ (void)data;
return false;
}
diff --git a/solaris/Platform.h b/solaris/Platform.h
index c338115..14431e3 100644
--- a/solaris/Platform.h
+++ b/solaris/Platform.h
@@ -5,13 +5,25 @@ 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.
+ * Since ncruses macros use the ERR macro, we can not use another name.
+ */
+#undef ERR
#include <libproc.h>
+#undef ERR
+#define ERR (-1)
+
#include <signal.h>
#include <stdbool.h>
+
#include <sys/mkdev.h>
#include <sys/proc.h>
#include <sys/types.h>
@@ -19,13 +31,18 @@ in the source distribution for its full text.
#include "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "NetworkIOMeter.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
+#include "CommandLine.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
#define kill(pid, signal) kill(pid / 1024, signal)
-extern ProcessFieldData Process_fields[];
typedef struct var kvar_t;
typedef struct envAccum_ {
@@ -35,21 +52,17 @@ typedef struct envAccum_ {
char* env;
} envAccum;
-extern double plat_loadavg[3];
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
extern const SignalItem Platform_signals[];
extern const unsigned int Platform_numberOfSignals;
-extern ProcessField Platform_defaultFields[];
-
extern const MeterClass* const Platform_meterTypes[];
-extern int Platform_numberOfFields;
-
-extern char Process_pidFormat[20];
-
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -61,7 +74,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* this, int cpu);
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
void Platform_setMemoryValues(Meter* this);
@@ -73,17 +86,74 @@ 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);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
void Platform_getBattery(double* percent, ACPresence* isOnAC);
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+static inline 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) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+static inline void Platform_gettime_monotonic(uint64_t* msec) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline void* kstat_data_lookup_wrapper(kstat_t* ksp, const char* name) {
+IGNORE_WCASTQUAL_BEGIN
+ return kstat_data_lookup(ksp, (char*)name);
+IGNORE_WCASTQUAL_END
+}
+
+static inline kstat_t* kstat_lookup_wrapper(kstat_ctl_t* kc, const char* ks_module, int ks_instance, const char* ks_name) {
+IGNORE_WCASTQUAL_BEGIN
+ return kstat_lookup(kc, (char*)ks_module, ks_instance, (char*)ks_name);
+IGNORE_WCASTQUAL_END
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
+
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
+
#endif
diff --git a/solaris/ProcessField.h b/solaris/ProcessField.h
new file mode 100644
index 0000000..65dbcdc
--- /dev/null
+++ b/solaris/ProcessField.h
@@ -0,0 +1,24 @@
+#ifndef HEADER_SolarisProcessField
+#define HEADER_SolarisProcessField
+/*
+htop - solaris/ProcessField.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+#define PLATFORM_PROCESS_FIELDS \
+ ZONEID = 100, \
+ ZONE = 101, \
+ PROJID = 102, \
+ TASKID = 103, \
+ POOLID = 104, \
+ CONTID = 105, \
+ LWPID = 106, \
+ \
+ DUMMY_BUMP_FIELD = CWD, \
+ // End of list
+
+
+#endif /* HEADER_SolarisProcessField */
diff --git a/solaris/SolarisProcess.c b/solaris/SolarisProcess.c
index e0a3db2..840757e 100644
--- a/solaris/SolarisProcess.c
+++ b/solaris/SolarisProcess.c
@@ -2,82 +2,61 @@
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 "Process.h"
-#include "ProcessList.h"
-#include "SolarisProcess.h"
-#include "Platform.h"
-#include "CRT.h"
+#include "solaris/SolarisProcess.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
+#include "Process.h"
+#include "ProcessList.h"
+#include "CRT.h"
+
+#include "solaris/Platform.h"
-const ProcessClass SolarisProcess_class = {
- .super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = SolarisProcess_compare
- },
- .writeField = SolarisProcess_writeField,
-};
-ProcessFieldData Process_fields[] = {
+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, },
+ [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, },
[COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
[STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, O onproc, Z zombie, T stopped, W waiting)", .flags = 0, },
- [PPID] = { .name = "PPID", .title = " PPID ", .description = "Parent process ID", .flags = 0, },
- [PGRP] = { .name = "PGRP", .title = " PGRP ", .description = "Process group ID", .flags = 0, },
- [SESSION] = { .name = "SESSION", .title = " SID ", .description = "Process's session ID", .flags = 0, },
- [TTY_NR] = { .name = "TTY_NR", .title = " TTY ", .description = "Controlling terminal", .flags = 0, },
- [TPGID] = { .name = "TPGID", .title = " TPGID ", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, },
- [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
+ [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, },
+ //[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, },
[PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
[NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
[STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, },
- [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, },
- [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, },
+ [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
+ [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
+ [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
[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, },
- [ZONEID] = { .name = "ZONEID", .title = " ZONEID ", .description = "Zone ID", .flags = 0, },
+ [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
+ [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process", .flags = 0, },
+ [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, },
+ [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
+ [ZONEID] = { .name = "ZONEID", .title = "ZONEID", .description = "Zone ID", .flags = 0, .pidColumn = true, },
[ZONE] = { .name = "ZONE", .title = "ZONE ", .description = "Zone name", .flags = 0, },
- [PROJID] = { .name = "PROJID", .title = " PRJID ", .description = "Project ID", .flags = 0, },
- [TASKID] = { .name = "TASKID", .title = " TSKID ", .description = "Task ID", .flags = 0, },
- [POOLID] = { .name = "POOLID", .title = " POLID ", .description = "Pool ID", .flags = 0, },
- [CONTID] = { .name = "CONTID", .title = " CNTID ", .description = "Contract ID", .flags = 0, },
- [LWPID] = { .name = "LWPID", .title = " LWPID ", .description = "LWP ID", .flags = 0, },
- [LAST_PROCESSFIELD] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, },
-};
-
-ProcessPidColumn Process_pidColumns[] = {
- { .id = ZONEID, .label = "ZONEID" },
- { .id = TASKID, .label = "TSKID" },
- { .id = PROJID, .label = "PRJID" },
- { .id = POOLID, .label = "POLID" },
- { .id = CONTID, .label = "CNTID" },
- { .id = PID, .label = "PID" },
- { .id = PPID, .label = "PPID" },
- { .id = LWPID, .label = "LWPID" },
- { .id = TPGID, .label = "TPGID" },
- { .id = TGID, .label = "TGID" },
- { .id = PGRP, .label = "PGRP" },
- { .id = SESSION, .label = "SID" },
- { .id = 0, .label = NULL },
+ [PROJID] = { .name = "PROJID", .title = "PRJID", .description = "Project ID", .flags = 0, .pidColumn = true, },
+ [TASKID] = { .name = "TASKID", .title = "TSKID", .description = "Task ID", .flags = 0, .pidColumn = true, },
+ [POOLID] = { .name = "POOLID", .title = "POLID", .description = "Pool ID", .flags = 0, .pidColumn = true, },
+ [CONTID] = { .name = "CONTID", .title = "CNTID", .description = "Contract ID", .flags = 0, .pidColumn = true, },
+ [LWPID] = { .name = "LWPID", .title = "LWPID", .description = "LWP ID", .flags = 0, .pidColumn = true, },
};
Process* SolarisProcess_new(const Settings* settings) {
@@ -94,42 +73,35 @@ void Process_delete(Object* cast) {
free(sp);
}
-void SolarisProcess_writeField(const Process* this, RichString* str, ProcessField field) {
+static void SolarisProcess_writeField(const Process* this, RichString* str, ProcessField field) {
const SolarisProcess* sp = (const SolarisProcess*) this;
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
int n = sizeof(buffer) - 1;
- switch ((int) field) {
+ switch (field) {
// add Solaris-specific fields here
- case ZONEID: xSnprintf(buffer, n, Process_pidFormat, sp->zoneid); break;
- case PROJID: xSnprintf(buffer, n, Process_pidFormat, sp->projid); break;
- case TASKID: xSnprintf(buffer, n, Process_pidFormat, sp->taskid); break;
- case POOLID: xSnprintf(buffer, n, Process_pidFormat, sp->poolid); break;
- case CONTID: xSnprintf(buffer, n, Process_pidFormat, sp->contid); break;
- case ZONE: xSnprintf(buffer, n, "%-*s ", ZONENAME_MAX/4, sp->zname); break;
- case PID: xSnprintf(buffer, n, Process_pidFormat, sp->realpid); break;
- case PPID: xSnprintf(buffer, n, Process_pidFormat, sp->realppid); break;
- case LWPID: xSnprintf(buffer, n, Process_pidFormat, sp->lwpid); break;
+ case ZONEID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->zoneid); break;
+ case PROJID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->projid); break;
+ 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 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);
return;
}
- RichString_append(str, attr, buffer);
+ RichString_appendWide(str, attr, buffer);
}
-long SolarisProcess_compare(const void* v1, const void* v2) {
- const SolarisProcess *p1, *p2;
- const Settings* settings = ((const Process*)v1)->settings;
-
- if (settings->direction == 1) {
- p1 = (const SolarisProcess*)v1;
- p2 = (const SolarisProcess*)v2;
- } else {
- p2 = (const SolarisProcess*)v1;
- p1 = (const SolarisProcess*)v2;
- }
+static int SolarisProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const SolarisProcess* p1 = (const SolarisProcess*)v1;
+ const SolarisProcess* p2 = (const SolarisProcess*)v2;
- switch ((int) settings->sortKey) {
+ switch (key) {
case ZONEID:
return SPACESHIP_NUMBER(p1->zoneid, p2->zoneid);
case PROJID:
@@ -149,18 +121,17 @@ long SolarisProcess_compare(const void* v1, const void* v2) {
case LWPID:
return SPACESHIP_NUMBER(p1->lwpid, p2->lwpid);
default:
- return Process_compare(v1, v2);
+ return Process_compareByKey_Base(v1, v2, key);
}
}
-bool Process_isThread(const Process* this) {
- const SolarisProcess* fp = (const SolarisProcess*) this;
-
- if (fp->kernel == 1 ) {
- return 1;
- } else if (fp->is_lwp) {
- return 1;
- } else {
- return 0;
- }
-}
+const ProcessClass SolarisProcess_class = {
+ .super = {
+ .extends = Class(Process),
+ .display = Process_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .writeField = SolarisProcess_writeField,
+ .compareByKey = SolarisProcess_compareByKey
+};
diff --git a/solaris/SolarisProcess.h b/solaris/SolarisProcess.h
index 4756634..13a2bc1 100644
--- a/solaris/SolarisProcess.h
+++ b/solaris/SolarisProcess.h
@@ -4,60 +4,46 @@
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 "Settings.h"
+#include "config.h" // IWYU pragma: keep
+
#include <zone.h>
#include <sys/proc.h>
+
+/* On OmniOS /usr/include/sys/regset.h redefines ERR to 13 - \r, breaking the Enter key.
+ * Since ncruses macros use the ERR macro, we can not use another name.
+ */
+#undef ERR
#include <libproc.h>
+#undef ERR
+#define ERR (-1)
+
+#include "Settings.h"
-typedef enum SolarisProcessField_ {
- // Add platform-specific fields here, with ids >= 100
- ZONEID = 100,
- ZONE = 101,
- PROJID = 102,
- TASKID = 103,
- POOLID = 104,
- CONTID = 105,
- LWPID = 106,
- LAST_PROCESSFIELD = 107,
-} SolarisProcessField;
typedef struct SolarisProcess_ {
Process super;
- int kernel;
zoneid_t zoneid;
char* zname;
taskid_t taskid;
projid_t projid;
poolid_t poolid;
ctid_t contid;
- bool is_lwp;
pid_t realpid;
pid_t realppid;
+ pid_t realtgid;
pid_t lwpid;
} SolarisProcess;
-#define Process_isKernelThread(_process) (_process->kernel == 1)
-
-#define Process_isUserlandThread(_process) (_process->pid != _process->tgid)
-
extern const ProcessClass SolarisProcess_class;
-extern ProcessFieldData Process_fields[];
-
-extern ProcessPidColumn Process_pidColumns[];
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
Process* SolarisProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-void SolarisProcess_writeField(const Process* this, RichString* str, ProcessField field);
-
-long SolarisProcess_compare(const void* v1, const void* v2);
-
-bool Process_isThread(const Process* this);
-
#endif
diff --git a/solaris/SolarisProcessList.c b/solaris/SolarisProcessList.c
index 4249fa6..905cfbd 100644
--- a/solaris/SolarisProcessList.c
+++ b/solaris/SolarisProcessList.c
@@ -2,19 +2,17 @@
htop - SolarisProcessList.c
(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 "ProcessList.h"
-#include "SolarisProcess.h"
-#include "SolarisProcessList.h"
+
+#include "solaris/SolarisProcessList.h"
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/user.h>
-#include <err.h>
#include <limits.h>
#include <string.h>
#include <procfs.h>
@@ -24,11 +22,17 @@ in the source distribution for its full text.
#include <time.h>
#include "CRT.h"
+#include "solaris/Platform.h"
+#include "solaris/SolarisProcess.h"
+
+#define GZONE "global "
+#define UZONE "unknown "
-#define MAXCMDLINE 255
+static int pageSize;
+static int pageSizeKB;
-char* SolarisProcessList_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) {
+static char* SolarisProcessList_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) {
char* zname;
if ( sproc->zoneid == 0 ) {
@@ -36,73 +40,128 @@ char* SolarisProcessList_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) {
} else if ( kd == NULL ) {
zname = xStrdup(UZONE);
} else {
- kstat_t* ks = kstat_lookup( kd, "zones", sproc->zoneid, NULL );
+ kstat_t* ks = kstat_lookup_wrapper( kd, "zones", sproc->zoneid, NULL );
zname = xStrdup(ks == NULL ? UZONE : ks->ks_name);
}
return zname;
}
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+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, pidMatchList, userId);
+ ProcessList_init(pl, Class(SolarisProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
spl->kd = kstat_open();
+ if (!spl->kd)
+ CRT_fatalError("Cannot open kstat handle");
- pl->cpuCount = sysconf(_SC_NPROCESSORS_ONLN);
+ pageSize = sysconf(_SC_PAGESIZE);
+ if (pageSize == -1)
+ CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)");
+ pageSizeKB = pageSize / 1024;
- if (pl->cpuCount == 1 ) {
- spl->cpus = xRealloc(spl->cpus, sizeof(CPUData));
- } else {
- spl->cpus = xRealloc(spl->cpus, (pl->cpuCount + 1) * sizeof(CPUData));
- }
+ SolarisProcessList_updateCPUcount(pl);
return pl;
}
static inline void SolarisProcessList_scanCPUTime(ProcessList* pl) {
const SolarisProcessList* spl = (SolarisProcessList*) pl;
- int cpus = pl->cpuCount;
+ unsigned int activeCPUs = pl->activeCPUs;
+ unsigned int existingCPUs = pl->existingCPUs;
kstat_t* cpuinfo = NULL;
- int kchain = 0;
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(cpus > 0);
+ assert(existingCPUs > 0);
+ assert(spl->kd);
- if (cpus > 1) {
+ 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 (int i = 0; i < cpus; i++) {
- if (spl->kd != NULL) {
- cpuinfo = kstat_lookup(spl->kd, "cpu", i, "sys");
- }
- if (cpuinfo != NULL) {
- kchain = kstat_read(spl->kd, cpuinfo, NULL);
- }
- if (kchain != -1 ) {
- idletime = kstat_data_lookup(cpuinfo, "cpu_nsec_idle");
- intrtime = kstat_data_lookup(cpuinfo, "cpu_nsec_intr");
- krnltime = kstat_data_lookup(cpuinfo, "cpu_nsec_kernel");
- usertime = kstat_data_lookup(cpuinfo, "cpu_nsec_user");
+ 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) );
- CPUData* cpuData = &(spl->cpus[i + arrskip]);
+ 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)
@@ -121,8 +180,10 @@ static inline void SolarisProcessList_scanCPUTime(ProcessList* pl) {
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 (cpus > 1) {
+ if (existingCPUs > 1) {
userbuf += cpuData->userPercent;
krnlbuf += cpuData->systemPercent;
intrbuf += cpuData->irqPercent;
@@ -130,14 +191,14 @@ static inline void SolarisProcessList_scanCPUTime(ProcessList* pl) {
}
}
- if (cpus > 1) {
+ if (existingCPUs > 1) {
CPUData* cpuData = &(spl->cpus[0]);
- cpuData->userPercent = userbuf / cpus;
+ cpuData->userPercent = userbuf / activeCPUs;
cpuData->nicePercent = (double)0.0; // Not implemented on Solaris
- cpuData->systemPercent = krnlbuf / cpus;
- cpuData->irqPercent = intrbuf / cpus;
+ cpuData->systemPercent = krnlbuf / activeCPUs;
+ cpuData->irqPercent = intrbuf / activeCPUs;
cpuData->systemAllPercent = cpuData->systemPercent + cpuData->irqPercent;
- cpuData->idlePercent = idlebuf / cpus;
+ cpuData->idlePercent = idlebuf / activeCPUs;
}
}
@@ -158,20 +219,20 @@ static inline void SolarisProcessList_scanMemoryInfo(ProcessList* pl) {
// Part 1 - physical memory
if (spl->kd != NULL && meminfo == NULL) {
- // Look up the kstat chain just one, it never changes
- meminfo = kstat_lookup(spl->kd, "unix", 0, "system_pages");
+ // 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(meminfo, "physmem");
- freemem_pgs = kstat_data_lookup(meminfo, "freemem");
- pages = kstat_data_lookup(meminfo, "pagestotal");
+ 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 * CRT_pageSizeKB;
- if (pl->totalMem > freemem_pgs->value.ui64 * CRT_pageSizeKB) {
- pl->usedMem = pl->totalMem - freemem_pgs->value.ui64 * CRT_pageSizeKB;
+ 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)
}
@@ -179,13 +240,13 @@ static inline void SolarisProcessList_scanMemoryInfo(ProcessList* pl) {
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) * CRT_pageSizeKB;
+ 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) * CRT_pageSize;
+ pl->totalMem = sysconf(_SC_PHYS_PAGES) * pageSize;
pl->buffersMem = 0;
pl->cachedMem = 0;
- pl->usedMem = pl->totalMem - (sysconf(_SC_AVPHYS_PAGES) * CRT_pageSize);
+ pl->usedMem = pl->totalMem - (sysconf(_SC_AVPHYS_PAGES) * pageSize);
}
// Part 2 - swap
@@ -215,8 +276,8 @@ static inline void SolarisProcessList_scanMemoryInfo(ProcessList* pl) {
}
free(spathbase);
free(sl);
- pl->totalSwap = totalswap * CRT_pageSizeKB;
- pl->usedSwap = pl->totalSwap - (totalfree * CRT_pageSizeKB);
+ pl->totalSwap = totalswap * pageSizeKB;
+ pl->usedSwap = pl->totalSwap - (totalfree * pageSizeKB);
}
static inline void SolarisProcessList_scanZfsArcstats(ProcessList* pl) {
@@ -226,39 +287,39 @@ static inline void SolarisProcessList_scanZfsArcstats(ProcessList* pl) {
kstat_named_t *cur_kstat = NULL;
if (spl->kd != NULL) {
- arcstats = kstat_lookup(spl->kd, "zfs", 0, "arcstats");
+ 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( arcstats, "size" );
+ 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( arcstats, "c_max" );
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "c_max" );
spl->zfs.max = cur_kstat->value.ui64 / 1024;
- cur_kstat = kstat_data_lookup( arcstats, "mfu_size" );
+ 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( arcstats, "mru_size" );
+ 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( arcstats, "anon_size" );
+ 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( arcstats, "hdr_size" );
+ 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( arcstats, "other_size" );
+ 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( arcstats, "compressed_size" )) != NULL) {
+ 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( arcstats, "uncompressed_size" );
+ cur_kstat = kstat_data_lookup_wrapper( arcstats, "uncompressed_size" );
spl->zfs.uncompressed = cur_kstat->value.ui64 / 1024;
} else {
spl->zfs.isCompressed = 0;
@@ -276,13 +337,52 @@ void ProcessList_delete(ProcessList* pl) {
free(spl);
}
+static void SolarisProcessList_updateExe(pid_t pid, Process* proc) {
+ char path[32];
+ xSnprintf(path, sizeof(path), "/proc/%d/path/a.out", pid);
+
+ char target[PATH_MAX];
+ ssize_t ret = readlink(path, target, sizeof(target) - 1);
+ if (ret <= 0)
+ return;
+
+ target[ret] = '\0';
+ Process_updateExe(proc, target);
+}
+
+static void SolarisProcessList_updateCwd(pid_t pid, Process* proc) {
+ char path[32];
+ xSnprintf(path, sizeof(path), "/proc/%d/cwd", pid);
+
+ char target[PATH_MAX];
+ ssize_t ret = readlink(path, target, sizeof(target) - 1);
+ if (ret <= 0)
+ return;
+
+ target[ret] = '\0';
+ free_and_xStrdup(&proc->procCwd, target);
+}
+
+/* Taken from: https://docs.oracle.com/cd/E19253-01/817-6223/6mlkidlom/index.html#tbl-sched-state */
+static inline ProcessState SolarisProcessList_getProcessState(char state) {
+ switch (state) {
+ case 'S': return SLEEPING;
+ case 'R': return RUNNABLE;
+ case 'O': return RUNNING;
+ case 'Z': return ZOMBIE;
+ case 'T': return STOPPED;
+ case 'I': return IDLE;
+ default: return UNKNOWN;
+ }
+}
+
/* NOTE: the following is a callback function of type proc_walk_f
* and MUST conform to the appropriate definition in order
* to work. See libproc(3LIB) on a Solaris or Illumos
* system for more info.
*/
-int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr) {
+static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr) {
bool preExisting;
pid_t getpid;
@@ -302,6 +402,7 @@ int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void*
} else {
getpid = lwpid;
}
+
Process* proc = ProcessList_getProcess(pl, getpid, &preExisting, SolarisProcess_new);
SolarisProcess* sproc = (SolarisProcess*) proc;
@@ -314,26 +415,45 @@ int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void*
proc->priority = _lwpsinfo->pr_pri;
proc->nice = _lwpsinfo->pr_nice - NZERO;
proc->processor = _lwpsinfo->pr_onpro;
- proc->state = _lwpsinfo->pr_sname;
+ proc->state = SolarisProcessList_getProcessState(_lwpsinfo->pr_sname);
// NOTE: This 'percentage' is a 16-bit BINARY FRACTIONS where 1.0 = 0x8000
// Source: https://docs.oracle.com/cd/E19253-01/816-5174/proc-4/index.html
// (accessed on 18 November 2017)
proc->percent_mem = ((uint16_t)_psinfo->pr_pctmem / (double)32768) * (double)100.0;
- proc->st_uid = _psinfo->pr_euid;
proc->pgrp = _psinfo->pr_pgid;
proc->nlwp = _psinfo->pr_nlwp;
+ proc->session = _psinfo->pr_sid;
+
proc->tty_nr = _psinfo->pr_ttydev;
- proc->m_resident = _psinfo->pr_rssize / CRT_pageSizeKB;
- proc->m_virt = _psinfo->pr_size / CRT_pageSizeKB;
+ 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);
- proc->user = UsersTable_getRef(pl->usersTable, proc->st_uid);
- proc->comm = xStrdup(_psinfo->pr_fname);
- proc->commLen = strnlen(_psinfo->pr_fname, PRFNSZ);
+ SolarisProcessList_updateExe(_psinfo->pr_pid, proc);
+
+ Process_updateComm(proc, _psinfo->pr_fname);
+ Process_updateCmdline(proc, _psinfo->pr_psargs, 0, 0);
+
+ if (proc->settings->ss->flags & PROCESS_FLAG_CWD) {
+ SolarisProcessList_updateCwd(_psinfo->pr_pid, proc);
+ }
}
// End common code pass 1
@@ -342,23 +462,27 @@ int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void*
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;
+ 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
- sproc->is_lwp = false;
+ proc->isUserlandThread = false;
proc->starttime_ctime = _psinfo->pr_start.tv_sec;
}
// Update proc and thread counts based on settings
- if (sproc->kernel && !pl->settings->hideKernelThreads) {
+ if (proc->isKernelThread && !pl->settings->hideKernelThreads) {
pl->kernelThreads += proc->nlwp;
pl->totalTasks += proc->nlwp + 1;
- if (proc->state == 'O') {
+ if (proc->state == RUNNING) {
pl->runningTasks++;
}
- } else if (!sproc->kernel) {
- if (proc->state == 'O') {
+ } else if (!proc->isKernelThread) {
+ if (proc->state == RUNNING) {
pl->runningTasks++;
}
if (pl->settings->hideUserlandThreads) {
@@ -368,24 +492,26 @@ int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void*
pl->totalTasks += proc->nlwp + 1;
}
}
- proc->show = !(pl->settings->hideKernelThreads && sproc->kernel);
+ 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;
+ 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
- sproc->is_lwp = true;
- proc->basenameOffset = -1;
+ 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 (sproc->kernel && !pl->settings->hideKernelThreads) {
+ if (proc->isKernelThread && !pl->settings->hideKernelThreads) {
proc->show = true;
}
- if (!sproc->kernel && !pl->settings->hideUserlandThreads) {
+ if (!proc->isKernelThread && !pl->settings->hideUserlandThreads) {
proc->show = true;
}
} // Top-level LWP or subordinate LWP
@@ -394,13 +520,15 @@ int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void*
if (!preExisting) {
if ((sproc->realppid <= 0) && !(sproc->realpid <= 1)) {
- sproc->kernel = true;
+ proc->isKernelThread = true;
} else {
- sproc->kernel = false;
+ proc->isKernelThread = false;
}
+
Process_fillStarttimeBuffer(proc);
ProcessList_add(pl, proc);
}
+
proc->updated = true;
// End common code pass 2
@@ -409,6 +537,7 @@ int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void*
}
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
+ SolarisProcessList_updateCPUcount(super);
SolarisProcessList_scanCPUTime(super);
SolarisProcessList_scanMemoryInfo(super);
SolarisProcessList_scanZfsArcstats(super);
@@ -421,3 +550,11 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
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/SolarisProcessList.h b/solaris/SolarisProcessList.h
index f800d9d..91fd4f4 100644
--- a/solaris/SolarisProcessList.h
+++ b/solaris/SolarisProcessList.h
@@ -4,18 +4,15 @@
htop - SolarisProcessList.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.
*/
-#define MAXCMDLINE 255
-
-#define GZONE "global "
-#define UZONE "unknown "
-
-#include "zfs/ZfsArcStats.h"
+#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>
@@ -23,6 +20,15 @@ in the source distribution for its full text.
#include <sys/sysinfo.h>
#include <sys/swap.h>
+#include "Hashtable.h"
+#include "ProcessList.h"
+#include "UsersTable.h"
+
+#include "solaris/SolarisProcess.h"
+
+#include "zfs/ZfsArcStats.h"
+
+
#define ZONE_ERRMSGLEN 1024
extern char zone_errmsg[ZONE_ERRMSGLEN];
@@ -33,10 +39,12 @@ typedef struct CPUData_ {
double irqPercent;
double idlePercent;
double systemAllPercent;
+ double frequency;
uint64_t luser;
uint64_t lkrnl;
uint64_t lintr;
uint64_t lidle;
+ bool online;
} CPUData;
typedef struct SolarisProcessList_ {
@@ -46,20 +54,12 @@ typedef struct SolarisProcessList_ {
ZfsArcStats zfs;
} SolarisProcessList;
-char* SolarisProcessList_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc);
-
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId);
void ProcessList_delete(ProcessList* pl);
-/* 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.
- */
-
-int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr);
-
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
+bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id);
+
#endif
diff --git a/unsupported/Platform.c b/unsupported/Platform.c
index 54fde50..27bc560 100644
--- a/unsupported/Platform.c
+++ b/unsupported/Platform.c
@@ -2,63 +2,47 @@
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.
*/
+#include "config.h" // IWYU pragma: keep
+
+#include "unsupported/Platform.h"
+
#include <math.h>
-#include "Platform.h"
-#include "Macros.h"
#include "CPUMeter.h"
-#include "MemoryMeter.h"
-#include "SwapMeter.h"
-#include "TasksMeter.h"
-#include "LoadAverageMeter.h"
#include "ClockMeter.h"
#include "DateMeter.h"
#include "DateTimeMeter.h"
#include "HostnameMeter.h"
+#include "LoadAverageMeter.h"
+#include "Macros.h"
+#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
+#include "SwapMeter.h"
+#include "SysArchMeter.h"
+#include "TasksMeter.h"
#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);
-ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
-
-ProcessFieldData Process_fields[] = {
- [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, },
- [PID] = { .name = "PID", .title = " PID ", .description = "Process/thread ID", .flags = 0, },
- [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
- [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)", .flags = 0, },
- [PPID] = { .name = "PPID", .title = " PPID ", .description = "Parent process ID", .flags = 0, },
- [PGRP] = { .name = "PGRP", .title = " PGRP ", .description = "Process group ID", .flags = 0, },
- [SESSION] = { .name = "SESSION", .title = " SID ", .description = "Process's session ID", .flags = 0, },
- [TTY_NR] = { .name = "TTY_NR", .title = " TTY ", .description = "Controlling terminal", .flags = 0, },
- [TPGID] = { .name = "TPGID", .title = " TPGID ", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, },
- [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, },
- [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, },
- [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
- [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
- [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
-
- [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
- [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
- [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
- [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, },
- [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, },
- [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
- [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, },
- [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
- [TGID] = { .name = "TGID", .title = " TGID ", .description = "Thread group ID (i.e. process ID)", .flags = 0, },
- [100] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, },
-};
-
const MeterClass* const Platform_meterTypes[] = {
&CPUMeter_class,
&ClockMeter_class,
@@ -68,9 +52,11 @@ const MeterClass* const Platform_meterTypes[] = {
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
+ &MemorySwapMeter_class,
&TasksMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
+ &SysArchMeter_class,
&UptimeMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
@@ -88,14 +74,11 @@ const MeterClass* const Platform_meterTypes[] = {
NULL
};
-int Platform_numberOfFields = 100;
-
-ProcessPidColumn Process_pidColumns[] = {
- { .id = 0, .label = 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) {
@@ -107,7 +90,7 @@ void Platform_setBindings(Htop_Action* keys) {
(void) keys;
}
-int Platform_getUptime() {
+int Platform_getUptime(void) {
return 0;
}
@@ -117,17 +100,19 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
*fifteen = 0;
}
-int Platform_getMaxPid() {
+int Platform_getMaxPid(void) {
return 1;
}
-double Platform_setCPUValues(Meter* this, int cpu) {
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
(void) cpu;
double* v = this->values;
v[CPU_METER_FREQUENCY] = NAN;
v[CPU_METER_TEMPERATURE] = NAN;
+ this->curItems = 1;
+
return 0.0;
}
@@ -139,25 +124,14 @@ void Platform_setSwapValues(Meter* this) {
(void) this;
}
-bool Process_isThread(const Process* this) {
- (void) this;
- return false;
-}
-
char* Platform_getProcessEnv(pid_t pid) {
(void) pid;
return NULL;
}
-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;
+ (void)pid;
+ return NULL;
}
bool Platform_getDiskIO(DiskIOData* data) {
@@ -165,14 +139,8 @@ bool Platform_getDiskIO(DiskIOData* data) {
return false;
}
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted) {
- *bytesReceived = 0;
- *packetsReceived = 0;
- *bytesTransmitted = 0;
- *packetsTransmitted = 0;
+bool Platform_getNetworkIO(NetworkIOData* data) {
+ (void)data;
return false;
}
@@ -180,3 +148,11 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) {
*percent = NAN;
*isOnAC = AC_ERROR;
}
+
+void Platform_getHostname(char* buffer, size_t size) {
+ String_safeStrncpy(buffer, Platform_unsupported, size);
+}
+
+void Platform_getRelease(char** string) {
+ *string = xStrdup(Platform_unsupported);
+}
diff --git a/unsupported/Platform.h b/unsupported/Platform.h
index d3f5d72..f475dda 100644
--- a/unsupported/Platform.h
+++ b/unsupported/Platform.h
@@ -4,34 +4,33 @@
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 "Action.h"
#include "BatteryMeter.h"
#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "NetworkIOMeter.h"
#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
-#include "UnsupportedProcess.h"
+#include "CommandLine.h"
+#include "generic/gettime.h"
+#include "unsupported/UnsupportedProcess.h"
-extern const SignalItem Platform_signals[];
-
-extern const unsigned int Platform_numberOfSignals;
-extern ProcessField Platform_defaultFields[];
+extern const ScreenDefaults Platform_defaultScreens[];
-extern ProcessFieldData Process_fields[];
+extern const unsigned int Platform_numberOfDefaultScreens;
-extern const MeterClass* const Platform_meterTypes[];
-
-extern int Platform_numberOfFields;
+extern const SignalItem Platform_signals[];
-extern char Process_pidFormat[20];
+extern const unsigned int Platform_numberOfSignals;
-extern ProcessPidColumn Process_pidColumns[];
+extern const MeterClass* const Platform_meterTypes[];
-void Platform_init(void);
+bool Platform_init(void);
void Platform_done(void);
@@ -43,27 +42,66 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid(void);
-double Platform_setCPUValues(Meter* this, int cpu);
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
void Platform_setMemoryValues(Meter* this);
void Platform_setSwapValues(Meter* this);
-bool Process_isThread(const Process* this);
-
char* Platform_getProcessEnv(pid_t pid);
-char* Platform_getInodeFilename(pid_t pid, ino_t inode);
-
FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
bool Platform_getDiskIO(DiskIOData* data);
-bool Platform_getNetworkIO(unsigned long int* bytesReceived,
- unsigned long int* packetsReceived,
- unsigned long int* bytesTransmitted,
- unsigned long int* packetsTransmitted);
+bool Platform_getNetworkIO(NetworkIOData* data);
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC);
+
+void Platform_getHostname(char* buffer, size_t size);
+
+void Platform_getRelease(char** string);
+
+#define PLATFORM_LONG_OPTIONS
+
+static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
+
+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) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+static inline void Platform_gettime_monotonic(uint64_t* msec) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
-void Platform_getBattery(double *percent, ACPresence *isOnAC);
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
#endif
diff --git a/unsupported/ProcessField.h b/unsupported/ProcessField.h
new file mode 100644
index 0000000..0c908ea
--- /dev/null
+++ b/unsupported/ProcessField.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_UnsupportedProcessField
+#define HEADER_UnsupportedProcessField
+/*
+htop - unsupported/ProcessField.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+#define PLATFORM_PROCESS_FIELDS \
+ // End of list
+
+
+#endif /* HEADER_UnsupportedProcessField */
diff --git a/unsupported/UnsupportedProcess.c b/unsupported/UnsupportedProcess.c
index 0827c60..2aca048 100644
--- a/unsupported/UnsupportedProcess.c
+++ b/unsupported/UnsupportedProcess.c
@@ -1,26 +1,104 @@
/*
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.
*/
-#include "Process.h"
-#include "UnsupportedProcess.h"
+#include "config.h" // IWYU pragma: keep
+
+#include "unsupported/UnsupportedProcess.h"
+
#include <stdlib.h>
+#include "CRT.h"
+#include "Process.h"
+
+
+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, },
+ [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
+ [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)", .flags = 0, },
+ [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, },
+ [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, },
+ [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
+ [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
+ [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
+ [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
+ [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
+ [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
+ [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
+ [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(Settings* settings) {
- Process* this = xCalloc(1, sizeof(Process));
- Object_setClass(this, Class(Process));
+Process* UnsupportedProcess_new(const Settings* settings) {
+ Process* this = xCalloc(1, sizeof(UnsupportedProcess));
+ Object_setClass(this, Class(UnsupportedProcess));
Process_init(this, settings);
return this;
}
-void UnsupportedProcess_delete(Object* cast) {
- Process* this = (Process*) cast;
- Object_setClass(this, Class(Process));
- Process_done((Process*)cast);
+void Process_delete(Object* cast) {
+ Process* super = (Process*) cast;
+ Process_done(super);
// free platform-specific fields here
- free(this);
+ free(cast);
}
+
+static void UnsupportedProcess_writeField(const Process* this, RichString* str, ProcessField field) {
+ const UnsupportedProcess* up = (const UnsupportedProcess*) this;
+ bool coloring = this->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);
+ return;
+ }
+ RichString_appendWide(str, attr, buffer);
+}
+
+static int UnsupportedProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const UnsupportedProcess* p1 = (const UnsupportedProcess*)v1;
+ const UnsupportedProcess* p2 = (const UnsupportedProcess*)v2;
+
+ (void) p1;
+ (void) p2;
+
+ switch (key) {
+ /* Add platform specific fields */
+ default:
+ return Process_compareByKey_Base(v1, v2, key);
+ }
+}
+
+const ProcessClass UnsupportedProcess_class = {
+ .super = {
+ .extends = Class(Process),
+ .display = Process_display,
+ .delete = Process_delete,
+ .compare = Process_compare
+ },
+ .writeField = UnsupportedProcess_writeField,
+ .compareByKey = UnsupportedProcess_compareByKey
+};
diff --git a/unsupported/UnsupportedProcess.h b/unsupported/UnsupportedProcess.h
index 11335cd..e30169c 100644
--- a/unsupported/UnsupportedProcess.h
+++ b/unsupported/UnsupportedProcess.h
@@ -3,16 +3,26 @@
/*
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"
-#define Process_delete UnsupportedProcess_delete
-Process* UnsupportedProcess_new(Settings* settings);
+typedef struct UnsupportedProcess_ {
+ Process super;
-void UnsupportedProcess_delete(Object* cast);
+ /* Add platform specific fields */
+} UnsupportedProcess;
+
+
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
+
+Process* UnsupportedProcess_new(const Settings* settings);
+
+void Process_delete(Object* cast);
+
+extern const ProcessClass UnsupportedProcess_class;
#endif
diff --git a/unsupported/UnsupportedProcessList.c b/unsupported/UnsupportedProcessList.c
index 098eb48..5291797 100644
--- a/unsupported/UnsupportedProcessList.c
+++ b/unsupported/UnsupportedProcessList.c
@@ -1,20 +1,25 @@
/*
htop - UnsupportedProcessList.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 "ProcessList.h"
-#include "UnsupportedProcess.h"
+#include "UnsupportedProcessList.h"
#include <stdlib.h>
#include <string.h>
+#include "ProcessList.h"
+#include "UnsupportedProcess.h"
+
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
+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, pidMatchList, userId);
+ ProcessList_init(this, Class(Process), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
+
+ this->existingCPUs = 1;
+ this->activeCPUs = 1;
return this;
}
@@ -41,23 +46,34 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
proc->pid = 1;
proc->ppid = 1;
proc->tgid = 0;
- proc->comm = "<unsupported architecture>";
- proc->basenameOffset = 0;
+
+ Process_updateComm(proc, "commof16char");
+ Process_updateCmdline(proc, "<unsupported architecture>", 0, 0);
+ Process_updateExe(proc, "/path/to/executable");
+
+ if (proc->settings->ss->flags & PROCESS_FLAG_CWD) {
+ free_and_xStrdup(&proc->procCwd, "/current/working/directory");
+ }
+
proc->updated = true;
- proc->state = 'R';
+ proc->state = RUNNING;
+ 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->st_uid = 0;
- proc->flags = 0;
proc->processor = 0;
proc->percent_cpu = 2.5;
proc->percent_mem = 2.5;
- proc->user = "nobody";
+ 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;
@@ -70,4 +86,15 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
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
index 1c53771..cbf25af 100644
--- a/unsupported/UnsupportedProcessList.h
+++ b/unsupported/UnsupportedProcessList.h
@@ -3,14 +3,19 @@
/*
htop - UnsupportedProcessList.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.
*/
-ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId);
+#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/zfs/ZfsArcMeter.c b/zfs/ZfsArcMeter.c
index e844d77..f124272 100644
--- a/zfs/ZfsArcMeter.c
+++ b/zfs/ZfsArcMeter.c
@@ -1,18 +1,21 @@
/*
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 "ZfsArcMeter.h"
-#include "ZfsArcStats.h"
+#include "zfs/ZfsArcMeter.h"
+
+#include <stddef.h>
#include "CRT.h"
#include "Object.h"
#include "Platform.h"
#include "RichString.h"
+#include "zfs/ZfsArcStats.h"
+
static const int ZfsArcMeter_attributes[] = {
ZFS_MFU, ZFS_MRU, ZFS_ANON, ZFS_HEADER, ZFS_OTHER
@@ -33,7 +36,9 @@ void ZfsArcMeter_readStats(Meter* this, const ZfsArcStats* stats) {
this->values[5] = stats->size;
}
-static void ZfsArcMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void ZfsArcMeter_updateValues(Meter* this) {
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
int written;
Platform_setZfsArcValues(this);
@@ -50,29 +55,29 @@ static void ZfsArcMeter_display(const Object* cast, RichString* out) {
if (this->values[5] > 0) {
char buffer[50];
- Meter_humanUnit(buffer, this->total, 50);
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
- Meter_humanUnit(buffer, this->values[5], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " Used:");
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
- Meter_humanUnit(buffer, this->values[0], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " MFU:");
- RichString_append(out, CRT_colors[ZFS_MFU], buffer);
- Meter_humanUnit(buffer, this->values[1], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " MRU:");
- RichString_append(out, CRT_colors[ZFS_MRU], buffer);
- Meter_humanUnit(buffer, this->values[2], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " Anon:");
- RichString_append(out, CRT_colors[ZFS_ANON], buffer);
- Meter_humanUnit(buffer, this->values[3], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " Hdr:");
- RichString_append(out, CRT_colors[ZFS_HEADER], buffer);
- Meter_humanUnit(buffer, this->values[4], 50);
- RichString_append(out, CRT_colors[METER_TEXT], " Oth:");
- RichString_append(out, CRT_colors[ZFS_OTHER], buffer);
+ Meter_humanUnit(buffer, this->total, sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+ Meter_humanUnit(buffer, this->values[5], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " Used:");
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+ Meter_humanUnit(buffer, this->values[0], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " MFU:");
+ RichString_appendAscii(out, CRT_colors[ZFS_MFU], buffer);
+ Meter_humanUnit(buffer, this->values[1], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " MRU:");
+ RichString_appendAscii(out, CRT_colors[ZFS_MRU], buffer);
+ Meter_humanUnit(buffer, this->values[2], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " Anon:");
+ RichString_appendAscii(out, CRT_colors[ZFS_ANON], buffer);
+ Meter_humanUnit(buffer, this->values[3], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " Hdr:");
+ RichString_appendAscii(out, CRT_colors[ZFS_HEADER], buffer);
+ Meter_humanUnit(buffer, this->values[4], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " Oth:");
+ RichString_appendAscii(out, CRT_colors[ZFS_OTHER], buffer);
} else {
- RichString_write(out, CRT_colors[METER_TEXT], " ");
- RichString_append(out, CRT_colors[FAILED_SEARCH], "Unavailable");
+ RichString_writeAscii(out, CRT_colors[METER_TEXT], " ");
+ RichString_appendAscii(out, CRT_colors[FAILED_READ], "Unavailable");
}
}
diff --git a/zfs/ZfsArcMeter.h b/zfs/ZfsArcMeter.h
index 52bf784..3f6ea5a 100644
--- a/zfs/ZfsArcMeter.h
+++ b/zfs/ZfsArcMeter.h
@@ -3,14 +3,15 @@
/*
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.
*/
-#include "ZfsArcStats.h"
+#include "zfs/ZfsArcStats.h"
#include "Meter.h"
+
void ZfsArcMeter_readStats(Meter* this, const ZfsArcStats* stats);
extern const MeterClass ZfsArcMeter_class;
diff --git a/zfs/ZfsArcStats.c b/zfs/ZfsArcStats.c
deleted file mode 100644
index bead846..0000000
--- a/zfs/ZfsArcStats.c
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
-htop - ZfsArcStats.c
-(C) 2014 Hisham H. Muhammad
-Released under the GNU GPLv2, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "Macros.h"
-
-static int make_iso_compilers_happy ATTR_UNUSED;
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 8766f80..2e49473 100644
--- a/zfs/ZfsCompressedArcMeter.c
+++ b/zfs/ZfsCompressedArcMeter.c
@@ -1,11 +1,13 @@
/*
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 "ZfsCompressedArcMeter.h"
+#include "zfs/ZfsCompressedArcMeter.h"
+
+#include <stddef.h>
#include "CRT.h"
#include "Meter.h"
@@ -13,7 +15,6 @@ in the source distribution for its full text.
#include "Platform.h"
#include "RichString.h"
#include "XUtils.h"
-
#include "zfs/ZfsArcStats.h"
@@ -32,14 +33,18 @@ void ZfsCompressedArcMeter_readStats(Meter* this, const ZfsArcStats* stats) {
}
}
-static void ZfsCompressedArcMeter_printRatioString(const Meter* this, char* buffer, size_t size) {
- xSnprintf(buffer, size, "%.2f:1", this->total / this->values[0]);
+static int ZfsCompressedArcMeter_printRatioString(const Meter* this, char* buffer, size_t size) {
+ if (this->values[0] > 0) {
+ return xSnprintf(buffer, size, "%.2f:1", this->total / this->values[0]);
+ }
+
+ return xSnprintf(buffer, size, "N/A");
}
-static void ZfsCompressedArcMeter_updateValues(Meter* this, char* buffer, size_t size) {
+static void ZfsCompressedArcMeter_updateValues(Meter* this) {
Platform_setZfsCompressedArcValues(this);
- ZfsCompressedArcMeter_printRatioString(this, buffer, size);
+ ZfsCompressedArcMeter_printRatioString(this, this->txtBuffer, sizeof(this->txtBuffer));
}
static void ZfsCompressedArcMeter_display(const Object* cast, RichString* out) {
@@ -47,18 +52,20 @@ static void ZfsCompressedArcMeter_display(const Object* cast, RichString* out) {
if (this->values[0] > 0) {
char buffer[50];
- Meter_humanUnit(buffer, this->total, 50);
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
- RichString_append(out, CRT_colors[METER_TEXT], " Uncompressed, ");
- Meter_humanUnit(buffer, this->values[0], 50);
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
- RichString_append(out, CRT_colors[METER_TEXT], " Compressed, ");
- ZfsCompressedArcMeter_printRatioString(this, buffer, 50);
- RichString_append(out, CRT_colors[METER_VALUE], buffer);
- RichString_append(out, CRT_colors[METER_TEXT], " Ratio");
+ int len;
+
+ Meter_humanUnit(buffer, this->total, sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " Uncompressed, ");
+ Meter_humanUnit(buffer, this->values[0], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " Compressed, ");
+ len = ZfsCompressedArcMeter_printRatioString(this, buffer, sizeof(buffer));
+ RichString_appendnAscii(out, CRT_colors[ZFS_RATIO], buffer, len);
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " Ratio");
} else {
- RichString_write(out, CRT_colors[METER_TEXT], " ");
- RichString_append(out, CRT_colors[FAILED_SEARCH], "Compression Unavailable");
+ RichString_writeAscii(out, CRT_colors[METER_TEXT], " ");
+ RichString_appendAscii(out, CRT_colors[FAILED_READ], "Compression Unavailable");
}
}
diff --git a/zfs/ZfsCompressedArcMeter.h b/zfs/ZfsCompressedArcMeter.h
index 025a9dd..6e2f45f 100644
--- a/zfs/ZfsCompressedArcMeter.h
+++ b/zfs/ZfsCompressedArcMeter.h
@@ -3,14 +3,15 @@
/*
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.
*/
-#include "ZfsArcStats.h"
+#include "zfs/ZfsArcStats.h"
#include "Meter.h"
+
void ZfsCompressedArcMeter_readStats(Meter* this, const ZfsArcStats* stats);
extern const MeterClass ZfsCompressedArcMeter_class;

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