summaryrefslogtreecommitdiffstats
path: root/linux
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:58:07 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:58:07 +0000
commit10eea2ab1bae2a8ec159d81c0446fd8061b33e2b (patch)
treee8270dd60ec096bee8157dbadf029e15ed584592 /linux
parentInitial commit. (diff)
downloadhtop-10eea2ab1bae2a8ec159d81c0446fd8061b33e2b.tar.xz
htop-10eea2ab1bae2a8ec159d81c0446fd8061b33e2b.zip
Adding upstream version 3.3.0.upstream/3.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--linux/CGroupUtils.c532
-rw-r--r--linux/CGroupUtils.h14
-rw-r--r--linux/HugePageMeter.c108
-rw-r--r--linux/HugePageMeter.h15
-rw-r--r--linux/IOPriority.h36
-rw-r--r--linux/IOPriorityPanel.c59
-rw-r--r--linux/IOPriorityPanel.h17
-rw-r--r--linux/LibSensors.c278
-rw-r--r--linux/LibSensors.h19
-rw-r--r--linux/LinuxMachine.c695
-rw-r--r--linux/LinuxMachine.h109
-rw-r--r--linux/LinuxProcess.c446
-rw-r--r--linux/LinuxProcess.h136
-rw-r--r--linux/LinuxProcessTable.c1668
-rw-r--r--linux/LinuxProcessTable.h35
-rw-r--r--linux/Platform.c1087
-rw-r--r--linux/Platform.h156
-rw-r--r--linux/PressureStallMeter.c170
-rw-r--r--linux/PressureStallMeter.h28
-rw-r--r--linux/ProcessField.h54
-rw-r--r--linux/SELinuxMeter.c95
-rw-r--r--linux/SELinuxMeter.h15
-rw-r--r--linux/SystemdMeter.c435
-rw-r--r--linux/SystemdMeter.h18
-rw-r--r--linux/ZramMeter.c84
-rw-r--r--linux/ZramMeter.h21
-rw-r--r--linux/ZramStats.h18
-rw-r--r--linux/ZswapStats.h19
28 files changed, 6367 insertions, 0 deletions
diff --git a/linux/CGroupUtils.c b/linux/CGroupUtils.c
new file mode 100644
index 0000000..f352b8e
--- /dev/null
+++ b/linux/CGroupUtils.c
@@ -0,0 +1,532 @@
+/*
+htop - CGroupUtils.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/CGroupUtils.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "Macros.h"
+#include "XUtils.h"
+
+
+static const char* str_slice_suffix = ".slice";
+static const char* str_system_slice = "system.slice";
+static const char* str_user_slice = "user.slice";
+static const char* str_machine_slice = "machine.slice";
+static const char* str_user_slice_prefix = "/user-";
+static const char* str_system_slice_prefix = "/system-";
+
+static const char* str_lxc_monitor_legacy = "lxc.monitor";
+static const char* str_lxc_payload_legacy = "lxc.payload";
+static const char* str_lxc_monitor_prefix = "lxc.monitor.";
+static const char* str_lxc_payload_prefix = "lxc.payload.";
+
+static const char* str_nspawn_scope_prefix = "machine-";
+static const char* str_nspawn_monitor_label = "/supervisor";
+static const char* str_nspawn_payload_label = "/payload";
+
+static const char* str_snap_scope_prefix = "snap.";
+static const char* str_pod_scope_prefix = "libpod-";
+static const char* str_docker_scope_prefix = "docker-";
+
+static const char* str_service_suffix = ".service";
+static const char* str_scope_suffix = ".scope";
+
+typedef struct StrBuf_state {
+ char* buf;
+ size_t size;
+ size_t pos;
+} StrBuf_state;
+
+typedef bool (*StrBuf_putc_t)(StrBuf_state* p, char c);
+
+static bool StrBuf_putc_count(StrBuf_state* p, ATTR_UNUSED char c) {
+ p->pos++;
+ return true;
+}
+
+static bool StrBuf_putc_write(StrBuf_state* p, char c) {
+ if (p->pos >= p->size)
+ return false;
+
+ p->buf[p->pos] = c;
+ p->pos++;
+ return true;
+}
+
+static bool StrBuf_putsn(StrBuf_state* p, StrBuf_putc_t w, const char* s, size_t count) {
+ for (; count; count--)
+ if (!w(p, *s++))
+ return false;
+
+ return true;
+}
+
+static bool StrBuf_putsz(StrBuf_state* p, StrBuf_putc_t w, const char* s) {
+ while (*s)
+ if (!w(p, *s++))
+ return false;
+
+ return true;
+}
+
+static bool Label_checkEqual(const char* labelStart, size_t labelLen, const char* expected) {
+ return labelLen == strlen(expected) && String_startsWith(labelStart, expected);
+}
+
+static bool Label_checkPrefix(const char* labelStart, size_t labelLen, const char* expected) {
+ return labelLen > strlen(expected) && String_startsWith(labelStart, expected);
+}
+
+static bool Label_checkSuffix(const char* labelStart, size_t labelLen, const char* expected) {
+ return labelLen > strlen(expected) && String_startsWith(labelStart + labelLen - strlen(expected), expected);
+}
+
+static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) {
+ while (*cgroup) {
+ if ('/' == *cgroup) {
+ while ('/' == *cgroup)
+ cgroup++;
+
+ if (!w(s, '/'))
+ return false;
+
+ continue;
+ }
+
+ const char* labelStart = cgroup;
+ const char* nextSlash = String_strchrnul(labelStart, '/');
+ const size_t labelLen = nextSlash - labelStart;
+
+ if (Label_checkEqual(labelStart, labelLen, str_system_slice)) {
+ cgroup = nextSlash;
+
+ if (!StrBuf_putsz(s, w, "[S]"))
+ return false;
+
+ if (String_startsWith(cgroup, str_system_slice_prefix)) {
+ cgroup = String_strchrnul(cgroup + 1, '/');
+ continue;
+ }
+
+ continue;
+ }
+
+ if (Label_checkEqual(labelStart, labelLen, str_machine_slice)) {
+ cgroup = nextSlash;
+
+ if (!StrBuf_putsz(s, w, "[M]"))
+ return false;
+
+ continue;
+ }
+
+ if (Label_checkEqual(labelStart, labelLen, str_user_slice)) {
+ cgroup = nextSlash;
+
+ if (!StrBuf_putsz(s, w, "[U]"))
+ return false;
+
+ if (!String_startsWith(cgroup, str_user_slice_prefix))
+ continue;
+
+ const char* userSliceSlash = String_strchrnul(cgroup + strlen(str_user_slice_prefix), '/');
+ const char* sliceSpec = userSliceSlash - strlen(str_slice_suffix);
+
+ if (!String_startsWith(sliceSpec, str_slice_suffix))
+ continue;
+
+ const size_t sliceNameLen = sliceSpec - (cgroup + strlen(str_user_slice_prefix));
+
+ s->pos--;
+ if (!w(s, ':'))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_user_slice_prefix), sliceNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = userSliceSlash;
+
+ continue;
+ }
+
+ if (Label_checkSuffix(labelStart, labelLen, str_slice_suffix)) {
+ const size_t sliceNameLen = labelLen - strlen(str_slice_suffix);
+
+ if (!w(s, '['))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup, sliceNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) {
+ const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix);
+
+ if (!StrBuf_putsz(s, w, "[lxc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ if (Label_checkPrefix(labelStart, labelLen, str_lxc_monitor_prefix)) {
+ const size_t cgroupNameLen = labelLen - strlen(str_lxc_monitor_prefix);
+
+ if (!StrBuf_putsz(s, w, "[LXC:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_monitor_prefix), cgroupNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ // LXC legacy cgroup naming
+ if (Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy) ||
+ Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) {
+ bool isMonitor = Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy);
+
+ labelStart = nextSlash;
+ while (*labelStart == '/')
+ labelStart++;
+
+ nextSlash = String_strchrnul(labelStart, '/');
+ if (nextSlash - labelStart > 0) {
+ if (!StrBuf_putsz(s, w, isMonitor ? "[LXC:" : "[lxc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+ continue;
+ }
+
+ labelStart = cgroup;
+ nextSlash = labelStart + labelLen;
+ }
+
+ if (Label_checkSuffix(labelStart, labelLen, str_service_suffix)) {
+ const size_t serviceNameLen = labelLen - strlen(str_service_suffix);
+
+ if (String_startsWith(cgroup, "user@")) {
+ cgroup = nextSlash;
+
+ while (*cgroup == '/')
+ cgroup++;
+
+ continue;
+ }
+
+ if (!StrBuf_putsn(s, w, cgroup, serviceNameLen))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) {
+ const size_t scopeNameLen = labelLen - strlen(str_scope_suffix);
+
+ if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) {
+ const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix);
+
+ const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label);
+
+ if (!StrBuf_putsz(s, w, is_monitor ? "[SNC:" : "[snc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen))
+ return false;
+
+ if (!w(s, ']'))
+ return false;
+
+ cgroup = nextSlash;
+ if (String_startsWith(nextSlash, str_nspawn_monitor_label))
+ cgroup += strlen(str_nspawn_monitor_label);
+ else if (String_startsWith(nextSlash, str_nspawn_payload_label))
+ cgroup += strlen(str_nspawn_payload_label);
+
+ continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_snap_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "!snap:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_snap_scope_prefix), nextDot - (labelStart + strlen(str_snap_scope_prefix))))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "!pod:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix),
+ MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12)))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "!docker:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix),
+ MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12)))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ if (!w(s, '!'))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup, scopeNameLen))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ // Default behavior: Copy the full label
+ cgroup = labelStart;
+
+ if (!StrBuf_putsn(s, w, cgroup, labelLen))
+ return false;
+
+ cgroup = nextSlash;
+ }
+
+ return true;
+}
+
+char* CGroup_filterName(const char* cgroup) {
+ StrBuf_state s = {
+ .buf = NULL,
+ .size = 0,
+ .pos = 0,
+ };
+
+ if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_count)) {
+ return NULL;
+ }
+
+ s.buf = xCalloc(s.pos + 1, sizeof(char));
+ s.size = s.pos;
+ s.pos = 0;
+
+ if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_write)) {
+ free(s.buf);
+ return NULL;
+ }
+
+ s.buf[s.size] = '\0';
+ return s.buf;
+}
+
+static bool CGroup_filterContainer_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) {
+ while (*cgroup) {
+ if ('/' == *cgroup) {
+ while ('/' == *cgroup)
+ cgroup++;
+
+ continue;
+ }
+
+ const char* labelStart = cgroup;
+ const char* nextSlash = String_strchrnul(labelStart, '/');
+ const size_t labelLen = nextSlash - labelStart;
+
+ if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) {
+ const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix);
+
+ if (!StrBuf_putsz(s, w, "/lxc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ // LXC legacy cgroup naming
+ if (Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) {
+ labelStart = nextSlash;
+ while (*labelStart == '/')
+ labelStart++;
+
+ nextSlash = String_strchrnul(labelStart, '/');
+ if (nextSlash - labelStart > 0) {
+ if (!StrBuf_putsz(s, w, "/lxc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart))
+ return false;
+
+ cgroup = nextSlash;
+ continue;
+ }
+
+ labelStart = cgroup;
+ nextSlash = labelStart + labelLen;
+ }
+
+ if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) {
+ const size_t scopeNameLen = labelLen - strlen(str_scope_suffix);
+
+ if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) {
+ const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix);
+
+ const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label);
+
+ if (!is_monitor) {
+ if (!StrBuf_putsz(s, w, "/snc:"))
+ return false;
+
+ if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen))
+ return false;
+ }
+
+ cgroup = nextSlash;
+ if (String_startsWith(nextSlash, str_nspawn_monitor_label))
+ cgroup += strlen(str_nspawn_monitor_label);
+ else if (String_startsWith(nextSlash, str_nspawn_payload_label))
+ cgroup += strlen(str_nspawn_payload_label);
+
+ continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "/pod:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix),
+ MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12)))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) {
+ const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.');
+
+ if (!StrBuf_putsz(s, w, "!docker:"))
+ return false;
+
+ if (nextDot >= labelStart + scopeNameLen) {
+ nextDot = labelStart + scopeNameLen;
+ }
+
+ if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix),
+ MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12)))
+ return false;
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ cgroup = nextSlash;
+
+ continue;
+ }
+
+ cgroup = nextSlash;
+ }
+
+ return true;
+}
+
+char* CGroup_filterContainer(const char* cgroup) {
+ StrBuf_state s = {
+ .buf = NULL,
+ .size = 0,
+ .pos = 0,
+ };
+
+ if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_count)) {
+ return NULL;
+ }
+
+ if (!s.pos) {
+ return xStrdup("/");
+ }
+
+ s.buf = xCalloc(s.pos + 1, sizeof(char));
+ s.size = s.pos;
+ s.pos = 0;
+
+ if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_write)) {
+ free(s.buf);
+ return NULL;
+ }
+
+ s.buf[s.size] = '\0';
+ return s.buf;
+}
diff --git a/linux/CGroupUtils.h b/linux/CGroupUtils.h
new file mode 100644
index 0000000..972a15b
--- /dev/null
+++ b/linux/CGroupUtils.h
@@ -0,0 +1,14 @@
+#ifndef HEADER_CGroupUtils
+#define HEADER_CGroupUtils
+/*
+htop - CGroupUtils.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+char* CGroup_filterName(const char* cgroup);
+char* CGroup_filterContainer(const char* cgroup);
+
+#endif /* HEADER_CGroupUtils */
diff --git a/linux/HugePageMeter.c b/linux/HugePageMeter.c
new file mode 100644
index 0000000..058ab4b
--- /dev/null
+++ b/linux/HugePageMeter.c
@@ -0,0 +1,108 @@
+/*
+htop - HugePageMeter.c
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/HugePageMeter.h"
+
+#include <assert.h>
+#include <math.h>
+#include <stddef.h>
+
+#include "CRT.h"
+#include "Machine.h"
+#include "Macros.h"
+#include "Object.h"
+#include "RichString.h"
+#include "linux/LinuxMachine.h"
+
+
+static const char* HugePageMeter_active_labels[4] = { NULL, NULL, NULL, NULL };
+
+static const int HugePageMeter_attributes[] = {
+ HUGEPAGE_1,
+ HUGEPAGE_2,
+ HUGEPAGE_3,
+ HUGEPAGE_4,
+};
+
+static const char* const HugePageMeter_labels[] = {
+ " 64K:", " 128K:", " 256K:", " 512K:",
+ " 1M:", " 2M:", " 4M:", " 8M:", " 16M:", " 32M:", " 64M:", " 128M:", " 256M:", " 512M:",
+ " 1G:", " 2G:", " 4G:", " 8G:", " 16G:", " 32G:", " 64G:", " 128G:", " 256G:", " 512G:",
+};
+
+static void HugePageMeter_updateValues(Meter* this) {
+ assert(ARRAYSIZE(HugePageMeter_labels) == HTOP_HUGEPAGE_COUNT);
+
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
+ int written;
+ memory_t usedTotal = 0;
+ unsigned nextUsed = 0;
+
+ const LinuxMachine* host = (const LinuxMachine*) this->host;
+ this->total = host->totalHugePageMem;
+ this->values[0] = 0;
+ HugePageMeter_active_labels[0] = " used:";
+ for (unsigned i = 1; i < ARRAYSIZE(HugePageMeter_active_labels); i++) {
+ this->values[i] = NAN;
+ HugePageMeter_active_labels[i] = NULL;
+ }
+ for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {
+ memory_t value = host->usedHugePageMem[i];
+ if (value != MEMORY_MAX) {
+ this->values[nextUsed] = value;
+ usedTotal += value;
+ HugePageMeter_active_labels[nextUsed] = HugePageMeter_labels[i];
+ if (++nextUsed == ARRAYSIZE(HugePageMeter_active_labels)) {
+ break;
+ }
+ }
+ }
+
+ written = Meter_humanUnit(buffer, usedTotal, size);
+ METER_BUFFER_CHECK(buffer, size, written);
+
+ METER_BUFFER_APPEND_CHR(buffer, size, '/');
+
+ Meter_humanUnit(buffer, this->total, size);
+}
+
+static void HugePageMeter_display(const Object* cast, RichString* out) {
+ char buffer[50];
+ const Meter* this = (const Meter*)cast;
+
+ RichString_writeAscii(out, CRT_colors[METER_TEXT], ":");
+ Meter_humanUnit(buffer, this->total, sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+
+ for (unsigned i = 0; i < ARRAYSIZE(HugePageMeter_active_labels); i++) {
+ if (!HugePageMeter_active_labels[i]) {
+ break;
+ }
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], HugePageMeter_active_labels[i]);
+ Meter_humanUnit(buffer, this->values[i], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[HUGEPAGE_1 + i], buffer);
+ }
+}
+
+const MeterClass HugePageMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = HugePageMeter_display,
+ },
+ .updateValues = HugePageMeter_updateValues,
+ .defaultMode = BAR_METERMODE,
+ .maxItems = ARRAYSIZE(HugePageMeter_active_labels),
+ .total = 100.0,
+ .attributes = HugePageMeter_attributes,
+ .name = "HugePages",
+ .uiName = "HugePages",
+ .caption = "HP"
+};
diff --git a/linux/HugePageMeter.h b/linux/HugePageMeter.h
new file mode 100644
index 0000000..d74a19e
--- /dev/null
+++ b/linux/HugePageMeter.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_HugePageMeter
+#define HEADER_HugePageMeter
+/*
+htop - HugePageMeter.h
+(C) 2021 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Meter.h"
+
+
+extern const MeterClass HugePageMeter_class;
+
+#endif /* HEADER_HugePageMeter */
diff --git a/linux/IOPriority.h b/linux/IOPriority.h
new file mode 100644
index 0000000..78bc470
--- /dev/null
+++ b/linux/IOPriority.h
@@ -0,0 +1,36 @@
+#ifndef HEADER_IOPriority
+#define HEADER_IOPriority
+/*
+htop - IOPriority.h
+(C) 2004-2012 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+
+Based on ionice,
+Copyright (C) 2005 Jens Axboe <jens@axboe.dk>
+Released under the terms of the GNU General Public License version 2
+*/
+
+enum {
+ IOPRIO_CLASS_NONE,
+ IOPRIO_CLASS_RT,
+ IOPRIO_CLASS_BE,
+ IOPRIO_CLASS_IDLE,
+};
+
+#define IOPRIO_WHO_PROCESS 1
+
+#define IOPRIO_CLASS_SHIFT (13)
+#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1)
+
+#define IOPriority_class(ioprio_) ((int) ((ioprio_) >> IOPRIO_CLASS_SHIFT) )
+#define IOPriority_data(ioprio_) ((int) ((ioprio_) & IOPRIO_PRIO_MASK) )
+
+typedef int IOPriority;
+
+#define IOPriority_tuple(class_, data_) (((class_) << IOPRIO_CLASS_SHIFT) | (data_))
+
+#define IOPriority_None IOPriority_tuple(IOPRIO_CLASS_NONE, 0)
+#define IOPriority_Idle IOPriority_tuple(IOPRIO_CLASS_IDLE, 7)
+
+#endif
diff --git a/linux/IOPriorityPanel.c b/linux/IOPriorityPanel.c
new file mode 100644
index 0000000..8d0ef7b
--- /dev/null
+++ b/linux/IOPriorityPanel.c
@@ -0,0 +1,59 @@
+/*
+htop - IOPriorityPanel.c
+(C) 2004-2012 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/IOPriorityPanel.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "FunctionBar.h"
+#include "ListItem.h"
+#include "Object.h"
+#include "XUtils.h"
+#include "IOPriority.h"
+
+
+Panel* IOPriorityPanel_new(IOPriority currPrio) {
+ Panel* this = Panel_new(1, 1, 1, 1, Class(ListItem), true, FunctionBar_newEnterEsc("Set ", "Cancel "));
+
+ Panel_setHeader(this, "IO Priority:");
+ Panel_add(this, (Object*) ListItem_new("None (based on nice)", IOPriority_None));
+ if (currPrio == IOPriority_None) {
+ Panel_setSelected(this, 0);
+ }
+ static const struct {
+ int klass;
+ const char* name;
+ } classes[] = {
+ { .klass = IOPRIO_CLASS_RT, .name = "Realtime" },
+ { .klass = IOPRIO_CLASS_BE, .name = "Best-effort" },
+ { .klass = 0, .name = NULL }
+ };
+ for (int c = 0; classes[c].name; c++) {
+ for (int i = 0; i < 8; i++) {
+ char name[50];
+ xSnprintf(name, sizeof(name), "%s %d %s", classes[c].name, i, i == 0 ? "(High)" : (i == 7 ? "(Low)" : ""));
+ IOPriority ioprio = IOPriority_tuple(classes[c].klass, i);
+ Panel_add(this, (Object*) ListItem_new(name, ioprio));
+ if (currPrio == ioprio) {
+ Panel_setSelected(this, Panel_size(this) - 1);
+ }
+ }
+ }
+ Panel_add(this, (Object*) ListItem_new("Idle", IOPriority_Idle));
+ if (currPrio == IOPriority_Idle) {
+ Panel_setSelected(this, Panel_size(this) - 1);
+ }
+ return this;
+}
+
+IOPriority IOPriorityPanel_getIOPriority(Panel* this) {
+ const ListItem* selected = (ListItem*) Panel_getSelected(this);
+ return selected ? selected->key : IOPriority_None;
+}
diff --git a/linux/IOPriorityPanel.h b/linux/IOPriorityPanel.h
new file mode 100644
index 0000000..cb5b338
--- /dev/null
+++ b/linux/IOPriorityPanel.h
@@ -0,0 +1,17 @@
+#ifndef HEADER_IOPriorityPanel
+#define HEADER_IOPriorityPanel
+/*
+htop - IOPriorityPanel.h
+(C) 2004-2012 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Panel.h"
+#include "linux/IOPriority.h"
+
+Panel* IOPriorityPanel_new(IOPriority currPrio);
+
+IOPriority IOPriorityPanel_getIOPriority(Panel* this);
+
+#endif
diff --git a/linux/LibSensors.c b/linux/LibSensors.c
new file mode 100644
index 0000000..5373416
--- /dev/null
+++ b/linux/LibSensors.c
@@ -0,0 +1,278 @@
+/*
+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 "linux/LibSensors.h"
+
+#ifdef HAVE_SENSORS_SENSORS_H
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sensors/sensors.h>
+
+#include "Macros.h"
+#include "XUtils.h"
+#include "linux/LinuxMachine.h"
+
+
+#ifdef BUILD_STATIC
+
+#define sym_sensors_init sensors_init
+#define sym_sensors_cleanup sensors_cleanup
+#define sym_sensors_get_detected_chips sensors_get_detected_chips
+#define sym_sensors_get_features sensors_get_features
+#define sym_sensors_get_subfeature sensors_get_subfeature
+#define sym_sensors_get_value sensors_get_value
+
+#else
+
+static int (*sym_sensors_init)(FILE*);
+static void (*sym_sensors_cleanup)(void);
+static const sensors_chip_name* (*sym_sensors_get_detected_chips)(const sensors_chip_name*, int*);
+static const sensors_feature* (*sym_sensors_get_features)(const sensors_chip_name*, int*);
+static const sensors_subfeature* (*sym_sensors_get_subfeature)(const sensors_chip_name*, const sensors_feature*, sensors_subfeature_type);
+static int (*sym_sensors_get_value)(const sensors_chip_name*, int, double*);
+
+static void* dlopenHandle = NULL;
+
+#endif /* BUILD_STATIC */
+
+int LibSensors_init(void) {
+#ifdef BUILD_STATIC
+
+ return sym_sensors_init(NULL);
+
+#else
+
+ if (!dlopenHandle) {
+ /* Find the unversioned libsensors.so (symlink) and prefer that, but Debian has .so.5 and Fedora .so.4 without
+ matching symlinks (unless people install the -dev packages) */
+ dlopenHandle = dlopen("libsensors.so", RTLD_LAZY);
+ if (!dlopenHandle)
+ dlopenHandle = dlopen("libsensors.so.5", RTLD_LAZY);
+ if (!dlopenHandle)
+ dlopenHandle = dlopen("libsensors.so.4", RTLD_LAZY);
+ if (!dlopenHandle)
+ goto dlfailure;
+
+ /* Clear any errors */
+ dlerror();
+
+ #define resolve(symbolname) do { \
+ *(void **)(&sym_##symbolname) = dlsym(dlopenHandle, #symbolname); \
+ if (!sym_##symbolname || dlerror() != NULL) \
+ goto dlfailure; \
+ } while(0)
+
+ resolve(sensors_init);
+ resolve(sensors_cleanup);
+ resolve(sensors_get_detected_chips);
+ resolve(sensors_get_features);
+ resolve(sensors_get_subfeature);
+ resolve(sensors_get_value);
+
+ #undef resolve
+ }
+
+ return sym_sensors_init(NULL);
+
+
+dlfailure:
+ if (dlopenHandle) {
+ dlclose(dlopenHandle);
+ dlopenHandle = NULL;
+ }
+ return -1;
+
+#endif /* BUILD_STATIC */
+}
+
+void LibSensors_cleanup(void) {
+#ifdef BUILD_STATIC
+
+ sym_sensors_cleanup();
+
+#else
+
+ if (dlopenHandle) {
+ sym_sensors_cleanup();
+
+ dlclose(dlopenHandle);
+ dlopenHandle = NULL;
+ }
+
+#endif /* BUILD_STATIC */
+}
+
+int LibSensors_reload(void) {
+#ifndef BUILD_STATIC
+ if (!dlopenHandle) {
+ errno = ENOTSUP;
+ return -1;
+ }
+#endif /* !BUILD_STATIC */
+
+ sym_sensors_cleanup();
+ return sym_sensors_init(NULL);
+}
+
+static int tempDriverPriority(const sensors_chip_name* chip) {
+ static const struct TempDriverDefs {
+ const char* prefix;
+ int priority;
+ } tempDrivers[] = {
+ { "coretemp", 0 },
+ { "via_cputemp", 0 },
+ { "cpu_thermal", 0 },
+ { "k10temp", 0 },
+ { "zenpower", 0 },
+ /* Low priority drivers */
+ { "acpitz", 1 },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE(tempDrivers); i++)
+ if (String_eq(chip->prefix, tempDrivers[i].prefix))
+ return tempDrivers[i].priority;
+
+ return -1;
+}
+
+void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs) {
+ assert(existingCPUs > 0 && existingCPUs < 16384);
+
+ double* data = xMallocArray(existingCPUs + 1, sizeof(double));
+ for (size_t i = 0; i < existingCPUs + 1; i++)
+ data[i] = NAN;
+
+#ifndef BUILD_STATIC
+ if (!dlopenHandle)
+ goto out;
+#endif /* !BUILD_STATIC */
+
+ unsigned int coreTempCount = 0;
+ int topPriority = 99;
+
+ int n = 0;
+ for (const sensors_chip_name* chip = sym_sensors_get_detected_chips(NULL, &n); chip; chip = sym_sensors_get_detected_chips(NULL, &n)) {
+ const int priority = tempDriverPriority(chip);
+ if (priority < 0)
+ continue;
+
+ if (priority > topPriority)
+ continue;
+
+ if (priority < topPriority) {
+ /* Clear data from lower priority sensor */
+ for (size_t i = 0; i < existingCPUs + 1; i++)
+ data[i] = NAN;
+ }
+
+ topPriority = priority;
+
+ int m = 0;
+ for (const sensors_feature* feature = sym_sensors_get_features(chip, &m); feature; feature = sym_sensors_get_features(chip, &m)) {
+ if (feature->type != SENSORS_FEATURE_TEMP)
+ continue;
+
+ if (!feature->name || !String_startsWith(feature->name, "temp"))
+ continue;
+
+ unsigned long int tempID = strtoul(feature->name + strlen("temp"), NULL, 10);
+ if (tempID == 0 || tempID == ULONG_MAX)
+ continue;
+
+ /* Feature name IDs start at 1, adjust to start at 0 to match data indices */
+ tempID--;
+
+ if (tempID > existingCPUs)
+ continue;
+
+ const sensors_subfeature* subFeature = sym_sensors_get_subfeature(chip, feature, SENSORS_SUBFEATURE_TEMP_INPUT);
+ if (!subFeature)
+ continue;
+
+ double temp;
+ int r = sym_sensors_get_value(chip, subFeature->number, &temp);
+ if (r != 0)
+ continue;
+
+ /* If already set, e.g. Ryzen reporting platform temperature for each die, use the bigger one */
+ if (isNaN(data[tempID])) {
+ data[tempID] = temp;
+ if (tempID > 0)
+ coreTempCount++;
+ } else {
+ data[tempID] = MAXIMUM(data[tempID], temp);
+ }
+ }
+ }
+
+ /* Adjust data for chips not providing a platform temperature */
+ if (coreTempCount + 1 == activeCPUs || coreTempCount + 1 == activeCPUs / 2) {
+ memmove(&data[1], &data[0], existingCPUs * sizeof(*data));
+ data[0] = NAN;
+ coreTempCount++;
+
+ /* Check for further adjustments */
+ }
+
+ /* Only package temperature - copy to all cores */
+ if (coreTempCount == 0 && !isNaN(data[0])) {
+ for (unsigned int i = 1; i <= existingCPUs; i++)
+ data[i] = data[0];
+
+ /* No further adjustments */
+ goto out;
+ }
+
+ /* No package temperature - set to max core temperature */
+ if (coreTempCount > 0 && isNaN(data[0])) {
+ double maxTemp = -HUGE_VAL;
+ for (unsigned int i = 1; i <= existingCPUs; i++) {
+ if (isgreater(data[i], maxTemp)) {
+ maxTemp = data[i];
+ data[0] = data[i];
+ }
+ }
+
+ /* Check for further adjustments */
+ }
+
+ /* Only temperature for core 0, maybe Ryzen - copy to all other cores */
+ if (coreTempCount == 1 && !isNaN(data[1])) {
+ for (unsigned int i = 2; i <= existingCPUs; i++)
+ data[i] = data[1];
+
+ /* No further adjustments */
+ goto out;
+ }
+
+ /* Half the temperatures, probably HT/SMT - copy to second half */
+ const unsigned int delta = activeCPUs / 2;
+ if (coreTempCount == delta) {
+ memcpy(&data[delta + 1], &data[1], delta * sizeof(*data));
+
+ /* No further adjustments */
+ goto out;
+ }
+
+out:
+ for (unsigned int i = 0; i <= existingCPUs; i++)
+ cpus[i].temperature = data[i];
+
+ free(data);
+}
+
+#endif /* HAVE_SENSORS_SENSORS_H */
diff --git a/linux/LibSensors.h b/linux/LibSensors.h
new file mode 100644
index 0000000..6f05448
--- /dev/null
+++ b/linux/LibSensors.h
@@ -0,0 +1,19 @@
+#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"
+
+
+int LibSensors_init(void);
+void LibSensors_cleanup(void);
+int LibSensors_reload(void);
+
+void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs);
+
+#endif /* HEADER_LibSensors */
diff --git a/linux/LinuxMachine.c b/linux/LinuxMachine.c
new file mode 100644
index 0000000..ae2930d
--- /dev/null
+++ b/linux/LinuxMachine.c
@@ -0,0 +1,695 @@
+/*
+htop - LinuxMachine.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/LinuxMachine.h"
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "Compat.h"
+#include "CRT.h"
+#include "Macros.h"
+#include "ProcessTable.h"
+#include "Row.h"
+#include "Settings.h"
+#include "UsersTable.h"
+#include "XUtils.h"
+
+#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep
+
+#ifdef HAVE_SENSORS_SENSORS_H
+#include "LibSensors.h"
+#endif
+
+#ifndef O_PATH
+#define O_PATH 010000000 // declare for ancient glibc versions
+#endif
+
+/* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF
+ * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD
+ */
+static void LinuxMachine_updateCPUcount(LinuxMachine* this) {
+ unsigned int existing = 0, active = 0;
+ Machine* super = &this->super;
+
+ // Initialize the cpuData array before anything else.
+ if (!this->cpuData) {
+ this->cpuData = xCalloc(2, sizeof(CPUData));
+ this->cpuData[0].online = true; /* average is always "online" */
+ this->cpuData[1].online = true;
+ super->activeCPUs = 1;
+ super->existingCPUs = 1;
+ }
+
+ DIR* dir = opendir("/sys/devices/system/cpu");
+ if (!dir)
+ return;
+
+ unsigned int currExisting = super->existingCPUs;
+
+ const struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
+ continue;
+
+ if (!String_startsWith(entry->d_name, "cpu"))
+ continue;
+
+ char* endp;
+ unsigned long int id = strtoul(entry->d_name + 3, &endp, 10);
+ if (id == ULONG_MAX || endp == entry->d_name + 3 || *endp != '\0')
+ continue;
+
+#ifdef HAVE_OPENAT
+ int cpuDirFd = openat(dirfd(dir), entry->d_name, O_DIRECTORY | O_PATH | O_NOFOLLOW);
+ if (cpuDirFd < 0)
+ continue;
+#else
+ char cpuDirFd[4096];
+ xSnprintf(cpuDirFd, sizeof(cpuDirFd), "/sys/devices/system/cpu/%s", entry->d_name);
+#endif
+
+ existing++;
+
+ /* readdir() iterates with no specific order */
+ unsigned int max = MAXIMUM(existing, id + 1);
+ if (max > currExisting) {
+ this->cpuData = xReallocArrayZero(this->cpuData, currExisting ? (currExisting + 1) : 0, max + /* aggregate */ 1, sizeof(CPUData));
+ this->cpuData[0].online = true; /* average is always "online" */
+ currExisting = max;
+ }
+
+ char buffer[8];
+ ssize_t res = xReadfileat(cpuDirFd, "online", buffer, sizeof(buffer));
+ /* If the file "online" does not exist or on failure count as active */
+ if (res < 1 || buffer[0] != '0') {
+ active++;
+ this->cpuData[id + 1].online = true;
+ } else {
+ this->cpuData[id + 1].online = false;
+ }
+
+ Compat_openatArgClose(cpuDirFd);
+ }
+
+ closedir(dir);
+
+ // return if no CPU is found
+ if (existing < 1)
+ return;
+
+#ifdef HAVE_SENSORS_SENSORS_H
+ /* When started with offline CPUs, libsensors does not monitor those,
+ * even when they become online. */
+ if (super->existingCPUs != 0 && (active > super->activeCPUs || currExisting > super->existingCPUs))
+ LibSensors_reload();
+#endif
+
+ super->activeCPUs = active;
+ assert(existing == currExisting);
+ super->existingCPUs = currExisting;
+}
+
+static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) {
+ Machine* host = &this->super;
+ memory_t availableMem = 0;
+ memory_t freeMem = 0;
+ memory_t totalMem = 0;
+ memory_t buffersMem = 0;
+ memory_t cachedMem = 0;
+ memory_t sharedMem = 0;
+ memory_t swapTotalMem = 0;
+ memory_t swapCacheMem = 0;
+ memory_t swapFreeMem = 0;
+ memory_t sreclaimableMem = 0;
+ memory_t zswapCompMem = 0;
+ memory_t zswapOrigMem = 0;
+
+ FILE* file = fopen(PROCMEMINFOFILE, "r");
+ if (!file)
+ CRT_fatalError("Cannot open " PROCMEMINFOFILE);
+
+ char buffer[128];
+ while (fgets(buffer, sizeof(buffer), file)) {
+
+ #define tryRead(label, variable) \
+ if (String_startsWith(buffer, label)) { \
+ memory_t parsed_; \
+ if (sscanf(buffer + strlen(label), "%llu kB", &parsed_) == 1) { \
+ (variable) = parsed_; \
+ } \
+ break; \
+ } else (void) 0 /* Require a ";" after the macro use. */
+
+ switch (buffer[0]) {
+ case 'M':
+ tryRead("MemAvailable:", availableMem);
+ tryRead("MemFree:", freeMem);
+ tryRead("MemTotal:", totalMem);
+ break;
+ case 'B':
+ tryRead("Buffers:", buffersMem);
+ break;
+ case 'C':
+ tryRead("Cached:", cachedMem);
+ break;
+ case 'S':
+ switch (buffer[1]) {
+ case 'h':
+ tryRead("Shmem:", sharedMem);
+ break;
+ case 'w':
+ tryRead("SwapTotal:", swapTotalMem);
+ tryRead("SwapCached:", swapCacheMem);
+ tryRead("SwapFree:", swapFreeMem);
+ break;
+ case 'R':
+ tryRead("SReclaimable:", sreclaimableMem);
+ break;
+ }
+ break;
+ case 'Z':
+ tryRead("Zswap:", zswapCompMem);
+ tryRead("Zswapped:", zswapOrigMem);
+ break;
+ }
+
+ #undef tryRead
+ }
+
+ fclose(file);
+
+ /*
+ * Compute memory partition like procps(free)
+ * https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c
+ *
+ * Adjustments:
+ * - Shmem in part of Cached (see https://lore.kernel.org/patchwork/patch/648763/),
+ * do not show twice by subtracting from Cached and do not subtract twice from used.
+ */
+ host->totalMem = totalMem;
+ host->cachedMem = cachedMem + sreclaimableMem - sharedMem;
+ host->sharedMem = sharedMem;
+ const memory_t usedDiff = freeMem + cachedMem + sreclaimableMem + buffersMem;
+ host->usedMem = (totalMem >= usedDiff) ? totalMem - usedDiff : totalMem - freeMem;
+ host->buffersMem = buffersMem;
+ host->availableMem = availableMem != 0 ? MINIMUM(availableMem, totalMem) : freeMem;
+ host->totalSwap = swapTotalMem;
+ host->usedSwap = swapTotalMem - swapFreeMem - swapCacheMem;
+ host->cachedSwap = swapCacheMem;
+ this->zswap.usedZswapComp = zswapCompMem;
+ this->zswap.usedZswapOrig = zswapOrigMem;
+}
+
+static void LinuxMachine_scanHugePages(LinuxMachine* this) {
+ this->totalHugePageMem = 0;
+ for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {
+ this->usedHugePageMem[i] = MEMORY_MAX;
+ }
+
+ DIR* dir = opendir("/sys/kernel/mm/hugepages");
+ if (!dir)
+ return;
+
+ const struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ const char* name = entry->d_name;
+
+ /* Ignore all non-directories */
+ if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
+ continue;
+
+ if (!String_startsWith(name, "hugepages-"))
+ continue;
+
+ char* endptr;
+ unsigned long int hugePageSize = strtoul(name + strlen("hugepages-"), &endptr, 10);
+ if (!endptr || *endptr != 'k')
+ continue;
+
+ char content[64];
+ char hugePagePath[128];
+ ssize_t r;
+
+ xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/nr_hugepages", name);
+ r = xReadfile(hugePagePath, content, sizeof(content));
+ if (r <= 0)
+ continue;
+
+ memory_t total = strtoull(content, NULL, 10);
+ if (total == 0)
+ continue;
+
+ xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/free_hugepages", name);
+ r = xReadfile(hugePagePath, content, sizeof(content));
+ if (r <= 0)
+ continue;
+
+ memory_t free = strtoull(content, NULL, 10);
+
+ int shift = ffsl(hugePageSize) - 1 - (HTOP_HUGEPAGE_BASE_SHIFT - 10);
+ assert(shift >= 0 && shift < HTOP_HUGEPAGE_COUNT);
+
+ this->totalHugePageMem += total * hugePageSize;
+ this->usedHugePageMem[shift] = (total - free) * hugePageSize;
+ }
+
+ closedir(dir);
+}
+
+static void LinuxMachine_scanZramInfo(LinuxMachine* this) {
+ memory_t totalZram = 0;
+ memory_t usedZramComp = 0;
+ memory_t usedZramOrig = 0;
+
+ char mm_stat[34];
+ char disksize[34];
+
+ unsigned int i = 0;
+ for (;;) {
+ xSnprintf(mm_stat, sizeof(mm_stat), "/sys/block/zram%u/mm_stat", i);
+ xSnprintf(disksize, sizeof(disksize), "/sys/block/zram%u/disksize", i);
+ i++;
+ FILE* disksize_file = fopen(disksize, "r");
+ FILE* mm_stat_file = fopen(mm_stat, "r");
+ if (disksize_file == NULL || mm_stat_file == NULL) {
+ if (disksize_file) {
+ fclose(disksize_file);
+ }
+ if (mm_stat_file) {
+ fclose(mm_stat_file);
+ }
+ break;
+ }
+ memory_t size = 0;
+ memory_t orig_data_size = 0;
+ memory_t compr_data_size = 0;
+
+ if (!fscanf(disksize_file, "%llu\n", &size) ||
+ !fscanf(mm_stat_file, " %llu %llu", &orig_data_size, &compr_data_size)) {
+ fclose(disksize_file);
+ fclose(mm_stat_file);
+ break;
+ }
+
+ totalZram += size;
+ usedZramComp += compr_data_size;
+ usedZramOrig += orig_data_size;
+
+ fclose(disksize_file);
+ fclose(mm_stat_file);
+ }
+
+ this->zram.totalZram = totalZram / 1024;
+ this->zram.usedZramComp = usedZramComp / 1024;
+ this->zram.usedZramOrig = usedZramOrig / 1024;
+ if (this->zram.usedZramComp > this->zram.usedZramOrig) {
+ this->zram.usedZramComp = this->zram.usedZramOrig;
+ }
+}
+
+static void LinuxMachine_scanZfsArcstats(LinuxMachine* this) {
+ memory_t dbufSize = 0;
+ memory_t dnodeSize = 0;
+ memory_t bonusSize = 0;
+
+ FILE* file = fopen(PROCARCSTATSFILE, "r");
+ if (file == NULL) {
+ this->zfs.enabled = 0;
+ return;
+ }
+ char buffer[128];
+ while (fgets(buffer, 128, file)) {
+ #define tryRead(label, variable) \
+ if (String_startsWith(buffer, label)) { \
+ sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
+ break; \
+ } else (void) 0 /* Require a ";" after the macro use. */
+ #define tryReadFlag(label, variable, flag) \
+ if (String_startsWith(buffer, label)) { \
+ (flag) = sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
+ break; \
+ } else (void) 0 /* Require a ";" after the macro use. */
+
+ switch (buffer[0]) {
+ case 'c':
+ tryRead("c_min", &this->zfs.min);
+ tryRead("c_max", &this->zfs.max);
+ tryReadFlag("compressed_size", &this->zfs.compressed, this->zfs.isCompressed);
+ break;
+ case 'u':
+ tryRead("uncompressed_size", &this->zfs.uncompressed);
+ break;
+ case 's':
+ tryRead("size", &this->zfs.size);
+ break;
+ case 'h':
+ tryRead("hdr_size", &this->zfs.header);
+ break;
+ case 'd':
+ tryRead("dbuf_size", &dbufSize);
+ tryRead("dnode_size", &dnodeSize);
+ break;
+ case 'b':
+ tryRead("bonus_size", &bonusSize);
+ break;
+ case 'a':
+ tryRead("anon_size", &this->zfs.anon);
+ break;
+ case 'm':
+ tryRead("mfu_size", &this->zfs.MFU);
+ tryRead("mru_size", &this->zfs.MRU);
+ break;
+ }
+
+ #undef tryRead
+ #undef tryReadFlag
+ }
+ fclose(file);
+
+ this->zfs.enabled = (this->zfs.size > 0 ? 1 : 0);
+ this->zfs.size /= 1024;
+ this->zfs.min /= 1024;
+ this->zfs.max /= 1024;
+ this->zfs.MFU /= 1024;
+ this->zfs.MRU /= 1024;
+ this->zfs.anon /= 1024;
+ this->zfs.header /= 1024;
+ this->zfs.other = (dbufSize + dnodeSize + bonusSize) / 1024;
+ if ( this->zfs.isCompressed ) {
+ this->zfs.compressed /= 1024;
+ this->zfs.uncompressed /= 1024;
+ }
+}
+
+static void LinuxMachine_scanCPUTime(LinuxMachine* this) {
+ const Machine* super = &this->super;
+
+ LinuxMachine_updateCPUcount(this);
+
+ FILE* file = fopen(PROCSTATFILE, "r");
+ if (!file)
+ CRT_fatalError("Cannot open " PROCSTATFILE);
+
+ unsigned int lastAdjCpuId = 0;
+
+ for (unsigned int i = 0; i <= super->existingCPUs; i++) {
+ char buffer[PROC_LINE_LENGTH + 1];
+ unsigned long long int usertime, nicetime, systemtime, idletime;
+ unsigned long long int ioWait = 0, irq = 0, softIrq = 0, steal = 0, guest = 0, guestnice = 0;
+
+ const char* ok = fgets(buffer, sizeof(buffer), file);
+ if (!ok)
+ break;
+
+ // cpu fields are sorted first
+ if (!String_startsWith(buffer, "cpu"))
+ break;
+
+ // Depending on your kernel version,
+ // 5, 7, 8 or 9 of these fields will be set.
+ // The rest will remain at zero.
+ unsigned int adjCpuId;
+ if (i == 0) {
+ (void) sscanf(buffer, "cpu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
+ adjCpuId = 0;
+ } else {
+ unsigned int cpuid;
+ (void) sscanf(buffer, "cpu%4u %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
+ adjCpuId = cpuid + 1;
+ }
+
+ if (adjCpuId > super->existingCPUs)
+ break;
+
+ for (unsigned int j = lastAdjCpuId + 1; j < adjCpuId; j++) {
+ // Skipped an ID, but /proc/stat is ordered => got offline CPU
+ memset(&(this->cpuData[j]), '\0', sizeof(CPUData));
+ }
+ lastAdjCpuId = adjCpuId;
+
+ // Guest time is already accounted in usertime
+ usertime -= guest;
+ nicetime -= guestnice;
+ // Fields existing on kernels >= 2.6
+ // (and RHEL's patched kernel 2.4...)
+ unsigned long long int idlealltime = idletime + ioWait;
+ unsigned long long int systemalltime = systemtime + irq + softIrq;
+ unsigned long long int virtalltime = guest + guestnice;
+ unsigned long long int totaltime = usertime + nicetime + systemalltime + idlealltime + steal + virtalltime;
+ CPUData* cpuData = &(this->cpuData[adjCpuId]);
+ // Since we do a subtraction (usertime - guest) and cputime64_to_clock_t()
+ // used in /proc/stat rounds down numbers, it can lead to a case where the
+ // integer overflow.
+ cpuData->userPeriod = saturatingSub(usertime, cpuData->userTime);
+ cpuData->nicePeriod = saturatingSub(nicetime, cpuData->niceTime);
+ cpuData->systemPeriod = saturatingSub(systemtime, cpuData->systemTime);
+ cpuData->systemAllPeriod = saturatingSub(systemalltime, cpuData->systemAllTime);
+ cpuData->idleAllPeriod = saturatingSub(idlealltime, cpuData->idleAllTime);
+ cpuData->idlePeriod = saturatingSub(idletime, cpuData->idleTime);
+ cpuData->ioWaitPeriod = saturatingSub(ioWait, cpuData->ioWaitTime);
+ cpuData->irqPeriod = saturatingSub(irq, cpuData->irqTime);
+ cpuData->softIrqPeriod = saturatingSub(softIrq, cpuData->softIrqTime);
+ cpuData->stealPeriod = saturatingSub(steal, cpuData->stealTime);
+ cpuData->guestPeriod = saturatingSub(virtalltime, cpuData->guestTime);
+ cpuData->totalPeriod = saturatingSub(totaltime, cpuData->totalTime);
+ cpuData->userTime = usertime;
+ cpuData->niceTime = nicetime;
+ cpuData->systemTime = systemtime;
+ cpuData->systemAllTime = systemalltime;
+ cpuData->idleAllTime = idlealltime;
+ cpuData->idleTime = idletime;
+ cpuData->ioWaitTime = ioWait;
+ cpuData->irqTime = irq;
+ cpuData->softIrqTime = softIrq;
+ cpuData->stealTime = steal;
+ cpuData->guestTime = virtalltime;
+ cpuData->totalTime = totaltime;
+ }
+
+ this->period = (double)this->cpuData[0].totalPeriod / super->activeCPUs;
+
+ char buffer[PROC_LINE_LENGTH + 1];
+ while (fgets(buffer, sizeof(buffer), file)) {
+ if (String_startsWith(buffer, "procs_running")) {
+ ProcessTable* pt = (ProcessTable*) super->processTable;
+ pt->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10);
+ break;
+ }
+ }
+
+ fclose(file);
+}
+
+static int scanCPUFrequencyFromSysCPUFreq(LinuxMachine* this) {
+ const Machine* super = &this->super;
+ int numCPUsWithFrequency = 0;
+ unsigned long totalFrequency = 0;
+
+ /*
+ * On some AMD and Intel CPUs read()ing scaling_cur_freq is quite slow (> 1ms). This delay
+ * accumulates for every core. For details see issue#471.
+ * If the read on CPU 0 takes longer than 500us bail out and fall back to reading the
+ * frequencies from /proc/cpuinfo.
+ * Once the condition has been met, bail out early for the next couple of scans.
+ */
+ static int timeout = 0;
+
+ if (timeout > 0) {
+ timeout--;
+ return -1;
+ }
+
+ for (unsigned int i = 0; i < super->existingCPUs; ++i) {
+ if (!Machine_isCPUonline(super, i))
+ continue;
+
+ char pathBuffer[64];
+ xSnprintf(pathBuffer, sizeof(pathBuffer), "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i);
+
+ struct timespec start;
+ if (i == 0)
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ FILE* file = fopen(pathBuffer, "r");
+ if (!file)
+ return -errno;
+
+ unsigned long frequency;
+ if (fscanf(file, "%lu", &frequency) == 1) {
+ /* convert kHz to MHz */
+ frequency = frequency / 1000;
+ this->cpuData[i + 1].frequency = frequency;
+ numCPUsWithFrequency++;
+ totalFrequency += frequency;
+ }
+
+ fclose(file);
+
+ if (i == 0) {
+ struct timespec end;
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ const time_t timeTakenUs = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;
+ if (timeTakenUs > 500) {
+ timeout = 30;
+ return -1;
+ }
+ }
+ }
+
+ if (numCPUsWithFrequency > 0)
+ this->cpuData[0].frequency = (double)totalFrequency / numCPUsWithFrequency;
+
+ return 0;
+}
+
+static void scanCPUFrequencyFromCPUinfo(LinuxMachine* this) {
+ const Machine* super = &this->super;
+
+ FILE* file = fopen(PROCCPUINFOFILE, "r");
+ if (file == NULL)
+ return;
+
+ int numCPUsWithFrequency = 0;
+ double totalFrequency = 0;
+ int cpuid = -1;
+
+ while (!feof(file)) {
+ double frequency;
+ char buffer[PROC_LINE_LENGTH];
+
+ if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
+ break;
+
+ if (sscanf(buffer, "processor : %d", &cpuid) == 1) {
+ continue;
+ } else if (
+ (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
+ (sscanf(buffer, "clock : %lfMHz", &frequency) == 1)
+ ) {
+ if (cpuid < 0 || (unsigned int)cpuid > (super->existingCPUs - 1)) {
+ continue;
+ }
+
+ CPUData* cpuData = &(this->cpuData[cpuid + 1]);
+ /* do not override sysfs data */
+ if (!isNonnegative(cpuData->frequency)) {
+ cpuData->frequency = frequency;
+ }
+ numCPUsWithFrequency++;
+ totalFrequency += frequency;
+ } else if (buffer[0] == '\n') {
+ cpuid = -1;
+ }
+ }
+ fclose(file);
+
+ if (numCPUsWithFrequency > 0) {
+ this->cpuData[0].frequency = totalFrequency / numCPUsWithFrequency;
+ }
+}
+
+static void LinuxMachine_scanCPUFrequency(LinuxMachine* this) {
+ const Machine* super = &this->super;
+
+ for (unsigned int i = 0; i <= super->existingCPUs; i++)
+ this->cpuData[i].frequency = NAN;
+
+ if (scanCPUFrequencyFromSysCPUFreq(this) == 0)
+ return;
+
+ scanCPUFrequencyFromCPUinfo(this);
+}
+
+void Machine_scan(Machine* super) {
+ LinuxMachine* this = (LinuxMachine*) super;
+
+ LinuxMachine_scanMemoryInfo(this);
+ LinuxMachine_scanHugePages(this);
+ LinuxMachine_scanZfsArcstats(this);
+ LinuxMachine_scanZramInfo(this);
+ LinuxMachine_scanCPUTime(this);
+
+ const Settings* settings = super->settings;
+ if (settings->showCPUFrequency)
+ LinuxMachine_scanCPUFrequency(this);
+
+ #ifdef HAVE_SENSORS_SENSORS_H
+ if (settings->showCPUTemperature)
+ LibSensors_getCPUTemperatures(this->cpuData, super->existingCPUs, super->activeCPUs);
+ #endif
+}
+
+Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
+ LinuxMachine* this = xCalloc(1, sizeof(LinuxMachine));
+ Machine* super = &this->super;
+
+ Machine_init(super, usersTable, userId);
+
+ // Initialize page size
+ if ((this->pageSize = sysconf(_SC_PAGESIZE)) == -1)
+ CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)");
+ this->pageSizeKB = this->pageSize / ONE_K;
+
+ // Initialize clock ticks
+ if ((this->jiffies = sysconf(_SC_CLK_TCK)) == -1)
+ CRT_fatalError("Cannot get clock ticks by sysconf(_SC_CLK_TCK)");
+
+ // Read btime (the kernel boot time, as number of seconds since the epoch)
+ FILE* statfile = fopen(PROCSTATFILE, "r");
+ if (statfile == NULL)
+ CRT_fatalError("Cannot open " PROCSTATFILE);
+
+ this->boottime = -1;
+
+ while (true) {
+ char buffer[PROC_LINE_LENGTH + 1];
+ if (fgets(buffer, sizeof(buffer), statfile) == NULL)
+ break;
+ if (String_startsWith(buffer, "btime ") == false)
+ continue;
+ if (sscanf(buffer, "btime %lld\n", &this->boottime) == 1)
+ break;
+ CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
+ }
+ fclose(statfile);
+
+ if (this->boottime == -1)
+ CRT_fatalError("No btime in " PROCSTATFILE);
+
+ // Initialize CPU count
+ LinuxMachine_updateCPUcount(this);
+
+ return super;
+}
+
+void Machine_delete(Machine* super) {
+ LinuxMachine* this = (LinuxMachine*) super;
+ Machine_done(super);
+ free(this->cpuData);
+ free(this);
+}
+
+bool Machine_isCPUonline(const Machine* super, unsigned int id) {
+ const LinuxMachine* this = (const LinuxMachine*) super;
+
+ assert(id < super->existingCPUs);
+ return this->cpuData[id + 1].online;
+}
diff --git a/linux/LinuxMachine.h b/linux/LinuxMachine.h
new file mode 100644
index 0000000..309b485
--- /dev/null
+++ b/linux/LinuxMachine.h
@@ -0,0 +1,109 @@
+#ifndef HEADER_LinuxMachine
+#define HEADER_LinuxMachine
+/*
+htop - LinuxMachine.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "Machine.h"
+#include "linux/ZramStats.h"
+#include "linux/ZswapStats.h"
+#include "zfs/ZfsArcStats.h"
+
+#define HTOP_HUGEPAGE_BASE_SHIFT 16
+#define HTOP_HUGEPAGE_COUNT 24
+
+typedef struct CPUData_ {
+ unsigned long long int totalTime;
+ unsigned long long int userTime;
+ unsigned long long int systemTime;
+ unsigned long long int systemAllTime;
+ unsigned long long int idleAllTime;
+ unsigned long long int idleTime;
+ unsigned long long int niceTime;
+ unsigned long long int ioWaitTime;
+ unsigned long long int irqTime;
+ unsigned long long int softIrqTime;
+ unsigned long long int stealTime;
+ unsigned long long int guestTime;
+
+ unsigned long long int totalPeriod;
+ unsigned long long int userPeriod;
+ unsigned long long int systemPeriod;
+ unsigned long long int systemAllPeriod;
+ unsigned long long int idleAllPeriod;
+ unsigned long long int idlePeriod;
+ unsigned long long int nicePeriod;
+ unsigned long long int ioWaitPeriod;
+ unsigned long long int irqPeriod;
+ unsigned long long int softIrqPeriod;
+ unsigned long long int stealPeriod;
+ unsigned long long int guestPeriod;
+
+ double frequency;
+
+ #ifdef HAVE_SENSORS_SENSORS_H
+ double temperature;
+ #endif
+
+ bool online;
+} CPUData;
+
+typedef struct LinuxMachine_ {
+ Machine super;
+
+ long jiffies;
+ int pageSize;
+ int pageSizeKB;
+
+ /* see Linux kernel source for further detail, fs/proc/stat.c */
+ unsigned int runningTasks; /* procs_running from /proc/stat */
+ long long boottime; /* btime field from /proc/stat */
+
+ double period;
+
+ CPUData* cpuData;
+
+ memory_t totalHugePageMem;
+ memory_t usedHugePageMem[HTOP_HUGEPAGE_COUNT];
+
+ memory_t availableMem;
+
+ ZfsArcStats zfs;
+ ZramStats zram;
+ ZswapStats zswap;
+} LinuxMachine;
+
+#ifndef PROCDIR
+#define PROCDIR "/proc"
+#endif
+
+#ifndef PROCCPUINFOFILE
+#define PROCCPUINFOFILE PROCDIR "/cpuinfo"
+#endif
+
+#ifndef PROCSTATFILE
+#define PROCSTATFILE PROCDIR "/stat"
+#endif
+
+#ifndef PROCMEMINFOFILE
+#define PROCMEMINFOFILE PROCDIR "/meminfo"
+#endif
+
+#ifndef PROCARCSTATSFILE
+#define PROCARCSTATSFILE PROCDIR "/spl/kstat/zfs/arcstats"
+#endif
+
+#ifndef PROCTTYDRIVERSFILE
+#define PROCTTYDRIVERSFILE PROCDIR "/tty/drivers"
+#endif
+
+#ifndef PROC_LINE_LENGTH
+#define PROC_LINE_LENGTH 4096
+#endif
+
+#endif
diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c
new file mode 100644
index 0000000..dc4f259
--- /dev/null
+++ b/linux/LinuxProcess.c
@@ -0,0 +1,446 @@
+/*
+htop - LinuxProcess.c
+(C) 2014 Hisham H. Muhammad
+(C) 2020 Red Hat, Inc. All Rights Reserved.
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/LinuxProcess.h"
+
+#include <assert.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include <unistd.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#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"
+
+
+const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
+ [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, },
+ [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, },
+ [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, },
+ [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging, I idle)", .flags = 0, },
+ [PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, .pidColumn = true, },
+ [PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, .pidColumn = true, },
+ [SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .flags = 0, .pidColumn = true, },
+ [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, },
+ [TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, .pidColumn = true, },
+ [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, },
+ [CMINFLT] = { .name = "CMINFLT", .title = " CMINFLT ", .description = "Children processes' minor faults", .flags = 0, .defaultSortDesc = true, },
+ [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, },
+ [CMAJFLT] = { .name = "CMAJFLT", .title = " CMAJFLT ", .description = "Children processes' major faults", .flags = 0, .defaultSortDesc = true, },
+ [UTIME] = { .name = "UTIME", .title = " UTIME+ ", .description = "User CPU time - time the process spent executing in user mode", .flags = 0, .defaultSortDesc = true, },
+ [STIME] = { .name = "STIME", .title = " STIME+ ", .description = "System CPU time - time the kernel spent running system calls for this process", .flags = 0, .defaultSortDesc = true, },
+ [CUTIME] = { .name = "CUTIME", .title = " CUTIME+ ", .description = "Children processes' user CPU time", .flags = 0, .defaultSortDesc = true, },
+ [CSTIME] = { .name = "CSTIME", .title = " CSTIME+ ", .description = "Children processes' system CPU time", .flags = 0, .defaultSortDesc = true, },
+ [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, },
+ [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, },
+ [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, },
+ [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, },
+ [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
+ [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
+ [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
+ [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, },
+ [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
+ [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
+ [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
+ [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
+ [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
+ [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
+ [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
+#ifdef HAVE_OPENVZ
+ [CTID] = { .name = "CTID", .title = " CTID ", .description = "OpenVZ container ID (a.k.a. virtual environment ID)", .flags = PROCESS_FLAG_LINUX_OPENVZ, },
+ [VPID] = { .name = "VPID", .title = "VPID", .description = "OpenVZ process ID", .flags = PROCESS_FLAG_LINUX_OPENVZ, .pidColumn = true, },
+#endif
+#ifdef HAVE_VSERVER
+ [VXID] = { .name = "VXID", .title = " VXID ", .description = "VServer process ID", .flags = PROCESS_FLAG_LINUX_VSERVER, },
+#endif
+ [RCHAR] = { .name = "RCHAR", .title = "RCHAR ", .description = "Number of bytes the process has read", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [WCHAR] = { .name = "WCHAR", .title = "WCHAR ", .description = "Number of bytes the process has written", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [SYSCR] = { .name = "SYSCR", .title = " READ_SYSC ", .description = "Number of read(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [SYSCW] = { .name = "SYSCW", .title = " WRITE_SYSC ", .description = "Number of write(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [RBYTES] = { .name = "RBYTES", .title = " IO_R ", .description = "Bytes of read(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [WBYTES] = { .name = "WBYTES", .title = " IO_W ", .description = "Bytes of write(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [CNCLWB] = { .name = "CNCLWB", .title = " IO_C ", .description = "Bytes of cancelled write(2) I/O", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
+ [CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw)", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
+ [CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed)", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
+ [CONTAINER] = { .name = "CONTAINER", .title = "CONTAINER", .description = "Name of the container the process is in (guessed by heuristics)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
+ [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, },
+ [IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, },
+#ifdef HAVE_DELAYACCT
+ [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
+ [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
+ [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWPD% ", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },
+#endif
+ [M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
+ [M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
+ [M_PSSWP] = { .name = "M_PSSWP", .title = " PSSWP ", .description = "shows proportional swap share of this mapping, unlike \"Swap\", this does not take into account swapped out page of underlying shmem objects", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
+ [CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, .defaultSortDesc = true, },
+ [SECATTR] = { .name = "SECATTR", .title = "Security Attribute", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, .autoWidth = true, },
+ [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process from /proc/[pid]/comm", .flags = 0, },
+ [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, },
+ [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
+ [AUTOGROUP_ID] = { .name = "AUTOGROUP_ID", .title = "AGRP", .description = "The autogroup identifier of the process", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },
+ [AUTOGROUP_NICE] = { .name = "AUTOGROUP_NICE", .title = " ANI", .description = "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },
+#ifdef SCHEDULER_SUPPORT
+ [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, },
+#endif
+};
+
+Process* LinuxProcess_new(const Machine* host) {
+ LinuxProcess* this = xCalloc(1, sizeof(LinuxProcess));
+ Object_setClass(this, Class(LinuxProcess));
+ Process_init(&this->super, host);
+ return &this->super;
+}
+
+void Process_delete(Object* cast) {
+ LinuxProcess* this = (LinuxProcess*) cast;
+ Process_done((Process*)cast);
+ free(this->container_short);
+ free(this->cgroup_short);
+ free(this->cgroup);
+#ifdef HAVE_OPENVZ
+ free(this->ctid);
+#endif
+ free(this->secattr);
+ free(this);
+}
+
+/*
+[1] Note that before kernel 2.6.26 a process that has not asked for
+an io priority formally uses "none" as scheduling class, but the
+io scheduler will treat such processes as if it were in the best
+effort class. The priority within the best effort class will be
+dynamically derived from the cpu nice level of the process:
+io_priority = (cpu_nice + 20) / 5. -- From ionice(1) man page
+*/
+static int LinuxProcess_effectiveIOPriority(const LinuxProcess* this) {
+ if (IOPriority_class(this->ioPriority) == IOPRIO_CLASS_NONE) {
+ return IOPriority_tuple(IOPRIO_CLASS_BE, (this->super.nice + 20) / 5);
+ }
+
+ return this->ioPriority;
+}
+
+#ifdef __ANDROID__
+#define SYS_ioprio_get __NR_ioprio_get
+#define SYS_ioprio_set __NR_ioprio_set
+#endif
+
+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, Process_getPid(p));
+#endif
+ LinuxProcess* this = (LinuxProcess*) p;
+ this->ioPriority = ioprio;
+ return 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, Process_getPid(p), ioprio.i);
+#endif
+ 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) {
+ char buf[16];
+ if (xReadfile(PROCDIR "/sys/kernel/sched_autogroup_enabled", buf, sizeof(buf)) < 0)
+ return false;
+ return buf[0] == '1';
+}
+
+static bool LinuxProcess_changeAutogroupPriorityBy(Process* p, Arg delta) {
+ char buffer[256];
+ pid_t pid = Process_getPid(p);
+ xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", pid);
+
+ FILE* file = fopen(buffer, "r+");
+ if (!file)
+ return false;
+
+ long int identity;
+ int nice;
+ int ok = fscanf(file, "/autogroup-%ld nice %d", &identity, &nice);
+ bool success = false;
+ if (ok == 2 && fseek(file, 0L, SEEK_SET) == 0) {
+ xSnprintf(buffer, sizeof(buffer), "%d", nice + delta.i);
+ success = fputs(buffer, file) > 0;
+ }
+
+ fclose(file);
+ return success;
+}
+
+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: 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) {
+ Row_printBytes(str, lp->m_lrs * lhost->pageSize, coloring);
+ return;
+ }
+
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, " N/A ");
+ break;
+ 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;
+ #endif
+ #ifdef HAVE_VSERVER
+ case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break;
+ #endif
+ 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);
+ if (klass == IOPRIO_CLASS_NONE) {
+ // see note [1] above
+ xSnprintf(buffer, n, "B%1d ", (int) (this->nice + 20) / 5);
+ } else if (klass == IOPRIO_CLASS_BE) {
+ xSnprintf(buffer, n, "B%1d ", IOPriority_data(lp->ioPriority));
+ } else if (klass == IOPRIO_CLASS_RT) {
+ attr = CRT_colors[PROCESS_HIGH_PRIORITY];
+ xSnprintf(buffer, n, "R%1d ", IOPriority_data(lp->ioPriority));
+ } else if (klass == IOPRIO_CLASS_IDLE) {
+ attr = CRT_colors[PROCESS_LOW_PRIORITY];
+ xSnprintf(buffer, n, "id ");
+ } else {
+ xSnprintf(buffer, n, "?? ");
+ }
+ break;
+ }
+ #ifdef HAVE_DELAYACCT
+ 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) {
+ attr |= A_BOLD;
+ }
+ xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff);
+ 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);
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, " N/A ");
+ }
+ break;
+ case AUTOGROUP_NICE:
+ if (lp->autogroup_id != -1) {
+ xSnprintf(buffer, n, "%3d ", lp->autogroup_nice);
+ attr = lp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]
+ : lp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
+ : CRT_colors[PROCESS_SHADOW];
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, "N/A ");
+ }
+ break;
+ default:
+ Process_writeField(this, str, field);
+ return;
+ }
+
+ RichString_appendAscii(str, attr, buffer);
+}
+
+static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {
+ const LinuxProcess* p1 = (const LinuxProcess*)v1;
+ const LinuxProcess* p2 = (const LinuxProcess*)v2;
+
+ switch (key) {
+ case M_DRS:
+ return SPACESHIP_NUMBER(p1->m_drs, p2->m_drs);
+ case M_LRS:
+ return SPACESHIP_NUMBER(p1->m_lrs, p2->m_lrs);
+ case M_TRS:
+ return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs);
+ case M_SHARE:
+ return SPACESHIP_NUMBER(p1->m_share, p2->m_share);
+ case M_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:
+ return SPACESHIP_NUMBER(p1->m_swap, p2->m_swap);
+ case M_PSSWP:
+ return SPACESHIP_NUMBER(p1->m_psswp, p2->m_psswp);
+ case UTIME:
+ return SPACESHIP_NUMBER(p1->utime, p2->utime);
+ case CUTIME:
+ return SPACESHIP_NUMBER(p1->cutime, p2->cutime);
+ case STIME:
+ return SPACESHIP_NUMBER(p1->stime, p2->stime);
+ case CSTIME:
+ return SPACESHIP_NUMBER(p1->cstime, p2->cstime);
+ case RCHAR:
+ return SPACESHIP_NUMBER(p1->io_rchar, p2->io_rchar);
+ case WCHAR:
+ return SPACESHIP_NUMBER(p1->io_wchar, p2->io_wchar);
+ case SYSCR:
+ return SPACESHIP_NUMBER(p1->io_syscr, p2->io_syscr);
+ case SYSCW:
+ return SPACESHIP_NUMBER(p1->io_syscw, p2->io_syscw);
+ case RBYTES:
+ return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes);
+ case WBYTES:
+ return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes);
+ case CNCLWB:
+ return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes);
+ case IO_READ_RATE:
+ return compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps);
+ case IO_WRITE_RATE:
+ return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps);
+ case IO_RATE:
+ return compareRealNumbers(LinuxProcess_totalIORate(p1), LinuxProcess_totalIORate(p2));
+ #ifdef HAVE_OPENVZ
+ case CTID:
+ return SPACESHIP_NULLSTR(p1->ctid, p2->ctid);
+ case VPID:
+ return SPACESHIP_NUMBER(p1->vpid, p2->vpid);
+ #endif
+ #ifdef HAVE_VSERVER
+ case VXID:
+ return SPACESHIP_NUMBER(p1->vxid, p2->vxid);
+ #endif
+ case CGROUP:
+ return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);
+ case CCGROUP:
+ return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short);
+ case 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 compareRealNumbers(p1->cpu_delay_percent, p2->cpu_delay_percent);
+ case PERCENT_IO_DELAY:
+ return compareRealNumbers(p1->blkio_delay_percent, p2->blkio_delay_percent);
+ case PERCENT_SWAP_DELAY:
+ return compareRealNumbers(p1->swapin_delay_percent, p2->swapin_delay_percent);
+ #endif
+ case IO_PRIORITY:
+ return SPACESHIP_NUMBER(LinuxProcess_effectiveIOPriority(p1), LinuxProcess_effectiveIOPriority(p2));
+ case CTXT:
+ return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff);
+ case SECATTR:
+ return SPACESHIP_NULLSTR(p1->secattr, p2->secattr);
+ case AUTOGROUP_ID:
+ return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id);
+ case AUTOGROUP_NICE:
+ return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice);
+ default:
+ return Process_compareByKey_Base(v1, v2, key);
+ }
+}
+
+const ProcessClass LinuxProcess_class = {
+ .super = {
+ .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
+ },
+ .compareByKey = LinuxProcess_compareByKey
+};
diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h
new file mode 100644
index 0000000..388e50d
--- /dev/null
+++ b/linux/LinuxProcess.h
@@ -0,0 +1,136 @@
+#ifndef HEADER_LinuxProcess
+#define HEADER_LinuxProcess
+/*
+htop - LinuxProcess.h
+(C) 2014 Hisham H. Muhammad
+(C) 2020 Red Hat, Inc. All Rights Reserved.
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "Machine.h"
+#include "Object.h"
+#include "Process.h"
+#include "Row.h"
+
+#include "linux/IOPriority.h"
+
+
+#define PROCESS_FLAG_LINUX_IOPRIO 0x00000100
+#define PROCESS_FLAG_LINUX_OPENVZ 0x00000200
+#define PROCESS_FLAG_LINUX_VSERVER 0x00000400
+#define PROCESS_FLAG_LINUX_CGROUP 0x00000800
+#define PROCESS_FLAG_LINUX_OOM 0x00001000
+#define PROCESS_FLAG_LINUX_SMAPS 0x00002000
+#define PROCESS_FLAG_LINUX_CTXT 0x00004000
+#define PROCESS_FLAG_LINUX_SECATTR 0x00008000
+#define PROCESS_FLAG_LINUX_LRS_FIX 0x00010000
+#define PROCESS_FLAG_LINUX_DELAYACCT 0x00040000
+#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000
+
+typedef struct LinuxProcess_ {
+ Process super;
+ IOPriority ioPriority;
+ unsigned long int cminflt;
+ unsigned long int cmajflt;
+ unsigned long long int utime;
+ unsigned long long int stime;
+ unsigned long long int cutime;
+ unsigned long long int cstime;
+ long m_share;
+ long m_priv;
+ long m_pss;
+ long m_swap;
+ long m_psswp;
+ long m_trs;
+ long m_drs;
+ long m_lrs;
+
+ /* Process flags */
+ unsigned long int flags;
+
+ /* Data read (in bytes) */
+ unsigned long long io_rchar;
+
+ /* Data written (in bytes) */
+ unsigned long long io_wchar;
+
+ /* Number of read(2) syscalls */
+ unsigned long long io_syscr;
+
+ /* Number of write(2) syscalls */
+ unsigned long long io_syscw;
+
+ /* Storage data read (in bytes) */
+ unsigned long long io_read_bytes;
+
+ /* Storage data written (in bytes) */
+ unsigned long long io_write_bytes;
+
+ /* Storage data cancelled (in bytes) */
+ unsigned long long io_cancelled_write_bytes;
+
+ /* Point in time of last io scan (in milliseconds elapsed since the Epoch) */
+ unsigned long long io_last_scan_time_ms;
+
+ /* Storage data read (in bytes per second) */
+ double io_rate_read_bps;
+
+ /* Storage data written (in bytes per second) */
+ double io_rate_write_bps;
+
+ #ifdef HAVE_OPENVZ
+ char* ctid;
+ pid_t vpid;
+ #endif
+ #ifdef HAVE_VSERVER
+ unsigned int vxid;
+ #endif
+ char* cgroup;
+ char* cgroup_short;
+ char* container_short;
+ unsigned int oom;
+ #ifdef HAVE_DELAYACCT
+ unsigned long long int delay_read_time;
+ unsigned long long cpu_delay_total;
+ unsigned long long blkio_delay_total;
+ unsigned long long swapin_delay_total;
+ float cpu_delay_percent;
+ float blkio_delay_percent;
+ float swapin_delay_percent;
+ #endif
+ unsigned long ctxt_total;
+ unsigned long ctxt_diff;
+ char* secattr;
+ unsigned long long int last_mlrs_calctime;
+
+ /* Autogroup scheduling (CFS) information */
+ long int autogroup_id;
+ int autogroup_nice;
+} LinuxProcess;
+
+extern int pageSize;
+
+extern int pageSizeKB;
+
+extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
+
+extern const ProcessClass LinuxProcess_class;
+
+Process* LinuxProcess_new(const Machine* host);
+
+void Process_delete(Object* cast);
+
+IOPriority LinuxProcess_updateIOPriority(Process* proc);
+
+bool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio);
+
+bool LinuxProcess_isAutogroupEnabled(void);
+
+bool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta);
+
+bool Process_isThread(const Process* this);
+
+#endif
diff --git a/linux/LinuxProcessTable.c b/linux/LinuxProcessTable.c
new file mode 100644
index 0000000..039a64e
--- /dev/null
+++ b/linux/LinuxProcessTable.c
@@ -0,0 +1,1668 @@
+/*
+htop - LinuxProcessTable.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/LinuxProcessTable.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_DELAYACCT
+#include <linux/netlink.h>
+#include <linux/taskstats.h>
+#include <netlink/attr.h>
+#include <netlink/handlers.h>
+#include <netlink/msg.h>
+#include <netlink/netlink.h>
+#include <netlink/socket.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#endif
+
+#include "Compat.h"
+#include "Hashtable.h"
+#include "Machine.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Process.h"
+#include "Row.h"
+#include "RowField.h"
+#include "Scheduling.h"
+#include "Settings.h"
+#include "Table.h"
+#include "UsersTable.h"
+#include "XUtils.h"
+#include "linux/CGroupUtils.h"
+#include "linux/LinuxMachine.h"
+#include "linux/LinuxProcess.h"
+#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep
+
+#if defined(MAJOR_IN_MKDEV)
+#include <sys/mkdev.h>
+#elif defined(MAJOR_IN_SYSMACROS)
+#include <sys/sysmacros.h>
+#endif
+
+/* Not exposed yet. Defined at include/linux/sched.h */
+#ifndef PF_KTHREAD
+#define PF_KTHREAD 0x00200000
+#endif
+
+static FILE* fopenat(openat_arg_t openatArg, const char* pathname, const char* mode) {
+ assert(String_eq(mode, "r")); /* only currently supported mode */
+
+ int fd = Compat_openat(openatArg, pathname, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ FILE* stream = fdopen(fd, mode);
+ if (!stream)
+ close(fd);
+
+ return stream;
+}
+
+static inline uint64_t fast_strtoull_dec(char** str, int maxlen) {
+ register uint64_t result = 0;
+
+ if (!maxlen)
+ --maxlen;
+
+ while (maxlen-- && **str >= '0' && **str <= '9') {
+ result *= 10;
+ result += **str - '0';
+ (*str)++;
+ }
+
+ return result;
+}
+
+static inline uint64_t fast_strtoull_hex(char** str, int maxlen) {
+ register uint64_t result = 0;
+ register int nibble, letter;
+ const long valid_mask = 0x03FF007E;
+
+ if (!maxlen)
+ --maxlen;
+
+ while (maxlen--) {
+ nibble = (unsigned char)**str;
+ if (!(valid_mask & (1 << (nibble & 0x1F))))
+ break;
+ if ((nibble < '0') || (nibble & ~0x20) > 'F')
+ break;
+ letter = (nibble & 0x40) ? 'A' - '9' - 1 : 0;
+ nibble &=~0x20; // to upper
+ nibble ^= 0x10; // switch letters and digits
+ nibble -= letter;
+ nibble &= 0x0f;
+ result <<= 4;
+ result += (uint64_t)nibble;
+ (*str)++;
+ }
+
+ return result;
+}
+
+static int sortTtyDrivers(const void* va, const void* vb) {
+ const TtyDriver* a = (const TtyDriver*) va;
+ const TtyDriver* b = (const TtyDriver*) vb;
+
+ int r = SPACESHIP_NUMBER(a->major, b->major);
+ if (r)
+ return r;
+
+ return SPACESHIP_NUMBER(a->minorFrom, b->minorFrom);
+}
+
+static void LinuxProcessTable_initTtyDrivers(LinuxProcessTable* this) {
+ TtyDriver* ttyDrivers;
+
+ char buf[16384];
+ ssize_t r = xReadfile(PROCTTYDRIVERSFILE, buf, sizeof(buf));
+ if (r < 0)
+ return;
+
+ int numDrivers = 0;
+ int allocd = 10;
+ ttyDrivers = xMallocArray(allocd, sizeof(TtyDriver));
+ char* at = buf;
+ while (*at != '\0') {
+ at = strchr(at, ' '); // skip first token
+ while (*at == ' ') at++; // skip spaces
+ const char* token = at; // mark beginning of path
+ at = strchr(at, ' '); // find end of path
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].path = xStrdup(token); // save
+ while (*at == ' ') at++; // skip spaces
+ token = at; // mark beginning of major
+ at = strchr(at, ' '); // find end of major
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].major = atoi(token); // save
+ while (*at == ' ') at++; // skip spaces
+ token = at; // mark beginning of minorFrom
+ while (*at >= '0' && *at <= '9') at++; //find end of minorFrom
+ if (*at == '-') { // if has range
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].minorFrom = atoi(token); // save
+ token = at; // mark beginning of minorTo
+ at = strchr(at, ' '); // find end of minorTo
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].minorTo = atoi(token); // save
+ } else { // no range
+ *at = '\0'; at++; // clear and skip
+ ttyDrivers[numDrivers].minorFrom = atoi(token); // save
+ ttyDrivers[numDrivers].minorTo = atoi(token); // save
+ }
+ at = strchr(at, '\n'); // go to end of line
+ at++; // skip
+ numDrivers++;
+ if (numDrivers == allocd) {
+ allocd += 10;
+ ttyDrivers = xReallocArray(ttyDrivers, allocd, sizeof(TtyDriver));
+ }
+ }
+ numDrivers++;
+ ttyDrivers = xRealloc(ttyDrivers, sizeof(TtyDriver) * numDrivers);
+ ttyDrivers[numDrivers - 1].path = NULL;
+ qsort(ttyDrivers, numDrivers - 1, sizeof(TtyDriver), sortTtyDrivers);
+ this->ttyDrivers = ttyDrivers;
+}
+
+#ifdef HAVE_DELAYACCT
+
+static void LinuxProcessTable_initNetlinkSocket(LinuxProcessTable* this) {
+ this->netlink_socket = nl_socket_alloc();
+ if (this->netlink_socket == NULL) {
+ return;
+ }
+ if (nl_connect(this->netlink_socket, NETLINK_GENERIC) < 0) {
+ return;
+ }
+ this->netlink_family = genl_ctrl_resolve(this->netlink_socket, TASKSTATS_GENL_NAME);
+}
+
+#endif
+
+ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
+ LinuxProcessTable* this = xCalloc(1, sizeof(LinuxProcessTable));
+ Object_setClass(this, Class(ProcessTable));
+
+ ProcessTable* super = &this->super;
+ ProcessTable_init(super, Class(LinuxProcess), host, pidMatchList);
+
+ LinuxProcessTable_initTtyDrivers(this);
+
+ // Test /proc/PID/smaps_rollup availability (faster to parse, Linux 4.14+)
+ this->haveSmapsRollup = (access(PROCDIR "/self/smaps_rollup", R_OK) == 0);
+
+ return super;
+}
+
+void ProcessTable_delete(Object* cast) {
+ LinuxProcessTable* this = (LinuxProcessTable*) cast;
+ ProcessTable_done(&this->super);
+ if (this->ttyDrivers) {
+ for (int i = 0; this->ttyDrivers[i].path; i++) {
+ free(this->ttyDrivers[i].path);
+ }
+ free(this->ttyDrivers);
+ }
+ #ifdef HAVE_DELAYACCT
+ if (this->netlink_socket) {
+ nl_close(this->netlink_socket);
+ nl_socket_free(this->netlink_socket);
+ }
+ #endif
+ free(this);
+}
+
+static inline unsigned long long LinuxProcessTable_adjustTime(const LinuxMachine* lhost, unsigned long long t) {
+ return t * 100 / lhost->jiffies;
+}
+
+/* Taken from: https://github.com/torvalds/linux/blob/64570fbc14f8d7cb3fe3995f20e26bc25ce4b2cc/fs/proc/array.c#L120 */
+static inline ProcessState LinuxProcessTable_getProcessState(char state) {
+ switch (state) {
+ case 'S': return SLEEPING;
+ case 'X': return DEFUNCT;
+ case 'Z': return ZOMBIE;
+ case 't': return TRACED;
+ case 'T': return STOPPED;
+ case 'D': return UNINTERRUPTIBLE_WAIT;
+ case 'R': return RUNNING;
+ case 'P': return BLOCKED;
+ case 'I': return IDLE;
+ default: return UNKNOWN;
+ }
+}
+
+static bool LinuxProcessTable_readStatFile(LinuxProcess* lp, openat_arg_t procFd, const LinuxMachine* lhost, bool scanMainThread, char* command, size_t commLen) {
+ Process* process = &lp->super;
+
+ char buf[MAX_READ + 1];
+ char path[22] = "stat";
+ if (scanMainThread) {
+ xSnprintf(path, sizeof(path), "task/%"PRIi32"/stat", (int32_t)Process_getPid(process));
+ }
+ ssize_t r = xReadfileat(procFd, path, buf, sizeof(buf));
+ if (r < 0)
+ return false;
+
+ /* (1) pid - %d */
+ assert(Process_getPid(process) == atoi(buf));
+ char* location = strchr(buf, ' ');
+ if (!location)
+ return false;
+
+ /* (2) comm - (%s) */
+ location += 2;
+ char* end = strrchr(location, ')');
+ if (!end)
+ return false;
+
+ String_safeStrncpy(command, location, MINIMUM((size_t)(end - location + 1), commLen));
+
+ location = end + 2;
+
+ /* (3) state - %c */
+ process->state = LinuxProcessTable_getProcessState(location[0]);
+ location += 2;
+
+ /* (4) ppid - %d */
+ Process_setParent(process, strtol(location, &location, 10));
+ location += 1;
+
+ /* (5) pgrp - %d */
+ process->pgrp = strtol(location, &location, 10);
+ location += 1;
+
+ /* (6) session - %d */
+ process->session = strtol(location, &location, 10);
+ location += 1;
+
+ /* (7) tty_nr - %d */
+ process->tty_nr = strtoul(location, &location, 10);
+ location += 1;
+
+ /* (8) tpgid - %d */
+ process->tpgid = strtol(location, &location, 10);
+ location += 1;
+
+ /* (9) flags - %u */
+ lp->flags = strtoul(location, &location, 10);
+ location += 1;
+
+ /* (10) minflt - %lu */
+ process->minflt = strtoull(location, &location, 10);
+ location += 1;
+
+ /* (11) cminflt - %lu */
+ lp->cminflt = strtoull(location, &location, 10);
+ location += 1;
+
+ /* (12) majflt - %lu */
+ process->majflt = strtoull(location, &location, 10);
+ location += 1;
+
+ /* (13) cmajflt - %lu */
+ lp->cmajflt = strtoull(location, &location, 10);
+ location += 1;
+
+ /* (14) utime - %lu */
+ lp->utime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10));
+ location += 1;
+
+ /* (15) stime - %lu */
+ lp->stime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10));
+ location += 1;
+
+ /* (16) cutime - %ld */
+ lp->cutime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10));
+ location += 1;
+
+ /* (17) cstime - %ld */
+ lp->cstime = LinuxProcessTable_adjustTime(lhost, strtoull(location, &location, 10));
+ location += 1;
+
+ /* (18) priority - %ld */
+ process->priority = strtol(location, &location, 10);
+ location += 1;
+
+ /* (19) nice - %ld */
+ process->nice = strtol(location, &location, 10);
+ location += 1;
+
+ /* (20) num_threads - %ld */
+ process->nlwp = strtol(location, &location, 10);
+ location += 1;
+
+ /* Skip (21) itrealvalue - %ld */
+ location = strchr(location, ' ') + 1;
+
+ /* (22) starttime - %llu */
+ if (process->starttime_ctime == 0) {
+ process->starttime_ctime = lhost->boottime + LinuxProcessTable_adjustTime(lhost, strtoll(location, &location, 10)) / 100;
+ } else {
+ location = strchr(location, ' ');
+ }
+ location += 1;
+
+ /* Skip (23) - (38) */
+ for (int i = 0; i < 16; i++) {
+ location = strchr(location, ' ') + 1;
+ }
+
+ assert(location != NULL);
+
+ /* (39) processor - %d */
+ process->processor = strtol(location, &location, 10);
+
+ /* Ignore further fields */
+
+ process->time = lp->utime + lp->stime;
+
+ return true;
+}
+
+static bool LinuxProcessTable_readStatusFile(Process* process, openat_arg_t procFd) {
+ LinuxProcess* lp = (LinuxProcess*) process;
+
+ unsigned long ctxt = 0;
+#ifdef HAVE_VSERVER
+ lp->vxid = 0;
+#endif
+
+ FILE* statusfile = fopenat(procFd, "status", "r");
+ if (!statusfile)
+ return false;
+
+ char buffer[PROC_LINE_LENGTH + 1];
+
+ while (fgets(buffer, sizeof(buffer), statusfile)) {
+
+ if (String_startsWith(buffer, "NSpid:")) {
+ const char* ptr = buffer;
+ int pid_ns_count = 0;
+ while (*ptr && *ptr != '\n' && !isdigit((unsigned char)*ptr))
+ ++ptr;
+
+ while (*ptr && *ptr != '\n') {
+ if (isdigit(*ptr))
+ pid_ns_count++;
+ while (isdigit((unsigned char)*ptr))
+ ++ptr;
+ while (*ptr && *ptr != '\n' && !isdigit((unsigned char)*ptr))
+ ++ptr;
+ }
+
+ if (pid_ns_count > 1)
+ process->isRunningInContainer = true;
+
+ } else if (String_startsWith(buffer, "CapPrm:")) {
+ char* ptr = buffer + strlen("CapPrm:");
+ while (*ptr == ' ' || *ptr == '\t')
+ ptr++;
+
+ uint64_t cap_permitted = fast_strtoull_hex(&ptr, 16);
+ process->elevated_priv = cap_permitted != 0 && process->st_uid != 0;
+
+ } else if (String_startsWith(buffer, "voluntary_ctxt_switches:")) {
+ unsigned long vctxt;
+ int ok = sscanf(buffer, "voluntary_ctxt_switches:\t%lu", &vctxt);
+ if (ok >= 1) {
+ ctxt += vctxt;
+ }
+
+ } else if (String_startsWith(buffer, "nonvoluntary_ctxt_switches:")) {
+ unsigned long nvctxt;
+ int ok = sscanf(buffer, "nonvoluntary_ctxt_switches:\t%lu", &nvctxt);
+ if (ok >= 1) {
+ ctxt += nvctxt;
+ }
+
+#ifdef HAVE_VSERVER
+ } else if (String_startsWith(buffer, "VxID:")) {
+ int vxid;
+ int ok = sscanf(buffer, "VxID:\t%32d", &vxid);
+ if (ok >= 1) {
+ lp->vxid = vxid;
+ }
+#ifdef HAVE_ANCIENT_VSERVER
+ } else if (String_startsWith(buffer, "s_context:")) {
+ int vxid;
+ int ok = sscanf(buffer, "s_context:\t%32d", &vxid);
+ if (ok >= 1) {
+ lp->vxid = vxid;
+ }
+#endif /* HAVE_ANCIENT_VSERVER */
+#endif /* HAVE_VSERVER */
+ }
+ }
+
+ fclose(statusfile);
+
+ lp->ctxt_diff = (ctxt > lp->ctxt_total) ? (ctxt - lp->ctxt_total) : 0;
+ lp->ctxt_total = ctxt;
+
+ return true;
+}
+
+static bool LinuxProcessTable_updateUser(const Machine* host, Process* process, openat_arg_t procFd) {
+ struct stat sstat;
+#ifdef HAVE_OPENAT
+ int statok = fstat(procFd, &sstat);
+#else
+ int statok = stat(procFd, &sstat);
+#endif
+ if (statok == -1)
+ return false;
+
+ if (process->st_uid != sstat.st_uid) {
+ process->st_uid = sstat.st_uid;
+ process->user = UsersTable_getRef(host->usersTable, sstat.st_uid);
+ }
+
+ return true;
+}
+
+static void LinuxProcessTable_readIoFile(LinuxProcess* lp, openat_arg_t procFd, bool scanMainThread) {
+ Process* process = &lp->super;
+ const Machine* host = process->super.host;
+ char path[20] = "io";
+ char buffer[1024];
+ if (scanMainThread) {
+ xSnprintf(path, sizeof(path), "task/%"PRIi32"/io", (int32_t)Process_getPid(process));
+ }
+ ssize_t r = xReadfileat(procFd, path, buffer, sizeof(buffer));
+ if (r < 0) {
+ lp->io_rate_read_bps = NAN;
+ lp->io_rate_write_bps = NAN;
+ lp->io_rchar = ULLONG_MAX;
+ lp->io_wchar = ULLONG_MAX;
+ lp->io_syscr = ULLONG_MAX;
+ lp->io_syscw = ULLONG_MAX;
+ lp->io_read_bytes = ULLONG_MAX;
+ lp->io_write_bytes = ULLONG_MAX;
+ lp->io_cancelled_write_bytes = ULLONG_MAX;
+ lp->io_last_scan_time_ms = host->realtimeMs;
+ return;
+ }
+
+ unsigned long long last_read = lp->io_read_bytes;
+ unsigned long long last_write = lp->io_write_bytes;
+ unsigned long long time_delta = saturatingSub(host->realtimeMs, lp->io_last_scan_time_ms);
+
+ // Note: Linux Kernel documentation states that /proc/<pid>/io may be racy
+ // on 32-bit machines. (Documentation/filesystems/proc.rst)
+
+ char* buf = buffer;
+ const char* line;
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ switch (line[0]) {
+ case 'r':
+ if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) {
+ lp->io_rchar = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "ead_bytes: ")) {
+ lp->io_read_bytes = strtoull(line + 12, NULL, 10);
+ lp->io_rate_read_bps = time_delta ? saturatingSub(lp->io_read_bytes, last_read) * /*ms to s*/1000. / time_delta : NAN;
+ }
+ break;
+ case 'w':
+ if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) {
+ lp->io_wchar = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "rite_bytes: ")) {
+ lp->io_write_bytes = strtoull(line + 13, NULL, 10);
+ lp->io_rate_write_bps = time_delta ? saturatingSub(lp->io_write_bytes, last_write) * /*ms to s*/1000. / time_delta : NAN;
+ }
+ break;
+ case 's':
+ if (line[4] == 'r' && String_startsWith(line + 1, "yscr: ")) {
+ lp->io_syscr = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "yscw: ")) {
+ lp->io_syscw = strtoull(line + 7, NULL, 10);
+ }
+ break;
+ case 'c':
+ if (String_startsWith(line + 1, "ancelled_write_bytes: ")) {
+ lp->io_cancelled_write_bytes = strtoull(line + 23, NULL, 10);
+ }
+ }
+ }
+
+ lp->io_last_scan_time_ms = host->realtimeMs;
+}
+
+typedef struct LibraryData_ {
+ uint64_t size;
+ bool exec;
+} LibraryData;
+
+static void LinuxProcessTable_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* value, void* data) {
+ if (!data)
+ return;
+
+ if (!value)
+ return;
+
+ const LibraryData* v = (const LibraryData*)value;
+ uint64_t* d = (uint64_t*)data;
+ if (!v->exec)
+ return;
+
+ *d += v->size;
+}
+
+static void LinuxProcessTable_readMaps(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host, bool calcSize, bool checkDeletedLib) {
+ Process* proc = (Process*)process;
+
+ proc->usesDeletedLib = false;
+
+ FILE* mapsfile = fopenat(procFd, "maps", "r");
+ if (!mapsfile)
+ return;
+
+ Hashtable* ht = NULL;
+ if (calcSize)
+ ht = Hashtable_new(64, true);
+
+ char buffer[1024];
+ while (fgets(buffer, sizeof(buffer), mapsfile)) {
+ uint64_t map_start;
+ uint64_t map_end;
+ bool map_execute;
+ unsigned int map_devmaj;
+ unsigned int map_devmin;
+ uint64_t map_inode;
+
+ // Short circuit test: Look for a slash
+ if (!strchr(buffer, '/'))
+ continue;
+
+ // Parse format: "%Lx-%Lx %4s %x %2x:%2x %Ld"
+ char* readptr = buffer;
+
+ map_start = fast_strtoull_hex(&readptr, 16);
+ if ('-' != *readptr++)
+ continue;
+
+ map_end = fast_strtoull_hex(&readptr, 16);
+ if (' ' != *readptr++)
+ continue;
+
+ if (!readptr[0] || !readptr[1] || !readptr[2] || !readptr[3])
+ continue;
+
+ map_execute = (readptr[2] == 'x');
+ readptr += 4;
+ if (' ' != *readptr++)
+ continue;
+
+ while (*readptr > ' ')
+ readptr++; // Skip parsing this hex value
+ if (' ' != *readptr++)
+ continue;
+
+ map_devmaj = fast_strtoull_hex(&readptr, 4);
+ if (':' != *readptr++)
+ continue;
+
+ map_devmin = fast_strtoull_hex(&readptr, 4);
+ if (' ' != *readptr++)
+ continue;
+
+ //Minor shortcut: Once we know there's no file for this region, we skip
+ if (!map_devmaj && !map_devmin)
+ continue;
+
+ map_inode = fast_strtoull_dec(&readptr, 20);
+ if (!map_inode)
+ continue;
+
+ if (calcSize) {
+ LibraryData* libdata = Hashtable_get(ht, map_inode);
+ if (!libdata) {
+ libdata = xCalloc(1, sizeof(LibraryData));
+ Hashtable_put(ht, map_inode, libdata);
+ }
+
+ libdata->size += map_end - map_start;
+ libdata->exec |= map_execute;
+ }
+
+ if (checkDeletedLib && map_execute && !proc->usesDeletedLib) {
+ while (*readptr == ' ')
+ readptr++;
+
+ if (*readptr != '/')
+ continue;
+
+ if (String_startsWith(readptr, "/memfd:"))
+ continue;
+
+ /* Virtualbox maps /dev/zero for memory allocation. That results in
+ * false positive, so ignore. */
+ if (String_eq(readptr, "/dev/zero (deleted)\n"))
+ continue;
+
+ if (strstr(readptr, " (deleted)\n")) {
+ proc->usesDeletedLib = true;
+ if (!calcSize)
+ break;
+ }
+ }
+ }
+
+ fclose(mapsfile);
+
+ if (calcSize) {
+ uint64_t total_size = 0;
+ Hashtable_foreach(ht, LinuxProcessTable_calcLibSize_helper, &total_size);
+
+ Hashtable_delete(ht);
+
+ process->m_lrs = total_size / host->pageSize;
+ }
+}
+
+static bool LinuxProcessTable_readStatmFile(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host) {
+ FILE* statmfile = fopenat(procFd, "statm", "r");
+ if (!statmfile)
+ return false;
+
+ long int dummy, dummy2;
+
+ int r = fscanf(statmfile, "%ld %ld %ld %ld %ld %ld %ld",
+ &process->super.m_virt,
+ &process->super.m_resident,
+ &process->m_share,
+ &process->m_trs,
+ &dummy, /* unused since Linux 2.6; always 0 */
+ &process->m_drs,
+ &dummy2); /* unused since Linux 2.6; always 0 */
+ fclose(statmfile);
+
+ if (r == 7) {
+ process->super.m_virt *= host->pageSizeKB;
+ process->super.m_resident *= host->pageSizeKB;
+
+ process->m_priv = process->super.m_resident - (process->m_share * host->pageSizeKB);
+ }
+
+ return r == 7;
+}
+
+static bool LinuxProcessTable_readSmapsFile(LinuxProcess* process, openat_arg_t procFd, bool haveSmapsRollup) {
+ //http://elixir.free-electrons.com/linux/v4.10/source/fs/proc/task_mmu.c#L719
+ //kernel will return data in chunks of size PAGE_SIZE or less.
+ FILE* f = fopenat(procFd, haveSmapsRollup ? "smaps_rollup" : "smaps", "r");
+ if (!f)
+ return false;
+
+ process->m_pss = 0;
+ process->m_swap = 0;
+ process->m_psswp = 0;
+
+ char buffer[256];
+ while (fgets(buffer, sizeof(buffer), f)) {
+ if (!strchr(buffer, '\n')) {
+ // Partial line, skip to end of this line
+ while (fgets(buffer, sizeof(buffer), f)) {
+ if (strchr(buffer, '\n')) {
+ break;
+ }
+ }
+ continue;
+ }
+
+ if (String_startsWith(buffer, "Pss:")) {
+ process->m_pss += strtol(buffer + 4, NULL, 10);
+ } else if (String_startsWith(buffer, "Swap:")) {
+ process->m_swap += strtol(buffer + 5, NULL, 10);
+ } else if (String_startsWith(buffer, "SwapPss:")) {
+ process->m_psswp += strtol(buffer + 8, NULL, 10);
+ }
+ }
+
+ fclose(f);
+ return true;
+}
+
+#ifdef HAVE_OPENVZ
+
+static void LinuxProcessTable_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) {
+ if (access(PROCDIR "/vz", R_OK) != 0) {
+ free(process->ctid);
+ process->ctid = NULL;
+ process->vpid = Process_getPid(&process->super);
+ return;
+ }
+
+ FILE* file = fopenat(procFd, "status", "r");
+ if (!file) {
+ free(process->ctid);
+ process->ctid = NULL;
+ process->vpid = Process_getPid(&process->super);
+ return;
+ }
+
+ bool foundEnvID = false;
+ bool foundVPid = false;
+ char linebuf[256];
+ while (fgets(linebuf, sizeof(linebuf), file) != NULL) {
+ if (strchr(linebuf, '\n') == NULL) {
+ // Partial line, skip to end of this line
+ while (fgets(linebuf, sizeof(linebuf), file) != NULL) {
+ if (strchr(linebuf, '\n') != NULL) {
+ break;
+ }
+ }
+ continue;
+ }
+
+ char* name_value_sep = strchr(linebuf, ':');
+ if (name_value_sep == NULL) {
+ continue;
+ }
+
+ int field;
+ if (0 == strncasecmp(linebuf, "envID", name_value_sep - linebuf)) {
+ field = 1;
+ } else if (0 == strncasecmp(linebuf, "VPid", name_value_sep - linebuf)) {
+ field = 2;
+ } else {
+ continue;
+ }
+
+ do {
+ name_value_sep++;
+ } while (*name_value_sep != '\0' && *name_value_sep <= 32);
+
+ char* value_end = name_value_sep;
+
+ while (*value_end > 32) {
+ value_end++;
+ }
+
+ if (name_value_sep == value_end) {
+ continue;
+ }
+
+ *value_end = '\0';
+
+ switch (field) {
+ case 1:
+ foundEnvID = true;
+ if (!String_eq(name_value_sep, process->ctid ? process->ctid : ""))
+ free_and_xStrdup(&process->ctid, name_value_sep);
+ break;
+ case 2:
+ foundVPid = true;
+ process->vpid = strtoul(name_value_sep, NULL, 0);
+ break;
+ default:
+ //Sanity Check: Should never reach here, or the implementation is missing something!
+ assert(false && "OpenVZ handling: Unimplemented case for field handling reached.");
+ }
+ }
+
+ fclose(file);
+
+ if (!foundEnvID) {
+ free(process->ctid);
+ process->ctid = NULL;
+ }
+
+ if (!foundVPid) {
+ process->vpid = Process_getPid(&process->super);
+ }
+}
+
+#endif
+
+static void LinuxProcessTable_readCGroupFile(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "cgroup", "r");
+ if (!file) {
+ if (process->cgroup) {
+ free(process->cgroup);
+ process->cgroup = NULL;
+ }
+ if (process->cgroup_short) {
+ free(process->cgroup_short);
+ process->cgroup_short = NULL;
+ }
+ if (process->container_short) {
+ free(process->container_short);
+ process->container_short = NULL;
+ }
+ return;
+ }
+ char output[PROC_LINE_LENGTH + 1];
+ output[0] = '\0';
+ char* at = output;
+ int left = PROC_LINE_LENGTH;
+ while (!feof(file) && left > 0) {
+ char buffer[PROC_LINE_LENGTH + 1];
+ const char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
+ if (!ok)
+ break;
+
+ char* group = buffer;
+ for (size_t i = 0; i < 2; i++) {
+ group = String_strchrnul(group, ':');
+ if (!*group)
+ break;
+ group++;
+ }
+
+ char* eol = String_strchrnul(group, '\n');
+ *eol = '\0';
+
+ if (at != output) {
+ *at = ';';
+ at++;
+ left--;
+ }
+ int wrote = snprintf(at, left, "%s", group);
+ left -= wrote;
+ }
+ fclose(file);
+
+ bool changed = !process->cgroup || !String_eq(process->cgroup, output);
+
+ Row_updateFieldWidth(CGROUP, strlen(output));
+ free_and_xStrdup(&process->cgroup, output);
+
+ if (!changed) {
+ if (process->cgroup_short) {
+ Row_updateFieldWidth(CCGROUP, strlen(process->cgroup_short));
+ } else {
+ //CCGROUP is alias to normal CGROUP if shortening fails
+ Row_updateFieldWidth(CCGROUP, strlen(process->cgroup));
+ }
+ if (process->container_short) {
+ Row_updateFieldWidth(CONTAINER, strlen(process->container_short));
+ } else {
+ Row_updateFieldWidth(CONTAINER, strlen("N/A"));
+ }
+ return;
+ }
+
+ char* cgroup_short = CGroup_filterName(process->cgroup);
+ if (cgroup_short) {
+ Row_updateFieldWidth(CCGROUP, strlen(cgroup_short));
+ free_and_xStrdup(&process->cgroup_short, cgroup_short);
+ free(cgroup_short);
+ } else {
+ //CCGROUP is alias to normal CGROUP if shortening fails
+ Row_updateFieldWidth(CCGROUP, strlen(process->cgroup));
+ free(process->cgroup_short);
+ process->cgroup_short = NULL;
+ }
+
+ char* container_short = CGroup_filterContainer(process->cgroup);
+ if (container_short) {
+ Row_updateFieldWidth(CONTAINER, strlen(container_short));
+ free_and_xStrdup(&process->container_short, container_short);
+ free(container_short);
+ } else {
+ //CONTAINER is just "N/A" if shortening fails
+ Row_updateFieldWidth(CONTAINER, strlen("N/A"));
+ free(process->container_short);
+ process->container_short = NULL;
+ }
+}
+
+static void LinuxProcessTable_readOomData(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "oom_score", "r");
+ if (!file)
+ return;
+
+ char buffer[PROC_LINE_LENGTH + 1];
+ if (fgets(buffer, PROC_LINE_LENGTH, file)) {
+ unsigned int oom;
+ int ok = sscanf(buffer, "%u", &oom);
+ if (ok >= 1) {
+ process->oom = oom;
+ }
+ }
+ fclose(file);
+}
+
+static void LinuxProcessTable_readAutogroup(LinuxProcess* process, openat_arg_t procFd) {
+ process->autogroup_id = -1;
+
+ char autogroup[64]; // space for two numeric values and fixed length strings
+ ssize_t amtRead = xReadfileat(procFd, "autogroup", autogroup, sizeof(autogroup));
+ if (amtRead < 0)
+ return;
+
+ long int identity;
+ int nice;
+ int ok = sscanf(autogroup, "/autogroup-%ld nice %d", &identity, &nice);
+ if (ok == 2) {
+ process->autogroup_id = identity;
+ process->autogroup_nice = nice;
+ }
+}
+
+static void LinuxProcessTable_readSecattrData(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "attr/current", "r");
+ if (!file) {
+ free(process->secattr);
+ process->secattr = NULL;
+ return;
+ }
+
+ char buffer[PROC_LINE_LENGTH + 1];
+ const char* res = fgets(buffer, sizeof(buffer), file);
+ fclose(file);
+ if (!res) {
+ free(process->secattr);
+ process->secattr = NULL;
+ return;
+ }
+ char* newline = strchr(buffer, '\n');
+ if (newline) {
+ *newline = '\0';
+ }
+
+ Row_updateFieldWidth(SECATTR, strlen(buffer));
+
+ if (process->secattr && String_eq(process->secattr, buffer)) {
+ return;
+ }
+ free_and_xStrdup(&process->secattr, buffer);
+}
+
+static void LinuxProcessTable_readCwd(LinuxProcess* process, openat_arg_t procFd) {
+ char pathBuffer[PATH_MAX + 1] = {0};
+
+#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)
+ ssize_t r = readlinkat(procFd, "cwd", pathBuffer, sizeof(pathBuffer) - 1);
+#else
+ ssize_t r = Compat_readlink(procFd, "cwd", pathBuffer, sizeof(pathBuffer) - 1);
+#endif
+
+ if (r < 0) {
+ free(process->super.procCwd);
+ process->super.procCwd = NULL;
+ return;
+ }
+
+ pathBuffer[r] = '\0';
+
+ if (process->super.procCwd && String_eq(process->super.procCwd, pathBuffer))
+ return;
+
+ free_and_xStrdup(&process->super.procCwd, pathBuffer);
+}
+
+#ifdef HAVE_DELAYACCT
+
+static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {
+ struct nlmsghdr* nlhdr;
+ struct nlattr* nlattrs[TASKSTATS_TYPE_MAX + 1];
+ const struct nlattr* nlattr;
+ struct taskstats stats;
+ int rem;
+ LinuxProcess* lp = (LinuxProcess*) linuxProcess;
+
+ nlhdr = nlmsg_hdr(nlmsg);
+
+ if (genlmsg_parse(nlhdr, 0, nlattrs, TASKSTATS_TYPE_MAX, NULL) < 0) {
+ return NL_SKIP;
+ }
+
+ if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) {
+ memcpy(&stats, nla_data(nla_next(nla_data(nlattr), &rem)), sizeof(stats));
+ assert(Process_getPid(&lp->super) == (pid_t)stats.ac_pid);
+
+ // The xxx_delay_total values wrap around on overflow.
+ // (Linux Kernel "Documentation/accounting/taskstats-struct.rst")
+ unsigned long long int timeDelta = stats.ac_etime * 1000 - lp->delay_read_time;
+ #define DELTAPERC(x, y) (timeDelta ? MINIMUM((float)((x) - (y)) / timeDelta * 100.0f, 100.0f) : NAN)
+ lp->cpu_delay_percent = DELTAPERC(stats.cpu_delay_total, lp->cpu_delay_total);
+ lp->blkio_delay_percent = DELTAPERC(stats.blkio_delay_total, lp->blkio_delay_total);
+ lp->swapin_delay_percent = DELTAPERC(stats.swapin_delay_total, lp->swapin_delay_total);
+ #undef DELTAPERC
+
+ lp->swapin_delay_total = stats.swapin_delay_total;
+ lp->blkio_delay_total = stats.blkio_delay_total;
+ lp->cpu_delay_total = stats.cpu_delay_total;
+ lp->delay_read_time = stats.ac_etime * 1000;
+ }
+ return NL_OK;
+}
+
+static void LinuxProcessTable_readDelayAcctData(LinuxProcessTable* this, LinuxProcess* process) {
+ struct nl_msg* msg;
+
+ if (!this->netlink_socket) {
+ LinuxProcessTable_initNetlinkSocket(this);
+ if (!this->netlink_socket) {
+ goto delayacct_failure;
+ }
+ }
+
+ if (nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) {
+ goto delayacct_failure;
+ }
+
+ if (! (msg = nlmsg_alloc())) {
+ goto delayacct_failure;
+ }
+
+ if (! genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, this->netlink_family, 0, NLM_F_REQUEST, TASKSTATS_CMD_GET, TASKSTATS_VERSION)) {
+ nlmsg_free(msg);
+ }
+
+ if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, Process_getPid(&process->super)) < 0) {
+ nlmsg_free(msg);
+ }
+
+ if (nl_send_sync(this->netlink_socket, msg) < 0) {
+ goto delayacct_failure;
+ }
+
+ if (nl_recvmsgs_default(this->netlink_socket) < 0) {
+ goto delayacct_failure;
+ }
+
+ return;
+
+delayacct_failure:
+ process->swapin_delay_percent = NAN;
+ process->blkio_delay_percent = NAN;
+ process->cpu_delay_percent = NAN;
+}
+
+#endif
+
+static bool LinuxProcessTable_readCmdlineFile(Process* process, openat_arg_t procFd) {
+ char command[4096 + 1]; // max cmdline length on Linux
+ ssize_t amtRead = xReadfileat(procFd, "cmdline", command, sizeof(command));
+ if (amtRead <= 0)
+ return false;
+
+ int tokenEnd = 0;
+ int tokenStart = 0;
+ int lastChar = 0;
+ bool argSepNUL = false;
+ bool argSepSpace = false;
+
+ for (int i = 0; i < amtRead; i++) {
+ /* newline used as delimiter - when forming the mergedCommand, newline is
+ * converted to space by Process_makeCommandStr */
+ if (command[i] == '\0') {
+ command[i] = '\n';
+ } else {
+ /* Record some information for the argument parsing heuristic below. */
+ if (tokenEnd)
+ argSepNUL = true;
+ if (command[i] <= ' ')
+ argSepSpace = true;
+ }
+
+ if (command[i] == '\n') {
+ if (tokenEnd == 0) {
+ tokenEnd = i;
+ }
+ } else {
+ /* htop considers the next character after the last / that is before
+ * basenameOffset, as the start of the basename in cmdline - see
+ * Process_writeCommand */
+ if (!tokenEnd && command[i] == '/') {
+ tokenStart = i + 1;
+ }
+ lastChar = i;
+ }
+ }
+
+ command[lastChar + 1] = '\0';
+
+ if (!argSepNUL && argSepSpace) {
+ /* Argument parsing heuristic.
+ *
+ * This heuristic is used for processes that rewrite their command line.
+ * Normally the command line is split by using NUL bytes between each argument.
+ * But some programs like chrome flatten this using spaces.
+ *
+ * This heuristic tries its best to undo this loss of information.
+ * To achieve this, we treat every character <= 32 as argument separators
+ * (i.e. all of ASCII control sequences and space).
+ * We then search for the basename of the cmdline in the first argument we found that way.
+ * As path names may contain we try to cross-validate if the path we got that way exists.
+ */
+
+ tokenStart = tokenEnd = 0;
+
+ // From initial scan we know there's at least one space.
+ // Check if that's part of a filename for an existing file.
+ if (Compat_faccessat(AT_FDCWD, command, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
+ // If we reach here the path does not exist.
+ // Thus begin searching for the part of it that actually is.
+
+ int tokenArg0Start = 0;
+
+ for (int i = 0; i <= lastChar; i++) {
+ /* Any ASCII control or space used as delimiter */
+ char tmpCommandChar = command[i];
+
+ if (command[i] <= ' ') {
+ if (!tokenEnd) {
+ command[i] = '\0';
+
+ bool found = Compat_faccessat(AT_FDCWD, command, F_OK, AT_SYMLINK_NOFOLLOW) == 0;
+
+ // Restore if this wasn't it
+ command[i] = found ? '\n' : tmpCommandChar;
+
+ if (found)
+ tokenEnd = i;
+ if (!tokenArg0Start)
+ tokenArg0Start = tokenStart;
+ } else {
+ // Split on every further separator, regardless of path correctness
+ command[i] = '\n';
+ }
+ } else if (!tokenEnd) {
+ if (command[i] == '/' || (command[i] == '\\' && (!tokenStart || command[tokenStart - 1] == '\\'))) {
+ tokenStart = i + 1;
+ } else if (command[i] == ':' && (command[i + 1] != '/' && command[i + 1] != '\\')) {
+ tokenEnd = i;
+ }
+ }
+ }
+
+ if (!tokenEnd) {
+ tokenStart = tokenArg0Start;
+
+ // No token delimiter found, forcibly split
+ for (int i = 0; i <= lastChar; i++) {
+ if (command[i] <= ' ') {
+ command[i] = '\n';
+ if (!tokenEnd) {
+ tokenEnd = i;
+ }
+ }
+ }
+ }
+ }
+
+ /* Some command lines are hard to parse, like
+ * file.so [kdeinit5] file local:/run/user/1000/klauncherdqbouY.1.slave-socket local:/run/user/1000/kded5TwsDAx.1.slave-socket
+ * Reset if start is behind end.
+ */
+ if (tokenStart >= tokenEnd)
+ tokenStart = tokenEnd = 0;
+ }
+
+ if (tokenEnd == 0) {
+ tokenEnd = lastChar + 1;
+ }
+
+ Process_updateCmdline(process, command, tokenStart, tokenEnd);
+
+ /* /proc/[pid]/comm could change, so should be updated */
+ if ((amtRead = xReadfileat(procFd, "comm", command, sizeof(command))) > 0) {
+ command[amtRead - 1] = '\0';
+ Process_updateComm(process, command);
+ } else {
+ Process_updateComm(process, NULL);
+ }
+
+ char filename[MAX_NAME + 1];
+
+ /* execve could change /proc/[pid]/exe, so procExe should be updated */
+#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)
+ amtRead = readlinkat(procFd, "exe", filename, sizeof(filename) - 1);
+#else
+ amtRead = Compat_readlink(procFd, "exe", filename, sizeof(filename) - 1);
+#endif
+ if (amtRead > 0) {
+ filename[amtRead] = 0;
+ if (!process->procExe ||
+ (!process->procExeDeleted && !String_eq(filename, process->procExe)) ||
+ process->procExeDeleted) {
+
+ const char* deletedMarker = " (deleted)";
+ const size_t markerLen = strlen(deletedMarker);
+ const size_t filenameLen = strlen(filename);
+
+ if (filenameLen > markerLen) {
+ bool oldExeDeleted = process->procExeDeleted;
+
+ process->procExeDeleted = String_eq(filename + filenameLen - markerLen, deletedMarker);
+
+ if (process->procExeDeleted)
+ filename[filenameLen - markerLen] = '\0';
+
+ if (oldExeDeleted != process->procExeDeleted)
+ process->mergedCommand.lastUpdate = 0;
+ }
+
+ Process_updateExe(process, filename);
+ }
+ } else if (process->procExe) {
+ Process_updateExe(process, NULL);
+ process->procExeDeleted = false;
+ }
+
+ return true;
+}
+
+static char* LinuxProcessTable_updateTtyDevice(TtyDriver* ttyDrivers, unsigned long int tty_nr) {
+ unsigned int maj = major(tty_nr);
+ unsigned int min = minor(tty_nr);
+
+ int i = -1;
+ for (;;) {
+ i++;
+ if ((!ttyDrivers[i].path) || maj < ttyDrivers[i].major) {
+ break;
+ }
+ if (maj > ttyDrivers[i].major) {
+ continue;
+ }
+ if (min < ttyDrivers[i].minorFrom) {
+ break;
+ }
+ if (min > ttyDrivers[i].minorTo) {
+ continue;
+ }
+ unsigned int idx = min - ttyDrivers[i].minorFrom;
+ struct stat sstat;
+ char* fullPath;
+ for (;;) {
+ xAsprintf(&fullPath, "%s/%d", ttyDrivers[i].path, idx);
+ int err = stat(fullPath, &sstat);
+ if (err == 0 && major(sstat.st_rdev) == maj && minor(sstat.st_rdev) == min) {
+ return fullPath;
+ }
+ free(fullPath);
+
+ xAsprintf(&fullPath, "%s%d", ttyDrivers[i].path, idx);
+ err = stat(fullPath, &sstat);
+ if (err == 0 && major(sstat.st_rdev) == maj && minor(sstat.st_rdev) == min) {
+ return fullPath;
+ }
+ free(fullPath);
+
+ if (idx == min) {
+ break;
+ }
+
+ idx = min;
+ }
+ int err = stat(ttyDrivers[i].path, &sstat);
+ if (err == 0 && tty_nr == sstat.st_rdev) {
+ return xStrdup(ttyDrivers[i].path);
+ }
+ }
+ char* out;
+ xAsprintf(&out, "/dev/%u:%u", maj, min);
+ return out;
+}
+
+static bool isOlderThan(const Process* proc, unsigned int seconds) {
+ const Machine* host = proc->super.host;
+
+ assert(host->realtimeMs > 0);
+
+ /* Starttime might not yet be parsed */
+ if (proc->starttime_ctime <= 0)
+ return false;
+
+ uint64_t realtime = host->realtimeMs / 1000;
+
+ if (realtime < (uint64_t)proc->starttime_ctime)
+ return false;
+
+ return realtime - proc->starttime_ctime > seconds;
+}
+
+static bool LinuxProcessTable_recurseProcTree(LinuxProcessTable* this, openat_arg_t parentFd, const LinuxMachine* lhost, const char* dirname, const Process* parent) {
+ ProcessTable* pt = (ProcessTable*) this;
+ const Machine* host = &lhost->super;
+ const Settings* settings = host->settings;
+ const ScreenSettings* ss = settings->ss;
+ const struct dirent* entry;
+
+ /* set runningTasks from /proc/stat (from Machine_scanCPUTime) */
+ pt->runningTasks = lhost->runningTasks;
+
+#ifdef HAVE_OPENAT
+ int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (dirFd < 0)
+ return false;
+ DIR* dir = fdopendir(dirFd);
+#else
+ char dirFd[4096];
+ xSnprintf(dirFd, sizeof(dirFd), "%s/%s", parentFd, dirname);
+ DIR* dir = opendir(dirFd);
+#endif
+ if (!dir) {
+ Compat_openatArgClose(dirFd);
+ return false;
+ }
+
+ const bool hideKernelThreads = settings->hideKernelThreads;
+ const bool hideUserlandThreads = settings->hideUserlandThreads;
+ const bool hideRunningInContainer = settings->hideRunningInContainer;
+ while ((entry = readdir(dir)) != NULL) {
+ const char* name = entry->d_name;
+
+ // Ignore all non-directories
+ if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) {
+ continue;
+ }
+
+ // The RedHat kernel hides threads with a dot.
+ // I believe this is non-standard.
+ if (name[0] == '.') {
+ name++;
+ }
+
+ // Just skip all non-number directories.
+ if (name[0] < '0' || name[0] > '9') {
+ continue;
+ }
+
+ // filename is a number: process directory
+ int pid;
+ {
+ char* endptr;
+ unsigned long parsedPid = strtoul(name, &endptr, 10);
+ if (parsedPid == 0 || parsedPid == ULONG_MAX || *endptr != '\0')
+ continue;
+ pid = parsedPid;
+ }
+
+ // Skip task directory of main thread
+ if (parent && pid == Process_getPid(parent))
+ continue;
+
+#ifdef HAVE_OPENAT
+ int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (procFd < 0)
+ continue;
+#else
+ char procFd[4096];
+ xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name);
+#endif
+
+ bool preExisting;
+ Process* proc = ProcessTable_getProcess(pt, pid, &preExisting, LinuxProcess_new);
+ LinuxProcess* lp = (LinuxProcess*) proc;
+
+ Process_setThreadGroup(proc, parent ? Process_getPid(parent) : pid);
+ proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc);
+
+ LinuxProcessTable_recurseProcTree(this, procFd, lhost, "task", proc);
+
+ /*
+ * These conditions will not trigger on first occurrence, cause we need to
+ * add the process to the ProcessTable and do all one time scans
+ * (e.g. parsing the cmdline to detect a kernel thread)
+ * But it will short-circuit subsequent scans.
+ */
+ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ pt->kernelThreads++;
+ pt->totalTasks++;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
+ if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ pt->userlandThreads++;
+ pt->totalTasks++;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
+ if (preExisting && hideRunningInContainer && proc->isRunningInContainer) {
+ proc->super.updated = true;
+ proc->super.show = false;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
+
+ bool scanMainThread = !hideUserlandThreads && !Process_isKernelThread(proc) && !parent;
+ if (ss->flags & PROCESS_FLAG_IO)
+ LinuxProcessTable_readIoFile(lp, procFd, scanMainThread);
+
+ if (!LinuxProcessTable_readStatmFile(lp, procFd, lhost))
+ goto errorReadingProcess;
+
+ {
+ bool prev = proc->usesDeletedLib;
+
+ if (!proc->isKernelThread && !proc->isUserlandThread &&
+ ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(proc, 10)))) {
+
+ // Check if we really should recalculate the M_LRS value for this process
+ uint64_t passedTimeInMs = host->realtimeMs - lp->last_mlrs_calctime;
+
+ uint64_t recheck = ((uint64_t)rand()) % 2048;
+
+ if (passedTimeInMs > recheck) {
+ lp->last_mlrs_calctime = host->realtimeMs;
+ LinuxProcessTable_readMaps(lp, procFd, lhost, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe);
+ }
+ } else {
+ /* Copy from process structure in threads and reset if setting got disabled */
+ proc->usesDeletedLib = (proc->isUserlandThread && parent) ? parent->usesDeletedLib : false;
+ lp->m_lrs = (proc->isUserlandThread && parent) ? ((const LinuxProcess*)parent)->m_lrs : 0;
+ }
+
+ if (prev != proc->usesDeletedLib)
+ proc->mergedCommand.lastUpdate = 0;
+ }
+
+ if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
+ if (!parent) {
+ // Read smaps file of each process only every second pass to improve performance
+ static int smaps_flag = 0;
+ if ((pid & 1) == smaps_flag) {
+ LinuxProcessTable_readSmapsFile(lp, procFd, this->haveSmapsRollup);
+ }
+ if (pid == 1) {
+ smaps_flag = !smaps_flag;
+ }
+ } else {
+ lp->m_pss = ((const LinuxProcess*)parent)->m_pss;
+ }
+ }
+
+ char statCommand[MAX_NAME + 1];
+ unsigned long long int lasttimes = (lp->utime + lp->stime);
+ unsigned long int tty_nr = proc->tty_nr;
+ if (!LinuxProcessTable_readStatFile(lp, procFd, lhost, scanMainThread, statCommand, sizeof(statCommand)))
+ goto errorReadingProcess;
+
+ if (lp->flags & PF_KTHREAD) {
+ proc->isKernelThread = true;
+ }
+
+ if (tty_nr != proc->tty_nr && this->ttyDrivers) {
+ free(proc->tty_name);
+ proc->tty_name = LinuxProcessTable_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) {
+ LinuxProcess_updateIOPriority(proc);
+ }
+
+ proc->percent_cpu = NAN;
+ /* lhost->period might be 0 after system sleep */
+ if (lhost->period > 0.0) {
+ float percent_cpu = saturatingSub(lp->utime + lp->stime, lasttimes) / lhost->period * 100.0;
+ proc->percent_cpu = MINIMUM(percent_cpu, host->activeCPUs * 100.0F);
+ }
+ proc->percent_mem = proc->m_resident / (double)(host->totalMem) * 100.0;
+ Process_updateCPUFieldWidths(proc->percent_cpu);
+
+ if (!LinuxProcessTable_updateUser(host, proc, procFd))
+ goto errorReadingProcess;
+
+ if (!LinuxProcessTable_readStatusFile(proc, procFd))
+ goto errorReadingProcess;
+
+ if (!preExisting) {
+
+ #ifdef HAVE_OPENVZ
+ if (ss->flags & PROCESS_FLAG_LINUX_OPENVZ) {
+ LinuxProcessTable_readOpenVZData(lp, procFd);
+ }
+ #endif
+
+ if (proc->isKernelThread) {
+ Process_updateCmdline(proc, NULL, 0, 0);
+ } else if (!LinuxProcessTable_readCmdlineFile(proc, procFd)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+
+ Process_fillStarttimeBuffer(proc);
+
+ ProcessTable_add(pt, proc);
+ } else {
+ if (settings->updateProcessNames && proc->state != ZOMBIE) {
+ if (proc->isKernelThread) {
+ Process_updateCmdline(proc, NULL, 0, 0);
+ } else if (!LinuxProcessTable_readCmdlineFile(proc, procFd)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+ }
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_CGROUP)
+ LinuxProcessTable_readCGroupFile(lp, procFd);
+
+ #ifdef HAVE_DELAYACCT
+ if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) {
+ LinuxProcessTable_readDelayAcctData(this, lp);
+ }
+ #endif
+
+ if (ss->flags & PROCESS_FLAG_LINUX_OOM) {
+ LinuxProcessTable_readOomData(lp, procFd);
+ }
+
+ if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) {
+ LinuxProcessTable_readSecattrData(lp, procFd);
+ }
+
+ if (ss->flags & PROCESS_FLAG_CWD) {
+ LinuxProcessTable_readCwd(lp, procFd);
+ }
+
+ if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {
+ LinuxProcessTable_readAutogroup(lp, procFd);
+ }
+
+ #ifdef SCHEDULER_SUPPORT
+ if (ss->flags & PROCESS_FLAG_SCHEDPOL) {
+ Scheduling_readProcessPolicy(proc);
+ }
+ #endif
+
+ if (!proc->cmdline && statCommand[0] &&
+ (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) {
+ Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
+ }
+
+ /*
+ * Final section after all data has been gathered
+ */
+
+ proc->super.updated = true;
+ Compat_openatArgClose(procFd);
+
+ if (hideRunningInContainer && proc->isRunningInContainer) {
+ proc->super.show = false;
+ continue;
+ }
+
+ if (Process_isKernelThread(proc)) {
+ pt->kernelThreads++;
+ } else if (Process_isUserlandThread(proc)) {
+ pt->userlandThreads++;
+ }
+
+ /* Set at the end when we know if a new entry is a thread */
+ proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+
+ pt->totalTasks++;
+ /* runningTasks is set in Machine_scanCPUTime() from /proc/stat */
+ continue;
+
+ // Exception handler.
+
+errorReadingProcess:
+ {
+#ifdef HAVE_OPENAT
+ if (procFd >= 0)
+ close(procFd);
+#endif
+
+ if (preExisting) {
+ /*
+ * The only real reason for coming here (apart from Linux violating the /proc API)
+ * would be the process going away with its /proc files disappearing (!HAVE_OPENAT).
+ * However, we want to keep in the process list for now for the "highlight dying" mode.
+ */
+ } else {
+ /* A really short-lived process that we don't have full info about */
+ Process_delete((Object*)proc);
+ }
+ }
+ }
+ closedir(dir);
+ return true;
+}
+
+void ProcessTable_goThroughEntries(ProcessTable* super) {
+ LinuxProcessTable* this = (LinuxProcessTable*) super;
+ const Machine* host = super->super.host;
+ const Settings* settings = host->settings;
+ const LinuxMachine* lhost = (const LinuxMachine*) host;
+
+ if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) {
+ // Refer to sched(7) 'autogroup feature' section
+ // The kernel feature can be enabled/disabled through procfs at
+ // any time, so check for it at the start of each sample - only
+ // read from per-process procfs files if it's globally enabled.
+ this->haveAutogroup = LinuxProcess_isAutogroupEnabled();
+ } else {
+ this->haveAutogroup = false;
+ }
+
+ /* PROCDIR is an absolute path */
+ assert(PROCDIR[0] == '/');
+#ifdef HAVE_OPENAT
+ openat_arg_t rootFd = AT_FDCWD;
+#else
+ openat_arg_t rootFd = "";
+#endif
+
+ LinuxProcessTable_recurseProcTree(this, rootFd, lhost, PROCDIR, NULL);
+}
diff --git a/linux/LinuxProcessTable.h b/linux/LinuxProcessTable.h
new file mode 100644
index 0000000..b87f9b0
--- /dev/null
+++ b/linux/LinuxProcessTable.h
@@ -0,0 +1,35 @@
+#ifndef HEADER_LinuxProcessTable
+#define HEADER_LinuxProcessTable
+/*
+htop - LinuxProcessTable.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <stdbool.h>
+
+#include "ProcessTable.h"
+
+
+typedef struct TtyDriver_ {
+ char* path;
+ unsigned int major;
+ unsigned int minorFrom;
+ unsigned int minorTo;
+} TtyDriver;
+
+typedef struct LinuxProcessTable_ {
+ ProcessTable super;
+
+ TtyDriver* ttyDrivers;
+ bool haveSmapsRollup;
+ bool haveAutogroup;
+
+ #ifdef HAVE_DELAYACCT
+ struct nl_sock* netlink_socket;
+ int netlink_family;
+ #endif
+} LinuxProcessTable;
+
+#endif
diff --git a/linux/Platform.c b/linux/Platform.c
new file mode 100644
index 0000000..8dc8bb5
--- /dev/null
+++ b/linux/Platform.c
@@ -0,0 +1,1087 @@
+/*
+htop - linux/Platform.c
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/Platform.h"
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/sysmacros.h>
+
+#include "BatteryMeter.h"
+#include "ClockMeter.h"
+#include "Compat.h"
+#include "CPUMeter.h"
+#include "DateMeter.h"
+#include "DateTimeMeter.h"
+#include "DiskIOMeter.h"
+#include "FileDescriptorMeter.h"
+#include "HostnameMeter.h"
+#include "HugePageMeter.h"
+#include "LoadAverageMeter.h"
+#include "Machine.h"
+#include "Macros.h"
+#include "MainPanel.h"
+#include "Meter.h"
+#include "MemoryMeter.h"
+#include "MemorySwapMeter.h"
+#include "NetworkIOMeter.h"
+#include "Object.h"
+#include "Panel.h"
+#include "PressureStallMeter.h"
+#include "ProvideCurses.h"
+#include "Settings.h"
+#include "SwapMeter.h"
+#include "SysArchMeter.h"
+#include "TasksMeter.h"
+#include "UptimeMeter.h"
+#include "XUtils.h"
+#include "linux/IOPriority.h"
+#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"
+
+#ifdef HAVE_LIBCAP
+#include <sys/capability.h>
+#endif
+
+#ifdef HAVE_SENSORS_SENSORS_H
+#include "LibSensors.h"
+#endif
+
+#ifndef O_PATH
+#define O_PATH 010000000 // declare for ancient glibc versions
+#endif
+
+
+#ifdef HAVE_LIBCAP
+enum CapMode {
+ CAP_MODE_OFF,
+ CAP_MODE_BASIC,
+ CAP_MODE_STRICT
+};
+#endif
+
+bool Running_containerized = false;
+
+const ScreenDefaults Platform_defaultScreens[] = {
+ {
+ .name = "Main",
+ .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command",
+ .sortKey = "PERCENT_CPU",
+ },
+ {
+ .name = "I/O",
+ .columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command",
+ .sortKey = "IO_RATE",
+ },
+};
+
+const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
+
+const SignalItem Platform_signals[] = {
+ { .name = " 0 Cancel", .number = 0 },
+ { .name = " 1 SIGHUP", .number = 1 },
+ { .name = " 2 SIGINT", .number = 2 },
+ { .name = " 3 SIGQUIT", .number = 3 },
+ { .name = " 4 SIGILL", .number = 4 },
+ { .name = " 5 SIGTRAP", .number = 5 },
+ { .name = " 6 SIGABRT", .number = 6 },
+ { .name = " 6 SIGIOT", .number = 6 },
+ { .name = " 7 SIGBUS", .number = 7 },
+ { .name = " 8 SIGFPE", .number = 8 },
+ { .name = " 9 SIGKILL", .number = 9 },
+ { .name = "10 SIGUSR1", .number = 10 },
+ { .name = "11 SIGSEGV", .number = 11 },
+ { .name = "12 SIGUSR2", .number = 12 },
+ { .name = "13 SIGPIPE", .number = 13 },
+ { .name = "14 SIGALRM", .number = 14 },
+ { .name = "15 SIGTERM", .number = 15 },
+ { .name = "16 SIGSTKFLT", .number = 16 },
+ { .name = "17 SIGCHLD", .number = 17 },
+ { .name = "18 SIGCONT", .number = 18 },
+ { .name = "19 SIGSTOP", .number = 19 },
+ { .name = "20 SIGTSTP", .number = 20 },
+ { .name = "21 SIGTTIN", .number = 21 },
+ { .name = "22 SIGTTOU", .number = 22 },
+ { .name = "23 SIGURG", .number = 23 },
+ { .name = "24 SIGXCPU", .number = 24 },
+ { .name = "25 SIGXFSZ", .number = 25 },
+ { .name = "26 SIGVTALRM", .number = 26 },
+ { .name = "27 SIGPROF", .number = 27 },
+ { .name = "28 SIGWINCH", .number = 28 },
+ { .name = "29 SIGIO", .number = 29 },
+ { .name = "29 SIGPOLL", .number = 29 },
+ { .name = "30 SIGPWR", .number = 30 },
+ { .name = "31 SIGSYS", .number = 31 },
+};
+
+const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
+
+static enum { BAT_PROC, BAT_SYS, BAT_ERR } Platform_Battery_method = BAT_PROC;
+static time_t Platform_Battery_cacheTime;
+static double Platform_Battery_cachePercent = NAN;
+static ACPresence Platform_Battery_cacheIsOnAC;
+
+#ifdef HAVE_LIBCAP
+static enum CapMode Platform_capabilitiesMode = CAP_MODE_BASIC;
+#endif
+
+static Htop_Reaction Platform_actionSetIOPriority(State* st) {
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ const LinuxProcess* p = (const LinuxProcess*) Panel_getSelected((Panel*)st->mainPanel);
+ if (!p)
+ return HTOP_OK;
+
+ IOPriority ioprio1 = p->ioPriority;
+ Panel* ioprioPanel = IOPriorityPanel_new(ioprio1);
+ const void* set = Action_pickFromVector(st, ioprioPanel, 20, true);
+ if (set) {
+ IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel);
+ bool ok = MainPanel_foreachRow(st->mainPanel, LinuxProcess_rowSetIOPriority, (Arg) { .i = ioprio2 }, NULL);
+ if (!ok) {
+ beep();
+ }
+ }
+ Panel_delete((Object*)ioprioPanel);
+ return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
+}
+
+static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) {
+ if (LinuxProcess_isAutogroupEnabled() == false) {
+ beep();
+ return false;
+ }
+ bool anyTagged;
+ bool ok = MainPanel_foreachRow(panel, LinuxProcess_rowChangeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged);
+ if (!ok)
+ beep();
+ return anyTagged;
+}
+
+static Htop_Reaction Platform_actionHigherAutogroupPriority(State* st) {
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ bool changed = Platform_changeAutogroupPriority(st->mainPanel, -1);
+ return changed ? HTOP_REFRESH : HTOP_OK;
+}
+
+static Htop_Reaction Platform_actionLowerAutogroupPriority(State* st) {
+ if (Settings_isReadonly())
+ return HTOP_OK;
+
+ bool changed = Platform_changeAutogroupPriority(st->mainPanel, 1);
+ return changed ? HTOP_REFRESH : HTOP_OK;
+}
+
+void Platform_setBindings(Htop_Action* keys) {
+ keys['i'] = Platform_actionSetIOPriority;
+ keys['{'] = Platform_actionLowerAutogroupPriority;
+ keys['}'] = Platform_actionHigherAutogroupPriority;
+ keys[KEY_F(19)] = Platform_actionLowerAutogroupPriority; // Shift-F7
+ keys[KEY_F(20)] = Platform_actionHigherAutogroupPriority; // Shift-F8
+}
+
+const MeterClass* const Platform_meterTypes[] = {
+ &CPUMeter_class,
+ &ClockMeter_class,
+ &DateMeter_class,
+ &DateTimeMeter_class,
+ &LoadAverageMeter_class,
+ &LoadMeter_class,
+ &MemoryMeter_class,
+ &SwapMeter_class,
+ &MemorySwapMeter_class,
+ &SysArchMeter_class,
+ &HugePageMeter_class,
+ &TasksMeter_class,
+ &UptimeMeter_class,
+ &BatteryMeter_class,
+ &HostnameMeter_class,
+ &AllCPUsMeter_class,
+ &AllCPUs2Meter_class,
+ &AllCPUs4Meter_class,
+ &AllCPUs8Meter_class,
+ &LeftCPUsMeter_class,
+ &RightCPUsMeter_class,
+ &LeftCPUs2Meter_class,
+ &RightCPUs2Meter_class,
+ &LeftCPUs4Meter_class,
+ &RightCPUs4Meter_class,
+ &LeftCPUs8Meter_class,
+ &RightCPUs8Meter_class,
+ &BlankMeter_class,
+ &PressureStallCPUSomeMeter_class,
+ &PressureStallIOSomeMeter_class,
+ &PressureStallIOFullMeter_class,
+ &PressureStallIRQFullMeter_class,
+ &PressureStallMemorySomeMeter_class,
+ &PressureStallMemoryFullMeter_class,
+ &ZfsArcMeter_class,
+ &ZfsCompressedArcMeter_class,
+ &ZramMeter_class,
+ &DiskIOMeter_class,
+ &NetworkIOMeter_class,
+ &SELinuxMeter_class,
+ &SystemdMeter_class,
+ &SystemdUserMeter_class,
+ &FileDescriptorMeter_class,
+ NULL
+};
+
+int Platform_getUptime(void) {
+ double uptime = 0;
+ FILE* fd = fopen(PROCDIR "/uptime", "r");
+ if (fd) {
+ int n = fscanf(fd, "%64lf", &uptime);
+ fclose(fd);
+ if (n <= 0) {
+ return 0;
+ }
+ }
+ return floor(uptime);
+}
+
+void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
+ FILE* fd = fopen(PROCDIR "/loadavg", "r");
+ if (!fd)
+ goto err;
+
+ double scanOne, scanFive, scanFifteen;
+ int r = fscanf(fd, "%lf %lf %lf", &scanOne, &scanFive, &scanFifteen);
+ fclose(fd);
+ if (r != 3)
+ goto err;
+
+ *one = scanOne;
+ *five = scanFive;
+ *fifteen = scanFifteen;
+ return;
+
+err:
+ *one = NAN;
+ *five = NAN;
+ *fifteen = NAN;
+}
+
+pid_t Platform_getMaxPid(void) {
+ pid_t maxPid = 4194303;
+ FILE* file = fopen(PROCDIR "/sys/kernel/pid_max", "r");
+ if (!file)
+ return maxPid;
+
+ int match = fscanf(file, "%32d", &maxPid);
+ (void) match;
+ fclose(file);
+ return maxPid;
+}
+
+double Platform_setCPUValues(Meter* this, unsigned int cpu) {
+ const LinuxMachine* lhost = (const LinuxMachine*) this->host;
+ const Settings* settings = this->host->settings;
+ const CPUData* cpuData = &(lhost->cpuData[cpu]);
+ double total = (double) ( cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod);
+ double percent;
+ double* v = this->values;
+
+ if (!cpuData->online) {
+ this->curItems = 0;
+ return NAN;
+ }
+
+ v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;
+ v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;
+ if (settings->detailedCPUTime) {
+ v[CPU_METER_KERNEL] = cpuData->systemPeriod / total * 100.0;
+ v[CPU_METER_IRQ] = cpuData->irqPeriod / total * 100.0;
+ v[CPU_METER_SOFTIRQ] = cpuData->softIrqPeriod / total * 100.0;
+ this->curItems = 5;
+
+ v[CPU_METER_STEAL] = cpuData->stealPeriod / total * 100.0;
+ v[CPU_METER_GUEST] = cpuData->guestPeriod / total * 100.0;
+ if (settings->accountGuestInCPUMeter) {
+ 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 = sumPositiveValues(v, this->curItems);
+ percent = MINIMUM(percent, 100.0);
+
+ if (settings->detailedCPUTime) {
+ this->curItems = 8;
+ }
+
+ v[CPU_METER_FREQUENCY] = cpuData->frequency;
+
+#ifdef HAVE_SENSORS_SENSORS_H
+ v[CPU_METER_TEMPERATURE] = cpuData->temperature;
+#else
+ v[CPU_METER_TEMPERATURE] = NAN;
+#endif
+
+ return percent;
+}
+
+void Platform_setMemoryValues(Meter* this) {
+ const Machine* host = this->host;
+ const LinuxMachine* lhost = (const LinuxMachine*) host;
+
+ this->total = host->totalMem;
+ this->values[MEMORY_METER_USED] = host->usedMem;
+ this->values[MEMORY_METER_SHARED] = host->sharedMem;
+ this->values[MEMORY_METER_COMPRESSED] = 0; /* compressed */
+ this->values[MEMORY_METER_BUFFERS] = host->buffersMem;
+ this->values[MEMORY_METER_CACHE] = host->cachedMem;
+ this->values[MEMORY_METER_AVAILABLE] = host->availableMem;
+
+ if (lhost->zfs.enabled != 0 && !Running_containerized) {
+ // ZFS does not shrink below the value of zfs_arc_min.
+ unsigned long long int shrinkableSize = 0;
+ if (lhost->zfs.size > lhost->zfs.min)
+ shrinkableSize = lhost->zfs.size - lhost->zfs.min;
+ this->values[MEMORY_METER_USED] -= shrinkableSize;
+ this->values[MEMORY_METER_CACHE] += shrinkableSize;
+ this->values[MEMORY_METER_AVAILABLE] += shrinkableSize;
+ }
+
+ if (lhost->zswap.usedZswapOrig > 0 || lhost->zswap.usedZswapComp > 0) {
+ this->values[MEMORY_METER_USED] -= lhost->zswap.usedZswapComp;
+ this->values[MEMORY_METER_COMPRESSED] += lhost->zswap.usedZswapComp;
+ }
+}
+
+void Platform_setSwapValues(Meter* this) {
+ const Machine* host = this->host;
+ const LinuxMachine* lhost = (const LinuxMachine*) host;
+
+ this->total = host->totalSwap;
+ this->values[SWAP_METER_USED] = host->usedSwap;
+ this->values[SWAP_METER_CACHE] = host->cachedSwap;
+ this->values[SWAP_METER_FRONTSWAP] = 0; /* frontswap -- memory that is accounted to swap but resides elsewhere */
+
+ if (lhost->zswap.usedZswapOrig > 0 || lhost->zswap.usedZswapComp > 0) {
+ /*
+ * FIXME: Zswapped pages can be both SwapUsed and SwapCached, and we do not know which.
+ *
+ * Apparently, it is possible that Zswapped > SwapUsed. This means that some of Zswapped pages
+ * were actually SwapCached, nor SwapUsed. Unfortunately, we cannot tell what exactly portion
+ * of Zswapped pages were SwapCached.
+ *
+ * For now, subtract Zswapped from SwapUsed and only if Zswapped > SwapUsed, subtract the
+ * overflow from SwapCached.
+ */
+ this->values[SWAP_METER_USED] -= lhost->zswap.usedZswapOrig;
+ if (this->values[SWAP_METER_USED] < 0) {
+ /* subtract the overflow from SwapCached */
+ this->values[SWAP_METER_CACHE] += this->values[SWAP_METER_USED];
+ this->values[SWAP_METER_USED] = 0;
+ }
+ this->values[SWAP_METER_FRONTSWAP] += lhost->zswap.usedZswapOrig;
+ }
+}
+
+void Platform_setZramValues(Meter* this) {
+ const LinuxMachine* lhost = (const LinuxMachine*) this->host;
+
+ this->total = lhost->zram.totalZram;
+ this->values[ZRAM_METER_COMPRESSED] = lhost->zram.usedZramComp;
+ this->values[ZRAM_METER_UNCOMPRESSED] = lhost->zram.usedZramOrig - lhost->zram.usedZramComp;
+}
+
+void Platform_setZfsArcValues(Meter* this) {
+ const LinuxMachine* lhost = (const LinuxMachine*) this->host;
+
+ ZfsArcMeter_readStats(this, &(lhost->zfs));
+}
+
+void Platform_setZfsCompressedArcValues(Meter* this) {
+ const LinuxMachine* lhost = (const LinuxMachine*) this->host;
+
+ ZfsCompressedArcMeter_readStats(this, &(lhost->zfs));
+}
+
+char* Platform_getProcessEnv(pid_t pid) {
+ char procname[128];
+ xSnprintf(procname, sizeof(procname), PROCDIR "/%d/environ", pid);
+ FILE* fd = fopen(procname, "r");
+ if (!fd)
+ return NULL;
+
+ char* env = NULL;
+
+ size_t capacity = 0;
+ size_t size = 0;
+ ssize_t bytes = 0;
+
+ do {
+ size += bytes;
+ capacity += 4096;
+ env = xRealloc(env, capacity);
+ } while ((bytes = fread(env + size, 1, capacity - size, fd)) > 0);
+
+ fclose(fd);
+
+ if (bytes < 0) {
+ free(env);
+ return NULL;
+ }
+
+ size += bytes;
+
+ env = xRealloc(env, size + 2);
+
+ env[size] = '\0';
+ env[size + 1] = '\0';
+
+ return env;
+}
+
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
+ FileLocks_ProcessData* pdata = xCalloc(1, sizeof(FileLocks_ProcessData));
+ DIR* dirp;
+ int dfd;
+
+ char path[PATH_MAX];
+ xSnprintf(path, sizeof(path), PROCDIR "/%d/fdinfo/", pid);
+ if (strlen(path) >= (sizeof(path) - 2))
+ goto err;
+
+ if (!(dirp = opendir(path)))
+ goto err;
+
+ if ((dfd = dirfd(dirp)) == -1) {
+ closedir(dirp);
+ goto err;
+ }
+
+ FileLocks_LockData** data_ref = &pdata->locks;
+ for (struct dirent* de; (de = readdir(dirp)); ) {
+ if (String_eq(de->d_name, ".") || String_eq(de->d_name, ".."))
+ continue;
+
+ errno = 0;
+ char* end = de->d_name;
+ int file = strtoull(de->d_name, &end, 10);
+ if (errno || *end)
+ continue;
+
+ int fd = openat(dfd, de->d_name, O_RDONLY | O_CLOEXEC);
+ if (fd == -1)
+ continue;
+ FILE* f = fdopen(fd, "r");
+ if (!f) {
+ close(fd);
+ continue;
+ }
+
+ for (char buffer[1024]; fgets(buffer, sizeof(buffer), f); ) {
+ if (!strchr(buffer, '\n'))
+ continue;
+
+ if (!String_startsWith(buffer, "lock:\t"))
+ continue;
+
+ FileLocks_Data data = {.fd = file};
+ int _;
+ unsigned int maj, min;
+ char lock_end[25], locktype[32], exclusive[32], readwrite[32];
+ if (10 != sscanf(buffer + strlen("lock:\t"), "%d: %31s %31s %31s %d %x:%x:%"PRIu64" %"PRIu64" %24s",
+ &_, locktype, exclusive, readwrite, &_,
+ &maj, &min, &data.inode,
+ &data.start, lock_end))
+ continue;
+
+ data.locktype = xStrdup(locktype);
+ data.exclusive = xStrdup(exclusive);
+ data.readwrite = xStrdup(readwrite);
+ data.dev = makedev(maj, min);
+
+ if (String_eq(lock_end, "EOF"))
+ data.end = ULLONG_MAX;
+ else
+ data.end = strtoull(lock_end, NULL, 10);
+
+ xSnprintf(path, sizeof(path), PROCDIR "/%d/fd/%s", pid, de->d_name);
+ char link[PATH_MAX];
+ ssize_t link_len;
+ if (strlen(path) < (sizeof(path) - 2) && (link_len = readlink(path, link, sizeof(link))) != -1)
+ data.filename = xStrndup(link, link_len);
+
+ *data_ref = xCalloc(1, sizeof(FileLocks_LockData));
+ (*data_ref)->data = data;
+ data_ref = &(*data_ref)->next;
+ }
+
+ fclose(f);
+ }
+
+ closedir(dirp);
+ return pdata;
+
+err:
+ pdata->error = true;
+ return pdata;
+}
+
+void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) {
+ *ten = *sixty = *threehundred = 0;
+ char procname[128];
+ xSnprintf(procname, sizeof(procname), PROCDIR "/pressure/%s", file);
+ FILE* fd = fopen(procname, "r");
+ if (!fd) {
+ *ten = *sixty = *threehundred = NAN;
+ return;
+ }
+ int total = fscanf(fd, "some avg10=%32lf avg60=%32lf avg300=%32lf total=%*f ", ten, sixty, threehundred);
+ if (!some) {
+ total = fscanf(fd, "full avg10=%32lf avg60=%32lf avg300=%32lf total=%*f ", ten, sixty, threehundred);
+ }
+ (void) total;
+ assert(total == 3);
+ fclose(fd);
+}
+
+void Platform_getFileDescriptors(double* used, double* max) {
+ *used = NAN;
+ *max = 65536;
+
+ FILE* fd = fopen(PROCDIR "/sys/fs/file-nr", "r");
+ if (!fd)
+ return;
+
+ unsigned long long v1, v2, v3;
+ int total = fscanf(fd, "%llu %llu %llu", &v1, &v2, &v3);
+ if (total == 3) {
+ *used = v1;
+ *max = v3;
+ }
+
+ fclose(fd);
+}
+
+bool Platform_getDiskIO(DiskIOData* data) {
+ FILE* fd = fopen(PROCDIR "/diskstats", "r");
+ if (!fd)
+ return false;
+
+ char lastTopDisk[32] = { '\0' };
+
+ unsigned long long int read_sum = 0, write_sum = 0, timeSpend_sum = 0;
+ char lineBuffer[256];
+ while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ char diskname[32];
+ unsigned long long int read_tmp, write_tmp, timeSpend_tmp;
+ if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu %*u %*u %llu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) {
+ if (String_startsWith(diskname, "dm-"))
+ continue;
+
+ if (String_startsWith(diskname, "zram"))
+ continue;
+
+ /* only count root disks, e.g. do not count IO from sda and sda1 twice */
+ if (lastTopDisk[0] && String_startsWith(diskname, lastTopDisk))
+ continue;
+
+ /* This assumes disks are listed directly before any of their partitions */
+ String_safeStrncpy(lastTopDisk, diskname, sizeof(lastTopDisk));
+
+ read_sum += read_tmp;
+ write_sum += write_tmp;
+ timeSpend_sum += timeSpend_tmp;
+ }
+ }
+ fclose(fd);
+ /* multiply with sector size */
+ data->totalBytesRead = 512 * read_sum;
+ data->totalBytesWritten = 512 * write_sum;
+ data->totalMsTimeSpend = timeSpend_sum;
+ return true;
+}
+
+bool Platform_getNetworkIO(NetworkIOData* data) {
+ FILE* fd = fopen(PROCDIR "/net/dev", "r");
+ if (!fd)
+ return false;
+
+ memset(data, 0, sizeof(NetworkIOData));
+ char lineBuffer[512];
+ while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ char interfaceName[32];
+ unsigned long long int bytesReceived, packetsReceived, bytesTransmitted, packetsTransmitted;
+ if (sscanf(lineBuffer, "%31s %llu %llu %*u %*u %*u %*u %*u %*u %llu %llu",
+ interfaceName,
+ &bytesReceived,
+ &packetsReceived,
+ &bytesTransmitted,
+ &packetsTransmitted) != 5)
+ continue;
+
+ if (String_eq(interfaceName, "lo:"))
+ continue;
+
+ data->bytesReceived += bytesReceived;
+ data->packetsReceived += packetsReceived;
+ data->bytesTransmitted += bytesTransmitted;
+ data->packetsTransmitted += packetsTransmitted;
+ }
+
+ fclose(fd);
+
+ return true;
+}
+
+// Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
+
+#define PROC_BATTERY_DIR PROCDIR "/acpi/battery"
+#define PROC_POWERSUPPLY_DIR PROCDIR "/acpi/ac_adapter"
+#define PROC_POWERSUPPLY_ACSTATE_FILE PROC_POWERSUPPLY_DIR "/AC/state"
+#define SYS_POWERSUPPLY_DIR "/sys/class/power_supply"
+
+// ----------------------------------------
+// READ FROM /proc
+// ----------------------------------------
+
+static double Platform_Battery_getProcBatInfo(void) {
+ DIR* batteryDir = opendir(PROC_BATTERY_DIR);
+ if (!batteryDir)
+ return NAN;
+
+ uint64_t totalFull = 0;
+ uint64_t totalRemain = 0;
+
+ struct dirent* dirEntry = NULL;
+ while ((dirEntry = readdir(batteryDir))) {
+ const char* entryName = dirEntry->d_name;
+ if (!String_startsWith(entryName, "BAT"))
+ continue;
+
+ char filePath[256];
+ char bufInfo[1024] = {0};
+ xSnprintf(filePath, sizeof(filePath), "%s/%s/info", PROC_BATTERY_DIR, entryName);
+ ssize_t r = xReadfile(filePath, bufInfo, sizeof(bufInfo));
+ if (r < 0)
+ continue;
+
+ char bufState[1024] = {0};
+ xSnprintf(filePath, sizeof(filePath), "%s/%s/state", PROC_BATTERY_DIR, entryName);
+ r = xReadfile(filePath, bufState, sizeof(bufState));
+ if (r < 0)
+ continue;
+
+ const char* line;
+
+ //Getting total charge for all batteries
+ char* buf = bufInfo;
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ char field[100] = {0};
+ int val = 0;
+ if (2 != sscanf(line, "%99[^:]:%d", field, &val))
+ continue;
+
+ if (String_eq(field, "last full capacity")) {
+ totalFull += val;
+ break;
+ }
+ }
+
+ //Getting remaining charge for all batteries
+ buf = bufState;
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ char field[100] = {0};
+ int val = 0;
+ if (2 != sscanf(line, "%99[^:]:%d", field, &val))
+ continue;
+
+ if (String_eq(field, "remaining capacity")) {
+ totalRemain += val;
+ break;
+ }
+ }
+ }
+
+ closedir(batteryDir);
+
+ return totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN;
+}
+
+static ACPresence procAcpiCheck(void) {
+ char buffer[1024] = {0};
+ ssize_t r = xReadfile(PROC_POWERSUPPLY_ACSTATE_FILE, buffer, sizeof(buffer));
+ if (r < 1)
+ return AC_ERROR;
+
+ return String_eq(buffer, "on-line") ? AC_PRESENT : AC_ABSENT;
+}
+
+static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {
+ *isOnAC = procAcpiCheck();
+ *percent = AC_ERROR != *isOnAC ? Platform_Battery_getProcBatInfo() : NAN;
+}
+
+// ----------------------------------------
+// READ FROM /sys
+// ----------------------------------------
+
+static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
+ *percent = NAN;
+ *isOnAC = AC_ERROR;
+
+ DIR* dir = opendir(SYS_POWERSUPPLY_DIR);
+ if (!dir)
+ return;
+
+ uint64_t totalFull = 0;
+ uint64_t totalRemain = 0;
+
+ const struct dirent* dirEntry;
+ while ((dirEntry = readdir(dir))) {
+ const char* entryName = dirEntry->d_name;
+
+#ifdef HAVE_OPENAT
+ int entryFd = openat(dirfd(dir), entryName, O_DIRECTORY | O_PATH);
+ if (entryFd < 0)
+ continue;
+#else
+ char entryFd[4096];
+ xSnprintf(entryFd, sizeof(entryFd), SYS_POWERSUPPLY_DIR "/%s", entryName);
+#endif
+
+ enum { AC, BAT } type;
+ if (String_startsWith(entryName, "BAT")) {
+ type = BAT;
+ } else if (String_startsWith(entryName, "AC")) {
+ type = AC;
+ } else {
+ char buffer[32];
+ ssize_t ret = xReadfileat(entryFd, "type", buffer, sizeof(buffer));
+ if (ret <= 0)
+ goto next;
+
+ /* drop optional trailing newlines */
+ for (char* buf = &buffer[(size_t)ret - 1]; *buf == '\n'; buf--)
+ *buf = '\0';
+
+ if (String_eq(buffer, "Battery"))
+ type = BAT;
+ else if (String_eq(buffer, "Mains"))
+ type = AC;
+ else
+ goto next;
+ }
+
+ if (type == BAT) {
+ char buffer[1024];
+ ssize_t r = xReadfileat(entryFd, "uevent", buffer, sizeof(buffer));
+ if (r < 0)
+ goto next;
+
+ bool full = false;
+ bool now = false;
+
+ double fullCharge = 0;
+ double capacityLevel = NAN;
+ const char* line;
+
+ char* buf = buffer;
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ char field[100] = {0};
+ int val = 0;
+ if (2 != sscanf(line, "POWER_SUPPLY_%99[^=]=%d", field, &val))
+ continue;
+
+ if (String_eq(field, "CAPACITY")) {
+ capacityLevel = val / 100.0;
+ continue;
+ }
+
+ if (String_eq(field, "ENERGY_FULL") || String_eq(field, "CHARGE_FULL")) {
+ fullCharge = val;
+ totalFull += fullCharge;
+ full = true;
+ if (now)
+ break;
+ continue;
+ }
+
+ if (String_eq(field, "ENERGY_NOW") || String_eq(field, "CHARGE_NOW")) {
+ totalRemain += val;
+ now = true;
+ if (full)
+ break;
+ continue;
+ }
+ }
+
+ if (!now && full && isNonnegative(capacityLevel))
+ totalRemain += capacityLevel * fullCharge;
+
+ } else if (type == AC) {
+ if (*isOnAC != AC_ERROR)
+ goto next;
+
+ char buffer[2];
+ ssize_t r = xReadfileat(entryFd, "online", buffer, sizeof(buffer));
+ if (r < 1) {
+ *isOnAC = AC_ERROR;
+ goto next;
+ }
+
+ if (buffer[0] == '0')
+ *isOnAC = AC_ABSENT;
+ else if (buffer[0] == '1')
+ *isOnAC = AC_PRESENT;
+ }
+
+next:
+ Compat_openatArgClose(entryFd);
+ }
+
+ closedir(dir);
+
+ *percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN;
+}
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC) {
+ time_t now = time(NULL);
+ // update battery reading is slow. Update it each 10 seconds only.
+ if (now < Platform_Battery_cacheTime + 10) {
+ *percent = Platform_Battery_cachePercent;
+ *isOnAC = Platform_Battery_cacheIsOnAC;
+ return;
+ }
+
+ if (Platform_Battery_method == BAT_PROC) {
+ Platform_Battery_getProcData(percent, isOnAC);
+ if (!isNonnegative(*percent))
+ Platform_Battery_method = BAT_SYS;
+ }
+ if (Platform_Battery_method == BAT_SYS) {
+ Platform_Battery_getSysData(percent, isOnAC);
+ if (!isNonnegative(*percent))
+ Platform_Battery_method = BAT_ERR;
+ }
+ if (Platform_Battery_method == BAT_ERR) {
+ *percent = NAN;
+ *isOnAC = AC_ERROR;
+ } else {
+ *percent = CLAMP(*percent, 0.0, 100.0);
+ }
+ Platform_Battery_cachePercent = *percent;
+ Platform_Battery_cacheIsOnAC = *isOnAC;
+ Platform_Battery_cacheTime = now;
+}
+
+void Platform_longOptionsUsage(const char* name)
+{
+#ifdef HAVE_LIBCAP
+ printf(
+" --drop-capabilities[=off|basic|strict] Drop Linux capabilities when running as root\n"
+" off - do not drop any capabilities\n"
+" basic (default) - drop all capabilities not needed by %s\n"
+" strict - drop all capabilities except those needed for\n"
+" core functionality\n", name);
+#else
+ (void) name;
+#endif
+}
+
+CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) {
+#ifndef HAVE_LIBCAP
+ (void) argc;
+ (void) argv;
+#endif
+
+ switch (opt) {
+#ifdef HAVE_LIBCAP
+ case 160: {
+ const char* mode = optarg;
+ if (!mode && optind < argc && argv[optind] != NULL &&
+ (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
+ mode = argv[optind++];
+ }
+
+ if (!mode || String_eq(mode, "basic")) {
+ Platform_capabilitiesMode = CAP_MODE_BASIC;
+ } else if (String_eq(mode, "off")) {
+ Platform_capabilitiesMode = CAP_MODE_OFF;
+ } else if (String_eq(mode, "strict")) {
+ Platform_capabilitiesMode = CAP_MODE_STRICT;
+ } else {
+ fprintf(stderr, "Error: invalid capabilities mode \"%s\".\n", mode);
+ return STATUS_ERROR_EXIT;
+ }
+ return STATUS_OK;
+ }
+#endif
+
+ default:
+ break;
+ }
+ return STATUS_ERROR_EXIT;
+}
+
+#ifdef HAVE_LIBCAP
+static int dropCapabilities(enum CapMode mode) {
+
+ if (mode == CAP_MODE_OFF)
+ return 0;
+
+ /* capabilities we keep to operate */
+ const cap_value_t keepcapsStrict[] = {
+ CAP_DAC_READ_SEARCH,
+ CAP_SYS_PTRACE,
+ };
+ const cap_value_t keepcapsBasic[] = {
+ CAP_DAC_READ_SEARCH, /* read non world-readable process files of other users, like /proc/[pid]/io */
+ CAP_KILL, /* send signals to processes of other users */
+ CAP_SYS_NICE, /* lower process nice value / change nice value for arbitrary processes */
+ CAP_SYS_PTRACE, /* read /proc/[pid]/exe */
+#ifdef HAVE_DELAYACCT
+ CAP_NET_ADMIN, /* communicate over netlink socket for delay accounting */
+#endif
+ };
+ const cap_value_t* const keepcaps = (mode == CAP_MODE_BASIC) ? keepcapsBasic : keepcapsStrict;
+ const size_t ncap = (mode == CAP_MODE_BASIC) ? ARRAYSIZE(keepcapsBasic) : ARRAYSIZE(keepcapsStrict);
+
+ cap_t caps = cap_init();
+ if (caps == NULL) {
+ fprintf(stderr, "Error: can not initialize capabilities: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (cap_clear(caps) < 0) {
+ fprintf(stderr, "Error: can not clear capabilities: %s\n", strerror(errno));
+ cap_free(caps);
+ return -1;
+ }
+
+ cap_t currCaps = cap_get_proc();
+ if (currCaps == NULL) {
+ fprintf(stderr, "Error: can not get current process capabilities: %s\n", strerror(errno));
+ cap_free(caps);
+ return -1;
+ }
+
+ for (size_t i = 0; i < ncap; i++) {
+ if (!CAP_IS_SUPPORTED(keepcaps[i]))
+ continue;
+
+ cap_flag_value_t current;
+ if (cap_get_flag(currCaps, keepcaps[i], CAP_PERMITTED, &current) < 0) {
+ fprintf(stderr, "Error: can not get current value of capability %d: %s\n", keepcaps[i], strerror(errno));
+ cap_free(currCaps);
+ cap_free(caps);
+ return -1;
+ }
+
+ if (current != CAP_SET)
+ continue;
+
+ if (cap_set_flag(caps, CAP_PERMITTED, 1, &keepcaps[i], CAP_SET) < 0) {
+ fprintf(stderr, "Error: can not set permitted capability %d: %s\n", keepcaps[i], strerror(errno));
+ cap_free(currCaps);
+ cap_free(caps);
+ return -1;
+ }
+
+ if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &keepcaps[i], CAP_SET) < 0) {
+ fprintf(stderr, "Error: can not set effective capability %d: %s\n", keepcaps[i], strerror(errno));
+ cap_free(currCaps);
+ cap_free(caps);
+ return -1;
+ }
+ }
+
+ if (cap_set_proc(caps) < 0) {
+ fprintf(stderr, "Error: can not set process capabilities: %s\n", strerror(errno));
+ cap_free(currCaps);
+ cap_free(caps);
+ return -1;
+ }
+
+ cap_free(currCaps);
+ cap_free(caps);
+
+ return 0;
+}
+#endif
+
+bool Platform_init(void) {
+#ifdef HAVE_LIBCAP
+ if (dropCapabilities(Platform_capabilitiesMode) < 0)
+ return false;
+#endif
+
+ if (access(PROCDIR, R_OK) != 0) {
+ fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR);
+ return false;
+ }
+
+#ifdef HAVE_SENSORS_SENSORS_H
+ LibSensors_init();
+#endif
+
+ char target[PATH_MAX];
+ ssize_t ret = readlink(PROCDIR "/self/ns/pid", target, sizeof(target) - 1);
+ if (ret > 0) {
+ target[ret] = '\0';
+
+ if (!String_eq("pid:[4026531836]", target)) { // magic constant PROC_PID_INIT_INO from include/linux/proc_ns.h#L46
+ Running_containerized = true;
+ return true; // early return
+ }
+ }
+
+ FILE* fd = fopen(PROCDIR "/1/mounts", "r");
+ if (fd) {
+ char lineBuffer[256];
+ while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ // detect lxc or overlayfs and guess that this means we are running containerized
+ if (String_startsWith(lineBuffer, "lxcfs /proc") || String_startsWith(lineBuffer, "overlay / overlay")) {
+ Running_containerized = true;
+ break;
+ }
+ }
+ fclose(fd);
+ } // if (fd)
+
+ return true;
+}
+
+void Platform_done(void) {
+#ifdef HAVE_SENSORS_SENSORS_H
+ LibSensors_cleanup();
+#endif
+}
diff --git a/linux/Platform.h b/linux/Platform.h
new file mode 100644
index 0000000..e99d1a2
--- /dev/null
+++ b/linux/Platform.h
@@ -0,0 +1,156 @@
+#ifndef HEADER_Platform
+#define HEADER_Platform
+/*
+htop - linux/Platform.h
+(C) 2014 Hisham H. Muhammad
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "Action.h"
+#include "BatteryMeter.h"
+#include "DiskIOMeter.h"
+#include "Hashtable.h"
+#include "Macros.h"
+#include "Meter.h"
+#include "NetworkIOMeter.h"
+#include "Panel.h"
+#include "Process.h"
+#include "ProcessLocksScreen.h"
+#include "RichString.h"
+#include "Settings.h"
+#include "SignalsPanel.h"
+#include "CommandLine.h"
+#include "generic/gettime.h"
+#include "generic/hostname.h"
+#include "generic/uname.h"
+
+
+/* GNU/Hurd does not have PATH_MAX in limits.h */
+#ifndef PATH_MAX
+ #define PATH_MAX 4096
+#endif
+
+
+extern const ScreenDefaults Platform_defaultScreens[];
+
+extern const unsigned int Platform_numberOfDefaultScreens;
+
+extern const SignalItem Platform_signals[];
+
+extern const unsigned int Platform_numberOfSignals;
+
+extern const MeterClass* const Platform_meterTypes[];
+
+bool Platform_init(void);
+void Platform_done(void);
+
+extern bool Running_containerized;
+
+void Platform_setBindings(Htop_Action* keys);
+
+int Platform_getUptime(void);
+
+void Platform_getLoadAverage(double* one, double* five, double* fifteen);
+
+pid_t Platform_getMaxPid(void);
+
+double Platform_setCPUValues(Meter* this, unsigned int cpu);
+
+void Platform_setMemoryValues(Meter* this);
+
+void Platform_setSwapValues(Meter* this);
+
+void Platform_setZramValues(Meter* this);
+
+void Platform_setZfsArcValues(Meter* this);
+
+void Platform_setZfsCompressedArcValues(Meter* this);
+
+char* Platform_getProcessEnv(pid_t pid);
+
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+
+void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred);
+
+void Platform_getFileDescriptors(double* used, double* max);
+
+bool Platform_getDiskIO(DiskIOData* data);
+
+bool Platform_getNetworkIO(NetworkIOData* data);
+
+void Platform_getBattery(double* percent, ACPresence* isOnAC);
+
+static inline void Platform_getHostname(char* buffer, size_t size) {
+ Generic_hostname(buffer, size);
+}
+
+static inline void Platform_getRelease(char** string) {
+ *string = Generic_uname();
+}
+
+#ifdef HAVE_LIBCAP
+ #define PLATFORM_LONG_OPTIONS \
+ {"drop-capabilities", optional_argument, 0, 160},
+#else
+ #define PLATFORM_LONG_OPTIONS
+#endif
+
+void Platform_longOptionsUsage(const char* name);
+
+CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);
+
+static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
+ Generic_gettime_realtime(tv, msec);
+}
+
+static inline void Platform_gettime_monotonic(uint64_t* msec) {
+ Generic_gettime_monotonic(msec);
+}
+
+static inline Hashtable* Platform_dynamicMeters(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }
+
+static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }
+
+static inline Hashtable* Platform_dynamicColumns(void) {
+ return NULL;
+}
+
+static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }
+
+static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {
+ return NULL;
+}
+
+static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {
+ return false;
+}
+
+static inline Hashtable* Platform_dynamicScreens(void) {
+ return NULL;
+}
+
+static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }
+
+static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }
+
+static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }
+
+static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }
+
+#endif
diff --git a/linux/PressureStallMeter.c b/linux/PressureStallMeter.c
new file mode 100644
index 0000000..f796247
--- /dev/null
+++ b/linux/PressureStallMeter.c
@@ -0,0 +1,170 @@
+/*
+htop - PressureStallMeter.c
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2019 Ran Benita
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/PressureStallMeter.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "CRT.h"
+#include "Meter.h"
+#include "Object.h"
+#include "Platform.h"
+#include "RichString.h"
+#include "XUtils.h"
+
+
+static const int PressureStallMeter_attributes[] = {
+ PRESSURE_STALL_TEN,
+ PRESSURE_STALL_SIXTY,
+ PRESSURE_STALL_THREEHUNDRED
+};
+
+static void PressureStallMeter_updateValues(Meter* this) {
+ const char* file;
+ if (strstr(Meter_name(this), "CPU")) {
+ file = "cpu";
+ } else if (strstr(Meter_name(this), "IO")) {
+ file = "io";
+ } else if (strstr(Meter_name(this), "IRQ")) {
+ file = "irq";
+ } else {
+ file = "memory";
+ }
+
+ bool some;
+ if (strstr(Meter_name(this), "Some")) {
+ some = true;
+ } else {
+ some = false;
+ }
+
+ Platform_getPressureStall(file, some, &this->values[0], &this->values[1], &this->values[2]);
+
+ /* only print bar for ten (not sixty and threehundred), cause the sum is meaningless */
+ this->curItems = 1;
+
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s %s %5.2lf%% %5.2lf%% %5.2lf%%", some ? "some" : "full", file, this->values[0], this->values[1], this->values[2]);
+}
+
+static void PressureStallMeter_display(const Object* cast, RichString* out) {
+ const Meter* this = (const Meter*)cast;
+ char buffer[20];
+ int len;
+
+ len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[0]);
+ RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_TEN], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[1]);
+ RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_SIXTY], buffer, len);
+ len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[2]);
+ RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_THREEHUNDRED], buffer, len);
+}
+
+const MeterClass PressureStallCPUSomeMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = PressureStallMeter_display,
+ },
+ .updateValues = PressureStallMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 3,
+ .total = 100.0,
+ .attributes = PressureStallMeter_attributes,
+ .name = "PressureStallCPUSome",
+ .uiName = "PSI some CPU",
+ .caption = "PSI some CPU: ",
+ .description = "Pressure Stall Information, some cpu"
+};
+
+const MeterClass PressureStallIOSomeMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = PressureStallMeter_display,
+ },
+ .updateValues = PressureStallMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 3,
+ .total = 100.0,
+ .attributes = PressureStallMeter_attributes,
+ .name = "PressureStallIOSome",
+ .uiName = "PSI some IO",
+ .caption = "PSI some IO: ",
+ .description = "Pressure Stall Information, some io"
+};
+
+const MeterClass PressureStallIOFullMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = PressureStallMeter_display,
+ },
+ .updateValues = PressureStallMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 3,
+ .total = 100.0,
+ .attributes = PressureStallMeter_attributes,
+ .name = "PressureStallIOFull",
+ .uiName = "PSI full IO",
+ .caption = "PSI full IO: ",
+ .description = "Pressure Stall Information, full io"
+};
+
+const MeterClass PressureStallIRQFullMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = PressureStallMeter_display,
+ },
+ .updateValues = PressureStallMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 3,
+ .total = 100.0,
+ .attributes = PressureStallMeter_attributes,
+ .name = "PressureStallIRQFull",
+ .uiName = "PSI full IRQ",
+ .caption = "PSI full IRQ: ",
+ .description = "Pressure Stall Information, full irq"
+};
+
+const MeterClass PressureStallMemorySomeMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = PressureStallMeter_display,
+ },
+ .updateValues = PressureStallMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 3,
+ .total = 100.0,
+ .attributes = PressureStallMeter_attributes,
+ .name = "PressureStallMemorySome",
+ .uiName = "PSI some memory",
+ .caption = "PSI some memory: ",
+ .description = "Pressure Stall Information, some memory"
+};
+
+const MeterClass PressureStallMemoryFullMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = PressureStallMeter_display,
+ },
+ .updateValues = PressureStallMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 3,
+ .total = 100.0,
+ .attributes = PressureStallMeter_attributes,
+ .name = "PressureStallMemoryFull",
+ .uiName = "PSI full memory",
+ .caption = "PSI full memory: ",
+ .description = "Pressure Stall Information, full memory"
+};
diff --git a/linux/PressureStallMeter.h b/linux/PressureStallMeter.h
new file mode 100644
index 0000000..93ebd27
--- /dev/null
+++ b/linux/PressureStallMeter.h
@@ -0,0 +1,28 @@
+/* Do not edit this file. It was automatically generated. */
+
+#ifndef HEADER_PressureStallMeter
+#define HEADER_PressureStallMeter
+/*
+htop - PressureStallMeter.h
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2019 Ran Benita
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Meter.h"
+
+
+extern const MeterClass PressureStallCPUSomeMeter_class;
+
+extern const MeterClass PressureStallIOSomeMeter_class;
+
+extern const MeterClass PressureStallIOFullMeter_class;
+
+extern const MeterClass PressureStallIRQFullMeter_class;
+
+extern const MeterClass PressureStallMemorySomeMeter_class;
+
+extern const MeterClass PressureStallMemoryFullMeter_class;
+
+#endif
diff --git a/linux/ProcessField.h b/linux/ProcessField.h
new file mode 100644
index 0000000..581a982
--- /dev/null
+++ b/linux/ProcessField.h
@@ -0,0 +1,54 @@
+#ifndef HEADER_LinuxProcessField
+#define HEADER_LinuxProcessField
+/*
+htop - linux/ProcessField.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+
+#define PLATFORM_PROCESS_FIELDS \
+ CMINFLT = 11, \
+ CMAJFLT = 13, \
+ UTIME = 14, \
+ STIME = 15, \
+ CUTIME = 16, \
+ CSTIME = 17, \
+ M_SHARE = 41, \
+ M_TRS = 42, \
+ M_DRS = 43, \
+ M_LRS = 44, \
+ CTID = 100, \
+ VPID = 101, \
+ VXID = 102, \
+ RCHAR = 103, \
+ WCHAR = 104, \
+ SYSCR = 105, \
+ SYSCW = 106, \
+ RBYTES = 107, \
+ WBYTES = 108, \
+ CNCLWB = 109, \
+ IO_READ_RATE = 110, \
+ IO_WRITE_RATE = 111, \
+ IO_RATE = 112, \
+ CGROUP = 113, \
+ OOM = 114, \
+ IO_PRIORITY = 115, \
+ PERCENT_CPU_DELAY = 116, \
+ PERCENT_IO_DELAY = 117, \
+ PERCENT_SWAP_DELAY = 118, \
+ M_PSS = 119, \
+ M_SWAP = 120, \
+ M_PSSWP = 121, \
+ CTXT = 122, \
+ SECATTR = 123, \
+ AUTOGROUP_ID = 127, \
+ AUTOGROUP_NICE = 128, \
+ CCGROUP = 129, \
+ CONTAINER = 130, \
+ M_PRIV = 131, \
+ // End of list
+
+
+#endif /* HEADER_LinuxProcessField */
diff --git a/linux/SELinuxMeter.c b/linux/SELinuxMeter.c
new file mode 100644
index 0000000..323c2a1
--- /dev/null
+++ b/linux/SELinuxMeter.c
@@ -0,0 +1,95 @@
+/*
+htop - SELinuxMeter.c
+(C) 2020 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/SELinuxMeter.h"
+
+#include "CRT.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/statfs.h>
+#include <sys/statvfs.h>
+
+#include "Object.h"
+#include "XUtils.h"
+
+
+static const int SELinuxMeter_attributes[] = {
+ METER_TEXT,
+};
+
+static bool enabled = false;
+static bool enforcing = false;
+
+static bool hasSELinuxMount(void) {
+ struct statfs sfbuf;
+ int r = statfs("/sys/fs/selinux", &sfbuf);
+ if (r != 0) {
+ return false;
+ }
+
+ if ((uint32_t)sfbuf.f_type != /* SELINUX_MAGIC */ 0xf97cff8cU) {
+ return false;
+ }
+
+ struct statvfs vfsbuf;
+ r = statvfs("/sys/fs/selinux", &vfsbuf);
+ if (r != 0 || (vfsbuf.f_flag & ST_RDONLY)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool isSelinuxEnabled(void) {
+ return hasSELinuxMount() && (0 == access("/etc/selinux/config", F_OK));
+}
+
+static bool isSelinuxEnforcing(void) {
+ if (!enabled) {
+ return false;
+ }
+
+ char buf[20];
+ ssize_t r = xReadfile("/sys/fs/selinux/enforce", buf, sizeof(buf));
+ if (r < 0)
+ return false;
+
+ int enforce = 0;
+ if (sscanf(buf, "%d", &enforce) != 1) {
+ return false;
+ }
+
+ return !!enforce;
+}
+
+static void SELinuxMeter_updateValues(Meter* this) {
+ enabled = isSelinuxEnabled();
+ enforcing = isSelinuxEnforcing();
+
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s%s", enabled ? "enabled" : "disabled", enabled ? (enforcing ? "; mode: enforcing" : "; mode: permissive") : "");
+}
+
+const MeterClass SELinuxMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ },
+ .updateValues = SELinuxMeter_updateValues,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 0,
+ .total = 100.0,
+ .attributes = SELinuxMeter_attributes,
+ .name = "SELinux",
+ .uiName = "SELinux",
+ .description = "SELinux state overview",
+ .caption = "SELinux: "
+};
diff --git a/linux/SELinuxMeter.h b/linux/SELinuxMeter.h
new file mode 100644
index 0000000..d8a04db
--- /dev/null
+++ b/linux/SELinuxMeter.h
@@ -0,0 +1,15 @@
+#ifndef HEADER_SELinuxMeter
+#define HEADER_SELinuxMeter
+/*
+htop - SELinuxMeter.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Meter.h"
+
+
+extern const MeterClass SELinuxMeter_class;
+
+#endif /* HEADER_SELinuxMeter */
diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c
new file mode 100644
index 0000000..e13c646
--- /dev/null
+++ b/linux/SystemdMeter.c
@@ -0,0 +1,435 @@
+/*
+htop - SystemdMeter.c
+(C) 2020 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/SystemdMeter.h"
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Object.h"
+#include "RichString.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+#if defined(BUILD_STATIC) && defined(HAVE_LIBSYSTEMD)
+#include <systemd/sd-bus.h>
+#endif
+
+
+#ifdef BUILD_STATIC
+
+#define sym_sd_bus_open_system sd_bus_open_system
+#define sym_sd_bus_get_property_string sd_bus_get_property_string
+#define sym_sd_bus_get_property_trivial sd_bus_get_property_trivial
+#define sym_sd_bus_unref sd_bus_unref
+
+#else
+
+typedef void sd_bus;
+typedef void sd_bus_error;
+static int (*sym_sd_bus_open_system)(sd_bus**);
+static int (*sym_sd_bus_open_user)(sd_bus**);
+static int (*sym_sd_bus_get_property_string)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char**);
+static int (*sym_sd_bus_get_property_trivial)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char, void*);
+static sd_bus* (*sym_sd_bus_unref)(sd_bus*);
+static void* dlopenHandle = NULL;
+
+#endif /* BUILD_STATIC */
+
+
+#define INVALID_VALUE ((unsigned int)-1)
+
+typedef struct SystemdMeterContext {
+#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
+ sd_bus* bus;
+#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
+ char* systemState;
+ unsigned int nFailedUnits;
+ unsigned int nInstalledJobs;
+ unsigned int nNames;
+ unsigned int nJobs;
+} SystemdMeterContext_t;
+
+static SystemdMeterContext_t ctx_system;
+static SystemdMeterContext_t ctx_user;
+
+static void SystemdMeter_done(ATTR_UNUSED Meter* this) {
+ SystemdMeterContext_t* ctx = String_eq(Meter_name(this), "SystemdUser") ? &ctx_user : &ctx_system;
+
+ free(ctx->systemState);
+ ctx->systemState = NULL;
+
+#ifdef BUILD_STATIC
+# ifdef HAVE_LIBSYSTEMD
+ if (ctx->bus) {
+ sym_sd_bus_unref(ctx->bus);
+ }
+ ctx->bus = NULL;
+# endif /* HAVE_LIBSYSTEMD */
+#else /* BUILD_STATIC */
+ if (ctx->bus && dlopenHandle) {
+ sym_sd_bus_unref(ctx->bus);
+ }
+ ctx->bus = NULL;
+
+ if (!ctx_system.systemState && !ctx_user.systemState && dlopenHandle) {
+ dlclose(dlopenHandle);
+ dlopenHandle = NULL;
+ }
+#endif /* BUILD_STATIC */
+}
+
+#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
+static int updateViaLib(bool user) {
+ SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
+#ifndef BUILD_STATIC
+ if (!dlopenHandle) {
+ dlopenHandle = dlopen("libsystemd.so.0", RTLD_LAZY);
+ if (!dlopenHandle)
+ goto dlfailure;
+
+ /* Clear any errors */
+ dlerror();
+
+ #define resolve(symbolname) do { \
+ *(void **)(&sym_##symbolname) = dlsym(dlopenHandle, #symbolname); \
+ if (!sym_##symbolname || dlerror() != NULL) \
+ goto dlfailure; \
+ } while(0)
+
+ resolve(sd_bus_open_system);
+ resolve(sd_bus_open_user);
+ resolve(sd_bus_get_property_string);
+ resolve(sd_bus_get_property_trivial);
+ resolve(sd_bus_unref);
+
+ #undef resolve
+ }
+#endif /* !BUILD_STATIC */
+
+ int r;
+ /* Connect to the system bus */
+ if (!ctx->bus) {
+ if (user) {
+ r = sym_sd_bus_open_user(&ctx->bus);
+ } else {
+ r = sym_sd_bus_open_system(&ctx->bus);
+ }
+ if (r < 0)
+ goto busfailure;
+ }
+
+ static const char* const busServiceName = "org.freedesktop.systemd1";
+ static const char* const busObjectPath = "/org/freedesktop/systemd1";
+ static const char* const busInterfaceName = "org.freedesktop.systemd1.Manager";
+
+ r = sym_sd_bus_get_property_string(ctx->bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "SystemState", /* property name */
+ NULL, /* object to return error in */
+ &ctx->systemState);
+ if (r < 0)
+ goto busfailure;
+
+ r = sym_sd_bus_get_property_trivial(ctx->bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "NFailedUnits", /* property name */
+ NULL, /* object to return error in */
+ 'u', /* property type */
+ &ctx->nFailedUnits);
+ if (r < 0)
+ goto busfailure;
+
+ r = sym_sd_bus_get_property_trivial(ctx->bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "NInstalledJobs", /* property name */
+ NULL, /* object to return error in */
+ 'u', /* property type */
+ &ctx->nInstalledJobs);
+ if (r < 0)
+ goto busfailure;
+
+ r = sym_sd_bus_get_property_trivial(ctx->bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "NNames", /* property name */
+ NULL, /* object to return error in */
+ 'u', /* property type */
+ &ctx->nNames);
+ if (r < 0)
+ goto busfailure;
+
+ r = sym_sd_bus_get_property_trivial(ctx->bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "NJobs", /* property name */
+ NULL, /* object to return error in */
+ 'u', /* property type */
+ &ctx->nJobs);
+ if (r < 0)
+ goto busfailure;
+
+ /* success */
+ return 0;
+
+busfailure:
+ sym_sd_bus_unref(ctx->bus);
+ ctx->bus = NULL;
+ return -2;
+
+#ifndef BUILD_STATIC
+dlfailure:
+ if (dlopenHandle) {
+ dlclose(dlopenHandle);
+ dlopenHandle = NULL;
+ }
+ return -1;
+#endif /* !BUILD_STATIC */
+}
+#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
+
+static void updateViaExec(bool user) {
+ SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
+
+ if (Settings_isReadonly())
+ return;
+
+ int fdpair[2];
+ if (pipe(fdpair) < 0)
+ return;
+
+ pid_t child = fork();
+ if (child < 0) {
+ close(fdpair[1]);
+ close(fdpair[0]);
+ return;
+ }
+
+ if (child == 0) {
+ close(fdpair[0]);
+ dup2(fdpair[1], STDOUT_FILENO);
+ close(fdpair[1]);
+ int fdnull = open("/dev/null", O_WRONLY);
+ if (fdnull < 0)
+ exit(1);
+ dup2(fdnull, STDERR_FILENO);
+ close(fdnull);
+ // Use of NULL in variadic functions must have a pointer cast.
+ // The NULL constant is not required by standard to have a pointer type.
+ execlp(
+ "systemctl",
+ "systemctl",
+ "show",
+ user ? "--user" : "--system",
+ "--property=SystemState",
+ "--property=NFailedUnits",
+ "--property=NNames",
+ "--property=NJobs",
+ "--property=NInstalledJobs",
+ (char*)NULL);
+ exit(127);
+ }
+ close(fdpair[1]);
+
+ int wstatus;
+ if (waitpid(child, &wstatus, 0) < 0 || !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
+ close(fdpair[0]);
+ return;
+ }
+
+ FILE* commandOutput = fdopen(fdpair[0], "r");
+ if (!commandOutput) {
+ close(fdpair[0]);
+ return;
+ }
+
+ char lineBuffer[128];
+ while (fgets(lineBuffer, sizeof(lineBuffer), commandOutput)) {
+ if (String_startsWith(lineBuffer, "SystemState=")) {
+ char* newline = strchr(lineBuffer + strlen("SystemState="), '\n');
+ if (newline) {
+ *newline = '\0';
+ }
+ free_and_xStrdup(&ctx->systemState, lineBuffer + strlen("SystemState="));
+ } else if (String_startsWith(lineBuffer, "NFailedUnits=")) {
+ ctx->nFailedUnits = strtoul(lineBuffer + strlen("NFailedUnits="), NULL, 10);
+ } else if (String_startsWith(lineBuffer, "NNames=")) {
+ ctx->nNames = strtoul(lineBuffer + strlen("NNames="), NULL, 10);
+ } else if (String_startsWith(lineBuffer, "NJobs=")) {
+ ctx->nJobs = strtoul(lineBuffer + strlen("NJobs="), NULL, 10);
+ } else if (String_startsWith(lineBuffer, "NInstalledJobs=")) {
+ ctx->nInstalledJobs = strtoul(lineBuffer + strlen("NInstalledJobs="), NULL, 10);
+ }
+ }
+
+ fclose(commandOutput);
+}
+
+static void SystemdMeter_updateValues(Meter* this) {
+ bool user = String_eq(Meter_name(this), "SystemdUser");
+ SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
+
+ free(ctx->systemState);
+ ctx->systemState = NULL;
+ ctx->nFailedUnits = ctx->nInstalledJobs = ctx->nNames = ctx->nJobs = INVALID_VALUE;
+
+#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
+ if (updateViaLib(user) < 0)
+ updateViaExec(user);
+#else
+ updateViaExec(user);
+#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
+
+ xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s", ctx->systemState ? ctx->systemState : "???");
+}
+
+static int zeroDigitColor(unsigned int value) {
+ switch (value) {
+ case 0:
+ return CRT_colors[METER_VALUE];
+ case INVALID_VALUE:
+ return CRT_colors[METER_VALUE_ERROR];
+ default:
+ return CRT_colors[METER_VALUE_NOTICE];
+ }
+}
+
+static int valueDigitColor(unsigned int value) {
+ switch (value) {
+ case 0:
+ return CRT_colors[METER_VALUE_NOTICE];
+ case INVALID_VALUE:
+ return CRT_colors[METER_VALUE_ERROR];
+ default:
+ return CRT_colors[METER_VALUE];
+ }
+}
+
+
+static void _SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out, SystemdMeterContext_t* ctx) {
+ char buffer[16];
+ int len;
+ int color = METER_VALUE_ERROR;
+
+ if (ctx->systemState) {
+ color = String_eq(ctx->systemState, "running") ? METER_VALUE_OK :
+ String_eq(ctx->systemState, "degraded") ? METER_VALUE_ERROR : METER_VALUE_WARN;
+ }
+ RichString_writeAscii(out, CRT_colors[color], ctx->systemState ? ctx->systemState : "N/A");
+
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " (");
+
+ if (ctx->nFailedUnits == INVALID_VALUE) {
+ buffer[0] = '?';
+ buffer[1] = '\0';
+ len = 1;
+ } else {
+ len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nFailedUnits);
+ }
+ RichString_appendnAscii(out, zeroDigitColor(ctx->nFailedUnits), buffer, len);
+
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "/");
+
+ if (ctx->nNames == INVALID_VALUE) {
+ buffer[0] = '?';
+ buffer[1] = '\0';
+ len = 1;
+ } else {
+ len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nNames);
+ }
+ RichString_appendnAscii(out, valueDigitColor(ctx->nNames), buffer, len);
+
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " failed) (");
+
+ if (ctx->nJobs == INVALID_VALUE) {
+ buffer[0] = '?';
+ buffer[1] = '\0';
+ len = 1;
+ } else {
+ len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nJobs);
+ }
+ RichString_appendnAscii(out, zeroDigitColor(ctx->nJobs), buffer, len);
+
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], "/");
+
+ if (ctx->nInstalledJobs == INVALID_VALUE) {
+ buffer[0] = '?';
+ buffer[1] = '\0';
+ len = 1;
+ } else {
+ len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nInstalledJobs);
+ }
+ RichString_appendnAscii(out, valueDigitColor(ctx->nInstalledJobs), buffer, len);
+
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " jobs)");
+}
+
+static void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
+ _SystemdMeter_display(cast, out, &ctx_system);
+}
+
+static void SystemdUserMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
+ _SystemdMeter_display(cast, out, &ctx_user);
+}
+
+static const int SystemdMeter_attributes[] = {
+ METER_VALUE
+};
+
+const MeterClass SystemdMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = SystemdMeter_display
+ },
+ .updateValues = SystemdMeter_updateValues,
+ .done = SystemdMeter_done,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 0,
+ .total = 100.0,
+ .attributes = SystemdMeter_attributes,
+ .name = "Systemd",
+ .uiName = "Systemd state",
+ .description = "Systemd system state and unit overview",
+ .caption = "Systemd: ",
+};
+
+const MeterClass SystemdUserMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = SystemdUserMeter_display
+ },
+ .updateValues = SystemdMeter_updateValues,
+ .done = SystemdMeter_done,
+ .defaultMode = TEXT_METERMODE,
+ .maxItems = 0,
+ .total = 100.0,
+ .attributes = SystemdMeter_attributes,
+ .name = "SystemdUser",
+ .uiName = "Systemd user state",
+ .description = "Systemd user state and unit overview",
+ .caption = "Systemd User: ",
+};
diff --git a/linux/SystemdMeter.h b/linux/SystemdMeter.h
new file mode 100644
index 0000000..50a793b
--- /dev/null
+++ b/linux/SystemdMeter.h
@@ -0,0 +1,18 @@
+#ifndef HEADER_SystemdMeter
+#define HEADER_SystemdMeter
+
+/*
+htop - SystemdMeter.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Meter.h"
+
+
+extern const MeterClass SystemdMeter_class;
+
+extern const MeterClass SystemdUserMeter_class;
+
+#endif /* HEADER_SystemdMeter */
diff --git a/linux/ZramMeter.c b/linux/ZramMeter.c
new file mode 100644
index 0000000..8329f01
--- /dev/null
+++ b/linux/ZramMeter.c
@@ -0,0 +1,84 @@
+/*
+htop - linux/ZramMeter.c
+(C) 2020 Murloc Knight
+(C) 2020-2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "config.h" // IWYU pragma: keep
+
+#include "linux/ZramMeter.h"
+
+#include <stddef.h>
+
+#include "CRT.h"
+#include "Meter.h"
+#include "Object.h"
+#include "Platform.h"
+#include "RichString.h"
+#include "ZramMeter.h"
+
+
+static const int ZramMeter_attributes[ZRAM_METER_ITEMCOUNT] = {
+ [ZRAM_METER_COMPRESSED] = ZRAM_COMPRESSED,
+ [ZRAM_METER_UNCOMPRESSED] = ZRAM_UNCOMPRESSED,
+};
+
+static void ZramMeter_updateValues(Meter* this) {
+ char* buffer = this->txtBuffer;
+ size_t size = sizeof(this->txtBuffer);
+ int written;
+
+ Platform_setZramValues(this);
+
+ written = Meter_humanUnit(buffer, this->values[ZRAM_METER_COMPRESSED], size);
+ METER_BUFFER_CHECK(buffer, size, written);
+
+ METER_BUFFER_APPEND_CHR(buffer, 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, ')');
+
+ METER_BUFFER_APPEND_CHR(buffer, size, '/');
+
+ Meter_humanUnit(buffer, this->total, size);
+}
+
+static void ZramMeter_display(const Object* cast, RichString* out) {
+ char buffer[50];
+ const Meter* this = (const Meter*)cast;
+
+ RichString_writeAscii(out, CRT_colors[METER_TEXT], ":");
+
+ Meter_humanUnit(buffer, this->total, sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
+
+ Meter_humanUnit(buffer, this->values[ZRAM_METER_COMPRESSED], sizeof(buffer));
+ RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:");
+ RichString_appendAscii(out, CRT_colors[METER_VALUE], 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);
+}
+
+const MeterClass ZramMeter_class = {
+ .super = {
+ .extends = Class(Meter),
+ .delete = Meter_delete,
+ .display = ZramMeter_display,
+ },
+ .updateValues = ZramMeter_updateValues,
+ .defaultMode = BAR_METERMODE,
+ .maxItems = ZRAM_METER_ITEMCOUNT,
+ .total = 100.0,
+ .attributes = ZramMeter_attributes,
+ .name = "Zram",
+ .uiName = "Zram",
+ .caption = "zrm"
+};
diff --git a/linux/ZramMeter.h b/linux/ZramMeter.h
new file mode 100644
index 0000000..14a5215
--- /dev/null
+++ b/linux/ZramMeter.h
@@ -0,0 +1,21 @@
+#ifndef HEADER_ZramMeter
+#define HEADER_ZramMeter
+/*
+htop - linux/ZramMeter.h
+(C) 2020 Murloc Knight
+(C) 2020-2023 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "Meter.h"
+
+typedef enum {
+ ZRAM_METER_COMPRESSED = 0,
+ ZRAM_METER_UNCOMPRESSED = 1,
+ ZRAM_METER_ITEMCOUNT = 2, // number of entries in this enum
+} ZramMeterValues;
+
+extern const MeterClass ZramMeter_class;
+
+#endif
diff --git a/linux/ZramStats.h b/linux/ZramStats.h
new file mode 100644
index 0000000..f71a6c2
--- /dev/null
+++ b/linux/ZramStats.h
@@ -0,0 +1,18 @@
+#ifndef HEADER_ZramStats
+#define HEADER_ZramStats
+/*
+htop - ZramStats.h
+(C) 2020 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "ProcessTable.h"
+
+typedef struct ZramStats_ {
+ memory_t totalZram;
+ memory_t usedZramComp;
+ memory_t usedZramOrig;
+} ZramStats;
+
+#endif
diff --git a/linux/ZswapStats.h b/linux/ZswapStats.h
new file mode 100644
index 0000000..29e516f
--- /dev/null
+++ b/linux/ZswapStats.h
@@ -0,0 +1,19 @@
+#ifndef HEADER_ZswapStats
+#define HEADER_ZswapStats
+/*
+htop - ZswapStats.h
+(C) 2022 htop dev team
+Released under the GNU GPLv2+, see the COPYING file
+in the source distribution for its full text.
+*/
+
+#include "ProcessTable.h"
+
+typedef struct ZswapStats_ {
+ /* amount of RAM used by the zswap pool */
+ memory_t usedZswapComp;
+ /* amount of data stored inside the zswap pool */
+ memory_t usedZswapOrig;
+} ZswapStats;
+
+#endif