From 4cbdf13666baf9d3edb110e3128d4d922d05752b Mon Sep 17 00:00:00 2001 From: Samuel Venable Date: Fri, 5 Jan 2024 22:31:59 -0700 Subject: [PATCH] Fix OpenBSD Build --- .github/workflows/build_release.yml | 2 +- .github/workflows/ci.yml | 90 ++- .github/workflows/codeql-analysis.yml | 6 +- .github/workflows/htoprc | 71 ++ Action.c | 185 +++-- Action.h | 5 +- Affinity.c | 35 +- Affinity.h | 8 +- AffinityPanel.c | 84 +-- AvailableColumnsPanel.c | 58 +- AvailableColumnsPanel.h | 2 + AvailableMetersPanel.c | 9 +- BatteryMeter.c | 25 +- CPUMeter.c | 30 +- CRT.c | 11 +- CRT.h | 2 - CategoriesPanel.c | 22 +- ChangeLog | 2 +- ClockMeter.c | 2 +- ColorsPanel.c | 48 +- ColumnsPanel.c | 35 +- ColumnsPanel.h | 1 + CommandLine.c | 40 +- CommandScreen.c | 10 +- CommandScreen.h | 7 + Compat.h | 4 +- DateMeter.c | 2 +- DateTimeMeter.c | 2 +- DiskIOMeter.c | 128 ++-- DiskIOMeter.h | 2 + DisplayOptionsPanel.c | 60 +- DynamicColumn.c | 16 +- DynamicColumn.h | 25 +- DynamicMeter.c | 3 +- DynamicMeter.h | 7 + DynamicScreen.c | 68 ++ DynamicScreen.h | 38 + EnvScreen.c | 13 +- EnvScreen.h | 8 + FileDescriptorMeter.c | 15 +- Header.c | 3 + Header.h | 4 +- HeaderLayout.h | 8 +- HeaderOptionsPanel.c | 46 +- InfoScreen.c | 88 +-- InfoScreen.h | 7 + ListItem.h | 1 + LoadAverageMeter.c | 4 +- Machine.c | 82 ++- Machine.h | 29 +- Macros.h | 42 +- MainPanel.c | 80 ++- MainPanel.h | 18 +- Makefile.am | 69 +- MemoryMeter.c | 27 +- MemoryMeter.h | 6 +- MemorySwapMeter.c | 3 + Meter.c | 150 ++-- Meter.h | 15 +- MetersPanel.c | 29 +- NetworkIOMeter.c | 167 ++--- NetworkIOMeter.h | 8 + Object.c | 2 + Object.h | 2 - OpenFilesScreen.c | 176 +++-- OptionItem.c | 2 + Panel.c | 120 ++-- Panel.h | 2 - Process.c | 655 +++++------------- Process.h | 201 ++---- ProcessList.c | 473 ------------- ProcessList.h | 83 --- ProcessLocksScreen.c | 4 +- ProcessTable.c | 92 +++ ProcessTable.h | 52 ++ ProvideCurses.h | 4 +- ProvideTerm.h | 2 +- RichString.c | 2 + RichString.h | 2 - Row.c | 497 +++++++++++++ Row.h | 181 +++++ RowField.h | 56 ++ Scheduling.c | 46 +- Scheduling.h | 7 +- ScreenManager.c | 16 +- ScreenTabsPanel.c | 374 ++++++++++ ScreenTabsPanel.h | 61 ++ ScreensPanel.c | 138 ++-- ScreensPanel.h | 6 +- Settings.c | 136 +++- Settings.h | 33 +- SignalsPanel.c | 2 + SignalsPanel.h | 4 +- SwapMeter.c | 5 +- TESTPLAN | 2 +- Table.c | 372 ++++++++++ Table.h | 95 +++ TasksMeter.c | 20 +- TraceScreen.c | 32 +- UptimeMeter.c | 2 + Vector.c | 6 +- XUtils.c | 27 + XUtils.h | 40 +- configure.ac | 108 ++- darwin/DarwinProcess.c | 58 +- darwin/DarwinProcess.h | 4 +- ...rwinProcessList.c => DarwinProcessTable.c} | 42 +- ...rwinProcessList.h => DarwinProcessTable.h} | 14 +- darwin/Platform.c | 70 +- darwin/Platform.h | 16 +- docs/styleguide.md | 5 + dragonflybsd/DragonFlyBSDMachine.c | 14 +- dragonflybsd/DragonFlyBSDMachine.h | 2 +- dragonflybsd/DragonFlyBSDProcess.c | 31 +- dragonflybsd/DragonFlyBSDProcessList.h | 21 - ...ocessList.c => DragonFlyBSDProcessTable.c} | 141 ++-- dragonflybsd/DragonFlyBSDProcessTable.h | 21 + dragonflybsd/Platform.c | 19 +- dragonflybsd/Platform.h | 16 +- freebsd/FreeBSDMachine.c | 19 +- freebsd/FreeBSDProcess.c | 35 +- ...BSDProcessList.c => FreeBSDProcessTable.c} | 79 ++- ...BSDProcessList.h => FreeBSDProcessTable.h} | 14 +- freebsd/Platform.c | 4 +- freebsd/Platform.h | 16 +- generic/fdstat_sysctl.c | 7 +- generic/gettime.c | 1 + generic/hostname.c | 1 + generic/uname.c | 1 + htop.1.in | 19 +- htop.desktop | 0 iwyu/htop.imp | 5 + iwyu/run_iwyu.sh | 1 + linux/CGroupUtils.c | 245 ++++++- linux/CGroupUtils.h | 4 +- linux/HugePageMeter.c | 6 +- linux/IOPriorityPanel.c | 2 + linux/LibSensors.c | 31 +- linux/LibSensors.h | 6 + linux/LinuxMachine.c | 152 ++-- linux/LinuxMachine.h | 4 - linux/LinuxProcess.c | 181 +++-- linux/LinuxProcess.h | 15 +- ...LinuxProcessList.c => LinuxProcessTable.c} | 356 +++++----- ...LinuxProcessList.h => LinuxProcessTable.h} | 19 +- linux/Platform.c | 50 +- linux/Platform.h | 21 +- linux/PressureStallMeter.c | 2 + linux/ProcessField.h | 2 + linux/SELinuxMeter.c | 2 + linux/SystemdMeter.c | 14 +- linux/ZramMeter.c | 17 +- linux/ZramMeter.h | 7 + linux/ZramStats.h | 2 +- linux/ZswapStats.h | 4 +- netbsd/NetBSDMachine.c | 12 +- netbsd/NetBSDMachine.h | 2 +- netbsd/NetBSDProcess.c | 27 +- ...tBSDProcessList.c => NetBSDProcessTable.c} | 109 +-- ...tBSDProcessList.h => NetBSDProcessTable.h} | 14 +- netbsd/Platform.c | 6 +- netbsd/Platform.h | 16 +- openbsd/OpenBSDMachine.c | 12 +- openbsd/OpenBSDProcess.c | 30 +- ...BSDProcessList.c => OpenBSDProcessTable.c} | 58 +- ...BSDProcessList.h => OpenBSDProcessTable.h} | 14 +- openbsd/Platform.c | 6 +- openbsd/Platform.h | 16 +- pcp-htop.5.in | 2 +- pcp/InDomTable.c | 99 +++ pcp/InDomTable.h | 34 + pcp/Instance.c | 163 +++++ pcp/Instance.h | 37 + pcp/{PCPMetric.c => Metric.c} | 52 +- pcp/{PCPMetric.h => Metric.h} | 38 +- pcp/PCPDynamicColumn.c | 268 +++++-- pcp/PCPDynamicColumn.h | 21 +- pcp/PCPDynamicMeter.c | 37 +- pcp/PCPDynamicMeter.h | 6 + pcp/PCPDynamicScreen.c | 407 +++++++++++ pcp/PCPDynamicScreen.h | 56 ++ pcp/PCPMachine.c | 105 +-- pcp/PCPMachine.h | 7 +- pcp/PCPProcess.c | 151 ++-- pcp/PCPProcess.h | 5 +- pcp/{PCPProcessList.c => PCPProcessTable.c} | 236 ++++--- pcp/{PCPProcessList.h => PCPProcessTable.h} | 16 +- pcp/Platform.c | 191 +++-- pcp/Platform.h | 22 +- pcp/ProcessField.h | 3 + pcp/screens/biosnoop | 41 ++ pcp/screens/cgroups | 45 ++ pcp/screens/cgroupsio | 49 ++ pcp/screens/cgroupsmem | 48 ++ pcp/screens/devices | 114 +++ pcp/screens/execsnoop | 37 + pcp/screens/exitsnoop | 48 ++ pcp/screens/filesystems | 50 ++ pcp/screens/opensnoop | 27 + solaris/Platform.c | 12 +- solaris/Platform.h | 20 +- solaris/SolarisMachine.c | 3 +- solaris/SolarisMachine.h | 6 +- solaris/SolarisProcess.c | 34 +- solaris/SolarisProcess.h | 2 - ...risProcessList.c => SolarisProcessTable.c} | 84 +-- ...risProcessList.h => SolarisProcessTable.h} | 16 +- unsupported/Platform.c | 4 +- unsupported/Platform.h | 19 +- unsupported/UnsupportedProcess.c | 28 +- unsupported/UnsupportedProcessList.h | 17 - ...rocessList.c => UnsupportedProcessTable.c} | 37 +- unsupported/UnsupportedProcessTable.h | 17 + zfs/ZfsArcMeter.c | 2 + zfs/ZfsCompressedArcMeter.c | 2 + 215 files changed, 7476 insertions(+), 3589 deletions(-) create mode 100644 .github/workflows/htoprc create mode 100644 DynamicScreen.c create mode 100644 DynamicScreen.h delete mode 100644 ProcessList.c delete mode 100644 ProcessList.h create mode 100644 ProcessTable.c create mode 100644 ProcessTable.h create mode 100644 Row.c create mode 100644 Row.h create mode 100644 RowField.h create mode 100644 ScreenTabsPanel.c create mode 100644 ScreenTabsPanel.h create mode 100644 Table.c create mode 100644 Table.h rename darwin/{DarwinProcessList.c => DarwinProcessTable.c} (68%) rename darwin/{DarwinProcessList.h => DarwinProcessTable.h} (52%) delete mode 100644 dragonflybsd/DragonFlyBSDProcessList.h rename dragonflybsd/{DragonFlyBSDProcessList.c => DragonFlyBSDProcessTable.c} (65%) create mode 100644 dragonflybsd/DragonFlyBSDProcessTable.h rename freebsd/{FreeBSDProcessList.c => FreeBSDProcessTable.c} (74%) rename freebsd/{FreeBSDProcessList.h => FreeBSDProcessTable.h} (52%) mode change 100644 => 100755 htop.desktop rename linux/{LinuxProcessList.c => LinuxProcessTable.c} (78%) rename linux/{LinuxProcessList.h => LinuxProcessTable.h} (62%) rename netbsd/{NetBSDProcessList.c => NetBSDProcessTable.c} (67%) rename netbsd/{NetBSDProcessList.h => NetBSDProcessTable.h} (58%) rename openbsd/{OpenBSDProcessList.c => OpenBSDProcessTable.c} (76%) rename openbsd/{OpenBSDProcessList.h => OpenBSDProcessTable.h} (50%) create mode 100644 pcp/InDomTable.c create mode 100644 pcp/InDomTable.h create mode 100644 pcp/Instance.c create mode 100644 pcp/Instance.h rename pcp/{PCPMetric.c => Metric.c} (74%) rename pcp/{PCPMetric.h => Metric.h} (89%) create mode 100644 pcp/PCPDynamicScreen.c create mode 100644 pcp/PCPDynamicScreen.h rename pcp/{PCPProcessList.c => PCPProcessTable.c} (59%) rename pcp/{PCPProcessList.h => PCPProcessTable.h} (53%) create mode 100644 pcp/screens/biosnoop create mode 100644 pcp/screens/cgroups create mode 100644 pcp/screens/cgroupsio create mode 100644 pcp/screens/cgroupsmem create mode 100644 pcp/screens/devices create mode 100644 pcp/screens/execsnoop create mode 100644 pcp/screens/exitsnoop create mode 100644 pcp/screens/filesystems create mode 100644 pcp/screens/opensnoop rename solaris/{SolarisProcessList.c => SolarisProcessTable.c} (74%) rename solaris/{SolarisProcessList.h => SolarisProcessTable.h} (64%) delete mode 100644 unsupported/UnsupportedProcessList.h rename unsupported/{UnsupportedProcessList.c => UnsupportedProcessTable.c} (59%) create mode 100644 unsupported/UnsupportedProcessTable.h diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 655853cc8..2bdf443d2 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -11,7 +11,7 @@ jobs: name: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e4ea0652..0582e054e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,13 +11,13 @@ jobs: build-ubuntu-latest-minimal-gcc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Dependencies run: sudo apt-get install --no-install-recommends libncursesw5-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors + run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors || (cat config.log; exit 1) - name: Enable compatibility modes run: | sed -i 's/#define HAVE_FSTATAT 1/#undef HAVE_FSTATAT/g' config.h @@ -31,20 +31,20 @@ jobs: build-ubuntu-latest-minimal-clang: runs-on: ubuntu-latest env: - CC: clang-15 + CC: clang-16 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: install clang repo run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add - - sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main' -y + sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main' -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-15 libncursesw5-dev + run: sudo apt-get install --no-install-recommends clang-16 libncursesw5-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors + run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Distcheck @@ -57,13 +57,13 @@ jobs: CFLAGS: -O3 -g -flto LDFLAGS: -O3 -g -flto -Wl,--as-needed steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Dependencies run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities + run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Distcheck @@ -72,20 +72,20 @@ jobs: build-ubuntu-latest-full-featured-clang: runs-on: ubuntu-latest env: - CC: clang-15 + CC: clang-16 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: install clang repo run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add - - sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main' -y + sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main' -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-15 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev + run: sudo apt-get install --no-install-recommends clang-16 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities + run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Distcheck @@ -98,13 +98,13 @@ jobs: CFLAGS: -O3 -g -flto LDFLAGS: -O3 -g -flto steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Dependencies run: sudo apt-get install --no-install-recommends libncursesw5-dev libtinfo-dev libgpm-dev libsensors4-dev libcap-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities + run: ./configure --enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Distcheck @@ -114,48 +114,84 @@ jobs: # we want PCP v5.2.3+ runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Dependencies run: sudo apt-get install --no-install-recommends libpcp3-dev libncursesw5-dev libtinfo-dev libgpm-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-pcp --enable-unicode + run: ./configure --enable-werror --enable-pcp --enable-unicode || ( cat config.log; exit 1; ) - name: Build run: make -k build-ubuntu-latest-clang-analyzer: runs-on: ubuntu-latest env: - CC: clang-15 + CC: clang-16 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: install clang repo run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add - - sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main' -y + sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main' -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-15 clang-tools-15 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev + run: sudo apt-get install --no-install-recommends clang-16 clang-tools-16 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: scan-build-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 + run: scan-build-16 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) - name: Build - run: scan-build-15 -analyze-headers --status-bugs make -j"$(nproc)" + run: scan-build-16 -analyze-headers --status-bugs make -j"$(nproc)" + + build-ubuntu-latest-clang-sanitizer: + runs-on: ubuntu-latest + env: + CC: clang-16 + CFLAGS: '-O1 -g -ftrivial-auto-var-init=pattern -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-address-use-after-return=always -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=nullability -fsanitize=implicit-conversion -fsanitize=integer -fsanitize=float-divide-by-zero -fsanitize=local-bounds' + LDFLAGS: '-ftrivial-auto-var-init=pattern -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-address-use-after-return=always -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=nullability -fsanitize=implicit-conversion -fsanitize=integer -fsanitize=float-divide-by-zero -fsanitize=local-bounds' + ASAN_OPTIONS: strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 + UBSAN_OPTIONS: print_stacktrace=1:print_summary=1:halt_on_error=1 + TERM: xterm-color + HTOPRC: .github/workflows/htoprc + steps: + - uses: actions/checkout@v4 + - name: install clang repo + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add - + sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main' -y + sudo apt-get update -q + - name: Install LLVM Toolchain + run: sudo apt-get install --no-install-recommends clang-16 libclang-rt-16-dev llvm-16 + - name: Install Dependencies + run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev + - name: Bootstrap + run: ./autogen.sh + - name: Configure + run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) + - name: Build + run: make -k + - name: Run sanitized htop (1) + run: ./htop -h + - name: Run sanitized htop (2) + run: ./htop -n 5 + - name: Run sanitized htop (3) + run: ./htop -n 5 -t + - name: Run sanitized htop (4) + run: ./htop -d 1 -n 50 build-macos-latest-clang: runs-on: macOS-latest env: CC: clang steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Dependencies run: brew install automake pkg-config - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror + run: ./configure --enable-werror || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Distcheck @@ -164,6 +200,6 @@ jobs: whitespace_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: check-whitespaces run: git diff-tree --check $(git hash-object -t tree /dev/null) HEAD diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 17356b67e..47383a78a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,10 +26,10 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: cpp @@ -46,4 +46,4 @@ jobs: run: make - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/htoprc b/.github/workflows/htoprc new file mode 100644 index 000000000..6c058ecbb --- /dev/null +++ b/.github/workflows/htoprc @@ -0,0 +1,71 @@ +# Beware! This file is rewritten by htop when settings are changed in the interface. +# The parser is also very primitive, and not human-friendly. +htop_version=3.3.0-dev +config_reader_min_version=3 +fields=0 48 17 18 38 39 40 2 46 47 49 1 +hide_kernel_threads=1 +hide_userland_threads=0 +hide_running_in_container=0 +shadow_other_users=1 +show_thread_names=1 +show_program_path=1 +highlight_base_name=1 +highlight_deleted_exe=1 +shadow_distribution_path_prefix=1 +highlight_megabytes=1 +highlight_threads=1 +highlight_changes=1 +highlight_changes_delay_secs=5 +find_comm_in_cmdline=1 +strip_exe_from_cmdline=1 +show_merged_command=1 +header_margin=1 +screen_tabs=1 +detailed_cpu_time=1 +cpu_count_from_one=0 +show_cpu_usage=1 +show_cpu_frequency=1 +show_cpu_temperature=1 +degree_fahrenheit=0 +update_process_names=1 +account_guest_in_cpu_meter=1 +color_scheme=0 +enable_mouse=1 +delay=15 +hide_function_bar=0 +header_layout=two_50_50 +column_meters_0=LeftCPUs4 CPU Memory Swap MemorySwap Zram Clock Date DateTime ZFSARC ZFSCARC SELinux SystemdUser FileDescriptors +column_meter_modes_0=1 1 1 1 1 1 2 2 2 2 2 2 2 2 +column_meters_1=RightCPUs4 Tasks LoadAverage Load Uptime Battery System HugePages Hostname Blank PressureStallCPUSome PressureStallIOSome PressureStallIOFull PressureStallIRQFull PressureStallMemorySome PressureStallMemoryFull DiskIO NetworkIO +column_meter_modes_1=1 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2 +tree_view=0 +sort_key=46 +tree_sort_key=0 +sort_direction=-1 +tree_sort_direction=1 +tree_view_always_by_pid=0 +all_branches_collapsed=0 +screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command +.sort_key=PERCENT_CPU +.tree_sort_key=PID +.tree_view_always_by_pid=0 +.tree_view=0 +.sort_direction=-1 +.tree_sort_direction=1 +.all_branches_collapsed=0 +screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command +.sort_key=IO_RATE +.tree_sort_key=PID +.tree_view_always_by_pid=0 +.tree_view=0 +.sort_direction=-1 +.tree_sort_direction=1 +.all_branches_collapsed=0 +screen:Dump=PID SCHEDULERPOLICY SYSCR CGROUP CCGROUP OOM IO_PRIORITY PERCENT_CPU_DELAY CTXT SECATTR CWD AUTOGROUP_ID Command +.sort_key=PID +.tree_sort_key=PID +.tree_view_always_by_pid=0 +.tree_view=0 +.sort_direction=1 +.tree_sort_direction=1 +.all_branches_collapsed=0 diff --git a/Action.c b/Action.c index 62308da72..4049a952f 100644 --- a/Action.c +++ b/Action.c @@ -9,9 +9,11 @@ in the source distribution for its full text. #include "Action.h" +#include #include #include #include +#include #include "CRT.h" #include "CategoriesPanel.h" @@ -27,13 +29,16 @@ in the source distribution for its full text. #include "MainPanel.h" #include "OpenFilesScreen.h" #include "Process.h" -#include "ProcessList.h" #include "ProcessLocksScreen.h" #include "ProvideCurses.h" +#include "Row.h" +#include "RowField.h" #include "Scheduling.h" #include "ScreenManager.h" #include "SignalsPanel.h" +#include "Table.h" #include "TraceScreen.h" +#include "UsersTable.h" #include "Vector.h" #include "XUtils.h" @@ -56,22 +61,22 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool follow) { Panel* panelFocus; int ch; bool unfollow = false; - int pid = follow ? MainPanel_selectedPid(mainPanel) : -1; - if (follow && host->pl->following == -1) { - host->pl->following = pid; + int row = follow ? MainPanel_selectedRow(mainPanel) : -1; + if (follow && host->activeTable->following == -1) { + host->activeTable->following = row; unfollow = true; } ScreenManager_run(scr, &panelFocus, &ch, NULL); if (unfollow) { - host->pl->following = -1; + host->activeTable->following = -1; } ScreenManager_delete(scr); Panel_move((Panel*)mainPanel, 0, y); Panel_resize((Panel*)mainPanel, COLS, LINES - y - 1); if (panelFocus == list && ch == 13) { if (follow) { - const Process* selected = (const Process*)Panel_getSelected((Panel*)mainPanel); - if (selected && selected->pid == pid) + const Row* selected = (const Row*)Panel_getSelected((Panel*)mainPanel); + if (selected && selected->id == row) return Panel_getSelected(list); beep(); @@ -99,7 +104,7 @@ static void Action_runSetup(State* st) { static bool changePriority(MainPanel* panel, int delta) { bool anyTagged; - bool ok = MainPanel_foreachProcess(panel, Process_changePriorityBy, (Arg) { .i = delta }, &anyTagged); + bool ok = MainPanel_foreachRow(panel, Process_rowChangePriorityBy, (Arg) { .i = delta }, &anyTagged); if (!ok) beep(); return anyTagged; @@ -121,36 +126,36 @@ bool Action_setUserOnly(const char* userName, uid_t* userId) { return false; } -static void tagAllChildren(Panel* panel, Process* parent) { +static void tagAllChildren(Panel* panel, Row* parent) { parent->tag = true; - pid_t ppid = parent->pid; + int parent_id = parent->id; for (int i = 0; i < Panel_size(panel); i++) { - Process* p = (Process*) Panel_get(panel, i); - if (!p->tag && Process_isChildOf(p, ppid)) { - tagAllChildren(panel, p); + Row* row = (Row*) Panel_get(panel, i); + if (!row->tag && Row_isChildOf(row, parent_id)) { + tagAllChildren(panel, row); } } } static bool expandCollapse(Panel* panel) { - Process* p = (Process*) Panel_getSelected(panel); - if (!p) + Row* row = (Row*) Panel_getSelected(panel); + if (!row) return false; - p->showChildren = !p->showChildren; + row->showChildren = !row->showChildren; return true; } static bool collapseIntoParent(Panel* panel) { - const Process* p = (Process*) Panel_getSelected(panel); - if (!p) + const Row* r = (Row*) Panel_getSelected(panel); + if (!r) return false; - pid_t ppid = Process_getParentPid(p); + int parent_id = Row_getGroupOrParent(r); for (int i = 0; i < Panel_size(panel); i++) { - Process* q = (Process*) Panel_get(panel, i); - if (q->pid == ppid) { - q->showChildren = false; + Row* row = (Row*) Panel_get(panel, i); + if (row->id == parent_id) { + row->showChildren = false; Panel_setSelected(panel, i); return true; } @@ -159,23 +164,34 @@ static bool collapseIntoParent(Panel* panel) { } Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) { - ScreenSettings_setSortKey(settings->ss, sortKey); + ScreenSettings_setSortKey(settings->ss, (RowField) sortKey); return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_UPDATE_PANELHDR | HTOP_KEEP_FOLLOWING; } // ---------------------------------------- +static bool Action_writeableProcess(State* st) { + const Settings* settings = st->host->settings; + bool readonly = Settings_isReadonly() || settings->ss->dynamic; + return !readonly; +} + +static bool Action_readableProcess(State* st) { + const Settings* settings = st->host->settings; + return !settings->ss->dynamic; +} + static Htop_Reaction actionSetSortColumn(State* st) { Htop_Reaction reaction = HTOP_OK; Panel* sortPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Sort ", "Cancel ")); Panel_setHeader(sortPanel, "Sort by"); Machine* host = st->host; Settings* settings = host->settings; - const ProcessField* fields = settings->ss->fields; + const RowField* fields = settings->ss->fields; Hashtable* dynamicColumns = settings->dynamicColumns; for (int i = 0; fields[i]; i++) { char* name = NULL; - if (fields[i] >= LAST_PROCESSFIELD) { + if (fields[i] >= ROW_DYNAMIC_FIELDS) { DynamicColumn* column = Hashtable_get(dynamicColumns, fields[i]); if (!column) continue; @@ -195,7 +211,7 @@ static Htop_Reaction actionSetSortColumn(State* st) { } Object_delete(sortPanel); - host->pl->needsSort = true; + host->activeTable->needsSort = true; return reaction | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } @@ -221,6 +237,8 @@ static Htop_Reaction actionToggleKernelThreads(State* st) { settings->hideKernelThreads = !settings->hideKernelThreads; settings->lastUpdate++; + Machine_scanTables(st->host); // needed to not have a visible delay showing wrong data + return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING; } @@ -229,6 +247,8 @@ static Htop_Reaction actionToggleUserlandThreads(State* st) { settings->hideUserlandThreads = !settings->hideUserlandThreads; settings->lastUpdate++; + Machine_scanTables(st->host); // needed to not have a visible delay showing wrong data + return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING; } @@ -262,9 +282,9 @@ static Htop_Reaction actionToggleTreeView(State* st) { ss->treeView = !ss->treeView; if (!ss->allBranchesCollapsed) - ProcessList_expandTree(host->pl); + Table_expandTree(host->activeTable); - host->pl->needsSort = true; + host->activeTable->needsSort = true; return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } @@ -282,9 +302,9 @@ static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) { } ss->allBranchesCollapsed = !ss->allBranchesCollapsed; if (ss->allBranchesCollapsed) - ProcessList_collapseAllBranches(host->pl); + Table_collapseAllBranches(host->activeTable); else - ProcessList_expandTree(host->pl); + Table_expandTree(host->activeTable); return HTOP_REFRESH | HTOP_SAVE_SETTINGS; } @@ -292,7 +312,7 @@ static Htop_Reaction actionIncFilter(State* st) { Machine* host = st->host; IncSet* inc = (st->mainPanel)->inc; IncSet_activate(inc, INC_FILTER, (Panel*)st->mainPanel); - host->pl->incFilter = IncSet_filter(inc); + host->activeTable->incFilter = IncSet_filter(inc); return HTOP_REFRESH | HTOP_KEEP_FOLLOWING; } @@ -303,7 +323,7 @@ static Htop_Reaction actionIncSearch(State* st) { } static Htop_Reaction actionHigherPriority(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; bool changed = changePriority(st->mainPanel, -1); @@ -311,7 +331,7 @@ static Htop_Reaction actionHigherPriority(State* st) { } static Htop_Reaction actionLowerPriority(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; bool changed = changePriority(st->mainPanel, 1); @@ -321,7 +341,7 @@ static Htop_Reaction actionLowerPriority(State* st) { static Htop_Reaction actionInvertSortOrder(State* st) { Machine* host = st->host; ScreenSettings_invertSortOrder(host->settings->ss); - host->pl->needsSort = true; + host->activeTable->needsSort = true; return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_UPDATE_PANELHDR; } @@ -345,13 +365,27 @@ static Htop_Reaction actionExpandCollapseOrSortColumn(State* st) { return st->host->settings->ss->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st); } +static inline void setActiveScreen(Settings* settings, State* st, unsigned int ssIdx) { + assert(settings->ssIndex == ssIdx); + Machine* host = st->host; + + settings->ss = settings->screens[ssIdx]; + if (!settings->ss->table) + settings->ss->table = host->processTable; + host->activeTable = settings->ss->table; + + // set correct functionBar - readonly if requested, and/or with non-process screens + bool readonly = Settings_isReadonly() || (host->activeTable != host->processTable); + MainPanel_setFunctionBar(st->mainPanel, readonly); +} + static Htop_Reaction actionNextScreen(State* st) { Settings* settings = st->host->settings; settings->ssIndex++; if (settings->ssIndex == settings->nScreens) { settings->ssIndex = 0; } - settings->ss = settings->screens[settings->ssIndex]; + setActiveScreen(settings, st, settings->ssIndex); return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR; } @@ -362,21 +396,22 @@ static Htop_Reaction actionPrevScreen(State* st) { } else { settings->ssIndex--; } - settings->ss = settings->screens[settings->ssIndex]; + setActiveScreen(settings, st, settings->ssIndex); return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR; } -Htop_Reaction Action_setScreenTab(Settings* settings, int x) { +Htop_Reaction Action_setScreenTab(State* st, int x) { + Settings* settings = st->host->settings; int s = 2; for (unsigned int i = 0; i < settings->nScreens; i++) { if (x < s) { return 0; } - const char* name = settings->screens[i]->name; - int len = strlen(name); + const char* tab = settings->screens[i]->heading; + int len = strlen(tab); if (x <= s + len + 1) { settings->ssIndex = i; - settings->ss = settings->screens[i]; + setActiveScreen(settings, st, i); return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR; } s += len + 3; @@ -389,7 +424,7 @@ static Htop_Reaction actionQuit(ATTR_UNUSED State* st) { } static Htop_Reaction actionSetAffinity(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; Machine* host = st->host; @@ -397,11 +432,11 @@ static Htop_Reaction actionSetAffinity(State* st) { return HTOP_OK; #if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)) - const Process* p = (const Process*) Panel_getSelected((Panel*)st->mainPanel); - if (!p) + const Row* row = (const Row*) Panel_getSelected((Panel*)st->mainPanel); + if (!row) return HTOP_OK; - Affinity* affinity1 = Affinity_get(p, host); + Affinity* affinity1 = Affinity_rowGet(row, host); if (!affinity1) return HTOP_OK; @@ -412,7 +447,7 @@ static Htop_Reaction actionSetAffinity(State* st) { const void* set = Action_pickFromVector(st, affinityPanel, width, true); if (set) { Affinity* affinity2 = AffinityPanel_getAffinity(affinityPanel, host); - bool ok = MainPanel_foreachProcess(st->mainPanel, Affinity_set, (Arg) { .v = affinity2 }, NULL); + bool ok = MainPanel_foreachRow(st->mainPanel, Affinity_rowSet, (Arg) { .v = affinity2 }, NULL); if (!ok) beep(); Affinity_delete(affinity2); @@ -422,12 +457,11 @@ static Htop_Reaction actionSetAffinity(State* st) { #else return HTOP_OK; #endif - } #ifdef SCHEDULER_SUPPORT static Htop_Reaction actionSetSchedPolicy(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_KEEP_FOLLOWING; static int preSelectedPolicy = SCHEDULINGPANEL_INITSELECTEDPOLICY; @@ -459,7 +493,7 @@ static Htop_Reaction actionSetSchedPolicy(State* st) { SchedulingArg v = { .policy = preSelectedPolicy, .priority = preSelectedPriority }; - bool ok = MainPanel_foreachProcess(st->mainPanel, Scheduling_setPolicy, (Arg) { .v = &v }, NULL); + bool ok = MainPanel_foreachRow(st->mainPanel, Scheduling_rowSetPolicy, (Arg) { .v = &v }, NULL); if (!ok) beep(); } @@ -471,7 +505,7 @@ static Htop_Reaction actionSetSchedPolicy(State* st) { #endif /* SCHEDULER_SUPPORT */ static Htop_Reaction actionKill(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; static int preSelectedSignal = SIGNALSPANEL_INITSELECTEDSIGNAL; @@ -483,10 +517,14 @@ static Htop_Reaction actionKill(State* st) { Panel_setHeader((Panel*)st->mainPanel, "Sending..."); Panel_draw((Panel*)st->mainPanel, false, true, true, State_hideFunctionBar(st)); refresh(); - MainPanel_foreachProcess(st->mainPanel, Process_sendSignal, (Arg) { .i = sgn->key }, NULL); + bool ok = MainPanel_foreachRow(st->mainPanel, Process_rowSendSignal, (Arg) { .i = sgn->key }, NULL); + if (!ok) { + beep(); + } napms(500); } Panel_delete((Object*)signalsPanel); + return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } @@ -511,7 +549,7 @@ static Htop_Reaction actionFilterByUser(State* st) { } Htop_Reaction Action_follow(State* st) { - st->host->pl->following = MainPanel_selectedPid(st->mainPanel); + st->host->activeTable->following = MainPanel_selectedRow(st->mainPanel); Panel_setSelectionColor((Panel*)st->mainPanel, PANEL_SELECTION_FOLLOW); return HTOP_KEEP_FOLLOWING; } @@ -522,13 +560,15 @@ static Htop_Reaction actionSetup(State* st) { } static Htop_Reaction actionLsof(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + OpenFilesScreen* ofs = OpenFilesScreen_new(p); InfoScreen_run((InfoScreen*)ofs); OpenFilesScreen_delete((Object*)ofs); @@ -538,9 +578,15 @@ static Htop_Reaction actionLsof(State* st) { } static Htop_Reaction actionShowLocks(State* st) { + if (!Action_readableProcess(st)) + return HTOP_OK; + const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; + + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + ProcessLocksScreen* pls = ProcessLocksScreen_new(p); InfoScreen_run((InfoScreen*)pls); ProcessLocksScreen_delete((Object*)pls); @@ -550,13 +596,15 @@ static Htop_Reaction actionShowLocks(State* st) { } static Htop_Reaction actionStrace(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + TraceScreen* ts = TraceScreen_new(p); bool ok = TraceScreen_forkTracer(ts); if (ok) { @@ -569,18 +617,19 @@ static Htop_Reaction actionStrace(State* st) { } static Htop_Reaction actionTag(State* st) { - Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); - if (!p) + Row* r = (Row*) Panel_getSelected((Panel*)st->mainPanel); + if (!r) return HTOP_OK; - Process_toggleTag(p); + Row_toggleTag(r); Panel_onKey((Panel*)st->mainPanel, KEY_DOWN); return HTOP_OK; } static Htop_Reaction actionRedraw(ATTR_UNUSED State* st) { clear(); - return HTOP_REFRESH | HTOP_REDRAW_BAR; + // HTOP_RECALCULATE here to make Ctrl-L also refresh the data and not only redraw + return HTOP_RECALCULATE | HTOP_REFRESH | HTOP_REDRAW_BAR; } static Htop_Reaction actionTogglePauseUpdate(State* st) { @@ -693,9 +742,9 @@ static Htop_Reaction actionHelp(State* st) { mvaddstr(line++, 0, "Memory bar: "); addattrstr(CRT_colors[BAR_BORDER], "["); addbartext(CRT_colors[MEMORY_USED], "", "used"); + addbartext(CRT_colors[MEMORY_SHARED], "/", "shared"); addbartext(CRT_colors[MEMORY_COMPRESSED], "/", "compressed"); 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"); @@ -783,26 +832,31 @@ static Htop_Reaction actionHelp(State* st) { static Htop_Reaction actionUntagAll(State* st) { for (int i = 0; i < Panel_size((Panel*)st->mainPanel); i++) { - Process* p = (Process*) Panel_get((Panel*)st->mainPanel, i); - p->tag = false; + Row* row = (Row*) Panel_get((Panel*)st->mainPanel, i); + row->tag = false; } return HTOP_REFRESH; } static Htop_Reaction actionTagAllChildren(State* st) { - Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); - if (!p) + Row* row = (Row*) Panel_getSelected((Panel*)st->mainPanel); + if (!row) return HTOP_OK; - tagAllChildren((Panel*)st->mainPanel, p); + tagAllChildren((Panel*)st->mainPanel, row); return HTOP_OK; } static Htop_Reaction actionShowEnvScreen(State* st) { + if (!Action_readableProcess(st)) + return HTOP_OK; + Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + EnvScreen* es = EnvScreen_new(p); InfoScreen_run((InfoScreen*)es); EnvScreen_delete((Object*)es); @@ -812,10 +866,15 @@ static Htop_Reaction actionShowEnvScreen(State* st) { } static Htop_Reaction actionShowCommandScreen(State* st) { + if (!Action_readableProcess(st)) + return HTOP_OK; + Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + CommandScreen* cmdScr = CommandScreen_new(p); InfoScreen_run((InfoScreen*)cmdScr); CommandScreen_delete((Object*)cmdScr); diff --git a/Action.h b/Action.h index 3540e93e3..caade03cd 100644 --- a/Action.h +++ b/Action.h @@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include @@ -18,7 +16,6 @@ in the source distribution for its full text. #include "Panel.h" #include "Process.h" #include "Settings.h" -#include "UsersTable.h" typedef enum { @@ -57,7 +54,7 @@ bool Action_setUserOnly(const char* userName, uid_t* userId); Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey); -Htop_Reaction Action_setScreenTab(Settings* settings, int x); +Htop_Reaction Action_setScreenTab(State* st, int x); Htop_Reaction Action_follow(State* st); diff --git a/Affinity.c b/Affinity.c index f7c597bfc..fab239eb3 100644 --- a/Affinity.c +++ b/Affinity.c @@ -10,8 +10,10 @@ in the source distribution for its full text. #include "Affinity.h" +#include #include +#include "Process.h" #include "XUtils.h" #if defined(HAVE_LIBHWLOC) @@ -49,12 +51,11 @@ void Affinity_add(Affinity* this, unsigned int id) { this->used++; } - #if defined(HAVE_LIBHWLOC) -Affinity* Affinity_get(const Process* proc, Machine* host) { +static Affinity* Affinity_get(const Process* p, Machine* host) { hwloc_cpuset_t cpuset = hwloc_bitmap_alloc(); - bool ok = (hwloc_get_proc_cpubind(host->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0); + bool ok = (hwloc_get_proc_cpubind(host->topology, Process_getPid(p), cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0); Affinity* affinity = NULL; if (ok) { affinity = Affinity_new(host); @@ -73,22 +74,22 @@ Affinity* Affinity_get(const Process* proc, Machine* host) { return affinity; } -bool Affinity_set(Process* proc, Arg arg) { +static bool Affinity_set(Process* p, Arg arg) { Affinity* this = arg.v; hwloc_cpuset_t cpuset = hwloc_bitmap_alloc(); for (unsigned int i = 0; i < this->used; i++) { hwloc_bitmap_set(cpuset, this->cpus[i]); } - bool ok = (hwloc_set_proc_cpubind(this->host->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0); + bool ok = (hwloc_set_proc_cpubind(this->host->topology, Process_getPid(p), cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0); hwloc_bitmap_free(cpuset); return ok; } #elif defined(HAVE_AFFINITY) -Affinity* Affinity_get(const Process* proc, Machine* host) { +static Affinity* Affinity_get(const Process* p, Machine* host) { cpu_set_t cpuset; - bool ok = (sched_getaffinity(proc->pid, sizeof(cpu_set_t), &cpuset) == 0); + bool ok = (sched_getaffinity(Process_getPid(p), sizeof(cpu_set_t), &cpuset) == 0); if (!ok) return NULL; @@ -101,15 +102,31 @@ Affinity* Affinity_get(const Process* proc, Machine* host) { return affinity; } -bool Affinity_set(Process* proc, Arg arg) { +static bool Affinity_set(Process* p, Arg arg) { Affinity* this = arg.v; cpu_set_t cpuset; CPU_ZERO(&cpuset); for (unsigned int i = 0; i < this->used; i++) { CPU_SET(this->cpus[i], &cpuset); } - bool ok = (sched_setaffinity(proc->pid, sizeof(unsigned long), &cpuset) == 0); + bool ok = (sched_setaffinity(Process_getPid(p), sizeof(unsigned long), &cpuset) == 0); return ok; } #endif + +#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY) + +bool Affinity_rowSet(Row* row, Arg arg) { + Process* p = (Process*) row; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return Affinity_set(p, arg); +} + +Affinity* Affinity_rowGet(const Row* row, Machine* host) { + const Process* p = (const Process*) row; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return Affinity_get(p, host); +} + +#endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */ diff --git a/Affinity.h b/Affinity.h index 58d9bd733..c21890627 100644 --- a/Affinity.h +++ b/Affinity.h @@ -8,15 +8,13 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include "Machine.h" #if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY) #include #include "Object.h" -#include "Process.h" +#include "Row.h" #endif @@ -40,9 +38,9 @@ void Affinity_add(Affinity* this, unsigned int id); #if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY) -Affinity* Affinity_get(const Process* proc, Machine* host); +Affinity* Affinity_rowGet(const Row* row, Machine* host); -bool Affinity_set(Process* proc, Arg arg); +bool Affinity_rowSet(Row* row, Arg arg); #endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */ diff --git a/AffinityPanel.c b/AffinityPanel.c index 1214a84f4..f4009080b 100644 --- a/AffinityPanel.c +++ b/AffinityPanel.c @@ -202,57 +202,57 @@ static HandlerResult AffinityPanel_eventHandler(Panel* super, int ch) { bool keepSelected = true; switch (ch) { - case KEY_MOUSE: - case KEY_RECLICK: - case ' ': - #ifdef HAVE_LIBHWLOC - if (selected->value == 2) { - /* Item was selected, so remove this mask from the top cpuset. */ - hwloc_bitmap_andnot(this->workCpuset, this->workCpuset, selected->cpuset); - selected->value = 0; - } else { - /* Item was not or only partial selected, so set all bits from this object - in the top cpuset. */ - hwloc_bitmap_or(this->workCpuset, this->workCpuset, selected->cpuset); - selected->value = 2; - } - #else - selected->value = selected->value ? 0 : 2; /* toggle between 0 and 2 */ - #endif + case KEY_MOUSE: + case KEY_RECLICK: + case ' ': + #ifdef HAVE_LIBHWLOC + if (selected->value == 2) { + /* Item was selected, so remove this mask from the top cpuset. */ + hwloc_bitmap_andnot(this->workCpuset, this->workCpuset, selected->cpuset); + selected->value = 0; + } else { + /* Item was not or only partial selected, so set all bits from this object + in the top cpuset. */ + hwloc_bitmap_or(this->workCpuset, this->workCpuset, selected->cpuset); + selected->value = 2; + } + #else + selected->value = selected->value ? 0 : 2; /* toggle between 0 and 2 */ + #endif - result = HANDLED; - break; + result = HANDLED; + break; - #ifdef HAVE_LIBHWLOC +#ifdef HAVE_LIBHWLOC - case KEY_F(1): - hwloc_bitmap_copy(this->workCpuset, this->allCpuset); - result = HANDLED; - break; + case KEY_F(1): + hwloc_bitmap_copy(this->workCpuset, this->allCpuset); + result = HANDLED; + break; - case KEY_F(2): - this->topoView = !this->topoView; - keepSelected = false; + case KEY_F(2): + this->topoView = !this->topoView; + keepSelected = false; - result = HANDLED; - break; + result = HANDLED; + break; - case KEY_F(3): - case '-': - case '+': - if (selected->sub_tree) - selected->sub_tree = 1 + !(selected->sub_tree - 1); /* toggle between 1 and 2 */ + case KEY_F(3): + case '-': + case '+': + if (selected->sub_tree) + selected->sub_tree = 1 + !(selected->sub_tree - 1); /* toggle between 1 and 2 */ - result = HANDLED; - break; + result = HANDLED; + break; - #endif +#endif - case 0x0a: - case 0x0d: - case KEY_ENTER: - result = BREAK_LOOP; - break; + case 0x0a: + case 0x0d: + case KEY_ENTER: + result = BREAK_LOOP; + break; } if (HANDLED == result) diff --git a/AvailableColumnsPanel.c b/AvailableColumnsPanel.c index b8c09c74c..545ef7d78 100644 --- a/AvailableColumnsPanel.c +++ b/AvailableColumnsPanel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "AvailableColumnsPanel.h" #include @@ -18,8 +20,10 @@ in the source distribution for its full text. #include "Hashtable.h" #include "ListItem.h" #include "Object.h" +#include "Platform.h" #include "Process.h" #include "ProvideCurses.h" +#include "RowField.h" #include "XUtils.h" @@ -34,8 +38,8 @@ static void AvailableColumnsPanel_delete(Object* object) { static void AvailableColumnsPanel_insert(AvailableColumnsPanel* this, int at, int key) { const char* name; - if (key >= LAST_PROCESSFIELD) - name = DynamicColumn_init(key); + if (key >= ROW_DYNAMIC_FIELDS) + name = DynamicColumn_name(key); else name = Process_fields[key].name; Panel_insert(this->columns, at, (Object*) ListItem_new(name, key)); @@ -48,8 +52,7 @@ static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) { switch (ch) { case 13: case KEY_ENTER: - case KEY_F(5): - { + case KEY_F(5): { const ListItem* selected = (ListItem*) Panel_getSelected(super); if (!selected) break; @@ -62,11 +65,9 @@ static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) { break; } default: - { if (0 < ch && ch < 255 && isgraph((unsigned char)ch)) result = Panel_selectByTyping(super, ch); break; - } } return result; } @@ -81,42 +82,61 @@ const PanelClass AvailableColumnsPanel_class = { static void AvailableColumnsPanel_addDynamicColumn(ht_key_t key, void* value, void* data) { const DynamicColumn* column = (const DynamicColumn*) value; - Panel* super = (Panel*) data; - const char* title = column->caption ? column->caption : column->heading; - if (!title) - title = column->name; // fallback to the only mandatory field + if (column->table) /* DynamicScreen, handled differently */ + return; + AvailableColumnsPanel* this = (AvailableColumnsPanel*) data; + const char* title = column->heading ? column->heading : column->name; + const char* text = column->description ? column->description : column->caption; char description[256]; - xSnprintf(description, sizeof(description), "%s - %s", title, column->description); - Panel_add(super, (Object*) ListItem_new(description, key)); + if (text) + xSnprintf(description, sizeof(description), "%s - %s", title, text); + else + xSnprintf(description, sizeof(description), "%s", title); + Panel_add(&this->super, (Object*) ListItem_new(description, key)); } // Handle DynamicColumns entries in the AvailableColumnsPanel -static void AvailableColumnsPanel_addDynamicColumns(Panel* super, Hashtable* dynamicColumns) { +static void AvailableColumnsPanel_addDynamicColumns(AvailableColumnsPanel* this, Hashtable* dynamicColumns) { assert(dynamicColumns); - Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, super); + Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, this); } // Handle remaining Platform Meter entries in the AvailableColumnsPanel -static void AvailableColumnsPanel_addPlatformColumn(Panel* super) { +static void AvailableColumnsPanel_addPlatformColumns(AvailableColumnsPanel* this) { for (int i = 1; i < LAST_PROCESSFIELD; i++) { if (i != COMM && Process_fields[i].description) { char description[256]; xSnprintf(description, sizeof(description), "%s - %s", Process_fields[i].name, Process_fields[i].description); - Panel_add(super, (Object*) ListItem_new(description, i)); + Panel_add(&this->super, (Object*) ListItem_new(description, i)); } } } +// Handle DynamicColumns entries associated with DynamicScreens +static void AvailableColumnsPanel_addDynamicScreens(AvailableColumnsPanel* this, const char* screen) { + Platform_addDynamicScreenAvailableColumns(&this->super, screen); +} + +void AvailableColumnsPanel_fill(AvailableColumnsPanel* this, const char* dynamicScreen, Hashtable* dynamicColumns) { + Panel* super = (Panel*) this; + Panel_prune(super); + if (dynamicScreen) { + AvailableColumnsPanel_addDynamicScreens(this, dynamicScreen); + } else { + AvailableColumnsPanel_addPlatformColumns(this); + AvailableColumnsPanel_addDynamicColumns(this, dynamicColumns); + } +} + AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns) { AvailableColumnsPanel* this = AllocThis(AvailableColumnsPanel); Panel* super = (Panel*) this; FunctionBar* fuBar = FunctionBar_new(AvailableColumnsFunctions, NULL, NULL); Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); - Panel_setHeader(super, "Available Columns"); - AvailableColumnsPanel_addPlatformColumn(super); - AvailableColumnsPanel_addDynamicColumns(super, dynamicColumns); this->columns = columns; + AvailableColumnsPanel_fill(this, NULL, dynamicColumns); + return this; } diff --git a/AvailableColumnsPanel.h b/AvailableColumnsPanel.h index aca59060d..0d8709fe0 100644 --- a/AvailableColumnsPanel.h +++ b/AvailableColumnsPanel.h @@ -20,4 +20,6 @@ extern const PanelClass AvailableColumnsPanel_class; AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns); +void AvailableColumnsPanel_fill(AvailableColumnsPanel* this, const char* dynamicScreen, Hashtable* dynamicColumns); + #endif diff --git a/AvailableMetersPanel.c b/AvailableMetersPanel.c index de1dd2114..9a1d367cc 100644 --- a/AvailableMetersPanel.c +++ b/AvailableMetersPanel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "AvailableMetersPanel.h" #include @@ -23,6 +25,7 @@ in the source distribution for its full text. #include "Object.h" #include "Platform.h" #include "ProvideCurses.h" +#include "Settings.h" #include "XUtils.h" @@ -58,25 +61,22 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) { case KEY_F(5): case 'l': case 'L': - { AvailableMetersPanel_addMeter(header, this->meterPanels[0], Platform_meterTypes[type], param, 0); result = HANDLED; update = true; break; - } case 0x0a: case 0x0d: case KEY_ENTER: case KEY_F(6): case 'r': case 'R': - { AvailableMetersPanel_addMeter(header, this->meterPanels[this->columns - 1], Platform_meterTypes[type], param, this->columns - 1); result = (KEY_LEFT << 16) | SYNTH_KEY; update = true; break; - } } + if (update) { Settings* settings = this->host->settings; settings->changed = true; @@ -86,6 +86,7 @@ static HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) { Header_draw(header); ScreenManager_resize(this->scr); } + return result; } diff --git a/BatteryMeter.c b/BatteryMeter.c index 33d17b734..dad7754ac 100644 --- a/BatteryMeter.c +++ b/BatteryMeter.c @@ -7,11 +7,14 @@ in the source distribution for its full text. This meter written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com). */ +#include "config.h" // IWYU pragma: keep + #include "BatteryMeter.h" #include #include "CRT.h" +#include "Macros.h" #include "Object.h" #include "Platform.h" #include "XUtils.h" @@ -27,7 +30,7 @@ static void BatteryMeter_updateValues(Meter* this) { Platform_getBattery(&percent, &isOnAC); - if (isnan(percent)) { + if (!isNonnegative(percent)) { this->values[0] = NAN; xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "N/A"); return; @@ -37,16 +40,16 @@ static void BatteryMeter_updateValues(Meter* this) { const char* text; switch (isOnAC) { - case AC_PRESENT: - text = this->mode == TEXT_METERMODE ? " (Running on A/C)" : "(A/C)"; - break; - case AC_ABSENT: - text = this->mode == TEXT_METERMODE ? " (Running on battery)" : "(bat)"; - break; - case AC_ERROR: - default: - text = ""; - break; + case AC_PRESENT: + text = this->mode == TEXT_METERMODE ? " (Running on A/C)" : "(A/C)"; + break; + case AC_ABSENT: + text = this->mode == TEXT_METERMODE ? " (Running on battery)" : "(bat)"; + break; + case AC_ERROR: + default: + text = ""; + break; } xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%%s", percent, text); diff --git a/CPUMeter.c b/CPUMeter.c index a946aa7d7..afcddeb1a 100644 --- a/CPUMeter.c +++ b/CPUMeter.c @@ -9,14 +9,16 @@ in the source distribution for its full text. #include "CPUMeter.h" -#include +#include +#include #include #include #include "CRT.h" +#include "Machine.h" +#include "Macros.h" #include "Object.h" #include "Platform.h" -#include "ProcessList.h" #include "RichString.h" #include "Settings.h" #include "XUtils.h" @@ -71,7 +73,7 @@ static void CPUMeter_updateValues(Meter* this) { } double percent = Platform_setCPUValues(this, cpu); - if (isnan(percent)) { + if (!isNonnegative(percent)) { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "offline"); return; } @@ -86,17 +88,17 @@ static void CPUMeter_updateValues(Meter* this) { if (settings->showCPUFrequency) { double cpuFrequency = this->values[CPU_METER_FREQUENCY]; - if (isnan(cpuFrequency)) { - xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A"); - } else { + if (isNonnegative(cpuFrequency)) { xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "%4uMHz", (unsigned)cpuFrequency); + } else { + xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A"); } } #ifdef BUILD_WITH_CPU_TEMP if (settings->showCPUTemperature) { double cpuTemperature = this->values[CPU_METER_TEMPERATURE]; - if (isnan(cpuTemperature)) + if (isNaN(cpuTemperature)) xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A"); else if (settings->degreeFahrenheit) xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%3d%sF", (int)(cpuTemperature * 9 / 5 + 32), CRT_degreeSign); @@ -146,12 +148,12 @@ static void CPUMeter_display(const Object* cast, RichString* out) { len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_SOFTIRQ]); RichString_appendAscii(out, CRT_colors[METER_TEXT], "si:"); RichString_appendnAscii(out, CRT_colors[CPU_SOFTIRQ], buffer, len); - if (!isnan(this->values[CPU_METER_STEAL])) { + if (isNonnegative(this->values[CPU_METER_STEAL])) { len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_STEAL]); RichString_appendAscii(out, CRT_colors[METER_TEXT], "st:"); RichString_appendnAscii(out, CRT_colors[CPU_STEAL], buffer, len); } - if (!isnan(this->values[CPU_METER_GUEST])) { + if (isNonnegative(this->values[CPU_METER_GUEST])) { len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_GUEST]); RichString_appendAscii(out, CRT_colors[METER_TEXT], "gu:"); RichString_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len); @@ -166,7 +168,7 @@ static void CPUMeter_display(const Object* cast, RichString* out) { len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]); RichString_appendAscii(out, CRT_colors[METER_TEXT], "low:"); RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len); - if (!isnan(this->values[CPU_METER_IRQ])) { + if (isNonnegative(this->values[CPU_METER_IRQ])) { len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]); RichString_appendAscii(out, CRT_colors[METER_TEXT], "vir:"); RichString_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len); @@ -176,10 +178,10 @@ static void CPUMeter_display(const Object* cast, RichString* out) { if (settings->showCPUFrequency) { char cpuFrequencyBuffer[10]; double cpuFrequency = this->values[CPU_METER_FREQUENCY]; - if (isnan(cpuFrequency)) { - len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A "); - } else { + if (isNonnegative(cpuFrequency)) { len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "%4uMHz ", (unsigned)cpuFrequency); + } else { + len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A "); } RichString_appendAscii(out, CRT_colors[METER_TEXT], "freq: "); RichString_appendnWide(out, CRT_colors[METER_VALUE], cpuFrequencyBuffer, len); @@ -189,7 +191,7 @@ static void CPUMeter_display(const Object* cast, RichString* out) { if (settings->showCPUTemperature) { char cpuTemperatureBuffer[10]; double cpuTemperature = this->values[CPU_METER_TEMPERATURE]; - if (isnan(cpuTemperature)) { + if (isNaN(cpuTemperature)) { len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A"); } else if (settings->degreeFahrenheit) { len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sF", cpuTemperature * 9 / 5 + 32, CRT_degreeSign); diff --git a/CRT.c b/CRT.c index 05acc92ac..bd5453bc4 100644 --- a/CRT.c +++ b/CRT.c @@ -1112,9 +1112,7 @@ void CRT_setColors(int colorScheme) { for (short int i = 0; i < 8; i++) { for (short int j = 0; j < 8; j++) { if (ColorIndex(i, j) != ColorIndexGrayBlack && ColorIndex(i, j) != ColorIndexWhiteDefault) { - short int bg = (colorScheme != COLORSCHEME_BLACKNIGHT) - ? (j == 0 ? -1 : j) - : j; + short int bg = (colorScheme != COLORSCHEME_BLACKNIGHT) && (j == 0) ? -1 : j; init_pair(ColorIndex(i, j), i, bg); } } @@ -1165,11 +1163,10 @@ static void print_backtrace(void) { #endif } - const char* frame = ""; - if (unw_is_signal_frame(&cursor) > 0) - frame = "{signal frame}"; + const bool is_signal_frame = unw_is_signal_frame(&cursor) > 0; + const char* frame = is_signal_frame ? " {signal frame}" : ""; - fprintf(stderr, "%2u: %#14lx %s (%s+%#lx) [%p]%s%s\n", item++, pc, fname, symbolName, offset, ptr, frame ? " " : "", frame); + fprintf(stderr, "%2u: %#14lx %s (%s+%#lx) [%p]%s\n", item++, pc, fname, symbolName, offset, ptr, frame); } #elif defined(HAVE_EXECINFO_H) void* backtraceArray[256]; diff --git a/CRT.h b/CRT.h index f5fd94527..580979865 100644 --- a/CRT.h +++ b/CRT.h @@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" - #include #include "Macros.h" diff --git a/CategoriesPanel.c b/CategoriesPanel.c index ba7ee5030..64a3f0624 100644 --- a/CategoriesPanel.c +++ b/CategoriesPanel.c @@ -5,13 +5,14 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "CategoriesPanel.h" #include #include #include -#include "AvailableColumnsPanel.h" #include "AvailableMetersPanel.h" #include "ColorsPanel.h" #include "DisplayOptionsPanel.h" @@ -25,6 +26,8 @@ in the source distribution for its full text. #include "Object.h" #include "ProvideCurses.h" #include "ScreensPanel.h" +#include "ScreenTabsPanel.h" +#include "Settings.h" #include "Vector.h" #include "XUtils.h" @@ -72,11 +75,21 @@ static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) { ScreenManager_add(this->scr, colors, -1); } +#if defined(HTOP_PCP) /* all platforms supporting dynamic screens */ +static void CategoriesPanel_makeScreenTabsPage(CategoriesPanel* this) { + Settings* settings = this->host->settings; + Panel* screenTabs = (Panel*) ScreenTabsPanel_new(settings); + Panel* screenNames = (Panel*) ((ScreenTabsPanel*)screenTabs)->names; + ScreenManager_add(this->scr, screenTabs, 20); + ScreenManager_add(this->scr, screenNames, -1); +} +#endif + static void CategoriesPanel_makeScreensPage(CategoriesPanel* this) { Settings* settings = this->host->settings; Panel* screens = (Panel*) ScreensPanel_new(settings); Panel* columns = (Panel*) ((ScreensPanel*)screens)->columns; - Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, settings->dynamicColumns); + Panel* availableColumns = (Panel*) ((ScreensPanel*)screens)->availableColumns; ScreenManager_add(this->scr, screens, 20); ScreenManager_add(this->scr, columns, 20); ScreenManager_add(this->scr, availableColumns, -1); @@ -94,10 +107,13 @@ typedef struct CategoriesPanelPage_ { CategoriesPanel_makePageFunc ctor; } CategoriesPanelPage; -static const CategoriesPanelPage categoriesPanelPages[] = { +static CategoriesPanelPage categoriesPanelPages[] = { { .name = "Display options", .ctor = CategoriesPanel_makeDisplayOptionsPage }, { .name = "Header layout", .ctor = CategoriesPanel_makeHeaderOptionsPage }, { .name = "Meters", .ctor = CategoriesPanel_makeMetersPage }, +#if defined(HTOP_PCP) /* all platforms supporting dynamic screens */ + { .name = "Screen tabs", .ctor = CategoriesPanel_makeScreenTabsPage }, +#endif { .name = "Screens", .ctor = CategoriesPanel_makeScreensPage }, { .name = "Colors", .ctor = CategoriesPanel_makeColorsPage }, }; diff --git a/ChangeLog b/ChangeLog index 5717a52ef..706dab675 100644 --- a/ChangeLog +++ b/ChangeLog @@ -53,7 +53,7 @@ What's new in version 3.2.1 * On Solaris, fix the build * On NetBSD, OpenBSD and Solaris ensure env buffer size is sufficient * On Linux, resolve processes exiting interfering with sampling -* Fix ProcessList quadratic removal when scanning processes +* Fix ProcessTable quadratic removal when scanning processes * Under LXC, limit CPU count to that given by /proc/cpuinfo * Improve container detection for LXC * Some minor documentation fixes diff --git a/ClockMeter.c b/ClockMeter.c index ee712ae2d..38f0591fa 100644 --- a/ClockMeter.c +++ b/ClockMeter.c @@ -13,8 +13,8 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "Machine.h" #include "Object.h" -#include "ProcessList.h" static const int ClockMeter_attributes[] = { diff --git a/ColorsPanel.c b/ColorsPanel.c index 590088423..581c3a00e 100644 --- a/ColorsPanel.c +++ b/ColorsPanel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "ColorsPanel.h" #include @@ -13,7 +15,6 @@ in the source distribution for its full text. #include "CRT.h" #include "FunctionBar.h" -#include "Macros.h" #include "Object.h" #include "OptionItem.h" #include "ProvideCurses.h" @@ -50,30 +51,31 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) { ColorsPanel* this = (ColorsPanel*) super; HandlerResult result = IGNORED; - int mark; switch (ch) { - case 0x0a: - case 0x0d: - case KEY_ENTER: - case KEY_MOUSE: - case KEY_RECLICK: - case ' ': - mark = Panel_getSelectedIndex(super); - assert(mark >= 0); - assert(mark < LAST_COLORSCHEME); - for (int i = 0; ColorSchemeNames[i] != NULL; i++) - CheckItem_set((CheckItem*)Panel_get(super, i), false); - CheckItem_set((CheckItem*)Panel_get(super, mark), true); - - this->settings->colorScheme = mark; - this->settings->changed = true; - this->settings->lastUpdate++; - - CRT_setColors(mark); - clear(); - - result = HANDLED | REDRAW; + case 0x0a: + case 0x0d: + case KEY_ENTER: + case KEY_MOUSE: + case KEY_RECLICK: + case ' ': { + int mark = Panel_getSelectedIndex(super); + assert(mark >= 0); + assert(mark < LAST_COLORSCHEME); + + for (int i = 0; ColorSchemeNames[i] != NULL; i++) + CheckItem_set((CheckItem*)Panel_get(super, i), false); + CheckItem_set((CheckItem*)Panel_get(super, mark), true); + + this->settings->colorScheme = mark; + this->settings->changed = true; + this->settings->lastUpdate++; + + CRT_setColors(mark); + clear(); + + result = HANDLED | REDRAW; + } } return result; diff --git a/ColumnsPanel.c b/ColumnsPanel.c index d53fff258..66625666d 100644 --- a/ColumnsPanel.c +++ b/ColumnsPanel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "ColumnsPanel.h" #include @@ -19,6 +21,7 @@ in the source distribution for its full text. #include "Object.h" #include "Process.h" #include "ProvideCurses.h" +#include "RowField.h" #include "XUtils.h" @@ -44,7 +47,6 @@ static HandlerResult ColumnsPanel_eventHandler(Panel* super, int ch) { case KEY_ENTER: case KEY_MOUSE: case KEY_RECLICK: - { if (selected < size - 1) { this->moving = !(this->moving); Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS); @@ -54,59 +56,45 @@ static HandlerResult ColumnsPanel_eventHandler(Panel* super, int ch) { result = HANDLED; } break; - } case KEY_UP: - { - if (!this->moving) { + if (!this->moving) break; - } - } /* else fallthrough */ case KEY_F(7): case '[': case '-': - { if (selected < size - 1) Panel_moveSelectedUp(super); result = HANDLED; break; - } case KEY_DOWN: - { - if (!this->moving) { + if (!this->moving) break; - } - } /* else fallthrough */ case KEY_F(8): case ']': case '+': - { if (selected < size - 2) Panel_moveSelectedDown(super); result = HANDLED; break; - } case KEY_F(9): case KEY_DC: - { - if (selected < size - 1) { + if (selected < size - 1) Panel_remove(super, selected); - } result = HANDLED; break; - } default: - { if (0 < ch && ch < 255 && isgraph((unsigned char)ch)) result = Panel_selectByTyping(super, ch); if (result == BREAK_LOOP) result = IGNORED; break; - } } + if (result == HANDLED) ColumnsPanel_update(super); + return result; } @@ -128,9 +116,8 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) if (!column) { name = NULL; } else { - name = column->caption ? column->caption : column->heading; - if (!name) - name = column->name; /* name is a mandatory field */ + /* heading preferred here but name is always available */ + name = column->heading ? column->heading : column->name; } } if (name == NULL) @@ -141,7 +128,7 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns) { Panel* super = (Panel*) this; Panel_prune(super); - for (const ProcessField* fields = ss->fields; *fields; fields++) + for (const RowField* fields = ss->fields; *fields; fields++) ColumnsPanel_add(super, *fields, columns); this->ss = ss; } diff --git a/ColumnsPanel.h b/ColumnsPanel.h index 63f6f92b0..723369fd9 100644 --- a/ColumnsPanel.h +++ b/ColumnsPanel.h @@ -9,6 +9,7 @@ in the source distribution for its full text. #include +#include "Hashtable.h" #include "Panel.h" #include "Settings.h" diff --git a/CommandLine.c b/CommandLine.c index 3d1f953fc..09e67b853 100644 --- a/CommandLine.c +++ b/CommandLine.c @@ -25,18 +25,20 @@ in the source distribution for its full text. #include "CRT.h" #include "DynamicColumn.h" #include "DynamicMeter.h" +#include "DynamicScreen.h" #include "Hashtable.h" #include "Header.h" #include "IncSet.h" +#include "Machine.h" #include "MainPanel.h" #include "MetersPanel.h" #include "Panel.h" #include "Platform.h" #include "Process.h" -#include "ProcessList.h" -#include "ProvideCurses.h" +#include "ProcessTable.h" #include "ScreenManager.h" #include "Settings.h" +#include "Table.h" #include "UsersTable.h" #include "XUtils.h" @@ -137,6 +139,7 @@ static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettin while ((opt = getopt_long(argc, argv, "hVMCs:td:n:u::Up:F:H::", long_opts, &opti))) { if (opt == EOF) break; + switch (opt) { case 'h': printHelpFlag(program); @@ -191,8 +194,7 @@ static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettin return STATUS_ERROR_EXIT; } break; - case 'u': - { + case 'u': { const char* username = optarg; if (!username && optind < argc && argv[optind] != NULL && (argv[optind][0] != '\0' && argv[optind][0] != '-')) { @@ -245,11 +247,10 @@ static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettin break; } - case 'F': { + case 'F': assert(optarg); free_and_xStrdup(&flags->commFilter, optarg); break; - } case 'H': { const char* delay = optarg; if (!delay && optind < argc && argv[optind] != NULL && @@ -303,11 +304,11 @@ static void CommandLine_delay(Machine* host, unsigned long millisec) { } static void setCommFilter(State* state, char** commFilter) { - ProcessList* pl = state->host->pl; + Table* table = state->host->activeTable; IncSet* inc = state->mainPanel->inc; IncSet_setFilter(inc, *commFilter); - pl->incFilter = IncSet_filter(inc); + table->incFilter = IncSet_filter(inc); free(*commFilter); *commFilter = NULL; @@ -334,20 +335,15 @@ int CommandLine_run(int argc, char** argv) { if (!Platform_init()) return 1; - Process_setupColumnWidths(); - UsersTable* ut = UsersTable_new(); Hashtable* dm = DynamicMeters_new(); Hashtable* dc = DynamicColumns_new(); - if (!dc) - dc = Hashtable_new(0, true); + Hashtable* ds = DynamicScreens_new(); Machine* host = Machine_new(ut, flags.userId); - ProcessList* pl = ProcessList_new(host, flags.pidMatchList); - Settings* settings = Settings_new(host->activeCPUs, dm, dc); - - host->settings = settings; - Machine_addList(host, pl); + ProcessTable* pt = ProcessTable_new(host, flags.pidMatchList); + Settings* settings = Settings_new(host->activeCPUs, dm, dc, ds); + Machine_populateTablesFromSettings(host, settings, &pt->super); Header* header = Header_new(host, 2); Header_populateFromSettings(header); @@ -379,7 +375,7 @@ int CommandLine_run(int argc, char** argv) { CRT_init(settings, flags.allowUnicode, flags.iterationsRemaining != -1); MainPanel* panel = MainPanel_new(); - ProcessList_setPanel(pl, (Panel*) panel); + Machine_setTablesPanel(host, (Panel*) panel); MainPanel_updateLabels(panel, settings->ss->treeView, flags.commFilter); @@ -400,13 +396,13 @@ int CommandLine_run(int argc, char** argv) { ScreenManager_add(scr, (Panel*) panel, -1); Machine_scan(host); - ProcessList_scan(pl); + Machine_scanTables(host); CommandLine_delay(host, 75); Machine_scan(host); - ProcessList_scan(pl); + Machine_scanTables(host); if (settings->ss->allBranchesCollapsed) - ProcessList_collapseAllBranches(pl); + Table_collapseAllBranches(&pt->super); ScreenManager_run(scr, NULL, NULL, NULL); @@ -421,7 +417,6 @@ int CommandLine_run(int argc, char** argv) { } Header_delete(header); - ProcessList_delete(pl); Machine_delete(host); ScreenManager_delete(scr); @@ -438,6 +433,7 @@ int CommandLine_run(int argc, char** argv) { Settings_delete(settings); DynamicColumns_delete(dc); DynamicMeters_delete(dm); + DynamicScreens_delete(ds); return 0; } diff --git a/CommandScreen.c b/CommandScreen.c index 6a87d1375..465e4c2f1 100644 --- a/CommandScreen.c +++ b/CommandScreen.c @@ -1,3 +1,11 @@ +/* +htop - CommandScreen.c +(C) 2017,2020 ryenus +(C) 2020,2021 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + #include "config.h" // IWYU pragma: keep #include "CommandScreen.h" @@ -46,7 +54,7 @@ static void CommandScreen_scan(InfoScreen* this) { } static void CommandScreen_draw(InfoScreen* this) { - InfoScreen_drawTitled(this, "Command of process %d - %s", this->process->pid, Process_getCommand(this->process)); + InfoScreen_drawTitled(this, "Command of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process)); } const InfoScreenClass CommandScreen_class = { diff --git a/CommandScreen.h b/CommandScreen.h index e56982b2e..2eef5e554 100644 --- a/CommandScreen.h +++ b/CommandScreen.h @@ -1,5 +1,12 @@ #ifndef HEADER_CommandScreen #define HEADER_CommandScreen +/* +htop - CommandScreen.h +(C) 2017,2020 ryenus +(C) 2020,2021 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include "InfoScreen.h" #include "Object.h" diff --git a/Compat.h b/Compat.h index b83fc3d80..2bc12dc63 100644 --- a/Compat.h +++ b/Compat.h @@ -7,11 +7,9 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include // IWYU pragma: keep #include -#include +#include // IWYU pragma: keep #include #include // IWYU pragma: keep diff --git a/DateMeter.c b/DateMeter.c index b38f43b0c..0bdb30a87 100644 --- a/DateMeter.c +++ b/DateMeter.c @@ -13,8 +13,8 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "Machine.h" #include "Object.h" -#include "ProcessList.h" static const int DateMeter_attributes[] = { diff --git a/DateTimeMeter.c b/DateTimeMeter.c index d46f3cb21..dcd85ea6c 100644 --- a/DateTimeMeter.c +++ b/DateTimeMeter.c @@ -13,8 +13,8 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "Machine.h" #include "Object.h" -#include "ProcessList.h" static const int DateTimeMeter_attributes[] = { diff --git a/DiskIOMeter.c b/DiskIOMeter.c index 545ec008a..8d658de84 100644 --- a/DiskIOMeter.c +++ b/DiskIOMeter.c @@ -5,18 +5,19 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "DiskIOMeter.h" #include -#include #include "CRT.h" +#include "Machine.h" #include "Macros.h" -#include "Meter.h" #include "Object.h" #include "Platform.h" -#include "ProcessList.h" #include "RichString.h" +#include "Row.h" #include "XUtils.h" @@ -27,8 +28,8 @@ static const int DiskIOMeter_attributes[] = { }; static MeterRateStatus status = RATESTATUS_INIT; -static uint32_t cached_read_diff; -static uint32_t cached_write_diff; +static char cached_read_diff_str[6]; +static char cached_write_diff_str[6]; static double cached_utilisation_diff; static void DiskIOMeter_updateValues(Meter* this) { @@ -36,16 +37,13 @@ static void DiskIOMeter_updateValues(Meter* this) { static uint64_t cached_last_update; uint64_t passedTimeInMs = host->realtimeMs - cached_last_update; + bool hasNewData = false; + DiskIOData data; /* update only every 500ms to have a sane span for rate calculation */ if (passedTimeInMs > 500) { - static uint64_t cached_read_total; - static uint64_t cached_write_total; - static uint64_t cached_msTimeSpend_total; - uint64_t diff; - - DiskIOData data; - if (!Platform_getDiskIO(&data)) { + hasNewData = Platform_getDiskIO(&data); + if (!hasNewData) { status = RATESTATUS_NODATA; } else if (cached_last_update == 0) { status = RATESTATUS_INIT; @@ -56,41 +54,54 @@ static void DiskIOMeter_updateValues(Meter* this) { } cached_last_update = host->realtimeMs; + } - if (status == RATESTATUS_NODATA) { - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); - return; - } + if (hasNewData) { + static uint64_t cached_read_total; + static uint64_t cached_write_total; + static uint64_t cached_msTimeSpend_total; - if (data.totalBytesRead > cached_read_total) { - diff = data.totalBytesRead - cached_read_total; - diff = (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; + if (status != RATESTATUS_INIT) { + uint64_t diff; + + if (data.totalBytesRead > cached_read_total) { + diff = data.totalBytesRead - cached_read_total; + diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ + diff /= ONE_K; /* convert to KiB/s */ + } else { + diff = 0; + } + Meter_humanUnit(cached_read_diff_str, diff, sizeof(cached_read_diff_str)); + + if (data.totalBytesWritten > cached_write_total) { + diff = data.totalBytesWritten - cached_write_total; + diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ + diff /= ONE_K; /* convert to KiB/s */ + } else { + diff = 0; + } + Meter_humanUnit(cached_write_diff_str, diff, sizeof(cached_write_diff_str)); + + if (data.totalMsTimeSpend > cached_msTimeSpend_total) { + diff = data.totalMsTimeSpend - cached_msTimeSpend_total; + cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs; + cached_utilisation_diff = MINIMUM(cached_utilisation_diff, 100.0); + } else { + cached_utilisation_diff = 0.0; + } } - cached_read_total = data.totalBytesRead; - if (data.totalBytesWritten > cached_write_total) { - diff = data.totalBytesWritten - cached_write_total; - diff = (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_read_total = data.totalBytesRead; cached_write_total = data.totalBytesWritten; - - if (data.totalMsTimeSpend > cached_msTimeSpend_total) { - diff = data.totalMsTimeSpend - cached_msTimeSpend_total; - cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs; - } else { - cached_utilisation_diff = 0.0; - } cached_msTimeSpend_total = data.totalMsTimeSpend; } + this->values[0] = cached_utilisation_diff; + + if (status == RATESTATUS_NODATA) { + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); + return; + } if (status == RATESTATUS_INIT) { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init"); return; @@ -100,45 +111,36 @@ static void DiskIOMeter_updateValues(Meter* this) { return; } - this->values[0] = cached_utilisation_diff; - this->total = MAXIMUM(this->values[0], 100.0); /* fix total after (initial) spike */ - - char bufferRead[12], bufferWrite[12]; - Meter_humanUnit(bufferRead, cached_read_diff, sizeof(bufferRead)); - Meter_humanUnit(bufferWrite, cached_write_diff, sizeof(bufferWrite)); - snprintf(this->txtBuffer, sizeof(this->txtBuffer), "r:%siB/s w:%siB/s %.1f%%", bufferRead, bufferWrite, cached_utilisation_diff); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "r:%siB/s w:%siB/s %.1f%%", cached_read_diff_str, cached_write_diff_str, cached_utilisation_diff); } static void DiskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { switch (status) { - case RATESTATUS_NODATA: - RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); - return; - case RATESTATUS_INIT: - RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing..."); - return; - case RATESTATUS_STALE: - RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data"); - return; - case RATESTATUS_DATA: - break; + case RATESTATUS_NODATA: + RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); + return; + case RATESTATUS_INIT: + RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing..."); + return; + case RATESTATUS_STALE: + RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data"); + return; + case RATESTATUS_DATA: + break; } char buffer[16]; - int len; int color = cached_utilisation_diff > 40.0 ? METER_VALUE_NOTICE : METER_VALUE; - len = xSnprintf(buffer, sizeof(buffer), "%.1f%%", cached_utilisation_diff); + int len = xSnprintf(buffer, sizeof(buffer), "%.1f%%", cached_utilisation_diff); RichString_appendnAscii(out, CRT_colors[color], buffer, len); RichString_appendAscii(out, CRT_colors[METER_TEXT], " read: "); - Meter_humanUnit(buffer, cached_read_diff, sizeof(buffer)); - RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], buffer); + RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], cached_read_diff_str); RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], "iB/s"); RichString_appendAscii(out, CRT_colors[METER_TEXT], " write: "); - Meter_humanUnit(buffer, cached_write_diff, sizeof(buffer)); - RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], buffer); + RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], cached_write_diff_str); RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s"); } diff --git a/DiskIOMeter.h b/DiskIOMeter.h index 3b03e321c..5ac9c4849 100644 --- a/DiskIOMeter.h +++ b/DiskIOMeter.h @@ -7,6 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include + #include "Meter.h" diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index f9fa9b12b..66793e164 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -11,6 +11,7 @@ in the source distribution for its full text. #include #include +#include #include "CRT.h" #include "FunctionBar.h" @@ -37,37 +38,37 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) { OptionItem* selected = (OptionItem*) Panel_getSelected(super); switch (ch) { - case '\n': - case '\r': - case KEY_ENTER: - case KEY_MOUSE: - case KEY_RECLICK: - case ' ': - switch (OptionItem_kind(selected)) { - case OPTION_ITEM_TEXT: + case '\n': + case '\r': + case KEY_ENTER: + case KEY_MOUSE: + case KEY_RECLICK: + case ' ': + switch (OptionItem_kind(selected)) { + case OPTION_ITEM_TEXT: + break; + case OPTION_ITEM_CHECK: + CheckItem_toggle((CheckItem*)selected); + result = HANDLED; + break; + case OPTION_ITEM_NUMBER: + NumberItem_toggle((NumberItem*)selected); + result = HANDLED; + break; + } break; - case OPTION_ITEM_CHECK: - CheckItem_toggle((CheckItem*)selected); - result = HANDLED; + case '-': + if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) { + NumberItem_decrease((NumberItem*)selected); + result = HANDLED; + } break; - case OPTION_ITEM_NUMBER: - NumberItem_toggle((NumberItem*)selected); - result = HANDLED; + case '+': + if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) { + NumberItem_increase((NumberItem*)selected); + result = HANDLED; + } break; - } - break; - case '-': - if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) { - NumberItem_decrease((NumberItem*)selected); - result = HANDLED; - } - break; - case '+': - if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) { - NumberItem_increase((NumberItem*)selected); - result = HANDLED; - } - break; } if (result == HANDLED) { @@ -80,6 +81,7 @@ static HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) { Header_draw(header); ScreenManager_resize(this->scr); } + return result; } @@ -104,7 +106,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* #define TABMSG "For current screen tab: \0" char tabheader[sizeof(TABMSG) + SCREEN_NAME_LEN + 1] = TABMSG; - strncat(tabheader, settings->ss->name, SCREEN_NAME_LEN); + strncat(tabheader, settings->ss->heading, SCREEN_NAME_LEN); Panel_add(super, (Object*) TextItem_new(tabheader)); #undef TABMSG diff --git a/DynamicColumn.c b/DynamicColumn.c index bd038df4e..5f021740f 100644 --- a/DynamicColumn.c +++ b/DynamicColumn.c @@ -12,6 +12,7 @@ in the source distribution for its full text. #include "DynamicColumn.h" #include +#include #include "Platform.h" #include "RichString.h" @@ -19,7 +20,10 @@ in the source distribution for its full text. Hashtable* DynamicColumns_new(void) { - return Platform_dynamicColumns(); + Hashtable* dynamics = Platform_dynamicColumns(); + if (!dynamics) + dynamics = Hashtable_new(0, true); + return dynamics; } void DynamicColumns_delete(Hashtable* dynamics) { @@ -29,8 +33,14 @@ void DynamicColumns_delete(Hashtable* dynamics) { } } -const char* DynamicColumn_init(unsigned int key) { - return Platform_dynamicColumnInit(key); +const char* DynamicColumn_name(unsigned int key) { + return Platform_dynamicColumnName(key); +} + +void DynamicColumn_done(DynamicColumn* this) { + free(this->heading); + free(this->caption); + free(this->description); } typedef struct { diff --git a/DynamicColumn.h b/DynamicColumn.h index 4760e6ea5..bdce82d21 100644 --- a/DynamicColumn.h +++ b/DynamicColumn.h @@ -1,29 +1,40 @@ #ifndef HEADER_DynamicColumn #define HEADER_DynamicColumn +/* +htop - DynamicColumn.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include #include "Hashtable.h" #include "Process.h" #include "RichString.h" +#include "Table.h" -#define DYNAMIC_MAX_COLUMN_WIDTH 28 +#define DYNAMIC_MAX_COLUMN_WIDTH 64 #define DYNAMIC_DEFAULT_COLUMN_WIDTH -5 typedef struct DynamicColumn_ { - char name[32]; /* unique, internal-only name */ - char* heading; /* displayed in main screen */ - char* caption; /* displayed in setup menu (short name) */ - char* description; /* displayed in setup menu (detail) */ - int width; /* display width +/- for value alignment */ + char name[32]; /* unique, internal-only name */ + char* heading; /* displayed in main screen */ + char* caption; /* displayed in setup menu (short name) */ + char* description; /* displayed in setup menu (detail) */ + int width; /* display width +/- for value alignment */ + bool enabled; /* false == ignore this column (until enabled) */ + Table* table; /* pointer to DynamicScreen or ProcessTable */ } DynamicColumn; Hashtable* DynamicColumns_new(void); void DynamicColumns_delete(Hashtable* dynamics); -const char* DynamicColumn_init(unsigned int key); +const char* DynamicColumn_name(unsigned int key); + +void DynamicColumn_done(DynamicColumn* this); const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key); diff --git a/DynamicMeter.c b/DynamicMeter.c index 82e73a929..15d853bd3 100644 --- a/DynamicMeter.c +++ b/DynamicMeter.c @@ -15,10 +15,11 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "Machine.h" #include "Object.h" #include "Platform.h" -#include "ProcessList.h" #include "RichString.h" +#include "Settings.h" #include "XUtils.h" diff --git a/DynamicMeter.h b/DynamicMeter.h index 3ef0176a9..2bc3cba15 100644 --- a/DynamicMeter.h +++ b/DynamicMeter.h @@ -1,5 +1,12 @@ #ifndef HEADER_DynamicMeter #define HEADER_DynamicMeter +/* +htop - DynamicMeter.h +(C) 2021 htop dev team +(C) 2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include diff --git a/DynamicScreen.c b/DynamicScreen.c new file mode 100644 index 000000000..9e3d5e496 --- /dev/null +++ b/DynamicScreen.c @@ -0,0 +1,68 @@ +/* +htop - DynamicScreen.c +(C) 2022 Sohaib Mohammed +(C) 2022-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "DynamicScreen.h" + +#include +#include +#include + +#include "Hashtable.h" +#include "Platform.h" +#include "XUtils.h" + + +Hashtable* DynamicScreens_new(void) { + return Platform_dynamicScreens(); +} + +void DynamicScreens_delete(Hashtable* screens) { + if (screens) { + Platform_dynamicScreensDone(screens); + Hashtable_delete(screens); + } +} + +void DynamicScreen_done(DynamicScreen* this) { + free(this->caption); + free(this->fields); + free(this->heading); + free(this->sortKey); + free(this->columnKeys); +} + +typedef struct { + ht_key_t key; + const char* name; + bool found; +} DynamicIterator; + +static void DynamicScreen_compare(ht_key_t key, void* value, void* data) { + const DynamicScreen* screen = (const DynamicScreen*)value; + DynamicIterator* iter = (DynamicIterator*)data; + if (String_eq(iter->name, screen->name)) { + iter->found = true; + iter->key = key; + } +} + +bool DynamicScreen_search(Hashtable* screens, const char* name, ht_key_t* key) { + DynamicIterator iter = { .key = 0, .name = name, .found = false }; + if (screens) + Hashtable_foreach(screens, DynamicScreen_compare, &iter); + if (key) + *key = iter.key; + return iter.found; +} + +const char* DynamicScreen_lookup(Hashtable* screens, ht_key_t key) { + const DynamicScreen* screen = Hashtable_get(screens, key); + return screen ? screen->name : NULL; +} diff --git a/DynamicScreen.h b/DynamicScreen.h new file mode 100644 index 000000000..fb08ebc96 --- /dev/null +++ b/DynamicScreen.h @@ -0,0 +1,38 @@ +#ifndef HEADER_DynamicScreen +#define HEADER_DynamicScreen +/* +htop - DynamicColumn.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include + +#include "Hashtable.h" +#include "Panel.h" + + +typedef struct DynamicScreen_ { + char name[32]; /* unique name cannot contain any spaces */ + char* heading; /* user-settable more readable name */ + char* caption; /* explanatory text for screen */ + char* fields; + char* sortKey; + char* columnKeys; + int direction; +} DynamicScreen; + +Hashtable* DynamicScreens_new(void); + +void DynamicScreens_delete(Hashtable* dynamics); + +void DynamicScreen_done(DynamicScreen* this); + +void DynamicScreens_addAvailableColumns(Panel* availableColumns, char* screen); + +const char* DynamicScreen_lookup(Hashtable* screens, unsigned int key); + +bool DynamicScreen_search(Hashtable* screens, const char* name, unsigned int* key); + +#endif diff --git a/EnvScreen.c b/EnvScreen.c index 0fcee83a2..4a36b318a 100644 --- a/EnvScreen.c +++ b/EnvScreen.c @@ -1,3 +1,12 @@ +/* +htop - EnvScreen.c +(C) 2015,2016 Michael Klein +(C) 2016,2017 Hisham H. Muhammad +(C) 2020,2021 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + #include "config.h" // IWYU pragma: keep #include "EnvScreen.h" @@ -24,7 +33,7 @@ void EnvScreen_delete(Object* this) { } static void EnvScreen_draw(InfoScreen* this) { - InfoScreen_drawTitled(this, "Environment of process %d - %s", this->process->pid, Process_getCommand(this->process)); + InfoScreen_drawTitled(this, "Environment of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process)); } static void EnvScreen_scan(InfoScreen* this) { @@ -33,7 +42,7 @@ static void EnvScreen_scan(InfoScreen* this) { Panel_prune(panel); - char* env = Platform_getProcessEnv(this->process->pid); + char* env = Platform_getProcessEnv(Process_getPid(this->process)); if (env) { for (const char* p = env; *p; p = strrchr(p, 0) + 1) InfoScreen_addLine(this, p); diff --git a/EnvScreen.h b/EnvScreen.h index 4d44c81c5..118c271c0 100644 --- a/EnvScreen.h +++ b/EnvScreen.h @@ -1,5 +1,13 @@ #ifndef HEADER_EnvScreen #define HEADER_EnvScreen +/* +htop - EnvScreen.h +(C) 2015,2016 Michael Klein +(C) 2016,2017 Hisham H. Muhammad +(C) 2020,2021 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include "InfoScreen.h" #include "Object.h" diff --git a/FileDescriptorMeter.c b/FileDescriptorMeter.c index 2d939d66d..cf1ec9331 100644 --- a/FileDescriptorMeter.c +++ b/FileDescriptorMeter.c @@ -5,13 +5,14 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "FileDescriptorMeter.h" #include -#include -#include #include "CRT.h" +#include "Macros.h" #include "Meter.h" #include "Object.h" #include "Platform.h" @@ -19,7 +20,7 @@ in the source distribution for its full text. #include "XUtils.h" -#define FD_EFFECTIVE_UNLIMITED(x) ((x) > (1<<30)) +#define FD_EFFECTIVE_UNLIMITED(x) (!isgreaterequal((double)(1<<30), (x))) static const int FileDescriptorMeter_attributes[] = { FILE_DESCRIPTOR_USED, @@ -67,9 +68,9 @@ static void FileDescriptorMeter_updateValues(Meter* this) { } } - if (isnan(this->values[0])) { + if (!isNonnegative(this->values[0])) { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "unknown/unknown"); - } else if (isnan(this->values[1]) || FD_EFFECTIVE_UNLIMITED(this->values[1])) { + } else if (FD_EFFECTIVE_UNLIMITED(this->values[1])) { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.0lf/unlimited", this->values[0]); } else { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.0lf/%.0lf", this->values[0], this->values[1]); @@ -81,7 +82,7 @@ static void FileDescriptorMeter_display(const Object* cast, RichString* out) { char buffer[50]; int len; - if (isnan(this->values[0])) { + if (!isNonnegative(this->values[0])) { RichString_appendAscii(out, CRT_colors[METER_TEXT], "unknown"); return; } @@ -91,7 +92,7 @@ static void FileDescriptorMeter_display(const Object* cast, RichString* out) { RichString_appendnAscii(out, CRT_colors[FILE_DESCRIPTOR_USED], buffer, len); RichString_appendAscii(out, CRT_colors[METER_TEXT], " max: "); - if (isnan(this->values[1]) || FD_EFFECTIVE_UNLIMITED(this->values[1])) { + if (FD_EFFECTIVE_UNLIMITED(this->values[1])) { RichString_appendAscii(out, CRT_colors[FILE_DESCRIPTOR_MAX], "unlimited"); } else { len = xSnprintf(buffer, sizeof(buffer), "%.0lf", this->values[1]); diff --git a/Header.c b/Header.c index 3fafc70af..4fee26b41 100644 --- a/Header.c +++ b/Header.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "Header.h" #include @@ -22,6 +24,7 @@ in the source distribution for its full text. #include "Object.h" #include "Platform.h" #include "ProvideCurses.h" +#include "Settings.h" #include "XUtils.h" diff --git a/Header.h b/Header.h index add4d76bc..2cc78ab72 100644 --- a/Header.h +++ b/Header.h @@ -7,6 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include + #include "HeaderLayout.h" #include "Machine.h" #include "Meter.h" @@ -23,7 +25,7 @@ typedef struct Header_ { #define Header_forEachColumn(this_, i_) for (size_t (i_)=0, H_fEC_numColumns_ = HeaderLayout_getColumns((this_)->headerLayout); (i_) < H_fEC_numColumns_; ++(i_)) -Header* Header_new(Machine *host, HeaderLayout hLayout); +Header* Header_new(Machine* host, HeaderLayout hLayout); void Header_delete(Header* this); diff --git a/HeaderLayout.h b/HeaderLayout.h index 1cf7bf7b7..473936e20 100644 --- a/HeaderLayout.h +++ b/HeaderLayout.h @@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include #include @@ -26,6 +24,9 @@ typedef enum HeaderLayout_ { HF_THREE_25_25_50, HF_THREE_25_50_25, HF_THREE_50_25_25, + HF_THREE_40_30_30, + HF_THREE_30_40_30, + HF_THREE_30_30_40, HF_THREE_40_20_40, HF_FOUR_25_25_25_25, LAST_HEADER_LAYOUT @@ -44,6 +45,9 @@ static const struct { [HF_THREE_25_25_50] = { 3, { 25, 25, 50, 0 }, "three_25_25_50", "3 columns - 25/25/50", }, [HF_THREE_25_50_25] = { 3, { 25, 50, 25, 0 }, "three_25_50_25", "3 columns - 25/50/25", }, [HF_THREE_50_25_25] = { 3, { 50, 25, 25, 0 }, "three_50_25_25", "3 columns - 50/25/25", }, + [HF_THREE_40_30_30] = { 3, { 40, 30, 30, 0 }, "three_40_30_30", "3 columns - 40/30/30", }, + [HF_THREE_30_40_30] = { 3, { 30, 40, 30, 0 }, "three_30_40_30", "3 columns - 30/40/30", }, + [HF_THREE_30_30_40] = { 3, { 30, 30, 40, 0 }, "three_30_30_40", "3 columns - 30/30/40", }, [HF_THREE_40_20_40] = { 3, { 40, 20, 40, 0 }, "three_40_20_40", "3 columns - 40/20/40", }, [HF_FOUR_25_25_25_25] = { 4, { 25, 25, 25, 25 }, "four_25_25_25_25", "4 columns - 25/25/25/25", }, }; diff --git a/HeaderOptionsPanel.c b/HeaderOptionsPanel.c index 25d1ddbb7..7b5c81be1 100644 --- a/HeaderOptionsPanel.c +++ b/HeaderOptionsPanel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "HeaderOptionsPanel.h" #include @@ -33,30 +35,30 @@ static HandlerResult HeaderOptionsPanel_eventHandler(Panel* super, int ch) { HeaderOptionsPanel* this = (HeaderOptionsPanel*) super; HandlerResult result = IGNORED; - int mark; switch (ch) { - case 0x0a: - case 0x0d: - case KEY_ENTER: - case KEY_MOUSE: - case KEY_RECLICK: - case ' ': - mark = Panel_getSelectedIndex(super); - assert(mark >= 0); - assert(mark < LAST_HEADER_LAYOUT); - - for (int i = 0; i < LAST_HEADER_LAYOUT; i++) - CheckItem_set((CheckItem*)Panel_get(super, i), false); - CheckItem_set((CheckItem*)Panel_get(super, mark), true); - - Header_setLayout(this->scr->header, mark); - this->settings->changed = true; - this->settings->lastUpdate++; - - ScreenManager_resize(this->scr); - - result = HANDLED; + case 0x0a: + case 0x0d: + case KEY_ENTER: + case KEY_MOUSE: + case KEY_RECLICK: + case ' ': { + int mark = Panel_getSelectedIndex(super); + assert(mark >= 0); + assert(mark < LAST_HEADER_LAYOUT); + + for (int i = 0; i < LAST_HEADER_LAYOUT; i++) + CheckItem_set((CheckItem*)Panel_get(super, i), false); + CheckItem_set((CheckItem*)Panel_get(super, mark), true); + + Header_setLayout(this->scr->header, mark); + this->settings->changed = true; + this->settings->lastUpdate++; + + ScreenManager_resize(this->scr); + + result = HANDLED; + } } return result; diff --git a/InfoScreen.c b/InfoScreen.c index 105d9c34d..c602cd463 100644 --- a/InfoScreen.c +++ b/InfoScreen.c @@ -1,3 +1,11 @@ +/* +htop - InfoScreen.c +(C) 2016 Hisham H. Muhammad +(C) 2020,2022 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + #include "config.h" // IWYU pragma: keep #include "InfoScreen.h" @@ -130,48 +138,48 @@ void InfoScreen_run(InfoScreen* this) { } switch (ch) { - case ERR: - continue; - case KEY_F(3): - case '/': - IncSet_activate(this->inc, INC_SEARCH, panel); - break; - case KEY_F(4): - case '\\': - IncSet_activate(this->inc, INC_FILTER, panel); - break; - case KEY_F(5): - clear(); - if (As_InfoScreen(this)->scan) { - Vector_prune(this->lines); - InfoScreen_scan(this); - } + case ERR: + continue; + case KEY_F(3): + case '/': + IncSet_activate(this->inc, INC_SEARCH, panel); + break; + case KEY_F(4): + case '\\': + IncSet_activate(this->inc, INC_FILTER, panel); + break; + case KEY_F(5): + clear(); + if (As_InfoScreen(this)->scan) { + Vector_prune(this->lines); + InfoScreen_scan(this); + } - InfoScreen_draw(this); - break; - case '\014': // Ctrl+L - clear(); - InfoScreen_draw(this); - break; - case 27: - case 'q': - case KEY_F(10): - looping = false; - break; - case KEY_RESIZE: - Panel_resize(panel, COLS, LINES - 2); - if (As_InfoScreen(this)->scan) { - Vector_prune(this->lines); - InfoScreen_scan(this); - } + InfoScreen_draw(this); + break; + case '\014': // Ctrl+L + clear(); + InfoScreen_draw(this); + break; + case 27: + case 'q': + case KEY_F(10): + looping = false; + break; + case KEY_RESIZE: + Panel_resize(panel, COLS, LINES - 2); + if (As_InfoScreen(this)->scan) { + Vector_prune(this->lines); + InfoScreen_scan(this); + } - InfoScreen_draw(this); - break; - default: - if (As_InfoScreen(this)->onKey && InfoScreen_onKey(this, ch)) { - continue; - } - Panel_onKey(panel, ch); + InfoScreen_draw(this); + break; + default: + if (As_InfoScreen(this)->onKey && InfoScreen_onKey(this, ch)) { + continue; + } + Panel_onKey(panel, ch); } } } diff --git a/InfoScreen.h b/InfoScreen.h index d7497bed2..469c27079 100644 --- a/InfoScreen.h +++ b/InfoScreen.h @@ -1,5 +1,12 @@ #ifndef HEADER_InfoScreen #define HEADER_InfoScreen +/* +htop - InfoScreen.h +(C) 2016 Hisham H. Muhammad +(C) 2020,2022 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include diff --git a/ListItem.h b/ListItem.h index b2c3e061e..5efe8743b 100644 --- a/ListItem.h +++ b/ListItem.h @@ -10,6 +10,7 @@ in the source distribution for its full text. #include #include "Object.h" +#include "RichString.h" typedef struct ListItem_ { diff --git a/LoadAverageMeter.c b/LoadAverageMeter.c index 0beae04be..30c58bb96 100644 --- a/LoadAverageMeter.c +++ b/LoadAverageMeter.c @@ -5,12 +5,14 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "LoadAverageMeter.h" #include "CRT.h" +#include "Machine.h" #include "Object.h" #include "Platform.h" -#include "ProcessList.h" #include "RichString.h" #include "XUtils.h" diff --git a/Machine.c b/Machine.c index 63a996ef5..44aa27c81 100644 --- a/Machine.c +++ b/Machine.c @@ -6,15 +6,16 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "Machine.h" #include -#include +#include -#include "CRT.h" -#include "Hashtable.h" -#include "Macros.h" +#include "Object.h" #include "Platform.h" +#include "Row.h" #include "XUtils.h" @@ -22,6 +23,11 @@ void Machine_init(Machine* this, UsersTable* usersTable, uid_t userId) { this->usersTable = usersTable; this->userId = userId; + this->htopUserId = getuid(); + + // discover fixed column width limits + Row_setPidColumnWidth(Platform_getMaxPid()); + // always maintain valid realtime timestamps Platform_gettime_realtime(&this->realtime, &this->realtimeMs); @@ -49,12 +55,70 @@ void Machine_done(Machine* this) { if (this->topologyOk) { hwloc_topology_destroy(this->topology); } -#else - (void)this; #endif + Object_delete(this->processTable); + free(this->tables); +} + +static void Machine_addTable(Machine* this, Table* table) { + /* check that this table has not been seen previously */ + for (size_t i = 0; i < this->tableCount; i++) + if (this->tables[i] == table) + return; + + size_t nmemb = this->tableCount + 1; + Table** tables = xReallocArray(this->tables, nmemb, sizeof(Table*)); + tables[nmemb - 1] = table; + this->tables = tables; + this->tableCount++; +} + +void Machine_populateTablesFromSettings(Machine* this, Settings* settings, Table* processTable) { + this->settings = settings; + this->processTable = processTable; + + for (size_t i = 0; i < settings->nScreens; i++) { + ScreenSettings* ss = settings->screens[i]; + Table* table = ss->table; + if (!table) + table = ss->table = processTable; + if (i == 0) + this->activeTable = table; + + Machine_addTable(this, table); + } } -void Machine_addList(Machine* this, struct ProcessList_ *pl) { - // currently only process lists are supported - this->pl = pl; +void Machine_setTablesPanel(Machine* this, Panel* panel) { + for (size_t i = 0; i < this->tableCount; i++) { + Table_setPanel(this->tables[i], panel); + } +} + +void Machine_scanTables(Machine* this) { + // set scan timestamp + static bool firstScanDone = false; + + if (firstScanDone) + Platform_gettime_monotonic(&this->monotonicMs); + else + firstScanDone = true; + + this->maxUserId = 0; + Row_resetFieldWidths(); + + for (size_t i = 0; i < this->tableCount; i++) { + Table* table = this->tables[i]; + + // pre-processing of each row + Table_scanPrepare(table); + + // scan values for this table + Table_scanIterate(table); + + // post-process after scanning + Table_scanCleanup(table); + } + + Row_setUidColumnWidth(this->maxUserId); } diff --git a/Machine.h b/Machine.h index 64d6f6c29..419911c31 100644 --- a/Machine.h +++ b/Machine.h @@ -8,18 +8,16 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include #include #include #include -#include "Hashtable.h" +#include "Panel.h" #include "Settings.h" +#include "Table.h" #include "UsersTable.h" -#include "Vector.h" #ifdef HAVE_LIBHWLOC #include @@ -37,10 +35,8 @@ in the source distribution for its full text. typedef unsigned long long int memory_t; #define MEMORY_MAX ULLONG_MAX -struct ProcessList_; - typedef struct Machine_ { - Settings* settings; + struct Settings_* settings; struct timeval realtime; /* time of the current sample */ uint64_t realtimeMs; /* current time in milliseconds */ @@ -68,11 +64,14 @@ typedef struct Machine_ { unsigned int existingCPUs; UsersTable* usersTable; - uid_t userId; - - /* To become an array of lists - processes, cgroups, filesystems,... etc */ - /* for now though, just point back to the one list we have at the moment */ - struct ProcessList_ *pl; + uid_t htopUserId; + uid_t maxUserId; /* recently observed */ + uid_t userId; /* selected row user ID */ + + size_t tableCount; + Table **tables; + Table *activeTable; + Table *processTable; } Machine; @@ -86,8 +85,12 @@ void Machine_done(Machine* this); bool Machine_isCPUonline(const Machine* this, unsigned int id); -void Machine_addList(Machine* this, struct ProcessList_ *pl); +void Machine_populateTablesFromSettings(Machine* this, Settings* settings, Table* processTable); + +void Machine_setTablesPanel(Machine* host, Panel* panel); void Machine_scan(Machine* this); +void Machine_scanTables(Machine* this); + #endif diff --git a/Macros.h b/Macros.h index 0f95347b7..01cc77740 100644 --- a/Macros.h +++ b/Macros.h @@ -1,7 +1,17 @@ #ifndef HEADER_Macros #define HEADER_Macros +/* +htop - Macros.h +(C) 2020-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include // IWYU pragma: keep +#include +#include +#include // IWYU pragma: keep + #ifndef MINIMUM #define MINIMUM(a, b) ((a) < (b) ? (a) : (b)) @@ -34,7 +44,6 @@ #ifdef __GNUC__ // defined by GCC and Clang #define ATTR_FORMAT(type, index, check) __attribute__((format (type, index, check))) -#define ATTR_NONNULL __attribute__((nonnull)) #define ATTR_NORETURN __attribute__((noreturn)) #define ATTR_UNUSED __attribute__((unused)) #define ATTR_MALLOC __attribute__((malloc)) @@ -42,13 +51,24 @@ #else /* __GNUC__ */ #define ATTR_FORMAT(type, index, check) -#define ATTR_NONNULL #define ATTR_NORETURN #define ATTR_UNUSED #define ATTR_MALLOC #endif /* __GNUC__ */ +#ifdef HAVE_ATTR_NONNULL + +#define ATTR_NONNULL __attribute__((nonnull)) +#define ATTR_NONNULL_N(...) __attribute__((nonnull(__VA_ARGS__))) + +#else + +#define ATTR_NONNULL +#define ATTR_NONNULL_N(...) + +#endif /* HAVE_ATTR_NONNULL */ + #ifdef HAVE_ATTR_ALLOC_SIZE #define ATTR_ALLOC_SIZE1(a) __attribute__((alloc_size (a))) @@ -98,6 +118,24 @@ #define IGNORE_WCASTQUAL_END #endif +/* Cheaper function for checking NaNs. Unlike the standard isnan(), this may + throw an FP exception on a "signaling" NaN. + (ISO/IEC TS 18661-1 and the C23 standard stated that isnan() throws no + exceptions even with a "signaling" NaN) */ +static inline bool isNaN(double x) { + return !isgreaterequal(x, x); +} + +/* Checks if x is nonnegative. Returns false if x is NaN. */ +static inline bool isNonnegative(double x) { + return isgreaterequal(x, 0.0); +} + +/* Checks if x is positive. Returns false if x is NaN. */ +static inline bool isPositive(double x) { + return isgreater(x, 0.0); +} + /* This subtraction is used by Linux / NetBSD / OpenBSD for calculation of CPU usage items. */ static inline unsigned long long saturatingSub(unsigned long long a, unsigned long long b) { return a > b ? a - b : 0; diff --git a/MainPanel.c b/MainPanel.c index 14bd3bbdf..83490236e 100644 --- a/MainPanel.c +++ b/MainPanel.c @@ -6,18 +6,23 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "MainPanel.h" #include #include +#include #include "CRT.h" #include "FunctionBar.h" +#include "Machine.h" #include "Platform.h" -#include "Process.h" -#include "ProcessList.h" #include "ProvideCurses.h" +#include "Row.h" +#include "RowField.h" #include "Settings.h" +#include "Table.h" #include "XUtils.h" @@ -30,25 +35,25 @@ void MainPanel_updateLabels(MainPanel* this, bool list, bool filter) { FunctionBar_setLabel(bar, KEY_F(4), filter ? "FILTER" : "Filter"); } -static void MainPanel_pidSearch(MainPanel* this, int ch) { +static void MainPanel_idSearch(MainPanel* this, int ch) { Panel* super = (Panel*) this; - pid_t pid = ch - 48 + this->pidSearch; + pid_t id = ch - 48 + this->idSearch; for (int i = 0; i < Panel_size(super); i++) { - const Process* p = (const Process*) Panel_get(super, i); - if (p && p->pid == pid) { + const Row* row = (const Row*) Panel_get(super, i); + if (row && row->id == id) { Panel_setSelected(super, i); break; } } - this->pidSearch = pid * 10; - if (this->pidSearch > 10000000) { - this->pidSearch = 0; + this->idSearch = id * 10; + if (this->idSearch > 10000000) { + this->idSearch = 0; } } static const char* MainPanel_getValue(Panel* this, int i) { - const Process* p = (const Process*) Panel_get(this, i); - return Process_getCommand(p); + Row* row = (Row*) Panel_get(this, i); + return Row_sortKeyString(row); } static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { @@ -77,7 +82,7 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { if (EVENT_IS_HEADER_CLICK(ch)) { int x = EVENT_HEADER_CLICK_GET_X(ch); int hx = super->scrollH + x + 1; - ProcessField field = ProcessList_keyAt(host->pl, hx); + RowField field = RowField_keyAt(settings, hx); if (ss->treeView && ss->treeViewAlwaysByPID) { ss->treeView = false; ss->direction = 1; @@ -91,12 +96,12 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { result = HANDLED; } else if (EVENT_IS_SCREEN_TAB_CLICK(ch)) { int x = EVENT_SCREEN_TAB_GET_X(ch); - reaction |= Action_setScreenTab(settings, x); + reaction |= Action_setScreenTab(this->state, x); result = HANDLED; } else if (ch != ERR && this->inc->active) { bool filterChanged = IncSet_handleKey(this->inc, ch, super, MainPanel_getValue, NULL); if (filterChanged) { - host->pl->incFilter = IncSet_filter(this->inc); + host->activeTable->incFilter = IncSet_filter(this->inc); reaction = HTOP_REFRESH | HTOP_REDRAW_BAR; } if (this->inc->found) { @@ -111,17 +116,17 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { reaction |= (this->keys[ch])(this->state); result = HANDLED; } else if (0 < ch && ch < 255 && isdigit((unsigned char)ch)) { - MainPanel_pidSearch(this, ch); + MainPanel_idSearch(this, ch); } else { if (ch != ERR) { - this->pidSearch = 0; + this->idSearch = 0; } else { reaction |= HTOP_KEEP_FOLLOWING; } } if ((reaction & HTOP_REDRAW_BAR) == HTOP_REDRAW_BAR) { - MainPanel_updateLabels(this, settings->ss->treeView, host->pl->incFilter); + MainPanel_updateLabels(this, settings->ss->treeView, host->activeTable->incFilter); } if ((reaction & HTOP_RESIZE) == HTOP_RESIZE) { result |= RESIZE; @@ -142,35 +147,32 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { return BREAK_LOOP; } if ((reaction & HTOP_KEEP_FOLLOWING) != HTOP_KEEP_FOLLOWING) { - host->pl->following = -1; + host->activeTable->following = -1; Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); } return result; } -int MainPanel_selectedPid(MainPanel* this) { - const Process* p = (const Process*) Panel_getSelected((Panel*)this); - if (p) { - return p->pid; - } - return -1; +int MainPanel_selectedRow(MainPanel* this) { + const Row* row = (const Row*) Panel_getSelected((Panel*)this); + return row ? row->id : -1; } -bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged) { +bool MainPanel_foreachRow(MainPanel* this, MainPanel_foreachRowFn fn, Arg arg, bool* wasAnyTagged) { Panel* super = (Panel*) this; bool ok = true; bool anyTagged = false; for (int i = 0; i < Panel_size(super); i++) { - Process* p = (Process*) Panel_get(super, i); - if (p->tag) { - ok = fn(p, arg) && ok; + Row* row = (Row*) Panel_get(super, i); + if (row->tag) { + ok &= fn(row, arg); anyTagged = true; } } if (!anyTagged) { - Process* p = (Process*) Panel_getSelected(super); - if (p) { - ok &= fn(p, arg); + Row* row = (Row*) Panel_getSelected(super); + if (row) { + ok &= fn(row, arg); } } @@ -196,7 +198,7 @@ static void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) { static void MainPanel_printHeader(Panel* super) { MainPanel* this = (MainPanel*) super; Machine* host = this->state->host; - ProcessList_printHeader(host->pl, &super->header); + Table_printHeader(host->settings, &super->header); } const PanelClass MainPanel_class = { @@ -211,9 +213,12 @@ const PanelClass MainPanel_class = { MainPanel* MainPanel_new(void) { MainPanel* this = AllocThis(MainPanel); - Panel_init((Panel*) this, 1, 1, 1, 1, Class(Process), false, FunctionBar_new(Settings_isReadonly() ? MainFunctions_ro : MainFunctions, NULL, NULL)); + this->processBar = FunctionBar_new(MainFunctions, NULL, NULL); + this->readonlyBar = FunctionBar_new(MainFunctions_ro, NULL, NULL); + FunctionBar* activeBar = Settings_isReadonly() ? this->readonlyBar : this->processBar; + Panel_init((Panel*) this, 1, 1, 1, 1, Class(Row), false, activeBar); this->keys = xCalloc(KEY_MAX, sizeof(Htop_Action)); - this->inc = IncSet_new(MainPanel_getFunctionBar(this)); + this->inc = IncSet_new(activeBar); Action_setBindings(this->keys); Platform_setBindings(this->keys); @@ -225,9 +230,16 @@ void MainPanel_setState(MainPanel* this, State* state) { this->state = state; } +void MainPanel_setFunctionBar(MainPanel* this, bool readonly) { + this->super.defaultBar = readonly ? this->readonlyBar : this->processBar; + this->inc->defaultBar = this->super.defaultBar; +} + void MainPanel_delete(Object* object) { Panel* super = (Panel*) object; MainPanel* this = (MainPanel*) object; + MainPanel_setFunctionBar(this, false); + FunctionBar_delete(this->readonlyBar); Panel_done(super); IncSet_delete(this->inc); free(this->keys); diff --git a/MainPanel.h b/MainPanel.h index bd22acd0c..105b46de1 100644 --- a/MainPanel.h +++ b/MainPanel.h @@ -8,16 +8,14 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include -#include #include "Action.h" +#include "FunctionBar.h" #include "IncSet.h" #include "Object.h" #include "Panel.h" -#include "Process.h" +#include "Row.h" typedef struct MainPanel_ { @@ -25,19 +23,21 @@ typedef struct MainPanel_ { State* state; IncSet* inc; Htop_Action* keys; - pid_t pidSearch; + FunctionBar* processBar; /* function bar with process-specific actions */ + FunctionBar* readonlyBar; /* function bar without process actions (ro) */ + unsigned int idSearch; } MainPanel; -typedef bool(*MainPanel_ForeachProcessFn)(Process*, Arg); +typedef bool(*MainPanel_foreachRowFn)(Row*, Arg); #define MainPanel_getFunctionBar(this_) (((Panel*)(this_))->defaultBar) // update the Label Keys in the MainPanel bar, list: list / tree mode, filter: filter (inc) active / inactive void MainPanel_updateLabels(MainPanel* this, bool list, bool filter); -int MainPanel_selectedPid(MainPanel* this); +int MainPanel_selectedRow(MainPanel* this); -bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged); +bool MainPanel_foreachRow(MainPanel* this, MainPanel_foreachRowFn fn, Arg arg, bool* wasAnyTagged); extern const PanelClass MainPanel_class; @@ -45,6 +45,8 @@ MainPanel* MainPanel_new(void); void MainPanel_setState(MainPanel* this, State* state); +void MainPanel_setFunctionBar(MainPanel* this, bool readonly); + void MainPanel_delete(Object* object); #endif diff --git a/Makefile.am b/Makefile.am index e36994c13..ed92afac4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,7 +24,7 @@ pixmap_DATA = htop.png appicondir = $(datadir)/icons/hicolor/scalable/apps appicon_DATA = htop.svg -AM_CFLAGS += -pedantic -std=c99 -D_XOPEN_SOURCE_EXTENDED -DSYSCONFDIR="\"$(sysconfdir)\"" -I"$(top_srcdir)/$(my_htop_platform)" +AM_CFLAGS += -D_XOPEN_SOURCE_EXTENDED -DSYSCONFDIR="\"$(sysconfdir)\"" -I"$(top_srcdir)/$(my_htop_platform)" AM_LDFLAGS = myhtopsources = \ @@ -49,6 +49,7 @@ myhtopsources = \ DisplayOptionsPanel.c \ DynamicColumn.c \ DynamicMeter.c \ + DynamicScreen.c \ EnvScreen.c \ FileDescriptorMeter.c \ FunctionBar.c \ @@ -72,16 +73,19 @@ myhtopsources = \ OptionItem.c \ Panel.c \ Process.c \ - ProcessList.c \ ProcessLocksScreen.c \ + ProcessTable.c \ + Row.c \ RichString.c \ Scheduling.c \ ScreenManager.c \ ScreensPanel.c \ + ScreenTabsPanel.c \ Settings.c \ SignalsPanel.c \ SwapMeter.c \ SysArchMeter.c \ + Table.c \ TasksMeter.c \ TraceScreen.c \ UptimeMeter.c \ @@ -111,6 +115,7 @@ myhtopheaders = \ DisplayOptionsPanel.h \ DynamicColumn.h \ DynamicMeter.h \ + DynamicScreen.h \ EnvScreen.h \ FileDescriptorMeter.h \ FunctionBar.h \ @@ -136,18 +141,22 @@ myhtopheaders = \ OptionItem.h \ Panel.h \ Process.h \ - ProcessList.h \ ProcessLocksScreen.h \ + ProcessTable.h \ ProvideCurses.h \ ProvideTerm.h \ RichString.h \ + Row.h \ + RowField.h \ Scheduling.h \ ScreenManager.h \ ScreensPanel.h \ + ScreenTabsPanel.h \ Settings.h \ SignalsPanel.h \ SwapMeter.h \ SysArchMeter.h \ + Table.h \ TasksMeter.h \ TraceScreen.h \ UptimeMeter.h \ @@ -169,7 +178,7 @@ linux_platform_headers = \ linux/LibSensors.h \ linux/LinuxMachine.h \ linux/LinuxProcess.h \ - linux/LinuxProcessList.h \ + linux/LinuxProcessTable.h \ linux/Platform.h \ linux/PressureStallMeter.h \ linux/ProcessField.h \ @@ -192,7 +201,7 @@ linux_platform_sources = \ linux/LibSensors.c \ linux/LinuxMachine.c \ linux/LinuxProcess.c \ - linux/LinuxProcessList.c \ + linux/LinuxProcessTable.c \ linux/Platform.c \ linux/PressureStallMeter.c \ linux/SELinuxMeter.c \ @@ -212,7 +221,7 @@ endif freebsd_platform_headers = \ freebsd/FreeBSDMachine.h \ - freebsd/FreeBSDProcessList.h \ + freebsd/FreeBSDProcessTable.h \ freebsd/FreeBSDProcess.h \ freebsd/Platform.h \ freebsd/ProcessField.h \ @@ -228,7 +237,7 @@ freebsd_platform_headers = \ freebsd_platform_sources = \ freebsd/Platform.c \ freebsd/FreeBSDMachine.c \ - freebsd/FreeBSDProcessList.c \ + freebsd/FreeBSDProcessTable.c \ freebsd/FreeBSDProcess.c \ generic/fdstat_sysctl.c \ generic/gettime.c \ @@ -248,7 +257,7 @@ endif dragonflybsd_platform_headers = \ dragonflybsd/DragonFlyBSDMachine.h \ - dragonflybsd/DragonFlyBSDProcessList.h \ + dragonflybsd/DragonFlyBSDProcessTable.h \ dragonflybsd/DragonFlyBSDProcess.h \ dragonflybsd/Platform.h \ dragonflybsd/ProcessField.h \ @@ -259,7 +268,7 @@ dragonflybsd_platform_headers = \ dragonflybsd_platform_sources = \ dragonflybsd/DragonFlyBSDMachine.c \ - dragonflybsd/DragonFlyBSDProcessList.c \ + dragonflybsd/DragonFlyBSDProcessTable.c \ dragonflybsd/DragonFlyBSDProcess.c \ dragonflybsd/Platform.c \ generic/fdstat_sysctl.c \ @@ -284,7 +293,7 @@ netbsd_platform_headers = \ netbsd/ProcessField.h \ netbsd/NetBSDMachine.h \ netbsd/NetBSDProcess.h \ - netbsd/NetBSDProcessList.h + netbsd/NetBSDProcessTable.h netbsd_platform_sources = \ generic/fdstat_sysctl.c \ @@ -294,7 +303,7 @@ netbsd_platform_sources = \ netbsd/Platform.c \ netbsd/NetBSDMachine.c \ netbsd/NetBSDProcess.c \ - netbsd/NetBSDProcessList.c + netbsd/NetBSDProcessTable.c if HTOP_NETBSD myhtopplatheaders = $(netbsd_platform_headers) @@ -309,7 +318,7 @@ openbsd_platform_headers = \ generic/hostname.h \ generic/uname.h \ openbsd/OpenBSDMachine.h \ - openbsd/OpenBSDProcessList.h \ + openbsd/OpenBSDProcessTable.h \ openbsd/OpenBSDProcess.h \ openbsd/Platform.h \ openbsd/ProcessField.h @@ -319,7 +328,7 @@ openbsd_platform_sources = \ generic/hostname.c \ generic/uname.c \ openbsd/OpenBSDMachine.c \ - openbsd/OpenBSDProcessList.c \ + openbsd/OpenBSDProcessTable.c \ openbsd/OpenBSDProcess.c \ openbsd/Platform.c @@ -334,7 +343,7 @@ endif darwin_platform_headers = \ darwin/DarwinMachine.h \ darwin/DarwinProcess.h \ - darwin/DarwinProcessList.h \ + darwin/DarwinProcessTable.h \ darwin/Platform.h \ darwin/PlatformHelpers.h \ darwin/ProcessField.h \ @@ -352,7 +361,7 @@ darwin_platform_sources = \ darwin/PlatformHelpers.c \ darwin/DarwinMachine.c \ darwin/DarwinProcess.c \ - darwin/DarwinProcessList.c \ + darwin/DarwinProcessTable.c \ generic/fdstat_sysctl.c \ generic/gettime.c \ generic/hostname.c \ @@ -378,7 +387,7 @@ solaris_platform_headers = \ solaris/Platform.h \ solaris/SolarisMachine.h \ solaris/SolarisProcess.h \ - solaris/SolarisProcessList.h \ + solaris/SolarisProcessTable.h \ zfs/ZfsArcMeter.h \ zfs/ZfsArcStats.h \ zfs/ZfsCompressedArcMeter.h @@ -390,7 +399,7 @@ solaris_platform_sources = \ solaris/Platform.c \ solaris/SolarisMachine.c \ solaris/SolarisProcess.c \ - solaris/SolarisProcessList.c \ + solaris/SolarisProcessTable.c \ zfs/ZfsArcMeter.c \ zfs/ZfsCompressedArcMeter.c @@ -403,31 +412,39 @@ endif # -------------------------- pcp_platform_headers = \ + linux/CGroupUtils.h \ linux/PressureStallMeter.h \ linux/ZramMeter.h \ linux/ZramStats.h \ + pcp/Instance.h \ + pcp/InDomTable.h \ + pcp/Metric.h \ + pcp/Platform.h \ + pcp/ProcessField.h \ pcp/PCPDynamicColumn.h \ pcp/PCPDynamicMeter.h \ + pcp/PCPDynamicScreen.h \ pcp/PCPMachine.h \ - pcp/PCPMetric.h \ pcp/PCPProcess.h \ - pcp/PCPProcessList.h \ - pcp/Platform.h \ - pcp/ProcessField.h \ + pcp/PCPProcessTable.h \ zfs/ZfsArcMeter.h \ zfs/ZfsArcStats.h \ zfs/ZfsCompressedArcMeter.h pcp_platform_sources = \ + linux/CGroupUtils.c \ linux/PressureStallMeter.c \ linux/ZramMeter.c \ + pcp/Instance.c \ + pcp/InDomTable.c \ + pcp/Metric.c \ + pcp/Platform.c \ pcp/PCPDynamicColumn.c \ pcp/PCPDynamicMeter.c \ + pcp/PCPDynamicScreen.c \ pcp/PCPMachine.c \ - pcp/PCPMetric.c \ pcp/PCPProcess.c \ - pcp/PCPProcessList.c \ - pcp/Platform.c \ + pcp/PCPProcessTable.c \ zfs/ZfsArcMeter.c \ zfs/ZfsCompressedArcMeter.c @@ -446,14 +463,14 @@ unsupported_platform_headers = \ unsupported/ProcessField.h \ unsupported/UnsupportedMachine.h \ unsupported/UnsupportedProcess.h \ - unsupported/UnsupportedProcessList.h + unsupported/UnsupportedProcessTable.h unsupported_platform_sources = \ generic/gettime.c \ unsupported/Platform.c \ unsupported/UnsupportedMachine.c \ unsupported/UnsupportedProcess.c \ - unsupported/UnsupportedProcessList.c + unsupported/UnsupportedProcessTable.c if HTOP_UNSUPPORTED myhtopplatsources = $(unsupported_platform_sources) diff --git a/MemoryMeter.c b/MemoryMeter.c index 28c0be277..573b89836 100644 --- a/MemoryMeter.c +++ b/MemoryMeter.c @@ -5,12 +5,16 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "MemoryMeter.h" +#include #include #include #include "CRT.h" +#include "Macros.h" #include "Object.h" #include "Platform.h" #include "RichString.h" @@ -18,9 +22,9 @@ in the source distribution for its full text. static const int MemoryMeter_attributes[] = { MEMORY_USED, - MEMORY_BUFFERS, MEMORY_SHARED, MEMORY_COMPRESSED, + MEMORY_BUFFERS, MEMORY_CACHE }; @@ -40,11 +44,12 @@ static void MemoryMeter_updateValues(Meter* this) { "MEMORY_METER_AVAILABLE is not the last item in MemoryMeterValues"); this->curItems = MEMORY_METER_AVAILABLE; - /* we actually want to show "used + compressed" */ + /* we actually want to show "used + shared + compressed" */ double used = this->values[MEMORY_METER_USED]; - if (!isnan(this->values[MEMORY_METER_COMPRESSED])) { + if (isPositive(this->values[MEMORY_METER_SHARED])) + used += this->values[MEMORY_METER_SHARED]; + if (isPositive(this->values[MEMORY_METER_COMPRESSED])) used += this->values[MEMORY_METER_COMPRESSED]; - } written = Meter_humanUnit(buffer, used, size); METER_BUFFER_CHECK(buffer, size, written); @@ -66,30 +71,30 @@ static void MemoryMeter_display(const Object* cast, RichString* out) { 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])) { + if (isNonnegative(this->values[MEMORY_METER_SHARED])) { Meter_humanUnit(buffer, this->values[MEMORY_METER_SHARED], sizeof(buffer)); RichString_appendAscii(out, CRT_colors[METER_TEXT], " shared:"); RichString_appendAscii(out, CRT_colors[MEMORY_SHARED], buffer); } /* compressed memory is not supported on all platforms */ - if (!isnan(this->values[MEMORY_METER_COMPRESSED])) { + if (isNonnegative(this->values[MEMORY_METER_COMPRESSED])) { Meter_humanUnit(buffer, this->values[MEMORY_METER_COMPRESSED], sizeof(buffer)); RichString_appendAscii(out, CRT_colors[METER_TEXT], " compressed:"); RichString_appendAscii(out, CRT_colors[MEMORY_COMPRESSED], buffer); } + Meter_humanUnit(buffer, this->values[MEMORY_METER_BUFFERS], sizeof(buffer)); + RichString_appendAscii(out, CRT_colors[METER_TEXT], " buffers:"); + RichString_appendAscii(out, CRT_colors[MEMORY_BUFFERS_TEXT], buffer); + Meter_humanUnit(buffer, this->values[MEMORY_METER_CACHE], sizeof(buffer)); RichString_appendAscii(out, CRT_colors[METER_TEXT], " cache:"); RichString_appendAscii(out, CRT_colors[MEMORY_CACHE], buffer); /* available memory is not supported on all platforms */ - if (!isnan(this->values[MEMORY_METER_AVAILABLE])) { + if (isNonnegative(this->values[MEMORY_METER_AVAILABLE])) { Meter_humanUnit(buffer, this->values[MEMORY_METER_AVAILABLE], sizeof(buffer)); RichString_appendAscii(out, CRT_colors[METER_TEXT], " available:"); RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); diff --git a/MemoryMeter.h b/MemoryMeter.h index b6568afca..e39b3bd38 100644 --- a/MemoryMeter.h +++ b/MemoryMeter.h @@ -11,9 +11,9 @@ in the source distribution for its full text. typedef enum { MEMORY_METER_USED = 0, - MEMORY_METER_BUFFERS = 1, - MEMORY_METER_SHARED = 2, - MEMORY_METER_COMPRESSED = 3, + MEMORY_METER_SHARED = 1, + MEMORY_METER_COMPRESSED = 2, + MEMORY_METER_BUFFERS = 3, MEMORY_METER_CACHE = 4, MEMORY_METER_AVAILABLE = 5, MEMORY_METER_ITEMCOUNT = 6, // number of entries in this enum diff --git a/MemorySwapMeter.c b/MemorySwapMeter.c index 814841f70..82eddee21 100644 --- a/MemorySwapMeter.c +++ b/MemorySwapMeter.c @@ -5,9 +5,12 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "MemorySwapMeter.h" #include +#include #include #include diff --git a/Meter.c b/Meter.c index cf0fe36ac..86f88856f 100644 --- a/Meter.c +++ b/Meter.c @@ -13,13 +13,13 @@ in the source distribution for its full text. #include #include #include -#include #include "CRT.h" #include "Macros.h" #include "Object.h" #include "ProvideCurses.h" #include "RichString.h" +#include "Row.h" #include "Settings.h" #include "XUtils.h" @@ -50,32 +50,40 @@ Meter* Meter_new(const Machine* host, unsigned int param, const MeterClass* type return this; } -int Meter_humanUnit(char* buffer, unsigned long int value, size_t size) { - const char* prefix = "KMGTPEZY"; - unsigned long int powi = 1; - unsigned int powj = 1, precision = 2; +/* Converts 'value' in kibibytes into a human readable string. + Example output strings: "0K", "1023K", "98.7M" and "1.23G" */ +int Meter_humanUnit(char* buffer, double value, size_t size) { + size_t i = 0; - for (;;) { - if (value / 1024 < powi) - break; - - if (prefix[1] == '\0') + assert(value >= 0.0); + while (value >= ONE_K) { + if (i >= ARRAYSIZE(unitPrefixes) - 1) { + if (value > 9999.0) { + return xSnprintf(buffer, size, "inf"); + } break; + } - powi *= 1024; - ++prefix; + value /= ONE_K; + ++i; } - if (*prefix == 'K') - precision = 0; + int precision = 0; - for (; precision > 0; precision--) { - powj *= 10; - if (value / powi < powj) - break; + if (i > 0) { + // Fraction digits for mebibytes and above + precision = value <= 99.9 ? (value <= 9.99 ? 2 : 1) : 0; + + // Round up if 'value' is in range (99.9, 100) or (9.99, 10) + if (precision < 2) { + double limit = precision == 1 ? 10.0 : 100.0; + if (value < limit) { + value = limit; + } + } } - return snprintf(buffer, size, "%.*f%c", precision, (double) value / powi, *prefix); + return xSnprintf(buffer, size, "%.*f%c", precision, value, unitPrefixes[i]); } void Meter_delete(Object* cast) { @@ -86,7 +94,7 @@ void Meter_delete(Object* cast) { if (Meter_doneFn(this)) { Meter_done(this); } - free(this->drawData); + free(this->drawData.values); free(this->caption); free(this->values); free(this); @@ -121,8 +129,9 @@ void Meter_setMode(Meter* this, int modeIndex) { } } else { assert(modeIndex >= 1); - free(this->drawData); - this->drawData = NULL; + free(this->drawData.values); + this->drawData.values = NULL; + this->drawData.nValues = 0; const MeterMode* mode = Meter_modes[modeIndex]; this->draw = mode->draw; @@ -219,30 +228,24 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) { assert(startPos + w <= RichString_sizeVal(bar)); int blockSizes[10]; - int blockSizeSum = 0; // First draw in the bar[] buffer... int offset = 0; for (uint8_t i = 0; i < this->curItems; i++) { double value = this->values[i]; - value = CLAMP(value, 0.0, this->total); - if (value > 0) { + if (isPositive(value) && this->total > 0.0) { + value = MINIMUM(value, this->total); blockSizes[i] = ceil((value / this->total) * w); } else { blockSizes[i] = 0; } - - if (Meter_comprisedValues(this)) { - blockSizes[i] = MAXIMUM(blockSizes[i] - blockSizeSum, 0); - blockSizeSum += blockSizes[i]; - } - int nextOffset = offset + blockSizes[i]; // (Control against invalid values) nextOffset = CLAMP(nextOffset, 0, w); for (int j = offset; j < nextOffset; j++) if (RichString_getCharVal(bar, startPos + j) == ' ') { if (CRT_colorScheme == COLORSCHEME_MONOCHROME) { + assert(i < strlen(BarMeterMode_characters)); RichString_setChar(&bar, startPos + j, BarMeterMode_characters[i]); } else { RichString_setChar(&bar, startPos + j, '|'); @@ -294,13 +297,46 @@ static const char* const GraphMeterMode_dotsAscii[] = { }; static void GraphMeterMode_draw(Meter* this, int x, int y, int w) { + const char* caption = Meter_getCaption(this); + attrset(CRT_colors[METER_TEXT]); + const int captionLen = 3; + mvaddnstr(y, x, caption, captionLen); + x += captionLen; + w -= captionLen; + + GraphData* data = &this->drawData; + assert(data->nValues / 2 <= INT_MAX); + if (w > (int)(data->nValues / 2) && MAX_METER_GRAPHDATA_VALUES > data->nValues) { + size_t oldNValues = data->nValues; + data->nValues = MAXIMUM(oldNValues + oldNValues / 2, (size_t)w * 2); + data->nValues = MINIMUM(data->nValues, MAX_METER_GRAPHDATA_VALUES); + data->values = xReallocArray(data->values, data->nValues, sizeof(*data->values)); + memmove(data->values + (data->nValues - oldNValues), data->values, oldNValues * sizeof(*data->values)); + memset(data->values, 0, (data->nValues - oldNValues) * sizeof(*data->values)); + } + + const size_t nValues = data->nValues; + if (nValues < 1) + return; + const Machine* host = this->host; + if (!timercmp(&host->realtime, &(data->time), <)) { + int globalDelay = host->settings->delay; + struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay % 10) * 100000L }; + timeradd(&host->realtime, &delay, &(data->time)); - if (!this->drawData) { - this->drawData = xCalloc(1, sizeof(GraphData)); + memmove(&data->values[0], &data->values[1], (nValues - 1) * sizeof(*data->values)); + + data->values[nValues - 1] = sumPositiveValues(this->values, this->curItems); + } + + if (w <= 0) + return; + + if ((size_t)w > nValues / 2) { + x += w - nValues / 2; + w = nValues / 2; } - GraphData* data = this->drawData; - const int nValues = METER_GRAPHDATA_SIZE; const char* const* GraphMeterMode_dots; int GraphMeterMode_pixPerRow; @@ -315,42 +351,12 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) { GraphMeterMode_pixPerRow = PIXPERROW_ASCII; } - const char* caption = Meter_getCaption(this); - attrset(CRT_colors[METER_TEXT]); - int captionLen = 3; - mvaddnstr(y, x, caption, captionLen); - x += captionLen; - w -= captionLen; - - if (!timercmp(&host->realtime, &(data->time), <)) { - int globalDelay = this->host->settings->delay; - struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay % 10) * 100000L }; - timeradd(&host->realtime, &delay, &(data->time)); - - for (int i = 0; i < nValues - 1; i++) - data->values[i] = data->values[i + 1]; - - if (Meter_comprisedValues(this)) { - data->values[nValues - 1] = (this->curItems > 0) ? this->values[this->curItems - 1] : 0.0; - } else { - double value = 0.0; - for (uint8_t i = 0; i < this->curItems; i++) - value += !isnan(this->values[i]) ? this->values[i] : 0; - data->values[nValues - 1] = value; - } - } - - int i = nValues - (w * 2), k = 0; - if (i < 0) { - k = -i / 2; - i = 0; - } - for (; i < nValues - 1; i += 2, k++) { + size_t i = nValues - (size_t)w * 2; + for (int col = 0; i < nValues - 1; i += 2, col++) { int pix = GraphMeterMode_pixPerRow * GRAPH_HEIGHT; - if (this->total < 1) - this->total = 1; - int v1 = CLAMP((int) lround(data->values[i] / this->total * pix), 1, pix); - int v2 = CLAMP((int) lround(data->values[i + 1] / this->total * pix), 1, pix); + double total = MAXIMUM(this->total, 1); + int v1 = CLAMP((int) lround(data->values[i] / total * pix), 1, pix); + int v2 = CLAMP((int) lround(data->values[i + 1] / total * pix), 1, pix); int colorIdx = GRAPH_1; for (int line = 0; line < GRAPH_HEIGHT; line++) { @@ -358,7 +364,7 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) { int line2 = CLAMP(v2 - (GraphMeterMode_pixPerRow * (GRAPH_HEIGHT - 1 - line)), 0, GraphMeterMode_pixPerRow); attrset(CRT_colors[colorIdx]); - mvaddstr(y + line, x + k, GraphMeterMode_dots[line1 * (GraphMeterMode_pixPerRow + 1) + line2]); + mvaddstr(y + line, x + col, GraphMeterMode_dots[line1 * (GraphMeterMode_pixPerRow + 1) + line2]); colorIdx = GRAPH_2; } } @@ -378,7 +384,7 @@ static const char* const LEDMeterMode_digitsAscii[] = { static const char* const LEDMeterMode_digitsUtf8[] = { "┌──┐", " ┐ ", "╶──┐", "╶──┐", "╷ ╷", "┌──╴", "┌──╴", "╶──┐", "┌──┐", "┌──┐", "│ │", " │ ", "┌──┘", " ──┤", "└──┤", "└──┐", "├──┐", " │", "├──┤", "└──┤", - "└──┘", " ╵ ", "└──╴", "╶──┘", " ╵", "╶──┘", "└──┘", " ╵", "└──┘", " ──┘" + "└──┘", " ╵ ", "└──╴", "╶──┘", " ╵", "╶──┘", "└──┘", " ╵", "└──┘", "╶──┘" }; #endif diff --git a/Meter.h b/Meter.h index db93e4c04..17d77f3e1 100644 --- a/Meter.h +++ b/Meter.h @@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include #include @@ -20,7 +18,7 @@ in the source distribution for its full text. #define METER_TXTBUFFER_LEN 256 -#define METER_GRAPHDATA_SIZE 256 +#define MAX_METER_GRAPHDATA_VALUES 32768 #define METER_BUFFER_CHECK(buffer, size, written) \ do { \ @@ -74,7 +72,6 @@ typedef struct MeterClass_ { 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) */ - const bool comprisedValues; /* whether latter values comprise previous ones (defaults to false) */ } MeterClass; #define As_Meter(this_) ((const MeterClass*)((this_)->super.klass)) @@ -95,11 +92,11 @@ typedef struct MeterClass_ { #define Meter_name(this_) As_Meter(this_)->name #define Meter_uiName(this_) As_Meter(this_)->uiName #define Meter_isMultiColumn(this_) As_Meter(this_)->isMultiColumn -#define Meter_comprisedValues(this_) As_Meter(this_)->comprisedValues typedef struct GraphData_ { struct timeval time; - double values[METER_GRAPHDATA_SIZE]; + size_t nValues; + double* values; } GraphData; struct Meter_ { @@ -110,7 +107,7 @@ struct Meter_ { char* caption; int mode; unsigned int param; - GraphData* drawData; + GraphData drawData; int h; int columnWidthCount; /**< only used internally by the Header */ uint8_t curItems; @@ -147,7 +144,9 @@ extern const MeterClass Meter_class; Meter* Meter_new(const Machine* host, unsigned int param, const MeterClass* type); -int Meter_humanUnit(char* buffer, unsigned long int value, size_t size); +/* Converts 'value' in kibibytes into a human readable string. + Example output strings: "0K", "1023K", "98.7M" and "1.23G" */ +int Meter_humanUnit(char* buffer, double value, size_t size); void Meter_delete(Object* cast); diff --git a/MetersPanel.c b/MetersPanel.c index 580e41bde..2678fb2fc 100644 --- a/MetersPanel.c +++ b/MetersPanel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "MetersPanel.h" #include @@ -95,17 +97,14 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { case 0x0a: case 0x0d: case KEY_ENTER: - { if (!Vector_size(this->meters)) break; MetersPanel_setMoving(this, !(this->moving)); result = HANDLED; break; - } case ' ': case KEY_F(4): - case 't': - { + case 't': { if (!Vector_size(this->meters)) break; Meter* meter = (Meter*) Vector_get(this->meters, selected); @@ -118,39 +117,28 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { break; } case KEY_UP: - { - if (!this->moving) { + if (!this->moving) break; - } - } /* else fallthrough */ case KEY_F(7): case '[': case '-': - { Vector_moveUp(this->meters, selected); Panel_moveSelectedUp(super); result = HANDLED; break; - } case KEY_DOWN: - { - if (!this->moving) { + if (!this->moving) break; - } - } /* else fallthrough */ case KEY_F(8): case ']': case '+': - { Vector_moveDown(this->meters, selected); Panel_moveSelectedDown(super); result = HANDLED; break; - } case KEY_RIGHT: - { sideMove = moveToNeighbor(this, this->rightNeighbor, selected); if (this->moving && !sideMove) { // lock user here until it exits positioning-mode @@ -159,18 +147,14 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { // if user is free, don't set HANDLED; // let ScreenManager handle focus. break; - } case KEY_LEFT: - { sideMove = moveToNeighbor(this, this->leftNeighbor, selected); if (this->moving && !sideMove) { result = HANDLED; } break; - } case KEY_F(9): case KEY_DC: - { if (!Vector_size(this->meters)) break; if (selected < Vector_size(this->meters)) { @@ -180,8 +164,8 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { MetersPanel_setMoving(this, false); result = HANDLED; break; - } } + if (result == HANDLED || sideMove) { Header* header = this->scr->header; this->settings->changed = true; @@ -189,6 +173,7 @@ static HandlerResult MetersPanel_eventHandler(Panel* super, int ch) { Header_calculateHeight(header); ScreenManager_resize(this->scr); } + return result; } diff --git a/NetworkIOMeter.c b/NetworkIOMeter.c index 5945bae79..6bc2d0827 100644 --- a/NetworkIOMeter.c +++ b/NetworkIOMeter.c @@ -1,16 +1,24 @@ +/* +htop - NetworkIOMeter.c +(C) 2020-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + #include "NetworkIOMeter.h" #include -#include #include "CRT.h" +#include "Machine.h" #include "Macros.h" #include "Meter.h" #include "Object.h" #include "Platform.h" -#include "Process.h" -#include "ProcessList.h" #include "RichString.h" +#include "Row.h" #include "XUtils.h" @@ -20,27 +28,25 @@ static const int NetworkIOMeter_attributes[] = { }; static MeterRateStatus status = RATESTATUS_INIT; -static uint32_t cached_rxb_diff; +static double cached_rxb_diff; +static char cached_rxb_diff_str[6]; static uint32_t cached_rxp_diff; -static uint32_t cached_txb_diff; +static double cached_txb_diff; +static char cached_txb_diff_str[6]; static uint32_t cached_txp_diff; static void NetworkIOMeter_updateValues(Meter* this) { const Machine* host = this->host; - static uint64_t cached_last_update = 0; + static uint64_t cached_last_update = 0; uint64_t passedTimeInMs = host->realtimeMs - cached_last_update; + bool hasNewData = false; + NetworkIOData data; /* update only every 500ms to have a sane span for rate calculation */ if (passedTimeInMs > 500) { - static uint64_t cached_rxb_total; - static uint64_t cached_rxp_total; - static uint64_t cached_txb_total; - static uint64_t cached_txp_total; - uint64_t diff; - - NetworkIOData data; - if (!Platform_getNetworkIO(&data)) { + hasNewData = Platform_getNetworkIO(&data); + if (!hasNewData) { status = RATESTATUS_NODATA; } else if (cached_last_update == 0) { status = RATESTATUS_INIT; @@ -51,51 +57,68 @@ static void NetworkIOMeter_updateValues(Meter* this) { } cached_last_update = host->realtimeMs; + } - if (status == RATESTATUS_NODATA) { - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); - return; - } + if (hasNewData) { + static uint64_t cached_rxb_total; + static uint64_t cached_rxp_total; + static uint64_t cached_txb_total; + static uint64_t cached_txp_total; - if (data.bytesReceived > cached_rxb_total) { - diff = data.bytesReceived - cached_rxb_total; - diff = (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; + if (status != RATESTATUS_INIT) { + uint64_t diff; + + if (data.bytesReceived > cached_rxb_total) { + diff = data.bytesReceived - cached_rxb_total; + diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ + cached_rxb_diff = diff; + } else { + cached_rxb_diff = 0; + } + Meter_humanUnit(cached_rxb_diff_str, cached_rxb_diff / ONE_K, sizeof(cached_rxb_diff_str)); + + if (data.packetsReceived > cached_rxp_total) { + diff = data.packetsReceived - cached_rxp_total; + diff = (1000 * diff) / passedTimeInMs; /* convert to pkts/s */ + cached_rxp_diff = (uint32_t)diff; + } else { + cached_rxp_diff = 0; + } + + if (data.bytesTransmitted > cached_txb_total) { + diff = data.bytesTransmitted - cached_txb_total; + diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ + cached_txb_diff = diff; + } else { + cached_txb_diff = 0; + } + Meter_humanUnit(cached_txb_diff_str, cached_txb_diff / ONE_K, sizeof(cached_txb_diff_str)); + + if (data.packetsTransmitted > cached_txp_total) { + diff = data.packetsTransmitted - cached_txp_total; + diff = (1000 * diff) / passedTimeInMs; /* convert to pkts/s */ + cached_txp_diff = (uint32_t)diff; + } else { + cached_txp_diff = 0; + } } - cached_rxb_total = data.bytesReceived; - if (data.packetsReceived > cached_rxp_total) { - diff = data.packetsReceived - cached_rxp_total; - diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ - cached_rxp_diff = (uint32_t)diff; - } else { - cached_rxp_diff = 0; - } + cached_rxb_total = data.bytesReceived; cached_rxp_total = data.packetsReceived; - - if (data.bytesTransmitted > cached_txb_total) { - diff = data.bytesTransmitted - cached_txb_total; - diff = (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 = data.bytesTransmitted; - - 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 = data.packetsTransmitted; } + this->values[0] = cached_rxb_diff; + this->values[1] = cached_txb_diff; + if (cached_rxb_diff + cached_txb_diff > this->total) { + this->total = cached_rxb_diff + cached_txb_diff; + } + + if (status == RATESTATUS_NODATA) { + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); + return; + } if (status == RATESTATUS_INIT) { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init"); return; @@ -105,48 +128,36 @@ static void NetworkIOMeter_updateValues(Meter* this) { return; } - this->values[0] = cached_rxb_diff; - this->values[1] = cached_txb_diff; - if (cached_rxb_diff + cached_txb_diff > this->total) { - this->total = cached_rxb_diff + cached_txb_diff; - } - - char bufferBytesReceived[12], bufferBytesTransmitted[12]; - Meter_humanUnit(bufferBytesReceived, cached_rxb_diff, sizeof(bufferBytesReceived)); - Meter_humanUnit(bufferBytesTransmitted, cached_txb_diff, sizeof(bufferBytesTransmitted)); - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "rx:%siB/s tx:%siB/s %d/%dpkts/s", - bufferBytesReceived, bufferBytesTransmitted, cached_rxp_diff, cached_txp_diff); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "rx:%siB/s tx:%siB/s %u/%upkts/s", + cached_rxb_diff_str, cached_txb_diff_str, cached_rxp_diff, cached_txp_diff); } static void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { switch (status) { - case RATESTATUS_NODATA: - RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); - return; - case RATESTATUS_INIT: - RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing..."); - return; - case RATESTATUS_STALE: - RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data"); - return; - case RATESTATUS_DATA: - break; + case RATESTATUS_NODATA: + RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); + return; + case RATESTATUS_INIT: + RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing..."); + return; + case RATESTATUS_STALE: + RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data"); + return; + case RATESTATUS_DATA: + break; } char buffer[64]; - int len; RichString_writeAscii(out, CRT_colors[METER_TEXT], "rx: "); - Meter_humanUnit(buffer, cached_rxb_diff, sizeof(buffer)); - RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], buffer); + RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], cached_rxb_diff_str); RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], "iB/s"); RichString_appendAscii(out, CRT_colors[METER_TEXT], " tx: "); - Meter_humanUnit(buffer, cached_txb_diff, sizeof(buffer)); - RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], buffer); + RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], cached_txb_diff_str); RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s"); - len = xSnprintf(buffer, sizeof(buffer), " (%u/%u pkts/s) ", cached_rxp_diff, cached_txp_diff); + int len = xSnprintf(buffer, sizeof(buffer), " (%u/%u pkts/s) ", cached_rxp_diff, cached_txp_diff); RichString_appendnAscii(out, CRT_colors[METER_TEXT], buffer, len); } diff --git a/NetworkIOMeter.h b/NetworkIOMeter.h index 18c23ce29..355b815b2 100644 --- a/NetworkIOMeter.h +++ b/NetworkIOMeter.h @@ -1,5 +1,13 @@ #ifndef HEADER_NetworkIOMeter #define HEADER_NetworkIOMeter +/* +htop - NetworkIOMeter.h +(C) 2020-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include #include "Meter.h" diff --git a/Object.c b/Object.c index f00914809..ae4d33f22 100644 --- a/Object.c +++ b/Object.c @@ -6,6 +6,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "Object.h" #include diff --git a/Object.h b/Object.h index d5b3bb39c..4d7d653ee 100644 --- a/Object.h +++ b/Object.h @@ -8,8 +8,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index 2e782b986..d6d663c06 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -12,9 +12,9 @@ in the source distribution for its full text. #include #include #include +#include #include #include -#include #include #include #include @@ -27,13 +27,17 @@ in the source distribution for its full text. #include "XUtils.h" +// cf. getIndexForType; must be larger than the maximum value returned. +#define LSOF_DATACOL_COUNT 8 + typedef struct OpenFiles_Data_ { - char* data[8]; + char* data[LSOF_DATACOL_COUNT]; } OpenFiles_Data; typedef struct OpenFiles_ProcessData_ { OpenFiles_Data data; int error; + int cols[LSOF_DATACOL_COUNT]; struct OpenFiles_FileData_* files; } OpenFiles_ProcessData; @@ -44,22 +48,22 @@ typedef struct OpenFiles_FileData_ { static size_t getIndexForType(char type) { switch (type) { - case 'f': - return 0; - case 'a': - return 1; - case 'D': - return 2; - case 'i': - return 3; - case 'n': - return 4; - case 's': - return 5; - case 't': - return 6; - case 'o': - return 7; + case 'f': + return 0; + case 'a': + return 1; + case 'D': + return 2; + case 'i': + return 3; + case 'n': + return 4; + case 's': + return 5; + case 't': + return 6; + case 'o': + return 7; } /* should never reach here */ @@ -72,12 +76,12 @@ static const char* getDataForType(const OpenFiles_Data* data, char type) { } OpenFilesScreen* OpenFilesScreen_new(const Process* process) { - OpenFilesScreen* this = xMalloc(sizeof(OpenFilesScreen)); + OpenFilesScreen* this = xCalloc(1, sizeof(OpenFilesScreen)); Object_setClass(this, Class(OpenFilesScreen)); if (Process_isThread(process)) { - this->pid = process->tgid; + this->pid = Process_getThreadGroup(process); } else { - this->pid = process->pid; + this->pid = Process_getPid(process); } return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE OFFSET NODE NAME"); } @@ -92,6 +96,9 @@ static void OpenFilesScreen_draw(InfoScreen* this) { static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { OpenFiles_ProcessData* pdata = xCalloc(1, sizeof(OpenFiles_ProcessData)); + pdata->cols[getIndexForType('s')] = 8; + pdata->cols[getIndexForType('o')] = 8; + pdata->cols[getIndexForType('i')] = 8; int fdpair[2] = {0, 0}; if (pipe(fdpair) == -1) { @@ -122,7 +129,7 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { xSnprintf(buffer, sizeof(buffer), "%d", pid); // Use of NULL in variadic functions must have a pointer cast. // The NULL constant is not required by standard to have a pointer type. - execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char *)NULL); + execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char*)NULL); exit(127); } close(fdpair[1]); @@ -144,52 +151,60 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { unsigned char cmd = line[0]; switch (cmd) { - case 'f': /* file descriptor */ - { - OpenFiles_FileData* nextFile = xCalloc(1, sizeof(OpenFiles_FileData)); - if (fdata == NULL) { - pdata->files = nextFile; - } else { - fdata->next = nextFile; - } - fdata = nextFile; - item = &(fdata->data); - } /* FALLTHRU */ - case 'a': /* file access mode */ - case 'D': /* file's major/minor device number */ - case 'i': /* file's inode number */ - case 'n': /* file name, comment, Internet address */ - case 's': /* file's size */ - case 't': /* file's type */ - { - size_t index = getIndexForType(cmd); - free_and_xStrdup(&item->data[index], line + 1); - break; - } - case 'o': /* file's offset */ - { - size_t index = getIndexForType(cmd); - if (String_startsWith(line + 1, "0t")) { - free_and_xStrdup(&item->data[index], line + 3); - } else { + case 'f': /* file descriptor */ + { + OpenFiles_FileData* nextFile = xCalloc(1, sizeof(OpenFiles_FileData)); + if (fdata == NULL) { + pdata->files = nextFile; + } else { + fdata->next = nextFile; + } + fdata = nextFile; + item = &(fdata->data); + } /* FALLTHRU */ + case 'a': /* file access mode */ + case 'D': /* file's major/minor device number */ + case 'i': /* file's inode number */ + case 'n': /* file name, comment, Internet address */ + case 's': /* file's size */ + case 't': /* file's type */ + { + size_t index = getIndexForType(cmd); free_and_xStrdup(&item->data[index], line + 1); + size_t dlen = strlen(item->data[index]); + if (dlen > (size_t)pdata->cols[index]) { + pdata->cols[index] = (int)CLAMP(dlen, 0, INT16_MAX); + } + break; } - break; - } - case 'c': /* process command name */ - case 'd': /* file's device character code */ - case 'g': /* process group ID */ - case 'G': /* file flags */ - case 'k': /* link count */ - case 'l': /* file's lock status */ - case 'L': /* process login name */ - case 'p': /* process ID */ - case 'P': /* protocol name */ - case 'R': /* parent process ID */ - case 'T': /* TCP/TPI information, identified by prefixes */ - case 'u': /* process user ID */ - /* ignore */ - break; + case 'o': /* file's offset */ + { + size_t index = getIndexForType(cmd); + if (String_startsWith(line + 1, "0t")) { + free_and_xStrdup(&item->data[index], line + 3); + } else { + free_and_xStrdup(&item->data[index], line + 1); + } + size_t dlen = strlen(item->data[index]); + if (dlen > (size_t)pdata->cols[index]) { + pdata->cols[index] = (int)CLAMP(dlen, 0, INT16_MAX); + } + break; + } + case 'c': /* process command name */ + case 'd': /* file's device character code */ + case 'g': /* process group ID */ + case 'G': /* file flags */ + case 'k': /* link count */ + case 'l': /* file's lock status */ + case 'L': /* process login name */ + case 'p': /* process ID */ + case 'P': /* protocol name */ + case 'R': /* parent process ID */ + case 'T': /* TCP/TPI information, identified by prefixes */ + case 'u': /* process user ID */ + /* ignore */ + break; } if (cmd == 's') @@ -239,30 +254,43 @@ static void OpenFiles_Data_clear(OpenFiles_Data* data) { free(data->data[i]); } -static void OpenFilesScreen_scan(InfoScreen* this) { - Panel* panel = this->display; +static void OpenFilesScreen_scan(InfoScreen* super) { + Panel* panel = super->display; int idx = Panel_getSelectedIndex(panel); Panel_prune(panel); - OpenFiles_ProcessData* pdata = OpenFilesScreen_getProcessData(((OpenFilesScreen*)this)->pid); + OpenFiles_ProcessData* pdata = OpenFilesScreen_getProcessData(((OpenFilesScreen*)super)->pid); if (pdata->error == 127) { - InfoScreen_addLine(this, "Could not execute 'lsof'. Please make sure it is available in your $PATH."); + InfoScreen_addLine(super, "Could not execute 'lsof'. Please make sure it is available in your $PATH."); } else if (pdata->error == 1) { - InfoScreen_addLine(this, "Failed listing open files."); + InfoScreen_addLine(super, "Failed listing open files."); } else { + char hdrbuf[128] = {0}; + snprintf(hdrbuf, sizeof(hdrbuf), "%5.5s %-7.7s %-4.4s %6.6s %*s %*s %*s %s", + "FD", "TYPE", "MODE", "DEVICE", + pdata->cols[getIndexForType('s')], "SIZE", + pdata->cols[getIndexForType('o')], "OFFSET", + pdata->cols[getIndexForType('i')], "NODE", + "NAME" + ); + Panel_setHeader(panel, hdrbuf); + OpenFiles_FileData* fdata = pdata->files; while (fdata) { OpenFiles_Data* data = &fdata->data; char* entry = NULL; - xAsprintf(&entry, "%5.5s %-7.7s %-4.4s %-10.10s %10.10s %10.10s %10.10s %s", + xAsprintf(&entry, "%5.5s %-7.7s %-4.4s %6.6s %*s %*s %*s %s", getDataForType(data, 'f'), getDataForType(data, 't'), getDataForType(data, 'a'), getDataForType(data, 'D'), + pdata->cols[getIndexForType('s')], getDataForType(data, 's'), + pdata->cols[getIndexForType('o')], getDataForType(data, 'o'), + pdata->cols[getIndexForType('i')], getDataForType(data, 'i'), getDataForType(data, 'n')); - InfoScreen_addLine(this, entry); + InfoScreen_addLine(super, entry); free(entry); OpenFiles_Data_clear(data); OpenFiles_FileData* old = fdata; @@ -272,7 +300,7 @@ static void OpenFilesScreen_scan(InfoScreen* this) { OpenFiles_Data_clear(&pdata->data); } free(pdata); - Vector_insertionSort(this->lines); + Vector_insertionSort(super->lines); Vector_insertionSort(panel->items); Panel_setSelected(panel, idx); } diff --git a/OptionItem.c b/OptionItem.c index 962c0a9c7..728113b9e 100644 --- a/OptionItem.c +++ b/OptionItem.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "OptionItem.h" #include diff --git a/Panel.c b/Panel.c index d1bc6a7b5..4784a6585 100644 --- a/Panel.c +++ b/Panel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "Panel.h" #include @@ -271,8 +273,8 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect int upTo = MINIMUM(first + h, size); int selectionColor = focus - ? CRT_colors[this->selectionColorId] - : CRT_colors[PANEL_SELECTION_UNFOCUS]; + ? CRT_colors[this->selectionColorId] + : CRT_colors[PANEL_SELECTION_UNFOCUS]; if (this->needsRedraw || force_redraw) { int line = 0; @@ -357,72 +359,74 @@ bool Panel_onKey(Panel* this, int key) { } while (0) switch (key) { - case KEY_DOWN: - case KEY_CTRL('N'): - #ifdef KEY_C_DOWN - case KEY_C_DOWN: - #endif - this->selected++; - break; - - case KEY_UP: - case KEY_CTRL('P'): - #ifdef KEY_C_UP - case KEY_C_UP: - #endif - this->selected--; - break; + case KEY_DOWN: + case KEY_CTRL('N'): + #ifdef KEY_C_DOWN + case KEY_C_DOWN: + #endif + this->selected++; + break; + + case KEY_UP: + case KEY_CTRL('P'): + #ifdef KEY_C_UP + case KEY_C_UP: + #endif + this->selected--; + break; + + case KEY_LEFT: + case KEY_CTRL('B'): + if (this->scrollH > 0) { + this->scrollH -= MAXIMUM(CRT_scrollHAmount, 0); + this->needsRedraw = true; + } + break; - case KEY_LEFT: - case KEY_CTRL('B'): - if (this->scrollH > 0) { - this->scrollH -= MAXIMUM(CRT_scrollHAmount, 0); + case KEY_RIGHT: + case KEY_CTRL('F'): + this->scrollH += CRT_scrollHAmount; this->needsRedraw = true; - } - break; + break; - case KEY_RIGHT: - case KEY_CTRL('F'): - this->scrollH += CRT_scrollHAmount; - this->needsRedraw = true; - break; + case KEY_PPAGE: + PANEL_SCROLL(-(this->h - Panel_headerHeight(this))); + break; - case KEY_PPAGE: - PANEL_SCROLL(-(this->h - Panel_headerHeight(this))); - break; + case KEY_NPAGE: + PANEL_SCROLL(+(this->h - Panel_headerHeight(this))); + break; - case KEY_NPAGE: - PANEL_SCROLL(+(this->h - Panel_headerHeight(this))); - break; + case KEY_WHEELUP: + PANEL_SCROLL(-CRT_scrollWheelVAmount); + break; - case KEY_WHEELUP: - PANEL_SCROLL(-CRT_scrollWheelVAmount); - break; + case KEY_WHEELDOWN: + PANEL_SCROLL(+CRT_scrollWheelVAmount); + break; - case KEY_WHEELDOWN: - PANEL_SCROLL(+CRT_scrollWheelVAmount); - break; + case KEY_HOME: + this->selected = 0; + break; - case KEY_HOME: - this->selected = 0; - break; + case KEY_END: + this->selected = size - 1; + break; - case KEY_END: - this->selected = size - 1; - break; + case KEY_CTRL('A'): + case '^': + this->scrollH = 0; + this->needsRedraw = true; + break; - case KEY_CTRL('A'): - case '^': - this->scrollH = 0; - this->needsRedraw = true; - break; - case KEY_CTRL('E'): - case '$': - this->scrollH = MAXIMUM(this->selectedLen - this->w, 0); - this->needsRedraw = true; - break; - default: - return false; + case KEY_CTRL('E'): + case '$': + this->scrollH = MAXIMUM(this->selectedLen - this->w, 0); + this->needsRedraw = true; + break; + + default: + return false; } #undef PANEL_SCROLL diff --git a/Panel.h b/Panel.h index 33d532e82..286e4464b 100644 --- a/Panel.h +++ b/Panel.h @@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include diff --git a/Process.c b/Process.c index 8f8eea8b6..a36ab6c77 100644 --- a/Process.c +++ b/Process.c @@ -11,7 +11,6 @@ in the source distribution for its full text. #include "Process.h" #include -#include #include #include #include @@ -19,17 +18,18 @@ in the source distribution for its full text. #include #include #include -#include #include #include "CRT.h" +#include "Hashtable.h" +#include "Machine.h" #include "Macros.h" -#include "Platform.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "DynamicColumn.h" #include "RichString.h" #include "Scheduling.h" #include "Settings.h" +#include "Table.h" #include "XUtils.h" #if defined(MAJOR_IN_MKDEV) @@ -40,246 +40,14 @@ in the source distribution for its full text. /* Used to identify kernel threads in Comm and Exe columns */ static const char* const kthreadID = "KTHREAD"; -static uid_t Process_getuid = (uid_t)-1; - -int Process_pidDigits = PROCESS_MIN_PID_DIGITS; -int Process_uidDigits = PROCESS_MIN_UID_DIGITS; - -void Process_setupColumnWidths(void) { - int maxPid = Platform_getMaxPid(); - if (maxPid == -1) - return; - - if (maxPid < (int)pow(10, PROCESS_MIN_PID_DIGITS)) { - Process_pidDigits = PROCESS_MIN_PID_DIGITS; - return; - } - - Process_pidDigits = (int)log10(maxPid) + 1; - assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS); -} - -void Process_setUidColumnWidth(uid_t maxUid) { - if (maxUid < (uid_t)pow(10, PROCESS_MIN_UID_DIGITS)) { - Process_uidDigits = PROCESS_MIN_UID_DIGITS; - return; - } - - Process_uidDigits = (int)log10(maxUid) + 1; - assert(Process_uidDigits <= PROCESS_MAX_UID_DIGITS); -} - -void Process_printBytes(RichString* str, unsigned long long number, bool coloring) { - char buffer[16]; - int len; - - int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; - int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; - int processGigabytesColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS]; - int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS]; - int processColor = CRT_colors[PROCESS]; - - if (number == ULLONG_MAX) { - //Invalid number - RichString_appendAscii(str, shadowColor, " N/A "); - return; - } - - number /= ONE_K; - - if (number < 1000) { - //Plain number, no markings - len = xSnprintf(buffer, sizeof(buffer), "%5llu ", number); - RichString_appendnAscii(str, processColor, buffer, len); - } else if (number < 100000) { - //2 digit MB, 3 digit KB - len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 1000); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - number %= 1000; - len = xSnprintf(buffer, sizeof(buffer), "%03llu ", number); - RichString_appendnAscii(str, processColor, buffer, len); - } else if (number < 1000 * ONE_K) { - //3 digit MB - number /= ONE_K; - len = xSnprintf(buffer, sizeof(buffer), "%4lluM ", number); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - } else if (number < 10000 * ONE_K) { - //1 digit GB, 3 digit MB - number /= ONE_K; - len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - number %= 1000; - len = xSnprintf(buffer, sizeof(buffer), "%03lluM ", number); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - } else if (number < 100000 * ONE_K) { - //2 digit GB, 1 digit MB - number /= 100 * ONE_K; - len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - number %= 10; - len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - RichString_appendAscii(str, processGigabytesColor, "G "); - } else if (number < 1000 * ONE_M) { - //3 digit GB - number /= ONE_M; - len = xSnprintf(buffer, sizeof(buffer), "%4lluG ", number); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - } else if (number < 10000ULL * ONE_M) { - //1 digit TB, 3 digit GB - number /= ONE_M; - len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - number %= 1000; - len = xSnprintf(buffer, sizeof(buffer), "%03lluG ", number); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - } else if (number < 100000 * ONE_M) { - //2 digit TB, 1 digit GB - number /= 100 * ONE_M; - len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - number %= 10; - len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - RichString_appendAscii(str, largeNumberColor, "T "); - } else if (number < 10000ULL * ONE_G) { - //3 digit TB or 1 digit PB, 3 digit TB - number /= ONE_G; - len = xSnprintf(buffer, sizeof(buffer), "%4lluT ", number); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } else { - //2 digit PB and above - len = xSnprintf(buffer, sizeof(buffer), "%4.1lfP ", (double)number / ONE_T); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } -} - -void Process_printKBytes(RichString* str, unsigned long long number, bool coloring) { - if (number == ULLONG_MAX) - Process_printBytes(str, ULLONG_MAX, coloring); - else - Process_printBytes(str, number * ONE_K, coloring); -} - -void Process_printCount(RichString* str, unsigned long long number, bool coloring) { - char buffer[13]; - - int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; - int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; - int processColor = CRT_colors[PROCESS]; - int processShadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS]; - - if (number == ULLONG_MAX) { - RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A "); - } else if (number >= 100000LL * ONE_DECIMAL_T) { - xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G); - RichString_appendnAscii(str, largeNumberColor, buffer, 12); - } else if (number >= 100LL * ONE_DECIMAL_T) { - xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M); - RichString_appendnAscii(str, largeNumberColor, buffer, 8); - RichString_appendnAscii(str, processMegabytesColor, buffer + 8, 4); - } else if (number >= 10LL * ONE_DECIMAL_G) { - xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_K); - RichString_appendnAscii(str, largeNumberColor, buffer, 5); - RichString_appendnAscii(str, processMegabytesColor, buffer + 5, 3); - RichString_appendnAscii(str, processColor, buffer + 8, 4); - } else { - xSnprintf(buffer, sizeof(buffer), "%11llu ", number); - RichString_appendnAscii(str, largeNumberColor, buffer, 2); - RichString_appendnAscii(str, processMegabytesColor, buffer + 2, 3); - RichString_appendnAscii(str, processColor, buffer + 5, 3); - RichString_appendnAscii(str, processShadowColor, buffer + 8, 4); - } -} - -void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) { - char buffer[10]; - int len; - - unsigned long long totalSeconds = totalHundredths / 100; - unsigned long long hours = totalSeconds / 3600; - unsigned long long days = totalSeconds / 86400; - int minutes = (totalSeconds / 60) % 60; - int seconds = totalSeconds % 60; - int hundredths = totalHundredths - (totalSeconds * 100); - - int yearColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; - int dayColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS]; - int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; - int defColor = CRT_colors[PROCESS]; - - if (days >= /* Ignore leap years */365) { - int years = days / 365; - int daysLeft = days - 365 * years; - - if (years >= 10000000) { - RichString_appendnAscii(str, yearColor, "eternity ", 9); - } else if (years >= 1000) { - len = xSnprintf(buffer, sizeof(buffer), "%7dy ", years); - RichString_appendnAscii(str, yearColor, buffer, len); - } else if (daysLeft >= 100) { - len = xSnprintf(buffer, sizeof(buffer), "%3dy", years); - RichString_appendnAscii(str, yearColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft); - RichString_appendnAscii(str, dayColor, buffer, len); - } else if (daysLeft >= 10) { - len = xSnprintf(buffer, sizeof(buffer), "%4dy", years); - RichString_appendnAscii(str, yearColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%2dd ", daysLeft); - RichString_appendnAscii(str, dayColor, buffer, len); - } else { - len = xSnprintf(buffer, sizeof(buffer), "%5dy", years); - RichString_appendnAscii(str, yearColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%1dd ", daysLeft); - RichString_appendnAscii(str, dayColor, buffer, len); - } - } else if (days >= 100) { - int hoursLeft = hours - days * 24; - - if (hoursLeft >= 10) { - len = xSnprintf(buffer, sizeof(buffer), "%4llud", days); - RichString_appendnAscii(str, dayColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%2dh ", hoursLeft); - RichString_appendnAscii(str, hourColor, buffer, len); - } else { - len = xSnprintf(buffer, sizeof(buffer), "%5llud", days); - RichString_appendnAscii(str, dayColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%1dh ", hoursLeft); - RichString_appendnAscii(str, hourColor, buffer, len); - } - } else if (hours >= 100) { - int minutesLeft = totalSeconds / 60 - hours * 60; - - if (minutesLeft >= 10) { - len = xSnprintf(buffer, sizeof(buffer), "%4lluh", hours); - RichString_appendnAscii(str, hourColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%2dm ", minutesLeft); - RichString_appendnAscii(str, defColor, buffer, len); - } else { - len = xSnprintf(buffer, sizeof(buffer), "%5lluh", hours); - RichString_appendnAscii(str, hourColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%1dm ", minutesLeft); - RichString_appendnAscii(str, defColor, buffer, len); - } - } else if (hours > 0) { - len = xSnprintf(buffer, sizeof(buffer), "%2lluh", hours); - RichString_appendnAscii(str, hourColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%02d:%02d ", minutes, seconds); - RichString_appendnAscii(str, defColor, buffer, len); - } else { - len = xSnprintf(buffer, sizeof(buffer), "%2d:%02d.%02d ", minutes, seconds, hundredths); - RichString_appendnAscii(str, defColor, buffer, len); - } -} - void Process_fillStarttimeBuffer(Process* this) { struct tm date; - time_t now = this->host->realtime.tv_sec; + time_t now = this->super.host->realtime.tv_sec; (void) localtime_r(&this->starttime_ctime, &date); strftime(this->starttime_show, sizeof(this->starttime_show) - 1, - (this->starttime_ctime > now - 86400) ? "%R " : (this->starttime_ctime > now - 364*86400) ? "%b%d " : " %Y ", + (this->starttime_ctime > now - 86400) ? "%R " : (this->starttime_ctime > now - 364 * 86400) ? "%b%d " : " %Y ", &date); } @@ -410,9 +178,8 @@ static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr * Process_writeCommand() for coloring. The merged Command string is also * returned by Process_getCommand() for searching, sorting and filtering. */ -void Process_makeCommandStr(Process* this) { +void Process_makeCommandStr(Process* this, const Settings* settings) { ProcessMergedCommand* mc = &this->mergedCommand; - const Settings* settings = this->host->settings; bool showMergedCommand = settings->showMergedCommand; bool showProgramPath = settings->showProgramPath; @@ -680,7 +447,7 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin int strStart = RichString_size(str); - const Settings* settings = this->host->settings; + const Settings* settings = this->super.host->settings; const bool highlightBaseName = settings->highlightBaseName; const bool highlightSeparator = true; const bool highlightDeleted = settings->highlightDeletedExe; @@ -746,73 +513,6 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin } } -void Process_printRate(RichString* str, double rate, bool coloring) { - char buffer[16]; - - int largeNumberColor = CRT_colors[LARGE_NUMBER]; - int processMegabytesColor = CRT_colors[PROCESS_MEGABYTES]; - int processColor = CRT_colors[PROCESS]; - int shadowColor = CRT_colors[PROCESS_SHADOW]; - - if (!coloring) { - largeNumberColor = CRT_colors[PROCESS]; - processMegabytesColor = CRT_colors[PROCESS]; - } - - if (isnan(rate)) { - RichString_appendAscii(str, shadowColor, " N/A "); - } else if (rate < 0.005) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate); - RichString_appendnAscii(str, shadowColor, buffer, len); - } else if (rate < ONE_K) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate); - RichString_appendnAscii(str, processColor, buffer, len); - } else if (rate < ONE_M) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K); - RichString_appendnAscii(str, processColor, buffer, len); - } else if (rate < ONE_G) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - } else if (rate < ONE_T) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f G/s ", rate / ONE_G); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } else if (rate < ONE_P) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f T/s ", rate / ONE_T); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } else { - int len = snprintf(buffer, sizeof(buffer), "%7.2f P/s ", rate / ONE_P); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } -} - -void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) { - int columns = width; - RichString_appendnWideColumns(str, attr, content, strlen(content), &columns); - RichString_appendChr(str, attr, ' ', width + 1 - columns); -} - -void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr) { - if (val >= 0) { - if (val < 0.05F) - *attr = CRT_colors[PROCESS_SHADOW]; - else if (val >= 99.9F) - *attr = CRT_colors[PROCESS_MEGABYTES]; - - int precision = 1; - - // Display "val" as "100" for columns like "MEM%". - if (width == 4 && val > 99.9F) { - precision = 0; - val = 100.0F; - } - - xSnprintf(buffer, n, "%*.*f ", width, precision, val); - } else { - *attr = CRT_colors[PROCESS_SHADOW]; - xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); - } -} - static inline char processStateChar(ProcessState state) { switch (state) { case UNKNOWN: return '?'; @@ -835,12 +535,21 @@ static inline char processStateChar(ProcessState state) { } } -void Process_writeField(const Process* this, RichString* str, ProcessField field) { - char buffer[256]; - size_t n = sizeof(buffer); - int attr = CRT_colors[DEFAULT_COLOR]; - const Settings* settings = this->host->settings; +static void Process_rowWriteField(const Row* super, RichString* str, RowField field) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + Process_writeField(this, str, field); +} + +void Process_writeField(const Process* this, RichString* str, RowField field) { + const Row* super = (const Row*) &this->super; + const Machine* host = super->host; + const Settings* settings = host->settings; + bool coloring = settings->highlightMegabytes; + char buffer[256]; buffer[255] = '\0'; + int attr = CRT_colors[DEFAULT_COLOR]; + size_t n = sizeof(buffer) - 1; switch (field) { case COMM: { @@ -850,15 +559,15 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field baseattr = CRT_colors[PROCESS_THREAD_BASENAME]; } const ScreenSettings* ss = settings->ss; - if (!ss->treeView || this->indent == 0) { + if (!ss->treeView || super->indent == 0) { Process_writeCommand(this, attr, baseattr, str); return; } char* buf = buffer; - const bool lastItem = (this->indent < 0); + const bool lastItem = (super->indent < 0); - for (uint32_t indent = (this->indent < 0 ? -this->indent : this->indent); indent > 1; indent >>= 1) { + for (uint32_t indent = (super->indent < 0 ? -super->indent : super->indent); indent > 1; indent >>= 1) { int written, ret; if (indent & 1U) { ret = xSnprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]); @@ -875,7 +584,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field } const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE]; - xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] ); + xSnprintf(buf, n, "%s%s ", draw, super->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] ); RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer); Process_writeCommand(this, attr, baseattr, str); return; @@ -890,7 +599,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field procComm = Process_isKernelThread(this) ? kthreadID : "N/A"; } - Process_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1); + Row_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1); return; } case PROC_EXE: { @@ -909,7 +618,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field procExe = Process_isKernelThread(this) ? kthreadID : "N/A"; } - Process_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1); + Row_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1); return; } case CWD: { @@ -923,27 +632,27 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field } else { cwd = this->procCwd; } - Process_printLeftAlignedField(str, attr, cwd, 25); + Row_printLeftAlignedField(str, attr, cwd, 25); return; } case ELAPSED: { - const uint64_t rt = this->host->realtimeMs; + const uint64_t rt = host->realtimeMs; const uint64_t st = this->starttime_ctime * 1000; const uint64_t dt = rt < st ? 0 : rt - st; - Process_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring); + Row_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring); return; } - case MAJFLT: Process_printCount(str, this->majflt, coloring); return; - case MINFLT: Process_printCount(str, this->minflt, coloring); return; - case M_RESIDENT: Process_printKBytes(str, this->m_resident, coloring); return; - case M_VIRT: Process_printKBytes(str, this->m_virt, coloring); return; + case MAJFLT: Row_printCount(str, this->majflt, coloring); return; + case MINFLT: Row_printCount(str, this->minflt, coloring); return; + case M_RESIDENT: Row_printKBytes(str, this->m_resident, coloring); return; + case M_VIRT: Row_printKBytes(str, this->m_virt, coloring); return; case NICE: xSnprintf(buffer, n, "%3ld ", this->nice); attr = this->nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY] - : this->nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] - : CRT_colors[PROCESS_SHADOW]; + : this->nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] + : CRT_colors[PROCESS_SHADOW]; break; case NLWP: if (this->nlwp == 1) @@ -951,16 +660,16 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field xSnprintf(buffer, n, "%4ld ", this->nlwp); break; - case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break; + case PERCENT_CPU: Row_printPercentage(this->percent_cpu, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); break; case PERCENT_NORM_CPU: { - float cpuPercentage = this->percent_cpu / this->host->activeCPUs; - Process_printPercentage(cpuPercentage, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); + float cpuPercentage = this->percent_cpu / host->activeCPUs; + Row_printPercentage(cpuPercentage, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); break; } - case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, 4, &attr); break; + case PERCENT_MEM: Row_printPercentage(this->percent_mem, buffer, n, 4, &attr); break; case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break; - case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pid); break; - case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->ppid); break; + case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getPid(this)); break; + case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getParent(this)); break; case PRIORITY: if (this->priority <= -100) xSnprintf(buffer, n, " RT "); @@ -982,39 +691,39 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field case STATE: xSnprintf(buffer, n, "%c ", processStateChar(this->state)); switch (this->state) { - case RUNNABLE: - case RUNNING: - case TRACED: - attr = CRT_colors[PROCESS_RUN_STATE]; - break; - - case BLOCKED: - case DEFUNCT: - case STOPPED: - case UNINTERRUPTIBLE_WAIT: - case ZOMBIE: - attr = CRT_colors[PROCESS_D_STATE]; - break; - - case QUEUED: - case WAITING: - case IDLE: - case SLEEPING: - attr = CRT_colors[PROCESS_SHADOW]; - break; - - case UNKNOWN: - case PAGING: - break; + case RUNNABLE: + case RUNNING: + case TRACED: + attr = CRT_colors[PROCESS_RUN_STATE]; + break; + + case BLOCKED: + case DEFUNCT: + case STOPPED: + case UNINTERRUPTIBLE_WAIT: + case ZOMBIE: + attr = CRT_colors[PROCESS_D_STATE]; + break; + + case QUEUED: + case WAITING: + case IDLE: + case SLEEPING: + attr = CRT_colors[PROCESS_SHADOW]; + break; + + case UNKNOWN: + case PAGING: + break; } break; case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break; - case TIME: Process_printTime(str, this->time, coloring); return; + case TIME: Row_printTime(str, this->time, coloring); return; case TGID: - if (this->tgid == this->pid) + if (Process_getThreadGroup(this) == Process_getPid(this)) attr = CRT_colors[PROCESS_SHADOW]; - xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tgid); + xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getThreadGroup(this)); break; case TPGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tpgid); break; case TTY: @@ -1029,11 +738,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field case USER: if (this->elevated_priv) attr = CRT_colors[PROCESS_PRIV]; - else if (Process_getuid != this->st_uid) + else if (host->htopUserId != this->st_uid) attr = CRT_colors[PROCESS_SHADOW]; if (this->user) { - Process_printLeftAlignedField(str, attr, this->user, 10); + Row_printLeftAlignedField(str, attr, this->user, 10); return; } @@ -1046,37 +755,12 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field xSnprintf(buffer, n, "- "); break; } - RichString_appendAscii(str, attr, buffer); -} - -void Process_display(const Object* cast, RichString* out) { - const Process* this = (const Process*) cast; - const Settings* settings = this->host->settings; - const ProcessField* fields = settings->ss->fields; - for (int i = 0; fields[i]; i++) - As_Process(this)->writeField(this, out, fields[i]); - - if (settings->shadowOtherUsers && this->st_uid != Process_getuid) { - RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]); - } - if (this->tag == true) { - RichString_setAttr(out, CRT_colors[PROCESS_TAG]); - } - - if (settings->highlightChanges) { - if (Process_isTomb(this)) { - out->highlightAttr = CRT_colors[PROCESS_TOMB]; - } else if (Process_isNew(this)) { - out->highlightAttr = CRT_colors[PROCESS_NEW]; - } - } - - assert(RichString_size(out) > 0); + RichString_appendAscii(str, attr, buffer); } void Process_done(Process* this) { - assert (this != NULL); + assert(this != NULL); free(this->cmdline); free(this->procComm); free(this->procExe); @@ -1089,7 +773,8 @@ void Process_done(Process* this) { * happens on what is displayed - whether comm, full path, basename, etc.. So * this follows Process_writeField(COMM) and Process_writeCommand */ const char* Process_getCommand(const Process* this) { - const Settings* settings = this->host->settings; + const Settings* settings = this->super.host->settings; + if ((Process_isUserlandThread(this) && settings->showThreadNames) || !this->mergedCommand.str) { return this->cmdline; } @@ -1097,75 +782,113 @@ const char* Process_getCommand(const Process* this) { return this->mergedCommand.str; } -const ProcessClass Process_class = { - .super = { - .extends = Class(Object), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare - }, - .writeField = Process_writeField, -}; +static const char* Process_getSortKey(const Process* this) { + return Process_getCommand(this); +} -void Process_init(Process* this, const Machine* host) { - this->host = host; - this->tag = false; - this->showChildren = true; - this->show = true; - this->updated = false; - this->cmdlineBasenameEnd = -1; - this->st_uid = (uid_t)-1; +const char* Process_rowGetSortKey(Row* super) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_getSortKey(this); +} - if (Process_getuid == (uid_t)-1) { - Process_getuid = getuid(); - } +/* Test whether display must highlight this row (if the htop UID matches) */ +static bool Process_isHighlighted(const Process* this) { + const Machine* host = this->super.host; + const Settings* settings = host->settings; + return settings->shadowOtherUsers && this->st_uid != host->htopUserId; } -void Process_toggleTag(Process* this) { - this->tag = !this->tag; +bool Process_rowIsHighlighted(const Row* super) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_isHighlighted(this); } -bool Process_isNew(const Process* this) { - assert(this->host); - const Machine* host = this->host; - if (host->monotonicMs >= this->seenStampMs) { - const Settings* settings = host->settings; - return host->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)settings->highlightDelaySecs; - } +/* Test whether display must follow parent process (if this thread is hidden) */ +static bool Process_isVisible(const Process* p, const Settings* settings) { + if (settings->hideUserlandThreads) + return !Process_isThread(p); + return true; +} + +bool Process_rowIsVisible(const Row* super, const Table* table) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_isVisible(this, table->host->settings); +} + +/* Test whether display must filter out this process (various mechanisms) */ +static bool Process_matchesFilter(const Process* this, const Table* table) { + const Machine* host = table->host; + if (host->userId != (uid_t) -1 && this->st_uid != host->userId) + return true; + + const char* incFilter = table->incFilter; + if (incFilter && !String_contains_i(Process_getCommand(this), incFilter, true)) + return true; + + const ProcessTable* pt = (const ProcessTable*) host->activeTable; + assert(Object_isA((const Object*) pt, (const ObjectClass*) &ProcessTable_class)); + if (pt->pidMatchList && !Hashtable_get(pt->pidMatchList, Process_getThreadGroup(this))) + return true; + return false; } -bool Process_isTomb(const Process* this) { - return this->tombStampMs > 0; +bool Process_rowMatchesFilter(const Row* super, const Table* table) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_matchesFilter(this, table); } -bool Process_setPriority(Process* this, int priority) { +void Process_init(Process* this, const Machine* host) { + Row_init(&this->super, host); + + this->cmdlineBasenameEnd = -1; + this->st_uid = (uid_t)-1; +} + +static bool Process_setPriority(Process* this, int priority) { if (Settings_isReadonly()) return false; - int old_prio = getpriority(PRIO_PROCESS, this->pid); - int err = setpriority(PRIO_PROCESS, this->pid, priority); + int old_prio = getpriority(PRIO_PROCESS, Process_getPid(this)); + int err = setpriority(PRIO_PROCESS, Process_getPid(this), priority); - if (err == 0 && old_prio != getpriority(PRIO_PROCESS, this->pid)) { + if (err == 0 && old_prio != getpriority(PRIO_PROCESS, Process_getPid(this))) { this->nice = priority; } return (err == 0); } -bool Process_changePriorityBy(Process* this, Arg delta) { +bool Process_rowSetPriority(Row* super, int priority) { + Process* this = (Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_setPriority(this, priority); +} + +bool Process_rowChangePriorityBy(Row* super, Arg delta) { + Process* this = (Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); return Process_setPriority(this, this->nice + delta.i); } -bool Process_sendSignal(Process* this, Arg sgn) { - return kill(this->pid, sgn.i) == 0; +static bool Process_sendSignal(Process* this, Arg sgn) { + return kill(Process_getPid(this), sgn.i) == 0; +} + +bool Process_rowSendSignal(Row* super, Arg sgn) { + Process* this = (Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_sendSignal(this, sgn); } int Process_compare(const void* v1, const void* v2) { const Process* p1 = (const Process*)v1; const Process* p2 = (const Process*)v2; - const Settings* settings = p1->host->settings; - const ScreenSettings* ss = settings->ss; + const ScreenSettings* ss = p1->super.host->settings->ss; ProcessField key = ScreenSettings_getActiveSortKey(ss); @@ -1173,18 +896,27 @@ int Process_compare(const void* v1, const void* v2) { // Implement tie-breaker (needed to make tree mode more stable) if (!result) - return SPACESHIP_NUMBER(p1->pid, p2->pid); + return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result; } +int Process_compareByParent(const Row* r1, const Row* r2) { + int result = Row_compareByParent_Base(r1, r2); + + if (result != 0) + return result; + + return Process_compare(r1, r2); +} + int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) { int r; switch (key) { case PERCENT_CPU: case PERCENT_NORM_CPU: - return SPACESHIP_NUMBER(p1->percent_cpu, p2->percent_cpu); + return compareRealNumbers(p1->percent_cpu, p2->percent_cpu); case PERCENT_MEM: return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident); case COMM: @@ -1203,7 +935,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField return SPACESHIP_NULLSTR(p1->procCwd, p2->procCwd); case ELAPSED: r = -SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime); - return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid); + return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); case MAJFLT: return SPACESHIP_NUMBER(p1->majflt, p2->majflt); case MINFLT: @@ -1219,9 +951,9 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField case PGRP: return SPACESHIP_NUMBER(p1->pgrp, p2->pgrp); case PID: - return SPACESHIP_NUMBER(p1->pid, p2->pid); + return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); case PPID: - return SPACESHIP_NUMBER(p1->ppid, p2->ppid); + return SPACESHIP_NUMBER(Process_getParent(p1), Process_getParent(p2)); case PRIORITY: return SPACESHIP_NUMBER(p1->priority, p2->priority); case PROCESSOR: @@ -1232,7 +964,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField return SPACESHIP_NUMBER(p1->session, p2->session); case STARTTIME: r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime); - return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid); + return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); case STATE: return SPACESHIP_NUMBER(p1->state, p2->state); case ST_UID: @@ -1240,7 +972,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField case TIME: return SPACESHIP_NUMBER(p1->time, p2->time); case TGID: - return SPACESHIP_NUMBER(p1->tgid, p2->tgid); + return SPACESHIP_NUMBER(Process_getThreadGroup(p1), Process_getThreadGroup(p2)); case TPGID: return SPACESHIP_NUMBER(p1->tpgid, p2->tpgid); case TTY: @@ -1251,7 +983,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField default: CRT_debug("Process_compareByKey_Base() called with key %d", key); assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */ - return SPACESHIP_NUMBER(p1->pid, p2->pid); + return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); } } @@ -1329,36 +1061,33 @@ void Process_updateExe(Process* this, const char* exe) { 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); + Row_updateFieldWidth(PERCENT_CPU, 4); + Row_updateFieldWidth(PERCENT_NORM_CPU, 4); return; } // Add additional two characters, one for "." and another for precision. uint8_t width = ceil(log10(percentage + 0.1)) + 2; - Process_updateFieldWidth(PERCENT_CPU, width); - Process_updateFieldWidth(PERCENT_NORM_CPU, width); + Row_updateFieldWidth(PERCENT_CPU, width); + Row_updateFieldWidth(PERCENT_NORM_CPU, width); } + +const ProcessClass Process_class = { + .super = { + .super = { + .extends = Class(Row), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .sortKeyString = Process_rowGetSortKey, + .compareByParent = Process_compareByParent, + .writeField = Process_rowWriteField + }, +}; diff --git a/Process.h b/Process.h index ad89fd452..3903a4077 100644 --- a/Process.h +++ b/Process.h @@ -13,8 +13,9 @@ in the source distribution for its full text. #include #include "Object.h" -#include "ProcessField.h" #include "RichString.h" +#include "Row.h" +#include "RowField.h" #define PROCESS_FLAG_IO 0x00000001 @@ -23,45 +24,6 @@ in the source distribution for its full text. #define DEFAULT_HIGHLIGHT_SECS 5 -typedef enum ProcessField_ { - NULL_PROCESSFIELD = 0, - PID = 1, - COMM = 2, - STATE = 3, - PPID = 4, - PGRP = 5, - SESSION = 6, - TTY = 7, - TPGID = 8, - MINFLT = 10, - MAJFLT = 12, - PRIORITY = 18, - NICE = 19, - STARTTIME = 21, - PROCESSOR = 38, - M_VIRT = 39, - M_RESIDENT = 40, - ST_UID = 46, - PERCENT_CPU = 47, - PERCENT_MEM = 48, - USER = 49, - TIME = 50, - NLWP = 51, - TGID = 52, - PERCENT_NORM_CPU = 53, - ELAPSED = 54, - SCHEDULERPOLICY = 55, - PROC_COMM = 124, - PROC_EXE = 125, - CWD = 126, - - /* Platform specific fields, defined in ${platform}/ProcessField.h */ - PLATFORM_PROCESS_FIELDS - - /* Do not add new fields after this entry (dynamic entries follow) */ - LAST_PROCESSFIELD -} ProcessField; - /* Core process states (shared by platforms) * NOTE: The enum has an ordering that is important! * See processStateChar in process.c for ProcessSate -> letter mapping */ @@ -82,7 +44,8 @@ typedef enum ProcessState_ { SLEEPING } ProcessState; -struct Machine_; +struct Machine_; // IWYU pragma: keep +struct Settings_; // IWYU pragma: keep /* Holds information about regions of the cmdline that should be * highlighted (e.g. program basename, delimiter, comm). */ @@ -106,19 +69,7 @@ typedef struct ProcessMergedCommand_ { typedef struct Process_ { /* Super object for emulated OOP */ - Object super; - - /* Pointer to quasi-global data */ - const struct Machine_* host; - - /* Process identifier */ - pid_t pid; - - /* Parent process identifier */ - pid_t ppid; - - /* Thread group identifier */ - pid_t tgid; + Row super; /* Process group identifier */ int pgrp; @@ -232,36 +183,6 @@ typedef struct Process_ { /* Current scheduling policy */ int scheduling_policy; - /* 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; - - /* Has no known parent process */ - bool isRoot; - /* * Internal state for merged Command display */ @@ -291,39 +212,57 @@ typedef struct ProcessFieldData_ { bool autoWidth; } ProcessFieldData; +#define LAST_PROCESSFIELD LAST_RESERVED_FIELD +typedef int32_t ProcessField; /* see ReservedField list in RowField.h */ + // Implemented in platform-specific code: -void Process_writeField(const Process* this, RichString* str, ProcessField field); +void Process_writeField(const Process* row, RichString* str, ProcessField field); int Process_compare(const void* v1, const void* v2); +int Process_compareByParent(const Row* r1, const Row* v2); void Process_delete(Object* cast); extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; -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; +#define Process_pidDigits Row_pidDigits +#define Process_uidDigits Row_uidDigits typedef Process* (*Process_New)(const struct Machine_*); -typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField); typedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField); typedef struct ProcessClass_ { - const ObjectClass super; - const Process_WriteField writeField; + const RowClass super; const Process_CompareByKey compareByKey; } ProcessClass; -#define As_Process(this_) ((const ProcessClass*)((this_)->super.klass)) +#define As_Process(this_) ((const ProcessClass*)((this_)->super.super.klass)) + +#define Process_compareByKey(p1_, p2_, key_) (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_)) + + +static inline void Process_setPid(Process* this, pid_t pid) { + this->super.id = pid; +} + +static inline pid_t Process_getPid(const Process* this) { + return (pid_t)this->super.id; +} -#define Process_compareByKey(p1_, p2_, key_) (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_)) +static inline void Process_setThreadGroup(Process* this, pid_t pid) { + this->super.group = pid; +} + +static inline pid_t Process_getThreadGroup(const Process* this) { + return (pid_t)this->super.group; +} -static inline pid_t Process_getParentPid(const Process* this) { - return this->tgid == this->pid ? this->ppid : this->tgid; +static inline void Process_setParent(Process* this, pid_t pid) { + this->super.parent = pid; } -static inline bool Process_isChildOf(const Process* this, pid_t pid) { - return pid == Process_getParentPid(this); +static inline pid_t Process_getParent(const Process* this) { + return (pid_t)this->super.parent; +} + +static inline pid_t Process_getGroupOrParent(const Process* this) { + return Row_getGroupOrParent(&this->super); } static inline bool Process_isKernelThread(const Process* this) { @@ -344,68 +283,30 @@ static inline bool Process_isThread(const Process* this) { #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); - -/* Sets the size of the UID column based on the passed UID */ -void Process_setUidColumnWidth(uid_t maxUid); - -/* Takes number in bytes (base 1024). Prints 6 columns. */ -void Process_printBytes(RichString* str, unsigned long long number, bool coloring); - -/* Takes number in kilo bytes (base 1024). Prints 6 columns. */ -void Process_printKBytes(RichString* str, unsigned long long number, bool coloring); - -/* Takes number as count (base 1000). Prints 12 columns. */ -void Process_printCount(RichString* str, unsigned long long number, bool coloring); - -/* Takes time in hundredths of a seconds. Prints 9 columns. */ -void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring); - -/* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */ -void Process_printRate(RichString* str, double rate, bool coloring); - void Process_fillStarttimeBuffer(Process* this); -void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width); - -void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr); - -void Process_display(const Object* cast, RichString* out); - void Process_done(Process* this); extern const ProcessClass Process_class; void Process_init(Process* this, const struct Machine_* host); -void Process_toggleTag(Process* this); +const char* Process_rowGetSortKey(Row* super); + +bool Process_rowSetPriority(Row* super, int priority); -bool Process_isNew(const Process* this); +bool Process_rowChangePriorityBy(Row* super, Arg delta); -bool Process_isTomb(const Process* this); +bool Process_rowSendSignal(Row* super, Arg sgn); -bool Process_setPriority(Process* this, int priority); +bool Process_rowIsHighlighted(const Row* super); -bool Process_changePriorityBy(Process* this, Arg delta); +bool Process_rowIsVisible(const Row* super, const struct Table_* table); -bool Process_sendSignal(Process* this, Arg sgn); +bool Process_rowMatchesFilter(const Row* super, const struct Table_* table); static inline int Process_pidEqualCompare(const void* v1, const void* v2) { - const pid_t p1 = ((const Process*)v1)->pid; - const pid_t p2 = ((const Process*)v2)->pid; - return p1 != p2; /* return zero when equal */ + return Row_idEqualCompare(v1, v2); } int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key); @@ -418,12 +319,10 @@ void Process_updateExe(Process* this, const char* exe); /* This function constructs the string that is displayed by * Process_writeCommand and also returned by Process_getCommand */ -void Process_makeCommandStr(Process* this); +void Process_makeCommandStr(Process* this, const struct Settings_ *settings); void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str); -void Process_resetFieldWidths(void); -void Process_updateFieldWidth(ProcessField key, size_t width); void Process_updateCPUFieldWidths(float percentage); #endif diff --git a/ProcessList.c b/ProcessList.c deleted file mode 100644 index 58e63d030..000000000 --- a/ProcessList.c +++ /dev/null @@ -1,473 +0,0 @@ -/* -htop - ProcessList.c -(C) 2004,2005 Hisham H. Muhammad -Released under the GNU GPLv2+, see the COPYING file -in the source distribution for its full text. -*/ - -#include "ProcessList.h" - -#include -#include -#include - -#include "CRT.h" -#include "DynamicColumn.h" -#include "Hashtable.h" -#include "Macros.h" -#include "Platform.h" -#include "Vector.h" -#include "XUtils.h" - - -void ProcessList_init(ProcessList* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList) { - this->processes = Vector_new(klass, true, DEFAULT_SIZE); - this->displayList = Vector_new(klass, false, DEFAULT_SIZE); - this->processTable = Hashtable_new(200, false); - this->pidMatchList = pidMatchList; - this->needsSort = true; - this->following = -1; - this->host = host; -} - -void ProcessList_done(ProcessList* this) { - Hashtable_delete(this->processTable); - Vector_delete(this->displayList); - Vector_delete(this->processes); -} - -void ProcessList_setPanel(ProcessList* this, Panel* panel) { - this->panel = panel; -} - -// helper function to fill an aligned title string for a dynamic column -static const char* alignedTitleDynamicColumn(const Settings* settings, int key, char* titleBuffer, size_t titleBufferSize) { - const DynamicColumn* column = Hashtable_get(settings->dynamicColumns, key); - if (column == NULL) - return "- "; - int width = column->width; - if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) - width = DYNAMIC_DEFAULT_COLUMN_WIDTH; - xSnprintf(titleBuffer, titleBufferSize, "%*s", width, column->heading); - return titleBuffer; -} - -// helper function to fill an aligned title string for a process field -static const char* alignedTitleProcessField(ProcessField field, char* titleBuffer, size_t titleBufferSize) { - const char* title = Process_fields[field].title; - if (!title) - return "- "; - - if (Process_fields[field].pidColumn) { - xSnprintf(titleBuffer, titleBufferSize, "%*s ", Process_pidDigits, title); - return titleBuffer; - } - - if (field == ST_UID) { - xSnprintf(titleBuffer, titleBufferSize, "%*s ", Process_uidDigits, title); - return titleBuffer; - } - - if (Process_fields[field].autoWidth) { - if (field == PERCENT_CPU) - xSnprintf(titleBuffer, titleBufferSize, "%*s ", Process_fieldWidths[field], title); - else - xSnprintf(titleBuffer, titleBufferSize, "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title); - return titleBuffer; - } - - return title; -} - -// helper function to create an aligned title string for a given field -static const char* ProcessField_alignedTitle(const Settings* settings, 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 alignedTitleProcessField(field, titleBuffer, sizeof(titleBuffer)); - return alignedTitleDynamicColumn(settings, field, titleBuffer, sizeof(titleBuffer)); -} - -void ProcessList_printHeader(const ProcessList* this, RichString* header) { - RichString_rewind(header, RichString_size(header)); - - const Settings* settings = this->host->settings; - const ScreenSettings* ss = settings->ss; - const ProcessField* fields = ss->fields; - - ProcessField key = ScreenSettings_getActiveSortKey(ss); - - for (int i = 0; fields[i]; i++) { - int color; - if (ss->treeView && ss->treeViewAlwaysByPID) { - color = CRT_colors[PANEL_HEADER_FOCUS]; - } else if (key == fields[i]) { - color = CRT_colors[PANEL_SELECTION_FOCUS]; - } else { - color = CRT_colors[PANEL_HEADER_FOCUS]; - } - - RichString_appendWide(header, color, ProcessField_alignedTitle(settings, fields[i])); - if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') { - bool ascending = ScreenSettings_getActiveDirection(ss) == 1; - RichString_rewind(header, 1); // rewind to override space - RichString_appendnWide(header, - CRT_colors[PANEL_SELECTION_FOCUS], - CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC], - 1); - } - if (COMM == fields[i] && settings->showMergedCommand) { - RichString_appendAscii(header, color, "(merged)"); - } - } -} - -void ProcessList_add(ProcessList* this, Process* p) { - assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) == -1); - assert(Hashtable_get(this->processTable, p->pid) == NULL); - - // highlighting processes found in first scan by first scan marked "far in the past" - p->seenStampMs = this->host->monotonicMs; - - Vector_add(this->processes, p); - Hashtable_put(this->processTable, p->pid, p); - - assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) != -1); - assert(Hashtable_get(this->processTable, p->pid) != NULL); - assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable))); -} - -// ProcessList_removeIndex removes Process p from the list's map and soft deletes -// it from its vector. Vector_compact *must* be called once the caller is done -// removing items. -// Should only be called from ProcessList_scan to avoid breaking dying process highlighting. -static void ProcessList_removeIndex(ProcessList* this, const Process* p, int idx) { - pid_t pid = p->pid; - - assert(p == (Process*)Vector_get(this->processes, idx)); - assert(Hashtable_get(this->processTable, pid) != NULL); - - Hashtable_remove(this->processTable, pid); - Vector_softRemove(this->processes, idx); - - if (this->following != -1 && this->following == pid) { - this->following = -1; - Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); - } - - assert(Hashtable_get(this->processTable, pid) == NULL); - assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable))); -} - -static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, 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; - - // The vector is sorted by parent PID, find the start of the range by bisection - int vsize = Vector_size(this->processes); - int l = 0; - int r = vsize; - while (l < r) { - int c = (l + r) / 2; - Process* process = (Process*)Vector_get(this->processes, c); - pid_t ppid = process->isRoot ? 0 : Process_getParentPid(process); - if (ppid < pid) { - l = c + 1; - } else { - r = c; - } - } - // Find the end to know the last line for indent handling purposes - int lastShown = r; - while (r < vsize) { - Process* process = (Process*)Vector_get(this->processes, r); - if (!Process_isChildOf(process, pid)) - break; - if (process->show) - lastShown = r; - r++; - } - - for (int i = l; i < r; i++) { - Process* process = (Process*)Vector_get(this->processes, i); - - if (!show) { - process->show = false; - } - - Vector_add(this->displayList, process); - - 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; - } - - process->tree_depth = level + 1; - } -} - -static int compareProcessByKnownParentThenNatural(const void* v1, const void* v2) { - const Process* p1 = (const Process*)v1; - const Process* p2 = (const Process*)v2; - - int result = SPACESHIP_NUMBER( - p1->isRoot ? 0 : Process_getParentPid(p1), - p2->isRoot ? 0 : Process_getParentPid(p2) - ); - - if (result != 0) - return result; - - return Process_compare(v1, v2); -} - -// Builds a sorted tree from scratch, without relying on previously gathered information -static void ProcessList_buildTree(ProcessList* this) { - Vector_prune(this->displayList); - - // Mark root processes - int vsize = Vector_size(this->processes); - for (int i = 0; i < vsize; i++) { - Process* process = (Process*)Vector_get(this->processes, i); - pid_t ppid = Process_getParentPid(process); - process->isRoot = false; - - // If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0) - // on Mac OS X 10.11.6) regard this process as root. - if (process->pid == ppid) { - process->isRoot = true; - continue; - } - - // On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2) - // use a ppid of 0. As that PID can't exist, we can skip searching for it. - if (!ppid) { - process->isRoot = true; - continue; - } - - // We don't know about its parent for whatever reason - if (ProcessList_findProcess(this, ppid) == NULL) - process->isRoot = true; - } - - // Sort by known parent PID (roots first), then PID - Vector_quickSortCustomCompare(this->processes, compareProcessByKnownParentThenNatural); - - // Find all processes whose parent is not visible - for (int i = 0; i < vsize; i++) { - Process* process = (Process*)Vector_get(this->processes, i); - - // If parent not found, then construct the tree with this node as root - if (process->isRoot) { - process = (Process*)Vector_get(this->processes, i); - process->indent = 0; - process->tree_depth = 0; - Vector_add(this->displayList, process); - ProcessList_buildTreeBranch(this, process->pid, 0, 0, process->showChildren); - continue; - } - } - - this->needsSort = false; - - // Check consistency of the built structures - assert(Vector_size(this->displayList) == vsize); (void)vsize; -} - -void ProcessList_updateDisplayList(ProcessList* this) { - if (this->host->settings->ss->treeView) { - if (this->needsSort) - ProcessList_buildTree(this); - } else { - if (this->needsSort) - Vector_insertionSort(this->processes); - Vector_prune(this->displayList); - int size = Vector_size(this->processes); - for (int i = 0; i < size; i++) - Vector_add(this->displayList, Vector_get(this->processes, i)); - } - this->needsSort = false; -} - -ProcessField ProcessList_keyAt(const ProcessList* this, int at) { - int x = 0; - const Settings* settings = this->host->settings; - const ProcessField* fields = settings->ss->fields; - ProcessField field; - for (int i = 0; (field = fields[i]); i++) { - int len = strlen(ProcessField_alignedTitle(settings, field)); - if (at >= x && at <= x + len) { - return field; - } - x += len; - } - return COMM; -} - -void ProcessList_expandTree(ProcessList* this) { - int size = Vector_size(this->processes); - for (int i = 0; i < size; i++) { - Process* process = (Process*) Vector_get(this->processes, i); - process->showChildren = true; - } -} - -// Called on collapse-all toggle and on startup, possibly in non-tree mode -void ProcessList_collapseAllBranches(ProcessList* this) { - ProcessList_buildTree(this); // Update `tree_depth` fields of the processes - this->needsSort = true; // ProcessList is sorted by parent now, force new sort - int size = Vector_size(this->processes); - for (int i = 0; i < size; i++) { - Process* process = (Process*) Vector_get(this->processes, i); - // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1 - if (process->tree_depth > 0 && process->pid > 1) - process->showChildren = false; - } -} - -void ProcessList_rebuildPanel(ProcessList* this) { - ProcessList_updateDisplayList(this); - - const char* incFilter = this->incFilter; - - const int currPos = Panel_getSelectedIndex(this->panel); - const int currScrollV = this->panel->scrollV; - const int currSize = Panel_size(this->panel); - - Panel_prune(this->panel); - - /* Follow main process if followed a userland thread and threads are now hidden */ - const Machine* host= this->host; - const Settings* settings = host->settings; - if (this->following != -1 && settings->hideUserlandThreads) { - const Process* followedProcess = (const Process*) Hashtable_get(this->processTable, this->following); - if (followedProcess && Process_isThread(followedProcess) && Hashtable_get(this->processTable, followedProcess->tgid) != NULL) { - this->following = followedProcess->tgid; - } - } - - const int processCount = Vector_size(this->displayList); - int idx = 0; - bool foundFollowed = false; - - for (int i = 0; i < processCount; i++) { - Process* p = (Process*) Vector_get(this->displayList, i); - - if ( (!p->show) - || (host->userId != (uid_t) -1 && (p->st_uid != host->userId)) - || (incFilter && !(String_contains_i(Process_getCommand(p), incFilter, true))) - || (this->pidMatchList && !Hashtable_get(this->pidMatchList, p->tgid)) ) - continue; - - Panel_set(this->panel, idx, (Object*)p); - - if (this->following != -1 && p->pid == this->following) { - foundFollowed = true; - Panel_setSelected(this->panel, idx); - /* Keep scroll position relative to followed process */ - this->panel->scrollV = idx - (currPos-currScrollV); - } - idx++; - } - - if (this->following != -1 && !foundFollowed) { - /* Reset if current followed pid not found */ - this->following = -1; - Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); - } - - if (this->following == -1) { - /* If the last item was selected, keep the new last item selected */ - if (currPos > 0 && currPos == currSize - 1) - Panel_setSelected(this->panel, Panel_size(this->panel) - 1); - else - Panel_setSelected(this->panel, currPos); - - this->panel->scrollV = currScrollV; - } -} - -Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process_New constructor) { - Process* proc = (Process*) Hashtable_get(this->processTable, pid); - *preExisting = proc != NULL; - if (proc) { - assert(Vector_indexOf(this->processes, proc, Process_pidEqualCompare) != -1); - assert(proc->pid == pid); - } else { - proc = constructor(this->host); - assert(proc->cmdline == NULL); - proc->pid = pid; - } - return proc; -} - -void ProcessList_scan(ProcessList* this) { - // mark all process as "dirty" - for (int i = 0; i < Vector_size(this->processes); i++) { - Process* p = (Process*) Vector_get(this->processes, i); - p->updated = false; - p->wasShown = p->show; - p->show = true; - } - - this->totalTasks = 0; - this->userlandThreads = 0; - this->kernelThreads = 0; - this->runningTasks = 0; - - Process_resetFieldWidths(); - - // set scan timestamp - static bool firstScanDone = false; - Machine* host = this->host; - if (firstScanDone) { - Platform_gettime_monotonic(&host->monotonicMs); - } else { - host->monotonicMs = 0; - firstScanDone = true; - } - - ProcessList_goThroughEntries(this); - - uid_t maxUid = 0; - const Settings* settings = host->settings; - for (int i = Vector_size(this->processes) - 1; i >= 0; i--) { - Process* p = (Process*) Vector_get(this->processes, i); - Process_makeCommandStr(p); - - // keep track of the highest UID for column scaling - if (p->st_uid > maxUid) - maxUid = p->st_uid; - - if (p->tombStampMs > 0) { - // remove tombed process - if (host->monotonicMs >= p->tombStampMs) { - ProcessList_removeIndex(this, p, i); - } - } else if (p->updated == false) { - // process no longer exists - if (settings->highlightChanges && p->wasShown) { - // mark tombed - p->tombStampMs = host->monotonicMs + 1000 * settings->highlightDelaySecs; - } else { - // immediately remove - ProcessList_removeIndex(this, p, i); - } - } - } - - // Compact the processes vector in case of any deletions - Vector_compact(this->processes); - - // Set UID column width based on max UID. - Process_setUidColumnWidth(maxUid); -} diff --git a/ProcessList.h b/ProcessList.h deleted file mode 100644 index 0f0f7d517..000000000 --- a/ProcessList.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef HEADER_ProcessList -#define HEADER_ProcessList -/* -htop - ProcessList.h -(C) 2004,2005 Hisham H. Muhammad -Released under the GNU GPLv2+, see the COPYING file -in the source distribution for its full text. -*/ - -#include "config.h" // IWYU pragma: keep - -#include -#include -#include -#include -#include - -#include "Hashtable.h" -#include "Machine.h" -#include "Object.h" -#include "Panel.h" -#include "Process.h" -#include "RichString.h" -#include "Settings.h" -#include "UsersTable.h" -#include "Vector.h" - - -typedef struct ProcessList_ { - struct Machine_* host; - - 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 */ - - bool needsSort; - - Panel* panel; - int following; - const char* incFilter; - Hashtable* pidMatchList; - - unsigned int totalTasks; - unsigned int runningTasks; - unsigned int userlandThreads; - unsigned int kernelThreads; -} ProcessList; - -/* Implemented by platforms */ -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList); -void ProcessList_delete(ProcessList* this); -void ProcessList_goThroughEntries(ProcessList* this); - -void ProcessList_init(ProcessList* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList); - -void ProcessList_done(ProcessList* this); - -void ProcessList_setPanel(ProcessList* this, Panel* panel); - -void ProcessList_printHeader(const ProcessList* this, RichString* header); - -void ProcessList_add(ProcessList* this, Process* p); - -void ProcessList_updateDisplayList(ProcessList* this); - -ProcessField ProcessList_keyAt(const ProcessList* this, int at); - -void ProcessList_expandTree(ProcessList* this); - -void ProcessList_collapseAllBranches(ProcessList* this); - -void ProcessList_rebuildPanel(ProcessList* this); - -Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process_New constructor); - -void ProcessList_scan(ProcessList* this); - -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 57c9ce75f..36a37f927 100644 --- a/ProcessLocksScreen.c +++ b/ProcessLocksScreen.c @@ -24,9 +24,9 @@ ProcessLocksScreen* ProcessLocksScreen_new(const Process* process) { ProcessLocksScreen* this = xMalloc(sizeof(ProcessLocksScreen)); Object_setClass(this, Class(ProcessLocksScreen)); if (Process_isThread(process)) - this->pid = process->tgid; + this->pid = Process_getThreadGroup(process); else - this->pid = process->pid; + this->pid = Process_getPid(process); return (ProcessLocksScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE EXCLUSION READ/WRITE DEVICE NODE START END FILENAME"); } diff --git a/ProcessTable.c b/ProcessTable.c new file mode 100644 index 000000000..ac6fc705f --- /dev/null +++ b/ProcessTable.c @@ -0,0 +1,92 @@ +/* +htop - ProcessTable.c +(C) 2004,2005 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "ProcessTable.h" + +#include +#include + +#include "Hashtable.h" +#include "Row.h" +#include "Settings.h" +#include "Vector.h" + + +void ProcessTable_init(ProcessTable* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList) { + Table_init(&this->super, klass, host); + + this->pidMatchList = pidMatchList; +} + +void ProcessTable_done(ProcessTable* this) { + Table_done(&this->super); +} + +Process* ProcessTable_getProcess(ProcessTable* this, pid_t pid, bool* preExisting, Process_New constructor) { + const Table* table = &this->super; + Process* proc = (Process*) Hashtable_get(table->table, pid); + *preExisting = proc != NULL; + if (proc) { + assert(Vector_indexOf(table->rows, proc, Row_idEqualCompare) != -1); + assert(Process_getPid(proc) == pid); + } else { + proc = constructor(table->host); + assert(proc->cmdline == NULL); + Process_setPid(proc, pid); + } + return proc; +} + +static void ProcessTable_prepareEntries(Table* super) { + ProcessTable* this = (ProcessTable*) super; + this->totalTasks = 0; + this->userlandThreads = 0; + this->kernelThreads = 0; + this->runningTasks = 0; + + Table_prepareEntries(super); +} + +static void ProcessTable_iterateEntries(Table* super) { + ProcessTable* this = (ProcessTable*) super; + // calling into platform-specific code + ProcessTable_goThroughEntries(this); +} + +static void ProcessTable_cleanupEntries(Table* super) { + Machine* host = super->host; + const Settings* settings = host->settings; + + // Finish process table update, culling any exit'd processes + for (int i = Vector_size(super->rows) - 1; i >= 0; i--) { + Process* p = (Process*) Vector_get(super->rows, i); + + // tidy up Process state after refreshing the ProcessTable table + Process_makeCommandStr(p, settings); + + // keep track of the highest UID for column scaling + if (p->st_uid > host->maxUserId) + host->maxUserId = p->st_uid; + + Table_cleanupRow(super, (Row*) p, i); + } + + // compact the table in case of deletions + Table_compact(super); +} + +const TableClass ProcessTable_class = { + .super = { + .extends = Class(Table), + .delete = ProcessTable_delete, + }, + .prepare = ProcessTable_prepareEntries, + .iterate = ProcessTable_iterateEntries, + .cleanup = ProcessTable_cleanupEntries, +}; diff --git a/ProcessTable.h b/ProcessTable.h new file mode 100644 index 000000000..96a517a07 --- /dev/null +++ b/ProcessTable.h @@ -0,0 +1,52 @@ +#ifndef HEADER_ProcessTable +#define HEADER_ProcessTable +/* +htop - ProcessTable.h +(C) 2004,2005 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + +#include "Hashtable.h" +#include "Machine.h" +#include "Object.h" +#include "Process.h" +#include "Table.h" + + +typedef struct ProcessTable_ { + Table super; + + Hashtable* pidMatchList; + + unsigned int totalTasks; + unsigned int runningTasks; + unsigned int userlandThreads; + unsigned int kernelThreads; +} ProcessTable; + +/* Implemented by platforms */ +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList); +void ProcessTable_delete(Object* cast); +void ProcessTable_goThroughEntries(ProcessTable* this); + +void ProcessTable_init(ProcessTable* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList); + +void ProcessTable_done(ProcessTable* this); + +extern const TableClass ProcessTable_class; + +static inline void ProcessTable_add(ProcessTable* this, Process* process) { + Table_add(&this->super, &process->super); +} + +Process* ProcessTable_getProcess(ProcessTable* this, pid_t pid, bool* preExisting, Process_New constructor); + +static inline Process* ProcessTable_findProcess(ProcessTable* this, pid_t pid) { + return (Process*) Table_findRow(&this->super, pid); +} + +#endif diff --git a/ProvideCurses.h b/ProvideCurses.h index 06602ff06..7ae99e620 100644 --- a/ProvideCurses.h +++ b/ProvideCurses.h @@ -1,14 +1,14 @@ #ifndef HEADER_ProvideCurses #define HEADER_ProvideCurses /* -htop - RichString.h +htop - ProvideCurses.h (C) 2004,2011 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" +#include "config.h" // IWYU pragma: keep // IWYU pragma: begin_exports diff --git a/ProvideTerm.h b/ProvideTerm.h index 0e07b1c49..a8910f1af 100644 --- a/ProvideTerm.h +++ b/ProvideTerm.h @@ -1,7 +1,7 @@ #ifndef HEADER_ProvideTerm #define HEADER_ProvideTerm /* -htop - Filename.h +htop - ProvideTerm.h (C) 2023 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. diff --git a/RichString.c b/RichString.c index daa0c91f9..ed852ada0 100644 --- a/RichString.c +++ b/RichString.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "RichString.h" #include diff --git a/RichString.h b/RichString.h index 9a09166c2..70ca2748e 100644 --- a/RichString.h +++ b/RichString.h @@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" - #include "ProvideCurses.h" diff --git a/Row.c b/Row.c new file mode 100644 index 000000000..9ea3f0731 --- /dev/null +++ b/Row.c @@ -0,0 +1,497 @@ +/* +htop - Row.c +(C) 2004-2015 Hisham H. Muhammad +(C) 2020-2023 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "Row.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "DynamicColumn.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Macros.h" +#include "Process.h" +#include "RichString.h" +#include "Settings.h" +#include "XUtils.h" + + +int Row_pidDigits = ROW_MIN_PID_DIGITS; +int Row_uidDigits = ROW_MIN_UID_DIGITS; + +void Row_init(Row* this, const Machine* host) { + this->host = host; + this->tag = false; + this->showChildren = true; + this->show = true; + this->wasShown = false; + this->updated = false; +} + +void Row_done(Row* this) { + assert(this != NULL); + (void) this; +} + +static inline bool Row_isNew(const Row* this) { + const Machine* host = this->host; + if (host->monotonicMs < this->seenStampMs) + return false; + + const Settings* settings = host->settings; + return host->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)settings->highlightDelaySecs; +} + +static inline bool Row_isTomb(const Row* this) { + return this->tombStampMs > 0; +} + +void Row_display(const Object* cast, RichString* out) { + const Row* this = (const Row*) cast; + const Settings* settings = this->host->settings; + const RowField* fields = settings->ss->fields; + + for (int i = 0; fields[i]; i++) + As_Row(this)->writeField(this, out, fields[i]); + + if (Row_isHighlighted(this)) + RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]); + + if (this->tag == true) + RichString_setAttr(out, CRT_colors[PROCESS_TAG]); + + if (settings->highlightChanges) { + if (Row_isTomb(this)) + out->highlightAttr = CRT_colors[PROCESS_TOMB]; + else if (Row_isNew(this)) + out->highlightAttr = CRT_colors[PROCESS_NEW]; + } + + assert(RichString_size(out) > 0); +} + +void Row_setPidColumnWidth(pid_t maxPid) { + if (maxPid < (int)pow(10, ROW_MIN_PID_DIGITS)) { + Row_pidDigits = ROW_MIN_PID_DIGITS; + return; + } + + Row_pidDigits = (int)log10(maxPid) + 1; + assert(Row_pidDigits <= ROW_MAX_PID_DIGITS); +} + +void Row_setUidColumnWidth(uid_t maxUid) { + if (maxUid < (uid_t)pow(10, ROW_MIN_UID_DIGITS)) { + Row_uidDigits = ROW_MIN_UID_DIGITS; + return; + } + + Row_uidDigits = (int)log10(maxUid) + 1; + assert(Row_uidDigits <= ROW_MAX_UID_DIGITS); +} + +uint8_t Row_fieldWidths[LAST_PROCESSFIELD] = { 0 }; + +void Row_resetFieldWidths(void) { + for (size_t i = 0; i < LAST_PROCESSFIELD; i++) { + if (!Process_fields[i].autoWidth) + continue; + + size_t len = strlen(Process_fields[i].title); + assert(len <= UINT8_MAX); + Row_fieldWidths[i] = (uint8_t)len; + } +} + +void Row_updateFieldWidth(RowField key, size_t width) { + if (width > UINT8_MAX) + Row_fieldWidths[key] = UINT8_MAX; + else if (width > Row_fieldWidths[key]) + Row_fieldWidths[key] = (uint8_t)width; +} + +// helper function to fill an aligned title string for a dynamic column +static const char* alignedTitleDynamicColumn(const Settings* settings, int key, char* titleBuffer, size_t titleBufferSize) { + const DynamicColumn* column = Hashtable_get(settings->dynamicColumns, key); + if (column == NULL) + return "- "; + + int width = column->width; + if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) + width = DYNAMIC_DEFAULT_COLUMN_WIDTH; + + xSnprintf(titleBuffer, titleBufferSize, "%*s ", width, column->heading); + return titleBuffer; +} + +// helper function to fill an aligned title string for a process field +static const char* alignedTitleProcessField(ProcessField field, char* titleBuffer, size_t titleBufferSize) { + const char* title = Process_fields[field].title; + if (!title) + return "- "; + + if (Process_fields[field].pidColumn) { + xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_pidDigits, title); + return titleBuffer; + } + + if (field == ST_UID) { + xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_uidDigits, title); + return titleBuffer; + } + + if (Process_fields[field].autoWidth) { + if (field == PERCENT_CPU) + xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_fieldWidths[field], title); + else + xSnprintf(titleBuffer, titleBufferSize, "%-*.*s ", Row_fieldWidths[field], Row_fieldWidths[field], title); + return titleBuffer; + } + + return title; +} + +// helper function to create an aligned title string for a given field +const char* RowField_alignedTitle(const Settings* settings, RowField field) { + static char titleBuffer[UINT8_MAX + sizeof(" ")]; + assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" ")); + assert(sizeof(titleBuffer) >= ROW_MAX_PID_DIGITS + sizeof(" ")); + assert(sizeof(titleBuffer) >= ROW_MAX_UID_DIGITS + sizeof(" ")); + + if (field < LAST_PROCESSFIELD) + return alignedTitleProcessField((ProcessField)field, titleBuffer, sizeof(titleBuffer)); + return alignedTitleDynamicColumn(settings, field, titleBuffer, sizeof(titleBuffer)); +} + +RowField RowField_keyAt(const Settings* settings, int at) { + const RowField* fields = (const RowField*) settings->ss->fields; + RowField field; + int x = 0; + for (int i = 0; (field = fields[i]); i++) { + int len = strlen(RowField_alignedTitle(settings, field)); + if (at >= x && at <= x + len) { + return field; + } + x += len; + } + return COMM; +} + +void Row_printKBytes(RichString* str, unsigned long long number, bool coloring) { + char buffer[16]; + int len; + + int color = CRT_colors[PROCESS]; + int nextUnitColor = CRT_colors[PROCESS]; + + const int colors[4] = { + [0] = CRT_colors[PROCESS], + [1] = CRT_colors[PROCESS_MEGABYTES], + [2] = CRT_colors[PROCESS_GIGABYTES], + [3] = CRT_colors[LARGE_NUMBER] + }; + + if (number == ULLONG_MAX) + goto invalidNumber; + + if (coloring) { + color = colors[0]; + nextUnitColor = colors[1]; + } + + if (number < 1000) { + // Plain number, no markings + len = xSnprintf(buffer, sizeof(buffer), "%5u ", (unsigned int)number); + RichString_appendnAscii(str, color, buffer, len); + return; + } + + if (number < 100000) { + // 2 digits for M, 3 digits for K + len = xSnprintf(buffer, sizeof(buffer), "%2u", (unsigned int)(number / 1000)); + RichString_appendnAscii(str, nextUnitColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%03u ", (unsigned int)(number % 1000)); + RichString_appendnAscii(str, color, buffer, len); + return; + } + + // 100000 KiB (97.6 MiB) or greater. A unit prefix would be added. + const size_t maxUnitIndex = (sizeof(number) * CHAR_BIT - 1) / 10 + 1; + const bool canOverflow = maxUnitIndex >= ARRAYSIZE(unitPrefixes); + + size_t i = 1; + int prevUnitColor; + // Convert KiB to (1/100) of MiB + unsigned long long hundredths = (number / 256) * 25 + (number % 256) * 25 / 256; + while (true) { + if (canOverflow && i >= ARRAYSIZE(unitPrefixes)) + goto invalidNumber; + + prevUnitColor = color; + color = nextUnitColor; + + if (coloring && i + 1 < ARRAYSIZE(colors)) + nextUnitColor = colors[i + 1]; + + if (hundredths < 1000000) + break; + + hundredths /= ONE_K; + i++; + } + + number = hundredths / 100; + hundredths %= 100; + if (number < 100) { + if (number < 10) { + // 1 digit + decimal point + 2 digits + // "9.76G", "9.99G", "9.76T", "9.99T", etc. + len = xSnprintf(buffer, sizeof(buffer), "%1u", (unsigned int)number); + RichString_appendnAscii(str, color, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), ".%02u", (unsigned int)hundredths); + } else { + // 2 digits + decimal point + 1 digit + // "97.6M", "99.9M", "10.0G", "99.9G", etc. + len = xSnprintf(buffer, sizeof(buffer), "%2u", (unsigned int)number); + RichString_appendnAscii(str, color, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), ".%1u", (unsigned int)hundredths / 10); + } + RichString_appendnAscii(str, prevUnitColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%c ", unitPrefixes[i]); + } else if (number < 1000) { + // 3 digits + // "100M", "999M", "100G", "999G", etc. + len = xSnprintf(buffer, sizeof(buffer), "%4u%c ", (unsigned int)number, unitPrefixes[i]); + } else { + // 1 digit + 3 digits + // "1000M", "9999M", "1000G", "9999G", etc. + assert(number < 10000); + + len = xSnprintf(buffer, sizeof(buffer), "%1u", (unsigned int)number / 1000); + RichString_appendnAscii(str, nextUnitColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%03u%c ", (unsigned int)number % 1000, unitPrefixes[i]); + } + RichString_appendnAscii(str, color, buffer, len); + return; + +invalidNumber: + if (coloring) + color = CRT_colors[PROCESS_SHADOW]; + + RichString_appendAscii(str, color, " N/A "); + return; +} + +void Row_printBytes(RichString* str, unsigned long long number, bool coloring) { + if (number == ULLONG_MAX) + Row_printKBytes(str, ULLONG_MAX, coloring); + else + Row_printKBytes(str, number / ONE_K, coloring); +} + +void Row_printCount(RichString* str, unsigned long long number, bool coloring) { + char buffer[13]; + + int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; + int megabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; + int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS]; + int baseColor = CRT_colors[PROCESS]; + + if (number == ULLONG_MAX) { + RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A "); + } else if (number >= 100000LL * ONE_DECIMAL_T) { + xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G); + RichString_appendnAscii(str, largeNumberColor, buffer, 12); + } else if (number >= 100LL * ONE_DECIMAL_T) { + xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M); + RichString_appendnAscii(str, largeNumberColor, buffer, 8); + RichString_appendnAscii(str, megabytesColor, buffer + 8, 4); + } else if (number >= 10LL * ONE_DECIMAL_G) { + xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_K); + RichString_appendnAscii(str, largeNumberColor, buffer, 5); + RichString_appendnAscii(str, megabytesColor, buffer + 5, 3); + RichString_appendnAscii(str, baseColor, buffer + 8, 4); + } else { + xSnprintf(buffer, sizeof(buffer), "%11llu ", number); + RichString_appendnAscii(str, largeNumberColor, buffer, 2); + RichString_appendnAscii(str, megabytesColor, buffer + 2, 3); + RichString_appendnAscii(str, baseColor, buffer + 5, 3); + RichString_appendnAscii(str, shadowColor, buffer + 8, 4); + } +} + +void Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) { + char buffer[10]; + int len; + + int yearColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; + int dayColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS]; + int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; + int baseColor = CRT_colors[PROCESS]; + + unsigned long long totalSeconds = totalHundredths / 100; + unsigned long long totalMinutes = totalSeconds / 60; + unsigned long long totalHours = totalMinutes / 60; + unsigned int seconds = totalSeconds % 60; + unsigned int minutes = totalMinutes % 60; + + if (totalMinutes < 60) { + unsigned int hundredths = totalHundredths % 100; + len = xSnprintf(buffer, sizeof(buffer), "%2u:%02u.%02u ", (unsigned int)totalMinutes, seconds, hundredths); + RichString_appendnAscii(str, baseColor, buffer, len); + return; + } + if (totalHours < 24) { + len = xSnprintf(buffer, sizeof(buffer), "%2uh", (unsigned int)totalHours); + RichString_appendnAscii(str, hourColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%02u:%02u ", minutes, seconds); + RichString_appendnAscii(str, baseColor, buffer, len); + return; + } + + unsigned long long totalDays = totalHours / 24; + unsigned int hours = totalHours % 24; + if (totalDays < 10) { + len = xSnprintf(buffer, sizeof(buffer), "%1ud", (unsigned int)totalDays); + RichString_appendnAscii(str, dayColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%02uh", hours); + RichString_appendnAscii(str, hourColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%02um ", minutes); + RichString_appendnAscii(str, baseColor, buffer, len); + return; + } + if (totalDays < /* Ignore leap years */365) { + len = xSnprintf(buffer, sizeof(buffer), "%4ud", (unsigned int)totalDays); + RichString_appendnAscii(str, dayColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%02uh ", hours); + RichString_appendnAscii(str, hourColor, buffer, len); + return; + } + + unsigned long long years = totalDays / 365; + unsigned int days = totalDays % 365; + if (years < 1000) { + len = xSnprintf(buffer, sizeof(buffer), "%3uy", (unsigned int)years); + RichString_appendnAscii(str, yearColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%03ud ", days); + RichString_appendnAscii(str, dayColor, buffer, len); + } else if (years < 10000000) { + len = xSnprintf(buffer, sizeof(buffer), "%7luy ", (unsigned long)years); + RichString_appendnAscii(str, yearColor, buffer, len); + } else { + RichString_appendAscii(str, yearColor, "eternity "); + } +} + +void Row_printRate(RichString* str, double rate, bool coloring) { + char buffer[16]; + + int largeNumberColor = CRT_colors[LARGE_NUMBER]; + int megabytesColor = CRT_colors[PROCESS_MEGABYTES]; + int shadowColor = CRT_colors[PROCESS_SHADOW]; + int baseColor = CRT_colors[PROCESS]; + + if (!coloring) { + largeNumberColor = CRT_colors[PROCESS]; + megabytesColor = CRT_colors[PROCESS]; + } + + if (!isNonnegative(rate)) { + RichString_appendAscii(str, shadowColor, " N/A "); + } else if (rate < 0.005) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate); + RichString_appendnAscii(str, shadowColor, buffer, len); + } else if (rate < ONE_K) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate); + RichString_appendnAscii(str, baseColor, buffer, len); + } else if (rate < ONE_M) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K); + RichString_appendnAscii(str, baseColor, buffer, len); + } else if (rate < ONE_G) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M); + RichString_appendnAscii(str, megabytesColor, buffer, len); + } else if (rate < ONE_T) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f G/s ", rate / ONE_G); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + } else if (rate < ONE_P) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f T/s ", rate / ONE_T); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + } else { + int len = snprintf(buffer, sizeof(buffer), "%7.2f P/s ", rate / ONE_P); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + } +} + +void Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) { + int columns = width; + RichString_appendnWideColumns(str, attr, content, strlen(content), &columns); + RichString_appendChr(str, attr, ' ', width + 1 - columns); +} + +int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) { + if (isNonnegative(val)) { + if (val < 0.05F) + *attr = CRT_colors[PROCESS_SHADOW]; + else if (val >= 99.9F) + *attr = CRT_colors[PROCESS_MEGABYTES]; + + int precision = 1; + + // Display "val" as "100" for columns like "MEM%". + if (width == 4 && val > 99.9F) { + precision = 0; + val = 100.0F; + } + + return xSnprintf(buffer, n, "%*.*f ", width, precision, val); + } + + *attr = CRT_colors[PROCESS_SHADOW]; + return xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); +} + +void Row_toggleTag(Row* this) { + this->tag = !this->tag; +} + +int Row_compare(const void* v1, const void* v2) { + const Row* r1 = (const Row*)v1; + const Row* r2 = (const Row*)v2; + + return SPACESHIP_NUMBER(r1->id, r2->id); +} + +int Row_compareByParent_Base(const void* v1, const void* v2) { + const Row* r1 = (const Row*)v1; + const Row* r2 = (const Row*)v2; + + int result = SPACESHIP_NUMBER( + r1->isRoot ? 0 : Row_getGroupOrParent(r1), + r2->isRoot ? 0 : Row_getGroupOrParent(r2) + ); + + if (result != 0) + return result; + + return Row_compare(v1, v2); +} + +const RowClass Row_class = { + .super = { + .extends = Class(Object), + .compare = Row_compare + }, +}; diff --git a/Row.h b/Row.h new file mode 100644 index 000000000..f67c61036 --- /dev/null +++ b/Row.h @@ -0,0 +1,181 @@ +#ifndef HEADER_Row +#define HEADER_Row +/* +htop - Row.h +(C) 2004-2015 Hisham H. Muhammad +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include +#include +#include + +#include "Object.h" +#include "RichString.h" +#include "RowField.h" + + +extern uint8_t Row_fieldWidths[LAST_RESERVED_FIELD]; +#define ROW_MIN_PID_DIGITS 5 +#define ROW_MAX_PID_DIGITS 19 +#define ROW_MIN_UID_DIGITS 5 +#define ROW_MAX_UID_DIGITS 20 +extern int Row_pidDigits; +extern int Row_uidDigits; + +struct Machine_; // IWYU pragma: keep +struct Settings_; // IWYU pragma: keep +struct Table_; // IWYU pragma: keep + +/* Class representing entities (such as processes) that can be + * represented in a tabular form in the lower half of the htop + * display. */ + +typedef struct Row_ { + /* Super object for emulated OOP */ + Object super; + + /* Pointer to quasi-global data */ + const struct Machine_* host; + + int id; + int group; + int parent; + + /* Has no known parent */ + bool isRoot; + + /* Whether the row was tagged by the user */ + bool tag; + + /* Whether to display this row */ + bool show; + + /* Whether this row was shown last cycle */ + bool wasShown; + + /* Whether to show children of this row in tree-mode */ + bool showChildren; + + /* Whether the row was updated during the last scan */ + bool updated; + + /* + * Internal state for tree-mode. + */ + int32_t indent; + unsigned int tree_depth; + + /* + * Internal time counts for showing new and exited processes. + */ + uint64_t seenStampMs; + uint64_t tombStampMs; +} Row; + +typedef Row* (*Row_New)(const struct Machine_*); +typedef void (*Row_WriteField)(const Row*, RichString*, RowField); +typedef bool (*Row_IsHighlighted)(const Row*); +typedef bool (*Row_IsVisible)(const Row*, const struct Table_*); +typedef bool (*Row_MatchesFilter)(const Row*, const struct Table_*); +typedef const char* (*Row_SortKeyString)(Row*); +typedef int (*Row_CompareByParent)(const Row*, const Row*); + +int Row_compare(const void* v1, const void* v2); + +typedef struct RowClass_ { + const ObjectClass super; + const Row_IsHighlighted isHighlighted; + const Row_IsVisible isVisible; + const Row_WriteField writeField; + const Row_MatchesFilter matchesFilter; + const Row_SortKeyString sortKeyString; + const Row_CompareByParent compareByParent; +} RowClass; + +#define As_Row(this_) ((const RowClass*)((this_)->super.klass)) + +#define Row_isHighlighted(r_) (As_Row(r_)->isHighlighted ? (As_Row(r_)->isHighlighted(r_)) : false) +#define Row_isVisible(r_, t_) (As_Row(r_)->isVisible ? (As_Row(r_)->isVisible(r_, t_)) : true) +#define Row_matchesFilter(r_, t_) (As_Row(r_)->matchesFilter ? (As_Row(r_)->matchesFilter(r_, t_)) : false) +#define Row_sortKeyString(r_) (As_Row(r_)->sortKeyString ? (As_Row(r_)->sortKeyString(r_)) : "") +#define Row_compareByParent(r1_, r2_) (As_Row(r1_)->compareByParent ? (As_Row(r1_)->compareByParent(r1_, r2_)) : Row_compareByParent_Base(r1_, r2_)) + +#define ONE_K 1024UL +#define ONE_M (ONE_K * ONE_K) +#define ONE_G (ONE_M * ONE_K) +#define ONE_T (1ULL * ONE_G * ONE_K) +#define ONE_P (1ULL * ONE_T * ONE_K) + +#define ONE_DECIMAL_K 1000UL +#define ONE_DECIMAL_M (ONE_DECIMAL_K * ONE_DECIMAL_K) +#define ONE_DECIMAL_G (ONE_DECIMAL_M * ONE_DECIMAL_K) +#define ONE_DECIMAL_T (1ULL * ONE_DECIMAL_G * ONE_DECIMAL_K) +#define ONE_DECIMAL_P (1ULL * ONE_DECIMAL_T * ONE_DECIMAL_K) + +extern const RowClass Row_class; + +void Row_init(Row* this, const struct Machine_* host); + +void Row_done(Row* this); + +void Row_display(const Object* cast, RichString* out); + +void Row_toggleTag(Row* this); + +void Row_resetFieldWidths(void); + +void Row_updateFieldWidth(RowField key, size_t width); + +void Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width); + +const char* RowField_alignedTitle(const struct Settings_* settings, RowField field); + +RowField RowField_keyAt(const struct Settings_* settings, int at); + +/* Sets the size of the PID column based on the passed PID */ +void Row_setPidColumnWidth(pid_t maxPid); + +/* Sets the size of the UID column based on the passed UID */ +void Row_setUidColumnWidth(uid_t maxUid); + +/* Takes number in kibibytes (base 1024). Prints 6 columns. */ +void Row_printKBytes(RichString* str, unsigned long long number, bool coloring); + +/* Takes number in bytes (base 1024). Prints 6 columns. */ +void Row_printBytes(RichString* str, unsigned long long number, bool coloring); + +/* Takes number as count (base 1000). Prints 12 columns. */ +void Row_printCount(RichString* str, unsigned long long number, bool coloring); + +/* Takes time in hundredths of a seconds. Prints 9 columns. */ +void Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring); + +/* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */ +void Row_printRate(RichString* str, double rate, bool coloring); + +int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr); + +void Row_display(const Object* cast, RichString* out); + +static inline int Row_idEqualCompare(const void* v1, const void* v2) { + const int p1 = ((const Row*)v1)->id; + const int p2 = ((const Row*)v2)->id; + return p1 != p2; /* return zero when equal */ +} + +/* Routines used primarily with the tree view */ +static inline int Row_getGroupOrParent(const Row* this) { + return this->group == this->id ? this->parent : this->group; +} + +static inline bool Row_isChildOf(const Row* this, int id) { + return id == Row_getGroupOrParent(this); +} + +int Row_compareByParent_Base(const void* v1, const void* v2); + +#endif diff --git a/RowField.h b/RowField.h new file mode 100644 index 000000000..1e01ea3e9 --- /dev/null +++ b/RowField.h @@ -0,0 +1,56 @@ +#ifndef HEADER_RowField +#define HEADER_RowField +/* +htop - RowField.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "ProcessField.h" // platform-specific fields reserved for processes + + +typedef enum ReservedFields_ { + NULL_FIELD = 0, + PID = 1, + COMM = 2, + STATE = 3, + PPID = 4, + PGRP = 5, + SESSION = 6, + TTY = 7, + TPGID = 8, + MINFLT = 10, + MAJFLT = 12, + PRIORITY = 18, + NICE = 19, + STARTTIME = 21, + PROCESSOR = 38, + M_VIRT = 39, + M_RESIDENT = 40, + ST_UID = 46, + PERCENT_CPU = 47, + PERCENT_MEM = 48, + USER = 49, + TIME = 50, + NLWP = 51, + TGID = 52, + PERCENT_NORM_CPU = 53, + ELAPSED = 54, + SCHEDULERPOLICY = 55, + PROC_COMM = 124, + PROC_EXE = 125, + CWD = 126, + + /* Platform specific fields, defined in ${platform}/ProcessField.h */ + PLATFORM_PROCESS_FIELDS + + /* Do not add new fields after this entry (dynamic entries follow) */ + LAST_RESERVED_FIELD +} ReservedFields; + +/* Follow ReservedField entries with dynamic fields defined at runtime */ +#define ROW_DYNAMIC_FIELDS LAST_RESERVED_FIELD +typedef int32_t RowField; + +#endif diff --git a/Scheduling.c b/Scheduling.c index 5ca49ae2d..d5c4b8a66 100644 --- a/Scheduling.c +++ b/Scheduling.c @@ -5,12 +5,14 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "Scheduling.h" -#include "EnvScreen.h" #ifdef SCHEDULER_SUPPORT -#include +#include +#include #include "FunctionBar.h" #include "ListItem.h" @@ -97,7 +99,7 @@ Panel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority) { return this; } -bool Scheduling_setPolicy(Process* proc, Arg arg) { +static bool Scheduling_setPolicy(Process* p, Arg arg) { const SchedulingArg* sarg = arg.v; int policy = sarg->policy; @@ -112,43 +114,49 @@ bool Scheduling_setPolicy(Process* proc, Arg arg) { policy &= SCHED_RESET_ON_FORK; #endif - int r = sched_setscheduler(proc->pid, policy, ¶m); + int r = sched_setscheduler(Process_getPid(p), policy, ¶m); /* POSIX says on success the previous scheduling policy should be returned, * but Linux always returns 0. */ return r != -1; } +bool Scheduling_rowSetPolicy(Row* row, Arg arg) { + Process* p = (Process*) row; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return Scheduling_setPolicy(p, arg); +} + const char* Scheduling_formatPolicy(int policy) { #ifdef SCHED_RESET_ON_FORK policy = policy & ~SCHED_RESET_ON_FORK; #endif switch (policy) { - case SCHED_OTHER: - return "OTHER"; - case SCHED_FIFO: - return "FIFO"; - case SCHED_RR: - return "RR"; + case SCHED_OTHER: + return "OTHER"; + case SCHED_FIFO: + return "FIFO"; + case SCHED_RR: + return "RR"; #ifdef SCHED_BATCH - case SCHED_BATCH: - return "BATCH"; + case SCHED_BATCH: + return "BATCH"; #endif #ifdef SCHED_IDLE - case SCHED_IDLE: - return "IDLE"; + case SCHED_IDLE: + return "IDLE"; #endif #ifdef SCHED_DEADLINE - case SCHED_DEADLINE: - return "EDF"; + case SCHED_DEADLINE: + return "EDF"; #endif - default: - return "???"; + default: + return "???"; } } void Scheduling_readProcessPolicy(Process* proc) { - proc->scheduling_policy = sched_getscheduler(proc->pid); + proc->scheduling_policy = sched_getscheduler(Process_getPid(proc)); } #endif /* SCHEDULER_SUPPORT */ diff --git a/Scheduling.h b/Scheduling.h index d91855ae4..610503cf2 100644 --- a/Scheduling.h +++ b/Scheduling.h @@ -7,12 +7,13 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include +#include "Object.h" #include "Panel.h" +#include "Process.h" +#include "Row.h" #if defined(HAVE_SCHED_SETSCHEDULER) && defined(HAVE_SCHED_GETSCHEDULER) @@ -38,7 +39,7 @@ typedef struct { int priority; } SchedulingArg; -bool Scheduling_setPolicy(Process* proc, Arg arg); +bool Scheduling_rowSetPolicy(Row* proc, Arg arg); const char* Scheduling_formatPolicy(int policy); diff --git a/ScreenManager.c b/ScreenManager.c index a089eda10..e7e82e1df 100644 --- a/ScreenManager.c +++ b/ScreenManager.c @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include #include +#include #include #include "CRT.h" @@ -20,8 +21,10 @@ in the source distribution for its full text. #include "Macros.h" #include "Object.h" #include "Platform.h" -#include "ProcessList.h" +#include "Process.h" #include "ProvideCurses.h" +#include "Settings.h" +#include "Table.h" #include "XUtils.h" @@ -133,13 +136,14 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi *oldTime = newTime; int oldUidDigits = Process_uidDigits; if (!this->state->pauseUpdate && (*sortTimeout == 0 || host->settings->ss->treeView)) { - host->pl->needsSort = true; + host->activeTable->needsSort = true; *sortTimeout = 1; } // sample current values for system metrics and processes if not paused Machine_scan(host); if (!this->state->pauseUpdate) - ProcessList_scan(host->pl); + Machine_scanTables(host); + // always update header, especially to avoid gaps in graph meters Header_updateData(this->header); // force redraw if the number of UID digits was changed @@ -149,7 +153,7 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi *redraw = true; } if (*redraw) { - ProcessList_rebuildPanel(host->pl); + Table_rebuildPanel(host->activeTable); if (!this->state->hideMeters) Header_draw(this->header); } @@ -192,7 +196,7 @@ static void ScreenManager_drawScreenTabs(ScreenManager* this) { } for (int s = 0; screens[s]; s++) { - bool ok = drawTab(&y, &x, l, screens[s]->name, s == cur); + bool ok = drawTab(&y, &x, l, screens[s]->heading, s == cur); if (!ok) { break; } @@ -317,12 +321,14 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, con redraw = false; continue; } + switch (ch) { case KEY_ALT('H'): ch = KEY_LEFT; break; case KEY_ALT('J'): ch = KEY_DOWN; break; case KEY_ALT('K'): ch = KEY_UP; break; case KEY_ALT('L'): ch = KEY_RIGHT; break; } + redraw = true; if (Panel_eventHandlerFn(panelFocus)) { result = Panel_eventHandler(panelFocus, ch); diff --git a/ScreenTabsPanel.c b/ScreenTabsPanel.c new file mode 100644 index 000000000..e48e5fb8b --- /dev/null +++ b/ScreenTabsPanel.c @@ -0,0 +1,374 @@ +/* +htop - ScreenTabsPanel.c +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "ScreenTabsPanel.h" + +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "FunctionBar.h" +#include "Hashtable.h" +#include "Macros.h" +#include "ProvideCurses.h" +#include "Settings.h" +#include "XUtils.h" + + +static HandlerResult ScreenNamesPanel_eventHandlerNormal(Panel* super, int ch); + +ObjectClass ScreenTabListItem_class = { + .extends = Class(ListItem), + .display = ListItem_display, + .delete = ListItem_delete, + .compare = ListItem_compare +}; + +static void ScreenNamesPanel_fill(ScreenNamesPanel* this, DynamicScreen* ds) { + const Settings* settings = this->settings; + Panel* super = (Panel*) this; + Panel_prune(super); + + for (unsigned int i = 0; i < settings->nScreens; i++) { + const ScreenSettings* ss = settings->screens[i]; + + if (ds == NULL) { + if (ss->dynamic != NULL) + continue; + /* built-in (processes, not dynamic) - e.g. Main or I/O */ + } else { + if (ss->dynamic == NULL) + continue; + if (!String_eq(ds->name, ss->dynamic)) + continue; + /* matching dynamic screen found, add it into the Panel */ + } + Panel_add(super, (Object*) ListItem_new(ss->heading, i)); + } + + this->ds = ds; +} + +static void ScreenTabsPanel_delete(Object* object) { + Panel* super = (Panel*) object; + ScreenTabsPanel* this = (ScreenTabsPanel*) object; + + Panel_done(super); + free(this); +} + +static HandlerResult ScreenTabsPanel_eventHandler(Panel* super, int ch) { + ScreenTabsPanel* const this = (ScreenTabsPanel* const) super; + + HandlerResult result = IGNORED; + + int selected = Panel_getSelectedIndex(super); + switch (ch) { + case EVENT_SET_SELECTED: + result = HANDLED; + break; + case KEY_F(5): + case KEY_CTRL('N'): + /* pass onto the Names panel for creating new screen */ + return ScreenNamesPanel_eventHandlerNormal(&this->names->super, ch); + case KEY_UP: + case KEY_DOWN: + case KEY_NPAGE: + case KEY_PPAGE: + case KEY_HOME: + case KEY_END: { + int previous = selected; + Panel_onKey(super, ch); + selected = Panel_getSelectedIndex(super); + if (previous != selected) + result = HANDLED; + break; + } + default: + if (ch < 255 && isalpha(ch)) + result = Panel_selectByTyping(super, ch); + if (result == BREAK_LOOP) + result = IGNORED; + break; + } + + if (result == HANDLED) { + ScreenTabListItem* focus = (ScreenTabListItem*) Panel_getSelected(super); + if (focus) { + ScreenNamesPanel_fill(this->names, focus->ds); + } + } + + return result; +} + +PanelClass ScreenTabsPanel_class = { + .super = { + .extends = Class(Panel), + .delete = ScreenTabsPanel_delete, + }, + .eventHandler = ScreenTabsPanel_eventHandler +}; + +static ScreenTabListItem* ScreenTabListItem_new(const char* value, DynamicScreen* ds) { + ScreenTabListItem* this = AllocThis(ScreenTabListItem); + ListItem_init((ListItem*)this, value, 0); + this->ds = ds; + return this; +} + +static void addDynamicScreen(ATTR_UNUSED ht_key_t key, void* value, void* userdata) { + DynamicScreen* screen = (DynamicScreen*) value; + Panel* super = (Panel*) userdata; + const char* name = screen->heading ? screen->heading : screen->name; + + Panel_add(super, (Object*) ScreenTabListItem_new(name, screen)); +} + +static const char* const ScreenTabsFunctions[] = {" ", " ", " ", " ", "New ", " ", " ", " ", " ", "Done ", NULL}; + +ScreenTabsPanel* ScreenTabsPanel_new(Settings* settings) { + ScreenTabsPanel* this = AllocThis(ScreenTabsPanel); + Panel* super = (Panel*) this; + FunctionBar* fuBar = FunctionBar_new(ScreenTabsFunctions, NULL, NULL); + Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); + + this->settings = settings; + this->names = ScreenNamesPanel_new(settings); + super->cursorOn = false; + this->cursor = 0; + Panel_setHeader(super, "Screen tabs"); + + assert(settings->dynamicScreens != NULL); + Panel_add(super, (Object*) ScreenTabListItem_new("Processes", NULL)); + Hashtable_foreach(settings->dynamicScreens, addDynamicScreen, super); + + return this; +} + +// ------------- + +ObjectClass ScreenNameListItem_class = { + .extends = Class(ListItem), + .display = ListItem_display, + .delete = ListItem_delete, + .compare = ListItem_compare +}; + +ScreenNameListItem* ScreenNameListItem_new(const char* value, ScreenSettings* ss) { + ScreenNameListItem* this = AllocThis(ScreenNameListItem); + ListItem_init((ListItem*)this, value, 0); + this->ss = ss; + return this; +} + +static const char* const ScreenNamesFunctions[] = {" ", " ", " ", " ", "New ", " ", " ", " ", " ", "Done ", NULL}; + +static void ScreenNamesPanel_delete(Object* object) { + Panel* super = (Panel*) object; + ScreenNamesPanel* this = (ScreenNamesPanel*) object; + + /* do not delete screen settings still in use */ + int n = Panel_size(super); + for (int i = 0; i < n; i++) { + ScreenNameListItem* item = (ScreenNameListItem*) Panel_get(super, i); + item->ss = NULL; + } + + /* during renaming the ListItem's value points to our static buffer */ + if (this->renamingItem) + this->renamingItem->value = this->saved; + + Panel_done(super); + free(this); +} + +static void renameScreenSettings(ScreenNamesPanel* this, const ListItem* item) { + const ScreenNameListItem* nameItem = (const ScreenNameListItem*) item; + + ScreenSettings* ss = nameItem->ss; + free_and_xStrdup(&ss->heading, item->value); + + Settings* settings = this->settings; + settings->changed = true; + settings->lastUpdate++; +} + +static HandlerResult ScreenNamesPanel_eventHandlerRenaming(Panel* super, int ch) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + + if (ch >= 32 && ch < 127 && ch != '=') { + if (this->cursor < SCREEN_NAME_LEN - 1) { + this->buffer[this->cursor] = (char)ch; + this->cursor++; + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); + } + + return HANDLED; + } + + switch (ch) { + case 127: + case KEY_BACKSPACE: + if (this->cursor > 0) { + this->cursor--; + this->buffer[this->cursor] = '\0'; + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); + } + break; + case '\n': + case '\r': + case KEY_ENTER: { + ListItem* item = (ListItem*) Panel_getSelected(super); + if (!item) + break; + assert(item == this->renamingItem); + free(this->saved); + item->value = xStrdup(this->buffer); + this->renamingItem = NULL; + super->cursorOn = false; + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + renameScreenSettings(this, item); + break; + } + case 27: { // Esc + ListItem* item = (ListItem*) Panel_getSelected(super); + if (!item) + break; + assert(item == this->renamingItem); + item->value = this->saved; + this->renamingItem = NULL; + super->cursorOn = false; + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + break; + } + } + + return HANDLED; +} + +static void startRenaming(Panel* super) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + + ListItem* item = (ListItem*) Panel_getSelected(super); + if (item == NULL) + return; + + this->renamingItem = item; + super->cursorOn = true; + char* name = item->value; + this->saved = name; + strncpy(this->buffer, name, SCREEN_NAME_LEN); + this->buffer[SCREEN_NAME_LEN] = '\0'; + this->cursor = strlen(this->buffer); + item->value = this->buffer; + Panel_setSelectionColor(super, PANEL_EDIT); + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); +} + +static void addNewScreen(Panel* super, DynamicScreen* ds) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + const char* name = "New"; + ScreenSettings* ss = (ds != NULL) ? Settings_newDynamicScreen(this->settings, name, ds, NULL) : Settings_newScreen(this->settings, &(const ScreenDefaults) { .name = name, .columns = "PID Command", .sortKey = "PID" }); + ScreenNameListItem* item = ScreenNameListItem_new(name, ss); + int idx = Panel_getSelectedIndex(super); + Panel_insert(super, idx + 1, (Object*) item); + Panel_setSelected(super, idx + 1); +} + +static HandlerResult ScreenNamesPanel_eventHandlerNormal(Panel* super, int ch) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + ScreenNameListItem* oldFocus = (ScreenNameListItem*) Panel_getSelected(super); + HandlerResult result = IGNORED; + + switch (ch) { + case '\n': + case '\r': + case KEY_ENTER: + case KEY_MOUSE: + case KEY_RECLICK: + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + result = HANDLED; + break; + case EVENT_SET_SELECTED: + result = HANDLED; + break; + case KEY_NPAGE: + case KEY_PPAGE: + case KEY_HOME: + case KEY_END: + Panel_onKey(super, ch); + break; + case KEY_F(5): + case KEY_CTRL('N'): + addNewScreen(super, this->ds); + startRenaming(super); + result = HANDLED; + break; + default: + if (ch < 255 && isalpha(ch)) + result = Panel_selectByTyping(super, ch); + if (result == BREAK_LOOP) + result = IGNORED; + break; + } + + ScreenNameListItem* newFocus = (ScreenNameListItem*) Panel_getSelected(super); + if (newFocus && oldFocus != newFocus) + result = HANDLED; + + return result; +} + +static HandlerResult ScreenNamesPanel_eventHandler(Panel* super, int ch) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + + if (!this->renamingItem) + return ScreenNamesPanel_eventHandlerNormal(super, ch); + return ScreenNamesPanel_eventHandlerRenaming(super, ch); +} + +PanelClass ScreenNamesPanel_class = { + .super = { + .extends = Class(Panel), + .delete = ScreenNamesPanel_delete + }, + .eventHandler = ScreenNamesPanel_eventHandler +}; + +ScreenNamesPanel* ScreenNamesPanel_new(Settings* settings) { + ScreenNamesPanel* this = AllocThis(ScreenNamesPanel); + Panel* super = (Panel*) this; + FunctionBar* fuBar = FunctionBar_new(ScreenNamesFunctions, NULL, NULL); + Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); + + this->settings = settings; + this->renamingItem = NULL; + memset(this->buffer, 0, sizeof(this->buffer)); + this->ds = NULL; + this->saved = NULL; + this->cursor = 0; + super->cursorOn = false; + Panel_setHeader(super, "Screens"); + + for (unsigned int i = 0; i < settings->nScreens; i++) { + ScreenSettings* ss = settings->screens[i]; + /* initially show only for Processes tabs (selected) */ + if (ss->dynamic) + continue; + Panel_add(super, (Object*) ScreenNameListItem_new(ss->heading, ss)); + } + return this; +} diff --git a/ScreenTabsPanel.h b/ScreenTabsPanel.h new file mode 100644 index 000000000..fe1a313c6 --- /dev/null +++ b/ScreenTabsPanel.h @@ -0,0 +1,61 @@ +#ifndef HEADER_ScreenTabsPanel +#define HEADER_ScreenTabsPanel +/* +htop - ScreenTabsPanel.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "DynamicScreen.h" +#include "ListItem.h" +#include "Object.h" +#include "Panel.h" +#include "ScreensPanel.h" +#include "ScreenManager.h" +#include "Settings.h" + + +typedef struct ScreenNamesPanel_ { + Panel super; + + ScreenManager* scr; + Settings* settings; + char buffer[SCREEN_NAME_LEN + 1]; + DynamicScreen* ds; + char* saved; + int cursor; + ListItem* renamingItem; +} ScreenNamesPanel; + +typedef struct ScreenNameListItem_ { + ListItem super; + ScreenSettings* ss; +} ScreenNameListItem; + +typedef struct ScreenTabsPanel_ { + Panel super; + + ScreenManager* scr; + Settings* settings; + ScreenNamesPanel* names; + int cursor; +} ScreenTabsPanel; + +typedef struct ScreenTabListItem_ { + ListItem super; + DynamicScreen* ds; +} ScreenTabListItem; + + +ScreenTabsPanel* ScreenTabsPanel_new(Settings* settings); + +extern ObjectClass ScreenNameListItem_class; + +ScreenNameListItem* ScreenNameListItem_new(const char* value, ScreenSettings* ss); + +extern PanelClass ScreenNamesPanel_class; + +ScreenNamesPanel* ScreenNamesPanel_new(Settings* settings); + +#endif diff --git a/ScreensPanel.c b/ScreensPanel.c index cb664ac45..4138066b5 100644 --- a/ScreensPanel.c +++ b/ScreensPanel.c @@ -1,17 +1,21 @@ /* htop - ScreensPanel.c (C) 2004-2011 Hisham H. Muhammad -(C) 2020-2022 htop dev team +(C) 2020-2023 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "ScreensPanel.h" -#include +#include #include +#include #include +#include "AvailableColumnsPanel.h" #include "CRT.h" #include "FunctionBar.h" #include "Hashtable.h" @@ -43,10 +47,10 @@ ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) { } static const char* const ScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL}; +static const char* const DynamicFunctions[] = {" ", "Rename", " ", " ", " ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL}; static void ScreensPanel_delete(Object* object) { Panel* super = (Panel*) object; - ScreensPanel* this = (ScreensPanel*) object; /* do not delete screen settings still in use */ int n = Panel_size(super); @@ -55,12 +59,7 @@ static void ScreensPanel_delete(Object* object) { 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); + Panel_delete(object); } static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) { @@ -73,49 +72,48 @@ static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) { super->selectedLen = strlen(this->buffer); Panel_setCursorToSelection(super); } - } else { - switch (ch) { - case 127: - case KEY_BACKSPACE: - { - if (this->cursor > 0) { - this->cursor--; - this->buffer[this->cursor] = '\0'; - super->selectedLen = strlen(this->buffer); - Panel_setCursorToSelection(super); - } - break; + + return HANDLED; + } + + switch (ch) { + case 127: + case KEY_BACKSPACE: + if (this->cursor > 0) { + this->cursor--; + this->buffer[this->cursor] = '\0'; + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); } - case '\n': - case '\r': - case KEY_ENTER: - { - ListItem* item = (ListItem*) Panel_getSelected(super); - if (!item) - break; - 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 '\n': + case '\r': + case KEY_ENTER: { + ListItem* item = (ListItem*) Panel_getSelected(super); + if (!item) break; - } - case 27: // Esc - { - ListItem* item = (ListItem*) Panel_getSelected(super); - if (!item) - break; - assert(item == this->renamingItem); - item->value = this->saved; - this->renamingItem = NULL; - super->cursorOn = false; - Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + assert(item == this->renamingItem); + free(this->saved); + item->value = xStrdup(this->buffer); + this->renamingItem = NULL; + super->cursorOn = false; + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + ScreensPanel_update(super); + break; + } + case 27: { // Esc + ListItem* item = (ListItem*) Panel_getSelected(super); + if (!item) break; - } + assert(item == this->renamingItem); + item->value = this->saved; + this->renamingItem = NULL; + super->cursorOn = false; + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + break; } } + return HANDLED; } @@ -177,14 +175,14 @@ static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) { ScreenListItem* oldFocus = (ScreenListItem*) Panel_getSelected(super); bool shouldRebuildArray = false; HandlerResult result = IGNORED; + switch (ch) { case '\n': case '\r': case KEY_ENTER: case KEY_MOUSE: - case KEY_RECLICK: - { - this->moving = !(this->moving); + case KEY_RECLICK: { + this->moving = !this->moving; Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS); ListItem* item = (ListItem*) Panel_getSelected(super); if (item) @@ -198,87 +196,77 @@ static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) { case KEY_NPAGE: case KEY_PPAGE: case KEY_HOME: - case KEY_END: { + case KEY_END: Panel_onKey(super, ch); break; - } case KEY_F(2): case KEY_CTRL('R'): - { startRenaming(super); result = HANDLED; break; - } case KEY_F(5): case KEY_CTRL('N'): - { + if (this->settings->dynamicScreens) + break; addNewScreen(super); startRenaming(super); shouldRebuildArray = true; result = HANDLED; break; - } case KEY_UP: - { if (!this->moving) { Panel_onKey(super, ch); break; } - /* else fallthrough */ - } /* FALLTHRU */ + /* FALLTHRU */ case KEY_F(7): case '[': case '-': - { Panel_moveSelectedUp(super); shouldRebuildArray = true; result = HANDLED; break; - } case KEY_DOWN: - { if (!this->moving) { Panel_onKey(super, ch); break; } - /* else fallthrough */ - } /* FALLTHRU */ + /* FALLTHRU */ case KEY_F(8): case ']': case '+': - { Panel_moveSelectedDown(super); shouldRebuildArray = true; result = HANDLED; break; - } case KEY_F(9): //case KEY_DC: - { if (Panel_size(super) > 1) Panel_remove(super, selected); shouldRebuildArray = true; result = HANDLED; break; - } default: - { if (ch < 255 && isalpha(ch)) result = Panel_selectByTyping(super, ch); if (result == BREAK_LOOP) result = IGNORED; break; - } } + ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super); if (newFocus && oldFocus != newFocus) { - ColumnsPanel_fill(this->columns, newFocus->ss, this->settings->dynamicColumns); + Hashtable* dynamicColumns = this->settings->dynamicColumns; + ColumnsPanel_fill(this->columns, newFocus->ss, dynamicColumns); + AvailableColumnsPanel_fill(this->availableColumns, newFocus->ss->dynamic, dynamicColumns); result = HANDLED; } + if (shouldRebuildArray) rebuildSettingsArray(super, selected); if (result == HANDLED) ScreensPanel_update(super); + return result; } @@ -304,11 +292,12 @@ ScreensPanel* ScreensPanel_new(Settings* settings) { ScreensPanel* this = AllocThis(ScreensPanel); Panel* super = (Panel*) this; Hashtable* columns = settings->dynamicColumns; - FunctionBar* fuBar = FunctionBar_new(ScreensFunctions, NULL, NULL); + FunctionBar* fuBar = FunctionBar_new(settings->dynamicScreens ? DynamicFunctions : ScreensFunctions, NULL, NULL); Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); this->settings = settings; this->columns = ColumnsPanel_new(settings->screens[0], columns, &(settings->changed)); + this->availableColumns = AvailableColumnsPanel_new((Panel*) this->columns, columns); this->moving = false; this->renamingItem = NULL; super->cursorOn = false; @@ -317,7 +306,7 @@ ScreensPanel* ScreensPanel_new(Settings* settings) { for (unsigned int i = 0; i < settings->nScreens; i++) { ScreenSettings* ss = settings->screens[i]; - char* name = ss->name; + char* name = ss->heading; Panel_add(super, (Object*) ScreenListItem_new(name, ss)); } return this; @@ -332,9 +321,8 @@ void ScreensPanel_update(Panel* super) { for (int i = 0; i < size; i++) { ScreenListItem* item = (ScreenListItem*) Panel_get(super, i); ScreenSettings* ss = item->ss; - free(ss->name); + free_and_xStrdup(&ss->heading, ((ListItem*) item)->value); this->settings->screens[i] = ss; - ss->name = xStrdup(((ListItem*) item)->value); } this->settings->screens[size] = NULL; } diff --git a/ScreensPanel.h b/ScreensPanel.h index 60aaf2a2d..0be0b8244 100644 --- a/ScreensPanel.h +++ b/ScreensPanel.h @@ -10,7 +10,9 @@ in the source distribution for its full text. #include +#include "AvailableColumnsPanel.h" #include "ColumnsPanel.h" +#include "DynamicScreen.h" #include "ListItem.h" #include "Object.h" #include "Panel.h" @@ -27,6 +29,7 @@ typedef struct ScreensPanel_ { ScreenManager* scr; Settings* settings; ColumnsPanel* columns; + AvailableColumnsPanel* availableColumns; char buffer[SCREEN_NAME_LEN + 1]; char* saved; int cursor; @@ -36,6 +39,7 @@ typedef struct ScreensPanel_ { typedef struct ScreenListItem_ { ListItem super; + DynamicScreen* ds; ScreenSettings* ss; } ScreenListItem; @@ -44,8 +48,6 @@ extern ObjectClass ScreenListItem_class; ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss); -extern PanelClass ScreensPanel_class; - ScreensPanel* ScreensPanel_new(Settings* settings); void ScreensPanel_update(Panel* super); diff --git a/Settings.c b/Settings.c index c712966e3..a01e2494c 100644 --- a/Settings.c +++ b/Settings.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "Settings.h" #include @@ -19,9 +21,12 @@ in the source distribution for its full text. #include "CRT.h" #include "DynamicColumn.h" +#include "DynamicScreen.h" #include "Macros.h" #include "Meter.h" #include "Platform.h" +#include "Process.h" +#include "Table.h" #include "XUtils.h" @@ -203,13 +208,25 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) this->hColumns[1].modes[r++] = TEXT_METERMODE; } -static const char* toFieldName(Hashtable* columns, int id) { - if (id < 0) +static const char* toFieldName(Hashtable* columns, int id, bool* enabled) { + if (id < 0) { + if (enabled) + *enabled = false; return NULL; - if (id >= LAST_PROCESSFIELD) { + } + if (id >= ROW_DYNAMIC_FIELDS) { const DynamicColumn* column = DynamicColumn_lookup(columns, id); + if (!column) { + if (enabled) + *enabled = false; + return NULL; + } + if (enabled) + *enabled = column->enabled; return column->name; } + if (enabled) + *enabled = true; return Process_fields[id].name; } @@ -217,7 +234,7 @@ static int toFieldIndex(Hashtable* columns, const char* str) { if (isdigit(str[0])) { // This "+1" is for compatibility with the older enum format. int id = atoi(str) + 1; - if (toFieldName(columns, id)) { + if (toFieldName(columns, id, NULL)) { return id; } } else { @@ -237,7 +254,7 @@ static int toFieldIndex(Hashtable* columns, const char* str) { } // Fallback to iterative scan of table of fields by-name. for (int p = 1; p < LAST_PROCESSFIELD; p++) { - const char* pName = toFieldName(columns, p); + const char* pName = toFieldName(columns, p, NULL); if (pName && strcmp(pName, str) == 0) return p; } @@ -269,34 +286,57 @@ static void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, co String_freeArray(ids); } +static ScreenSettings* Settings_initScreenSettings(ScreenSettings* ss, Settings* this, const char* columns) { + ScreenSettings_readFields(ss, this->dynamicColumns, columns); + this->screens[this->nScreens] = ss; + this->nScreens++; + this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1)); + this->screens[this->nScreens] = NULL; + return ss; +} + ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults) { int sortKey = defaults->sortKey ? toFieldIndex(this->dynamicColumns, defaults->sortKey) : PID; + int treeSortKey = defaults->treeSortKey ? toFieldIndex(this->dynamicColumns, defaults->treeSortKey) : PID; int sortDesc = (sortKey >= 0 && sortKey < LAST_PROCESSFIELD) ? Process_fields[sortKey].defaultSortDesc : 1; ScreenSettings* ss = xMalloc(sizeof(ScreenSettings)); *ss = (ScreenSettings) { - .name = xStrdup(defaults->name), + .heading = xStrdup(defaults->name), + .dynamic = NULL, + .table = NULL, .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)), .flags = 0, .direction = sortDesc ? -1 : 1, .treeDirection = 1, .sortKey = sortKey, - .treeSortKey = PID, + .treeSortKey = treeSortKey, .treeView = false, .treeViewAlwaysByPID = false, .allBranchesCollapsed = false, }; + return Settings_initScreenSettings(ss, this, defaults->columns); +} - ScreenSettings_readFields(ss, this->dynamicColumns, defaults->columns); - this->screens[this->nScreens] = ss; - this->nScreens++; - this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1)); - this->screens[this->nScreens] = NULL; - return ss; +ScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const DynamicScreen* screen, Table* table) { + int sortKey = toFieldIndex(this->dynamicColumns, screen->columnKeys); + + ScreenSettings* ss = xMalloc(sizeof(ScreenSettings)); + *ss = (ScreenSettings) { + .heading = xStrdup(tab), + .dynamic = xStrdup(screen->name), + .table = table, + .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)), + .direction = screen->direction, + .treeDirection = 1, + .sortKey = sortKey, + }; + return Settings_initScreenSettings(ss, this, screen->columnKeys); } void ScreenSettings_delete(ScreenSettings* this) { - free(this->name); + free(this->heading); + free(this->dynamic); free(this->fields); free(this); } @@ -308,6 +348,7 @@ static ScreenSettings* Settings_defaultScreens(Settings* this) { const ScreenDefaults* defaults = &Platform_defaultScreens[i]; Settings_newScreen(this, defaults); } + Platform_defaultDynamicScreens(this); return this->screens[0]; } @@ -505,6 +546,11 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini } else if (String_eq(option[0], ".all_branches_collapsed")) { if (screen) screen->allBranchesCollapsed = atoi(option[1]); + } else if (String_eq(option[0], ".dynamic")) { + if (screen) { + free_and_xStrdup(&screen->dynamic, option[1]); + Platform_addDynamicScreen(screen); + } } String_freeArray(option); } @@ -520,11 +566,13 @@ static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns const char* sep = ""; for (unsigned int i = 0; fields[i]; i++) { if (fields[i] < LAST_PROCESSFIELD && byName) { - const char* pName = toFieldName(columns, fields[i]); + const char* pName = toFieldName(columns, fields[i], NULL); 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); + bool enabled; + const char* pName = toFieldName(columns, fields[i], &enabled); + if (enabled) + fprintf(fd, "%sDynamic(%s)", sep, pName); } else { // This "-1" is for compatibility with the older enum format. fprintf(fd, "%s%d", sep, (int) fields[i] - 1); @@ -544,26 +592,41 @@ static void writeList(FILE* fd, char** list, int len, char separator) { } static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) { - writeList(fd, this->hColumns[column].names, this->hColumns[column].len, separator); + if (this->hColumns[column].len) { + writeList(fd, this->hColumns[column].names, this->hColumns[column].len, separator); + } else { + fputc('!', fd); + fputc(separator, fd); + } } static void writeMeterModes(const Settings* this, FILE* fd, char separator, unsigned int column) { - const char* sep = ""; - for (size_t i = 0; i < this->hColumns[column].len; i++) { - fprintf(fd, "%s%d", sep, this->hColumns[column].modes[i]); - sep = " "; + if (this->hColumns[column].len) { + const char* sep = ""; + for (size_t i = 0; i < this->hColumns[column].len; i++) { + fprintf(fd, "%s%d", sep, this->hColumns[column].modes[i]); + sep = " "; + } + } else { + fputc('!', fd); } + fputc(separator, fd); } int Settings_write(const Settings* this, bool onCrash) { FILE* fd; char separator; + char* tmpFilename = NULL; if (onCrash) { fd = stderr; separator = ';'; } else { - fd = fopen(this->filename, "w"); + xAsprintf(&tmpFilename, "%s.tmp.XXXXXX", this->filename); + int fdtmp = mkstemp(tmpFilename); + if (fdtmp == -1) + return -errno; + fd = fdopen(fdtmp, "w"); if (fd == NULL) return -errno; separator = '\n'; @@ -639,12 +702,23 @@ int Settings_write(const Settings* this, bool onCrash) { for (unsigned int i = 0; i < this->nScreens; i++) { ScreenSettings* ss = this->screens[i]; - fprintf(fd, "screen:%s=", ss->name); + const char* sortKey = toFieldName(this->dynamicColumns, ss->sortKey, NULL); + const char* treeSortKey = toFieldName(this->dynamicColumns, ss->treeSortKey, NULL); + + fprintf(fd, "screen:%s=", ss->heading); writeFields(fd, ss->fields, this->dynamicColumns, true, separator); - printSettingString(".sort_key", toFieldName(this->dynamicColumns, ss->sortKey)); - printSettingString(".tree_sort_key", toFieldName(this->dynamicColumns, ss->treeSortKey)); + if (ss->dynamic) { + printSettingString(".dynamic", ss->dynamic); + if (ss->sortKey && ss->sortKey != PID) + fprintf(fd, "%s=Dynamic(%s)%c", ".sort_key", sortKey, separator); + if (ss->treeSortKey && ss->treeSortKey != PID) + fprintf(fd, "%s=Dynamic(%s)%c", ".tree_sort_key", treeSortKey, separator); + } else { + printSettingString(".sort_key", sortKey); + printSettingString(".tree_sort_key", treeSortKey); + printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID); + } printSettingInteger(".tree_view", ss->treeView); - printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID); printSettingInteger(".sort_direction", ss->direction); printSettingInteger(".tree_sort_direction", ss->treeDirection); printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed); @@ -664,12 +738,18 @@ int Settings_write(const Settings* this, bool onCrash) { if (fclose(fd) != 0) r = r ? r : -errno; + if (r == 0) + r = (rename(tmpFilename, this->filename) == -1) ? -errno : 0; + + free(tmpFilename); + return r; } -Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns) { +Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens) { Settings* this = xCalloc(1, sizeof(Settings)); + this->dynamicScreens = dynamicScreens; this->dynamicColumns = dynamicColumns; this->dynamicMeters = dynamicMeters; this->hLayout = HF_TWO_50_50; diff --git a/Settings.h b/Settings.h index 48c62590a..a7740ff3a 100644 --- a/Settings.h +++ b/Settings.h @@ -7,24 +7,28 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include +#include #include #include "Hashtable.h" #include "HeaderLayout.h" -#include "Process.h" +#include "Row.h" +#include "RowField.h" #define DEFAULT_DELAY 15 #define CONFIG_READER_MIN_VERSION 3 +struct DynamicScreen_; // IWYU pragma: keep +struct Table_; // IWYU pragma: keep + typedef struct { const char* name; const char* columns; const char* sortKey; + const char* treeSortKey; } ScreenDefaults; typedef struct { @@ -33,14 +37,16 @@ typedef struct { int* modes; } MeterColumnSetting; -typedef struct { - char* name; - ProcessField* fields; +typedef struct ScreenSettings_ { + char* heading; /* user-editable screen name (pretty) */ + char* dynamic; /* from DynamicScreen config (fixed) */ + struct Table_* table; + RowField* fields; uint32_t flags; int direction; int treeDirection; - ProcessField sortKey; - ProcessField treeSortKey; + RowField sortKey; + RowField treeSortKey; bool treeView; bool treeViewAlwaysByPID; bool allBranchesCollapsed; @@ -53,6 +59,7 @@ typedef struct Settings_ { MeterColumnSetting* hColumns; Hashtable* dynamicColumns; /* runtime-discovered columns */ Hashtable* dynamicMeters; /* runtime-discovered meters */ + Hashtable* dynamicScreens; /* runtime-discovered screens */ ScreenSettings** screens; unsigned int nScreens; @@ -104,9 +111,9 @@ typedef struct Settings_ { #define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu)) -static inline ProcessField ScreenSettings_getActiveSortKey(const ScreenSettings* this) { +static inline RowField ScreenSettings_getActiveSortKey(const ScreenSettings* this) { return (this->treeView) - ? (this->treeViewAlwaysByPID ? PID : this->treeSortKey) + ? (this->treeViewAlwaysByPID ? 1 : this->treeSortKey) : this->sortKey; } @@ -118,15 +125,17 @@ void Settings_delete(Settings* this); int Settings_write(const Settings* this, bool onCrash); -Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns); +Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens); ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults); +ScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const struct DynamicScreen_* screen, struct Table_* table); + void ScreenSettings_delete(ScreenSettings* this); void ScreenSettings_invertSortOrder(ScreenSettings* this); -void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey); +void ScreenSettings_setSortKey(ScreenSettings* this, RowField sortKey); void Settings_enableReadonly(void); diff --git a/SignalsPanel.c b/SignalsPanel.c index 7e21ce931..28d307e28 100644 --- a/SignalsPanel.c +++ b/SignalsPanel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "SignalsPanel.h" // the above contains #include so do not add that here again (breaks Solaris build) diff --git a/SignalsPanel.h b/SignalsPanel.h index 529043a19..20fb4a684 100644 --- a/SignalsPanel.h +++ b/SignalsPanel.h @@ -7,14 +7,12 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep +#include "Panel.h" #ifndef HTOP_SOLARIS #include #endif -#include "Panel.h" - typedef struct SignalItem_ { const char* name; diff --git a/SwapMeter.c b/SwapMeter.c index 84e58a26e..1055a6e70 100644 --- a/SwapMeter.c +++ b/SwapMeter.c @@ -13,6 +13,7 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "Macros.h" #include "Object.h" #include "Platform.h" #include "RichString.h" @@ -51,13 +52,13 @@ static void SwapMeter_display(const Object* cast, RichString* out) { RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:"); RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); - if (!isnan(this->values[SWAP_METER_CACHE])) { + if (isNonnegative(this->values[SWAP_METER_CACHE])) { Meter_humanUnit(buffer, this->values[SWAP_METER_CACHE], sizeof(buffer)); RichString_appendAscii(out, CRT_colors[METER_TEXT], " cache:"); RichString_appendAscii(out, CRT_colors[SWAP_CACHE], buffer); } - if (!isnan(this->values[SWAP_METER_FRONTSWAP])) { + if (isNonnegative(this->values[SWAP_METER_FRONTSWAP])) { Meter_humanUnit(buffer, this->values[SWAP_METER_FRONTSWAP], sizeof(buffer)); RichString_appendAscii(out, CRT_colors[METER_TEXT], " frontswap:"); RichString_appendAscii(out, CRT_colors[SWAP_FRONTSWAP], buffer); diff --git a/TESTPLAN b/TESTPLAN index 88fe039de..b6ddfa6aa 100644 --- a/TESTPLAN +++ b/TESTPLAN @@ -5,7 +5,7 @@ Main screen: Mouse click header - nothing happens. - Mouse click on ProcessList title bar - exit Tree view, update FunctionBar, title bar updates, sort by clicked field. + Mouse click on ProcessTable title bar - exit Tree view, update FunctionBar, title bar updates, sort by clicked field. *** FAILING: wrong FB update depending on mode; does not change sort in wip branch click on same entry - invert sort. click on another entry - sort another field. diff --git a/Table.c b/Table.c new file mode 100644 index 000000000..d807ce5f5 --- /dev/null +++ b/Table.c @@ -0,0 +1,372 @@ +/* +htop - Table.c +(C) 2004,2005 Hisham H. Muhammad +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "Table.h" + +#include +#include +#include + +#include "CRT.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Macros.h" +#include "Panel.h" +#include "RowField.h" +#include "Vector.h" + + +Table* Table_init(Table* this, const ObjectClass* klass, Machine* host) { + this->rows = Vector_new(klass, true, DEFAULT_SIZE); + this->displayList = Vector_new(klass, false, DEFAULT_SIZE); + this->table = Hashtable_new(200, false); + this->needsSort = true; + this->following = -1; + this->host = host; + return this; +} + +void Table_done(Table* this) { + Hashtable_delete(this->table); + Vector_delete(this->displayList); + Vector_delete(this->rows); +} + +static void Table_delete(Object* cast) { + Table* this = (Table*) cast; + Table_done(this); + free(this); +} + +void Table_setPanel(Table* this, Panel* panel) { + this->panel = panel; +} + +void Table_add(Table* this, Row* row) { + assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) == -1); + assert(Hashtable_get(this->table, row->id) == NULL); + + // highlighting row found in first scan by first scan marked "far in the past" + row->seenStampMs = this->host->monotonicMs; + + Vector_add(this->rows, row); + Hashtable_put(this->table, row->id, row); + + assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) != -1); + assert(Hashtable_get(this->table, row->id) != NULL); + assert(Vector_countEquals(this->rows, Hashtable_count(this->table))); +} + +// Table_removeIndex removes a given row from the lists map and soft deletes +// it from its vector. Vector_compact *must* be called once the caller is done +// removing items. +// Note: for processes should only be called from ProcessTable_iterate to avoid +// breaking dying process highlighting. +void Table_removeIndex(Table* this, const Row* row, int idx) { + int rowid = row->id; + + assert(row == (Row*)Vector_get(this->rows, idx)); + assert(Hashtable_get(this->table, rowid) != NULL); + + Hashtable_remove(this->table, rowid); + Vector_softRemove(this->rows, idx); + + if (this->following != -1 && this->following == rowid) { + this->following = -1; + Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); + } + + assert(Hashtable_get(this->table, rowid) == NULL); + assert(Vector_countEquals(this->rows, Hashtable_count(this->table))); +} + +static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, int32_t indent, bool show) { + // Do not treat zero as root of any tree. + // (e.g. on OpenBSD the kernel thread 'swapper' has pid 0.) + if (rowid == 0) + return; + + // The vector is sorted by parent, find the start of the range by bisection + int vsize = Vector_size(this->rows); + int l = 0; + int r = vsize; + while (l < r) { + int c = (l + r) / 2; + Row* row = (Row*)Vector_get(this->rows, c); + int parent = row->isRoot ? 0 : Row_getGroupOrParent(row); + if (parent < rowid) { + l = c + 1; + } else { + r = c; + } + } + // Find the end to know the last line for indent handling purposes + int lastShown = r; + while (r < vsize) { + Row* row = (Row*)Vector_get(this->rows, r); + if (!Row_isChildOf(row, rowid)) + break; + if (row->show) + lastShown = r; + r++; + } + + for (int i = l; i < r; i++) { + Row* row = (Row*)Vector_get(this->rows, i); + + if (!show) + row->show = false; + + Vector_add(this->displayList, row); + + int32_t nextIndent = indent | ((int32_t)1 << MINIMUM(level, sizeof(row->indent) * 8 - 2)); + Table_buildTreeBranch(this, row->id, level + 1, (i < lastShown) ? nextIndent : indent, row->show && row->showChildren); + if (i == lastShown) + row->indent = -nextIndent; + else + row->indent = nextIndent; + + row->tree_depth = level + 1; + } +} + +static int compareRowByKnownParentThenNatural(const void* v1, const void* v2) { + return Row_compareByParent((const Row*) v1, (const Row*) v2); +} + +// Builds a sorted tree from scratch, without relying on previously gathered information +static void Table_buildTree(Table* this) { + Vector_prune(this->displayList); + + // Mark root processes + int vsize = Vector_size(this->rows); + for (int i = 0; i < vsize; i++) { + Row* row = (Row*) Vector_get(this->rows, i); + int parent = Row_getGroupOrParent(row); + row->isRoot = false; + + if (row->id == parent) { + row->isRoot = true; + continue; + } + + if (!parent) { + row->isRoot = true; + continue; + } + + // We don't know about its parent for whatever reason + if (Table_findRow(this, parent) == NULL) + row->isRoot = true; + } + + // Sort by known parent (roots first), then row ID + Vector_quickSortCustomCompare(this->rows, compareRowByKnownParentThenNatural); + + // Find all processes whose parent is not visible + for (int i = 0; i < vsize; i++) { + Row* row = (Row*)Vector_get(this->rows, i); + + // If parent not found, then construct the tree with this node as root + if (row->isRoot) { + row = (Row*)Vector_get(this->rows, i); + row->indent = 0; + row->tree_depth = 0; + Vector_add(this->displayList, row); + Table_buildTreeBranch(this, row->id, 0, 0, row->showChildren); + continue; + } + } + + this->needsSort = false; + + // Check consistency of the built structures + assert(Vector_size(this->displayList) == vsize); (void)vsize; +} + +void Table_updateDisplayList(Table* this) { + const Settings* settings = this->host->settings; + + if (settings->ss->treeView) { + if (this->needsSort) + Table_buildTree(this); + } else { + if (this->needsSort) + Vector_insertionSort(this->rows); + Vector_prune(this->displayList); + int size = Vector_size(this->rows); + for (int i = 0; i < size; i++) + Vector_add(this->displayList, Vector_get(this->rows, i)); + } + this->needsSort = false; +} + +void Table_expandTree(Table* this) { + int size = Vector_size(this->rows); + for (int i = 0; i < size; i++) { + Row* row = (Row*) Vector_get(this->rows, i); + row->showChildren = true; + } +} + +// Called on collapse-all toggle and on startup, possibly in non-tree mode +void Table_collapseAllBranches(Table* this) { + Table_buildTree(this); // Update `tree_depth` fields of the rows + this->needsSort = true; // Table is sorted by parent now, force new sort + int size = Vector_size(this->rows); + for (int i = 0; i < size; i++) { + Row* row = (Row*) Vector_get(this->rows, i); + // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1 + if (row->tree_depth > 0 && row->id > 1) + row->showChildren = false; + } +} + +void Table_rebuildPanel(Table* this) { + Table_updateDisplayList(this); + + const int currPos = Panel_getSelectedIndex(this->panel); + const int currScrollV = this->panel->scrollV; + const int currSize = Panel_size(this->panel); + + Panel_prune(this->panel); + + /* Follow main group row instead if following a row that is occluded (hidden) */ + if (this->following != -1) { + const Row* followed = (const Row*) Hashtable_get(this->table, this->following); + if (followed != NULL + && Hashtable_get(this->table, followed->group) + && Row_isVisible(followed, this) == false ) { + this->following = followed->group; + } + } + + const int rowCount = Vector_size(this->displayList); + bool foundFollowed = false; + int idx = 0; + + for (int i = 0; i < rowCount; i++) { + Row* row = (Row*) Vector_get(this->displayList, i); + + if ( !row->show || (Row_matchesFilter(row, this) == true) ) + continue; + + Panel_set(this->panel, idx, (Object*)row); + + if (this->following != -1 && row->id == this->following) { + foundFollowed = true; + Panel_setSelected(this->panel, idx); + /* Keep scroll position relative to followed row */ + this->panel->scrollV = idx - (currPos - currScrollV); + } + idx++; + } + + if (this->following != -1 && !foundFollowed) { + /* Reset if current followed row not found */ + this->following = -1; + Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); + } + + if (this->following == -1) { + /* If the last item was selected, keep the new last item selected */ + if (currPos > 0 && currPos == currSize - 1) + Panel_setSelected(this->panel, Panel_size(this->panel) - 1); + else + Panel_setSelected(this->panel, currPos); + + this->panel->scrollV = currScrollV; + } +} + +void Table_printHeader(const Settings* settings, RichString* header) { + RichString_rewind(header, RichString_size(header)); + + const ScreenSettings* ss = settings->ss; + const RowField* fields = ss->fields; + + RowField key = ScreenSettings_getActiveSortKey(ss); + + for (int i = 0; fields[i]; i++) { + int color; + if (ss->treeView && ss->treeViewAlwaysByPID) { + color = CRT_colors[PANEL_HEADER_FOCUS]; + } else if (key == fields[i]) { + color = CRT_colors[PANEL_SELECTION_FOCUS]; + } else { + color = CRT_colors[PANEL_HEADER_FOCUS]; + } + + RichString_appendWide(header, color, RowField_alignedTitle(settings, fields[i])); + if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') { + bool ascending = ScreenSettings_getActiveDirection(ss) == 1; + RichString_rewind(header, 1); // rewind to override space + RichString_appendnWide(header, + CRT_colors[PANEL_SELECTION_FOCUS], + CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC], + 1); + } + if (COMM == fields[i] && settings->showMergedCommand) { + RichString_appendAscii(header, color, "(merged)"); + } + } +} + +// set flags on an existing rows before refreshing table +void Table_prepareEntries(Table* this) { + for (int i = 0; i < Vector_size(this->rows); i++) { + Row* row = (struct Row_*) Vector_get(this->rows, i); + row->updated = false; + row->wasShown = row->show; + row->show = true; + } +} + +// tidy up Row state after refreshing the table +void Table_cleanupRow(Table* table, Row* row, int idx) { + Machine* host = table->host; + const Settings* settings = host->settings; + + if (row->tombStampMs > 0) { + // remove tombed process + if (host->monotonicMs >= row->tombStampMs) { + Table_removeIndex(table, row, idx); + } + } else if (row->updated == false) { + // process no longer exists + if (settings->highlightChanges && row->wasShown) { + // mark tombed + row->tombStampMs = host->monotonicMs + 1000 * settings->highlightDelaySecs; + } else { + // immediately remove + Table_removeIndex(table, row, idx); + } + } +} + +void Table_cleanupEntries(Table* this) { + // Finish process table update, culling any removed rows + for (int i = Vector_size(this->rows) - 1; i >= 0; i--) { + Row* row = (Row*) Vector_get(this->rows, i); + Table_cleanupRow(this, row, i); + } + + // compact the table in case of any earlier row removals + Table_compact(this); +} + +const TableClass Table_class = { + .super = { + .extends = Class(Object), + .delete = Table_delete, + }, + .prepare = Table_prepareEntries, + .cleanup = Table_cleanupEntries, +}; diff --git a/Table.h b/Table.h new file mode 100644 index 000000000..fa85fd660 --- /dev/null +++ b/Table.h @@ -0,0 +1,95 @@ +#ifndef HEADER_Table +#define HEADER_Table +/* +htop - Table.h +(C) 2004,2005 Hisham H. Muhammad +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include + +#include "Hashtable.h" +#include "Object.h" +#include "RichString.h" +#include "Settings.h" +#include "Vector.h" + + +struct Machine_; // IWYU pragma: keep +struct Panel_; // IWYU pragma: keep +struct Row_; // IWYU pragma: keep + +typedef struct Table_ { + /* Super object for emulated OOP */ + Object super; + + Vector* rows; /* all known; sort order can vary and differ from display order */ + Vector* displayList; /* row tree flattened in display order (borrowed); + updated in Table_updateDisplayList when rebuilding panel */ + Hashtable* table; /* fast known row lookup by identifier */ + + struct Machine_* host; + const char* incFilter; + bool needsSort; + int following; /* -1 or row being visually tracked in the user interface */ + + struct Panel_* panel; +} Table; + +typedef Table* (*Table_New)(const struct Machine_*); +typedef void (*Table_ScanPrepare)(Table* this); +typedef void (*Table_ScanIterate)(Table* this); +typedef void (*Table_ScanCleanup)(Table* this); + +typedef struct TableClass_ { + const ObjectClass super; + const Table_ScanPrepare prepare; + const Table_ScanIterate iterate; + const Table_ScanCleanup cleanup; +} TableClass; + +#define As_Table(this_) ((const TableClass*)((this_)->super.klass)) + +#define Table_scanPrepare(t_) (As_Table(t_)->prepare ? (As_Table(t_)->prepare(t_)) : Table_prepareEntries(t_)) +#define Table_scanIterate(t_) (As_Table(t_)->iterate(t_)) /* mandatory; must have a custom iterate method */ +#define Table_scanCleanup(t_) (As_Table(t_)->cleanup ? (As_Table(t_)->cleanup(t_)) : Table_cleanupEntries(t_)) + +Table* Table_init(Table* this, const ObjectClass* klass, struct Machine_* host); + +void Table_done(Table* this); + +extern const TableClass Table_class; + +void Table_setPanel(Table* this, struct Panel_* panel); + +void Table_printHeader(const Settings* settings, RichString* header); + +void Table_add(Table* this, struct Row_* row); + +void Table_removeIndex(Table* this, const struct Row_* row, int idx); + +void Table_updateDisplayList(Table* this); + +void Table_expandTree(Table* this); + +void Table_collapseAllBranches(Table* this); + +void Table_rebuildPanel(Table* this); + +static inline struct Row_* Table_findRow(Table* this, int id) { + return (struct Row_*) Hashtable_get(this->table, id); +} + +void Table_prepareEntries(Table* this); + +void Table_cleanupEntries(Table* this); + +void Table_cleanupRow(Table* this, Row* row, int idx); + +static inline void Table_compact(Table* this) { + Vector_compact(this->rows); +} + +#endif diff --git a/TasksMeter.c b/TasksMeter.c index b5563fcab..aa41e631e 100644 --- a/TasksMeter.c +++ b/TasksMeter.c @@ -5,12 +5,15 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "TasksMeter.h" #include "CRT.h" +#include "Machine.h" #include "Macros.h" #include "Object.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "RichString.h" #include "Settings.h" #include "XUtils.h" @@ -25,14 +28,15 @@ static const int TasksMeter_attributes[] = { static void TasksMeter_updateValues(Meter* this) { const Machine* host = this->host; - const ProcessList* pl = host->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, host->activeCPUs); - this->total = pl->totalTasks; + const ProcessTable* pt = (const ProcessTable*) host->processTable; + + this->values[0] = pt->kernelThreads; + this->values[1] = pt->userlandThreads; + this->values[2] = pt->totalTasks - pt->kernelThreads - pt->userlandThreads; + this->values[3] = MINIMUM(pt->runningTasks, host->activeCPUs); + this->total = pt->totalTasks; - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%u/%u", MINIMUM(pl->runningTasks, host->activeCPUs), pl->totalTasks); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%u/%u", MINIMUM(pt->runningTasks, host->activeCPUs), pt->totalTasks); } static void TasksMeter_display(const Object* cast, RichString* out) { diff --git a/TraceScreen.c b/TraceScreen.c index e8f55ff56..8a69b6789 100644 --- a/TraceScreen.c +++ b/TraceScreen.c @@ -62,7 +62,7 @@ void TraceScreen_delete(Object* cast) { } static void TraceScreen_draw(InfoScreen* this) { - InfoScreen_drawTitled(this, "Trace of process %d - %s", this->process->pid, Process_getCommand(this->process)); + InfoScreen_drawTitled(this, "Trace of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process)); } bool TraceScreen_forkTracer(TraceScreen* this) { @@ -89,14 +89,26 @@ bool TraceScreen_forkTracer(TraceScreen* this) { close(fdpair[1]); char buffer[32] = {0}; - xSnprintf(buffer, sizeof(buffer), "%d", this->super.process->pid); - // Use of NULL in variadic functions must have a pointer cast. - // The NULL constant is not required by standard to have a pointer type. - execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (char*)NULL); - - // Should never reach here, unless execlp fails ... - const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH."; - (void)! write(STDERR_FILENO, message, strlen(message)); + xSnprintf(buffer, sizeof(buffer), "%d", Process_getPid(this->super.process)); + + #if defined(HTOP_FREEBSD) || defined(HTOP_OPENBSD) || defined(HTOP_NETBSD) || defined(HTOP_DRAGONFLYBSD) || defined(HTOP_SOLARIS) + // Use of NULL in variadic functions must have a pointer cast. + // The NULL constant is not required by standard to have a pointer type. + execlp("truss", "truss", "-s", "512", "-p", buffer, (void*)NULL); + + // Should never reach here, unless execlp fails ... + const char* message = "Could not execute 'truss'. Please make sure it is available in your $PATH."; + (void)! write(STDERR_FILENO, message, strlen(message)); + #elif defined(HTOP_LINUX) + execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (void*)NULL); + + // Should never reach here, unless execlp fails ... + const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH."; + (void)! write(STDERR_FILENO, message, strlen(message)); + #else // HTOP_DARWIN, HTOP_PCP == HTOP_UNSUPPORTED + const char* message = "Tracing unavailable on not supported system."; + (void)! write(STDERR_FILENO, message, strlen(message)); + #endif exit(127); } @@ -164,6 +176,7 @@ static void TraceScreen_updateTrace(InfoScreen* super) { static bool TraceScreen_onKey(InfoScreen* super, int ch) { TraceScreen* this = (TraceScreen*) super; + switch (ch) { case 'f': case KEY_F(8): @@ -178,6 +191,7 @@ static bool TraceScreen_onKey(InfoScreen* super, int ch) { InfoScreen_draw(this); return true; } + this->follow = false; return false; } diff --git a/UptimeMeter.c b/UptimeMeter.c index d4b3175e1..622deda3e 100644 --- a/UptimeMeter.c +++ b/UptimeMeter.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "UptimeMeter.h" #include "CRT.h" diff --git a/Vector.c b/Vector.c index 47a772cee..0e08c6508 100644 --- a/Vector.c +++ b/Vector.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "Vector.h" #include @@ -101,7 +103,7 @@ void Vector_prune(Vector* this) { this->items = 0; this->dirty_index = -1; this->dirty_count = 0; - memset(this->array, '\0', this->arraySize * sizeof(Object *)); + memset(this->array, '\0', this->arraySize * sizeof(Object*)); } //static int comparisons = 0; @@ -198,7 +200,7 @@ static void Vector_resizeIfNecessary(Vector* this, int newSize) { assert(Vector_isConsistent(this)); int oldSize = this->arraySize; this->arraySize = newSize + this->growthRate; - this->array = (Object **)xReallocArrayZero(this->array, oldSize, this->arraySize, sizeof(Object*)); + this->array = (Object**)xReallocArrayZero(this->array, oldSize, this->arraySize, sizeof(Object*)); } assert(Vector_isConsistent(this)); } diff --git a/XUtils.c b/XUtils.c index f54ad49f8..160386c98 100644 --- a/XUtils.c +++ b/XUtils.c @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "Macros.h" void fail(void) { @@ -282,10 +284,13 @@ static ssize_t readfd_internal(int fd, void* buffer, size_t count) { continue; close(fd); + *((char*)buffer) = '\0'; return -errno; } if (res > 0) { + assert((size_t)res <= count); + buffer = ((char*)buffer) + res; count -= (size_t)res; alreadyRead += res; @@ -337,3 +342,25 @@ ssize_t full_write(int fd, const void* buf, size_t count) { return written; } + +/* Compares floating point values for ordering data entries. In this function, + NaN is considered "less than" any other floating point value (regardless of + sign), and two NaNs are considered "equal" regardless of payload. */ +int compareRealNumbers(double a, double b) { + int result = isgreater(a, b) - isgreater(b, a); + if (result) + return result; + return !isNaN(a) - !isNaN(b); +} + +/* Computes the sum of all positive floating point values in an array. + NaN values in the array are skipped. The returned sum will always be + nonnegative. */ +double sumPositiveValues(const double* array, size_t count) { + double sum = 0.0; + for (size_t i = 0; i < count; i++) { + if (isPositive(array[i])) + sum += array[i]; + } + return sum; +} diff --git a/XUtils.h b/XUtils.h index 64583db8b..68b948361 100644 --- a/XUtils.h +++ b/XUtils.h @@ -3,13 +3,19 @@ /* htop - StringUtils.h (C) 2004-2011 Hisham H. Muhammad +(C) 2020-2023 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep +// IWYU pragma: no_include "config.h" +#ifndef PACKAGE +// strchrnul() needs _GNU_SOURCE defined, see PR #1337 for details +#error "Must have #include \"config.h\" line at the top of the file that includes these XUtils helper functions" +#endif #include +#include // IWYU pragma: keep #include #include // IWYU pragma: keep #include // IWYU pragma: keep @@ -36,32 +42,51 @@ void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size) * String_startsWith gives better performance if strlen(match) can be computed * at compile time (e.g. when they are immutable string literals). :) */ +ATTR_NONNULL static inline bool String_startsWith(const char* s, const char* match) { return strncmp(s, match, strlen(match)) == 0; } bool String_contains_i(const char* s1, const char* s2, bool multi); +ATTR_NONNULL static inline bool String_eq(const char* s1, const char* s2) { return strcmp(s1, s2) == 0; } +ATTR_NONNULL char* String_cat(const char* s1, const char* s2) ATTR_MALLOC; +ATTR_NONNULL char* String_trim(const char* in) ATTR_MALLOC; +ATTR_NONNULL_N(1) char** String_split(const char* s, char sep, size_t* n); void String_freeArray(char** s); +ATTR_NONNULL char* String_readLine(FILE* fd) ATTR_MALLOC; +ATTR_NONNULL +static inline char* String_strchrnul(const char* s, int c) { +#ifdef HAVE_STRCHRNUL + return strchrnul(s, c); +#else + char* result = strchr(s, c); + if (result) + return result; + return strchr(s, '\0'); +#endif +} + /* Always null-terminates dest. Caller must pass a strictly positive size. */ ATTR_ACCESS3_W(1, 3) ATTR_ACCESS3_R(2, 3) size_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size); ATTR_FORMAT(printf, 2, 3) +ATTR_NONNULL_N(1, 2) int xAsprintf(char** strp, const char* fmt, ...); ATTR_FORMAT(printf, 3, 4) @@ -82,4 +107,17 @@ ssize_t xReadfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size ATTR_ACCESS3_R(2, 3) ssize_t full_write(int fd, const void* buf, size_t count); +/* Compares floating point values for ordering data entries. In this function, + NaN is considered "less than" any other floating point value (regardless of + sign), and two NaNs are considered "equal" regardless of payload. */ +int compareRealNumbers(double a, double b); + +/* Computes the sum of all positive floating point values in an array. + NaN values in the array are skipped. The returned sum will always be + nonnegative. */ +double sumPositiveValues(const double* array, size_t count); + +/* IEC unit prefixes */ +static const char unitPrefixes[] = { 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q' }; + #endif diff --git a/configure.ac b/configure.ac index a3cc572f3..8ffe6d07d 100644 --- a/configure.ac +++ b/configure.ac @@ -60,6 +60,9 @@ esac # Enable extensions, required by hwloc scripts AC_USE_SYSTEM_EXTENSIONS +# Activate some more of the missing global defines +AC_SYS_LARGEFILE + # ---------------------------------------------------------------------- @@ -71,6 +74,7 @@ AC_PROG_CC AM_PROG_CC_C_O m4_version_prereq([2.70], [], [AC_PROG_CC_C99]) AS_IF([test "x$ac_cv_prog_cc_c99" = xno], [AC_MSG_ERROR([htop is written in C99. A newer compiler is required.])]) +AM_CFLAGS="-std=c99 -pedantic" # ---------------------------------------------------------------------- @@ -149,7 +153,7 @@ dnl been updated in Autoconf 2.69, so use a workaround: m4_version_prereq([2.70], [], [if test "x$ac_cv_header_sys_mkdev_h" != xyes; then AC_CHECK_HEADER([sys/sysmacros.h], [AC_DEFINE([MAJOR_IN_SYSMACROS], [1], - [Define to 1 if `major', `minor', and `makedev' are declared in .])]) + [Define to 1 if 'major', 'minor', and 'makedev' are declared in .])]) fi]) # Optional Section @@ -167,7 +171,12 @@ fi # Checks for typedefs, structures, and compiler characteristics. # ---------------------------------------------------------------------- +AC_TYPE_MBSTATE_T +AC_TYPE_MODE_T +AC_TYPE_OFF_T AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T AC_TYPE_UID_T AC_TYPE_UINT8_T AC_TYPE_UINT16_T @@ -202,22 +211,59 @@ AC_COMPILE_IFELSE([ AC_MSG_RESULT(no)) CFLAGS="$old_CFLAGS" +AC_MSG_CHECKING(for nonnull) +old_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -Wno-error -Werror=attributes" +AC_COMPILE_IFELSE([ + AC_LANG_SOURCE( + [[ + /* Attribute supported in GCC 3.3 or later */ + __attribute__((nonnull)) int my_strcmp(const char* a, const char* b); + __attribute__((nonnull(1))) long my_strtol(const char* str, char** endptr, int base); + ]] + )], + AC_DEFINE([HAVE_ATTR_NONNULL], 1, [The nonnull attribute is supported.]) + AC_MSG_RESULT(yes), + AC_MSG_RESULT(no)) +CFLAGS="$old_CFLAGS" + AC_MSG_CHECKING(for NaN support) -AC_RUN_IFELSE([ +dnl Note: AC_RUN_IFELSE does not try compiling the program at all when +dnl $cross_compiling is 'yes'. +AC_LINK_IFELSE([ AC_LANG_PROGRAM( [[ - #include +#include ]], [[ - double x = NAN; return !isnan(x); + double x = NAN; + /* Both should evaluate to false -> 0 (exit success) */ + return isgreater(x, x) || isgreaterequal(x, x); ]] )], - [AC_MSG_RESULT(yes)], - [ + [flag_finite_math_only=unknown + if test "$cross_compiling" = yes; then + AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([[ +/* __FINITE_MATH_ONLY__ is documented in Clang. */ +#ifdef __FINITE_MATH_ONLY__ +#error "should not enable -ffinite-math-only" +#endif + ]])], + AC_MSG_RESULT([assume yes (cross compiling)]), + flag_finite_math_only=yes) + elif ./conftest$EXEEXT >&AS_MESSAGE_LOG_FD; then + flag_finite_math_only=no + AC_MSG_RESULT(yes) + else + flag_finite_math_only=yes + fi + if test "$flag_finite_math_only" = yes; then AC_MSG_RESULT(no) - AC_MSG_WARN([Compiler does not respect NaN, some functionality might break; consider using '-fno-finite-math-only']) - ], - [AC_MSG_RESULT(skipped)]) + AC_MSG_WARN([runtime behavior with NaN is not compliant - some functionality might break; consider using '-fno-finite-math-only']) + fi], + [AC_MSG_RESULT(no) + AC_MSG_ERROR([can not find required macros: NAN, isgreater() and isgreaterequal()])]) # ---------------------------------------------------------------------- @@ -276,6 +322,7 @@ AC_CHECK_FUNCS([ \ readlinkat \ sched_getscheduler \ sched_setscheduler \ + strchrnul \ ]) if test "$my_htop_platform" = darwin; then @@ -311,15 +358,15 @@ m4_define([HTOP_CHECK_SCRIPT], htop_config_script_cflags=$([$4] --cflags 2> /dev/null) fi htop_script_success=no - htop_save_CFLAGS="$CFLAGS" + htop_save_CFLAGS="$AM_CFLAGS" if test ! "x$htop_config_script_libs" = x; then - CFLAGS="$htop_config_script_cflags $CFLAGS" + AM_CFLAGS="$AM_CFLAGS $htop_config_script_cflags" AC_CHECK_LIB([$1], [$2], [ AC_DEFINE([$3], 1, [The library is present.]) LIBS="$htop_config_script_libs $LIBS " htop_script_success=yes ], [ - CFLAGS="$htop_save_CFLAGS" + AM_CFLAGS="$htop_save_CFLAGS" ], [ $htop_config_script_libs ]) @@ -428,7 +475,7 @@ if test "x$enable_affinity" = xcheck; then AC_MSG_RESULT([yes])], [enable_affinity=no AC_MSG_RESULT([no])], - [AC_MSG_RESULT([yes (assumed while cross compiling)])]) + [AC_MSG_RESULT([assume yes (cross compiling)])]) fi fi if test "x$enable_affinity" = xyes; then @@ -465,7 +512,7 @@ case "$enable_unwind" in yes) AC_CHECK_LIB([unwind], [backtrace], [], [AC_MSG_ERROR([can not find required library libunwind])]) AC_CHECK_HEADERS([libunwind.h], [], [ - CFLAGS="$CFLAGS -I/usr/include/libunwind" + AM_CFLAGS="$AM_CFLAGS -I/usr/include/libunwind" AC_CHECK_HEADERS([libunwind/libunwind.h], [], [AC_MSG_ERROR([can not find required header file libunwind.h])]) ]) ;; @@ -491,9 +538,9 @@ case "$enable_hwloc" in m4_ifdef([PKG_PROG_PKG_CONFIG], [ PKG_PROG_PKG_CONFIG() PKG_CHECK_MODULES(HWLOC, hwloc, [ - CFLAGS="$CFLAGS $HWLOC_CFLAGS" + AM_CFLAGS="$AM_CFLAGS $HWLOC_CFLAGS" LIBS="$LIBS $HWLOC_LIBS" - AC_DEFINE([HAVE_LIBHWLOC], [1], [Define to 1 if you have the `hwloc' library (-lhwloc).]) + AC_DEFINE([HAVE_LIBHWLOC], [1], [Define to 1 if you have the 'hwloc' library (-lhwloc).]) ], [ AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])]) AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])]) @@ -616,7 +663,7 @@ case "$enable_delayacct" in PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [enable_delayacct=no]) PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [enable_delayacct=no]) if test "$enable_delayacct" = yes; then - CFLAGS="$CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS" + AM_CFLAGS="$AM_CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS" LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS" AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.]) fi @@ -631,7 +678,7 @@ case "$enable_delayacct" in PKG_PROG_PKG_CONFIG() PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [AC_MSG_ERROR([can not find required library libnl3])]) PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [AC_MSG_ERROR([can not find required library libnl3genl])]) - CFLAGS="$CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS" + AM_CFLAGS="$AM_CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS" LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS" AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.]) ], [ @@ -682,7 +729,7 @@ fi # Checks for compiler warnings. # ---------------------------------------------------------------------- -AM_CFLAGS="\ +AM_CFLAGS="$AM_CFLAGS\ -Wall\ -Wcast-align\ -Wcast-qual\ @@ -700,11 +747,6 @@ AM_CFLAGS="\ -Wunused\ -Wwrite-strings" -# FreeBSD uses C11 _Generic in its isnan implementation, even with -std=c99 -if test "$my_htop_platform" = freebsd; then - AM_CFLAGS="$AM_CFLAGS -Wno-c11-extensions" -fi - dnl https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html AC_DEFUN([AX_CHECK_COMPILE_FLAG], [ @@ -742,6 +784,22 @@ AC_ARG_ENABLE([debug], [enable_debug=no]) if test "x$enable_debug" != xyes; then AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG" + + AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([[ +#ifdef __SUPPORT_SNAN__ +#error "signaling NaN support not recommended" +#endif + ]])], + :, + [warning_msg="signaling NaN support is enabled; not recommended for htop" + case "$CC" in + *gcc*) + warning_msg="$warning_msg (use '-fno-signaling-nans' compiler flag to disable)" + ;; + esac + AC_MSG_WARN([$warning_msg]) + ]) else AM_CPPFLAGS="$AM_CPPFLAGS -ggdb3" fi @@ -757,7 +815,7 @@ AC_SUBST([AM_CPPFLAGS]) # We're done, let's go! # ---------------------------------------------------------------------- -AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2023 htop dev team."], [Copyright message.]) +AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2024 htop dev team."], [Copyright message.]) AM_CONDITIONAL([HTOP_LINUX], [test "$my_htop_platform" = linux]) AM_CONDITIONAL([HTOP_FREEBSD], [test "$my_htop_platform" = freebsd]) diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index e6366d70f..1e315ebaf 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -72,18 +72,21 @@ void Process_delete(Object* cast) { free(this); } -static void DarwinProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const DarwinProcess* dp = (const DarwinProcess*) this; +static void DarwinProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const DarwinProcess* dp = (const DarwinProcess*) super; + char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; - int n = sizeof(buffer) - 1; + size_t n = sizeof(buffer) - 1; + switch (field) { // add Platform-specific fields here case TRANSLATED: xSnprintf(buffer, n, "%c ", dp->translated ? 'T' : 'N'); break; default: - Process_writeField(this, str, field); + Process_writeField(&dp->super, str, field); return; } + RichString_appendWide(str, attr, buffer); } @@ -292,14 +295,14 @@ static char* DarwinProcess_getDevname(dev_t dev) { void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) { DarwinProcess* dp = (DarwinProcess*)proc; - const Settings* settings = proc->host->settings; + const Settings* settings = proc->super.host->settings; const struct extern_proc* ep = &ps->kp_proc; /* UNSET HERE : * * processor - * user (set at ProcessList level) + * user (set at ProcessTable level) * nlwp * percent_cpu * percent_mem @@ -312,12 +315,12 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, /* First, the "immutable" parts */ if (!exists) { /* Set the PID/PGID/etc. */ - proc->pid = ep->p_pid; - proc->ppid = ps->kp_eproc.e_ppid; + Process_setPid(proc, ep->p_pid); + Process_setThreadGroup(proc, ep->p_pid); + Process_setParent(proc, ps->kp_eproc.e_ppid); proc->pgrp = ps->kp_eproc.e_pgid; proc->session = 0; /* TODO Get the session id */ proc->tpgid = ps->kp_eproc.e_tpgid; - proc->tgid = proc->pid; proc->isKernelThread = false; proc->isUserlandThread = false; dp->translated = ps->kp_proc.p_flag & P_TRANSLATED; @@ -359,14 +362,14 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, proc->state = (ep->p_stat == SZOMB) ? ZOMBIE : UNKNOWN; /* Make sure the updated flag is set */ - proc->updated = true; + proc->super.updated = true; } -void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS) { +void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable* dpt, double timeIntervalNS) { struct proc_taskinfo pti; - if (sizeof(pti) == proc_pidinfo(proc->super.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { - const DarwinMachine* dhost = (const DarwinMachine*) proc->super.host; + if (sizeof(pti) == proc_pidinfo(Process_getPid(&proc->super), PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { + const DarwinMachine* dhost = (const DarwinMachine*) proc->super.super.host; uint64_t total_existing_time_ns = proc->stime + proc->utime; @@ -394,10 +397,10 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* proc->stime = system_time_ns; proc->utime = user_time_ns; - dpl->super.kernelThreads += 0; /*pti.pti_threads_system;*/ - dpl->super.userlandThreads += pti.pti_threadnum; /*pti.pti_threads_user;*/ - dpl->super.totalTasks += pti.pti_threadnum; - dpl->super.runningTasks += pti.pti_numrunning; + dpt->super.kernelThreads += 0; /*pti.pti_threads_system;*/ + dpt->super.userlandThreads += pti.pti_threadnum; /*pti.pti_threads_user;*/ + dpt->super.totalTasks += pti.pti_threadnum; + dpt->super.runningTasks += pti.pti_numrunning; } } @@ -419,7 +422,7 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) { } task_t port; - ret = task_for_pid(mach_task_self(), proc->pid, &port); + ret = task_for_pid(mach_task_self(), Process_getPid(proc), &port); if (ret != KERN_SUCCESS) { dp->taskAccess = false; return; @@ -472,11 +475,18 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) { const ProcessClass DarwinProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = DarwinProcess_rowWriteField }, - .writeField = DarwinProcess_writeField, - .compareByKey = DarwinProcess_compareByKey, + .compareByKey = DarwinProcess_compareByKey }; diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h index 89a0576da..496b179b3 100644 --- a/darwin/DarwinProcess.h +++ b/darwin/DarwinProcess.h @@ -10,7 +10,7 @@ in the source distribution for its full text. #include #include "Machine.h" -#include "darwin/DarwinProcessList.h" +#include "darwin/DarwinProcessTable.h" #define PROCESS_FLAG_TTY 0x00000100 @@ -34,7 +34,7 @@ void Process_delete(Object* cast); void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists); -void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS); +void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable* dpt, double timeIntervalNS); /* * Scan threads for process state information. diff --git a/darwin/DarwinProcessList.c b/darwin/DarwinProcessTable.c similarity index 68% rename from darwin/DarwinProcessList.c rename to darwin/DarwinProcessTable.c index c10e772c9..850b503fa 100644 --- a/darwin/DarwinProcessList.c +++ b/darwin/DarwinProcessTable.c @@ -1,11 +1,11 @@ /* -htop - DarwinProcessList.c +htop - DarwinProcessTable.c (C) 2014 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "darwin/DarwinProcessList.h" +#include "darwin/DarwinProcessTable.h" #include #include @@ -19,7 +19,7 @@ in the source distribution for its full text. #include #include "CRT.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "darwin/DarwinMachine.h" #include "darwin/DarwinProcess.h" #include "darwin/Platform.h" @@ -28,7 +28,7 @@ in the source distribution for its full text. #include "zfs/ZfsArcStats.h" -static struct kinfo_proc* ProcessList_getKInfoProcs(size_t* count) { +static struct kinfo_proc* ProcessTable_getKInfoProcs(size_t* count) { int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; struct kinfo_proc* processes = NULL; @@ -53,38 +53,40 @@ static struct kinfo_proc* ProcessList_getKInfoProcs(size_t* count) { CRT_fatalError("Unable to get kinfo_procs"); } -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - DarwinProcessList* this = xCalloc(1, sizeof(DarwinProcessList)); - ProcessList* super = &this->super; +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + DarwinProcessTable* this = xCalloc(1, sizeof(DarwinProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(super, Class(DarwinProcess), host, pidMatchList); + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(DarwinProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* this) { - ProcessList_done(this); +void ProcessTable_delete(Object* cast) { + DarwinProcessTable* this = (DarwinProcessTable*) cast; + ProcessTable_done(&this->super); free(this); } -void ProcessList_goThroughEntries(ProcessList* super) { - const Machine* host = super->host; +void ProcessTable_goThroughEntries(ProcessTable* super) { + const Machine* host = super->super.host; const DarwinMachine* dhost = (const DarwinMachine*) host; - DarwinProcessList* dpl = (DarwinProcessList*) super; + DarwinProcessTable* dpt = (DarwinProcessTable*) super; bool preExisting = true; struct kinfo_proc* ps; size_t count; DarwinProcess* proc; /* Get the time difference */ - dpl->global_diff = 0; + dpt->global_diff = 0; for (unsigned int i = 0; i < host->existingCPUs; ++i) { for (size_t j = 0; j < CPU_STATE_MAX; ++j) { - dpl->global_diff += dhost->curr_load[i].cpu_ticks[j] - dhost->prev_load[i].cpu_ticks[j]; + dpt->global_diff += dhost->curr_load[i].cpu_ticks[j] - dhost->prev_load[i].cpu_ticks[j]; } } - const double time_interval_ns = Platform_schedulerTicksToNanoseconds(dpl->global_diff) / (double) host->activeCPUs; + const double time_interval_ns = Platform_schedulerTicksToNanoseconds(dpt->global_diff) / (double) host->activeCPUs; /* We use kinfo_procs for initial data since : * @@ -93,13 +95,13 @@ void ProcessList_goThroughEntries(ProcessList* super) { * * We attempt to fill-in additional information with libproc. */ - ps = ProcessList_getKInfoProcs(&count); + ps = ProcessTable_getKInfoProcs(&count); for (size_t i = 0; i < count; ++i) { - proc = (DarwinProcess*)ProcessList_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new); + proc = (DarwinProcess*)ProcessTable_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new); DarwinProcess_setFromKInfoProc(&proc->super, &ps[i], preExisting); - DarwinProcess_setFromLibprocPidinfo(proc, dpl, time_interval_ns); + DarwinProcess_setFromLibprocPidinfo(proc, dpt, time_interval_ns); if (proc->super.st_uid != ps[i].kp_eproc.e_ucred.cr_uid) { proc->super.st_uid = ps[i].kp_eproc.e_ucred.cr_uid; @@ -116,7 +118,7 @@ void ProcessList_goThroughEntries(ProcessList* super) { super->totalTasks += 1; if (!preExisting) { - ProcessList_add(super, &proc->super); + ProcessTable_add(super, &proc->super); } } diff --git a/darwin/DarwinProcessList.h b/darwin/DarwinProcessTable.h similarity index 52% rename from darwin/DarwinProcessList.h rename to darwin/DarwinProcessTable.h index 56d6c1b52..7467bfd97 100644 --- a/darwin/DarwinProcessList.h +++ b/darwin/DarwinProcessTable.h @@ -1,7 +1,7 @@ -#ifndef HEADER_DarwinProcessList -#define HEADER_DarwinProcessList +#ifndef HEADER_DarwinProcessTable +#define HEADER_DarwinProcessTable /* -htop - DarwinProcessList.h +htop - DarwinProcessTable.h (C) 2014 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. @@ -10,13 +10,13 @@ in the source distribution for its full text. #include #include -#include "ProcessList.h" +#include "ProcessTable.h" -typedef struct DarwinProcessList_ { - ProcessList super; +typedef struct DarwinProcessTable_ { + ProcessTable super; uint64_t global_diff; -} DarwinProcessList; +} DarwinProcessTable; #endif diff --git a/darwin/Platform.c b/darwin/Platform.c index bb1ae92f7..387910e16 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -14,6 +14,10 @@ in the source distribution for its full text. #include #include #include +#include +#include +#include +#include #include #include @@ -138,6 +142,7 @@ const MeterClass* const Platform_meterTypes[] = { &ZfsArcMeter_class, &ZfsCompressedArcMeter_class, &DiskIOMeter_class, + &NetworkIOMeter_class, &FileDescriptorMeter_class, &BlankMeter_class, NULL @@ -227,7 +232,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { } } -int Platform_getMaxPid(void) { +pid_t Platform_getMaxPid(void) { /* http://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/proc_internal.hh */ return 99999; } @@ -292,9 +297,9 @@ void Platform_setMemoryValues(Meter* mtr) { mtr->total = dhost->host_info.max_mem / 1024; mtr->values[MEMORY_METER_USED] = (double)(vm->active_count + vm->wire_count) * page_K; - mtr->values[MEMORY_METER_BUFFERS] = (double)vm->purgeable_count * page_K; // mtr->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm" // mtr->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + mtr->values[MEMORY_METER_BUFFERS] = (double)vm->purgeable_count * page_K; mtr->values[MEMORY_METER_CACHE] = (double)vm->inactive_count * page_K; // mtr->values[MEMORY_METER_AVAILABLE] = "available memory" } @@ -467,10 +472,65 @@ bool Platform_getDiskIO(DiskIOData* data) { return true; } +/* Caution: Given that interfaces are dynamic, and it is not possible to get statistics on interfaces that no longer exist, + if some interface disappears between the time of two samples, the values of the second sample may be lower than those of + the first one. */ bool Platform_getNetworkIO(NetworkIOData* data) { - // TODO - (void)data; - return false; + int mib[6] = {CTL_NET, + PF_ROUTE, /* routing messages */ + 0, /* protocal number, currently always 0 */ + 0, /* select all address families */ + NET_RT_IFLIST2, /* interface list with addresses */ + 0}; + + for (size_t retry = 0; retry < 4; retry++) { + size_t len = 0; + + /* Determine len */ + if (sysctl(mib, ARRAYSIZE(mib), NULL, &len, NULL, 0) < 0 || len == 0) + return false; + + len += 16 * retry * retry * sizeof(struct if_msghdr2); + char *buf = xMalloc(len); + + if (sysctl(mib, ARRAYSIZE(mib), buf, &len, NULL, 0) < 0) { + free(buf); + if (errno == ENOMEM && retry < 3) + continue; + else + return false; + } + + uint64_t bytesReceived_sum = 0, packetsReceived_sum = 0, bytesTransmitted_sum = 0, packetsTransmitted_sum = 0; + + for (char *next = buf; next < buf + len;) { + void *tmp = (void*) next; + struct if_msghdr *ifm = (struct if_msghdr*) tmp; + + next += ifm->ifm_msglen; + + if (ifm->ifm_type != RTM_IFINFO2) + continue; + + struct if_msghdr2 *ifm2 = (struct if_msghdr2*) ifm; + + if (ifm2->ifm_data.ifi_type != IFT_LOOP) { /* do not count loopback traffic */ + bytesReceived_sum += ifm2->ifm_data.ifi_ibytes; + packetsReceived_sum += ifm2->ifm_data.ifi_ipackets; + bytesTransmitted_sum += ifm2->ifm_data.ifi_obytes; + packetsTransmitted_sum += ifm2->ifm_data.ifi_opackets; + } + } + + data->bytesReceived = bytesReceived_sum; + data->packetsReceived = packetsReceived_sum; + data->bytesTransmitted = bytesTransmitted_sum; + data->packetsTransmitted = packetsTransmitted_sum; + + free(buf); + } + + return true; } void Platform_getBattery(double* percent, ACPresence* isOnAC) { diff --git a/darwin/Platform.h b/darwin/Platform.h index 5cd672979..f67db8ff4 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -54,7 +54,7 @@ int Platform_getUptime(void); void Platform_getLoadAverage(double* one, double* five, double* fifteen); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* mtr, unsigned int cpu); @@ -118,7 +118,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -126,4 +126,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/docs/styleguide.md b/docs/styleguide.md index 82198f6b3..92e69f5a3 100644 --- a/docs/styleguide.md +++ b/docs/styleguide.md @@ -75,6 +75,11 @@ The include list should be in the following order, with each group separated by The list of headers should be sorted with includes from subdirectories following after files inside their parent directory. Thus `unistd.h` sorts before `sys/time.h`. +When `XUtils.h` is used by the module itself or any of its included headers, the C source file must include `config.h` in the manner noted above. +Failure to do so will cause a compilation error (sanity check inside `XUtils.h`) or may result in other, hard-to-debug compilation issues. +The include for `config.h` is only ever placed in the C source file and never in any header file. +For further details see PR #1337 in our issue tracker. + Symbol Exports -------------- diff --git a/dragonflybsd/DragonFlyBSDMachine.c b/dragonflybsd/DragonFlyBSDMachine.c index 6831fa3df..fd5b58b67 100644 --- a/dragonflybsd/DragonFlyBSDMachine.c +++ b/dragonflybsd/DragonFlyBSDMachine.c @@ -104,10 +104,10 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { this->cpus = xRealloc(this->cpus, (super->existingCPUs + 1) * sizeof(CPUData)); } - len = sizeof(kernelFScale); - if (sysctlbyname("kern.fscale", &kernelFScale, &len, NULL, 0) == -1) { + len = sizeof(this->kernelFScale); + if (sysctlbyname("kern.fscale", &this->kernelFScale, &len, NULL, 0) == -1 || this->kernelFScale <= 0) { //sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed - kernelFScale = 2048; + this->kernelFScale = 2048; } this->kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf); @@ -223,7 +223,7 @@ static void DragonFlyBSDMachine_scanCPUTime(Machine* super) { } static void DragonFlyBSDMachine_scanMemoryInfo(Machine* super) { - DragonFlyBSDMachine* this = (DragonFlyBSDProcessList*) super; + DragonFlyBSDMachine* this = (DragonFlyBSDProcessTable*) super; // @etosan: // memory counter relationships seem to be these: @@ -235,10 +235,10 @@ static void DragonFlyBSDMachine_scanMemoryInfo(Machine* super) { //disabled for now, as it is always smaller than phycal amount of memory... //...to avoid "where is my memory?" questions - //sysctl(MIB_vm_stats_vm_v_page_count, 4, &(pl->totalMem), &len, NULL, 0); - //pl->totalMem *= pageSizeKb; + //sysctl(MIB_vm_stats_vm_v_page_count, 4, &(this->totalMem), &len, NULL, 0); + //this->totalMem *= pageSizeKb; sysctl(MIB_hw_physmem, 2, &(super->totalMem), &len, NULL, 0); - pl->totalMem /= 1024; + super->totalMem /= 1024; sysctl(MIB_vm_stats_vm_v_active_count, 4, &(this->memActive), &len, NULL, 0); this->memActive *= this->pageSizeKb; diff --git a/dragonflybsd/DragonFlyBSDMachine.h b/dragonflybsd/DragonFlyBSDMachine.h index 276a73d75..0d4d8ef1a 100644 --- a/dragonflybsd/DragonFlyBSDMachine.h +++ b/dragonflybsd/DragonFlyBSDMachine.h @@ -19,7 +19,7 @@ in the source distribution for its full text. #include "Hashtable.h" #include "Machine.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "UsersTable.h" diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c index 7cfc71be5..4be2198d3 100644 --- a/dragonflybsd/DragonFlyBSDProcess.c +++ b/dragonflybsd/DragonFlyBSDProcess.c @@ -66,20 +66,24 @@ void Process_delete(Object* cast) { free(this); } -static void DragonFlyBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) this; +static void DragonFlyBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const Process* this = (const Process*) super; + const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) super; + char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; + switch (field) { // add Platform-specific fields here - case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_isKernelThread(this) ? -1 : this->pid); break; + case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_isKernelThread(this) ? -1 : Process_getPid(this)); break; case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break; - case JAIL: Process_printLeftAlignedField(str, attr, fp->jname, 11); return; + case JAIL: Row_printLeftAlignedField(str, attr, fp->jname, 11); return; default: - Process_writeField(this, str, field); + Process_writeField(&fp->super, str, field); return; } + RichString_appendWide(str, attr, buffer); } @@ -100,11 +104,18 @@ static int DragonFlyBSDProcess_compareByKey(const Process* v1, const Process* v2 const ProcessClass DragonFlyBSDProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = DragonFlyBSDProcess_rowWriteField }, - .writeField = DragonFlyBSDProcess_writeField, .compareByKey = DragonFlyBSDProcess_compareByKey }; diff --git a/dragonflybsd/DragonFlyBSDProcessList.h b/dragonflybsd/DragonFlyBSDProcessList.h deleted file mode 100644 index 3f5cdc3e2..000000000 --- a/dragonflybsd/DragonFlyBSDProcessList.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef HEADER_DragonFlyBSDProcessList -#define HEADER_DragonFlyBSDProcessList -/* -htop - DragonFlyBSDProcessList.h -(C) 2014 Hisham H. Muhammad -(C) 2017 Diederik de Groot -Released under the GNU GPLv2+, see the COPYING file -in the source distribution for its full text. -*/ - -#include -#include - -#include "ProcessList.h" - - -typedef struct DragonFlyBSDProcessList_ { - ProcessList super; -} DragonFlyBSDProcessList; - -#endif diff --git a/dragonflybsd/DragonFlyBSDProcessList.c b/dragonflybsd/DragonFlyBSDProcessTable.c similarity index 65% rename from dragonflybsd/DragonFlyBSDProcessList.c rename to dragonflybsd/DragonFlyBSDProcessTable.c index 4ff17932c..e36086f29 100644 --- a/dragonflybsd/DragonFlyBSDProcessList.c +++ b/dragonflybsd/DragonFlyBSDProcessTable.c @@ -1,12 +1,12 @@ /* -htop - DragonFlyBSDProcessList.c +htop - DragonFlyBSDProcessTable.c (C) 2014 Hisham H. Muhammad (C) 2017 Diederik de Groot Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "dragonflybsd/DragonFlyBSDProcessList.h" +#include "dragonflybsd/DragonFlyBSDProcessTable.h" #include #include @@ -26,22 +26,23 @@ in the source distribution for its full text. #include "dragonflybsd/DragonFlyBSDProcess.h" -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - DragonFlyBSDProcessList* this = xCalloc(1, sizeof(DragonFlyBSDProcessList)); - ProcessList* super = (ProcessList*) this; +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + DragonFlyBSDProcessTable* this = xCalloc(1, sizeof(DragonFlyBSDProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(super, Class(DragonFlyBSDProcess), host, pidMatchList); + ProcessTable* super = (ProcessTable*) this; + ProcessTable_init(super, Class(DragonFlyBSDProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* super) { - const DragonFlyBSDProcessList* this = (DragonFlyBSDProcessList*) super; - ProcessList_done(super); +void ProcessTable_delete(Object* cast) { + const DragonFlyBSDProcessTable* this = (DragonFlyBSDProcessTable*) cast; + ProcessTable_done(&this->super); free(this); } -//static void DragonFlyBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) { +//static void DragonFlyBSDProcessTable_updateExe(const struct kinfo_proc* kproc, Process* proc) { // const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, kproc->kp_pid }; // char buffer[2048]; // size_t size = sizeof(buffer); @@ -59,7 +60,7 @@ void ProcessList_delete(ProcessList* super) { // Process_updateExe(proc, buffer); //} -static void DragonFlyBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) { +static void DragonFlyBSDProcessTable_updateExe(const struct kinfo_proc* kproc, Process* proc) { if (Process_isKernelThread(proc)) return; @@ -75,7 +76,7 @@ static void DragonFlyBSDProcessList_updateExe(const struct kinfo_proc* kproc, Pr Process_updateExe(proc, target); } -static void DragonFlyBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) { +static void DragonFlyBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) { const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, kproc->kp_pid }; char buffer[2048]; size_t size = sizeof(buffer); @@ -95,7 +96,7 @@ static void DragonFlyBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Pr free_and_xStrdup(&proc->procCwd, buffer); } -static void DragonFlyBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) { +static void DragonFlyBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) { Process_updateComm(proc, kproc->kp_comm); char** argv = kvm_getargv(kd, kproc, 0); @@ -128,7 +129,7 @@ static void DragonFlyBSDProcessList_updateProcessName(kvm_t* kd, const struct ki free(cmdline); } -void ProcessList_goThroughEntries(ProcessList* super) { +void ProcessTable_goThroughEntries(ProcessTable* super) { const Machine* host = super->host; const DragonFlyMachine* dhost = (const DragonFlyMachine*) host; const Settings* settings = host->settings; @@ -146,24 +147,24 @@ void ProcessList_goThroughEntries(ProcessList* super) { bool ATTR_UNUSED isIdleProcess = false; // note: dragonflybsd kernel processes all have the same pid, so we misuse the kernel thread address to give them a unique identifier - Process* proc = ProcessList_getProcess(super, kproc->kp_ktaddr ? (pid_t)kproc->kp_ktaddr : kproc->kp_pid, &preExisting, DragonFlyBSDProcess_new); + Process* proc = ProcessTable_getProcess(super, kproc->kp_ktaddr ? (pid_t)kproc->kp_ktaddr : kproc->kp_pid, &preExisting, DragonFlyBSDProcess_new); DragonFlyBSDProcess* dfp = (DragonFlyBSDProcess*) proc; if (!preExisting) { dfp->jid = kproc->kp_jailid; if (kproc->kp_ktaddr && kproc->kp_flags & P_SYSTEM) { // dfb kernel threads all have the same pid, so we misuse the kernel thread address to give them a unique identifier - proc->pid = (pid_t)kproc->kp_ktaddr; + Process_setPid(proc, (pid_t)kproc->kp_ktaddr); proc->isKernelThread = true; } else { - proc->pid = kproc->kp_pid; // process ID + Process_setPid(proc, kproc->kp_pid); // process ID proc->isKernelThread = false; } proc->isUserlandThread = kproc->kp_nthreads > 1; - proc->ppid = kproc->kp_ppid; // parent process id + Process_setParent(proc, 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 + //Process_setThreadGroup(proc, kproc->kp_lwp.kl_tid); // thread group id + Process_setThreadGroup(proc, kproc->kp_pid); proc->pgrp = kproc->kp_pgid; // process group id proc->session = kproc->kp_sid; proc->st_uid = kproc->kp_uid; // user ID @@ -181,14 +182,14 @@ void ProcessList_goThroughEntries(ProcessList* super) { free_and_xStrdup(&proc->tty_name, name); } - DragonFlyBSDProcessList_updateExe(kproc, proc); - DragonFlyBSDProcessList_updateProcessName(dhost->kd, kproc, proc); + DragonFlyBSDProcessTable_updateExe(kproc, proc); + DragonFlyBSDProcessTable_updateProcessName(dhost->kd, kproc, proc); if (settings->ss->flags & PROCESS_FLAG_CWD) { - DragonFlyBSDProcessList_updateCwd(kproc, proc); + DragonFlyBSDProcessTable_updateCwd(kproc, proc); } - ProcessList_add(super, proc); + ProcessTable_add(super, proc); dfp->jname = DragonFlyBSDMachine_readJailName(dhost, kproc->kp_jailid); } else { @@ -199,13 +200,13 @@ void ProcessList_goThroughEntries(ProcessList* super) { dfp->jname = DragonFlyBSDMachine_readJailName(dhost, kproc->kp_jailid); } // if there are reapers in the system, process can get reparented anytime - proc->ppid = kproc->kp_ppid; + Process_setParent(proc, kproc->kp_ppid); if (proc->st_uid != kproc->kp_uid) { // some processes change users (eg. to lower privs) proc->st_uid = kproc->kp_uid; proc->user = UsersTable_getRef(host->usersTable, proc->st_uid); } if (settings->updateProcessNames) { - DragonFlyBSDProcessList_updateProcessName(dhost->kd, kproc, proc); + DragonFlyBSDProcessTable_updateProcessName(dhost->kd, kproc, proc); } } @@ -248,44 +249,56 @@ void ProcessList_goThroughEntries(ProcessList* super) { // would be nice if we could store multiple states in proc->state (as enum) and have writeField render them /* Taken from: https://github.com/DragonFlyBSD/DragonFlyBSD/blob/c163a4d7ee9c6857ee4e04a3a2cbb50c3de29da1/sys/sys/proc_common.h */ switch (kproc->kp_stat) { - case SIDL: proc->state = IDLE; isIdleProcess = true; break; - case SACTIVE: - switch (kproc->kp_lwp.kl_stat) { - case LSSLEEP: - if (kproc->kp_lwp.kl_flags & LWP_SINTR) // interruptible wait short/long - if (kproc->kp_lwp.kl_slptime >= MAXSLP) { - proc->state = IDLE; - isIdleProcess = true; - } else { + case SIDL: + proc->state = IDLE; + isIdleProcess = true; + break; + case SACTIVE: + switch (kproc->kp_lwp.kl_stat) { + case LSSLEEP: + if (kproc->kp_lwp.kl_flags & LWP_SINTR) { // interruptible wait short/long + if (kproc->kp_lwp.kl_slptime >= MAXSLP) { + proc->state = IDLE; + isIdleProcess = true; + } else { + proc->state = SLEEPING; + } + } else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) { // interruptible lwkt wait proc->state = SLEEPING; + } else if (kproc->kp_paddr) { // uninterruptible wait + proc->state = UNINTERRUPTIBLE_WAIT; + } else { // uninterruptible lwkt wait + proc->state = UNINTERRUPTIBLE_WAIT; } - else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) // interruptible lwkt wait - proc->state = SLEEPING; - else if (kproc->kp_paddr) // uninterruptible wait - proc->state = UNINTERRUPTIBLE_WAIT; - else // uninterruptible lwkt wait - proc->state = UNINTERRUPTIBLE_WAIT; - break; - case LSRUN: - if (kproc->kp_lwp.kl_stat == LSRUN) { - if (!(kproc->kp_lwp.kl_tdflags & (TDF_RUNNING | TDF_RUNQ))) - proc->state = QUEUED; - else - proc->state = RUNNING; - } - break; - case LSSTOP: - proc->state = STOPPED; - break; - default: - proc->state = PAGING; - break; - } - break; - case SSTOP: proc->state = STOPPED; break; - case SZOMB: proc->state = ZOMBIE; break; - case SCORE: proc->state = BLOCKED; break; - default: proc->state = UNKNOWN; + break; + case LSRUN: + if (kproc->kp_lwp.kl_stat == LSRUN) { + if (!(kproc->kp_lwp.kl_tdflags & (TDF_RUNNING | TDF_RUNQ))) { + proc->state = QUEUED; + } else { + proc->state = RUNNING; + } + } + break; + case LSSTOP: + proc->state = STOPPED; + break; + default: + proc->state = PAGING; + break; + } + break; + case SSTOP: + proc->state = STOPPED; + break; + case SZOMB: + proc->state = ZOMBIE; + break; + case SCORE: + proc->state = BLOCKED; + break; + default: + proc->state = UNKNOWN; } if (kproc->kp_flags & P_SWAPPEDOUT) @@ -303,7 +316,7 @@ void ProcessList_goThroughEntries(ProcessList* super) { if (proc->state == RUNNING) super->runningTasks++; - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); - proc->updated = true; + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.updated = true; } } diff --git a/dragonflybsd/DragonFlyBSDProcessTable.h b/dragonflybsd/DragonFlyBSDProcessTable.h new file mode 100644 index 000000000..e8ff1af46 --- /dev/null +++ b/dragonflybsd/DragonFlyBSDProcessTable.h @@ -0,0 +1,21 @@ +#ifndef HEADER_DragonFlyBSDProcessTable +#define HEADER_DragonFlyBSDProcessTable +/* +htop - DragonFlyBSDProcessTable.h +(C) 2014 Hisham H. Muhammad +(C) 2017 Diederik de Groot +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + +#include "ProcessTable.h" + + +typedef struct DragonFlyBSDProcessTable_ { + ProcessTable super; +} DragonFlyBSDProcessTable; + +#endif diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index 36307e931..25afa8b18 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -6,6 +6,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "dragonflybsd/Platform.h" #include @@ -23,15 +25,17 @@ in the source distribution for its full text. #include "FileDescriptorMeter.h" #include "HostnameMeter.h" #include "LoadAverageMeter.h" +#include "Macros.h" #include "MemoryMeter.h" #include "MemorySwapMeter.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "SwapMeter.h" #include "SysArchMeter.h" #include "TasksMeter.h" #include "UptimeMeter.h" +#include "XUtils.h" #include "dragonflybsd/DragonFlyBSDProcess.h" -#include "dragonflybsd/DragonFlyBSDProcessList.h" +#include "dragonflybsd/DragonFlyBSDProcessTable.h" #include "generic/fdstat_sysctl.h" @@ -161,7 +165,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale; } -int Platform_getMaxPid(void) { +pid_t Platform_getMaxPid(void) { int maxPid; size_t size = sizeof(maxPid); int err = sysctlbyname("kern.pid_max", &maxPid, &size, NULL, 0); @@ -193,14 +197,13 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) { v[CPU_METER_KERNEL] = cpuData->systemPercent; v[CPU_METER_IRQ] = cpuData->irqPercent; this->curItems = 4; - percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ]; } else { v[CPU_METER_KERNEL] = cpuData->systemAllPercent; this->curItems = 3; - percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL]; } - percent = isnan(percent) ? 0.0 : CLAMP(percent, 0.0, 100.0); + percent = sumPositiveValues(v, this->curItems); + percent = MINIMUM(percent, 100.0); v[CPU_METER_FREQUENCY] = NAN; v[CPU_METER_TEMPERATURE] = NAN; @@ -213,9 +216,9 @@ void Platform_setMemoryValues(Meter* this) { this->total = host->totalMem; this->values[MEMORY_METER_USED] = host->usedMem; - this->values[MEMORY_METER_BUFFERS] = host->buffersMem; // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm" - // mtr->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_CACHE] = host->cachedMem; // this->values[MEMORY_METER_AVAILABLE] = "available memory" } diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h index b37cea2a1..606b004c0 100644 --- a/dragonflybsd/Platform.h +++ b/dragonflybsd/Platform.h @@ -49,7 +49,7 @@ int Platform_getUptime(void); void Platform_getLoadAverage(double* one, double* five, double* fifteen); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* this, unsigned int cpu); @@ -111,7 +111,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -119,4 +119,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/freebsd/FreeBSDMachine.c b/freebsd/FreeBSDMachine.c index 26da667a2..d781414b4 100644 --- a/freebsd/FreeBSDMachine.c +++ b/freebsd/FreeBSDMachine.c @@ -136,7 +136,7 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { } len = sizeof(this->kernelFScale); - if (sysctlbyname("kern.fscale", &this->kernelFScale, &len, NULL, 0) == -1) { + if (sysctlbyname("kern.fscale", &this->kernelFScale, &len, NULL, 0) == -1 || this->kernelFScale <= 0) { //sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed this->kernelFScale = 2048; } @@ -281,24 +281,21 @@ static inline void FreeBSDMachine_scanCPU(Machine* super) { // propagate frequency to all cores if only supplied for CPU 0 if (cpus > 1) { if (super->settings->showCPUTemperature) { - double maxTemp = NAN; + double maxTemp = -HUGE_VAL; for (unsigned int i = 1; i < maxcpu; i++) { - const double coreTemp = this->cpus[i].temperature; - if (isnan(coreTemp)) - continue; - - maxTemp = MAXIMUM(maxTemp, coreTemp); + if (isgreater(this->cpus[i].temperature, maxTemp)) { + maxTemp = this->cpus[i].temperature; + this->cpus[0].temperature = maxTemp; + } } - - this->cpus[0].temperature = maxTemp; } if (super->settings->showCPUFrequency) { const double coreZeroFreq = this->cpus[1].frequency; double freqSum = coreZeroFreq; - if (!isnan(coreZeroFreq)) { + if (isNonnegative(coreZeroFreq)) { for (unsigned int i = 2; i < maxcpu; i++) { - if (isnan(this->cpus[i].frequency)) + if (!isNonnegative(this->cpus[i].frequency)) this->cpus[i].frequency = coreZeroFreq; freqSum += this->cpus[i].frequency; diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c index 4970ff2cc..e7582a75d 100644 --- a/freebsd/FreeBSDProcess.c +++ b/freebsd/FreeBSDProcess.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "freebsd/FreeBSDProcess.h" #include @@ -71,25 +73,27 @@ void Process_delete(Object* cast) { free(this); } -static void FreeBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const FreeBSDProcess* fp = (const FreeBSDProcess*) this; - char buffer[256]; - size_t n = sizeof(buffer); +static void FreeBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const FreeBSDProcess* fp = (const FreeBSDProcess*) super; + + char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; + size_t n = sizeof(buffer) - 1; switch (field) { // add FreeBSD-specific fields here case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break; case JAIL: - Process_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11); + Row_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11); return; case EMULATION: - Process_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16); + Row_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16); return; default: - Process_writeField(this, str, field); + Process_writeField(&fp->super, str, field); return; } + RichString_appendWide(str, attr, buffer); } @@ -112,11 +116,18 @@ static int FreeBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro const ProcessClass FreeBSDProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = FreeBSDProcess_rowWriteField }, - .writeField = FreeBSDProcess_writeField, .compareByKey = FreeBSDProcess_compareByKey }; diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessTable.c similarity index 74% rename from freebsd/FreeBSDProcessList.c rename to freebsd/FreeBSDProcessTable.c index d8d4bbe04..9e18b8abc 100644 --- a/freebsd/FreeBSDProcessList.c +++ b/freebsd/FreeBSDProcessTable.c @@ -1,5 +1,5 @@ /* -htop - FreeBSDProcessList.c +htop - FreeBSDProcessTable.c (C) 2014 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. @@ -7,7 +7,7 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep -#include "freebsd/FreeBSDProcessList.h" +#include "freebsd/FreeBSDProcessTable.h" #include #include @@ -32,7 +32,7 @@ in the source distribution for its full text. #include "Macros.h" #include "Object.h" #include "Process.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "Scheduling.h" #include "Settings.h" #include "XUtils.h" @@ -41,24 +41,23 @@ in the source distribution for its full text. #include "freebsd/FreeBSDProcess.h" -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - FreeBSDProcessList* this = xCalloc(1, sizeof(FreeBSDProcessList)); - ProcessList* super = &this->super; +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + FreeBSDProcessTable* this = xCalloc(1, sizeof(FreeBSDProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(super, Class(FreeBSDProcess), host, pidMatchList); + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(FreeBSDProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* super) { - FreeBSDProcessList* this = (FreeBSDProcessList*) super; - - ProcessList_done(super); - +void ProcessTable_delete(Object* cast) { + FreeBSDProcessTable* this = (FreeBSDProcessTable*) cast; + ProcessTable_done(&this->super); free(this); } -static void FreeBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) { +static void FreeBSDProcessTable_updateExe(const struct kinfo_proc* kproc, Process* proc) { if (Process_isKernelThread(proc)) { Process_updateExe(proc, NULL); return; @@ -75,7 +74,7 @@ static void FreeBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process Process_updateExe(proc, buffer); } -static void FreeBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) { +static void FreeBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) { #ifdef KERN_PROC_CWD const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, kproc->ki_pid }; char buffer[2048]; @@ -99,7 +98,7 @@ static void FreeBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process #endif } -static void FreeBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) { +static void FreeBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) { Process_updateComm(proc, kproc->ki_comm); char** argv = kvm_getargv(kd, kproc, 0); @@ -131,7 +130,7 @@ static void FreeBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_p free(cmdline); } -static char* FreeBSDProcessList_readJailName(const struct kinfo_proc* kproc) { +static char* FreeBSDProcessTable_readJailName(const struct kinfo_proc* kproc) { if (kproc->ki_jid == 0) return xStrdup("-"); @@ -156,8 +155,8 @@ IGNORE_WCASTQUAL_END return NULL; } -void ProcessList_goThroughEntries(ProcessList* super) { - const Machine* host = super->host; +void ProcessTable_goThroughEntries(ProcessTable* super) { + const Machine* host = super->super.host; const FreeBSDMachine* fhost = (const FreeBSDMachine*) host; const Settings* settings = host->settings; bool hideKernelThreads = settings->hideKernelThreads; @@ -169,17 +168,17 @@ void ProcessList_goThroughEntries(ProcessList* super) { for (int i = 0; i < count; i++) { const struct kinfo_proc* kproc = &kprocs[i]; bool preExisting = false; - Process* proc = ProcessList_getProcess(super, kproc->ki_pid, &preExisting, FreeBSDProcess_new); + Process* proc = ProcessTable_getProcess(super, kproc->ki_pid, &preExisting, FreeBSDProcess_new); FreeBSDProcess* fp = (FreeBSDProcess*) proc; if (!preExisting) { fp->jid = kproc->ki_jid; - proc->pid = kproc->ki_pid; + Process_setPid(proc, kproc->ki_pid); + Process_setThreadGroup(proc, kproc->ki_pid); + Process_setParent(proc, kproc->ki_ppid); proc->isKernelThread = kproc->ki_pid != 1 && (kproc->ki_flag & P_SYSTEM); proc->isUserlandThread = false; - proc->ppid = kproc->ki_ppid; proc->tpgid = kproc->ki_tpgid; - proc->tgid = kproc->ki_pid; proc->session = kproc->ki_sid; proc->pgrp = kproc->ki_pgid; proc->st_uid = kproc->ki_uid; @@ -189,16 +188,16 @@ void ProcessList_goThroughEntries(ProcessList* super) { } Process_fillStarttimeBuffer(proc); proc->user = UsersTable_getRef(host->usersTable, proc->st_uid); - ProcessList_add(super, proc); + ProcessTable_add(super, proc); - FreeBSDProcessList_updateExe(kproc, proc); - FreeBSDProcessList_updateProcessName(fhost->kd, kproc, proc); + FreeBSDProcessTable_updateExe(kproc, proc); + FreeBSDProcessTable_updateProcessName(fhost->kd, kproc, proc); if (settings->ss->flags & PROCESS_FLAG_CWD) { - FreeBSDProcessList_updateCwd(kproc, proc); + FreeBSDProcessTable_updateCwd(kproc, proc); } - fp->jname = FreeBSDProcessList_readJailName(kproc); + fp->jname = FreeBSDProcessTable_readJailName(kproc); proc->tty_nr = kproc->ki_tdev; const char* name = (kproc->ki_tdev != NODEV) ? devname(kproc->ki_tdev, S_IFCHR) : NULL; @@ -213,17 +212,17 @@ void ProcessList_goThroughEntries(ProcessList* super) { // process can enter jail anytime fp->jid = kproc->ki_jid; free(fp->jname); - fp->jname = FreeBSDProcessList_readJailName(kproc); + fp->jname = FreeBSDProcessTable_readJailName(kproc); } // if there are reapers in the system, process can get reparented anytime - proc->ppid = kproc->ki_ppid; + Process_setParent(proc, kproc->ki_ppid); if (proc->st_uid != kproc->ki_uid) { // some processes change users (eg. to lower privs) proc->st_uid = kproc->ki_uid; proc->user = UsersTable_getRef(host->usersTable, proc->st_uid); } if (settings->updateProcessNames) { - FreeBSDProcessList_updateProcessName(fhost->kd, kproc, proc); + FreeBSDProcessTable_updateProcessName(fhost->kd, kproc, proc); } } @@ -261,14 +260,14 @@ void ProcessList_goThroughEntries(ProcessList* super) { /* Taken from: https://github.com/freebsd/freebsd-src/blob/1ad2d87778970582854082bcedd2df0394fd4933/sys/sys/proc.h#L851 */ switch (kproc->ki_stat) { - case SIDL: proc->state = IDLE; break; - case SRUN: proc->state = RUNNING; break; - case SSLEEP: proc->state = SLEEPING; break; - case SSTOP: proc->state = STOPPED; break; - case SZOMB: proc->state = ZOMBIE; break; - case SWAIT: proc->state = WAITING; break; - case SLOCK: proc->state = BLOCKED; break; - default: proc->state = UNKNOWN; + 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)) @@ -279,11 +278,11 @@ void ProcessList_goThroughEntries(ProcessList* super) { Scheduling_readProcessPolicy(proc); #endif - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); super->totalTasks++; if (proc->state == RUNNING) super->runningTasks++; - proc->updated = true; + proc->super.updated = true; } } diff --git a/freebsd/FreeBSDProcessList.h b/freebsd/FreeBSDProcessTable.h similarity index 52% rename from freebsd/FreeBSDProcessList.h rename to freebsd/FreeBSDProcessTable.h index 55247eb79..23a6ab221 100644 --- a/freebsd/FreeBSDProcessList.h +++ b/freebsd/FreeBSDProcessTable.h @@ -1,7 +1,7 @@ -#ifndef HEADER_FreeBSDProcessList -#define HEADER_FreeBSDProcessList +#ifndef HEADER_FreeBSDProcessTable +#define HEADER_FreeBSDProcessTable /* -htop - FreeBSDProcessList.h +htop - FreeBSDProcessTable.h (C) 2014 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. @@ -11,11 +11,11 @@ in the source distribution for its full text. #include #include "Hashtable.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "UsersTable.h" -typedef struct FreeBSDProcessList_ { - ProcessList super; -} FreeBSDProcessList; +typedef struct FreeBSDProcessTable_ { + ProcessTable super; +} FreeBSDProcessTable; #endif diff --git a/freebsd/Platform.c b/freebsd/Platform.c index 66e1463df..9be7195e5 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -181,7 +181,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale; } -int Platform_getMaxPid(void) { +pid_t Platform_getMaxPid(void) { int maxPid; size_t size = sizeof(maxPid); int err = sysctlbyname("kern.pid_max", &maxPid, &size, NULL, 0); @@ -229,9 +229,9 @@ void Platform_setMemoryValues(Meter* this) { this->total = host->totalMem; this->values[MEMORY_METER_USED] = host->usedMem; - this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_SHARED] = host->sharedMem; // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_CACHE] = host->cachedMem; // this->values[MEMORY_METER_AVAILABLE] = "available memory" diff --git a/freebsd/Platform.h b/freebsd/Platform.h index 849f7ddf3..c358d85d3 100644 --- a/freebsd/Platform.h +++ b/freebsd/Platform.h @@ -45,7 +45,7 @@ int Platform_getUptime(void); void Platform_getLoadAverage(double* one, double* five, double* fifteen); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* this, unsigned int cpu); @@ -109,7 +109,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -119,4 +119,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/generic/fdstat_sysctl.c b/generic/fdstat_sysctl.c index 49e8e362a..ea374fb2a 100644 --- a/generic/fdstat_sysctl.c +++ b/generic/fdstat_sysctl.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "generic/fdstat_sysctl.h" #include @@ -14,8 +16,6 @@ in the source distribution for its full text. #include // Shitty FreeBSD upstream headers #include -#include "config.h" - static void Generic_getFileDescriptors_sysctl_internal( const char* sysctlname_maxfiles, @@ -43,9 +43,6 @@ static void Generic_getFileDescriptors_sysctl_internal( len = sizeof(open_fd); if (sysctlname_numfiles && sysctlbyname(sysctlname_numfiles, &open_fd, &len, NULL, 0) == 0) { *used = open_fd; - } - - if (!isnan(*used)) { return; } diff --git a/generic/gettime.c b/generic/gettime.c index b7c4885eb..209f52329 100644 --- a/generic/gettime.c +++ b/generic/gettime.c @@ -4,6 +4,7 @@ htop - generic/gettime.c Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ + #include "config.h" // IWYU pragma: keep #include "generic/gettime.h" diff --git a/generic/hostname.c b/generic/hostname.c index 69a414687..e3275828c 100644 --- a/generic/hostname.c +++ b/generic/hostname.c @@ -4,6 +4,7 @@ htop - generic/hostname.c Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ + #include "config.h" // IWYU pragma: keep #include "generic/hostname.h" diff --git a/generic/uname.c b/generic/uname.c index 2a734dc1d..b5bb5834a 100644 --- a/generic/uname.c +++ b/generic/uname.c @@ -4,6 +4,7 @@ htop - generic/uname.c Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ + #include "config.h" // IWYU pragma: keep #include "generic/uname.h" diff --git a/htop.1.in b/htop.1.in index 18be08dc6..2376b7a56 100644 --- a/htop.1.in +++ b/htop.1.in @@ -1,4 +1,4 @@ -.TH "HTOP" "1" "2023" "@PACKAGE_STRING@" "User Commands" +.TH "HTOP" "1" "2024" "@PACKAGE_STRING@" "User Commands" .SH "NAME" htop, pcp-htop \- interactive process viewer .SH "SYNOPSIS" @@ -602,7 +602,7 @@ By default .B htop reads its configuration from the XDG-compliant path .IR ~/.config/htop/htoprc . -The configuration file is overwritten by +The configuration file is overwritten upon clean exit by .BR htop 's in-program Setup configuration, so it should not be hand-edited. If no user configuration exists @@ -644,15 +644,24 @@ can also be displayed by if the .BR pmdaopenmetrics (1) component is configured. +.LP +The configuration for both +.B htop +and +.B pcp-htop +is only saved when a clean exit is performed. Sending any signal will cause +.I all configuration changes to be lost. .SH "MEMORY SIZES" Memory sizes in .B htop are displayed in a human-readable form. -Sizes are printed in powers of 1024. (e.g., 1023M = 1072693248 Bytes) +Sizes are printed in powers of 1024 using binary IEC units. +If no suffix is shown the units are implicitly K as in KiB (kibibyte, 1 KiB = 1024 bytes). .LP The decision to use this convention was made in order to conserve screen space and make memory size representations consistent throughout -.BR htop . +.B htop +as allocations are granular to full memory pages (4 KiB for most platforms). .SH "SEE ALSO" .BR proc (5), .BR top (1), @@ -678,7 +687,7 @@ communities, and forms part of the Performance Co-Pilot suite of tools. .SH "COPYRIGHT" Copyright \(co 2004-2019 Hisham Muhammad. .br -Copyright \(co 2020-2023 htop dev team. +Copyright \(co 2020-2024 htop dev team. .LP License GPLv2+: GNU General Public License version 2 or, at your option, any later version. .LP diff --git a/htop.desktop b/htop.desktop old mode 100644 new mode 100755 diff --git a/iwyu/htop.imp b/iwyu/htop.imp index 5e87cdbff..e2fc72b5a 100644 --- a/iwyu/htop.imp +++ b/iwyu/htop.imp @@ -8,6 +8,9 @@ { include: ["", "private", "\"ProvideTerm.h\"", "public"] }, { include: ["", "private", "\"ProvideTerm.h\"", "public"] }, + { include: ["", "private", "", "public"] }, + { include: ["\"ibunwind-x86_64.h\"", "private", "", "public"] }, + { include: ["", "private", "", "public"] }, { include: ["", "private", "", "public"] }, @@ -25,4 +28,6 @@ { include: ["", "private", "", "public"] }, { include: ["", "private", "", "public"] }, + + { include: ["", "private", "", "public"] }, ] diff --git a/iwyu/run_iwyu.sh b/iwyu/run_iwyu.sh index 6139ebec8..8456240a9 100755 --- a/iwyu/run_iwyu.sh +++ b/iwyu/run_iwyu.sh @@ -10,5 +10,6 @@ IWYU=${IWYU:-iwyu} cd "$SOURCEDIR" || exit +./configure CC=clang CXX=clang++ --enable-silent-rules make clean make -k -s CC="$IWYU" CFLAGS="-Xiwyu --no_comments -Xiwyu --no_fwd_decl -Xiwyu --mapping_file='$SCRIPTDIR/htop.imp' $PKG_NL3" diff --git a/linux/CGroupUtils.c b/linux/CGroupUtils.c index c11c46088..f352b8e2e 100644 --- a/linux/CGroupUtils.c +++ b/linux/CGroupUtils.c @@ -1,15 +1,46 @@ /* -htop - CGroupUtils.h +htop - CGroupUtils.c (C) 2021 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "linux/CGroupUtils.h" +#include +#include +#include +#include + +#include "Macros.h" #include "XUtils.h" +static const char* str_slice_suffix = ".slice"; +static const char* str_system_slice = "system.slice"; +static const char* str_user_slice = "user.slice"; +static const char* str_machine_slice = "machine.slice"; +static const char* str_user_slice_prefix = "/user-"; +static const char* str_system_slice_prefix = "/system-"; + +static const char* str_lxc_monitor_legacy = "lxc.monitor"; +static const char* str_lxc_payload_legacy = "lxc.payload"; +static const char* str_lxc_monitor_prefix = "lxc.monitor."; +static const char* str_lxc_payload_prefix = "lxc.payload."; + +static const char* str_nspawn_scope_prefix = "machine-"; +static const char* str_nspawn_monitor_label = "/supervisor"; +static const char* str_nspawn_payload_label = "/payload"; + +static const char* str_snap_scope_prefix = "snap."; +static const char* str_pod_scope_prefix = "libpod-"; +static const char* str_docker_scope_prefix = "docker-"; + +static const char* str_service_suffix = ".service"; +static const char* str_scope_suffix = ".scope"; + typedef struct StrBuf_state { char* buf; size_t size; @@ -61,27 +92,6 @@ static bool Label_checkSuffix(const char* labelStart, size_t labelLen, const cha } 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) @@ -94,7 +104,7 @@ static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrB } const char* labelStart = cgroup; - const char* nextSlash = strchrnul(labelStart, '/'); + const char* nextSlash = String_strchrnul(labelStart, '/'); const size_t labelLen = nextSlash - labelStart; if (Label_checkEqual(labelStart, labelLen, str_system_slice)) { @@ -104,7 +114,7 @@ static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrB return false; if (String_startsWith(cgroup, str_system_slice_prefix)) { - cgroup = strchrnul(cgroup + 1, '/'); + cgroup = String_strchrnul(cgroup + 1, '/'); continue; } @@ -129,7 +139,7 @@ static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrB if (!String_startsWith(cgroup, str_user_slice_prefix)) continue; - const char* userSliceSlash = strchrnul(cgroup + strlen(str_user_slice_prefix), '/'); + const char* userSliceSlash = String_strchrnul(cgroup + strlen(str_user_slice_prefix), '/'); const char* sliceSpec = userSliceSlash - strlen(str_slice_suffix); if (!String_startsWith(sliceSpec, str_slice_suffix)) @@ -212,7 +222,7 @@ static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrB while (*labelStart == '/') labelStart++; - nextSlash = strchrnul(labelStart, '/'); + nextSlash = String_strchrnul(labelStart, '/'); if (nextSlash - labelStart > 0) { if (!StrBuf_putsz(s, w, isMonitor ? "[LXC:" : "[lxc:")) return false; @@ -276,7 +286,7 @@ static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrB continue; } else if (Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) { - const char* nextDot = strchrnul(labelStart + strlen(str_snap_scope_prefix), '.'); + const char* nextDot = String_strchrnul(labelStart + strlen(str_snap_scope_prefix), '.'); if (!StrBuf_putsz(s, w, "!snap:")) return false; @@ -290,6 +300,40 @@ static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrB cgroup = nextSlash; + continue; + } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) { + const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.'); + + if (!StrBuf_putsz(s, w, "!pod:")) + return false; + + if (nextDot >= labelStart + scopeNameLen) { + nextDot = labelStart + scopeNameLen; + } + + if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix), + MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12))) + return false; + + cgroup = nextSlash; + + continue; + } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) { + const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.'); + + if (!StrBuf_putsz(s, w, "!docker:")) + return false; + + if (nextDot >= labelStart + scopeNameLen) { + nextDot = labelStart + scopeNameLen; + } + + if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix), + MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12))) + return false; + + cgroup = nextSlash; + continue; } @@ -339,3 +383,150 @@ char* CGroup_filterName(const char* cgroup) { s.buf[s.size] = '\0'; return s.buf; } + +static bool CGroup_filterContainer_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) { + while (*cgroup) { + if ('/' == *cgroup) { + while ('/' == *cgroup) + cgroup++; + + continue; + } + + const char* labelStart = cgroup; + const char* nextSlash = String_strchrnul(labelStart, '/'); + const size_t labelLen = nextSlash - labelStart; + + if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) { + const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix); + + if (!StrBuf_putsz(s, w, "/lxc:")) + return false; + + if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen)) + return false; + + cgroup = nextSlash; + + continue; + } + + // LXC legacy cgroup naming + if (Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) { + labelStart = nextSlash; + while (*labelStart == '/') + labelStart++; + + nextSlash = String_strchrnul(labelStart, '/'); + if (nextSlash - labelStart > 0) { + if (!StrBuf_putsz(s, w, "/lxc:")) + return false; + + if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart)) + return false; + + cgroup = nextSlash; + continue; + } + + labelStart = cgroup; + nextSlash = labelStart + labelLen; + } + + if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) { + const size_t scopeNameLen = labelLen - strlen(str_scope_suffix); + + if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) { + const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix); + + const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label); + + if (!is_monitor) { + if (!StrBuf_putsz(s, w, "/snc:")) + return false; + + if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen)) + return false; + } + + cgroup = nextSlash; + if (String_startsWith(nextSlash, str_nspawn_monitor_label)) + cgroup += strlen(str_nspawn_monitor_label); + else if (String_startsWith(nextSlash, str_nspawn_payload_label)) + cgroup += strlen(str_nspawn_payload_label); + + continue; + } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) { + const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.'); + + if (!StrBuf_putsz(s, w, "/pod:")) + return false; + + if (nextDot >= labelStart + scopeNameLen) { + nextDot = labelStart + scopeNameLen; + } + + if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix), + MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12))) + return false; + + cgroup = nextSlash; + + continue; + } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) { + const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.'); + + if (!StrBuf_putsz(s, w, "!docker:")) + return false; + + if (nextDot >= labelStart + scopeNameLen) { + nextDot = labelStart + scopeNameLen; + } + + if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix), + MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12))) + return false; + + cgroup = nextSlash; + + continue; + } + + cgroup = nextSlash; + + continue; + } + + cgroup = nextSlash; + } + + return true; +} + +char* CGroup_filterContainer(const char* cgroup) { + StrBuf_state s = { + .buf = NULL, + .size = 0, + .pos = 0, + }; + + if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_count)) { + return NULL; + } + + if (!s.pos) { + return xStrdup("/"); + } + + s.buf = xCalloc(s.pos + 1, sizeof(char)); + s.size = s.pos; + s.pos = 0; + + if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_write)) { + free(s.buf); + return NULL; + } + + s.buf[s.size] = '\0'; + return s.buf; +} diff --git a/linux/CGroupUtils.h b/linux/CGroupUtils.h index ff1343741..972a15bb5 100644 --- a/linux/CGroupUtils.h +++ b/linux/CGroupUtils.h @@ -7,10 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include -#include - char* CGroup_filterName(const char* cgroup); +char* CGroup_filterContainer(const char* cgroup); #endif /* HEADER_CGroupUtils */ diff --git a/linux/HugePageMeter.c b/linux/HugePageMeter.c index ec3804eee..058ab4b64 100644 --- a/linux/HugePageMeter.c +++ b/linux/HugePageMeter.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "linux/HugePageMeter.h" #include @@ -12,9 +14,9 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "Machine.h" #include "Macros.h" #include "Object.h" -#include "ProcessList.h" #include "RichString.h" #include "linux/LinuxMachine.h" @@ -80,7 +82,7 @@ static void HugePageMeter_display(const Object* cast, RichString* out) { RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); for (unsigned i = 0; i < ARRAYSIZE(HugePageMeter_active_labels); i++) { - if (isnan(this->values[i])) { + if (!HugePageMeter_active_labels[i]) { break; } RichString_appendAscii(out, CRT_colors[METER_TEXT], HugePageMeter_active_labels[i]); diff --git a/linux/IOPriorityPanel.c b/linux/IOPriorityPanel.c index 3e91bc4d1..8d0ef7b5c 100644 --- a/linux/IOPriorityPanel.c +++ b/linux/IOPriorityPanel.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "linux/IOPriorityPanel.h" #include diff --git a/linux/LibSensors.c b/linux/LibSensors.c index cd24b797d..537341612 100644 --- a/linux/LibSensors.c +++ b/linux/LibSensors.c @@ -1,6 +1,13 @@ -#include "linux/LibSensors.h" +/* +htop - linux/LibSensors.c +(C) 2020-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep -#include "config.h" +#include "linux/LibSensors.h" #ifdef HAVE_SENSORS_SENSORS_H @@ -202,7 +209,7 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns continue; /* If already set, e.g. Ryzen reporting platform temperature for each die, use the bigger one */ - if (isnan(data[tempID])) { + if (isNaN(data[tempID])) { data[tempID] = temp; if (tempID > 0) coreTempCount++; @@ -222,7 +229,7 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns } /* Only package temperature - copy to all cores */ - if (coreTempCount == 0 && !isnan(data[0])) { + if (coreTempCount == 0 && !isNaN(data[0])) { for (unsigned int i = 1; i <= existingCPUs; i++) data[i] = data[0]; @@ -231,22 +238,20 @@ void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, uns } /* No package temperature - set to max core temperature */ - if (isnan(data[0]) && coreTempCount != 0) { - double maxTemp = NAN; + if (coreTempCount > 0 && isNaN(data[0])) { + double maxTemp = -HUGE_VAL; for (unsigned int i = 1; i <= existingCPUs; i++) { - if (isnan(data[i])) - continue; - - maxTemp = MAXIMUM(maxTemp, data[i]); + if (isgreater(data[i], maxTemp)) { + maxTemp = data[i]; + data[0] = data[i]; + } } - data[0] = maxTemp; - /* Check for further adjustments */ } /* Only temperature for core 0, maybe Ryzen - copy to all other cores */ - if (coreTempCount == 1 && !isnan(data[1])) { + if (coreTempCount == 1 && !isNaN(data[1])) { for (unsigned int i = 2; i <= existingCPUs; i++) data[i] = data[1]; diff --git a/linux/LibSensors.h b/linux/LibSensors.h index 2b9801bcd..6f0544897 100644 --- a/linux/LibSensors.h +++ b/linux/LibSensors.h @@ -1,5 +1,11 @@ #ifndef HEADER_LibSensors #define HEADER_LibSensors +/* +htop - linux/LibSensors.h +(C) 2020-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include "linux/LinuxMachine.h" diff --git a/linux/LinuxMachine.c b/linux/LinuxMachine.c index 68b731850..ae2930d40 100644 --- a/linux/LinuxMachine.c +++ b/linux/LinuxMachine.c @@ -10,25 +10,28 @@ in the source distribution for its full text. #include "linux/LinuxMachine.h" #include -#include #include #include #include -#include +#include #include #include -#include #include #include #include #include #include -#include #include #include "Compat.h" +#include "CRT.h" +#include "Macros.h" +#include "ProcessTable.h" +#include "Row.h" +#include "Settings.h" +#include "UsersTable.h" #include "XUtils.h" -#include "linux/LinuxMachine.h" + #include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep #ifdef HAVE_SENSORS_SENSORS_H @@ -156,36 +159,36 @@ static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) { } else (void) 0 /* Require a ";" after the macro use. */ switch (buffer[0]) { - case 'M': - tryRead("MemAvailable:", availableMem); - tryRead("MemFree:", freeMem); - tryRead("MemTotal:", totalMem); - break; - case 'B': - tryRead("Buffers:", buffersMem); - break; - case 'C': - tryRead("Cached:", cachedMem); - break; - case 'S': - switch (buffer[1]) { - case 'h': - tryRead("Shmem:", sharedMem); + case 'M': + tryRead("MemAvailable:", availableMem); + tryRead("MemFree:", freeMem); + tryRead("MemTotal:", totalMem); break; - case 'w': - tryRead("SwapTotal:", swapTotalMem); - tryRead("SwapCached:", swapCacheMem); - tryRead("SwapFree:", swapFreeMem); + case 'B': + tryRead("Buffers:", buffersMem); break; - case 'R': - tryRead("SReclaimable:", sreclaimableMem); + case 'C': + tryRead("Cached:", cachedMem); + break; + case 'S': + switch (buffer[1]) { + case 'h': + tryRead("Shmem:", sharedMem); + break; + case 'w': + tryRead("SwapTotal:", swapTotalMem); + tryRead("SwapCached:", swapCacheMem); + tryRead("SwapFree:", swapFreeMem); + break; + case 'R': + tryRead("SReclaimable:", sreclaimableMem); + break; + } + break; + case 'Z': + tryRead("Zswap:", zswapCompMem); + tryRead("Zswapped:", zswapOrigMem); break; - } - break; - case 'Z': - tryRead("Zswap:", zswapCompMem); - tryRead("Zswapped:", zswapOrigMem); - break; } #undef tryRead @@ -271,25 +274,6 @@ static void LinuxMachine_scanHugePages(LinuxMachine* this) { closedir(dir); } -static inline void LinuxMachine_scanZswapInfo(LinuxMachine* this) { - const Machine* host = &this->super; - long max_pool_percent = 0; - char buf[256]; - int r; - - r = xReadfile("/sys/module/zswap/parameters/max_pool_percent", buf, 256); - if (r <= 0) { - return; - } - max_pool_percent = strtol(buf, NULL, 10); - if (max_pool_percent < 0 || max_pool_percent > 100) { - return; - } - - this->zswap.totalZswapPool = host->totalMem * max_pool_percent / 100; - /* the rest of the metrics are set in LinuxMachine_scanMemoryInfo() */ -} - static void LinuxMachine_scanZramInfo(LinuxMachine* this) { memory_t totalZram = 0; memory_t usedZramComp = 0; @@ -336,6 +320,9 @@ static void LinuxMachine_scanZramInfo(LinuxMachine* this) { this->zram.totalZram = totalZram / 1024; this->zram.usedZramComp = usedZramComp / 1024; this->zram.usedZramOrig = usedZramOrig / 1024; + if (this->zram.usedZramComp > this->zram.usedZramOrig) { + this->zram.usedZramComp = this->zram.usedZramOrig; + } } static void LinuxMachine_scanZfsArcstats(LinuxMachine* this) { @@ -362,35 +349,36 @@ static void LinuxMachine_scanZfsArcstats(LinuxMachine* this) { } else (void) 0 /* Require a ";" after the macro use. */ switch (buffer[0]) { - case 'c': - tryRead("c_min", &this->zfs.min); - tryRead("c_max", &this->zfs.max); - tryReadFlag("compressed_size", &this->zfs.compressed, this->zfs.isCompressed); - break; - case 'u': - tryRead("uncompressed_size", &this->zfs.uncompressed); - break; - case 's': - tryRead("size", &this->zfs.size); - break; - case 'h': - tryRead("hdr_size", &this->zfs.header); - break; - case 'd': - tryRead("dbuf_size", &dbufSize); - tryRead("dnode_size", &dnodeSize); - break; - case 'b': - tryRead("bonus_size", &bonusSize); - break; - case 'a': - tryRead("anon_size", &this->zfs.anon); - break; - case 'm': - tryRead("mfu_size", &this->zfs.MFU); - tryRead("mru_size", &this->zfs.MRU); - break; + case 'c': + tryRead("c_min", &this->zfs.min); + tryRead("c_max", &this->zfs.max); + tryReadFlag("compressed_size", &this->zfs.compressed, this->zfs.isCompressed); + break; + case 'u': + tryRead("uncompressed_size", &this->zfs.uncompressed); + break; + case 's': + tryRead("size", &this->zfs.size); + break; + case 'h': + tryRead("hdr_size", &this->zfs.header); + break; + case 'd': + tryRead("dbuf_size", &dbufSize); + tryRead("dnode_size", &dnodeSize); + break; + case 'b': + tryRead("bonus_size", &bonusSize); + break; + case 'a': + tryRead("anon_size", &this->zfs.anon); + break; + case 'm': + tryRead("mfu_size", &this->zfs.MFU); + tryRead("mru_size", &this->zfs.MRU); + break; } + #undef tryRead #undef tryReadFlag } @@ -501,7 +489,8 @@ static void LinuxMachine_scanCPUTime(LinuxMachine* this) { char buffer[PROC_LINE_LENGTH + 1]; while (fgets(buffer, sizeof(buffer), file)) { if (String_startsWith(buffer, "procs_running")) { - this->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10); + ProcessTable* pt = (ProcessTable*) super->processTable; + pt->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10); break; } } @@ -601,7 +590,7 @@ static void scanCPUFrequencyFromCPUinfo(LinuxMachine* this) { CPUData* cpuData = &(this->cpuData[cpuid + 1]); /* do not override sysfs data */ - if (isnan(cpuData->frequency)) { + if (!isNonnegative(cpuData->frequency)) { cpuData->frequency = frequency; } numCPUsWithFrequency++; @@ -636,7 +625,6 @@ void Machine_scan(Machine* super) { LinuxMachine_scanHugePages(this); LinuxMachine_scanZfsArcstats(this); LinuxMachine_scanZramInfo(this); - LinuxMachine_scanZswapInfo(this); LinuxMachine_scanCPUTime(this); const Settings* settings = super->settings; diff --git a/linux/LinuxMachine.h b/linux/LinuxMachine.h index c3076a304..309b4850f 100644 --- a/linux/LinuxMachine.h +++ b/linux/LinuxMachine.h @@ -7,13 +7,9 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" - #include -#include #include "Machine.h" -#include "UsersTable.h" #include "linux/ZramStats.h" #include "linux/ZswapStats.h" #include "zfs/ZfsArcStats.h" diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index b815c5b55..c20397964 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -6,9 +6,13 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "linux/LinuxProcess.h" +#include #include +#include #include #include #include @@ -19,7 +23,9 @@ in the source distribution for its full text. #include "Process.h" #include "ProvideCurses.h" #include "RichString.h" +#include "RowField.h" #include "Scheduling.h" +#include "Settings.h" #include "XUtils.h" #include "linux/IOPriority.h" #include "linux/LinuxMachine.h" @@ -51,6 +57,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, [M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, .defaultSortDesc = true, }, + [M_PRIV] = { .name = "M_PRIV", .title = " PRIV ", .description = "The private memory size of the process - resident set size minus shared memory", .flags = 0, .defaultSortDesc = true, }, [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the .text segment of the process (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, }, @@ -76,11 +83,12 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [RBYTES] = { .name = "RBYTES", .title = " IO_R ", .description = "Bytes of read(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [WBYTES] = { .name = "WBYTES", .title = " IO_W ", .description = "Bytes of write(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [CNCLWB] = { .name = "CNCLWB", .title = " IO_C ", .description = "Bytes of cancelled write(2) I/O", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, - [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw)", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, }, [CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed)", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, }, + [CONTAINER] = { .name = "CONTAINER", .title = "CONTAINER", .description = "Name of the container the process is in (guessed by heuristics)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, }, [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, }, [IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, }, #ifdef HAVE_DELAYACCT @@ -113,6 +121,7 @@ Process* LinuxProcess_new(const Machine* host) { void Process_delete(Object* cast) { LinuxProcess* this = (LinuxProcess*) cast; Process_done((Process*)cast); + free(this->container_short); free(this->cgroup_short); free(this->cgroup); #ifdef HAVE_OPENVZ @@ -143,22 +152,29 @@ static int LinuxProcess_effectiveIOPriority(const LinuxProcess* this) { #define SYS_ioprio_set __NR_ioprio_set #endif -IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this) { +IOPriority LinuxProcess_updateIOPriority(Process* p) { IOPriority ioprio = 0; // Other OSes masquerading as Linux (NetBSD?) don't have this syscall #ifdef SYS_ioprio_get - ioprio = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, this->super.pid); + ioprio = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, Process_getPid(p)); #endif + LinuxProcess* this = (LinuxProcess*) p; this->ioPriority = ioprio; return ioprio; } -bool LinuxProcess_setIOPriority(Process* this, Arg ioprio) { +static bool LinuxProcess_setIOPriority(Process* p, Arg ioprio) { // Other OSes masquerading as Linux (NetBSD?) don't have this syscall #ifdef SYS_ioprio_set - syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, this->pid, ioprio.i); + syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, Process_getPid(p), ioprio.i); #endif - return (LinuxProcess_updateIOPriority((LinuxProcess*)this) == ioprio.i); + return LinuxProcess_updateIOPriority(p) == ioprio.i; +} + +bool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio) { + Process* p = (Process*) super; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return LinuxProcess_setIOPriority(p, ioprio); } bool LinuxProcess_isAutogroupEnabled(void) { @@ -168,9 +184,10 @@ bool LinuxProcess_isAutogroupEnabled(void) { return buf[0] == '1'; } -bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) { +static bool LinuxProcess_changeAutogroupPriorityBy(Process* p, Arg delta) { char buffer[256]; - xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", this->pid); + pid_t pid = Process_getPid(p); + xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", pid); FILE* file = fopen(buffer, "r+"); if (!file) @@ -192,57 +209,69 @@ bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) { return success; } -static void LinuxProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const LinuxProcess* lp = (const LinuxProcess*) this; - const LinuxMachine* lhost = (const LinuxMachine*) this->host; - bool coloring = this->host->settings->highlightMegabytes; +bool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta) { + Process* p = (Process*) super; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return LinuxProcess_changeAutogroupPriorityBy(p, delta); +} + +static double LinuxProcess_totalIORate(const LinuxProcess* lp) { + double totalRate = NAN; + if (isNonnegative(lp->io_rate_read_bps)) { + totalRate = lp->io_rate_read_bps; + if (isNonnegative(lp->io_rate_write_bps)) { + totalRate += lp->io_rate_write_bps; + } + } else if (isNonnegative(lp->io_rate_write_bps)) { + totalRate = lp->io_rate_write_bps; + } + return totalRate; +} + +static void LinuxProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const Process* this = (const Process*) super; + const LinuxProcess* lp = (const LinuxProcess*) super; + const Machine* host = (const Machine*) super->host; + const LinuxMachine* lhost = (const LinuxMachine*) super->host; + + bool coloring = host->settings->highlightMegabytes; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; + switch (field) { - case CMINFLT: Process_printCount(str, lp->cminflt, coloring); return; - case CMAJFLT: Process_printCount(str, lp->cmajflt, coloring); return; - case M_DRS: Process_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; + case CMINFLT: Row_printCount(str, lp->cminflt, coloring); return; + case CMAJFLT: Row_printCount(str, lp->cmajflt, coloring); return; + case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; case M_LRS: if (lp->m_lrs) { - Process_printBytes(str, lp->m_lrs * lhost->pageSize, coloring); + Row_printBytes(str, lp->m_lrs * lhost->pageSize, coloring); return; } attr = CRT_colors[PROCESS_SHADOW]; xSnprintf(buffer, n, " N/A "); break; - case M_TRS: Process_printBytes(str, lp->m_trs * lhost->pageSize, coloring); return; - case M_SHARE: Process_printBytes(str, lp->m_share * lhost->pageSize, coloring); return; - case M_PSS: Process_printKBytes(str, lp->m_pss, coloring); return; - case M_SWAP: Process_printKBytes(str, lp->m_swap, coloring); return; - case M_PSSWP: Process_printKBytes(str, lp->m_psswp, coloring); return; - case UTIME: Process_printTime(str, lp->utime, coloring); return; - case STIME: Process_printTime(str, lp->stime, coloring); return; - case CUTIME: Process_printTime(str, lp->cutime, coloring); return; - case CSTIME: Process_printTime(str, lp->cstime, coloring); return; - case RCHAR: Process_printBytes(str, lp->io_rchar, coloring); return; - case WCHAR: Process_printBytes(str, lp->io_wchar, coloring); return; - case SYSCR: Process_printCount(str, lp->io_syscr, coloring); return; - case SYSCW: Process_printCount(str, lp->io_syscw, coloring); return; - case RBYTES: Process_printBytes(str, lp->io_read_bytes, coloring); return; - case WBYTES: Process_printBytes(str, lp->io_write_bytes, coloring); return; - case CNCLWB: Process_printBytes(str, lp->io_cancelled_write_bytes, coloring); return; - case IO_READ_RATE: Process_printRate(str, lp->io_rate_read_bps, coloring); return; - case IO_WRITE_RATE: Process_printRate(str, lp->io_rate_write_bps, coloring); return; - case IO_RATE: { - double totalRate; - if (!isnan(lp->io_rate_read_bps) && !isnan(lp->io_rate_write_bps)) - totalRate = lp->io_rate_read_bps + lp->io_rate_write_bps; - else if (!isnan(lp->io_rate_read_bps)) - totalRate = lp->io_rate_read_bps; - else if (!isnan(lp->io_rate_write_bps)) - totalRate = lp->io_rate_write_bps; - else - totalRate = NAN; - Process_printRate(str, totalRate, coloring); - return; - } + case M_TRS: Row_printBytes(str, lp->m_trs * lhost->pageSize, coloring); return; + case M_SHARE: Row_printBytes(str, lp->m_share * lhost->pageSize, coloring); return; + case M_PRIV: Row_printKBytes(str, lp->m_priv, coloring); return; + case M_PSS: Row_printKBytes(str, lp->m_pss, coloring); return; + case M_SWAP: Row_printKBytes(str, lp->m_swap, coloring); return; + case M_PSSWP: Row_printKBytes(str, lp->m_psswp, coloring); return; + case UTIME: Row_printTime(str, lp->utime, coloring); return; + case STIME: Row_printTime(str, lp->stime, coloring); return; + case CUTIME: Row_printTime(str, lp->cutime, coloring); return; + case CSTIME: Row_printTime(str, lp->cstime, coloring); return; + case RCHAR: Row_printBytes(str, lp->io_rchar, coloring); return; + case WCHAR: Row_printBytes(str, lp->io_wchar, coloring); return; + case SYSCR: Row_printCount(str, lp->io_syscr, coloring); return; + case SYSCW: Row_printCount(str, lp->io_syscw, coloring); return; + case RBYTES: Row_printBytes(str, lp->io_read_bytes, coloring); return; + case WBYTES: Row_printBytes(str, lp->io_write_bytes, coloring); return; + case CNCLWB: Row_printBytes(str, lp->io_cancelled_write_bytes, coloring); return; + case IO_READ_RATE: Row_printRate(str, lp->io_rate_read_bps, coloring); return; + case IO_WRITE_RATE: Row_printRate(str, lp->io_rate_write_bps, coloring); return; + case IO_RATE: Row_printRate(str, LinuxProcess_totalIORate(lp), coloring); return; #ifdef HAVE_OPENVZ case CTID: xSnprintf(buffer, n, "%-8s ", lp->ctid ? lp->ctid : ""); break; case VPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, lp->vpid); break; @@ -250,8 +279,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces #ifdef HAVE_VSERVER case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break; #endif - case CGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CGROUP], Process_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break; - case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CCGROUP], Process_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break; + case CGROUP: xSnprintf(buffer, n, "%-*.*s ", Row_fieldWidths[CGROUP], Row_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break; + case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Row_fieldWidths[CCGROUP], Row_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break; + case CONTAINER: xSnprintf(buffer, n, "%-*.*s ", Row_fieldWidths[CONTAINER], Row_fieldWidths[CONTAINER], lp->container_short ? lp->container_short : "N/A"); break; case OOM: xSnprintf(buffer, n, "%4u ", lp->oom); break; case IO_PRIORITY: { int klass = IOPriority_class(lp->ioPriority); @@ -272,9 +302,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces break; } #ifdef HAVE_DELAYACCT - case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break; - case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break; - case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break; + case PERCENT_CPU_DELAY: Row_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break; + case PERCENT_IO_DELAY: Row_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break; + case PERCENT_SWAP_DELAY: Row_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break; #endif case CTXT: if (lp->ctxt_diff > 1000) { @@ -282,7 +312,7 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces } xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff); break; - case SECATTR: snprintf(buffer, n, "%-*.*s ", Process_fieldWidths[SECATTR], Process_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break; + case SECATTR: snprintf(buffer, n, "%-*.*s ", Row_fieldWidths[SECATTR], Row_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break; case AUTOGROUP_ID: if (lp->autogroup_id != -1) { xSnprintf(buffer, n, "%4ld ", lp->autogroup_id); @@ -295,8 +325,8 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces if (lp->autogroup_id != -1) { xSnprintf(buffer, n, "%3d ", lp->autogroup_nice); attr = lp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY] - : lp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] - : CRT_colors[PROCESS_SHADOW]; + : lp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] + : CRT_colors[PROCESS_SHADOW]; } else { attr = CRT_colors[PROCESS_SHADOW]; xSnprintf(buffer, n, "N/A "); @@ -306,14 +336,8 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces Process_writeField(this, str, field); return; } - RichString_appendAscii(str, attr, buffer); -} -static double adjustNaN(double num) { - if (isnan(num)) - return -0.0005; - - return num; + RichString_appendAscii(str, attr, buffer); } static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) { @@ -329,6 +353,8 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs); case M_SHARE: return SPACESHIP_NUMBER(p1->m_share, p2->m_share); + case M_PRIV: + return SPACESHIP_NUMBER(p1->m_priv, p2->m_priv); case M_PSS: return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss); case M_SWAP: @@ -358,11 +384,11 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce case CNCLWB: return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes); case IO_READ_RATE: - return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps), adjustNaN(p2->io_rate_read_bps)); + return compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps); case IO_WRITE_RATE: - return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_write_bps)); + return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps); case IO_RATE: - return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps) + adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_read_bps) + adjustNaN(p2->io_rate_write_bps)); + return compareRealNumbers(LinuxProcess_totalIORate(p1), LinuxProcess_totalIORate(p2)); #ifdef HAVE_OPENVZ case CTID: return SPACESHIP_NULLSTR(p1->ctid, p2->ctid); @@ -377,15 +403,17 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup); case CCGROUP: return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short); + case CONTAINER: + return SPACESHIP_NULLSTR(p1->container_short, p2->container_short); case OOM: return SPACESHIP_NUMBER(p1->oom, p2->oom); #ifdef HAVE_DELAYACCT case PERCENT_CPU_DELAY: - return SPACESHIP_NUMBER(p1->cpu_delay_percent, p2->cpu_delay_percent); + return compareRealNumbers(p1->cpu_delay_percent, p2->cpu_delay_percent); case PERCENT_IO_DELAY: - return SPACESHIP_NUMBER(p1->blkio_delay_percent, p2->blkio_delay_percent); + return compareRealNumbers(p1->blkio_delay_percent, p2->blkio_delay_percent); case PERCENT_SWAP_DELAY: - return SPACESHIP_NUMBER(p1->swapin_delay_percent, p2->swapin_delay_percent); + return compareRealNumbers(p1->swapin_delay_percent, p2->swapin_delay_percent); #endif case IO_PRIORITY: return SPACESHIP_NUMBER(LinuxProcess_effectiveIOPriority(p1), LinuxProcess_effectiveIOPriority(p2)); @@ -404,11 +432,18 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce const ProcessClass LinuxProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = LinuxProcess_rowWriteField }, - .writeField = LinuxProcess_writeField, .compareByKey = LinuxProcess_compareByKey }; diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index 6c309893c..388e50d37 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -8,15 +8,14 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include -#include -#include "linux/IOPriority.h" #include "Machine.h" #include "Object.h" #include "Process.h" +#include "Row.h" + +#include "linux/IOPriority.h" #define PROCESS_FLAG_LINUX_IOPRIO 0x00000100 @@ -41,6 +40,7 @@ typedef struct LinuxProcess_ { unsigned long long int cutime; unsigned long long int cstime; long m_share; + long m_priv; long m_pss; long m_swap; long m_psswp; @@ -90,6 +90,7 @@ typedef struct LinuxProcess_ { #endif char* cgroup; char* cgroup_short; + char* container_short; unsigned int oom; #ifdef HAVE_DELAYACCT unsigned long long int delay_read_time; @@ -122,13 +123,13 @@ Process* LinuxProcess_new(const Machine* host); void Process_delete(Object* cast); -IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this); +IOPriority LinuxProcess_updateIOPriority(Process* proc); -bool LinuxProcess_setIOPriority(Process* this, Arg ioprio); +bool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio); bool LinuxProcess_isAutogroupEnabled(void); -bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta); +bool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta); bool Process_isThread(const Process* this); diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessTable.c similarity index 78% rename from linux/LinuxProcessList.c rename to linux/LinuxProcessTable.c index cd3a2607b..039a64ec3 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessTable.c @@ -1,5 +1,5 @@ /* -htop - LinuxProcessList.c +htop - LinuxProcessTable.c (C) 2014 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. @@ -7,26 +7,21 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep -#include "linux/LinuxProcessList.h" +#include "linux/LinuxProcessTable.h" #include #include #include -#include #include #include #include #include #include -#include #include #include #include -#include -#include #include #include -#include #ifdef HAVE_DELAYACCT #include @@ -41,12 +36,17 @@ in the source distribution for its full text. #endif #include "Compat.h" -#include "CRT.h" +#include "Hashtable.h" +#include "Machine.h" #include "Macros.h" #include "Object.h" #include "Process.h" +#include "Row.h" +#include "RowField.h" #include "Scheduling.h" #include "Settings.h" +#include "Table.h" +#include "UsersTable.h" #include "XUtils.h" #include "linux/CGroupUtils.h" #include "linux/LinuxMachine.h" @@ -131,7 +131,7 @@ static int sortTtyDrivers(const void* va, const void* vb) { return SPACESHIP_NUMBER(a->minorFrom, b->minorFrom); } -static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) { +static void LinuxProcessTable_initTtyDrivers(LinuxProcessTable* this) { TtyDriver* ttyDrivers; char buf[16384]; @@ -187,7 +187,7 @@ static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) { #ifdef HAVE_DELAYACCT -static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) { +static void LinuxProcessTable_initNetlinkSocket(LinuxProcessTable* this) { this->netlink_socket = nl_socket_alloc(); if (this->netlink_socket == NULL) { return; @@ -200,12 +200,14 @@ static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) { #endif -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - LinuxProcessList* this = xCalloc(1, sizeof(LinuxProcessList)); - ProcessList* super = &this->super; +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + LinuxProcessTable* this = xCalloc(1, sizeof(LinuxProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(super, Class(LinuxProcess), host, pidMatchList); - LinuxProcessList_initTtyDrivers(this); + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(LinuxProcess), host, pidMatchList); + + LinuxProcessTable_initTtyDrivers(this); // Test /proc/PID/smaps_rollup availability (faster to parse, Linux 4.14+) this->haveSmapsRollup = (access(PROCDIR "/self/smaps_rollup", R_OK) == 0); @@ -213,9 +215,9 @@ ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { return super; } -void ProcessList_delete(ProcessList* pl) { - LinuxProcessList* this = (LinuxProcessList*) pl; - ProcessList_done(pl); +void ProcessTable_delete(Object* cast) { + LinuxProcessTable* this = (LinuxProcessTable*) cast; + ProcessTable_done(&this->super); if (this->ttyDrivers) { for (int i = 0; this->ttyDrivers[i].path; i++) { free(this->ttyDrivers[i].path); @@ -231,12 +233,12 @@ void ProcessList_delete(ProcessList* pl) { free(this); } -static inline unsigned long long LinuxProcessList_adjustTime(const LinuxMachine* lhost, unsigned long long t) { +static inline unsigned long long LinuxProcessTable_adjustTime(const LinuxMachine* lhost, unsigned long long t) { return t * 100 / lhost->jiffies; } /* Taken from: https://github.com/torvalds/linux/blob/64570fbc14f8d7cb3fe3995f20e26bc25ce4b2cc/fs/proc/array.c#L120 */ -static inline ProcessState LinuxProcessList_getProcessState(char state) { +static inline ProcessState LinuxProcessTable_getProcessState(char state) { switch (state) { case 'S': return SLEEPING; case 'X': return DEFUNCT; @@ -251,20 +253,20 @@ static inline ProcessState LinuxProcessList_getProcessState(char state) { } } -static bool LinuxProcessList_readStatFile(LinuxProcess* lp, openat_arg_t procFd, const LinuxMachine* lhost, bool scanMainThread, char* command, size_t commLen) { +static bool LinuxProcessTable_readStatFile(LinuxProcess* lp, openat_arg_t procFd, const LinuxMachine* lhost, bool scanMainThread, char* command, size_t commLen) { Process* process = &lp->super; char buf[MAX_READ + 1]; char path[22] = "stat"; if (scanMainThread) { - xSnprintf(path, sizeof(path), "task/%"PRIi32"/stat", (int32_t)process->pid); + xSnprintf(path, sizeof(path), "task/%"PRIi32"/stat", (int32_t)Process_getPid(process)); } ssize_t r = xReadfileat(procFd, path, buf, sizeof(buf)); if (r < 0) return false; /* (1) pid - %d */ - assert(process->pid == atoi(buf)); + assert(Process_getPid(process) == atoi(buf)); char* location = strchr(buf, ' '); if (!location) return false; @@ -280,11 +282,11 @@ static bool LinuxProcessList_readStatFile(LinuxProcess* lp, openat_arg_t procFd, location = end + 2; /* (3) state - %c */ - process->state = LinuxProcessList_getProcessState(location[0]); + process->state = LinuxProcessTable_getProcessState(location[0]); location += 2; /* (4) ppid - %d */ - process->ppid = strtol(location, &location, 10); + Process_setParent(process, strtol(location, &location, 10)); location += 1; /* (5) pgrp - %d */ @@ -324,19 +326,19 @@ static bool LinuxProcessList_readStatFile(LinuxProcess* lp, openat_arg_t procFd, location += 1; /* (14) utime - %lu */ - lp->utime = LinuxProcessList_adjustTime(lhost, strtoull(location, &location, 10)); + lp->utime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10)); location += 1; /* (15) stime - %lu */ - lp->stime = LinuxProcessList_adjustTime(lhost, strtoull(location, &location, 10)); + lp->stime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10)); location += 1; /* (16) cutime - %ld */ - lp->cutime = LinuxProcessList_adjustTime(lhost, strtoull(location, &location, 10)); + lp->cutime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10)); location += 1; /* (17) cstime - %ld */ - lp->cstime = LinuxProcessList_adjustTime(lhost, strtoull(location, &location, 10)); + lp->cstime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10)); location += 1; /* (18) priority - %ld */ @@ -356,7 +358,7 @@ static bool LinuxProcessList_readStatFile(LinuxProcess* lp, openat_arg_t procFd, /* (22) starttime - %llu */ if (process->starttime_ctime == 0) { - process->starttime_ctime = lhost->boottime + LinuxProcessList_adjustTime(lhost, strtoll(location, &location, 10)) / 100; + process->starttime_ctime = lhost->boottime + LinuxProcessTable_adjustTime(lhost, strtoll(location, &location, 10)) / 100; } else { location = strchr(location, ' '); } @@ -379,7 +381,7 @@ static bool LinuxProcessList_readStatFile(LinuxProcess* lp, openat_arg_t procFd, return true; } -static bool LinuxProcessList_readStatusFile(Process* process, openat_arg_t procFd) { +static bool LinuxProcessTable_readStatusFile(Process* process, openat_arg_t procFd) { LinuxProcess* lp = (LinuxProcess*) process; unsigned long ctxt = 0; @@ -462,7 +464,7 @@ static bool LinuxProcessList_readStatusFile(Process* process, openat_arg_t procF return true; } -static bool LinuxProcessList_updateUser(const Machine* host, Process* process, openat_arg_t procFd) { +static bool LinuxProcessTable_updateUser(const Machine* host, Process* process, openat_arg_t procFd) { struct stat sstat; #ifdef HAVE_OPENAT int statok = fstat(procFd, &sstat); @@ -480,13 +482,13 @@ static bool LinuxProcessList_updateUser(const Machine* host, Process* process, o return true; } -static void LinuxProcessList_readIoFile(LinuxProcess* lp, openat_arg_t procFd, bool scanMainThread) { +static void LinuxProcessTable_readIoFile(LinuxProcess* lp, openat_arg_t procFd, bool scanMainThread) { Process* process = &lp->super; - const Machine* host = process->host; + const Machine* host = process->super.host; char path[20] = "io"; char buffer[1024]; if (scanMainThread) { - xSnprintf(path, sizeof(path), "task/%"PRIi32"/io", (int32_t)process->pid); + xSnprintf(path, sizeof(path), "task/%"PRIi32"/io", (int32_t)Process_getPid(process)); } ssize_t r = xReadfileat(procFd, path, buffer, sizeof(buffer)); if (r < 0) { @@ -505,39 +507,42 @@ static void LinuxProcessList_readIoFile(LinuxProcess* lp, openat_arg_t procFd, b unsigned long long last_read = lp->io_read_bytes; unsigned long long last_write = lp->io_write_bytes; - unsigned long long time_delta = host->realtimeMs > lp->io_last_scan_time_ms ? host->realtimeMs - lp->io_last_scan_time_ms : 0; + unsigned long long time_delta = saturatingSub(host->realtimeMs, lp->io_last_scan_time_ms); + + // Note: Linux Kernel documentation states that /proc//io may be racy + // on 32-bit machines. (Documentation/filesystems/proc.rst) char* buf = buffer; const char* line; while ((line = strsep(&buf, "\n")) != NULL) { switch (line[0]) { - case 'r': - if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) { - lp->io_rchar = strtoull(line + 7, NULL, 10); - } else if (String_startsWith(line + 1, "ead_bytes: ")) { - lp->io_read_bytes = strtoull(line + 12, NULL, 10); - lp->io_rate_read_bps = time_delta ? (lp->io_read_bytes - last_read) * /*ms to s*/1000. / time_delta : NAN; - } - break; - case 'w': - if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) { - lp->io_wchar = strtoull(line + 7, NULL, 10); - } else if (String_startsWith(line + 1, "rite_bytes: ")) { - lp->io_write_bytes = strtoull(line + 13, NULL, 10); - lp->io_rate_write_bps = time_delta ? (lp->io_write_bytes - last_write) * /*ms to s*/1000. / time_delta : NAN; - } - break; - case 's': - if (line[4] == 'r' && String_startsWith(line + 1, "yscr: ")) { - lp->io_syscr = strtoull(line + 7, NULL, 10); - } else if (String_startsWith(line + 1, "yscw: ")) { - lp->io_syscw = strtoull(line + 7, NULL, 10); - } - break; - case 'c': - if (String_startsWith(line + 1, "ancelled_write_bytes: ")) { - lp->io_cancelled_write_bytes = strtoull(line + 23, NULL, 10); - } + case 'r': + if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) { + lp->io_rchar = strtoull(line + 7, NULL, 10); + } else if (String_startsWith(line + 1, "ead_bytes: ")) { + lp->io_read_bytes = strtoull(line + 12, NULL, 10); + lp->io_rate_read_bps = time_delta ? saturatingSub(lp->io_read_bytes, last_read) * /*ms to s*/1000. / time_delta : NAN; + } + break; + case 'w': + if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) { + lp->io_wchar = strtoull(line + 7, NULL, 10); + } else if (String_startsWith(line + 1, "rite_bytes: ")) { + lp->io_write_bytes = strtoull(line + 13, NULL, 10); + lp->io_rate_write_bps = time_delta ? saturatingSub(lp->io_write_bytes, last_write) * /*ms to s*/1000. / time_delta : NAN; + } + break; + case 's': + if (line[4] == 'r' && String_startsWith(line + 1, "yscr: ")) { + lp->io_syscr = strtoull(line + 7, NULL, 10); + } else if (String_startsWith(line + 1, "yscw: ")) { + lp->io_syscw = strtoull(line + 7, NULL, 10); + } + break; + case 'c': + if (String_startsWith(line + 1, "ancelled_write_bytes: ")) { + lp->io_cancelled_write_bytes = strtoull(line + 23, NULL, 10); + } } } @@ -549,7 +554,7 @@ typedef struct LibraryData_ { bool exec; } LibraryData; -static void LinuxProcessList_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* value, void* data) { +static void LinuxProcessTable_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* value, void* data) { if (!data) return; @@ -564,7 +569,7 @@ static void LinuxProcessList_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* *d += v->size; } -static void LinuxProcessList_readMaps(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host, bool calcSize, bool checkDeletedLib) { +static void LinuxProcessTable_readMaps(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host, bool calcSize, bool checkDeletedLib) { Process* proc = (Process*)process; proc->usesDeletedLib = false; @@ -601,6 +606,9 @@ static void LinuxProcessList_readMaps(LinuxProcess* process, openat_arg_t procFd if (' ' != *readptr++) continue; + if (!readptr[0] || !readptr[1] || !readptr[2] || !readptr[3]) + continue; + map_execute = (readptr[2] == 'x'); readptr += 4; if (' ' != *readptr++) @@ -665,7 +673,7 @@ static void LinuxProcessList_readMaps(LinuxProcess* process, openat_arg_t procFd if (calcSize) { uint64_t total_size = 0; - Hashtable_foreach(ht, LinuxProcessList_calcLibSize_helper, &total_size); + Hashtable_foreach(ht, LinuxProcessTable_calcLibSize_helper, &total_size); Hashtable_delete(ht); @@ -673,7 +681,7 @@ static void LinuxProcessList_readMaps(LinuxProcess* process, openat_arg_t procFd } } -static bool LinuxProcessList_readStatmFile(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host) { +static bool LinuxProcessTable_readStatmFile(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host) { FILE* statmfile = fopenat(procFd, "statm", "r"); if (!statmfile) return false; @@ -693,12 +701,14 @@ static bool LinuxProcessList_readStatmFile(LinuxProcess* process, openat_arg_t p if (r == 7) { process->super.m_virt *= host->pageSizeKB; process->super.m_resident *= host->pageSizeKB; + + process->m_priv = process->super.m_resident - (process->m_share * host->pageSizeKB); } return r == 7; } -static bool LinuxProcessList_readSmapsFile(LinuxProcess* process, openat_arg_t procFd, bool haveSmapsRollup) { +static bool LinuxProcessTable_readSmapsFile(LinuxProcess* process, openat_arg_t procFd, bool haveSmapsRollup) { //http://elixir.free-electrons.com/linux/v4.10/source/fs/proc/task_mmu.c#L719 //kernel will return data in chunks of size PAGE_SIZE or less. FILE* f = fopenat(procFd, haveSmapsRollup ? "smaps_rollup" : "smaps", "r"); @@ -736,11 +746,11 @@ static bool LinuxProcessList_readSmapsFile(LinuxProcess* process, openat_arg_t p #ifdef HAVE_OPENVZ -static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) { +static void LinuxProcessTable_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) { if (access(PROCDIR "/vz", R_OK) != 0) { free(process->ctid); process->ctid = NULL; - process->vpid = process->super.pid; + process->vpid = Process_getPid(&process->super); return; } @@ -748,7 +758,7 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t if (!file) { free(process->ctid); process->ctid = NULL; - process->vpid = process->super.pid; + process->vpid = Process_getPid(&process->super); return; } @@ -797,18 +807,18 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t *value_end = '\0'; switch (field) { - case 1: - foundEnvID = true; - if (!String_eq(name_value_sep, process->ctid ? process->ctid : "")) - free_and_xStrdup(&process->ctid, name_value_sep); - break; - case 2: - foundVPid = true; - process->vpid = strtoul(name_value_sep, NULL, 0); - break; - default: - //Sanity Check: Should never reach here, or the implementation is missing something! - assert(false && "OpenVZ handling: Unimplemented case for field handling reached."); + case 1: + foundEnvID = true; + if (!String_eq(name_value_sep, process->ctid ? process->ctid : "")) + free_and_xStrdup(&process->ctid, name_value_sep); + break; + case 2: + foundVPid = true; + process->vpid = strtoul(name_value_sep, NULL, 0); + break; + default: + //Sanity Check: Should never reach here, or the implementation is missing something! + assert(false && "OpenVZ handling: Unimplemented case for field handling reached."); } } @@ -820,13 +830,13 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t } if (!foundVPid) { - process->vpid = process->super.pid; + process->vpid = Process_getPid(&process->super); } } #endif -static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t procFd) { +static void LinuxProcessTable_readCGroupFile(LinuxProcess* process, openat_arg_t procFd) { FILE* file = fopenat(procFd, "cgroup", "r"); if (!file) { if (process->cgroup) { @@ -837,6 +847,10 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t free(process->cgroup_short); process->cgroup_short = NULL; } + if (process->container_short) { + free(process->container_short); + process->container_short = NULL; + } return; } char output[PROC_LINE_LENGTH + 1]; @@ -851,13 +865,13 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t char* group = buffer; for (size_t i = 0; i < 2; i++) { - group = strchrnul(group, ':'); + group = String_strchrnul(group, ':'); if (!*group) break; group++; } - char* eol = strchrnul(group, '\n'); + char* eol = String_strchrnul(group, '\n'); *eol = '\0'; if (at != output) { @@ -872,33 +886,50 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t bool changed = !process->cgroup || !String_eq(process->cgroup, output); - Process_updateFieldWidth(CGROUP, strlen(output)); + Row_updateFieldWidth(CGROUP, strlen(output)); free_and_xStrdup(&process->cgroup, output); if (!changed) { if (process->cgroup_short) { - Process_updateFieldWidth(CCGROUP, strlen(process->cgroup_short)); + Row_updateFieldWidth(CCGROUP, strlen(process->cgroup_short)); } else { //CCGROUP is alias to normal CGROUP if shortening fails - Process_updateFieldWidth(CCGROUP, strlen(process->cgroup)); + Row_updateFieldWidth(CCGROUP, strlen(process->cgroup)); + } + if (process->container_short) { + Row_updateFieldWidth(CONTAINER, strlen(process->container_short)); + } else { + Row_updateFieldWidth(CONTAINER, strlen("N/A")); } return; } char* cgroup_short = CGroup_filterName(process->cgroup); if (cgroup_short) { - Process_updateFieldWidth(CCGROUP, strlen(cgroup_short)); + Row_updateFieldWidth(CCGROUP, strlen(cgroup_short)); free_and_xStrdup(&process->cgroup_short, cgroup_short); free(cgroup_short); } else { //CCGROUP is alias to normal CGROUP if shortening fails - Process_updateFieldWidth(CCGROUP, strlen(process->cgroup)); + Row_updateFieldWidth(CCGROUP, strlen(process->cgroup)); free(process->cgroup_short); process->cgroup_short = NULL; } + + char* container_short = CGroup_filterContainer(process->cgroup); + if (container_short) { + Row_updateFieldWidth(CONTAINER, strlen(container_short)); + free_and_xStrdup(&process->container_short, container_short); + free(container_short); + } else { + //CONTAINER is just "N/A" if shortening fails + Row_updateFieldWidth(CONTAINER, strlen("N/A")); + free(process->container_short); + process->container_short = NULL; + } } -static void LinuxProcessList_readOomData(LinuxProcess* process, openat_arg_t procFd) { +static void LinuxProcessTable_readOomData(LinuxProcess* process, openat_arg_t procFd) { FILE* file = fopenat(procFd, "oom_score", "r"); if (!file) return; @@ -914,7 +945,7 @@ static void LinuxProcessList_readOomData(LinuxProcess* process, openat_arg_t pro fclose(file); } -static void LinuxProcessList_readAutogroup(LinuxProcess* process, openat_arg_t procFd) { +static void LinuxProcessTable_readAutogroup(LinuxProcess* process, openat_arg_t procFd) { process->autogroup_id = -1; char autogroup[64]; // space for two numeric values and fixed length strings @@ -931,7 +962,7 @@ static void LinuxProcessList_readAutogroup(LinuxProcess* process, openat_arg_t p } } -static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t procFd) { +static void LinuxProcessTable_readSecattrData(LinuxProcess* process, openat_arg_t procFd) { FILE* file = fopenat(procFd, "attr/current", "r"); if (!file) { free(process->secattr); @@ -952,7 +983,7 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t *newline = '\0'; } - Process_updateFieldWidth(SECATTR, strlen(buffer)); + Row_updateFieldWidth(SECATTR, strlen(buffer)); if (process->secattr && String_eq(process->secattr, buffer)) { return; @@ -960,7 +991,7 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t free_and_xStrdup(&process->secattr, buffer); } -static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd) { +static void LinuxProcessTable_readCwd(LinuxProcess* process, openat_arg_t procFd) { char pathBuffer[PATH_MAX + 1] = {0}; #if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT) @@ -1001,16 +1032,16 @@ static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) { if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) { memcpy(&stats, nla_data(nla_next(nla_data(nlattr), &rem)), sizeof(stats)); - assert(lp->super.pid == (pid_t)stats.ac_pid); + assert(Process_getPid(&lp->super) == (pid_t)stats.ac_pid); + // The xxx_delay_total values wrap around on overflow. + // (Linux Kernel "Documentation/accounting/taskstats-struct.rst") unsigned long long int timeDelta = stats.ac_etime * 1000 - lp->delay_read_time; - #define BOUNDS(x) (isnan(x) ? 0.0 : ((x) > 100) ? 100.0 : (x)) - #define DELTAPERC(x,y) BOUNDS((float) ((x) - (y)) / timeDelta * 100) + #define DELTAPERC(x, y) (timeDelta ? MINIMUM((float)((x) - (y)) / timeDelta * 100.0f, 100.0f) : NAN) lp->cpu_delay_percent = DELTAPERC(stats.cpu_delay_total, lp->cpu_delay_total); lp->blkio_delay_percent = DELTAPERC(stats.blkio_delay_total, lp->blkio_delay_total); lp->swapin_delay_percent = DELTAPERC(stats.swapin_delay_total, lp->swapin_delay_total); #undef DELTAPERC - #undef BOUNDS lp->swapin_delay_total = stats.swapin_delay_total; lp->blkio_delay_total = stats.blkio_delay_total; @@ -1020,11 +1051,11 @@ static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) { return NL_OK; } -static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProcess* process) { +static void LinuxProcessTable_readDelayAcctData(LinuxProcessTable* this, LinuxProcess* process) { struct nl_msg* msg; if (!this->netlink_socket) { - LinuxProcessList_initNetlinkSocket(this); + LinuxProcessTable_initNetlinkSocket(this); if (!this->netlink_socket) { goto delayacct_failure; } @@ -1042,7 +1073,7 @@ static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProc nlmsg_free(msg); } - if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, process->super.pid) < 0) { + if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, Process_getPid(&process->super)) < 0) { nlmsg_free(msg); } @@ -1064,7 +1095,7 @@ static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProc #endif -static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t procFd) { +static bool LinuxProcessTable_readCmdlineFile(Process* process, openat_arg_t procFd) { char command[4096 + 1]; // max cmdline length on Linux ssize_t amtRead = xReadfileat(procFd, "cmdline", command, sizeof(command)); if (amtRead <= 0) @@ -1237,7 +1268,7 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc return true; } -static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned long int tty_nr) { +static char* LinuxProcessTable_updateTtyDevice(TtyDriver* ttyDrivers, unsigned long int tty_nr) { unsigned int maj = major(tty_nr); unsigned int min = minor(tty_nr); @@ -1291,13 +1322,15 @@ static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned lo } static bool isOlderThan(const Process* proc, unsigned int seconds) { - assert(proc->host->realtimeMs > 0); + const Machine* host = proc->super.host; + + assert(host->realtimeMs > 0); /* Starttime might not yet be parsed */ if (proc->starttime_ctime <= 0) return false; - uint64_t realtime = proc->host->realtimeMs / 1000; + uint64_t realtime = host->realtimeMs / 1000; if (realtime < (uint64_t)proc->starttime_ctime) return false; @@ -1305,15 +1338,15 @@ static bool isOlderThan(const Process* proc, unsigned int seconds) { return realtime - proc->starttime_ctime > seconds; } -static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const LinuxMachine* lhost, const char* dirname, const Process* parent) { - ProcessList* pl = (ProcessList*) this; +static bool LinuxProcessTable_recurseProcTree(LinuxProcessTable* this, openat_arg_t parentFd, const LinuxMachine* lhost, const char* dirname, const Process* parent) { + ProcessTable* pt = (ProcessTable*) this; const Machine* host = &lhost->super; const Settings* settings = host->settings; const ScreenSettings* ss = settings->ss; const struct dirent* entry; /* set runningTasks from /proc/stat (from Machine_scanCPUTime) */ - pl->runningTasks = lhost->runningTasks; + pt->runningTasks = lhost->runningTasks; #ifdef HAVE_OPENAT int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); @@ -1363,7 +1396,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } // Skip task directory of main thread - if (parent && pid == parent->pid) + if (parent && pid == Process_getPid(parent)) continue; #ifdef HAVE_OPENAT @@ -1376,55 +1409,55 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ #endif bool preExisting; - Process* proc = ProcessList_getProcess(pl, pid, &preExisting, LinuxProcess_new); + Process* proc = ProcessTable_getProcess(pt, pid, &preExisting, LinuxProcess_new); LinuxProcess* lp = (LinuxProcess*) proc; - proc->tgid = parent ? parent->pid : pid; - proc->isUserlandThread = proc->pid != proc->tgid; + Process_setThreadGroup(proc, parent ? Process_getPid(parent) : pid); + proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); - LinuxProcessList_recurseProcTree(this, procFd, lhost, "task", proc); + LinuxProcessTable_recurseProcTree(this, procFd, lhost, "task", proc); /* * These conditions will not trigger on first occurrence, cause we need to - * add the process to the ProcessList and do all one time scans + * add the process to the ProcessTable and do all one time scans * (e.g. parsing the cmdline to detect a kernel thread) * But it will short-circuit subsequent scans. */ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) { - proc->updated = true; - proc->show = false; - pl->kernelThreads++; - pl->totalTasks++; + proc->super.updated = true; + proc->super.show = false; + pt->kernelThreads++; + pt->totalTasks++; Compat_openatArgClose(procFd); continue; } if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) { - proc->updated = true; - proc->show = false; - pl->userlandThreads++; - pl->totalTasks++; + proc->super.updated = true; + proc->super.show = false; + pt->userlandThreads++; + pt->totalTasks++; Compat_openatArgClose(procFd); continue; } if (preExisting && hideRunningInContainer && proc->isRunningInContainer) { - proc->updated = true; - proc->show = false; + proc->super.updated = true; + proc->super.show = false; Compat_openatArgClose(procFd); continue; } bool scanMainThread = !hideUserlandThreads && !Process_isKernelThread(proc) && !parent; if (ss->flags & PROCESS_FLAG_IO) - LinuxProcessList_readIoFile(lp, procFd, scanMainThread); + LinuxProcessTable_readIoFile(lp, procFd, scanMainThread); - if (!LinuxProcessList_readStatmFile(lp, procFd, lhost)) + if (!LinuxProcessTable_readStatmFile(lp, procFd, lhost)) goto errorReadingProcess; { bool prev = proc->usesDeletedLib; if (!proc->isKernelThread && !proc->isUserlandThread && - ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(proc, 10)))) { + ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(proc, 10)))) { // Check if we really should recalculate the M_LRS value for this process uint64_t passedTimeInMs = host->realtimeMs - lp->last_mlrs_calctime; @@ -1433,7 +1466,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ if (passedTimeInMs > recheck) { lp->last_mlrs_calctime = host->realtimeMs; - LinuxProcessList_readMaps(lp, procFd, lhost, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe); + LinuxProcessTable_readMaps(lp, procFd, lhost, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe); } } else { /* Copy from process structure in threads and reset if setting got disabled */ @@ -1450,7 +1483,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ // Read smaps file of each process only every second pass to improve performance static int smaps_flag = 0; if ((pid & 1) == smaps_flag) { - LinuxProcessList_readSmapsFile(lp, procFd, this->haveSmapsRollup); + LinuxProcessTable_readSmapsFile(lp, procFd, this->haveSmapsRollup); } if (pid == 1) { smaps_flag = !smaps_flag; @@ -1463,7 +1496,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ char statCommand[MAX_NAME + 1]; unsigned long long int lasttimes = (lp->utime + lp->stime); unsigned long int tty_nr = proc->tty_nr; - if (!LinuxProcessList_readStatFile(lp, procFd, lhost, scanMainThread, statCommand, sizeof(statCommand))) + if (!LinuxProcessTable_readStatFile(lp, procFd, lhost, scanMainThread, statCommand, sizeof(statCommand))) goto errorReadingProcess; if (lp->flags & PF_KTHREAD) { @@ -1472,75 +1505,78 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ if (tty_nr != proc->tty_nr && this->ttyDrivers) { free(proc->tty_name); - proc->tty_name = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr); + proc->tty_name = LinuxProcessTable_updateTtyDevice(this->ttyDrivers, proc->tty_nr); } if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) { - LinuxProcess_updateIOPriority(lp); + LinuxProcess_updateIOPriority(proc); } - /* period might be 0 after system sleep */ - float percent_cpu = (lhost->period < 1E-6) ? 0.0F : ((lp->utime + lp->stime - lasttimes) / lhost->period * 100.0); - proc->percent_cpu = CLAMP(percent_cpu, 0.0F, host->activeCPUs * 100.0F); + proc->percent_cpu = NAN; + /* lhost->period might be 0 after system sleep */ + if (lhost->period > 0.0) { + float percent_cpu = saturatingSub(lp->utime + lp->stime, lasttimes) / lhost->period * 100.0; + proc->percent_cpu = MINIMUM(percent_cpu, host->activeCPUs * 100.0F); + } proc->percent_mem = proc->m_resident / (double)(host->totalMem) * 100.0; Process_updateCPUFieldWidths(proc->percent_cpu); - if (! LinuxProcessList_updateUser(host, proc, procFd)) + if (!LinuxProcessTable_updateUser(host, proc, procFd)) goto errorReadingProcess; - if (!LinuxProcessList_readStatusFile(proc, procFd)) + if (!LinuxProcessTable_readStatusFile(proc, procFd)) goto errorReadingProcess; if (!preExisting) { #ifdef HAVE_OPENVZ if (ss->flags & PROCESS_FLAG_LINUX_OPENVZ) { - LinuxProcessList_readOpenVZData(lp, procFd); + LinuxProcessTable_readOpenVZData(lp, procFd); } #endif if (proc->isKernelThread) { Process_updateCmdline(proc, NULL, 0, 0); - } else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) { + } else if (!LinuxProcessTable_readCmdlineFile(proc, procFd)) { Process_updateCmdline(proc, statCommand, 0, strlen(statCommand)); } Process_fillStarttimeBuffer(proc); - ProcessList_add(pl, proc); + ProcessTable_add(pt, proc); } else { if (settings->updateProcessNames && proc->state != ZOMBIE) { if (proc->isKernelThread) { Process_updateCmdline(proc, NULL, 0, 0); - } else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) { + } else if (!LinuxProcessTable_readCmdlineFile(proc, procFd)) { Process_updateCmdline(proc, statCommand, 0, strlen(statCommand)); } } } if (ss->flags & PROCESS_FLAG_LINUX_CGROUP) - LinuxProcessList_readCGroupFile(lp, procFd); + LinuxProcessTable_readCGroupFile(lp, procFd); #ifdef HAVE_DELAYACCT if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) { - LinuxProcessList_readDelayAcctData(this, lp); + LinuxProcessTable_readDelayAcctData(this, lp); } #endif if (ss->flags & PROCESS_FLAG_LINUX_OOM) { - LinuxProcessList_readOomData(lp, procFd); + LinuxProcessTable_readOomData(lp, procFd); } if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) { - LinuxProcessList_readSecattrData(lp, procFd); + LinuxProcessTable_readSecattrData(lp, procFd); } if (ss->flags & PROCESS_FLAG_CWD) { - LinuxProcessList_readCwd(lp, procFd); + LinuxProcessTable_readCwd(lp, procFd); } if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) { - LinuxProcessList_readAutogroup(lp, procFd); + LinuxProcessTable_readAutogroup(lp, procFd); } #ifdef SCHEDULER_SUPPORT @@ -1558,24 +1594,24 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ * Final section after all data has been gathered */ - proc->updated = true; + proc->super.updated = true; Compat_openatArgClose(procFd); if (hideRunningInContainer && proc->isRunningInContainer) { - proc->show = false; + proc->super.show = false; continue; } if (Process_isKernelThread(proc)) { - pl->kernelThreads++; + pt->kernelThreads++; } else if (Process_isUserlandThread(proc)) { - pl->userlandThreads++; + pt->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))); + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); - pl->totalTasks++; + pt->totalTasks++; /* runningTasks is set in Machine_scanCPUTime() from /proc/stat */ continue; @@ -1604,9 +1640,9 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ return true; } -void ProcessList_goThroughEntries(ProcessList* super) { - LinuxProcessList* this = (LinuxProcessList*) super; - const Machine* host = super->host; +void ProcessTable_goThroughEntries(ProcessTable* super) { + LinuxProcessTable* this = (LinuxProcessTable*) super; + const Machine* host = super->super.host; const Settings* settings = host->settings; const LinuxMachine* lhost = (const LinuxMachine*) host; @@ -1628,5 +1664,5 @@ void ProcessList_goThroughEntries(ProcessList* super) { openat_arg_t rootFd = ""; #endif - LinuxProcessList_recurseProcTree(this, rootFd, lhost, PROCDIR, NULL); + LinuxProcessTable_recurseProcTree(this, rootFd, lhost, PROCDIR, NULL); } diff --git a/linux/LinuxProcessList.h b/linux/LinuxProcessTable.h similarity index 62% rename from linux/LinuxProcessList.h rename to linux/LinuxProcessTable.h index 824de482e..b87f9b0a8 100644 --- a/linux/LinuxProcessList.h +++ b/linux/LinuxProcessTable.h @@ -1,20 +1,15 @@ -#ifndef HEADER_LinuxProcessList -#define HEADER_LinuxProcessList +#ifndef HEADER_LinuxProcessTable +#define HEADER_LinuxProcessTable /* -htop - LinuxProcessList.h +htop - LinuxProcessTable.h (C) 2014 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" - #include -#include -#include "Hashtable.h" -#include "ProcessList.h" -#include "UsersTable.h" +#include "ProcessTable.h" typedef struct TtyDriver_ { @@ -24,8 +19,8 @@ typedef struct TtyDriver_ { unsigned int minorTo; } TtyDriver; -typedef struct LinuxProcessList_ { - ProcessList super; +typedef struct LinuxProcessTable_ { + ProcessTable super; TtyDriver* ttyDrivers; bool haveSmapsRollup; @@ -35,6 +30,6 @@ typedef struct LinuxProcessList_ { struct nl_sock* netlink_socket; int netlink_family; #endif -} LinuxProcessList; +} LinuxProcessTable; #endif diff --git a/linux/Platform.c b/linux/Platform.c index e0b633ba1..8dc8bb595 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -5,13 +5,11 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "ZramMeter.h" -#include "config.h" +#include "config.h" // IWYU pragma: keep #include "linux/Platform.h" #include -#include #include #include #include @@ -47,7 +45,6 @@ in the source distribution for its full text. #include "Panel.h" #include "PressureStallMeter.h" #include "ProvideCurses.h" -#include "linux/SELinuxMeter.h" #include "Settings.h" #include "SwapMeter.h" #include "SysArchMeter.h" @@ -58,9 +55,11 @@ in the source distribution for its full text. #include "linux/IOPriorityPanel.h" #include "linux/LinuxMachine.h" #include "linux/LinuxProcess.h" +#include "linux/SELinuxMeter.h" #include "linux/SystemdMeter.h" #include "linux/ZramMeter.h" #include "linux/ZramStats.h" +#include "linux/ZswapStats.h" #include "zfs/ZfsArcMeter.h" #include "zfs/ZfsArcStats.h" #include "zfs/ZfsCompressedArcMeter.h" @@ -164,7 +163,7 @@ static Htop_Reaction Platform_actionSetIOPriority(State* st) { const void* set = Action_pickFromVector(st, ioprioPanel, 20, true); if (set) { IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel); - bool ok = MainPanel_foreachProcess(st->mainPanel, LinuxProcess_setIOPriority, (Arg) { .i = ioprio2 }, NULL); + bool ok = MainPanel_foreachRow(st->mainPanel, LinuxProcess_rowSetIOPriority, (Arg) { .i = ioprio2 }, NULL); if (!ok) { beep(); } @@ -179,7 +178,7 @@ static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) { return false; } bool anyTagged; - bool ok = MainPanel_foreachProcess(panel, LinuxProcess_changeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged); + bool ok = MainPanel_foreachRow(panel, LinuxProcess_rowChangeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged); if (!ok) beep(); return anyTagged; @@ -291,12 +290,12 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *fifteen = NAN; } -int Platform_getMaxPid(void) { +pid_t Platform_getMaxPid(void) { + pid_t maxPid = 4194303; FILE* file = fopen(PROCDIR "/sys/kernel/pid_max", "r"); if (!file) - return -1; + return maxPid; - int maxPid = 4194303; int match = fscanf(file, "%32d", &maxPid); (void) match; fclose(file); @@ -304,7 +303,7 @@ int Platform_getMaxPid(void) { } double Platform_setCPUValues(Meter* this, unsigned int cpu) { - const LinuxMachine* lhost = (const LinuxMachine *) this->host; + const LinuxMachine* lhost = (const LinuxMachine*) this->host; const Settings* settings = this->host->settings; const CPUData* cpuData = &(lhost->cpuData[cpu]); double total = (double) ( cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod); @@ -322,23 +321,26 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) { v[CPU_METER_KERNEL] = cpuData->systemPeriod / total * 100.0; v[CPU_METER_IRQ] = cpuData->irqPeriod / total * 100.0; v[CPU_METER_SOFTIRQ] = cpuData->softIrqPeriod / total * 100.0; + this->curItems = 5; + v[CPU_METER_STEAL] = cpuData->stealPeriod / total * 100.0; v[CPU_METER_GUEST] = cpuData->guestPeriod / total * 100.0; - v[CPU_METER_IOWAIT] = cpuData->ioWaitPeriod / total * 100.0; - this->curItems = 8; - percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ] + v[CPU_METER_SOFTIRQ]; if (settings->accountGuestInCPUMeter) { - percent += v[CPU_METER_STEAL] + v[CPU_METER_GUEST]; + this->curItems = 7; } + + v[CPU_METER_IOWAIT] = cpuData->ioWaitPeriod / total * 100.0; } else { v[CPU_METER_KERNEL] = cpuData->systemAllPeriod / total * 100.0; v[CPU_METER_IRQ] = (cpuData->stealPeriod + cpuData->guestPeriod) / total * 100.0; this->curItems = 4; - percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ]; } - percent = CLAMP(percent, 0.0, 100.0); - if (isnan(percent)) { - percent = 0.0; + + percent = sumPositiveValues(v, this->curItems); + percent = MINIMUM(percent, 100.0); + + if (settings->detailedCPUTime) { + this->curItems = 8; } v[CPU_METER_FREQUENCY] = cpuData->frequency; @@ -358,9 +360,9 @@ void Platform_setMemoryValues(Meter* this) { this->total = host->totalMem; this->values[MEMORY_METER_USED] = host->usedMem; - this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_SHARED] = host->sharedMem; this->values[MEMORY_METER_COMPRESSED] = 0; /* compressed */ + this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_CACHE] = host->cachedMem; this->values[MEMORY_METER_AVAILABLE] = host->availableMem; @@ -415,7 +417,7 @@ void Platform_setZramValues(Meter* this) { this->total = lhost->zram.totalZram; this->values[ZRAM_METER_COMPRESSED] = lhost->zram.usedZramComp; - this->values[ZRAM_METER_UNCOMPRESSED] = lhost->zram.usedZramOrig; + this->values[ZRAM_METER_UNCOMPRESSED] = lhost->zram.usedZramOrig - lhost->zram.usedZramComp; } void Platform_setZfsArcValues(Meter* this) { @@ -842,7 +844,7 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { } } - if (!now && full && !isnan(capacityLevel)) + if (!now && full && isNonnegative(capacityLevel)) totalRemain += capacityLevel * fullCharge; } else if (type == AC) { @@ -882,12 +884,12 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { if (Platform_Battery_method == BAT_PROC) { Platform_Battery_getProcData(percent, isOnAC); - if (isnan(*percent)) + if (!isNonnegative(*percent)) Platform_Battery_method = BAT_SYS; } if (Platform_Battery_method == BAT_SYS) { Platform_Battery_getSysData(percent, isOnAC); - if (isnan(*percent)) + if (!isNonnegative(*percent)) Platform_Battery_method = BAT_ERR; } if (Platform_Battery_method == BAT_ERR) { @@ -926,7 +928,7 @@ CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) { case 160: { const char* mode = optarg; if (!mode && optind < argc && argv[optind] != NULL && - (argv[optind][0] != '\0' && argv[optind][0] != '-')) { + (argv[optind][0] != '\0' && argv[optind][0] != '-')) { mode = argv[optind++]; } diff --git a/linux/Platform.h b/linux/Platform.h index 1621d5628..e99d1a226 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" - #include #include #include @@ -23,15 +21,18 @@ in the source distribution for its full text. #include "Macros.h" #include "Meter.h" #include "NetworkIOMeter.h" +#include "Panel.h" #include "Process.h" #include "ProcessLocksScreen.h" #include "RichString.h" +#include "Settings.h" #include "SignalsPanel.h" #include "CommandLine.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" + /* GNU/Hurd does not have PATH_MAX in limits.h */ #ifndef PATH_MAX #define PATH_MAX 4096 @@ -59,7 +60,7 @@ int Platform_getUptime(void); void Platform_getLoadAverage(double* one, double* five, double* fifteen); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* this, unsigned int cpu); @@ -132,7 +133,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -140,4 +141,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/linux/PressureStallMeter.c b/linux/PressureStallMeter.c index c4f534ea5..f7962475e 100644 --- a/linux/PressureStallMeter.c +++ b/linux/PressureStallMeter.c @@ -6,6 +6,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "linux/PressureStallMeter.h" #include diff --git a/linux/ProcessField.h b/linux/ProcessField.h index 17cafa964..581a982e9 100644 --- a/linux/ProcessField.h +++ b/linux/ProcessField.h @@ -46,6 +46,8 @@ in the source distribution for its full text. AUTOGROUP_ID = 127, \ AUTOGROUP_NICE = 128, \ CCGROUP = 129, \ + CONTAINER = 130, \ + M_PRIV = 131, \ // End of list diff --git a/linux/SELinuxMeter.c b/linux/SELinuxMeter.c index c35cb686f..323c2a171 100644 --- a/linux/SELinuxMeter.c +++ b/linux/SELinuxMeter.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "linux/SELinuxMeter.h" #include "CRT.h" diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c index ff178e0ef..e13c64618 100644 --- a/linux/SystemdMeter.c +++ b/linux/SystemdMeter.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "linux/SystemdMeter.h" #include @@ -305,12 +307,12 @@ static void SystemdMeter_updateValues(Meter* this) { static int zeroDigitColor(unsigned int value) { switch (value) { - case 0: - return CRT_colors[METER_VALUE]; - case INVALID_VALUE: - return CRT_colors[METER_VALUE_ERROR]; - default: - return CRT_colors[METER_VALUE_NOTICE]; + case 0: + return CRT_colors[METER_VALUE]; + case INVALID_VALUE: + return CRT_colors[METER_VALUE_ERROR]; + default: + return CRT_colors[METER_VALUE_NOTICE]; } } diff --git a/linux/ZramMeter.c b/linux/ZramMeter.c index 6e80eb1a7..8329f0146 100644 --- a/linux/ZramMeter.c +++ b/linux/ZramMeter.c @@ -1,3 +1,13 @@ +/* +htop - linux/ZramMeter.c +(C) 2020 Murloc Knight +(C) 2020-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + #include "linux/ZramMeter.h" #include @@ -27,7 +37,8 @@ static void ZramMeter_updateValues(Meter* this) { METER_BUFFER_APPEND_CHR(buffer, size, '('); - written = Meter_humanUnit(buffer, this->values[ZRAM_METER_UNCOMPRESSED], size); + double uncompressed = this->values[ZRAM_METER_COMPRESSED] + this->values[ZRAM_METER_UNCOMPRESSED]; + written = Meter_humanUnit(buffer, uncompressed, size); METER_BUFFER_CHECK(buffer, size, written); METER_BUFFER_APPEND_CHR(buffer, size, ')'); @@ -50,7 +61,8 @@ static void ZramMeter_display(const Object* cast, RichString* out) { RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:"); RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); - Meter_humanUnit(buffer, this->values[ZRAM_METER_UNCOMPRESSED], sizeof(buffer)); + double uncompressed = this->values[ZRAM_METER_COMPRESSED] + this->values[ZRAM_METER_UNCOMPRESSED]; + Meter_humanUnit(buffer, uncompressed, sizeof(buffer)); RichString_appendAscii(out, CRT_colors[METER_TEXT], " uncompressed:"); RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); } @@ -64,7 +76,6 @@ const MeterClass ZramMeter_class = { .updateValues = ZramMeter_updateValues, .defaultMode = BAR_METERMODE, .maxItems = ZRAM_METER_ITEMCOUNT, - .comprisedValues = true, .total = 100.0, .attributes = ZramMeter_attributes, .name = "Zram", diff --git a/linux/ZramMeter.h b/linux/ZramMeter.h index db27d0b35..14a52155a 100644 --- a/linux/ZramMeter.h +++ b/linux/ZramMeter.h @@ -1,5 +1,12 @@ #ifndef HEADER_ZramMeter #define HEADER_ZramMeter +/* +htop - linux/ZramMeter.h +(C) 2020 Murloc Knight +(C) 2020-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include "Meter.h" diff --git a/linux/ZramStats.h b/linux/ZramStats.h index 1c1625a42..f71a6c274 100644 --- a/linux/ZramStats.h +++ b/linux/ZramStats.h @@ -7,7 +7,7 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "ProcessList.h" +#include "ProcessTable.h" typedef struct ZramStats_ { memory_t totalZram; diff --git a/linux/ZswapStats.h b/linux/ZswapStats.h index 78771e101..29e516f48 100644 --- a/linux/ZswapStats.h +++ b/linux/ZswapStats.h @@ -7,11 +7,9 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "ProcessList.h" +#include "ProcessTable.h" typedef struct ZswapStats_ { - /* maximum size of the zswap pool */ - memory_t totalZswapPool; /* amount of RAM used by the zswap pool */ memory_t usedZswapComp; /* amount of data stored inside the zswap pool */ diff --git a/netbsd/NetBSDMachine.c b/netbsd/NetBSDMachine.c index 9f3b32de1..9c04cdd3f 100644 --- a/netbsd/NetBSDMachine.c +++ b/netbsd/NetBSDMachine.c @@ -8,6 +8,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "netbsd/NetBSDMachine.h" #include @@ -112,7 +114,7 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { NetBSDMachine_updateCPUcount(this); size = sizeof(this->fscale); - if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0) { + if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0 || this->fscale <= 0) { CRT_fatalError("fscale sysctl call failed"); } @@ -129,7 +131,7 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { } void Machine_delete(Machine* super) { - NetBSDMachine* this = (NetBSDProcessList*) super; + NetBSDMachine* this = (NetBSDMachine*) super; Machine_done(super); @@ -266,11 +268,11 @@ static void NetBSDMachine_scanCPUFrequency(NetBSDMachine* this) { void Machine_scan(Machine* super) { NetBSDMachine* this = (NetBSDMachine*) super; - NetBSDProcessList_scanMemoryInfo(this); - NetBSDProcessList_scanCPUTime(this); + NetBSDProcessTable_scanMemoryInfo(this); + NetBSDProcessTable_scanCPUTime(this); if (super->settings->showCPUFrequency) { - NetBSDProcessList_scanCPUFrequency(npl); + NetBSDProcessTable_scanCPUFrequency(npl); } } diff --git a/netbsd/NetBSDMachine.h b/netbsd/NetBSDMachine.h index 9c4e75ed2..9d3aa0b2a 100644 --- a/netbsd/NetBSDMachine.h +++ b/netbsd/NetBSDMachine.h @@ -15,7 +15,7 @@ in the source distribution for its full text. #include #include "Machine.h" -#include "ProcessList.h" +#include "ProcessTable.h" typedef struct CPUData_ { diff --git a/netbsd/NetBSDProcess.c b/netbsd/NetBSDProcess.c index bdb0f50c0..3faa25a2e 100644 --- a/netbsd/NetBSDProcess.c +++ b/netbsd/NetBSDProcess.c @@ -8,6 +8,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "netbsd/NetBSDProcess.h" #include @@ -225,16 +227,20 @@ void Process_delete(Object* cast) { free(this); } -static void NetBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) { +static void NetBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const NetBSDProcess* np = (const NetBSDProcess*) super; + char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; + //size_t n = sizeof(buffer) - 1; switch (field) { // add NetBSD-specific fields here default: - Process_writeField(this, str, field); + Process_writeField(np->super, str, field); return; } + RichString_appendWide(str, attr, buffer); } @@ -254,11 +260,18 @@ static int NetBSDProcess_compareByKey(const Process* v1, const Process* v2, Proc const ProcessClass NetBSDProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = NetBSDProcess_rowWriteField }, - .writeField = NetBSDProcess_writeField, .compareByKey = NetBSDProcess_compareByKey }; diff --git a/netbsd/NetBSDProcessList.c b/netbsd/NetBSDProcessTable.c similarity index 67% rename from netbsd/NetBSDProcessList.c rename to netbsd/NetBSDProcessTable.c index 9e5a9be4d..f09a3a4b5 100644 --- a/netbsd/NetBSDProcessList.c +++ b/netbsd/NetBSDProcessTable.c @@ -1,5 +1,5 @@ /* -htop - NetBSDProcessList.c +htop - NetBSDProcessTable.c (C) 2014 Hisham H. Muhammad (C) 2015 Michael McConville (C) 2021 Santhosh Raju @@ -8,7 +8,9 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "netbsd/NetBSDProcessList.h" +#include "config.h" // IWYU pragma: keep + +#include "netbsd/NetBSDProcessTable.h" #include #include @@ -28,29 +30,30 @@ in the source distribution for its full text. #include "Macros.h" #include "Object.h" #include "Process.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "Settings.h" #include "XUtils.h" #include "netbsd/NetBSDMachine.h" #include "netbsd/NetBSDProcess.h" -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - NetBSDProcessList* this = xCalloc(1, sizeof(NetBSDProcessList)); - ProcessList* super = (ProcessList*) this; +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + NetBSDProcessTable* this = xCalloc(1, sizeof(NetBSDProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(super, Class(NetBSDProcess), host, pidMatchList); + ProcessTable* super = (ProcessTable*) this; + ProcessTable_init(super, Class(NetBSDProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* this) { - NetBSDProcessList* npl = (NetBSDProcessList*) this; - ProcessList_done(this); - free(npl); +void ProcessTable_delete(Object* cast) { + NetBSDProcessTable* this = (NetBSDProcessTable*) cast; + ProcessTable_done(&this->super); + free(this); } -static void NetBSDProcessList_updateExe(const struct kinfo_proc2* kproc, Process* proc) { +static void NetBSDProcessTable_updateExe(const struct kinfo_proc2* kproc, Process* proc) { const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_PATHNAME }; char buffer[2048]; size_t size = sizeof(buffer); @@ -68,7 +71,7 @@ static void NetBSDProcessList_updateExe(const struct kinfo_proc2* kproc, Process Process_updateExe(proc, buffer); } -static void NetBSDProcessList_updateCwd(const struct kinfo_proc2* kproc, Process* proc) { +static void NetBSDProcessTable_updateCwd(const struct kinfo_proc2* kproc, Process* proc) { const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_CWD }; char buffer[2048]; size_t size = sizeof(buffer); @@ -88,7 +91,7 @@ static void NetBSDProcessList_updateCwd(const struct kinfo_proc2* kproc, Process free_and_xStrdup(&proc->procCwd, buffer); } -static void NetBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc2* kproc, Process* proc) { +static void NetBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc2* kproc, Process* proc) { Process_updateComm(proc, kproc->p_comm); /* @@ -147,7 +150,7 @@ static double getpcpu(const NetBSDMachine* nhost, const struct kinfo_proc2* kp) return 100.0 * (double)kp->p_pctcpu / nhost->fscale; } -static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { +static void NetBSDProcessTable_scanProcs(NetBSDProcessTable* this) { const Machine* host = this->super.host; const NetBSDMachine* nhost = (const NetBSDMachine*) host; const Settings* settings = host->settings; @@ -161,22 +164,22 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { const struct kinfo_proc2* kproc = &kprocs[i]; bool preExisting = false; - Process* proc = ProcessList_getProcess(&this->super, kproc->p_pid, &preExisting, NetBSDProcess_new); + Process* proc = ProcessTable_getProcess(&this->super, kproc->p_pid, &preExisting, NetBSDProcess_new); - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); if (!preExisting) { - proc->pid = kproc->p_pid; - proc->ppid = kproc->p_ppid; + Process_setPid(proc, kproc->p_pid); + Process_setParent(proc, kproc->p_ppid); + Process_setThreadGroup(proc, kproc->p_pid); 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->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); // eh? proc->starttime_ctime = kproc->p_ustart_sec; Process_fillStarttimeBuffer(proc); - ProcessList_add(&this->super, proc); + ProcessTable_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; @@ -187,16 +190,16 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { free_and_xStrdup(&proc->tty_name, name); } - NetBSDProcessList_updateExe(kproc, proc); - NetBSDProcessList_updateProcessName(nhost->kd, kproc, proc); + NetBSDProcessTable_updateExe(kproc, proc); + NetBSDProcessTable_updateProcessName(nhost->kd, kproc, proc); } else { if (settings->updateProcessNames) { - NetBSDProcessList_updateProcessName(nhost->kd, kproc, proc); + NetBSDProcessTable_updateProcessName(nhost->kd, kproc, proc); } } if (settings->ss->flags & PROCESS_FLAG_CWD) { - NetBSDProcessList_updateCwd(kproc, proc); + NetBSDProcessTable_updateCwd(kproc, proc); } if (proc->st_uid != kproc->p_uid) { @@ -225,30 +228,32 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { /* 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) + 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; - } 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; + 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)) { @@ -261,12 +266,12 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { if (proc->state == RUNNING) { this->super.runningTasks++; } - proc->updated = true; + proc->super.updated = true; } } -void ProcessList_goThroughEntries(ProcessList* super) { - NetBSDProcessList* npl = (NetBSDProcessList*) super; +void ProcessTable_goThroughEntries(ProcessTable* super) { + NetBSDProcessTable* npt = (NetBSDProcessTable*) super; - NetBSDProcessList_scanProcs(npl); + NetBSDProcessTable_scanProcs(npt); } diff --git a/netbsd/NetBSDProcessList.h b/netbsd/NetBSDProcessTable.h similarity index 58% rename from netbsd/NetBSDProcessList.h rename to netbsd/NetBSDProcessTable.h index 362d84fc0..1bcfa9852 100644 --- a/netbsd/NetBSDProcessList.h +++ b/netbsd/NetBSDProcessTable.h @@ -1,7 +1,7 @@ -#ifndef HEADER_NetBSDProcessList -#define HEADER_NetBSDProcessList +#ifndef HEADER_NetBSDProcessTable +#define HEADER_NetBSDProcessTable /* -htop - NetBSDProcessList.h +htop - NetBSDProcessTable.h (C) 2014 Hisham H. Muhammad (C) 2015 Michael McConville (C) 2021 Santhosh Raju @@ -14,11 +14,11 @@ in the source distribution for its full text. #include #include "Hashtable.h" -#include "ProcessList.h" +#include "ProcessTable.h" -typedef struct NetBSDProcessList_ { - ProcessList super; -} NetBSDProcessList; +typedef struct NetBSDProcessTable_ { + ProcessTable super; +} NetBSDProcessTable; #endif diff --git a/netbsd/Platform.c b/netbsd/Platform.c index 1d6509ff7..f458c239f 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -9,6 +9,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "netbsd/Platform.h" #include @@ -229,7 +231,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale; } -int Platform_getMaxPid(void) { +pid_t Platform_getMaxPid(void) { // https://nxr.netbsd.org/xref/src/sys/sys/ansi.h#__pid_t // pid is assigned as a 32bit Integer. return INT32_MAX; @@ -272,9 +274,9 @@ void Platform_setMemoryValues(Meter* this) { const Machine* host = this->host; this->total = host->totalMem; this->values[MEMORY_METER_USED] = host->usedMem; - this->values[MEMORY_METER_BUFFERS] = host->buffersMem; // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm" // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_CACHE] = host->cachedMem; // this->values[MEMORY_METER_AVAILABLE] = "available memory" } diff --git a/netbsd/Platform.h b/netbsd/Platform.h index a75c766cf..a543f52dd 100644 --- a/netbsd/Platform.h +++ b/netbsd/Platform.h @@ -55,7 +55,7 @@ int Platform_getUptime(void); void Platform_getLoadAverage(double* one, double* five, double* fifteen); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* this, int cpu); @@ -115,7 +115,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -123,4 +123,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/openbsd/OpenBSDMachine.c b/openbsd/OpenBSDMachine.c index e8ff92320..0ff893b59 100644 --- a/openbsd/OpenBSDMachine.c +++ b/openbsd/OpenBSDMachine.c @@ -6,6 +6,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "openbsd/OpenBSDMachine.h" #include @@ -96,10 +98,10 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { Machine_init(super, usersTable, userId); - OpenBSDProcessList_updateCPUcount(this); + OpenBSDMachine_updateCPUcount(this); size = sizeof(this->fscale); - if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0) { + if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0 || this->fscale <= 0) { CRT_fatalError("fscale sysctl call failed"); } @@ -114,7 +116,7 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { this->cpuSpeed = -1; - return this; + return super; } void Machine_delete(Machine* super) { @@ -129,7 +131,7 @@ void Machine_delete(Machine* super) { } static void OpenBSDMachine_scanMemoryInfo(OpenBSDMachine* this) { - Machine* host = &this->super; + Machine* super = &this->super; const int uvmexp_mib[] = { CTL_VM, VM_UVMEXP }; struct uvmexp uvmexp; size_t size_uvmexp = sizeof(uvmexp); @@ -230,7 +232,7 @@ static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) { } static void OpenBSDMachine_scanCPUTime(OpenBSDMachine* this) { - Machine* host = &this->super; + Machine* super = &this->super; u_int64_t kernelTimes[CPUSTATES] = {0}; u_int64_t avg[CPUSTATES] = {0}; diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c index 0875dc00f..681d516d9 100644 --- a/openbsd/OpenBSDProcess.c +++ b/openbsd/OpenBSDProcess.c @@ -6,6 +6,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "openbsd/OpenBSDProcess.h" #include @@ -217,17 +219,20 @@ void Process_delete(Object* cast) { free(this); } -static void OpenBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) { - //const OpenBSDProcess* op = (const OpenBSDProcess*) this; +static void OpenBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const OpenBSDProcess* op = (const OpenBSDProcess*) super; + char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; - //int n = sizeof(buffer) - 1; + //size_t n = sizeof(buffer) - 1; + switch (field) { // add OpenBSD-specific fields here default: - Process_writeField(this, str, field); + Process_writeField(&op->super, str, field); return; } + RichString_appendWide(str, attr, buffer); } @@ -247,11 +252,18 @@ static int OpenBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro const ProcessClass OpenBSDProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = OpenBSDProcess_rowWriteField }, - .writeField = OpenBSDProcess_writeField, .compareByKey = OpenBSDProcess_compareByKey }; diff --git a/openbsd/OpenBSDProcessList.c b/openbsd/OpenBSDProcessTable.c similarity index 76% rename from openbsd/OpenBSDProcessList.c rename to openbsd/OpenBSDProcessTable.c index 84c833c3c..f2980a4bf 100644 --- a/openbsd/OpenBSDProcessList.c +++ b/openbsd/OpenBSDProcessTable.c @@ -1,12 +1,14 @@ /* -htop - OpenBSDProcessList.c +htop - OpenBSDProcessTable.c (C) 2014 Hisham H. Muhammad (C) 2015 Michael McConville Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "openbsd/OpenBSDProcessList.h" +#include "config.h" // IWYU pragma: keep + +#include "openbsd/OpenBSDProcessTable.h" #include #include @@ -25,30 +27,30 @@ in the source distribution for its full text. #include "Macros.h" #include "Object.h" #include "Process.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "Settings.h" #include "XUtils.h" #include "openbsd/OpenBSDMachine.h" #include "openbsd/OpenBSDProcess.h" -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - OpenBSDProcessList* this = xCalloc(1, sizeof(OpenBSDProcessList)); - ProcessList* super = (ProcessList*) this; +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + OpenBSDProcessTable* this = xCalloc(1, sizeof(OpenBSDProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(super, Class(OpenBSDProcess), host, pidMatchList); + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(OpenBSDProcess), host, pidMatchList); return this; } -void ProcessList_delete(ProcessList* super) { - OpenBSDProcessList* this = (OpenBSDProcessList*) super; - - ProcessList_done(super); +void ProcessTable_delete(Object* cast) { + OpenBSDProcessTable* this = (OpenBSDProcessTable*) cast; + ProcessTable_done(&this->super); free(this); } -static void OpenBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) { +static void OpenBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) { const int mib[] = { CTL_KERN, KERN_PROC_CWD, kproc->p_pid }; char buffer[2048]; size_t size = sizeof(buffer); @@ -68,7 +70,7 @@ static void OpenBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process free_and_xStrdup(&proc->procCwd, buffer); } -static void OpenBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) { +static void OpenBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) { Process_updateComm(proc, kproc->p_comm); /* @@ -127,8 +129,8 @@ static double getpcpu(const OpenBSDMachine* ohost, const struct kinfo_proc* kp) return 100.0 * (double)kp->p_pctcpu / ohost->fscale; } -static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { - Machine* host = this->super.host; +static void OpenBSDProcessTable_scanProcs(OpenBSDProcessTable* this) { + Machine* host = this->super.super.host; OpenBSDMachine* ohost = (OpenBSDMachine*) host; const Settings* settings = host->settings; const bool hideKernelThreads = settings->hideKernelThreads; @@ -142,7 +144,7 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { /* Ignore main threads */ if (kproc->p_tid != -1) { - Process* containingProcess = ProcessList_findProcess(&this->super, kproc->p_pid); + Process* containingProcess = ProcessTable_findProcess(&this->super, kproc->p_pid); if (containingProcess) { if (((OpenBSDProcess*)containingProcess)->addr == kproc->p_addr) continue; @@ -152,25 +154,25 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { } bool preExisting = false; - Process* proc = ProcessList_getProcess(&this->super, (kproc->p_tid == -1) ? kproc->p_pid : kproc->p_tid, &preExisting, OpenBSDProcess_new); + Process* proc = ProcessTable_getProcess(&this->super, (kproc->p_tid == -1) ? kproc->p_pid : kproc->p_tid, &preExisting, OpenBSDProcess_new); OpenBSDProcess* op = (OpenBSDProcess*) proc; if (!preExisting) { - proc->ppid = kproc->p_ppid; + Process_setParent(proc, kproc->p_ppid); + Process_setThreadGroup(proc, kproc->p_pid); proc->tpgid = kproc->p_tpgid; - proc->tgid = kproc->p_pid; proc->session = kproc->p_sid; proc->pgrp = kproc->p__pgid; proc->isKernelThread = proc->pgrp == 0; proc->isUserlandThread = kproc->p_tid != -1; proc->starttime_ctime = kproc->p_ustart_sec; Process_fillStarttimeBuffer(proc); - ProcessList_add(&this->super, proc); + ProcessTable_add(&this->super, proc); - OpenBSDProcessList_updateProcessName(ohost->kd, kproc, proc); + OpenBSDProcessTable_updateProcessName(ohost->kd, kproc, proc); if (settings->ss->flags & PROCESS_FLAG_CWD) { - OpenBSDProcessList_updateCwd(kproc, proc); + OpenBSDProcessTable_updateCwd(kproc, proc); } proc->tty_nr = kproc->p_tdev; @@ -183,7 +185,7 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { } } else { if (settings->updateProcessNames) { - OpenBSDProcessList_updateProcessName(ohost->kd, kproc, proc); + OpenBSDProcessTable_updateProcessName(ohost->kd, kproc, proc); } } @@ -231,13 +233,13 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { this->super.runningTasks++; } - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); - proc->updated = true; + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.updated = true; } } -void ProcessList_goThroughEntries(ProcessList* super) { - OpenBSDProcessList* this = (OpenBSDProcessList*) super; +void ProcessTable_goThroughEntries(ProcessTable* super) { + OpenBSDProcessTable* this = (OpenBSDProcessTable*) super; - OpenBSDProcessList_scanProcs(this); + OpenBSDProcessTable_scanProcs(this); } diff --git a/openbsd/OpenBSDProcessList.h b/openbsd/OpenBSDProcessTable.h similarity index 50% rename from openbsd/OpenBSDProcessList.h rename to openbsd/OpenBSDProcessTable.h index 8a03fecb8..f911a10be 100644 --- a/openbsd/OpenBSDProcessList.h +++ b/openbsd/OpenBSDProcessTable.h @@ -1,7 +1,7 @@ -#ifndef HEADER_OpenBSDProcessList -#define HEADER_OpenBSDProcessList +#ifndef HEADER_OpenBSDProcessTable +#define HEADER_OpenBSDProcessTable /* -htop - OpenBSDProcessList.h +htop - OpenBSDProcessTable.h (C) 2014 Hisham H. Muhammad (C) 2015 Michael McConville Released under the GNU GPLv2+, see the COPYING file @@ -11,11 +11,11 @@ in the source distribution for its full text. #include #include -#include "ProcessList.h" +#include "ProcessTable.h" -typedef struct OpenBSDProcessList_ { - ProcessList super; -} OpenBSDProcessList; +typedef struct OpenBSDProcessTable_ { + ProcessTable super; +} OpenBSDProcessTable; #endif diff --git a/openbsd/Platform.c b/openbsd/Platform.c index 065940892..a8b5d212d 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -6,6 +6,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "openbsd/Platform.h" #include @@ -175,7 +177,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale; } -int Platform_getMaxPid(void) { +pid_t Platform_getMaxPid(void) { return 2 * THREAD_PID_OFFSET; } @@ -229,9 +231,9 @@ void Platform_setMemoryValues(Meter* this) { usedMem -= buffersMem + cachedMem; this->total = host->totalMem; 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_COMPRESSED] = "compressed memory, like zswap on linux" + this->values[MEMORY_METER_BUFFERS] = buffersMem; this->values[MEMORY_METER_CACHE] = cachedMem; // this->values[MEMORY_METER_AVAILABLE] = "available memory" } diff --git a/openbsd/Platform.h b/openbsd/Platform.h index f357006cc..339616c11 100644 --- a/openbsd/Platform.h +++ b/openbsd/Platform.h @@ -47,7 +47,7 @@ int Platform_getUptime(void); void Platform_getLoadAverage(double* one, double* five, double* fifteen); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* this, unsigned int cpu); @@ -109,7 +109,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -117,4 +117,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/pcp-htop.5.in b/pcp-htop.5.in index dbc0dfb25..ebb47487e 100644 --- a/pcp-htop.5.in +++ b/pcp-htop.5.in @@ -1,4 +1,4 @@ -.TH "PCP-HTOP" "5" "2023" "@PACKAGE_STRING@" "File Formats" +.TH "PCP-HTOP" "5" "2024" "@PACKAGE_STRING@" "File Formats" .SH "NAME" \f3pcp-htop\f1 \- pcp-htop configuration file .SH "DESCRIPTION" diff --git a/pcp/InDomTable.c b/pcp/InDomTable.c new file mode 100644 index 000000000..2f9a50082 --- /dev/null +++ b/pcp/InDomTable.c @@ -0,0 +1,99 @@ +/* +htop - InDomTable.c +(C) 2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/InDomTable.h" + +#include +#include +#include + +#include "CRT.h" +#include "DynamicColumn.h" +#include "Hashtable.h" +#include "Macros.h" +#include "Platform.h" +#include "Table.h" +#include "Vector.h" +#include "XUtils.h" + +#include "pcp/Instance.h" +#include "pcp/Metric.h" +#include "pcp/PCPDynamicColumn.h" + + +InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey) { + InDomTable* this = xCalloc(1, sizeof(InDomTable)); + Object_setClass(this, Class(InDomTable)); + this->metricKey = metricKey; + this->id = indom; + + Table* super = &this->super; + Table_init(super, Class(Row), host); + + return this; +} + +void InDomTable_done(InDomTable* this) { + Table_done(&this->super); +} + +static void InDomTable_delete(Object* cast) { + InDomTable* this = (InDomTable*) cast; + InDomTable_done(this); + free(this); +} + +static Instance* InDomTable_getInstance(InDomTable* this, int id, bool* preExisting) { + const Table* super = &this->super; + Instance* inst = (Instance*) Hashtable_get(super->table, id); + *preExisting = inst != NULL; + if (inst) { + assert(Vector_indexOf(super->rows, inst, Row_idEqualCompare) != -1); + assert(Instance_getId(inst) == id); + } else { + inst = Instance_new(super->host, this); + assert(inst->name == NULL); + Instance_setId(inst, id); + } + return inst; +} + +static void InDomTable_goThroughEntries(InDomTable* this) { + Table* super = &this->super; + + /* for every instance ... */ + int instid = -1, offset = -1; + while (Metric_iterate(this->metricKey, &instid, &offset)) { + bool preExisting; + Instance* inst = InDomTable_getInstance(this, instid, &preExisting); + inst->offset = offset >= 0 ? offset : 0; + + Row* row = (Row*) inst; + if (!preExisting) + Table_add(super, row); + row->updated = true; + row->show = true; + } +} + +static void InDomTable_iterateEntries(Table* super) { + InDomTable* this = (InDomTable*) super; + InDomTable_goThroughEntries(this); +} + +const TableClass InDomTable_class = { + .super = { + .extends = Class(Table), + .delete = InDomTable_delete, + }, + .prepare = Table_prepareEntries, + .iterate = InDomTable_iterateEntries, + .cleanup = Table_cleanupEntries, +}; diff --git a/pcp/InDomTable.h b/pcp/InDomTable.h new file mode 100644 index 000000000..f44a39f64 --- /dev/null +++ b/pcp/InDomTable.h @@ -0,0 +1,34 @@ +#ifndef HEADER_InDomTable +#define HEADER_InDomTable +/* +htop - InDomTable.h +(C) 2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + +#include "Platform.h" +#include "Table.h" + + +typedef struct InDomTable_ { + Table super; + pmInDom id; /* shared by metrics in the table */ + unsigned int metricKey; /* representative metric using this indom */ +} InDomTable; + +extern const TableClass InDomTable_class; + +InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey); + +void InDomTable_done(InDomTable* this); + +RowField RowField_keyAt(const Settings* settings, int at); + +void InDomTable_scan(Table* super); + +#endif diff --git a/pcp/Instance.c b/pcp/Instance.c new file mode 100644 index 000000000..8ae90512f --- /dev/null +++ b/pcp/Instance.c @@ -0,0 +1,163 @@ +/* +htop - Instance.c +(C) 2022-2023 Sohaib Mohammed +(C) 2022-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/Instance.h" + +#include +#include + +#include "CRT.h" +#include "DynamicColumn.h" +#include "DynamicScreen.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Macros.h" +#include "Metric.h" +#include "Platform.h" +#include "PCPDynamicColumn.h" +#include "PCPDynamicScreen.h" +#include "Row.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/InDomTable.h" +#include "pcp/Metric.h" + + +Instance* Instance_new(const Machine* host, const InDomTable* indom) { + Instance* this = xCalloc(1, sizeof(Instance)); + Object_setClass(this, Class(Instance)); + + Row* super = &this->super; + Row_init(super, host); + + this->indom = indom; + + return this; +} + +void Instance_done(Instance* this) { + if (this->name) + free(this->name); + Row_done(&this->super); +} + +static void Instance_delete(Object* cast) { + Instance* this = (Instance*) cast; + Instance_done(this); + free(this); +} + +static void Instance_writeField(const Row* super, RichString* str, RowField field) { + const Instance* this = (const Instance*) super; + int instid = Instance_getId(this); + + const Settings* settings = super->host->settings; + DynamicColumn* column = Hashtable_get(settings->dynamicColumns, field); + PCPDynamicColumn* cp = (PCPDynamicColumn*) column; + if (!cp) + return; + + pmAtomValue atom; + pmAtomValue* ap = &atom; + const pmDesc* descp = Metric_desc(cp->id); + if (!Metric_instance(cp->id, instid, this->offset, ap, descp->type)) + ap = NULL; + + PCPDynamicColumn_writeAtomValue(cp, str, settings, cp->id, instid, descp, ap); + + if (ap && descp->type == PM_TYPE_STRING) + free(ap->cp); +} + +static const char* Instance_externalName(Row* super) { + Instance* this = (Instance*) super; + + if (!this->name) + /* ignore any failure here - its safe and we try again next time */ + (void)pmNameInDom(InDom_getId(this), Instance_getId(this), &this->name); + return this->name; +} + +static int Instance_compareByKey(const Row* v1, const Row* v2, int key) { + const Instance* i1 = (const Instance*)v1; + const Instance* i2 = (const Instance*)v2; + + if (key < 0) + return 0; + + Hashtable* dc = Platform_dynamicColumns(); + const PCPDynamicColumn* column = Hashtable_get(dc, key); + if (!column) + return -1; + + size_t metric = column->id; + unsigned int type = Metric_type(metric); + + pmAtomValue atom1 = {0}, atom2 = {0}; + if (!Metric_instance(metric, i1->offset, i1->offset, &atom1, type) || + !Metric_instance(metric, i2->offset, i2->offset, &atom2, type)) { + if (type == PM_TYPE_STRING) { + free(atom1.cp); + free(atom2.cp); + } + return -1; + } + + switch (type) { + case PM_TYPE_STRING: { + int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp); + free(atom2.cp); + free(atom1.cp); + return cmp; + } + case PM_TYPE_32: + return SPACESHIP_NUMBER(atom2.l, atom1.l); + case PM_TYPE_U32: + return SPACESHIP_NUMBER(atom2.ul, atom1.ul); + case PM_TYPE_64: + return SPACESHIP_NUMBER(atom2.ll, atom1.ll); + case PM_TYPE_U64: + return SPACESHIP_NUMBER(atom2.ull, atom1.ull); + case PM_TYPE_FLOAT: + return SPACESHIP_NUMBER(atom2.f, atom1.f); + case PM_TYPE_DOUBLE: + return SPACESHIP_NUMBER(atom2.d, atom1.d); + default: + break; + } + + return 0; +} + +static int Instance_compare(const void* v1, const void* v2) { + const Instance* i1 = (const Instance*)v1; + const Instance* i2 = (const Instance*)v2; + const ScreenSettings* ss = i1->super.host->settings->ss; + RowField key = ScreenSettings_getActiveSortKey(ss); + int result = Instance_compareByKey(v1, v2, key); + + // Implement tie-breaker (needed to make tree mode more stable) + if (!result) + return SPACESHIP_NUMBER(Instance_getId(i1), Instance_getId(i2)); + + return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result; +} + +const RowClass Instance_class = { + .super = { + .extends = Class(Row), + .display = Row_display, + .delete = Instance_delete, + .compare = Instance_compare, + }, + .sortKeyString = Instance_externalName, + .writeField = Instance_writeField, +}; diff --git a/pcp/Instance.h b/pcp/Instance.h new file mode 100644 index 000000000..aefd64266 --- /dev/null +++ b/pcp/Instance.h @@ -0,0 +1,37 @@ +#ifndef HEADER_Instance +#define HEADER_Instance +/* +htop - Instance.h +(C) 2022-2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Hashtable.h" +#include "Object.h" +#include "Platform.h" +#include "Row.h" + + +typedef struct Instance_ { + Row super; + + char* name; /* external instance name */ + const struct InDomTable_* indom; /* instance domain */ + + /* default result offset to use for searching metrics with instances */ + unsigned int offset; +} Instance; + +#define InDom_getId(i_) ((i_)->indom->id) +#define Instance_getId(i_) ((i_)->super.id) +#define Instance_setId(i_, id_) ((i_)->super.id = (id_)) + +extern const RowClass Instance_class; + +Instance* Instance_new(const Machine* host, const struct InDomTable_* indom); + +void Instance_done(Instance* this); + +#endif diff --git a/pcp/PCPMetric.c b/pcp/Metric.c similarity index 74% rename from pcp/PCPMetric.c rename to pcp/Metric.c index 606a5df06..4a3c858d0 100644 --- a/pcp/PCPMetric.c +++ b/pcp/Metric.c @@ -1,5 +1,5 @@ /* -htop - PCPMetric.c +htop - Metric.c (C) 2020-2021 htop dev team (C) 2020-2021 Red Hat, Inc. Released under the GNU GPLv2+, see the COPYING file @@ -8,8 +8,9 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep -#include "pcp/PCPMetric.h" +#include "pcp/Metric.h" +#include #include #include #include @@ -21,15 +22,15 @@ in the source distribution for its full text. extern Platform* pcp; -const pmDesc* PCPMetric_desc(PCPMetric metric) { +const pmDesc* Metric_desc(Metric metric) { return &pcp->descs[metric]; } -int PCPMetric_type(PCPMetric metric) { +int Metric_type(Metric metric) { return pcp->descs[metric].type; } -pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type) { +pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type) { if (pcp->result == NULL) return NULL; @@ -54,14 +55,14 @@ pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, in return atom; } -int PCPMetric_instanceCount(PCPMetric metric) { +int Metric_instanceCount(Metric metric) { pmValueSet* vset = pcp->result->vset[metric]; if (vset) return vset->numval; return 0; } -int PCPMetric_instanceOffset(PCPMetric metric, int inst) { +int Metric_instanceOffset(Metric metric, int inst) { pmValueSet* vset = pcp->result->vset[metric]; if (!vset || vset->numval <= 0) return 0; @@ -74,7 +75,7 @@ int PCPMetric_instanceOffset(PCPMetric metric, int inst) { return 0; } -static pmAtomValue* PCPMetric_extract(PCPMetric metric, int inst, int offset, pmValueSet* vset, pmAtomValue* atom, int type) { +static pmAtomValue* Metric_extract(Metric metric, int inst, int offset, pmValueSet* vset, pmAtomValue* atom, int type) { /* extract value (using requested type) of given metric instance */ const pmDesc* desc = &pcp->descs[metric]; @@ -89,7 +90,7 @@ static pmAtomValue* PCPMetric_extract(PCPMetric metric, int inst, int offset, pm return atom; } -pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type) { +pmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type) { pmValueSet* vset = pcp->result->vset[metric]; if (!vset || vset->numval <= 0) @@ -97,12 +98,12 @@ pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomVa /* fast-path using heuristic offset based on expected location */ if (offset >= 0 && offset < vset->numval && inst == vset->vlist[offset].inst) - return PCPMetric_extract(metric, inst, offset, vset, atom, type); + return Metric_extract(metric, inst, offset, vset, atom, type); /* slow-path using a linear search for the requested instance */ for (int i = 0; i < vset->numval; i++) { if (inst == vset->vlist[i].inst) - return PCPMetric_extract(metric, inst, i, vset, atom, type); + return Metric_extract(metric, inst, i, vset, atom, type); } return NULL; } @@ -113,7 +114,7 @@ pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomVa * * Start it off by passing offset -1 into the routine. */ -bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp) { +bool Metric_iterate(Metric metric, int* instp, int* offsetp) { if (!pcp->result) return false; @@ -132,15 +133,15 @@ bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp) { } /* Switch on/off a metric for value fetching (sampling) */ -void PCPMetric_enable(PCPMetric metric, bool enable) { +void Metric_enable(Metric metric, bool enable) { pcp->fetch[metric] = enable ? pcp->pmids[metric] : PM_ID_NULL; } -bool PCPMetric_enabled(PCPMetric metric) { +bool Metric_enabled(Metric metric) { return pcp->fetch[metric] != PM_ID_NULL; } -void PCPMetric_enableThreads(void) { +void Metric_enableThreads(void) { pmValueSet* vset = xCalloc(1, sizeof(pmValueSet)); vset->vlist[0].inst = PM_IN_NULL; vset->vlist[0].value.lval = 1; @@ -159,7 +160,7 @@ void PCPMetric_enableThreads(void) { pmFreeResult(result); } -bool PCPMetric_fetch(struct timeval* timestamp) { +bool Metric_fetch(struct timeval* timestamp) { if (pcp->result) { pmFreeResult(pcp->result); pcp->result = NULL; @@ -178,3 +179,22 @@ bool PCPMetric_fetch(struct timeval* timestamp) { *timestamp = pcp->result->timestamp; return true; } + +void Metric_externalName(Metric metric, int inst, char** externalName) { + const pmDesc* desc = &pcp->descs[metric]; + /* ignore a failure here - its safe to do so */ + (void)pmNameInDom(desc->indom, inst, externalName); +} + +int Metric_lookupText(const char* metric, char** desc) { + pmID pmid; + int sts; + + sts = pmLookupName(1, &metric, &pmid); + if (sts < 0) + return sts; + + if (pmLookupText(pmid, PM_TEXT_ONELINE, desc) >= 0) + (*desc)[0] = toupper((*desc)[0]); /* UI consistency */ + return 0; +} diff --git a/pcp/PCPMetric.h b/pcp/Metric.h similarity index 89% rename from pcp/PCPMetric.h rename to pcp/Metric.h index e89a0a4c7..e72f6e19b 100644 --- a/pcp/PCPMetric.h +++ b/pcp/Metric.h @@ -1,7 +1,7 @@ -#ifndef HEADER_PCPMetric -#define HEADER_PCPMetric +#ifndef HEADER_Metric +#define HEADER_Metric /* -htop - PCPMetric.h +htop - Metric.h (C) 2020-2021 htop dev team (C) 2020-2021 Red Hat, Inc. Released under the GNU GPLv2+, see the COPYING file @@ -22,7 +22,7 @@ in the source distribution for its full text. #undef PACKAGE_BUGREPORT -typedef enum PCPMetric_ { +typedef enum Metric_ { PCP_CONTROL_THREADS, /* proc.control.perclient.threads */ PCP_HINV_NCPU, /* hinv.ncpu */ @@ -93,6 +93,8 @@ typedef enum PCPMetric_ { PCP_ZRAM_CAPACITY, /* zram.capacity */ PCP_ZRAM_ORIGINAL, /* zram.mm_stat.data_size.original */ PCP_ZRAM_COMPRESSED, /* zram.mm_stat.data_size.compressed */ + PCP_MEM_ZSWAP, /* mem.util.zswap */ + PCP_MEM_ZSWAPPED, /* mem.util.zswapped */ PCP_VFS_FILES_COUNT, /* vfs.files.count */ PCP_VFS_FILES_MAX, /* vfs.files.max */ @@ -156,28 +158,32 @@ typedef enum PCPMetric_ { PCP_PROC_SMAPS_SWAPPSS, /* proc.smaps.swappss */ PCP_METRIC_COUNT /* total metric count */ -} PCPMetric; +} Metric; -void PCPMetric_enable(PCPMetric metric, bool enable); +void Metric_enable(Metric metric, bool enable); -bool PCPMetric_enabled(PCPMetric metric); +bool Metric_enabled(Metric metric); -void PCPMetric_enableThreads(void); +void Metric_enableThreads(void); -bool PCPMetric_fetch(struct timeval* timestamp); +bool Metric_fetch(struct timeval* timestamp); -bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp); +bool Metric_iterate(Metric metric, int* instp, int* offsetp); -pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type); +pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type); -const pmDesc* PCPMetric_desc(PCPMetric metric); +const pmDesc* Metric_desc(Metric metric); -int PCPMetric_type(PCPMetric metric); +int Metric_type(Metric metric); -int PCPMetric_instanceCount(PCPMetric metric); +int Metric_instanceCount(Metric metric); -int PCPMetric_instanceOffset(PCPMetric metric, int inst); +int Metric_instanceOffset(Metric metric, int inst); -pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type); +pmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type); + +void Metric_externalName(Metric metric, int inst, char** externalName); + +int Metric_lookupText(const char* metric, char** desc); #endif diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c index 23b896e6f..b0bd03e1b 100644 --- a/pcp/PCPDynamicColumn.c +++ b/pcp/PCPDynamicColumn.c @@ -1,8 +1,7 @@ /* htop - PCPDynamicColumn.c -(C) 2021 Sohaib Mohammed -(C) 2021 htop dev team -(C) 2021 Red Hat, Inc. +(C) 2021-2023 Sohaib Mohammed +(C) 2021-2023 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ @@ -24,12 +23,13 @@ in the source distribution for its full text. #include "Macros.h" #include "Platform.h" #include "Process.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "RichString.h" #include "XUtils.h" +#include "linux/CGroupUtils.h" +#include "pcp/Metric.h" #include "pcp/PCPProcess.h" -#include "pcp/PCPMetric.h" static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) { @@ -49,6 +49,10 @@ static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicCol } static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) { + /* pmLookupText */ + if (!column->super.description) + Metric_lookupText(value, &column->super.description); + /* lookup a dynamic metric with this name, else create */ if (PCPDynamicColumn_addMetric(columns, column) == false) return; @@ -108,6 +112,10 @@ static bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) { static PCPDynamicColumn* PCPDynamicColumn_new(PCPDynamicColumns* columns, const char* name) { PCPDynamicColumn* column = xCalloc(1, sizeof(*column)); String_safeStrncpy(column->super.name, name, sizeof(column->super.name)); + column->super.enabled = false; + column->percent = false; + column->instances = false; + column->defaultEnabled = true; size_t id = columns->count + LAST_PROCESSFIELD; Hashtable_put(columns->table, id, column); @@ -160,6 +168,14 @@ static void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* p free_and_xStrdup(&column->super.description, value); } else if (value && column && String_eq(key, "width")) { column->super.width = strtoul(value, NULL, 10); + } else if (value && column && String_eq(key, "format")) { + free_and_xStrdup(&column->format, value); + } else if (value && column && String_eq(key, "instances")) { + if (String_eq(value, "True") || String_eq(value, "true")) + column->instances = true; + } else if (value && column && (String_eq(key, "default") || String_eq(key, "enabled"))) { + if (String_eq(value, "False") || String_eq(value, "false")) + column->defaultEnabled = false; } else if (value && column && String_eq(key, "metric")) { PCPDynamicColumn_parseMetric(columns, column, path, lineno, value); } @@ -233,88 +249,239 @@ void PCPDynamicColumns_init(PCPDynamicColumns* columns) { free(path); } +void PCPDynamicColumn_done(PCPDynamicColumn* this) { + DynamicColumn_done(&this->super); + free(this->metricName); + free(this->format); +} + static void PCPDynamicColumns_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { PCPDynamicColumn* column = (PCPDynamicColumn*) value; - free(column->metricName); - free(column->super.heading); - free(column->super.caption); - free(column->super.description); + PCPDynamicColumn_done(column); } void PCPDynamicColumns_done(Hashtable* table) { Hashtable_foreach(table, PCPDynamicColumns_free, NULL); } -void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) { - const PCPProcess* pp = (const PCPProcess*) proc; - unsigned int type = PCPMetric_type(this->id); +static void PCPDynamicColumn_setupWidth(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicColumn* column = (PCPDynamicColumn*) value; - pmAtomValue atom; - if (!PCPMetric_instance(this->id, proc->pid, pp->offset, &atom, type)) { - RichString_appendAscii(str, CRT_colors[METER_VALUE_ERROR], "no data"); + /* calculate column size based on config file and metric units */ + const pmDesc* desc = Metric_desc(column->id); + + if (column->instances || desc->type == PM_TYPE_STRING) { + column->super.width = column->width; + if (column->super.width == 0) + column->super.width = -16; return; } - int width = this->super.width; - if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) - width = DYNAMIC_DEFAULT_COLUMN_WIDTH; - int abswidth = abs(width); - if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) { - abswidth = DYNAMIC_MAX_COLUMN_WIDTH; - width = -abswidth; + if (column->format) { + if (strcmp(column->format, "percent") == 0) { + column->super.width = 5; + return; + } + if (strcmp(column->format, "process") == 0) { + column->super.width = Process_pidDigits; + return; + } } - char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1]; - int attr = CRT_colors[DEFAULT_COLOR]; + if (column->width) { + column->super.width = column->width; + return; + } + + pmUnits units = desc->units; + if (units.dimSpace && units.dimTime) + column->super.width = 11; // Row_printRate + else if (units.dimSpace) + column->super.width = 5; // Row_printBytes + else if (units.dimCount && units.dimTime) + column->super.width = 11; // Row_printCount + else if (units.dimTime) + column->super.width = 8; // Row_printTime + else + column->super.width = 11; // Row_printCount +} + +void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns) { + Hashtable_foreach(columns->table, PCPDynamicColumn_setupWidth, NULL); +} + +/* normalize output units to bytes and seconds */ +static int PCPDynamicColumn_normalize(const pmDesc* desc, const pmAtomValue* ap, double* value) { + /* form normalized units based on the original metric units */ + pmUnits units = desc->units; + if (units.dimTime) + units.scaleTime = PM_TIME_SEC; + if (units.dimSpace) + units.scaleSpace = PM_SPACE_BYTE; + if (units.dimCount) + units.scaleCount = PM_COUNT_ONE; + + pmAtomValue atom; + int sts, type = desc->type; + if ((sts = pmConvScale(type, ap, &desc->units, &atom, &units)) < 0) + return sts; + switch (type) { - case PM_TYPE_STRING: - attr = CRT_colors[PROCESS_SHADOW]; - Process_printLeftAlignedField(str, attr, atom.cp, abswidth); - free(atom.cp); - break; case PM_TYPE_32: - xSnprintf(buffer, sizeof(buffer), "%*d ", width, atom.l); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.l; break; case PM_TYPE_U32: - xSnprintf(buffer, sizeof(buffer), "%*u ", width, atom.ul); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.ul; break; case PM_TYPE_64: - xSnprintf(buffer, sizeof(buffer), "%*lld ", width, (long long) atom.ll); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.ll; break; case PM_TYPE_U64: - xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long) atom.ull); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.ull; break; case PM_TYPE_FLOAT: - xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, (double) atom.f); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.f; break; case PM_TYPE_DOUBLE: - xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, atom.d); - RichString_appendAscii(str, attr, buffer); + *value = atom.d; break; default: - attr = CRT_colors[METER_VALUE_ERROR]; - RichString_appendAscii(str, attr, "no type"); - break; + return PM_ERR_CONV; + } + + return 0; +} + +void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const pmDesc* desc, const void* atom) { + const pmAtomValue* atomvalue = (const pmAtomValue*) atom; + char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /*space*/ 1 + /*null*/ 1]; + int attr = CRT_colors[DEFAULT_COLOR]; + int width = column->super.width; + int n; + + if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) + width = DYNAMIC_DEFAULT_COLUMN_WIDTH; + int abswidth = abs(width); + if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) { + abswidth = DYNAMIC_MAX_COLUMN_WIDTH; + width = -abswidth; + } + + if (atomvalue == NULL) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A"); + RichString_appendnAscii(str, CRT_colors[PROCESS_SHADOW], buffer, n); + return; + } + + /* deal with instance names and metrics with string values first */ + if (column->instances || desc->type == PM_TYPE_STRING) { + char* value = NULL; + char* dupd1 = NULL; + if (column->instances) { + attr = CRT_colors[DYNAMIC_GRAY]; + Metric_externalName(metric, instance, &dupd1); + value = dupd1; + } else { + attr = CRT_colors[DYNAMIC_GREEN]; + value = atomvalue->cp; + } + if (column->format && value) { + char* dupd2 = NULL; + if (strcmp(column->format, "command") == 0) + attr = CRT_colors[PROCESS_COMM]; + else if (strcmp(column->format, "process") == 0) + attr = CRT_colors[PROCESS_SHADOW]; + else if (strcmp(column->format, "device") == 0 && strncmp(value, "/dev/", 5) == 0) + value += 5; + else if (strcmp(column->format, "cgroup") == 0 && (dupd2 = CGroup_filterName(value))) + value = dupd2; + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value); + if (dupd2) + free(dupd2); + } else if (value) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value); + } else { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A"); + } + if (dupd1) + free(dupd1); + RichString_appendnAscii(str, attr, buffer, n); + return; + } + + /* deal with any numeric value - first, normalize units to bytes/seconds */ + double value; + if (PCPDynamicColumn_normalize(desc, atomvalue, &value) < 0) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "no conv"); + RichString_appendnAscii(str, CRT_colors[METER_VALUE_ERROR], buffer, n); + return; + } + + if (column->format) { + if (strcmp(column->format, "percent") == 0) { + n = Row_printPercentage(value, buffer, sizeof(buffer), width, &attr); + RichString_appendnAscii(str, attr, buffer, n); + return; + } + if (strcmp(column->format, "process") == 0) { + n = xSnprintf(buffer, sizeof(buffer), "%*d ", Row_pidDigits, (int)value); + RichString_appendnAscii(str, attr, buffer, n); + return; + } } + + /* width overrides unit suffix and coloring; too complex for a corner case */ + if (column->width) { + if (value - (unsigned long long)value > 0) /* display floating point */ + n = xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, value); + else /* display as integer */ + n = xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long)value); + RichString_appendnAscii(str, CRT_colors[PROCESS], buffer, n); + return; + } + + bool coloring = settings->highlightMegabytes; + pmUnits units = desc->units; + if (units.dimSpace && units.dimTime) + Row_printRate(str, value, coloring); + else if (units.dimSpace) + Row_printBytes(str, value, coloring); + else if (units.dimCount) + Row_printCount(str, value, coloring); + else if (units.dimTime) + Row_printTime(str, value / 10 /* hundreds of a second */, coloring); + else + Row_printCount(str, value, 0); /* e.g. PID */ +} + +void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) { + const Settings* settings = proc->super.host->settings; + const PCPProcess* pp = (const PCPProcess*) proc; + const pmDesc* desc = Metric_desc(this->id); + pid_t pid = Process_getPid(proc); + + pmAtomValue atom; + pmAtomValue* ap = &atom; + if (!Metric_instance(this->id, pid, pp->offset, ap, desc->type)) + ap = NULL; + + PCPDynamicColumn_writeAtomValue(this, str, settings, this->id, pid, desc, ap); } int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) { - const PCPDynamicColumn* column = Hashtable_get(p1->super.host->settings->dynamicColumns, key); + const Process* proc = &p1->super; + const Settings* settings = proc->super.host->settings; + const PCPDynamicColumn* column = Hashtable_get(settings->dynamicColumns, key); if (!column) return -1; size_t metric = column->id; - unsigned int type = PCPMetric_type(metric); + unsigned int type = Metric_type(metric); pmAtomValue atom1 = {0}, atom2 = {0}; - if (!PCPMetric_instance(metric, p1->super.pid, p1->offset, &atom1, type) || - !PCPMetric_instance(metric, p2->super.pid, p2->offset, &atom2, type)) { + if (!Metric_instance(metric, Process_getPid(&p1->super), p1->offset, &atom1, type) || + !Metric_instance(metric, Process_getPid(&p2->super), p2->offset, &atom2, type)) { if (type == PM_TYPE_STRING) { free(atom1.cp); free(atom2.cp); @@ -338,11 +505,12 @@ int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, Pr case PM_TYPE_U64: return SPACESHIP_NUMBER(atom2.ull, atom1.ull); case PM_TYPE_FLOAT: - return SPACESHIP_NUMBER(atom2.f, atom1.f); + return compareRealNumbers(atom2.f, atom1.f); case PM_TYPE_DOUBLE: - return SPACESHIP_NUMBER(atom2.d, atom1.d); + return compareRealNumbers(atom2.d, atom1.d); default: break; } + return -1; } diff --git a/pcp/PCPDynamicColumn.h b/pcp/PCPDynamicColumn.h index d0ffe7192..ade782b7f 100644 --- a/pcp/PCPDynamicColumn.h +++ b/pcp/PCPDynamicColumn.h @@ -1,5 +1,11 @@ #ifndef HEADER_PCPDynamicColumn #define HEADER_PCPDynamicColumn +/* +htop - PCPDynamicColumn.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include @@ -11,15 +17,22 @@ #include "pcp/PCPProcess.h" +struct pmDesc; + typedef struct PCPDynamicColumn_ { DynamicColumn super; char* metricName; + char* format; size_t id; /* identifier for metric array lookups */ + int width; /* optional width from configuration file */ + bool defaultEnabled; /* default enabled in dynamic screen */ + bool percent; + bool instances; /* an instance *names* column, not values */ } PCPDynamicColumn; typedef struct PCPDynamicColumns_ { Hashtable* table; - size_t count; /* count of dynamic meters discovered by scan */ + size_t count; /* count of dynamic columns discovered by scan */ size_t offset; /* start offset into the Platform metric array */ size_t cursor; /* identifier allocator for each new metric used */ } PCPDynamicColumns; @@ -28,8 +41,14 @@ void PCPDynamicColumns_init(PCPDynamicColumns* columns); void PCPDynamicColumns_done(Hashtable* table); +void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns); + void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str); +void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const struct pmDesc* desc, const void* atomvalue); + int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key); +void PCPDynamicColumn_done(PCPDynamicColumn* this); + #endif diff --git a/pcp/PCPDynamicMeter.c b/pcp/PCPDynamicMeter.c index e89998813..11df5f0a4 100644 --- a/pcp/PCPDynamicMeter.c +++ b/pcp/PCPDynamicMeter.c @@ -5,6 +5,7 @@ htop - PCPDynamicMeter.c Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ + #include "config.h" // IWYU pragma: keep #include "pcp/PCPDynamicMeter.h" @@ -25,7 +26,7 @@ in the source distribution for its full text. #include "RichString.h" #include "XUtils.h" -#include "pcp/PCPMetric.h" +#include "pcp/Metric.h" static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) { @@ -309,7 +310,7 @@ void PCPDynamicMeters_done(Hashtable* table) { void PCPDynamicMeter_enable(PCPDynamicMeter* this) { for (size_t i = 0; i < this->totalMetrics; i++) - PCPMetric_enable(this->metrics[i].id, true); + Metric_enable(this->metrics[i].id, true); } void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { @@ -322,10 +323,10 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { buffer[bytes++] = '/'; /* separator */ PCPDynamicMetric* metric = &this->metrics[i]; - const pmDesc* desc = PCPMetric_desc(metric->id); + const pmDesc* desc = Metric_desc(metric->id); pmAtomValue atom, raw; - if (!PCPMetric_values(metric->id, &raw, 1, desc->type)) { + if (!Metric_values(metric->id, &raw, 1, desc->type)) { bytes--; /* clear the separator */ continue; } @@ -350,27 +351,27 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { break; case PM_TYPE_32: bytes += conv.dimSpace ? - Meter_humanUnit(buffer + bytes, atom.l, size - bytes) : + Meter_humanUnit(buffer + bytes, (double) atom.l, size - bytes) : xSnprintf(buffer + bytes, size - bytes, "%d", atom.l); break; case PM_TYPE_U32: bytes += conv.dimSpace ? - Meter_humanUnit(buffer + bytes, atom.ul, size - bytes) : + Meter_humanUnit(buffer + bytes, (double) atom.ul, size - bytes) : xSnprintf(buffer + bytes, size - bytes, "%u", atom.ul); break; case PM_TYPE_64: bytes += conv.dimSpace ? - Meter_humanUnit(buffer + bytes, atom.ll, size - bytes) : + Meter_humanUnit(buffer + bytes, (double) atom.ll, size - bytes) : xSnprintf(buffer + bytes, size - bytes, "%lld", (long long) atom.ll); break; case PM_TYPE_U64: bytes += conv.dimSpace ? - Meter_humanUnit(buffer + bytes, atom.ull, size - bytes) : + Meter_humanUnit(buffer + bytes, (double) atom.ull, size - bytes) : xSnprintf(buffer + bytes, size - bytes, "%llu", (unsigned long long) atom.ull); break; case PM_TYPE_FLOAT: bytes += conv.dimSpace ? - Meter_humanUnit(buffer + bytes, atom.f, size - bytes) : + Meter_humanUnit(buffer + bytes, (double) atom.f, size - bytes) : xSnprintf(buffer + bytes, size - bytes, "%.2f", (double) atom.f); break; case PM_TYPE_DOUBLE: @@ -381,9 +382,11 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { default: break; } + if (saved != bytes && metric->suffix) bytes += xSnprintf(buffer + bytes, size - bytes, "%s", metric->suffix); } + if (!bytes) xSnprintf(buffer, size, "no data"); } @@ -393,11 +396,11 @@ void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* met for (size_t i = 0; i < this->totalMetrics; i++) { PCPDynamicMetric* metric = &this->metrics[i]; - const pmDesc* desc = PCPMetric_desc(metric->id); + const pmDesc* desc = Metric_desc(metric->id); pmAtomValue atom, raw; char buffer[64]; - if (!PCPMetric_values(metric->id, &raw, 1, desc->type)) + if (!Metric_values(metric->id, &raw, 1, desc->type)) continue; pmUnits conv = desc->units; /* convert to canonical units */ @@ -426,27 +429,27 @@ void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* met break; case PM_TYPE_32: len = conv.dimSpace ? - Meter_humanUnit(buffer, atom.l, sizeof(buffer)) : + Meter_humanUnit(buffer, (double) atom.l, sizeof(buffer)) : xSnprintf(buffer, sizeof(buffer), "%d", atom.l); break; case PM_TYPE_U32: len = conv.dimSpace ? - Meter_humanUnit(buffer, atom.ul, sizeof(buffer)) : + Meter_humanUnit(buffer, (double) atom.ul, sizeof(buffer)) : xSnprintf(buffer, sizeof(buffer), "%u", atom.ul); break; case PM_TYPE_64: len = conv.dimSpace ? - Meter_humanUnit(buffer, atom.ll, sizeof(buffer)) : + Meter_humanUnit(buffer, (double) atom.ll, sizeof(buffer)) : xSnprintf(buffer, sizeof(buffer), "%lld", (long long) atom.ll); break; case PM_TYPE_U64: len = conv.dimSpace ? - Meter_humanUnit(buffer, atom.ull, sizeof(buffer)) : + Meter_humanUnit(buffer, (double) atom.ull, sizeof(buffer)) : xSnprintf(buffer, sizeof(buffer), "%llu", (unsigned long long) atom.ull); break; case PM_TYPE_FLOAT: len = conv.dimSpace ? - Meter_humanUnit(buffer, atom.f, sizeof(buffer)) : + Meter_humanUnit(buffer, (double) atom.f, sizeof(buffer)) : xSnprintf(buffer, sizeof(buffer), "%.2f", (double) atom.f); break; case PM_TYPE_DOUBLE: @@ -457,12 +460,14 @@ void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* met default: break; } + if (len) { RichString_appendnAscii(out, CRT_colors[metric->color], buffer, len); if (metric->suffix) RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->suffix); } } + if (nodata) RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); } diff --git a/pcp/PCPDynamicMeter.h b/pcp/PCPDynamicMeter.h index 0e5ddd2b5..3a72d13c3 100644 --- a/pcp/PCPDynamicMeter.h +++ b/pcp/PCPDynamicMeter.h @@ -1,5 +1,11 @@ #ifndef HEADER_PCPDynamicMeter #define HEADER_PCPDynamicMeter +/* +htop - PCPDynamicMeter.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include diff --git a/pcp/PCPDynamicScreen.c b/pcp/PCPDynamicScreen.c new file mode 100644 index 000000000..22228225d --- /dev/null +++ b/pcp/PCPDynamicScreen.c @@ -0,0 +1,407 @@ +/* +htop - PCPDynamicScreen.c +(C) 2022 Sohaib Mohammed +(C) 2022-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPDynamicScreen.h" + +#include +#include +#include +#include + +#include "AvailableColumnsPanel.h" +#include "Macros.h" +#include "Platform.h" +#include "Settings.h" +#include "XUtils.h" + +#include "pcp/InDomTable.h" +#include "pcp/PCPDynamicColumn.h" + + +static char* formatFields(PCPDynamicScreen* screen) { + char* columns = strdup(""); + + for (size_t j = 0; j < screen->totalColumns; j++) { + const PCPDynamicColumn* column = screen->columns[j]; + if (column->super.enabled == false) + continue; + char* prefix = columns; + xAsprintf(&columns, "%s Dynamic(%s)", prefix, column->super.name); + free(prefix); + } + + return columns; +} + +static void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { + for (size_t i = 0; i < screens->count; i++) { + PCPDynamicScreen* screen = Hashtable_get(screens->table, i); + if (!screen) + return; + + /* setup default fields (columns) based on configuration */ + for (size_t j = 0; j < screen->totalColumns; j++) { + PCPDynamicColumn* column = screen->columns[j]; + + column->id = columns->offset + columns->cursor; + columns->cursor++; + Platform_addMetric(column->id, column->metricName); + + size_t id = columns->count + LAST_PROCESSFIELD; + Hashtable_put(columns->table, id, column); + columns->count++; + + if (j == 0) { + const pmDesc* desc = Metric_desc(column->id); + assert(desc->indom != PM_INDOM_NULL); + screen->indom = desc->indom; + screen->key = column->id; + } + } + screen->super.columnKeys = formatFields(screen); + } +} + +static PCPDynamicColumn* PCPDynamicScreen_lookupMetric(PCPDynamicScreen* screen, const char* name) { + PCPDynamicColumn* column = NULL; + size_t bytes = strlen(name) + strlen(screen->super.name) + 1; /* colon */ + if (bytes >= sizeof(column->super.name)) + return NULL; + + bytes += 16; /* prefix, dots and terminator */ + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.screen.%s.%s", screen->super.name, name); + + for (size_t i = 0; i < screen->totalColumns; i++) { + column = screen->columns[i]; + if (String_eq(column->metricName, metricName)) { + free(metricName); + return column; + } + } + + /* not an existing column in this screen - create it and add to the list */ + column = xCalloc(1, sizeof(PCPDynamicColumn)); + xSnprintf(column->super.name, sizeof(column->super.name), "%s:%s", screen->super.name, name); + column->super.table = &screen->table->super; + column->metricName = metricName; + column->super.enabled = true; + + size_t n = screen->totalColumns + 1; + screen->columns = xReallocArray(screen->columns, n, sizeof(PCPDynamicColumn*)); + screen->columns[n - 1] = column; + screen->totalColumns = n; + + return column; +} + +static void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* path, unsigned int line, char* key, char* value) { + PCPDynamicColumn* column; + char* p; + + if ((p = strchr(key, '.')) == NULL) + return; + *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */ + + /* lookup a dynamic column with this name, else create */ + column = PCPDynamicScreen_lookupMetric(screen, key); + + if (String_eq(p, "metric")) { + char* error; + if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) { + char* note; + xAsprintf(¬e, + "%s: failed to parse expression in %s at line %u\n%s\n", + pmGetProgname(), path, line, error); + free(error); + errno = EINVAL; + CRT_fatalError(note); + free(note); + } + + /* pmLookupText - add optional metric help text */ + if (!column->super.description && !column->instances) + Metric_lookupText(value, &column->super.description); + + } else { + /* this is a property of a dynamic column - the column expression */ + /* may not have been observed yet; i.e. we allow for any ordering */ + + if (String_eq(p, "caption")) { + free_and_xStrdup(&column->super.caption, value); + } else if (String_eq(p, "heading")) { + free_and_xStrdup(&column->super.heading, value); + } else if (String_eq(p, "description")) { + free_and_xStrdup(&column->super.description, value); + } else if (String_eq(p, "width")) { + column->width = strtoul(value, NULL, 10); + } else if (String_eq(p, "format")) { + free_and_xStrdup(&column->format, value); + } else if (String_eq(p, "instances")) { + if (String_eq(value, "True") || String_eq(value, "true")) + column->instances = true; + free_and_xStrdup(&column->super.description, screen->super.caption); + } else if (String_eq(p, "default")) { /* displayed by default */ + if (String_eq(value, "False") || String_eq(value, "false")) + column->defaultEnabled = column->super.enabled = false; + } + } +} + +static bool PCPDynamicScreen_validateScreenName(char* key, const char* path, unsigned int line) { + char* p = key; + char* end = strrchr(key, ']'); + + if (end) { + *end = '\0'; + } else { + fprintf(stderr, + "%s: no closing brace on screen name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + + while (*p) { + if (p == key) { + if (!isalpha(*p) && *p != '_') + break; + } else { + if (!isalnum(*p) && *p != '_') + break; + } + p++; + } + if (*p != '\0') { /* badness */ + fprintf(stderr, + "%s: invalid screen name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +/* Ensure a screen name has not been defined previously */ +static bool PCPDynamicScreen_uniqueName(char* key, PCPDynamicScreens* screens) { + return !DynamicScreen_search(screens->table, key, NULL); +} + +static PCPDynamicScreen* PCPDynamicScreen_new(PCPDynamicScreens* screens, const char* name) { + PCPDynamicScreen* screen = xCalloc(1, sizeof(*screen)); + String_safeStrncpy(screen->super.name, name, sizeof(screen->super.name)); + screen->defaultEnabled = true; + + size_t id = screens->count; + Hashtable_put(screens->table, id, screen); + screens->count++; + + return screen; +} + +static void PCPDynamicScreen_parseFile(PCPDynamicScreens* screens, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicScreen* screen = NULL; + unsigned int lineno = 0; + bool ok = true; + for (;;) { + char* line = String_readLine(file); + if (!line) + break; + lineno++; + + /* cleanup whitespace, skip comment lines */ + char* trimmed = String_trim(line); + free(line); + if (!trimmed || !trimmed[0] || trimmed[0] == '#') { + free(trimmed); + continue; + } + + size_t n; + char** config = String_split(trimmed, '=', &n); + free(trimmed); + if (config == NULL) + continue; + + char* key = String_trim(config[0]); + char* value = n > 1 ? String_trim(config[1]) : NULL; + if (key[0] == '[') { /* new section name - i.e. new screen */ + ok = PCPDynamicScreen_validateScreenName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicScreen_uniqueName(key + 1, screens); + if (ok) + screen = PCPDynamicScreen_new(screens, key + 1); + if (pmDebugOptions.appl0) + fprintf(stderr, "[%s] screen: %s\n", path, key + 1); + } else if (!ok) { + ; /* skip this one, we're looking for a new header */ + } else if (!value || !screen) { + ; /* skip this one as we always need value strings */ + } else if (String_eq(key, "heading")) { + free_and_xStrdup(&screen->super.heading, value); + } else if (String_eq(key, "caption")) { + free_and_xStrdup(&screen->super.caption, value); + } else if (String_eq(key, "sortKey")) { + free_and_xStrdup(&screen->super.sortKey, value); + } else if (String_eq(key, "sortDirection")) { + screen->super.direction = strtoul(value, NULL, 10); + } else if (String_eq(key, "default") || String_eq(key, "enabled")) { + if (String_eq(value, "False") || String_eq(value, "false")) + screen->defaultEnabled = false; + else if (String_eq(value, "True") || String_eq(value, "true")) + screen->defaultEnabled = true; /* also default */ + } else { + PCPDynamicScreen_parseColumn(screen, path, lineno, key, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicScreen_scanDir(PCPDynamicScreens* screens, char* path) { + DIR* dir = opendir(path); + if (!dir) + return; + + struct dirent* dirent; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + + char* file = String_cat(path, dirent->d_name); + PCPDynamicScreen_parseFile(screens, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicScreens_init(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { + const char* share = pmGetConfig("PCP_SHARE_DIR"); + const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR"); + const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + const char* override = getenv("PCP_HTOP_DIR"); + const char* home = getenv("HOME"); + char* path; + + screens->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/screens/"); + else if (home) + path = String_cat(home, "/.config/htop/screens/"); + else + path = NULL; + if (path) { + PCPDynamicScreen_scanDir(screens, path); + free(path); + } + + /* next, search in the system screens directory */ + path = String_cat(sysconf, "/htop/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + + /* next, try the readonly system screens directory */ + path = String_cat(share, "/htop/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + + /* establish internal metric identifier mappings */ + PCPDynamicScreens_appendDynamicColumns(screens, columns); +} + +static void PCPDynamicScreen_done(PCPDynamicScreen* ds) { + DynamicScreen_done(&ds->super); + Object_delete(ds->table); + free(ds->columns); +} + +static void PCPDynamicScreens_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicScreen* ds = (PCPDynamicScreen*) value; + PCPDynamicScreen_done(ds); +} + +void PCPDynamicScreens_done(Hashtable* table) { + Hashtable_foreach(table, PCPDynamicScreens_free, NULL); +} + +void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + ds->table = InDomTable_new(host, ds->indom, ds->key); + } +} + +void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (ds->defaultEnabled == false) + continue; + const char* tab = ds->super.heading; + Settings_newDynamicScreen(settings, tab, &ds->super, &ds->table->super); + } +} + +/* called when htoprc .dynamic line is parsed for a dynamic screen */ +void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (String_eq(ss->dynamic, ds->super.name) == false) + continue; + ss->table = &ds->table->super; + } +} + +void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen) { + Vector_prune(availableColumns->items); + + bool success; + unsigned int key; + success = DynamicScreen_search(screens, screen, &key); + if (!success) + return; + + PCPDynamicScreen* dynamicScreen = Hashtable_get(screens, key); + if (!screen) + return; + + for (unsigned int j = 0; j < dynamicScreen->totalColumns; j++) { + PCPDynamicColumn* column = dynamicScreen->columns[j]; + const char* title = column->super.heading ? column->super.heading : column->super.name; + const char* text = column->super.description ? column->super.description : column->super.caption; + char description[256]; + if (text) + xSnprintf(description, sizeof(description), "%s - %s", title, text); + else + xSnprintf(description, sizeof(description), "%s", title); + Panel_add(availableColumns, (Object*) ListItem_new(description, j)); + } +} diff --git a/pcp/PCPDynamicScreen.h b/pcp/PCPDynamicScreen.h new file mode 100644 index 000000000..624839417 --- /dev/null +++ b/pcp/PCPDynamicScreen.h @@ -0,0 +1,56 @@ +#ifndef HEADER_PCPDynamicScreen +#define HEADER_PCPDynamicScreen +/* +htop - PCPDynamicScreen.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + +#include "CRT.h" +#include "DynamicScreen.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Panel.h" +#include "Settings.h" + + +struct InDomTable_; +struct PCPDynamicColumn_; +struct PCPDynamicColumns_; + +typedef struct PCPDynamicScreen_ { + DynamicScreen super; + + struct InDomTable_* table; + struct PCPDynamicColumn_** columns; + size_t totalColumns; + + unsigned int indom; /* instance domain number */ + unsigned int key; /* PCPMetric identifier */ + + bool defaultEnabled; /* enabled setting from configuration file */ + /* at runtime enabled screens have entries in settings->screens */ +} PCPDynamicScreen; + +typedef struct PCPDynamicScreens_ { + Hashtable* table; + size_t count; /* count of dynamic screens discovered from scan */ +} PCPDynamicScreens; + +void PCPDynamicScreens_init(PCPDynamicScreens* screens, struct PCPDynamicColumns_* columns); + +void PCPDynamicScreens_done(Hashtable* table); + +void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host); + +void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings); + +void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss); + +void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen); + +#endif diff --git a/pcp/PCPMachine.c b/pcp/PCPMachine.c index 59e056247..2e8725346 100644 --- a/pcp/PCPMachine.c +++ b/pcp/PCPMachine.c @@ -1,5 +1,5 @@ /* -htop - PCPProcessList.c +htop - PCPProcessTable.c (C) 2014 Hisham H. Muhammad (C) 2020-2023 htop dev team (C) 2020-2023 Red Hat, Inc. @@ -18,20 +18,20 @@ in the source distribution for its full text. #include #include -#include "Macros.h" #include "Machine.h" +#include "Macros.h" #include "Object.h" #include "Platform.h" #include "Settings.h" #include "XUtils.h" -#include "pcp/PCPMetric.h" +#include "pcp/Metric.h" #include "pcp/PCPProcess.h" static void PCPMachine_updateCPUcount(PCPMachine* this) { Machine* super = &this->super; - super->activeCPUs = PCPMetric_instanceCount(PCP_PERCPU_SYSTEM); + super->activeCPUs = Metric_instanceCount(PCP_PERCPU_SYSTEM); unsigned int cpus = Platform_getMaxCPU(); if (cpus == super->existingCPUs) return; @@ -58,30 +58,30 @@ static void PCPMachine_updateMemoryInfo(Machine* host) { host->usedSwap = host->totalSwap = host->sharedMem = 0; pmAtomValue value; - if (PCPMetric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL) host->totalMem = value.ull; - if (PCPMetric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_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) + if (Metric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL) host->buffersMem = value.ull; - if (PCPMetric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_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) + if (Metric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL) host->sharedMem = value.ull; - if (PCPMetric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL) host->cachedMem = value.ull + sreclaimableMem - host->sharedMem; const memory_t usedDiff = freeMem + host->cachedMem + sreclaimableMem + host->buffersMem; host->usedMem = (host->totalMem >= usedDiff) ? host->totalMem - usedDiff : host->totalMem - freeMem; - if (PCPMetric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL) host->availableMem = MINIMUM(value.ull, host->totalMem); else host->availableMem = freeMem; - if (PCPMetric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_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) + if (Metric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL) host->totalSwap = value.ull; - if (PCPMetric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL) host->cachedSwap = value.ull; host->usedSwap = host->totalSwap - swapFreeMem - host->cachedSwap; } @@ -153,31 +153,41 @@ static void PCPMachine_deriveCPUTime(pmAtomValue* values) { PCPMachine_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime); } -static void PCPMachine_updateAllCPUTime(PCPMachine* this, PCPMetric metric, CPUMetric cpumetric) +static void PCPMachine_updateAllCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric) { pmAtomValue* value = &this->cpu[cpumetric]; - if (PCPMetric_values(metric, value, 1, PM_TYPE_U64) == NULL) + if (Metric_values(metric, value, 1, PM_TYPE_U64) == NULL) memset(value, 0, sizeof(pmAtomValue)); } -static void PCPMachine_updatePerCPUTime(PCPMachine* this, PCPMetric metric, CPUMetric cpumetric) +static void PCPMachine_updatePerCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric) { int cpus = this->super.existingCPUs; - if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL) + if (Metric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL) memset(this->values, 0, cpus * sizeof(pmAtomValue)); for (int i = 0; i < cpus; i++) this->percpu[i][cpumetric].ull = this->values[i].ull; } -static void PCPMachine_updatePerCPUReal(PCPMachine* this, PCPMetric metric, CPUMetric cpumetric) +static void PCPMachine_updatePerCPUReal(PCPMachine* this, Metric metric, CPUMetric cpumetric) { int cpus = this->super.existingCPUs; - if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL) + if (Metric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL) memset(this->values, 0, cpus * sizeof(pmAtomValue)); for (int i = 0; i < cpus; i++) this->percpu[i][cpumetric].d = this->values[i].d; } +static inline void PCPMachine_scanZswapInfo(PCPMachine* this) { + pmAtomValue value; + + memset(&this->zswap, 0, sizeof(ZswapStats)); + if (Metric_values(PCP_MEM_ZSWAP, &value, 1, PM_TYPE_U64)) + this->zswap.usedZswapComp = value.ull; + if (Metric_values(PCP_MEM_ZSWAPPED, &value, 1, PM_TYPE_U64)) + this->zswap.usedZswapOrig = value.ull; +} + static inline void PCPMachine_scanZfsArcstats(PCPMachine* this) { unsigned long long int dbufSize = 0; unsigned long long int dnodeSize = 0; @@ -185,29 +195,29 @@ static inline void PCPMachine_scanZfsArcstats(PCPMachine* this) { pmAtomValue value; memset(&this->zfs, 0, sizeof(ZfsArcStats)); - if (PCPMetric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_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)) + if (Metric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.size = value.ull / ONE_K; this->zfs.other = (dbufSize + dnodeSize + bonusSize) / ONE_K; @@ -251,6 +261,7 @@ static void PCPMachine_scan(PCPMachine* this) { PCPMachine_updatePerCPUReal(this, PCP_HINV_CPUCLOCK, CPU_FREQUENCY); PCPMachine_scanZfsArcstats(this); + PCPMachine_scanZswapInfo(this); } void Machine_scan(Machine* super) { @@ -260,31 +271,31 @@ void Machine_scan(Machine* super) { bool flagged; for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++) - PCPMetric_enable(metric, true); + Metric_enable(metric, true); flagged = settings->showCPUFrequency; - PCPMetric_enable(PCP_HINV_CPUCLOCK, flagged); + Metric_enable(PCP_HINV_CPUCLOCK, flagged); flagged = flags & PROCESS_FLAG_LINUX_CGROUP; - PCPMetric_enable(PCP_PROC_CGROUPS, flagged); + Metric_enable(PCP_PROC_CGROUPS, flagged); flagged = flags & PROCESS_FLAG_LINUX_OOM; - PCPMetric_enable(PCP_PROC_OOMSCORE, flagged); + Metric_enable(PCP_PROC_OOMSCORE, flagged); flagged = flags & PROCESS_FLAG_LINUX_CTXT; - PCPMetric_enable(PCP_PROC_VCTXSW, flagged); - PCPMetric_enable(PCP_PROC_NVCTXSW, flagged); + Metric_enable(PCP_PROC_VCTXSW, flagged); + Metric_enable(PCP_PROC_NVCTXSW, flagged); flagged = flags & PROCESS_FLAG_LINUX_SECATTR; - PCPMetric_enable(PCP_PROC_LABELS, flagged); + Metric_enable(PCP_PROC_LABELS, flagged); flagged = flags & PROCESS_FLAG_LINUX_AUTOGROUP; - PCPMetric_enable(PCP_PROC_AUTOGROUP_ID, flagged); - PCPMetric_enable(PCP_PROC_AUTOGROUP_NICE, flagged); + Metric_enable(PCP_PROC_AUTOGROUP_ID, flagged); + Metric_enable(PCP_PROC_AUTOGROUP_NICE, flagged); /* Sample smaps metrics on every second pass to improve performance */ host->smaps_flag = !!host->smaps_flag; - PCPMetric_enable(PCP_PROC_SMAPS_PSS, host->smaps_flag); - PCPMetric_enable(PCP_PROC_SMAPS_SWAP, host->smaps_flag); - PCPMetric_enable(PCP_PROC_SMAPS_SWAPPSS, host->smaps_flag); + Metric_enable(PCP_PROC_SMAPS_PSS, host->smaps_flag); + Metric_enable(PCP_PROC_SMAPS_SWAP, host->smaps_flag); + Metric_enable(PCP_PROC_SMAPS_SWAPPSS, host->smaps_flag); struct timeval timestamp; - if (PCPMetric_fetch(×tamp) != true) + if (Metric_fetch(×tamp) != true) return; double sample = host->timestamp; @@ -307,6 +318,8 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue)); PCPMachine_updateCPUcount(this); + Platform_updateTables(super); + return super; } @@ -326,7 +339,7 @@ bool Machine_isCPUonline(const Machine* host, unsigned int id) { (void) host; pmAtomValue value; - if (PCPMetric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32)) + if (Metric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32)) return true; return false; } diff --git a/pcp/PCPMachine.h b/pcp/PCPMachine.h index faca8efc9..6518bd49f 100644 --- a/pcp/PCPMachine.h +++ b/pcp/PCPMachine.h @@ -7,8 +7,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include @@ -17,6 +15,7 @@ in the source distribution for its full text. #include "UsersTable.h" #include "pcp/Platform.h" +#include "linux/ZswapStats.h" #include "zfs/ZfsArcStats.h" @@ -59,10 +58,14 @@ typedef struct PCPMachine_ { int smaps_flag; double period; double timestamp; /* previous sample timestamp */ + pmAtomValue* cpu; /* aggregate values for each metric */ pmAtomValue** percpu; /* per-processor values for each metric */ pmAtomValue* values; /* per-processor buffer for just one metric */ + ZfsArcStats zfs; + /*ZramStats zram; -- not needed, calculated in-line in Platform.c */ + ZswapStats zswap; } PCPMachine; #endif diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c index cefd0ac3a..69e2972f3 100644 --- a/pcp/PCPProcess.c +++ b/pcp/PCPProcess.c @@ -7,6 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "pcp/PCPProcess.h" #include @@ -49,6 +51,7 @@ const ProcessFieldData Process_fields[] = { [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, [M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, .defaultSortDesc = true, }, + [M_PRIV] = { .name = "M_PRIV", .title = " PRIV ", .description = "The private memory size of the process - resident set size minus shared memory", .flags = 0, .defaultSortDesc = true, }, [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, .defaultSortDesc = true, }, [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, }, [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, }, @@ -71,7 +74,9 @@ const ProcessFieldData Process_fields[] = { [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, - [CGROUP] = { .name = "CGROUP", .title = " CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, }, + [CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw) ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, }, + [CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed) ", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, }, + [CONTAINER] = { .name = "CONTAINER", .title = "CONTAINER ", .description = "Name of the container the process is in (guessed by heuristics)", .flags = PROCESS_FLAG_LINUX_CGROUP, }, [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, }, [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = 0, .defaultSortDesc = true, }, [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, }, @@ -98,63 +103,70 @@ Process* PCPProcess_new(const Machine* host) { void Process_delete(Object* cast) { PCPProcess* this = (PCPProcess*) cast; Process_done((Process*)cast); + free(this->cgroup_short); free(this->cgroup); free(this->secattr); free(this); } -static void PCPProcess_printDelay(float delay_percent, char* buffer, int n) { - if (isnan(delay_percent)) { - xSnprintf(buffer, n, " N/A "); - } else { +static void PCPProcess_printDelay(float delay_percent, char* buffer, size_t n) { + if (isNonnegative(delay_percent)) { xSnprintf(buffer, n, "%4.1f ", delay_percent); + } else { + xSnprintf(buffer, n, " N/A "); + } +} + +static double PCPProcess_totalIORate(const PCPProcess* pp) { + double totalRate = NAN; + if (isNonnegative(pp->io_rate_read_bps)) { + totalRate = pp->io_rate_read_bps; + if (isNonnegative(pp->io_rate_write_bps)) { + totalRate += pp->io_rate_write_bps; + } + } else if (isNonnegative(pp->io_rate_write_bps)) { + totalRate = pp->io_rate_write_bps; } + return totalRate; } -static void PCPProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const PCPProcess* pp = (const PCPProcess*) this; - bool coloring = this->host->settings->highlightMegabytes; +static void PCPProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const PCPProcess* pp = (const PCPProcess*) super; + + bool coloring = super->host->settings->highlightMegabytes; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; - int n = sizeof(buffer) - 1; + size_t n = sizeof(buffer) - 1; + switch ((int)field) { - case CMINFLT: Process_printCount(str, pp->cminflt, coloring); return; - case CMAJFLT: Process_printCount(str, pp->cmajflt, coloring); return; - case M_DRS: Process_printBytes(str, pp->m_drs, coloring); return; - case M_DT: Process_printBytes(str, pp->m_dt, coloring); return; - case M_LRS: Process_printBytes(str, pp->m_lrs, coloring); return; - case M_TRS: Process_printBytes(str, pp->m_trs, coloring); return; - case M_SHARE: Process_printBytes(str, pp->m_share, coloring); return; - case M_PSS: Process_printKBytes(str, pp->m_pss, coloring); return; - case M_SWAP: Process_printKBytes(str, pp->m_swap, coloring); return; - case M_PSSWP: Process_printKBytes(str, pp->m_psswp, coloring); return; - case UTIME: Process_printTime(str, pp->utime, coloring); return; - case STIME: Process_printTime(str, pp->stime, coloring); return; - case CUTIME: Process_printTime(str, pp->cutime, coloring); return; - case CSTIME: Process_printTime(str, pp->cstime, coloring); return; - case RCHAR: Process_printBytes(str, pp->io_rchar, coloring); return; - case WCHAR: Process_printBytes(str, pp->io_wchar, coloring); return; - case SYSCR: Process_printCount(str, pp->io_syscr, coloring); return; - case SYSCW: Process_printCount(str, pp->io_syscw, coloring); return; - case RBYTES: Process_printBytes(str, pp->io_read_bytes, coloring); return; - case WBYTES: Process_printBytes(str, pp->io_write_bytes, coloring); return; - case CNCLWB: Process_printBytes(str, pp->io_cancelled_write_bytes, coloring); return; - case IO_READ_RATE: Process_printRate(str, pp->io_rate_read_bps, coloring); return; - case IO_WRITE_RATE: Process_printRate(str, pp->io_rate_write_bps, coloring); return; - case IO_RATE: { - double totalRate = NAN; - if (!isnan(pp->io_rate_read_bps) && !isnan(pp->io_rate_write_bps)) - totalRate = pp->io_rate_read_bps + pp->io_rate_write_bps; - else if (!isnan(pp->io_rate_read_bps)) - totalRate = pp->io_rate_read_bps; - else if (!isnan(pp->io_rate_write_bps)) - totalRate = pp->io_rate_write_bps; - else - totalRate = NAN; - Process_printRate(str, totalRate, coloring); - return; - } - case CGROUP: xSnprintf(buffer, n, "%-10s ", pp->cgroup ? pp->cgroup : ""); break; + case CMINFLT: Row_printCount(str, pp->cminflt, coloring); return; + case CMAJFLT: Row_printCount(str, pp->cmajflt, coloring); return; + case M_DRS: Row_printBytes(str, pp->m_drs, coloring); return; + case M_DT: Row_printBytes(str, pp->m_dt, coloring); return; + case M_LRS: Row_printBytes(str, pp->m_lrs, coloring); return; + case M_TRS: Row_printBytes(str, pp->m_trs, coloring); return; + case M_SHARE: Row_printBytes(str, pp->m_share, coloring); return; + case M_PRIV: Row_printBytes(str, pp->m_priv, coloring); return; + case M_PSS: Row_printKBytes(str, pp->m_pss, coloring); return; + case M_SWAP: Row_printKBytes(str, pp->m_swap, coloring); return; + case M_PSSWP: Row_printKBytes(str, pp->m_psswp, coloring); return; + case UTIME: Row_printTime(str, pp->utime, coloring); return; + case STIME: Row_printTime(str, pp->stime, coloring); return; + case CUTIME: Row_printTime(str, pp->cutime, coloring); return; + case CSTIME: Row_printTime(str, pp->cstime, coloring); return; + case RCHAR: Row_printBytes(str, pp->io_rchar, coloring); return; + case WCHAR: Row_printBytes(str, pp->io_wchar, coloring); return; + case SYSCR: Row_printCount(str, pp->io_syscr, coloring); return; + case SYSCW: Row_printCount(str, pp->io_syscw, coloring); return; + case RBYTES: Row_printBytes(str, pp->io_read_bytes, coloring); return; + case WBYTES: Row_printBytes(str, pp->io_write_bytes, coloring); return; + case CNCLWB: Row_printBytes(str, pp->io_cancelled_write_bytes, coloring); return; + case IO_READ_RATE: Row_printRate(str, pp->io_rate_read_bps, coloring); return; + case IO_WRITE_RATE: Row_printRate(str, pp->io_rate_write_bps, coloring); return; + case IO_RATE: Row_printRate(str, PCPProcess_totalIORate(pp), coloring); return; + case CGROUP: xSnprintf(buffer, n, "%-35.35s ", pp->cgroup ? pp->cgroup : "N/A"); break; + case CCGROUP: xSnprintf(buffer, n, "%-35.35s ", pp->cgroup_short ? pp->cgroup_short : (pp->cgroup ? pp->cgroup : "N/A")); break; + case CONTAINER: xSnprintf(buffer, n, "%-35.35s ", pp->container_short ? pp->container_short : "N/A"); break; case OOM: xSnprintf(buffer, n, "%4u ", pp->oom); break; case PERCENT_CPU_DELAY: PCPProcess_printDelay(pp->cpu_delay_percent, buffer, n); @@ -192,17 +204,11 @@ static void PCPProcess_writeField(const Process* this, RichString* str, ProcessF } break; default: - Process_writeField(this, str, field); + Process_writeField(&pp->super, str, field); return; } - RichString_appendWide(str, attr, buffer); -} -static double adjustNaN(double num) { - if (isnan(num)) - return -0.0005; - - return num; + RichString_appendWide(str, attr, buffer); } static int PCPProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) { @@ -220,6 +226,8 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs); case M_SHARE: return SPACESHIP_NUMBER(p1->m_share, p2->m_share); + case M_PRIV: + return SPACESHIP_NUMBER(p1->m_priv, p2->m_priv); case M_PSS: return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss); case M_SWAP: @@ -249,21 +257,25 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process case CNCLWB: return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes); case IO_READ_RATE: - return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps), adjustNaN(p2->io_rate_read_bps)); + return compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps); case IO_WRITE_RATE: - return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_write_bps)); + return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps); case IO_RATE: - return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps) + adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_read_bps) + adjustNaN(p2->io_rate_write_bps)); + return compareRealNumbers(PCPProcess_totalIORate(p1), PCPProcess_totalIORate(p2)); case CGROUP: return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup); + case CCGROUP: + return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short); + case CONTAINER: + return SPACESHIP_NULLSTR(p1->container_short, p2->container_short); case OOM: return SPACESHIP_NUMBER(p1->oom, p2->oom); case PERCENT_CPU_DELAY: - return SPACESHIP_NUMBER(p1->cpu_delay_percent, p2->cpu_delay_percent); + return compareRealNumbers(p1->cpu_delay_percent, p2->cpu_delay_percent); case PERCENT_IO_DELAY: - return SPACESHIP_NUMBER(p1->blkio_delay_percent, p2->blkio_delay_percent); + return compareRealNumbers(p1->blkio_delay_percent, p2->blkio_delay_percent); case PERCENT_SWAP_DELAY: - return SPACESHIP_NUMBER(p1->swapin_delay_percent, p2->swapin_delay_percent); + return compareRealNumbers(p1->swapin_delay_percent, p2->swapin_delay_percent); case CTXT: return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff); case SECATTR: @@ -281,11 +293,18 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process const ProcessClass PCPProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = PCPProcess_rowWriteField, }, - .writeField = PCPProcess_writeField, - .compareByKey = PCPProcess_compareByKey + .compareByKey = PCPProcess_compareByKey, }; diff --git a/pcp/PCPProcess.h b/pcp/PCPProcess.h index be33111e0..e95375714 100644 --- a/pcp/PCPProcess.h +++ b/pcp/PCPProcess.h @@ -9,8 +9,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include "Machine.h" @@ -38,6 +36,7 @@ typedef struct PCPProcess_ { unsigned long long int cutime; unsigned long long int cstime; long m_share; + long m_priv; long m_pss; long m_swap; long m_psswp; @@ -73,6 +72,8 @@ typedef struct PCPProcess_ { double io_rate_read_bps; double io_rate_write_bps; char* cgroup; + char* cgroup_short; + char* container_short; long int autogroup_id; int autogroup_nice; unsigned int oom; diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessTable.c similarity index 59% rename from pcp/PCPProcessList.c rename to pcp/PCPProcessTable.c index c18d74be9..4999bdc27 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessTable.c @@ -1,5 +1,5 @@ /* -htop - PCPProcessList.c +htop - PCPProcessTable.c (C) 2014 Hisham H. Muhammad (C) 2020-2021 htop dev team (C) 2020-2021 Red Hat, Inc. @@ -9,7 +9,7 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep -#include "pcp/PCPProcessList.h" +#include "pcp/PCPProcessTable.h" #include #include @@ -18,79 +18,81 @@ in the source distribution for its full text. #include #include -#include "Macros.h" #include "Machine.h" +#include "Macros.h" #include "Object.h" #include "Platform.h" #include "Process.h" #include "Settings.h" #include "XUtils.h" +#include "linux/CGroupUtils.h" +#include "pcp/Metric.h" #include "pcp/PCPMachine.h" -#include "pcp/PCPMetric.h" #include "pcp/PCPProcess.h" -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - PCPProcessList* this = xCalloc(1, sizeof(PCPProcessList)); - ProcessList* super = &(this->super); +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + PCPProcessTable* this = xCalloc(1, sizeof(PCPProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(super, Class(PCPProcess), host, pidMatchList); + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(PCPProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* super) { - PCPProcessList* this = (PCPProcessList*) super; - ProcessList_done(super); +void ProcessTable_delete(Object* cast) { + PCPProcessTable* this = (PCPProcessTable*) cast; + ProcessTable_done(&this->super); free(this); } static inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_32)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_32)) return value.l; return fallback; } static inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_64)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_64)) return value.l; return fallback; } static inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U32)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U32)) return value.ul; return fallback; } static inline unsigned long long Metric_instance_u64(int metric, int pid, int offset, unsigned long long fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) return value.ull; return fallback; } static inline unsigned long long Metric_instance_time(int metric, int pid, int offset) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) return value.ull / 10; return 0; } static inline unsigned long long Metric_instance_ONE_K(int metric, int pid, int offset) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) return value.ull / ONE_K; return ULLONG_MAX; } static inline char Metric_instance_char(int metric, int pid, int offset, char fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) { + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) { char uchar = value.cp[0]; free(value.cp); return uchar; @@ -104,14 +106,14 @@ static char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) { return name; pmAtomValue value; - if (PCPMetric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) { + if (Metric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) { Hashtable_put(this->users, uid, value.cp); name = value.cp; } return name; } -static inline ProcessState PCPProcessList_getProcessState(char state) { +static inline ProcessState PCPProcessTable_getProcessState(char state) { switch (state) { case '?': return UNKNOWN; case 'R': return RUNNING; @@ -128,17 +130,17 @@ static inline ProcessState PCPProcessList_getProcessState(char state) { } } -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 PCPProcessTable_updateID(Process* process, int pid, int offset) { + Process_setThreadGroup(process, Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1)); + Process_setParent(process, Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1)); + process->state = PCPProcessTable_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?')); } -static void PCPProcessList_updateInfo(Process* process, int pid, int offset, char* command, size_t commLen) { - PCPProcess* pp = (PCPProcess*) process; +static void PCPProcessTable_updateInfo(PCPProcess* pp, int pid, int offset, char* command, size_t commLen) { + Process* process = &pp->super; pmAtomValue value; - if (!PCPMetric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING)) + if (!Metric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING)) value.cp = xStrdup(""); String_safeStrncpy(command, value.cp, commLen); free(value.cp); @@ -164,7 +166,7 @@ static void PCPProcessList_updateInfo(Process* process, int pid, int offset, cha process->time = pp->utime + pp->stime; } -static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigned long long now) { +static void PCPProcessTable_updateIO(PCPProcess* pp, int pid, int offset, unsigned long long now) { pmAtomValue value; pp->io_rchar = Metric_instance_ONE_K(PCP_PROC_IO_RCHAR, pid, offset); @@ -173,7 +175,7 @@ static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigne 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)) { + if (Metric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) { unsigned long long last_read = pp->io_read_bytes; pp->io_read_bytes = value.ull / ONE_K; pp->io_rate_read_bps = ONE_K * (pp->io_read_bytes - last_read) / @@ -183,7 +185,7 @@ static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigne pp->io_rate_read_bps = NAN; } - if (PCPMetric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) { + if (Metric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) { unsigned long long last_write = pp->io_write_bytes; pp->io_write_bytes = value.ull; pp->io_rate_write_bps = ONE_K * (pp->io_write_bytes - last_write) / @@ -196,79 +198,111 @@ static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigne pp->io_last_scan_time = now; } -static void PCPProcessList_updateMemory(PCPProcess* pp, int pid, int offset) { +static void PCPProcessTable_updateMemory(PCPProcess* pp, int pid, int offset) { pp->super.m_virt = Metric_instance_u32(PCP_PROC_MEM_SIZE, pid, offset, 0); pp->super.m_resident = Metric_instance_u32(PCP_PROC_MEM_RSS, pid, offset, 0); pp->m_share = Metric_instance_u32(PCP_PROC_MEM_SHARE, pid, offset, 0); + pp->m_priv = pp->super.m_resident - pp->m_share; pp->m_trs = Metric_instance_u32(PCP_PROC_MEM_TEXTRS, pid, offset, 0); pp->m_lrs = Metric_instance_u32(PCP_PROC_MEM_LIBRS, pid, offset, 0); pp->m_drs = Metric_instance_u32(PCP_PROC_MEM_DATRS, pid, offset, 0); pp->m_dt = Metric_instance_u32(PCP_PROC_MEM_DIRTY, pid, offset, 0); } -static void PCPProcessList_updateSmaps(PCPProcess* pp, pid_t pid, int offset) { +static void PCPProcessTable_updateSmaps(PCPProcess* pp, pid_t pid, int offset) { pp->m_pss = Metric_instance_u64(PCP_PROC_SMAPS_PSS, pid, offset, 0); pp->m_swap = Metric_instance_u64(PCP_PROC_SMAPS_SWAP, pid, offset, 0); pp->m_psswp = Metric_instance_u64(PCP_PROC_SMAPS_SWAPPSS, pid, offset, 0); } -static void PCPProcessList_readOomData(PCPProcess* pp, int pid, int offset) { +static void PCPProcessTable_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) { +static void PCPProcessTable_readAutogroup(PCPProcess* pp, int pid, int offset) { pp->autogroup_id = Metric_instance_s64(PCP_PROC_AUTOGROUP_ID, pid, offset, -1); pp->autogroup_nice = Metric_instance_s32(PCP_PROC_AUTOGROUP_NICE, pid, offset, 0); } -static void PCPProcessList_readCtxtData(PCPProcess* pp, int pid, int offset) { +static void PCPProcessTable_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)) + if (Metric_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)) + if (Metric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32)) ctxt += value.ul; pp->ctxt_diff = ctxt > pp->ctxt_total ? ctxt - pp->ctxt_total : 0; pp->ctxt_total = ctxt; } -static char* setString(PCPMetric metric, int pid, int offset, char* string) { +static char* setString(Metric metric, int pid, int offset, char* string) { if (string) free(string); pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) + if (Metric_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) { +static void PCPProcessTable_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) { +static void PCPProcessTable_readCGroups(PCPProcess* pp, int pid, int offset) { pp->cgroup = setString(PCP_PROC_CGROUPS, pid, offset, pp->cgroup); + + if (pp->cgroup) { + char* cgroup_short = CGroup_filterName(pp->cgroup); + if (cgroup_short) { + Row_updateFieldWidth(CCGROUP, strlen(cgroup_short)); + free_and_xStrdup(&pp->cgroup_short, cgroup_short); + free(cgroup_short); + } else { + //CCGROUP is alias to normal CGROUP if shortening fails + Row_updateFieldWidth(CCGROUP, strlen(pp->cgroup)); + free(pp->cgroup_short); + pp->cgroup_short = NULL; + } + + char* container_short = CGroup_filterName(pp->cgroup); + if (container_short) { + Row_updateFieldWidth(CONTAINER, strlen(container_short)); + free_and_xStrdup(&pp->container_short, container_short); + free(container_short); + } else { + Row_updateFieldWidth(CONTAINER, strlen("N/A")); + free(pp->container_short); + pp->container_short = NULL; + } + } else { + free(pp->cgroup_short); + pp->cgroup_short = NULL; + + free(pp->container_short); + pp->container_short = NULL; + } } -static void PCPProcessList_readSecattrData(PCPProcess* pp, int pid, int offset) { +static void PCPProcessTable_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) { +static void PCPProcessTable_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) { +static void PCPProcessTable_updateUsername(Process* process, int pid, int offset, UsersTable* users) { process->st_uid = Metric_instance_u32(PCP_PROC_ID_UID, pid, offset, 0); process->user = setUser(users, process->st_uid, pid, offset); } -static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, const char* comm) { +static void PCPProcessTable_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 (!Metric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) { if (process->state != ZOMBIE) process->isKernelThread = true; Process_updateCmdline(process, NULL, 0, 0); @@ -287,30 +321,38 @@ static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, process->isKernelThread = true; } + int tokenEnd = 0; int tokenStart = 0; + bool argSepSpace = false; + for (int i = 0; i < length; i++) { /* htop considers the next character after the last / that is before * basenameOffset, as the start of the basename in cmdline - see * Process_writeCommand */ if (command[i] == '/') tokenStart = i + 1; + /* special-case arguments for problematic situations like "find /" */ + if (command[i] <= ' ') + argSepSpace = true; } - int tokenEnd = length; + tokenEnd = length; + if (argSepSpace) + tokenStart = 0; 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)) { + if (Metric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) { Process_updateExe(process, value.cp[0] ? value.cp : NULL); free(value.cp); } } -static bool PCPProcessList_updateProcesses(PCPProcessList* this) { - ProcessList* pl = (ProcessList*) this; - Machine* host = pl->host; +static bool PCPProcessTable_updateProcesses(PCPProcessTable* this) { + ProcessTable* pt = (ProcessTable*) this; + Machine* host = pt->super.host; PCPMachine* phost = (PCPMachine*) host; const Settings* settings = host->settings; @@ -322,93 +364,95 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this) { int pid = -1, offset = -1; /* for every process ... */ - while (PCPMetric_iterate(PCP_PROC_PID, &pid, &offset)) { + while (Metric_iterate(PCP_PROC_PID, &pid, &offset)) { bool preExisting; - Process* proc = ProcessList_getProcess(pl, pid, &preExisting, PCPProcess_new); + Process* proc = ProcessTable_getProcess(pt, pid, &preExisting, PCPProcess_new); PCPProcess* pp = (PCPProcess*) proc; - PCPProcessList_updateID(proc, pid, offset); - proc->isUserlandThread = proc->pid != proc->tgid; + PCPProcessTable_updateID(proc, pid, offset); + proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); pp->offset = offset >= 0 ? offset : 0; /* * These conditions will not trigger on first occurrence, cause we need to - * add the process to the ProcessList and do all one time scans + * add the process to the ProcessTable and do all one time scans * (e.g. parsing the cmdline to detect a kernel thread) * But it will short-circuit subsequent scans. */ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) { - proc->updated = true; - proc->show = false; + proc->super.updated = true; + proc->super.show = false; if (proc->state == RUNNING) - pl->runningTasks++; - pl->kernelThreads++; - pl->totalTasks++; + pt->runningTasks++; + pt->kernelThreads++; + pt->totalTasks++; continue; } if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) { - proc->updated = true; - proc->show = false; + proc->super.updated = true; + proc->super.show = false; if (proc->state == RUNNING) - pl->runningTasks++; - pl->userlandThreads++; - pl->totalTasks++; + pt->runningTasks++; + pt->userlandThreads++; + pt->totalTasks++; continue; } if (flags & PROCESS_FLAG_IO) - PCPProcessList_updateIO(pp, pid, offset, now); + PCPProcessTable_updateIO(pp, pid, offset, now); - PCPProcessList_updateMemory(pp, pid, offset); + PCPProcessTable_updateMemory(pp, pid, offset); - if ((flags & PROCESS_FLAG_LINUX_SMAPS) && - (Process_isKernelThread(proc) == false)) { - if (PCPMetric_enabled(PCP_PROC_SMAPS_PSS)) - PCPProcessList_updateSmaps(pp, pid, offset); + if ((flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) { + if (Metric_enabled(PCP_PROC_SMAPS_PSS)) { + PCPProcessTable_updateSmaps(pp, pid, offset); + } } char command[MAX_NAME + 1]; unsigned int tty_nr = proc->tty_nr; unsigned long long int lasttimes = pp->utime + pp->stime; - PCPProcessList_updateInfo(proc, pid, offset, command, sizeof(command)); + PCPProcessTable_updateInfo(pp, pid, offset, command, sizeof(command)); proc->starttime_ctime += Platform_getBootTime(); if (tty_nr != proc->tty_nr) - PCPProcessList_updateTTY(proc, pid, offset); + PCPProcessTable_updateTTY(proc, pid, offset); - float percent_cpu = (pp->utime + pp->stime - lasttimes) / phost->period * 100.0; - proc->percent_cpu = isnan(percent_cpu) ? - 0.0 : CLAMP(percent_cpu, 0.0, host->activeCPUs * 100.0); + proc->percent_cpu = NAN; + if (phost->period > 0.0) { + float percent_cpu = saturatingSub(pp->utime + pp->stime, lasttimes) / phost->period * 100.0; + proc->percent_cpu = MINIMUM(percent_cpu, host->activeCPUs * 100.0F); + } proc->percent_mem = proc->m_resident / (double) host->totalMem * 100.0; Process_updateCPUFieldWidths(proc->percent_cpu); - PCPProcessList_updateUsername(proc, pid, offset, host->usersTable); + PCPProcessTable_updateUsername(proc, pid, offset, host->usersTable); if (!preExisting) { - PCPProcessList_updateCmdline(proc, pid, offset, command); + PCPProcessTable_updateCmdline(proc, pid, offset, command); Process_fillStarttimeBuffer(proc); - ProcessList_add(pl, proc); + ProcessTable_add(pt, proc); } else if (settings->updateProcessNames && proc->state != ZOMBIE) { - PCPProcessList_updateCmdline(proc, pid, offset, command); + PCPProcessTable_updateCmdline(proc, pid, offset, command); } if (flags & PROCESS_FLAG_LINUX_CGROUP) - PCPProcessList_readCGroups(pp, pid, offset); + PCPProcessTable_readCGroups(pp, pid, offset); if (flags & PROCESS_FLAG_LINUX_OOM) - PCPProcessList_readOomData(pp, pid, offset); + PCPProcessTable_readOomData(pp, pid, offset); if (flags & PROCESS_FLAG_LINUX_CTXT) - PCPProcessList_readCtxtData(pp, pid, offset); + PCPProcessTable_readCtxtData(pp, pid, offset); if (flags & PROCESS_FLAG_LINUX_SECATTR) - PCPProcessList_readSecattrData(pp, pid, offset); + PCPProcessTable_readSecattrData(pp, pid, offset); if (flags & PROCESS_FLAG_CWD) - PCPProcessList_readCwd(pp, pid, offset); + PCPProcessTable_readCwd(pp, pid, offset); if (flags & PROCESS_FLAG_LINUX_AUTOGROUP) - PCPProcessList_readAutogroup(pp, pid, offset); + PCPProcessTable_readAutogroup(pp, pid, offset); if (proc->state == ZOMBIE && !proc->cmdline && command[0]) { Process_updateCmdline(proc, command, 0, strlen(command)); @@ -418,25 +462,25 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this) { } if (Process_isKernelThread(proc)) { - pl->kernelThreads++; + pt->kernelThreads++; } else { - pl->userlandThreads++; + pt->userlandThreads++; } } /* Set at the end when we know if a new entry is a thread */ - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); - pl->totalTasks++; + pt->totalTasks++; if (proc->state == RUNNING) - pl->runningTasks++; - proc->updated = true; + pt->runningTasks++; + proc->super.updated = true; } return true; } -void ProcessList_goThroughEntries(ProcessList* super) { - PCPProcessList* this = (PCPProcessList*) super; - PCPProcessList_updateProcesses(this); +void ProcessTable_goThroughEntries(ProcessTable* super) { + PCPProcessTable* this = (PCPProcessTable*) super; + PCPProcessTable_updateProcesses(this); } diff --git a/pcp/PCPProcessList.h b/pcp/PCPProcessTable.h similarity index 53% rename from pcp/PCPProcessList.h rename to pcp/PCPProcessTable.h index 47a360263..e55c85ba5 100644 --- a/pcp/PCPProcessList.h +++ b/pcp/PCPProcessTable.h @@ -1,26 +1,24 @@ -#ifndef HEADER_PCPProcessList -#define HEADER_PCPProcessList +#ifndef HEADER_PCPProcessTable +#define HEADER_PCPProcessTable /* -htop - PCPProcessList.h +htop - PCPProcessTable.h (C) 2014 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include #include "Hashtable.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "UsersTable.h" #include "pcp/Platform.h" -typedef struct PCPProcessList_ { - ProcessList super; -} PCPProcessList; +typedef struct PCPProcessTable_ { + ProcessTable super; +} PCPProcessTable; #endif diff --git a/pcp/Platform.c b/pcp/Platform.c index 12e0f4d78..0b5f33447 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -25,6 +25,7 @@ in the source distribution for its full text. #include "DiskIOMeter.h" #include "DynamicColumn.h" #include "DynamicMeter.h" +#include "DynamicScreen.h" #include "FileDescriptorMeter.h" #include "HostnameMeter.h" #include "LoadAverageMeter.h" @@ -33,7 +34,7 @@ in the source distribution for its full text. #include "MemorySwapMeter.h" #include "Meter.h" #include "NetworkIOMeter.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "Settings.h" #include "SwapMeter.h" #include "SysArchMeter.h" @@ -44,11 +45,12 @@ in the source distribution for its full text. #include "linux/PressureStallMeter.h" #include "linux/ZramMeter.h" #include "linux/ZramStats.h" +#include "pcp/Metric.h" #include "pcp/PCPDynamicColumn.h" #include "pcp/PCPDynamicMeter.h" +#include "pcp/PCPDynamicScreen.h" #include "pcp/PCPMachine.h" -#include "pcp/PCPMetric.h" -#include "pcp/PCPProcessList.h" +#include "pcp/PCPProcessTable.h" #include "zfs/ZfsArcMeter.h" #include "zfs/ZfsArcStats.h" #include "zfs/ZfsCompressedArcMeter.h" @@ -195,6 +197,8 @@ static const char* Platform_metricNames[] = { [PCP_ZRAM_CAPACITY] = "zram.capacity", [PCP_ZRAM_ORIGINAL] = "zram.mm_stat.data_size.original", [PCP_ZRAM_COMPRESSED] = "zram.mm_stat.data_size.compressed", + [PCP_MEM_ZSWAP] = "mem.util.zswap", + [PCP_MEM_ZSWAPPED] = "mem.util.zswapped", [PCP_VFS_FILES_COUNT] = "vfs.files.count", [PCP_VFS_FILES_MAX] = "vfs.files.max", @@ -286,7 +290,7 @@ int pmLookupDescs(int numpmid, pmID* pmids, pmDesc* descs) { } #endif -size_t Platform_addMetric(PCPMetric id, const char* name) { +size_t Platform_addMetric(Metric id, const char* name) { unsigned int i = (unsigned int)id; if (i >= PCP_METRIC_COUNT && i >= pcp->totalMetrics) { @@ -355,6 +359,7 @@ bool Platform_init(void) { pcp->columns.offset = PCP_METRIC_COUNT + pcp->meters.cursor; PCPDynamicColumns_init(&pcp->columns); + PCPDynamicScreens_init(&pcp->screens, &pcp->columns); sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids); if (sts < 0) { @@ -374,31 +379,32 @@ bool Platform_init(void) { } /* set proc.control.perclient.threads to 1 for live contexts */ - PCPMetric_enableThreads(); + Metric_enableThreads(); /* extract values needed for setup - e.g. cpu count, pid_max */ - PCPMetric_enable(PCP_PID_MAX, true); - PCPMetric_enable(PCP_BOOTTIME, true); - PCPMetric_enable(PCP_HINV_NCPU, true); - PCPMetric_enable(PCP_PERCPU_SYSTEM, true); - PCPMetric_enable(PCP_UNAME_SYSNAME, true); - PCPMetric_enable(PCP_UNAME_RELEASE, true); - PCPMetric_enable(PCP_UNAME_MACHINE, true); - PCPMetric_enable(PCP_UNAME_DISTRO, true); - + Metric_enable(PCP_PID_MAX, true); + Metric_enable(PCP_BOOTTIME, true); + Metric_enable(PCP_HINV_NCPU, true); + Metric_enable(PCP_PERCPU_SYSTEM, true); + Metric_enable(PCP_UNAME_SYSNAME, true); + Metric_enable(PCP_UNAME_RELEASE, true); + Metric_enable(PCP_UNAME_MACHINE, true); + Metric_enable(PCP_UNAME_DISTRO, true); + + /* enable metrics for all dynamic columns (including those from dynamic screens) */ for (size_t i = pcp->columns.offset; i < pcp->columns.offset + pcp->columns.count; i++) - PCPMetric_enable(i, true); + Metric_enable(i, true); - PCPMetric_fetch(NULL); + Metric_fetch(NULL); - for (PCPMetric metric = 0; metric < PCP_PROC_PID; metric++) - PCPMetric_enable(metric, true); - PCPMetric_enable(PCP_PID_MAX, false); /* needed one time only */ - PCPMetric_enable(PCP_BOOTTIME, false); - PCPMetric_enable(PCP_UNAME_SYSNAME, false); - PCPMetric_enable(PCP_UNAME_RELEASE, false); - PCPMetric_enable(PCP_UNAME_MACHINE, false); - PCPMetric_enable(PCP_UNAME_DISTRO, false); + for (Metric metric = 0; metric < PCP_PROC_PID; metric++) + Metric_enable(metric, true); + Metric_enable(PCP_PID_MAX, false); /* needed one time only */ + Metric_enable(PCP_BOOTTIME, false); + Metric_enable(PCP_UNAME_SYSNAME, false); + Metric_enable(PCP_UNAME_RELEASE, false); + Metric_enable(PCP_UNAME_MACHINE, false); + Metric_enable(PCP_UNAME_DISTRO, false); /* first sample (fetch) performed above, save constants */ Platform_getBootTime(); @@ -417,6 +423,10 @@ void Platform_dynamicMetersDone(Hashtable* meters) { PCPDynamicMeters_done(meters); } +void Platform_dynamicScreensDone(Hashtable* screens) { + PCPDynamicScreens_done(screens); +} + void Platform_done(void) { pmDestroyContext(pcp->context); if (pcp->result) @@ -436,7 +446,7 @@ void Platform_setBindings(Htop_Action* keys) { int Platform_getUptime(void) { pmAtomValue value; - if (PCPMetric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL) + if (Metric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL) return 0; return value.l; } @@ -445,7 +455,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *one = *five = *fifteen = 0.0; pmAtomValue values[3] = {0}; - if (PCPMetric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) { + if (Metric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) { *one = values[0].d; *five = values[1].d; *fifteen = values[2].d; @@ -457,20 +467,20 @@ unsigned int Platform_getMaxCPU(void) { return pcp->ncpu; pmAtomValue value; - if (PCPMetric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL) + if (Metric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL) pcp->ncpu = value.ul; else pcp->ncpu = 1; return pcp->ncpu; } -int Platform_getMaxPid(void) { +pid_t Platform_getMaxPid(void) { if (pcp->pidmax) return pcp->pidmax; pmAtomValue value; - if (PCPMetric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL) - return -1; + if (Metric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL) + return UINT_MAX; pcp->pidmax = value.l; return pcp->pidmax; } @@ -480,7 +490,7 @@ long long Platform_getBootTime(void) { return pcp->btime; pmAtomValue value; - if (PCPMetric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL) + if (Metric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL) pcp->btime = value.ll; return pcp->btime; } @@ -497,24 +507,28 @@ static double Platform_setOneCPUValues(Meter* this, const Settings* settings, pm v[CPU_METER_KERNEL] = values[CPU_SYSTEM_PERIOD].ull / total * 100.0; v[CPU_METER_IRQ] = values[CPU_IRQ_PERIOD].ull / total * 100.0; v[CPU_METER_SOFTIRQ] = values[CPU_SOFTIRQ_PERIOD].ull / total * 100.0; + this->curItems = 5; + v[CPU_METER_STEAL] = values[CPU_STEAL_PERIOD].ull / total * 100.0; v[CPU_METER_GUEST] = values[CPU_GUEST_PERIOD].ull / total * 100.0; - v[CPU_METER_IOWAIT] = values[CPU_IOWAIT_PERIOD].ull / total * 100.0; - this->curItems = 8; - percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ] + v[CPU_METER_SOFTIRQ]; if (settings->accountGuestInCPUMeter) { - percent += v[CPU_METER_STEAL] + v[CPU_METER_GUEST]; + this->curItems = 7; } + + v[CPU_METER_IOWAIT] = values[CPU_IOWAIT_PERIOD].ull / total * 100.0; } else { v[CPU_METER_KERNEL] = values[CPU_SYSTEM_ALL_PERIOD].ull / total * 100.0; value = values[CPU_STEAL_PERIOD].ull + values[CPU_GUEST_PERIOD].ull; v[CPU_METER_IRQ] = value / total * 100.0; this->curItems = 4; - percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ]; } - percent = CLAMP(percent, 0.0, 100.0); - if (isnan(percent)) - percent = 0.0; + + percent = sumPositiveValues(v, this->curItems); + percent = MINIMUM(percent, 100.0); + + if (settings->detailedCPUTime) { + this->curItems = 8; + } v[CPU_METER_FREQUENCY] = values[CPU_FREQUENCY].d; v[CPU_METER_TEMPERATURE] = NAN; @@ -537,9 +551,9 @@ void Platform_setMemoryValues(Meter* this) { this->total = host->totalMem; this->values[MEMORY_METER_USED] = host->usedMem; - this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_SHARED] = host->sharedMem; - // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + this->values[MEMORY_METER_COMPRESSED] = 0; + this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_CACHE] = host->cachedMem; this->values[MEMORY_METER_AVAILABLE] = host->availableMem; @@ -552,18 +566,36 @@ void Platform_setMemoryValues(Meter* this) { this->values[MEMORY_METER_CACHE] += shrinkableSize; this->values[MEMORY_METER_AVAILABLE] += shrinkableSize; } + + if (phost->zswap.usedZswapOrig > 0 || phost->zswap.usedZswapComp > 0) { + this->values[MEMORY_METER_USED] -= phost->zswap.usedZswapComp; + this->values[MEMORY_METER_COMPRESSED] += phost->zswap.usedZswapComp; + } } void Platform_setSwapValues(Meter* this) { const Machine* host = this->host; + const PCPMachine* phost = (const PCPMachine*) host; + this->total = host->totalSwap; this->values[SWAP_METER_USED] = host->usedSwap; this->values[SWAP_METER_CACHE] = host->cachedSwap; - // this->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux" + this->values[SWAP_METER_FRONTSWAP] = 0; /* frontswap -- memory that is accounted to swap but resides elsewhere */ + + if (phost->zswap.usedZswapOrig > 0 || phost->zswap.usedZswapComp > 0) { + /* refer to linux/Platform.c::Platform_setSwapValues for details */ + this->values[SWAP_METER_USED] -= phost->zswap.usedZswapOrig; + if (this->values[SWAP_METER_USED] < 0) { + /* subtract the overflow from SwapCached */ + this->values[SWAP_METER_CACHE] += this->values[SWAP_METER_USED]; + this->values[SWAP_METER_USED] = 0; + } + this->values[SWAP_METER_FRONTSWAP] += phost->zswap.usedZswapOrig; + } } void Platform_setZramValues(Meter* this) { - int i, count = PCPMetric_instanceCount(PCP_ZRAM_CAPACITY); + int i, count = Metric_instanceCount(PCP_ZRAM_CAPACITY); if (!count) { this->total = 0; this->values[0] = 0; @@ -574,24 +606,28 @@ void Platform_setZramValues(Meter* this) { pmAtomValue* values = xCalloc(count, sizeof(pmAtomValue)); ZramStats stats = {0}; - if (PCPMetric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) { + if (Metric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) { for (i = 0; i < count; i++) stats.totalZram += values[i].ull; } - if (PCPMetric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) { + if (Metric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) { for (i = 0; i < count; i++) stats.usedZramOrig += values[i].ull; } - if (PCPMetric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) { + if (Metric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) { for (i = 0; i < count; i++) stats.usedZramComp += values[i].ull; } free(values); + if (stats.usedZramComp > stats.usedZramOrig) { + stats.usedZramComp = stats.usedZramOrig; + } + this->total = stats.totalZram; this->values[0] = stats.usedZramComp; - this->values[1] = stats.usedZramOrig; + this->values[1] = stats.usedZramOrig - stats.usedZramComp; } void Platform_setZfsArcValues(Meter* this) { @@ -620,13 +656,13 @@ void Platform_getRelease(char** string) { /* first call, extract just-sampled values */ pmAtomValue sysname, release, machine, distro; - if (!PCPMetric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING)) + if (!Metric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING)) sysname.cp = NULL; - if (!PCPMetric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING)) + if (!Metric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING)) release.cp = NULL; - if (!PCPMetric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING)) + if (!Metric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING)) machine.cp = NULL; - if (!PCPMetric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING)) + if (!Metric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING)) distro.cp = NULL; size_t length = 16; /* padded for formatting characters */ @@ -674,7 +710,7 @@ void Platform_getRelease(char** string) { char* Platform_getProcessEnv(pid_t pid) { pmAtomValue value; - if (!PCPMetric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING)) + if (!Metric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING)) return NULL; return value.cp; } @@ -687,7 +723,7 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) { void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) { *ten = *sixty = *threehundred = 0; - PCPMetric metric; + Metric metric; if (String_eq(file, "cpu")) metric = PCP_PSI_CPUSOME; else if (String_eq(file, "io")) @@ -700,7 +736,7 @@ void Platform_getPressureStall(const char* file, bool some, double* ten, double* return; pmAtomValue values[3] = {0}; - if (PCPMetric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) { + if (Metric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) { *ten = values[0].d; *sixty = values[1].d; *threehundred = values[2].d; @@ -711,11 +747,11 @@ bool Platform_getDiskIO(DiskIOData* data) { memset(data, 0, sizeof(*data)); pmAtomValue value; - if (PCPMetric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL) data->totalBytesRead = value.ull; - if (PCPMetric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL) data->totalBytesWritten = value.ull; - if (PCPMetric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL) data->totalMsTimeSpend = value.ull; return true; } @@ -724,13 +760,13 @@ bool Platform_getNetworkIO(NetworkIOData* data) { memset(data, 0, sizeof(*data)); pmAtomValue value; - if (PCPMetric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL) data->bytesReceived = value.ull; - if (PCPMetric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL) data->bytesTransmitted = value.ull; - if (PCPMetric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL) data->packetsReceived = value.ull; - if (PCPMetric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL) data->packetsTransmitted = value.ull; return true; } @@ -740,9 +776,9 @@ void Platform_getFileDescriptors(double* used, double* max) { *max = 65536; pmAtomValue value; - if (PCPMetric_values(PCP_VFS_FILES_COUNT, &value, 1, PM_TYPE_32) != NULL) + if (Metric_values(PCP_VFS_FILES_COUNT, &value, 1, PM_TYPE_32) != NULL) *used = value.l; - if (PCPMetric_values(PCP_VFS_FILES_MAX, &value, 1, PM_TYPE_32) != NULL) + if (Metric_values(PCP_VFS_FILES_MAX, &value, 1, PM_TYPE_32) != NULL) *max = value.l; } @@ -766,7 +802,7 @@ CommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** a case PLATFORM_LONGOPT_HOST: /* --host=HOSTSPEC */ if (argv[optind][0] == '\0') return STATUS_ERROR_EXIT; - __pmAddOptHost(&opts, optarg); + __pmAddOptHost(&opts, optarg); return STATUS_OK; case PLATFORM_LONGOPT_HOSTZONE: /* --hostzone */ @@ -792,6 +828,7 @@ CommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** a default: break; } + return STATUS_ERROR_EXIT; } @@ -842,10 +879,10 @@ Hashtable* Platform_dynamicColumns(void) { return pcp->columns.table; } -const char* Platform_dynamicColumnInit(unsigned int key) { +const char* Platform_dynamicColumnName(unsigned int key) { PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); if (this) { - PCPMetric_enable(this->id, true); + Metric_enable(this->id, true); if (this->super.caption) return this->super.caption; if (this->super.heading) @@ -863,3 +900,25 @@ bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsi } return false; } + +Hashtable* Platform_dynamicScreens(void) { + return pcp->screens.table; +} + +void Platform_defaultDynamicScreens(Settings* settings) { + PCPDynamicScreen_appendScreens(&pcp->screens, settings); +} + +void Platform_addDynamicScreen(ScreenSettings* ss) { + PCPDynamicScreen_addDynamicScreen(&pcp->screens, ss); +} + +void Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen) { + Hashtable* screens = pcp->screens.table; + PCPDynamicScreens_addAvailableColumns(availableColumns, screens, screen); +} + +void Platform_updateTables(Machine* host) { + PCPDynamicScreen_appendTables(&pcp->screens, host); + PCPDynamicColumns_setupWidths(&pcp->columns); +} diff --git a/pcp/Platform.h b/pcp/Platform.h index f90e28135..f43ed54f2 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -36,9 +36,10 @@ in the source distribution for its full text. #include "SignalsPanel.h" #include "CommandLine.h" +#include "pcp/Metric.h" #include "pcp/PCPDynamicColumn.h" #include "pcp/PCPDynamicMeter.h" -#include "pcp/PCPMetric.h" +#include "pcp/PCPDynamicScreen.h" typedef struct Platform_ { @@ -51,6 +52,7 @@ typedef struct Platform_ { pmResult* result; /* sample values result indexed by Metric */ PCPDynamicMeters meters; /* dynamic meters via configuration files */ PCPDynamicColumns columns; /* dynamic columns via configuration files */ + PCPDynamicScreens screens; /* dynamic screens via configuration files */ struct timeval offset; /* time offset used in archive mode only */ long long btime; /* boottime in seconds since the epoch */ char* release; /* uname and distro from this context */ @@ -82,7 +84,7 @@ long long Platform_getBootTime(void); unsigned int Platform_getMaxCPU(void); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* this, int cpu); @@ -129,7 +131,7 @@ CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv); extern pmOptions opts; -size_t Platform_addMetric(PCPMetric id, const char* name); +size_t Platform_addMetric(Metric id, const char* name); void Platform_getFileDescriptors(double* used, double* max); @@ -151,8 +153,20 @@ Hashtable* Platform_dynamicColumns(void); void Platform_dynamicColumnsDone(Hashtable* columns); -const char* Platform_dynamicColumnInit(unsigned int key); +const char* Platform_dynamicColumnName(unsigned int key); bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key); +Hashtable* Platform_dynamicScreens(void); + +void Platform_defaultDynamicScreens(Settings* settings); + +void Platform_addDynamicScreen(ScreenSettings* ss); + +void Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen); + +void Platform_dynamicScreensDone(Hashtable* screens); + +void Platform_updateTables(Machine* host); + #endif diff --git a/pcp/ProcessField.h b/pcp/ProcessField.h index b3ba2657a..634256129 100644 --- a/pcp/ProcessField.h +++ b/pcp/ProcessField.h @@ -45,6 +45,9 @@ in the source distribution for its full text. SECATTR = 123, \ AUTOGROUP_ID = 127, \ AUTOGROUP_NICE = 128, \ + CCGROUP = 129, \ + CONTAINER = 130, \ + M_PRIV = 131, \ // End of list diff --git a/pcp/screens/biosnoop b/pcp/screens/biosnoop new file mode 100644 index 000000000..e6cdf8945 --- /dev/null +++ b/pcp/screens/biosnoop @@ -0,0 +1,41 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[biosnoop] +heading = BioSnoop +caption = BPF block I/O snoop +default = false + +pid.heading = PID +pid.caption = Process identifier +pid.metric = bpf.biosnoop.pid +pid.format = process + +disk.heading = DISK +disk.caption = Device name +disk.width = -7 +disk.metric = bpf.biosnoop.disk + +rwbs.heading = TYPE +rwbs.caption = I/O type string +rwbs.width = -4 +rwbs.metric = bpf.biosnoop.rwbs + +bytes.heading = BYTES +bytes.caption = I/O size in bytes +bytes.metric = bpf.biosnoop.bytes + +lat.heading = LAT +lat.caption = I/O latency +lat.metric = bpf.biosnoop.lat + +sector.heading = SECTOR +sector.caption = Device sector +sector.metric = bpf.biosnoop.sector + +comm.heading = Command +comm.caption = Process command name +comm.width = -16 +comm.metric = bpf.biosnoop.comm +comm.format = process diff --git a/pcp/screens/cgroups b/pcp/screens/cgroups new file mode 100644 index 000000000..0ddc65c4a --- /dev/null +++ b/pcp/screens/cgroups @@ -0,0 +1,45 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroups] +heading = CGroups +caption = Control Groups +default = true + +user_cpu.heading = UTIME +user_cpu.caption = User CPU Time +user_cpu.metric = 1000 * rate(cgroup.cpu.stat.user) +user_cpu.width = 6 + +system_cpu.heading = STIME +system_cpu.caption = Kernel CPU Time +system_cpu.metric = 1000 * rate(cgroup.cpu.stat.system) +system_cpu.width = 6 + +cpu_usage.heading = CPU% +cpu_usage.caption = CPU Utilization +cpu_usage.metric = 100 * (rate(cgroup.cpu.stat.usage) / hinv.ncpu) +cpu_usage.format = percent + +cpu_psi.heading = CPU-PSI +cpu_psi.caption = CPU Pressure Stall Information +cpu_psi.metric = 1000 * rate(cgroup.pressure.cpu.some.total) +cpu_psi.width = 7 + +mem_psi.heading = MEM-PSI +mem_psi.caption = Memory Pressure Stall Information +mem_psi.metric = 1000 * rate(cgroup.pressure.memory.some.total) +mem_psi.width = 7 + +io_psi.heading = I/O-PSI +io_psi.caption = I/O Pressure Stall Information +io_psi.metric = 1000 * rate(cgroup.pressure.io.some.total) +io_psi.width = 7 + +name.heading = Control group +name.caption = Control group name +name.width = -64 +name.metric = cgroup.cpu.stat.system +name.instances = true +name.format = cgroup diff --git a/pcp/screens/cgroupsio b/pcp/screens/cgroupsio new file mode 100644 index 000000000..3a431db5a --- /dev/null +++ b/pcp/screens/cgroupsio @@ -0,0 +1,49 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroupsio] +heading = CGroupsIO +caption = Control Groups I/O +default = false + +iops.heading = IOPS +iops.caption = I/O operations +iops.metric = rate(cgroup.io.stat.rios) + rate(cgroup.io.stat.wios) + rate(cgroup.io.stat.dios) + +readops.heading = RDIO +readops.caption = Read operations +readops.metric = rate(cgroup.io.stat.rios) +readops.default = false + +writeops.heading = WRIO +writeops.caption = Write operations +writeops.metric = rate(cgroup.io.stat.wios) +writeops.default = false + +directops.heading = DIO +directops.caption = Direct I/O operations +directops.metric = rate(cgroup.io.stat.dios) +directops.default = false + +totalbytes.heading = R/W/D +totalbytes.caption = Disk throughput +totalbytes.metric = rate(cgroup.io.stat.rbytes) + rate(cgroup.io.stat.wbytes) + rate(cgroup.io.stat.dbytes) + +readbytes.heading = RBYTE +readbytes.caption = Disk read throughput +readbytes.metric = rate(cgroup.io.stat.rbytes) + +writebytes.heading = WBYTE +writebytes.caption = Disk throughput +writebytes.metric = rate(cgroup.io.stat.wbytes) + +directio.heading = DBYTE +directio.caption = Direct I/O throughput +directio.metric = rate(cgroup.io.stat.dbytes) + +name.heading = Control group device +name.caption = Control group device +name.width = -64 +name.metric = cgroup.io.stat.rbytes +name.instances = true diff --git a/pcp/screens/cgroupsmem b/pcp/screens/cgroupsmem new file mode 100644 index 000000000..17bc1e38f --- /dev/null +++ b/pcp/screens/cgroupsmem @@ -0,0 +1,48 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroupsmem] +heading = CGroupsMem +caption = Control Groups Memory +default = false + +current.heading = MEM +current.caption = Current memory +current.metric = cgroup.memory.current + +usage.heading = USAGE +usage.caption = Memory usage +usage.metric = cgroup.memory.usage + +container.heading = CONTAINER +container.caption = Container Name +container.metric = cgroup.memory.id.container + +resident.heading = RSS +resident.metric = cgroup.memory.stat.rss + +cresident.heading = CRSS +cresident.metric = cgroup.memory.stat.total.rss + +anonmem.heading = ANON +anonmem.metric = cgroup.memory.stat.anon + +filemem.heading = FILE +filemem.metric = cgroup.memory.stat.file + +shared.heading = SHMEM +shared.metric = cgroup.memory.stat.shmem + +swap.heading = SWAP +swap.metric = cgroup.memory.stat.swap + +pgfault.heading = FAULTS +pgfault.metric = cgroup.memory.stat.pgfaults + +name.heading = Control group +name.caption = Control group name +name.width = -64 +name.metric = cgroup.memory.current +name.instances = true +name.format = cgroup diff --git a/pcp/screens/devices b/pcp/screens/devices new file mode 100644 index 000000000..7399f82f7 --- /dev/null +++ b/pcp/screens/devices @@ -0,0 +1,114 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[disks] +heading = Disks +caption = Disk devices + +diskdev.heading = Device +diskdev.metric = disk.dev.read +diskdev.instances = true +diskdev.format = device +diskdev.width = -8 + +total.heading = TPS +total.metric = rate(disk.dev.read) + rate(disk.dev.write) + rate(disk.dev.discard) +total.caption = Rate of read requests + +read.heading = RR/S +read.metric = rate(disk.dev.read) +read.caption = Rate of read requests + +read_bytes.heading = RRB/S +read_bytes.metric = rate(disk.dev.read_bytes) +read_bytes.caption = Read throughput from the device + +read_merge.heading = RRQM/S +read_merge.metric = rate(disk.dev.read_merge) +read_merge.caption = Rate reads merged before queued +read_merge.default = false + +read_merge_pct.heading = RRQM% +read_merge_pct.metric = 100 * rate(disk.dev.read_merge) / rate(disk.dev.read) +read_merge_pct.caption = Percentage reads merged before queued +read_merge_pct.format = percent + +read_await.heading = RAWAIT +read_await.metric = disk.dev.r_await +read_await.default = false + +read_avqsz.heading = RARQSZ +read_avqsz.metric = disk.dev.r_avg_rqsz +read_avqsz.default = false + +write.heading = WR/S +write.metric = rate(disk.dev.write) +write.caption = Rate of write requests + +write_bytes.heading = WRB/S +write_bytes.metric = rate(disk.dev.write_bytes) +write_bytes.caption = Write throughput to the device + +write_merge.heading = WRQM/S +write_merge.metric = rate(disk.dev.write_merge) +write_merge.caption = Rate writes merged before queued +write_merge.default = false + +write_merge_pct.heading = WRQM% +write_merge_pct.metric = 100 * rate(disk.dev.write_merge) / rate(disk.dev.write) +write_merge_pct.caption = Percentage writes merged before queued +write_merge_pct.format = percent + +write_await.heading = WAWAIT +write_await.metric = disk.dev.w_await +write_await.default = false + +write_avqsz.heading = WARQSZ +write_avqsz.metric = disk.dev.w_avg_rqsz +write_avqsz.default = false + +discard.heading = DR/S +discard.metric = rate(disk.dev.discard) +discard.caption = Rate of discard requests + +discard_bytes.heading = DRB/S +discard_bytes.metric = rate(disk.dev.discard_bytes) +discard_bytes.caption = Discard request throughput +discard_bytes.default = false + +discard_merge.heading = DRQM/S +discard_merge.metric = rate(disk.dev.discard_merge) +discard_merge.caption = Rate discards merged before queued +discard_merge.default = false + +discard_merge_pct.heading = DRQM% +discard_merge_pct.metric = 100 * rate(disk.dev.discard_merge) / rate(disk.dev.discard) +discard_merge_pct.caption = Percentage discards merged before queued +discard_merge_pct.format = percent +discard_merge_pct.default = false + +discard_await.heading = DAWAIT +discard_await.metric = disk.dev.d_await +discard_await.default = false + +discard_avqsz.heading = DARQSZ +discard_avqsz.metric = disk.dev.d_avg_rqsz +discard_avqsz.default = false + +flush.heading = F/S +flush.metric = rate(disk.dev.flush) +flush.default = false +flush.caption = Flushes per second + +flush_await.heading = FAWAIT +flush_await.metric = disk.dev.f_await +flush_await.default = false + +qlen.heading = AQU-SZ +qlen.metric = disk.dev.avg_qlen + +util.heading = UTIL% +util.metric = 100 * disk.dev.util +util.caption = Perentage device utilization +util.format = percent diff --git a/pcp/screens/execsnoop b/pcp/screens/execsnoop new file mode 100644 index 000000000..d706e7640 --- /dev/null +++ b/pcp/screens/execsnoop @@ -0,0 +1,37 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[execsnoop] +heading = ExecSnoop +caption = BPF exec(2) syscall snoop +default = false + +pid.heading = PID +pid.caption = Process Identifier +pid.metric = bpf.execsnoop.pid +pid.format = process + +ppid.heading = PPID +ppid.caption = Parent Process +ppid.metric = bpf.execsnoop.ppid +ppid.format = process + +uid.heading = UID +uid.caption = User Identifier +uid.metric = bpf.execsnoop.uid + +comm.heading = COMM +comm.caption = Command +comm.width = -16 +comm.metric = bpf.execsnoop.comm +comm.format = command + +ret.heading = RET +ret.caption = Return Code +ret.metric = bpf.execsnoop.ret + +path.heading = Arguments +path.caption = Arguments +path.width = -12 +path.metric = bpf.execsnoop.args diff --git a/pcp/screens/exitsnoop b/pcp/screens/exitsnoop new file mode 100644 index 000000000..6c6b867c0 --- /dev/null +++ b/pcp/screens/exitsnoop @@ -0,0 +1,48 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[exitsnoop] +heading = ExitSnoop +caption = BPF process exit(2) snoop +default = false + +pid.heading = PID +pid.caption = Process Identifier +pid.metric = bpf.exitsnoop.pid +pid.format = process + +ppid.heading = PPID +ppid.caption = Parent Process +ppid.metric = bpf.exitsnoop.ppid +ppid.format = process + +tid.heading = TID +tid.caption = Task Identifier +tid.metric = bpf.exitsnoop.tid +tid.format = process +tid.default = false + +signal.heading = SIG +signal.caption = Signal number +signal.metric = bpf.exitsnoop.sig + +exit.heading = EXIT +exit.caption = Exit Code +exit.metric = bpf.exitsnoop.exit_code + +core.heading = CORE +core.caption = Dumped core +core.metric = bpf.exitsnoop.coredump +core.default = false + +age.heading = AGE +age.caption = Process age +age.metric = bpf.exitsnoop.age +age.default = false + +comm.heading = Command +comm.caption = COMM +comm.width = -16 +comm.metric = bpf.exitsnoop.comm +comm.format = command diff --git a/pcp/screens/filesystems b/pcp/screens/filesystems new file mode 100644 index 000000000..06f3bf236 --- /dev/null +++ b/pcp/screens/filesystems @@ -0,0 +1,50 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[filesystems] +heading = Filesystems +caption = Mounted block device filesystems + +blockdev.heading = Device +blockdev.metric = filesys.mountdir +blockdev.instances = true +blockdev.width = -14 + +blocksize.heading = BSIZE +blocksize.metric = filesys.blocksize +blocksize.default = false + +capacity.heading = SIZE +capacity.metric = filesys.capacity + +used.heading = USED +used.metric = filesys.used + +free.heading = FREE +free.metric = filesys.free +free.default = false + +avail.heading = AVAIL +avail.metric = filesys.avail + +full.heading = USE% +full.metric = filesys.full +full.format = percent + +usedfiles.heading = USEDF +usedfiles.metric = filesys.usedfiles +usedfiles.default = false + +freefiles.heading = FREEF +freefiles.metric = filesys.freefiles +freefiles.default = false + +maxfiles.heading = MAXF +maxfiles.metric = filesys.maxfiles +maxfiles.default = false + +mountdir.heading = Mount point +mountdir.metric = filesys.mountdir +mountdir.format = path +mountdir.width = -33 diff --git a/pcp/screens/opensnoop b/pcp/screens/opensnoop new file mode 100644 index 000000000..ec209b03f --- /dev/null +++ b/pcp/screens/opensnoop @@ -0,0 +1,27 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[opensnoop] +heading = OpenSnoop +caption = BPF open(2) syscall snoop +default = false + +pid.heading = PID +pid.metric = bpf.opensnoop.pid +pid.format = process + +comm.heading = COMM +comm.metric = bpf.opensnoop.comm +comm.format = command + +fd.heading = FD +fd.metric = bpf.opensnoop.fd + +err.heading = ERR +err.metric = bpf.opensnoop.err + +file.heading = File name +file.width = -32 +file.metric = bpf.opensnoop.fname +file.format = path diff --git a/solaris/Platform.c b/solaris/Platform.c index 95c50b4d7..5faa91ae7 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -7,6 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "solaris/Platform.h" #include @@ -34,6 +36,7 @@ in the source distribution for its full text. #include "HostnameMeter.h" #include "SysArchMeter.h" #include "UptimeMeter.h" +#include "XUtils.h" #include "solaris/SolarisMachine.h" @@ -174,7 +177,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *fifteen = plat_loadavg[LOADAVG_15MIN]; } -int Platform_getMaxPid(void) { +pid_t Platform_getMaxPid(void) { int vproc = 32778; // Reasonable Solaris default kstat_ctl_t* kc = kstat_open(); @@ -221,14 +224,13 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) { v[CPU_METER_KERNEL] = cpuData->systemPercent; v[CPU_METER_IRQ] = cpuData->irqPercent; this->curItems = 4; - percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ]; } else { v[CPU_METER_KERNEL] = cpuData->systemAllPercent; this->curItems = 3; - percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL]; } - percent = isnan(percent) ? 0.0 : CLAMP(percent, 0.0, 100.0); + percent = sumPositiveValues(v, this->curItems); + percent = MINIMUM(percent, 100.0); v[CPU_METER_FREQUENCY] = cpuData->frequency; v[CPU_METER_TEMPERATURE] = NAN; @@ -240,9 +242,9 @@ void Platform_setMemoryValues(Meter* this) { const Machine* host = this->host; this->total = host->totalMem; this->values[MEMORY_METER_USED] = host->usedMem; - this->values[MEMORY_METER_BUFFERS] = host->buffersMem; // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm" // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + this->values[MEMORY_METER_BUFFERS] = host->buffersMem; this->values[MEMORY_METER_CACHE] = host->cachedMem; // this->values[MEMORY_METER_AVAILABLE] = "available memory" } diff --git a/solaris/Platform.h b/solaris/Platform.h index 3dc6e3b59..1a31c2e7a 100644 --- a/solaris/Platform.h +++ b/solaris/Platform.h @@ -9,8 +9,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include /* On OmniOS /usr/include/sys/regset.h redefines ERR to 13 - \r, breaking the Enter key. @@ -30,12 +28,12 @@ in the source distribution for its full text. #include "Action.h" #include "BatteryMeter.h" +#include "CommandLine.h" #include "DiskIOMeter.h" #include "Hashtable.h" #include "NetworkIOMeter.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" -#include "CommandLine.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -72,7 +70,7 @@ int Platform_getUptime(void); void Platform_getLoadAverage(double* one, double* five, double* fifteen); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* this, unsigned int cpu); @@ -150,7 +148,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -158,4 +156,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/solaris/SolarisMachine.c b/solaris/SolarisMachine.c index b94fc742d..4f740d321 100644 --- a/solaris/SolarisMachine.c +++ b/solaris/SolarisMachine.c @@ -6,6 +6,7 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep #include "solaris/SolarisMachine.h" @@ -300,7 +301,7 @@ void Machine_scan(Machine* super) { Machine* Machine_new(UsersTable* usersTable, uid_t userId) { SolarisMachine* this = xCalloc(1, sizeof(SolarisMachine)); - Machine *super = &this->super; + Machine* super = &this->super; Machine_init(super, usersTable, userId); diff --git a/solaris/SolarisMachine.h b/solaris/SolarisMachine.h index da091c6e9..2208a888e 100644 --- a/solaris/SolarisMachine.h +++ b/solaris/SolarisMachine.h @@ -8,17 +8,15 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include #include #include -#include #include +#include #include #include -#include +#include #include "Hashtable.h" #include "UsersTable.h" diff --git a/solaris/SolarisProcess.c b/solaris/SolarisProcess.c index 3b5ea1ae5..449861b25 100644 --- a/solaris/SolarisProcess.c +++ b/solaris/SolarisProcess.c @@ -6,6 +6,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "solaris/SolarisProcess.h" #include @@ -14,7 +16,7 @@ in the source distribution for its full text. #include #include "Process.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "CRT.h" #include "solaris/Platform.h" @@ -73,11 +75,13 @@ void Process_delete(Object* cast) { free(sp); } -static void SolarisProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const SolarisProcess* sp = (const SolarisProcess*) this; +static void SolarisProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const SolarisProcess* sp = (const SolarisProcess*) super; + char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; - int n = sizeof(buffer) - 1; + size_t n = sizeof(buffer) - 1; + switch (field) { // add Solaris-specific fields here case ZONEID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->zoneid); break; @@ -85,15 +89,16 @@ static void SolarisProcess_writeField(const Process* this, RichString* str, Proc case TASKID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->taskid); break; case POOLID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->poolid); break; case CONTID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->contid); break; - case ZONE: Process_printLeftAlignedField(str, attr, sp->zname ? sp->zname : "global", ZONENAME_MAX/4); return; + case ZONE: Row_printLeftAlignedField(str, attr, sp->zname ? sp->zname : "global", ZONENAME_MAX/4); return; case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realpid); break; case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realppid); break; case TGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realtgid); break; case LWPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->lwpid); break; default: - Process_writeField(this, str, field); + Process_writeField(&sp->super, str, field); return; } + RichString_appendWide(str, attr, buffer); } @@ -127,11 +132,18 @@ static int SolarisProcess_compareByKey(const Process* v1, const Process* v2, Pro const ProcessClass SolarisProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = SolarisProcess_rowWriteField }, - .writeField = SolarisProcess_writeField, .compareByKey = SolarisProcess_compareByKey }; diff --git a/solaris/SolarisProcess.h b/solaris/SolarisProcess.h index 6bb3ca1ce..1a8d18c76 100644 --- a/solaris/SolarisProcess.h +++ b/solaris/SolarisProcess.h @@ -8,8 +8,6 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include diff --git a/solaris/SolarisProcessList.c b/solaris/SolarisProcessTable.c similarity index 74% rename from solaris/SolarisProcessList.c rename to solaris/SolarisProcessTable.c index e759b1877..549c753cd 100644 --- a/solaris/SolarisProcessList.c +++ b/solaris/SolarisProcessTable.c @@ -1,13 +1,14 @@ /* -htop - SolarisProcessList.c +htop - SolarisProcessTable.c (C) 2014 Hisham H. Muhammad (C) 2017,2018 Guy M. Broome Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep -#include "solaris/SolarisProcessList.h" +#include "solaris/SolarisProcessTable.h" #include #include @@ -29,7 +30,7 @@ in the source distribution for its full text. #define GZONE "global " #define UZONE "unknown " -static char* SolarisProcessList_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) { +static char* SolarisProcessTable_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) { char* zname; if ( sproc->zoneid == 0 ) { @@ -44,22 +45,23 @@ static char* SolarisProcessList_readZoneName(kstat_ctl_t* kd, SolarisProcess* sp return zname; } -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - SolarisProcessList* spl = xCalloc(1, sizeof(SolarisProcessList)); - ProcessList* pl = (ProcessList*) spl; +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + SolarisProcessTable* this = xCalloc(1, sizeof(SolarisProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(pl, Class(SolarisProcess), host, pidMatchList); + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(SolarisProcess), host, pidMatchList); - return pl; + return super; } -void ProcessList_delete(ProcessList* pl) { - SolarisProcessList* spl = (SolarisProcessList*) pl; - ProcessList_done(pl); - free(spl); +void ProcessTable_delete(Object* cast) { + SolarisProcessTable* this = (SolarisProcessTable*) cast; + ProcessTable_done(&this->super); + free(this); } -static void SolarisProcessList_updateExe(pid_t pid, Process* proc) { +static void SolarisProcessTable_updateExe(pid_t pid, Process* proc) { char path[32]; xSnprintf(path, sizeof(path), "/proc/%d/path/a.out", pid); @@ -72,7 +74,7 @@ static void SolarisProcessList_updateExe(pid_t pid, Process* proc) { Process_updateExe(proc, target); } -static void SolarisProcessList_updateCwd(pid_t pid, Process* proc) { +static void SolarisProcessTable_updateCwd(pid_t pid, Process* proc) { char path[32]; xSnprintf(path, sizeof(path), "/proc/%d/cwd", pid); @@ -86,7 +88,7 @@ static void SolarisProcessList_updateCwd(pid_t pid, Process* proc) { } /* Taken from: https://docs.oracle.com/cd/E19253-01/817-6223/6mlkidlom/index.html#tbl-sched-state */ -static inline ProcessState SolarisProcessList_getProcessState(char state) { +static inline ProcessState SolarisProcessTable_getProcessState(char state) { switch (state) { case 'S': return SLEEPING; case 'R': return RUNNABLE; @@ -104,14 +106,14 @@ static inline ProcessState SolarisProcessList_getProcessState(char state) { * system for more info. */ -static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr) { +static int SolarisProcessTable_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr) { bool preExisting; pid_t getpid; // Setup process list - ProcessList* pl = (ProcessList*) listptr; - SolarisProcessList* spl = (SolarisProcessList*) listptr; - Machine* host = pl->host; + ProcessTable* pt = (ProcessTable*) listptr; + SolarisProcessTable* spt = (SolarisProcessTable*) listptr; + Machine* host = pt->host; id_t lwpid_real = _lwpsinfo->pr_lwpid; if (lwpid_real > 1023) { @@ -126,7 +128,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, getpid = lwpid; } - Process* proc = ProcessList_getProcess(pl, getpid, &preExisting, SolarisProcess_new); + Process* proc = ProcessTable_getProcess(pt, getpid, &preExisting, SolarisProcess_new); SolarisProcess* sproc = (SolarisProcess*) proc; const Settings* settings = host->settings; @@ -139,7 +141,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, proc->priority = _lwpsinfo->pr_pri; proc->nice = _lwpsinfo->pr_nice - NZERO; proc->processor = _lwpsinfo->pr_onpro; - proc->state = SolarisProcessList_getProcessState(_lwpsinfo->pr_sname); + proc->state = SolarisProcessTable_getProcessState(_lwpsinfo->pr_sname); // NOTE: This 'percentage' is a 16-bit BINARY FRACTIONS where 1.0 = 0x8000 // Source: https://docs.oracle.com/cd/E19253-01/816-5174/proc-4/index.html // (accessed on 18 November 2017) @@ -169,22 +171,22 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, sproc->realpid = _psinfo->pr_pid; sproc->lwpid = lwpid_real; sproc->zoneid = _psinfo->pr_zoneid; - sproc->zname = SolarisProcessList_readZoneName(spl->kd, sproc); - SolarisProcessList_updateExe(_psinfo->pr_pid, proc); + sproc->zname = SolarisProcessTable_readZoneName(spt->kd, sproc); + SolarisProcessTable_updateExe(_psinfo->pr_pid, proc); Process_updateComm(proc, _psinfo->pr_fname); Process_updateCmdline(proc, _psinfo->pr_psargs, 0, 0); if (settings->ss->flags & PROCESS_FLAG_CWD) { - SolarisProcessList_updateCwd(_psinfo->pr_pid, proc); + SolarisProcessTable_updateCwd(_psinfo->pr_pid, proc); } } // End common code pass 1 if (onMasterLWP) { // Are we on the representative LWP? - proc->ppid = (_psinfo->pr_ppid * 1024); - proc->tgid = (_psinfo->pr_ppid * 1024); + Process_setParent(proc, (_psinfo->pr_ppid * 1024)); + Process_setThreadGroup(proc, (_psinfo->pr_ppid * 1024)); sproc->realppid = _psinfo->pr_ppid; sproc->realtgid = _psinfo->pr_ppid; @@ -200,20 +202,20 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, // Update proc and thread counts based on settings if (proc->isKernelThread && !settings->hideKernelThreads) { - pl->kernelThreads += proc->nlwp; - pl->totalTasks += proc->nlwp + 1; + pt->kernelThreads += proc->nlwp; + pt->totalTasks += proc->nlwp + 1; if (proc->state == RUNNING) { - pl->runningTasks++; + pt->runningTasks++; } } else if (!proc->isKernelThread) { if (proc->state == RUNNING) { - pl->runningTasks++; + pt->runningTasks++; } if (settings->hideUserlandThreads) { - pl->totalTasks++; + pt->totalTasks++; } else { - pl->userlandThreads += proc->nlwp; - pl->totalTasks += proc->nlwp + 1; + pt->userlandThreads += proc->nlwp; + pt->totalTasks += proc->nlwp + 1; } } proc->show = !(settings->hideKernelThreads && proc->isKernelThread); @@ -224,8 +226,8 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, proc->time = _lwpsinfo->pr_time.tv_sec * 100 + _lwpsinfo->pr_time.tv_nsec / 10000000; if (!preExisting) { // Tasks done only for NEW LWPs proc->isUserlandThread = true; - proc->ppid = _psinfo->pr_pid * 1024; - proc->tgid = _psinfo->pr_pid * 1024; + Process_setParent(proc, _psinfo->pr_pid * 1024); + Process_setThreadGroup(proc, _psinfo->pr_pid * 1024); sproc->realppid = _psinfo->pr_pid; sproc->realtgid = _psinfo->pr_pid; proc->starttime_ctime = _lwpsinfo->pr_start.tv_sec; @@ -233,10 +235,10 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, // Top-level process only gets this for the representative LWP if (proc->isKernelThread && !settings->hideKernelThreads) { - proc->show = true; + proc->super.show = true; } if (!proc->isKernelThread && !settings->hideUserlandThreads) { - proc->show = true; + proc->super.show = true; } } // Top-level LWP or subordinate LWP @@ -250,17 +252,17 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, } Process_fillStarttimeBuffer(proc); - ProcessList_add(pl, proc); + ProcessTable_add(pt, proc); } - proc->updated = true; + proc->super.updated = true; // End common code pass 2 return 0; } -void ProcessList_goThroughEntries(ProcessList* super) { +void ProcessTable_goThroughEntries(ProcessTable* super) { super->kernelThreads = 1; - proc_walk(&SolarisProcessList_walkproc, super, PR_WALK_LWP); + proc_walk(&SolarisProcessTable_walkproc, super, PR_WALK_LWP); } diff --git a/solaris/SolarisProcessList.h b/solaris/SolarisProcessTable.h similarity index 64% rename from solaris/SolarisProcessList.h rename to solaris/SolarisProcessTable.h index d8280117d..7c5ae8f38 100644 --- a/solaris/SolarisProcessList.h +++ b/solaris/SolarisProcessTable.h @@ -1,15 +1,13 @@ -#ifndef HEADER_SolarisProcessList -#define HEADER_SolarisProcessList +#ifndef HEADER_SolarisProcessTable +#define HEADER_SolarisProcessTable /* -htop - SolarisProcessList.h +htop - SolarisProcessTable.h (C) 2014 Hisham H. Muhammad (C) 2017,2018 Guy M. Broome Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "config.h" // IWYU pragma: keep - #include #include #include @@ -20,14 +18,14 @@ in the source distribution for its full text. #include #include "Hashtable.h" -#include "ProcessList.h" +#include "ProcessTable.h" #include "UsersTable.h" #include "solaris/SolarisProcess.h" -typedef struct SolarisProcessList_ { - ProcessList super; -} SolarisProcessList; +typedef struct SolarisProcessTable_ { + ProcessTable super; +} SolarisProcessTable; #endif diff --git a/unsupported/Platform.c b/unsupported/Platform.c index df7f3600a..dbfddd916 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -102,8 +102,8 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *fifteen = 0; } -int Platform_getMaxPid(void) { - return 1; +pid_t Platform_getMaxPid(void) { + return INT_MAX; } double Platform_setCPUValues(Meter* this, unsigned int cpu) { diff --git a/unsupported/Platform.h b/unsupported/Platform.h index a718ca09e..c4cd06a04 100644 --- a/unsupported/Platform.h +++ b/unsupported/Platform.h @@ -8,6 +8,9 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include +#include + #include "Action.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" @@ -40,7 +43,7 @@ int Platform_getUptime(void); void Platform_getLoadAverage(double* one, double* five, double* fifteen); -int Platform_getMaxPid(void); +pid_t Platform_getMaxPid(void); double Platform_setCPUValues(Meter* this, unsigned int cpu); @@ -98,7 +101,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -106,4 +109,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/unsupported/UnsupportedProcess.c b/unsupported/UnsupportedProcess.c index 4d8cb0803..3d6d883f3 100644 --- a/unsupported/UnsupportedProcess.c +++ b/unsupported/UnsupportedProcess.c @@ -58,23 +58,24 @@ void Process_delete(Object* cast) { free(cast); } -static void UnsupportedProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const UnsupportedProcess* up = (const UnsupportedProcess*) this; - bool coloring = this->host->settings->highlightMegabytes; +static void UnsupportedProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const UnsupportedProcess* up = (const UnsupportedProcess*) super; + + bool coloring = super->host->settings->highlightMegabytes; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; - (void) up; (void) coloring; (void) n; switch (field) { /* Add platform specific fields */ default: - Process_writeField(this, str, field); + Process_writeField(&up->super, str, field); return; } + RichString_appendWide(str, attr, buffer); } @@ -94,11 +95,18 @@ static int UnsupportedProcess_compareByKey(const Process* v1, const Process* v2, const ProcessClass UnsupportedProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = UnsupportedProcess_rowWriteField }, - .writeField = UnsupportedProcess_writeField, .compareByKey = UnsupportedProcess_compareByKey }; diff --git a/unsupported/UnsupportedProcessList.h b/unsupported/UnsupportedProcessList.h deleted file mode 100644 index 96efdcd2f..000000000 --- a/unsupported/UnsupportedProcessList.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef HEADER_UnsupportedProcessList -#define HEADER_UnsupportedProcessList -/* -htop - UnsupportedProcessList.h -(C) 2014 Hisham H. Muhammad -Released under the GNU GPLv2+, see the COPYING file -in the source distribution for its full text. -*/ - -#include "ProcessList.h" - - -typedef struct UnsupportedProcessList_ { - ProcessList super; -} UnsupportedProcessList; - -#endif diff --git a/unsupported/UnsupportedProcessList.c b/unsupported/UnsupportedProcessTable.c similarity index 59% rename from unsupported/UnsupportedProcessList.c rename to unsupported/UnsupportedProcessTable.c index e56f49782..db43f1eb4 100644 --- a/unsupported/UnsupportedProcessList.c +++ b/unsupported/UnsupportedProcessTable.c @@ -1,43 +1,48 @@ /* -htop - UnsupportedProcessList.c +htop - UnsupportedProcessTable.c (C) 2014 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ -#include "UnsupportedProcessList.h" +#include "config.h" // IWYU pragma: keep + +#include "UnsupportedProcessTable.h" #include #include -#include "ProcessList.h" +#include "ProcessTable.h" #include "UnsupportedProcess.h" -ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - ProcessList* this = xCalloc(1, sizeof(ProcessList)); +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + UnsupportedProcessTable* this = xCalloc(1, sizeof(UnsupportedProcessTable)); + Object_setClass(this, Class(ProcessTable)); - ProcessList_init(this, Class(Process), host, pidMatchList); + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(Process), host, pidMatchList); return this; } -void ProcessList_delete(ProcessList* this) { - ProcessList_done(this); +void ProcessTable_delete(Object* cast) { + UnsupportedProcessTable* this = (UnsupportedProcessTable*) cast; + ProcessTable_done(&this->super); free(this); } -void ProcessList_goThroughEntries(ProcessList* super) { +void ProcessTable_goThroughEntries(ProcessTable* super) { bool preExisting = true; Process* proc; - proc = ProcessList_getProcess(super, 1, &preExisting, UnsupportedProcess_new); + proc = ProcessTable_getProcess(super, 1, &preExisting, UnsupportedProcess_new); /* Empty values */ proc->time = proc->time + 10; - proc->pid = 1; - proc->ppid = 1; - proc->tgid = 0; + Process_setPid(proc, 1); + Process_setParent(proc, 1); + Process_setThreadGroup(proc, 0); Process_updateComm(proc, "commof16char"); Process_updateCmdline(proc, "", 0, 0); @@ -48,12 +53,12 @@ void ProcessList_goThroughEntries(ProcessList* super) { free_and_xStrdup(&proc->procCwd, "/current/working/directory"); } - proc->updated = true; + proc->super.updated = true; proc->state = RUNNING; proc->isKernelThread = false; proc->isUserlandThread = false; - proc->show = true; /* Reflected in settings-> "hideXXX" really */ + proc->super.show = true; /* Reflected in settings-> "hideXXX" really */ proc->pgrp = 0; proc->session = 0; proc->tty_nr = 0; @@ -81,5 +86,5 @@ void ProcessList_goThroughEntries(ProcessList* super) { proc->majflt = 20; if (!preExisting) - ProcessList_add(super, proc); + ProcessTable_add(super, proc); } diff --git a/unsupported/UnsupportedProcessTable.h b/unsupported/UnsupportedProcessTable.h new file mode 100644 index 000000000..1de8087db --- /dev/null +++ b/unsupported/UnsupportedProcessTable.h @@ -0,0 +1,17 @@ +#ifndef HEADER_UnsupportedProcessTable +#define HEADER_UnsupportedProcessTable +/* +htop - UnsupportedProcessTable.h +(C) 2014 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "ProcessTable.h" + + +typedef struct UnsupportedProcessTable_ { + ProcessTable super; +} UnsupportedProcessTable; + +#endif diff --git a/zfs/ZfsArcMeter.c b/zfs/ZfsArcMeter.c index f124272f6..32f5bb34b 100644 --- a/zfs/ZfsArcMeter.c +++ b/zfs/ZfsArcMeter.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "zfs/ZfsArcMeter.h" #include diff --git a/zfs/ZfsCompressedArcMeter.c b/zfs/ZfsCompressedArcMeter.c index 2e494736d..4d47040ca 100644 --- a/zfs/ZfsCompressedArcMeter.c +++ b/zfs/ZfsCompressedArcMeter.c @@ -5,6 +5,8 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep + #include "zfs/ZfsCompressedArcMeter.h" #include