aboutsummaryrefslogtreecommitdiffstats
path: root/linux
diff options
context:
space:
mode:
authorDaniel Lange <DLange@git.local>2020-12-07 10:26:01 +0100
committerDaniel Lange <DLange@git.local>2020-12-07 10:26:01 +0100
commit65357c8c46154de4e4eca14075bfe5523bb5fc14 (patch)
tree8f430ee5a0d5de377c4e7c94e47842a27c70d7e8 /linux
parentf80394a20254938142011855f2954b3f63fe5909 (diff)
downloaddebian_htop-65357c8c46154de4e4eca14075bfe5523bb5fc14.tar.gz
debian_htop-65357c8c46154de4e4eca14075bfe5523bb5fc14.tar.bz2
debian_htop-65357c8c46154de4e4eca14075bfe5523bb5fc14.zip
New upstream version 3.0.3upstream/3.0.3
Diffstat (limited to 'linux')
-rw-r--r--linux/Battery.c322
-rw-r--r--linux/Battery.h14
-rw-r--r--linux/IOPriority.h4
-rw-r--r--linux/IOPriorityPanel.c32
-rw-r--r--linux/IOPriorityPanel.h3
-rw-r--r--linux/LibSensors.c104
-rw-r--r--linux/LibSensors.h16
-rw-r--r--linux/LinuxCRT.c36
-rw-r--r--linux/LinuxCRT.h12
-rw-r--r--linux/LinuxProcess.c739
-rw-r--r--linux/LinuxProcess.h119
-rw-r--r--linux/LinuxProcessList.c1564
-rw-r--r--linux/LinuxProcessList.h20
-rw-r--r--linux/Platform.c707
-rw-r--r--linux/Platform.h39
-rw-r--r--linux/PressureStallMeter.c92
-rw-r--r--linux/PressureStallMeter.h12
-rw-r--r--linux/SELinuxMeter.c94
-rw-r--r--linux/SELinuxMeter.h14
-rw-r--r--linux/SystemdMeter.c335
-rw-r--r--linux/SystemdMeter.h15
-rw-r--r--linux/ZramMeter.c67
-rw-r--r--linux/ZramMeter.h8
-rw-r--r--linux/ZramStats.h10
24 files changed, 3281 insertions, 1097 deletions
diff --git a/linux/Battery.c b/linux/Battery.c
deleted file mode 100644
index a8784da..0000000
--- a/linux/Battery.c
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
-htop - linux/Battery.c
-(C) 2004-2014 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
-in the source distribution for its full text.
-
-Linux battery readings written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
-*/
-
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#include <dirent.h>
-#include <errno.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <fcntl.h>
-#include <time.h>
-#include "BatteryMeter.h"
-#include "StringUtils.h"
-
-#define SYS_POWERSUPPLY_DIR "/sys/class/power_supply"
-
-// ----------------------------------------
-// READ FROM /proc
-// ----------------------------------------
-
-// This implementation reading from from /proc/acpi is really inefficient,
-// but I think this is on the way out so I did not rewrite it.
-// The /sys implementation below does things the right way.
-
-static unsigned long int parseBatInfo(const char *fileName, const unsigned short int lineNum, const unsigned short int wordNum) {
- const char batteryPath[] = PROCDIR "/acpi/battery/";
- DIR* batteryDir = opendir(batteryPath);
- if (!batteryDir)
- return 0;
-
- #define MAX_BATTERIES 64
- char* batteries[MAX_BATTERIES];
- unsigned int nBatteries = 0;
- memset(batteries, 0, MAX_BATTERIES * sizeof(char*));
-
- while (nBatteries < MAX_BATTERIES) {
- struct dirent* dirEntry = readdir(batteryDir);
- if (!dirEntry)
- break;
- char* entryName = dirEntry->d_name;
- if (strncmp(entryName, "BAT", 3))
- continue;
- batteries[nBatteries] = xStrdup(entryName);
- nBatteries++;
- }
- closedir(batteryDir);
-
- unsigned long int total = 0;
- for (unsigned int i = 0; i < nBatteries; i++) {
- char infoPath[30];
- xSnprintf(infoPath, sizeof infoPath, "%s%s/%s", batteryPath, batteries[i], fileName);
-
- FILE* file = fopen(infoPath, "r");
- if (!file) {
- break;
- }
-
- char* line = NULL;
- for (unsigned short int j = 0; j < lineNum; j++) {
- free(line);
- line = String_readLine(file);
- if (!line) break;
- }
-
- fclose(file);
-
- if (!line) break;
-
- char *foundNumStr = String_getToken(line, wordNum);
- const unsigned long int foundNum = atoi(foundNumStr);
- free(foundNumStr);
- free(line);
-
- total += foundNum;
- }
-
- for (unsigned int i = 0; i < nBatteries; i++) {
- free(batteries[i]);
- }
-
- return total;
-}
-
-static ACPresence procAcpiCheck() {
- ACPresence isOn = AC_ERROR;
- const char *power_supplyPath = PROCDIR "/acpi/ac_adapter";
- DIR *dir = opendir(power_supplyPath);
- if (!dir) {
- return AC_ERROR;
- }
-
- for (;;) {
- struct dirent* dirEntry = readdir((DIR *) dir);
- if (!dirEntry)
- break;
-
- char* entryName = (char *) dirEntry->d_name;
-
- if (entryName[0] != 'A')
- continue;
-
- char statePath[256];
- xSnprintf((char *) statePath, sizeof statePath, "%s/%s/state", power_supplyPath, entryName);
- FILE* file = fopen(statePath, "r");
- if (!file) {
- isOn = AC_ERROR;
- continue;
- }
- char* line = String_readLine(file);
- fclose(file);
- if (!line) continue;
-
- const char *isOnline = String_getToken(line, 2);
- free(line);
-
- if (strcmp(isOnline, "on-line") == 0) {
- isOn = AC_PRESENT;
- } else {
- isOn = AC_ABSENT;
- }
- free((char *) isOnline);
- if (isOn == AC_PRESENT) {
- break;
- }
- }
-
- if (dir)
- closedir(dir);
- return isOn;
-}
-
-static double Battery_getProcBatData() {
- const unsigned long int totalFull = parseBatInfo("info", 3, 4);
- if (totalFull == 0)
- return 0;
-
- const unsigned long int totalRemain = parseBatInfo("state", 5, 3);
- if (totalRemain == 0)
- return 0;
-
- return totalRemain * 100.0 / (double) totalFull;
-}
-
-static void Battery_getProcData(double* level, ACPresence* isOnAC) {
- *level = Battery_getProcBatData();
- *isOnAC = procAcpiCheck();
-}
-
-// ----------------------------------------
-// READ FROM /sys
-// ----------------------------------------
-
-static inline ssize_t xread(int fd, void *buf, size_t count) {
- // Read some bytes. Retry on EINTR and when we don't get as many bytes as we requested.
- size_t alreadyRead = 0;
- for(;;) {
- ssize_t res = read(fd, buf, count);
- if (res == -1 && errno == EINTR) continue;
- if (res > 0) {
- buf = ((char*)buf)+res;
- count -= res;
- alreadyRead += res;
- }
- if (res == -1) return -1;
- if (count == 0 || res == 0) return alreadyRead;
- }
-}
-
-static void Battery_getSysData(double* level, ACPresence* isOnAC) {
-
- *level = 0;
- *isOnAC = AC_ERROR;
-
- DIR *dir = opendir(SYS_POWERSUPPLY_DIR);
- if (!dir)
- return;
-
- unsigned long int totalFull = 0;
- unsigned long int totalRemain = 0;
-
- for (;;) {
- struct dirent* dirEntry = readdir((DIR *) dir);
- if (!dirEntry)
- break;
- char* entryName = (char *) dirEntry->d_name;
- const char filePath[256];
-
- xSnprintf((char *) filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/type", entryName);
- int fd1 = open(filePath, O_RDONLY);
- if (fd1 == -1)
- continue;
-
- char type[8];
- ssize_t typelen = xread(fd1, type, 7);
- close(fd1);
- if (typelen < 1)
- continue;
-
- if (type[0] == 'B' && type[1] == 'a' && type[2] == 't') {
- xSnprintf((char *) filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/uevent", entryName);
- int fd2 = open(filePath, O_RDONLY);
- if (fd2 == -1) {
- closedir(dir);
- return;
- }
- char buffer[1024];
- ssize_t buflen = xread(fd2, buffer, 1023);
- close(fd2);
- if (buflen < 1) {
- closedir(dir);
- return;
- }
- buffer[buflen] = '\0';
- char *buf = buffer;
- char *line = NULL;
- bool full = false;
- bool now = false;
- while ((line = strsep(&buf, "\n")) != NULL) {
- #define match(str,prefix) \
- (String_startsWith(str,prefix) ? (str) + strlen(prefix) : NULL)
- const char* ps = match(line, "POWER_SUPPLY_");
- if (!ps) {
- continue;
- }
- const char* energy = match(ps, "ENERGY_");
- if (!energy) {
- energy = match(ps, "CHARGE_");
- }
- if (!energy) {
- continue;
- }
- const char* value = (!full) ? match(energy, "FULL=") : NULL;
- if (value) {
- totalFull += atoi(value);
- full = true;
- if (now) break;
- continue;
- }
- value = (!now) ? match(energy, "NOW=") : NULL;
- if (value) {
- totalRemain += atoi(value);
- now = true;
- if (full) break;
- continue;
- }
- }
- #undef match
- } else if (entryName[0] == 'A') {
- if (*isOnAC != AC_ERROR) {
- continue;
- }
-
- xSnprintf((char *) filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/online", entryName);
- int fd3 = open(filePath, O_RDONLY);
- if (fd3 == -1) {
- closedir(dir);
- return;
- }
- char buffer[2] = "";
- for(;;) {
- ssize_t res = read(fd3, buffer, 1);
- if (res == -1 && errno == EINTR) continue;
- break;
- }
- close(fd3);
- if (buffer[0] == '0') {
- *isOnAC = AC_ABSENT;
- } else if (buffer[0] == '1') {
- *isOnAC = AC_PRESENT;
- }
- }
- }
- closedir(dir);
- *level = totalFull > 0 ? ((double) totalRemain * 100) / (double) totalFull : 0;
-}
-
-static enum { BAT_PROC, BAT_SYS, BAT_ERR } Battery_method = BAT_PROC;
-
-static time_t Battery_cacheTime = 0;
-static double Battery_cacheLevel = 0;
-static ACPresence Battery_cacheIsOnAC = 0;
-
-void Battery_getData(double* level, ACPresence* isOnAC) {
- time_t now = time(NULL);
- // update battery reading is slow. Update it each 10 seconds only.
- if (now < Battery_cacheTime + 10) {
- *level = Battery_cacheLevel;
- *isOnAC = Battery_cacheIsOnAC;
- return;
- }
-
- if (Battery_method == BAT_PROC) {
- Battery_getProcData(level, isOnAC);
- if (*level == 0) {
- Battery_method = BAT_SYS;
- }
- }
- if (Battery_method == BAT_SYS) {
- Battery_getSysData(level, isOnAC);
- if (*level == 0) {
- Battery_method = BAT_ERR;
- }
- }
- if (Battery_method == BAT_ERR) {
- *level = -1;
- *isOnAC = AC_ERROR;
- }
- if (*level > 100.0) {
- *level = 100.0;
- }
- Battery_cacheLevel = *level;
- Battery_cacheIsOnAC = *isOnAC;
- Battery_cacheTime = now;
-}
diff --git a/linux/Battery.h b/linux/Battery.h
deleted file mode 100644
index 8ca0d7f..0000000
--- a/linux/Battery.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#ifndef HEADER_Battery
-#define HEADER_Battery
-/*
-htop - linux/Battery.h
-(C) 2004-2014 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
-in the source distribution for its full text.
-
-Linux battery readings written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
-*/
-
-void Battery_getData(double* level, ACPresence* isOnAC);
-
-#endif
diff --git a/linux/IOPriority.h b/linux/IOPriority.h
index 7c4f3b6..551a7d5 100644
--- a/linux/IOPriority.h
+++ b/linux/IOPriority.h
@@ -3,7 +3,7 @@
/*
htop - IOPriority.h
(C) 2004-2012 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
Based on ionice,
@@ -28,7 +28,7 @@ enum {
typedef int IOPriority;
-#define IOPriority_tuple(class_, data_) (((class_) << IOPRIO_CLASS_SHIFT) | data_)
+#define IOPriority_tuple(class_, data_) (((class_) << IOPRIO_CLASS_SHIFT) | (data_))
#define IOPriority_error 0xffffffff
diff --git a/linux/IOPriorityPanel.c b/linux/IOPriorityPanel.c
index ebb77bf..c5a4a4c 100644
--- a/linux/IOPriorityPanel.c
+++ b/linux/IOPriorityPanel.c
@@ -1,20 +1,33 @@
/*
htop - IOPriorityPanel.c
(C) 2004-2012 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
#include "IOPriorityPanel.h"
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "FunctionBar.h"
+#include "ListItem.h"
+#include "Object.h"
+#include "XUtils.h"
+
Panel* IOPriorityPanel_new(IOPriority currPrio) {
Panel* this = Panel_new(1, 1, 1, 1, true, Class(ListItem), 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[] = {
+ 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 }
@@ -22,17 +35,22 @@ Panel* IOPriorityPanel_new(IOPriority currPrio) {
for (int c = 0; classes[c].name; c++) {
for (int i = 0; i < 8; i++) {
char name[50];
- xSnprintf(name, sizeof(name)-1, "%s %d %s", classes[c].name, i, i == 0 ? "(High)" : (i == 7 ? "(Low)" : ""));
+ 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);
+ 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);
+ if (currPrio == IOPriority_Idle) {
+ Panel_setSelected(this, Panel_size(this) - 1);
+ }
return this;
}
IOPriority IOPriorityPanel_getIOPriority(Panel* this) {
- return (IOPriority) ( ((ListItem*) Panel_getSelected(this))->key );
+ const ListItem* selected = (ListItem*) Panel_getSelected(this);
+ return selected ? selected->key : IOPriority_None;
}
diff --git a/linux/IOPriorityPanel.h b/linux/IOPriorityPanel.h
index 04c1d43..2ac4b31 100644
--- a/linux/IOPriorityPanel.h
+++ b/linux/IOPriorityPanel.h
@@ -3,13 +3,12 @@
/*
htop - IOPriorityPanel.h
(C) 2004-2012 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
#include "Panel.h"
#include "IOPriority.h"
-#include "ListItem.h"
Panel* IOPriorityPanel_new(IOPriority currPrio);
diff --git a/linux/LibSensors.c b/linux/LibSensors.c
new file mode 100644
index 0000000..a30e21b
--- /dev/null
+++ b/linux/LibSensors.c
@@ -0,0 +1,104 @@
+#include "LibSensors.h"
+
+#ifdef HAVE_SENSORS_SENSORS_H
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <sensors/sensors.h>
+
+#include "XUtils.h"
+
+
+static int (*sym_sensors_init)(FILE*);
+static void (*sym_sensors_cleanup)(void);
+static const sensors_chip_name* (*sym_sensors_get_detected_chips)(const sensors_chip_name*, int*);
+static int (*sym_sensors_snprintf_chip_name)(char*, size_t, const sensors_chip_name*);
+static const sensors_feature* (*sym_sensors_get_features)(const sensors_chip_name*, int*);
+static const sensors_subfeature* (*sym_sensors_get_subfeature)(const sensors_chip_name*, const sensors_feature*, sensors_subfeature_type);
+static int (*sym_sensors_get_value)(const sensors_chip_name*, int, double*);
+
+static void* dlopenHandle = NULL;
+
+int LibSensors_init(FILE* input) {
+ if (!dlopenHandle) {
+ dlopenHandle = dlopen("libsensors.so", 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_snprintf_chip_name);
+ resolve(sensors_get_features);
+ resolve(sensors_get_subfeature);
+ resolve(sensors_get_value);
+
+ #undef resolve
+ }
+
+ return sym_sensors_init(input);
+
+dlfailure:
+ if (dlopenHandle) {
+ dlclose(dlopenHandle);
+ dlopenHandle = NULL;
+ }
+ return -1;
+}
+
+void LibSensors_cleanup(void) {
+ if (dlopenHandle) {
+ sym_sensors_cleanup();
+
+ dlclose(dlopenHandle);
+ dlopenHandle = NULL;
+ }
+}
+
+int LibSensors_getCPUTemperatures(CPUData* cpus, int cpuCount) {
+ if (!dlopenHandle)
+ return -ENOTSUP;
+
+ int tempCount = 0;
+
+ int n = 0;
+ for (const sensors_chip_name *chip = sym_sensors_get_detected_chips(NULL, &n); chip; chip = sym_sensors_get_detected_chips(NULL, &n)) {
+ char buffer[32];
+ sym_sensors_snprintf_chip_name(buffer, sizeof(buffer), chip);
+ if (!String_startsWith(buffer, "coretemp") && !String_startsWith(buffer, "cpu_thermal"))
+ continue;
+
+ 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->number > cpuCount)
+ continue;
+
+ const sensors_subfeature *sub_feature = sym_sensors_get_subfeature(chip, feature, SENSORS_SUBFEATURE_TEMP_INPUT);
+ if (sub_feature) {
+ double temp;
+ int r = sym_sensors_get_value(chip, sub_feature->number, &temp);
+ if (r != 0)
+ continue;
+
+ cpus[feature->number].temperature = temp;
+ tempCount++;
+ }
+ }
+ }
+
+ return tempCount;
+}
+
+#endif /* HAVE_SENSORS_SENSORS_H */
diff --git a/linux/LibSensors.h b/linux/LibSensors.h
new file mode 100644
index 0000000..ed9be7b
--- /dev/null
+++ b/linux/LibSensors.h
@@ -0,0 +1,16 @@
+#ifndef HEADER_LibSensors
+#define HEADER_LibSensors
+
+#include "config.h" // IWYU pragma: keep
+
+#include <stdio.h>
+
+#include "LinuxProcessList.h"
+
+
+int LibSensors_init(FILE* input);
+void LibSensors_cleanup(void);
+
+int LibSensors_getCPUTemperatures(CPUData* cpus, int cpuCount);
+
+#endif /* HEADER_LibSensors */
diff --git a/linux/LinuxCRT.c b/linux/LinuxCRT.c
deleted file mode 100644
index c65b782..0000000
--- a/linux/LinuxCRT.c
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-htop - LinuxCRT.c
-(C) 2014 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
-in the source distribution for its full text.
-*/
-
-#include "config.h"
-#include "CRT.h"
-#include <stdio.h>
-#include <stdlib.h>
-#ifdef HAVE_EXECINFO_H
-#include <execinfo.h>
-#endif
-
-void CRT_handleSIGSEGV(int sgn) {
- (void) sgn;
- CRT_done();
- #ifdef __linux
- fprintf(stderr, "\n\nhtop " VERSION " aborting. Please report bug at https://htop.dev\n");
- #ifdef HAVE_EXECINFO_H
- size_t size = backtrace(backtraceArray, sizeof(backtraceArray) / sizeof(void *));
- fprintf(stderr, "\n Please include in your report the following backtrace: \n");
- backtrace_symbols_fd(backtraceArray, size, 2);
- fprintf(stderr, "\nAdditionally, in order to make the above backtrace useful,");
- fprintf(stderr, "\nplease also run the following command to generate a disassembly of your binary:");
- fprintf(stderr, "\n\n objdump -d `which htop` > ~/htop.objdump");
- fprintf(stderr, "\n\nand then attach the file ~/htop.objdump to your bug report.");
- fprintf(stderr, "\n\nThank you for helping to improve htop!\n\n");
- #endif
- #else
- fprintf(stderr, "\nUnfortunately, you seem to be using an unsupported platform!");
- fprintf(stderr, "\nPlease contact your platform package maintainer!\n\n");
- #endif
- abort();
-}
diff --git a/linux/LinuxCRT.h b/linux/LinuxCRT.h
deleted file mode 100644
index c379a8f..0000000
--- a/linux/LinuxCRT.h
+++ /dev/null
@@ -1,12 +0,0 @@
-#ifndef HEADER_LinuxCRT
-#define HEADER_LinuxCRT
-/*
-htop - LinuxCRT.h
-(C) 2014 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
-in the source distribution for its full text.
-*/
-
-void CRT_handleSIGSEGV(int sgn);
-
-#endif
diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c
index 377aa5b..8298000 100644
--- a/linux/LinuxProcess.c
+++ b/linux/LinuxProcess.c
@@ -2,25 +2,33 @@
htop - LinuxProcess.c
(C) 2014 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#include "Process.h"
-#include "ProcessList.h"
#include "LinuxProcess.h"
-#include "Platform.h"
-#include "CRT.h"
+#include <math.h>
+#include <stdio.h>
#include <stdlib.h>
-#include <unistd.h>
#include <string.h>
-#include <sys/syscall.h>
-#include <time.h>
+#include <syscall.h>
+#include <unistd.h>
+
+#include "CRT.h"
+#include "Macros.h"
+#include "Process.h"
+#include "ProvideCurses.h"
+#include "RichString.h"
+#include "XUtils.h"
+
/* semi-global */
long long btime;
+/* Used to identify kernel threads in Comm and Exe columns */
+static const char *const kthreadID = "KTHREAD";
+
ProcessFieldData Process_fields[] = {
[0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, },
[PID] = { .name = "PID", .title = " PID ", .description = "Process/thread ID", .flags = 0, },
@@ -61,28 +69,28 @@ ProcessFieldData Process_fields[] = {
[CNSWAP] = { .name = "CNSWAP", .title = NULL, .description = NULL, .flags = 0, },
[EXIT_SIGNAL] = { .name = "EXIT_SIGNAL", .title = NULL, .description = NULL, .flags = 0, },
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
- [M_SIZE] = { .name = "M_SIZE", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
+ [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, },
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, },
[M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, },
[M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, },
[M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, },
- [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process", .flags = 0, },
- [M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process", .flags = 0, },
+ [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, },
+ [M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process (unused since Linux 2.6; always 0)", .flags = 0, },
[ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, },
+ [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, },
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, },
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, },
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
[TGID] = { .name = "TGID", .title = " TGID ", .description = "Thread group ID (i.e. process ID)", .flags = 0, },
#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, },
+ [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, },
#endif
#ifdef HAVE_VSERVER
[VXID] = { .name = "VXID", .title = " VXID ", .description = "VServer process ID", .flags = PROCESS_FLAG_LINUX_VSERVER, },
#endif
-#ifdef HAVE_TASKSTATS
[RCHAR] = { .name = "RCHAR", .title = " RD_CHAR ", .description = "Number of bytes the process has read", .flags = PROCESS_FLAG_IO, },
[WCHAR] = { .name = "WCHAR", .title = " WR_CHAR ", .description = "Number of bytes the process has written", .flags = PROCESS_FLAG_IO, },
[SYSCR] = { .name = "SYSCR", .title = " RD_SYSC ", .description = "Number of read(2) syscalls for the process", .flags = PROCESS_FLAG_IO, },
@@ -93,10 +101,7 @@ ProcessFieldData Process_fields[] = {
[IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, },
[IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, },
[IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, },
-#endif
-#ifdef HAVE_CGROUP
[CGROUP] = { .name = "CGROUP", .title = " CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, },
-#endif
[OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, },
[IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, },
#ifdef HAVE_DELAYACCT
@@ -107,6 +112,11 @@ ProcessFieldData Process_fields[] = {
[M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it.", .flags = PROCESS_FLAG_LINUX_SMAPS, },
[M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, },
[M_PSSWP] = { .name = "M_PSSWP", .title = " PSSWP ", .description = "shows proportional swap share of this mapping, Unlike \"Swap\", this does not take into account swapped out page of underlying shmem objects.", .flags = PROCESS_FLAG_LINUX_SMAPS, },
+ [CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, },
+ [SECATTR] = { .name = "SECATTR", .title = " Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, },
+ [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process from /proc/[pid]/comm", .flags = 0, },
+ [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, },
+ [CWD] = { .name ="CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_LINUX_CWD, },
[LAST_PROCESSFIELD] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, },
};
@@ -123,30 +133,37 @@ ProcessPidColumn Process_pidColumns[] = {
{ .id = 0, .label = NULL },
};
-ProcessClass LinuxProcess_class = {
- .super = {
- .extends = Class(Process),
- .display = Process_display,
- .delete = Process_delete,
- .compare = LinuxProcess_compare
- },
- .writeField = (Process_WriteField) LinuxProcess_writeField,
-};
+/* This function returns the string displayed in Command column, so that sorting
+ * happens on what is displayed - whether comm, full path, basename, etc.. So
+ * this follows LinuxProcess_writeField(COMM) and LinuxProcess_writeCommand */
+static const char* LinuxProcess_getCommandStr(const Process *this) {
+ const LinuxProcess *lp = (const LinuxProcess *)this;
+ if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !lp->mergedCommand.str) {
+ return this->comm;
+ }
+ return lp->mergedCommand.str;
+}
-LinuxProcess* LinuxProcess_new(Settings* settings) {
+Process* LinuxProcess_new(const Settings* settings) {
LinuxProcess* this = xCalloc(1, sizeof(LinuxProcess));
Object_setClass(this, Class(LinuxProcess));
Process_init(&this->super, settings);
- return this;
+ return &this->super;
}
void Process_delete(Object* cast) {
LinuxProcess* this = (LinuxProcess*) cast;
Process_done((Process*)cast);
-#ifdef HAVE_CGROUP
free(this->cgroup);
+#ifdef HAVE_OPENVZ
+ free(this->ctid);
#endif
+ free(this->cwd);
+ free(this->secattr);
free(this->ttyDevice);
+ free(this->procExe);
+ free(this->procComm);
+ free(this->mergedCommand.str);
free(this);
}
@@ -158,7 +175,13 @@ 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
*/
-#define LinuxProcess_effectiveIOPriority(p_) (IOPriority_class(p_->ioPriority) == IOPRIO_CLASS_NONE ? IOPriority_tuple(IOPRIO_CLASS_BE, (p_->super.nice + 20) / 5) : p_->ioPriority)
+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;
+}
IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this) {
IOPriority ioprio = 0;
@@ -170,30 +193,432 @@ IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this) {
return ioprio;
}
-bool LinuxProcess_setIOPriority(LinuxProcess* this, Arg ioprio) {
+bool LinuxProcess_setIOPriority(Process* this, Arg ioprio) {
// Other OSes masquerading as Linux (NetBSD?) don't have this syscall
#ifdef SYS_ioprio_set
- syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, this->super.pid, ioprio.i);
+ syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, this->pid, ioprio.i);
#endif
- return (LinuxProcess_updateIOPriority(this) == ioprio.i);
+ return (LinuxProcess_updateIOPriority((LinuxProcess*)this) == ioprio.i);
}
#ifdef HAVE_DELAYACCT
-void LinuxProcess_printDelay(float delay_percent, char* buffer, int n) {
- if (delay_percent == -1LL) {
- xSnprintf(buffer, n, " N/A ");
- } else {
- xSnprintf(buffer, n, "%4.1f ", delay_percent);
- }
+static void LinuxProcess_printDelay(float delay_percent, char* buffer, int n) {
+ if (isnan(delay_percent)) {
+ xSnprintf(buffer, n, " N/A ");
+ } else {
+ xSnprintf(buffer, n, "%4.1f ", delay_percent);
+ }
}
#endif
-void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field) {
- LinuxProcess* lp = (LinuxProcess*) this;
+/*
+TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is
+not available in an userspace header - so define it. Note: when colorizing a
+basename with the comm prefix, the entire basename (not just the comm prefix) is
+colorized for better readability, and it is implicit that only upto
+(TASK_COMM_LEN - 1) could be comm
+*/
+#define TASK_COMM_LEN 16
+
+static bool findCommInCmdline(const char *comm, const char *cmdline, int cmdlineBasenameOffset, int *pCommStart, int *pCommEnd) {
+ /* Try to find procComm in tokenized cmdline - this might in rare cases
+ * mis-identify a string or fail, if comm or cmdline had been unsuitably
+ * modified by the process */
+ const char *token;
+ const char *tokenBase;
+ size_t tokenLen;
+ const size_t commLen = strlen(comm);
+
+ for (token = cmdline + cmdlineBasenameOffset; *token; ) {
+ for (tokenBase = token; *token && *token != '\n'; ++token) {
+ if (*token == '/') {
+ tokenBase = token + 1;
+ }
+ }
+ tokenLen = token - tokenBase;
+
+ if ((tokenLen == commLen || (tokenLen > commLen && commLen == (TASK_COMM_LEN - 1))) &&
+ strncmp(tokenBase, comm, commLen) == 0) {
+ *pCommStart = tokenBase - cmdline;
+ *pCommEnd = token - cmdline;
+ return true;
+ }
+ if (*token) {
+ do {
+ ++token;
+ } while ('\n' == *token);
+ }
+ }
+ return false;
+}
+
+static int matchCmdlinePrefixWithExeSuffix(const char *cmdline, int cmdlineBaseOffset, const char *exe, int exeBaseOffset, int exeBaseLen) {
+ int matchLen; /* matching length to be returned */
+ char delim; /* delimiter following basename */
+
+ /* cmdline prefix is an absolute path: it must match whole exe. */
+ if (cmdline[0] == '/') {
+ matchLen = exeBaseLen + exeBaseOffset;
+ if (strncmp(cmdline, exe, matchLen) == 0) {
+ delim = cmdline[matchLen];
+ if (delim == 0 || delim == '\n' || delim == ' ') {
+ return matchLen;
+ }
+ }
+ return 0;
+ }
+
+ /* cmdline prefix is a relative path: We need to first match the basename at
+ * cmdlineBaseOffset and then reverse match the cmdline prefix with the exe
+ * suffix. But there is a catch: Some processes modify their cmdline in ways
+ * that make htop's identification of the basename in cmdline unreliable.
+ * For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to
+ * "gdm-session-worker [pam/gdm-autologin]" and htop ends up with
+ * procCmdlineBasenameOffset at "gdm-autologin]". This issue could arise with
+ * chrome as well as it stores in cmdline its concatenated argument vector,
+ * without NUL delimiter between the arguments (which may contain a '/')
+ *
+ * So if needed, we adjust cmdlineBaseOffset to the previous (if any)
+ * component of the cmdline relative path, and retry the procedure. */
+ bool delimFound; /* if valid basename delimiter found */
+ do {
+ /* match basename */
+ matchLen = exeBaseLen + cmdlineBaseOffset;
+ if (cmdlineBaseOffset < exeBaseOffset &&
+ strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) {
+ delim = cmdline[matchLen];
+ if (delim == 0 || delim == '\n' || delim == ' ') {
+ int i, j;
+ /* reverse match the cmdline prefix and exe suffix */
+ for (i = cmdlineBaseOffset - 1, j = exeBaseOffset - 1;
+ i >= 0 && cmdline[i] == exe[j]; --i, --j)
+ ;
+ /* full match, with exe suffix being a valid relative path */
+ if (i < 0 && exe[j] == '/') {
+ return matchLen;
+ }
+ }
+ }
+ /* Try to find the previous potential cmdlineBaseOffset - it would be
+ * preceded by '/' or nothing, and delimited by ' ' or '\n' */
+ for (delimFound = false, cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) {
+ if (delimFound) {
+ if (cmdline[cmdlineBaseOffset - 1] == '/') {
+ break;
+ }
+ } else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\n') {
+ delimFound = true;
+ }
+ }
+ } while (delimFound);
+
+ return 0;
+}
+
+/* stpcpy, but also converts newlines to spaces */
+static inline char *stpcpyWithNewlineConversion(char *dstStr, const char *srcStr) {
+ for (; *srcStr; ++srcStr) {
+ *dstStr++ = (*srcStr == '\n') ? ' ' : *srcStr;
+ }
+ *dstStr = 0;
+ return dstStr;
+}
+
+/*
+This function makes the merged Command string. It also stores the offsets of the
+basename, comm w.r.t the merged Command string - these offsets will be used by
+LinuxProcess_writeCommand() for coloring. The merged Command string is also
+returned by LinuxProcess_getCommandStr() for searching, sorting and filtering.
+*/
+void LinuxProcess_makeCommandStr(Process* this) {
+ LinuxProcess *lp = (LinuxProcess *)this;
+ LinuxProcessMergedCommand *mc = &lp->mergedCommand;
+
+ bool showMergedCommand = this->settings->showMergedCommand;
+ bool showProgramPath = this->settings->showProgramPath;
+ bool searchCommInCmdline = this->settings->findCommInCmdline;
+ bool stripExeFromCmdline = this->settings->stripExeFromCmdline;
+
+ /* lp->mergedCommand.str needs updating only if its state or contents changed.
+ * Its content is based on the fields cmdline, comm, and exe. */
+ if (
+ mc->prevMergeSet == showMergedCommand &&
+ mc->prevPathSet == showProgramPath &&
+ mc->prevCommSet == searchCommInCmdline &&
+ mc->prevCmdlineSet == stripExeFromCmdline &&
+ !mc->cmdlineChanged &&
+ !mc->commChanged &&
+ !mc->exeChanged
+ ) {
+ return;
+ }
+
+ /* The field separtor "│" has been chosen such that it will not match any
+ * valid string used for searching or filtering */
+ const char *SEPARATOR = CRT_treeStr[TREE_STR_VERT];
+ const int SEPARATOR_LEN = strlen(SEPARATOR);
+
+ /* Check for any changed fields since we last built this string */
+ if (mc->cmdlineChanged || mc->commChanged || mc->exeChanged) {
+ free(mc->str);
+ /* Accommodate the column text, two field separators and terminating NUL */
+ mc->str = xCalloc(1, mc->maxLen + 2*SEPARATOR_LEN + 1);
+ }
+
+ /* Preserve the settings used in this run */
+ mc->prevMergeSet = showMergedCommand;
+ mc->prevPathSet = showProgramPath;
+ mc->prevCommSet = searchCommInCmdline;
+ mc->prevCmdlineSet = stripExeFromCmdline;
+
+ /* Mark everything as unchanged */
+ mc->cmdlineChanged = false;
+ mc->commChanged = false;
+ mc->exeChanged = false;
+
+ /* Clear any separators */
+ mc->sep1 = 0;
+ mc->sep2 = 0;
+
+ /* Clear any highlighting locations */
+ mc->baseStart = 0;
+ mc->baseEnd = 0;
+ mc->commStart = 0;
+ mc->commEnd = 0;
+
+ const char *cmdline = this->comm;
+ const char *procExe = lp->procExe;
+ const char *procComm = lp->procComm;
+
+ char *strStart = mc->str;
+ char *str = strStart;
+
+ int cmdlineBasenameOffset = lp->procCmdlineBasenameOffset;
+
+ if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */
+ if (showMergedCommand && !procExe && procComm && strlen(procComm)) { /* Prefix column with comm */
+ if (strncmp(cmdline + cmdlineBasenameOffset, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) {
+ mc->commStart = 0;
+ mc->commEnd = strlen(procComm);
+
+ str = stpcpy(str, procComm);
+
+ mc->sep1 = str - strStart;
+ str = stpcpy(str, SEPARATOR);
+ }
+ }
+
+ if (showProgramPath) {
+ (void) stpcpyWithNewlineConversion(str, cmdline);
+ mc->baseStart = cmdlineBasenameOffset;
+ mc->baseEnd = lp->procCmdlineBasenameEnd;
+ } else {
+ (void) stpcpyWithNewlineConversion(str, cmdline + cmdlineBasenameOffset);
+ mc->baseStart = 0;
+ mc->baseEnd = lp->procCmdlineBasenameEnd - cmdlineBasenameOffset;
+ }
+
+ if (mc->sep1) {
+ mc->baseStart += str - strStart - SEPARATOR_LEN + 1;
+ mc->baseEnd += str - strStart - SEPARATOR_LEN + 1;
+ }
+
+ return;
+ }
+
+ int exeLen = lp->procExeLen;
+ int exeBasenameOffset = lp->procExeBasenameOffset;
+ int exeBasenameLen = exeLen - exeBasenameOffset;
+
+ /* Start with copying exe */
+ if (showProgramPath) {
+ str = stpcpy(str, procExe);
+ mc->baseStart = exeBasenameOffset;
+ mc->baseEnd = exeLen;
+ } else {
+ str = stpcpy(str, procExe + exeBasenameOffset);
+ mc->baseStart = 0;
+ mc->baseEnd = exeBasenameLen;
+ }
+
+ mc->sep1 = 0;
+ mc->sep2 = 0;
+
+ int commStart = 0;
+ int commEnd = 0;
+ bool commInCmdline = false;
+
+ /* Try to match procComm with procExe's basename: This is reliable (predictable) */
+ if (strncmp(procExe + exeBasenameOffset, procComm, TASK_COMM_LEN - 1) == 0) {
+ commStart = mc->baseStart;
+ commEnd = mc->baseEnd;
+ } else if (searchCommInCmdline) {
+ /* commStart/commEnd will be adjusted later along with cmdline */
+ commInCmdline = findCommInCmdline(procComm, cmdline, cmdlineBasenameOffset, &commStart, &commEnd);
+ }
+
+ int matchLen = matchCmdlinePrefixWithExeSuffix(cmdline, cmdlineBasenameOffset, procExe, exeBasenameOffset, exeBasenameLen);
+
+ /* Note: commStart, commEnd are offsets into RichString. But the multibyte
+ * separator (with size SEPARATOR_LEN) has size 1 in RichString. The offset
+ * adjustments below reflect this. */
+ if (commEnd) {
+ mc->unmatchedExe = !matchLen;
+
+ if (matchLen) {
+ /* strip the matched exe prefix */
+ cmdline += matchLen;
+
+ if (commInCmdline) {
+ commStart += str - strStart - matchLen;
+ commEnd += str - strStart - matchLen;
+ }
+ } else {
+ /* cmdline will be a separate field */
+ mc->sep1 = str - strStart;
+ str = stpcpy(str, SEPARATOR);
+
+ if (commInCmdline) {
+ commStart += str - strStart - SEPARATOR_LEN + 1;
+ commEnd += str - strStart - SEPARATOR_LEN + 1;
+ }
+ }
+
+ mc->separateComm = false; /* procComm merged */
+ } else {
+ mc->sep1 = str - strStart;
+ str = stpcpy(str, SEPARATOR);
+
+ commStart = str - strStart - SEPARATOR_LEN + 1;
+ str = stpcpy(str, procComm);
+ commEnd = str - strStart - SEPARATOR_LEN + 1; /* or commStart + strlen(procComm) */
+
+ mc->unmatchedExe = !matchLen;
+
+ if (matchLen) {
+ if (stripExeFromCmdline) {
+ cmdline += matchLen;
+ }
+ }
+
+ if (*cmdline) {
+ mc->sep2 = str - strStart - SEPARATOR_LEN + 1;
+ str = stpcpy(str, SEPARATOR);
+ }
+
+ mc->separateComm = true; /* procComm a separate field */
+ }
+
+ /* Display cmdline if it hasn't been consumed by procExe */
+ if (*cmdline) {
+ (void) stpcpyWithNewlineConversion(str, cmdline);
+ }
+
+ mc->commStart = commStart;
+ mc->commEnd = commEnd;
+}
+
+static void LinuxProcess_writeCommand(const Process* this, int attr, int baseAttr, RichString* str) {
+ const LinuxProcess *lp = (const LinuxProcess *)this;
+ const LinuxProcessMergedCommand *mc = &lp->mergedCommand;
+
+ int strStart = RichString_size(str);
+
+ int baseStart = strStart + lp->mergedCommand.baseStart;
+ int baseEnd = strStart + lp->mergedCommand.baseEnd;
+ int commStart = strStart + lp->mergedCommand.commStart;
+ int commEnd = strStart + lp->mergedCommand.commEnd;
+
+ int commAttr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
+
+ bool highlightBaseName = this->settings->highlightBaseName;
+
+ if(lp->procExeDeleted)
+ baseAttr = CRT_colors[FAILED_READ];
+
+ RichString_append(str, attr, lp->mergedCommand.str);
+
+ if (lp->mergedCommand.commEnd) {
+ if (!lp->mergedCommand.separateComm && commStart == baseStart && highlightBaseName) {
+ /* If it was matched with procExe's basename, make it bold if needed */
+ if (commEnd > baseEnd) {
+ RichString_setAttrn(str, A_BOLD | baseAttr, baseStart, baseEnd - 1);
+ RichString_setAttrn(str, A_BOLD | commAttr, baseEnd, commEnd - 1);
+ } else if (commEnd < baseEnd) {
+ RichString_setAttrn(str, A_BOLD | commAttr, commStart, commEnd - 1);
+ RichString_setAttrn(str, A_BOLD | baseAttr, commEnd, baseEnd - 1);
+ } else {
+ // Actually should be highlighted commAttr, but marked baseAttr to reduce visual noise
+ RichString_setAttrn(str, A_BOLD | baseAttr, commStart, commEnd - 1);
+ }
+
+ baseStart = baseEnd;
+ } else {
+ RichString_setAttrn(str, commAttr, commStart, commEnd - 1);
+ }
+ }
+
+ if (baseStart < baseEnd && highlightBaseName) {
+ RichString_setAttrn(str, baseAttr, baseStart, baseEnd - 1);
+ }
+
+ if (mc->sep1)
+ RichString_setAttrn(str, CRT_colors[FAILED_READ], strStart + mc->sep1, strStart + mc->sep1);
+ if (mc->sep2)
+ RichString_setAttrn(str, CRT_colors[FAILED_READ], strStart + mc->sep2, strStart + mc->sep2);
+}
+
+static void LinuxProcess_writeCommandField(const Process *this, RichString *str, char *buffer, int n, int attr) {
+ /* This code is from Process_writeField for COMM, but we invoke
+ * LinuxProcess_writeCommand to display
+ * /proc/pid/exe (or its basename)│/proc/pid/comm│/proc/pid/cmdline */
+ int baseattr = CRT_colors[PROCESS_BASENAME];
+ if (this->settings->highlightThreads && Process_isThread(this)) {
+ attr = CRT_colors[PROCESS_THREAD];
+ baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
+ }
+ if (!this->settings->treeView || this->indent == 0) {
+ LinuxProcess_writeCommand(this, attr, baseattr, str);
+ } else {
+ char* buf = buffer;
+ int maxIndent = 0;
+ bool lastItem = (this->indent < 0);
+ int indent = (this->indent < 0 ? -this->indent : this->indent);
+ int vertLen = strlen(CRT_treeStr[TREE_STR_VERT]);
+
+ for (int i = 0; i < 32; i++) {
+ if (indent & (1U << i)) {
+ maxIndent = i+1;
+ }
+ }
+ for (int i = 0; i < maxIndent - 1; i++) {
+ if (indent & (1 << i)) {
+ if (buf - buffer + (vertLen + 3) > n) {
+ break;
+ }
+ buf = stpcpy(buf, CRT_treeStr[TREE_STR_VERT]);
+ buf = stpcpy(buf, " ");
+ } else {
+ if (buf - buffer + 4 > n) {
+ break;
+ }
+ buf = stpcpy(buf, " ");
+ }
+ }
+ n -= (buf - buffer);
+ const char* draw = CRT_treeStr[lastItem ? (this->settings->direction == 1 ? TREE_STR_BEND : TREE_STR_TEND) : TREE_STR_RTEE];
+ xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
+ RichString_append(str, CRT_colors[PROCESS_TREE], buffer);
+ LinuxProcess_writeCommand(this, attr, baseattr, str);
+ }
+}
+
+static void LinuxProcess_writeField(const Process* this, RichString* str, ProcessField field) {
+ const LinuxProcess* lp = (const LinuxProcess*) this;
bool coloring = this->settings->highlightMegabytes;
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
- int n = sizeof(buffer) - 1;
+ size_t n = sizeof(buffer) - 1;
switch ((int)field) {
case TTY_NR: {
if (lp->ttyDevice) {
@@ -206,11 +631,19 @@ void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field)
}
case CMINFLT: Process_colorNumber(str, lp->cminflt, coloring); return;
case CMAJFLT: Process_colorNumber(str, lp->cmajflt, coloring); return;
- case M_DRS: Process_humanNumber(str, lp->m_drs * PAGE_SIZE_KB, coloring); return;
- case M_DT: Process_humanNumber(str, lp->m_dt * PAGE_SIZE_KB, coloring); return;
- case M_LRS: Process_humanNumber(str, lp->m_lrs * PAGE_SIZE_KB, coloring); return;
- case M_TRS: Process_humanNumber(str, lp->m_trs * PAGE_SIZE_KB, coloring); return;
- case M_SHARE: Process_humanNumber(str, lp->m_share * PAGE_SIZE_KB, coloring); return;
+ case M_DRS: Process_humanNumber(str, lp->m_drs * CRT_pageSizeKB, coloring); return;
+ case M_DT: Process_humanNumber(str, lp->m_dt * CRT_pageSizeKB, coloring); return;
+ case M_LRS:
+ if (lp->m_lrs) {
+ Process_humanNumber(str, lp->m_lrs * CRT_pageSizeKB, coloring);
+ return;
+ }
+
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, " N/A ");
+ break;
+ case M_TRS: Process_humanNumber(str, lp->m_trs * CRT_pageSizeKB, coloring); return;
+ case M_SHARE: Process_humanNumber(str, lp->m_share * CRT_pageSizeKB, coloring); return;
case M_PSS: Process_humanNumber(str, lp->m_pss, coloring); return;
case M_SWAP: Process_humanNumber(str, lp->m_swap, coloring); return;
case M_PSSWP: Process_humanNumber(str, lp->m_psswp, coloring); return;
@@ -218,14 +651,6 @@ void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field)
case STIME: Process_printTime(str, lp->stime); return;
case CUTIME: Process_printTime(str, lp->cutime); return;
case CSTIME: Process_printTime(str, lp->cstime); return;
- case STARTTIME: {
- struct tm date;
- time_t starttimewall = btime + (lp->starttime / sysconf(_SC_CLK_TCK));
- (void) localtime_r(&starttimewall, &date);
- strftime(buffer, n, ((starttimewall > time(NULL) - 86400) ? "%R " : "%b%d "), &date);
- break;
- }
- #ifdef HAVE_TASKSTATS
case RCHAR: Process_colorNumber(str, lp->io_rchar, coloring); return;
case WCHAR: Process_colorNumber(str, lp->io_wchar, coloring); return;
case SYSCR: Process_colorNumber(str, lp->io_syscr, coloring); return;
@@ -236,22 +661,25 @@ void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field)
case IO_READ_RATE: Process_outputRate(str, buffer, n, lp->io_rate_read_bps, coloring); return;
case IO_WRITE_RATE: Process_outputRate(str, buffer, n, lp->io_rate_write_bps, coloring); return;
case IO_RATE: {
- double totalRate = (lp->io_rate_read_bps != -1)
- ? (lp->io_rate_read_bps + lp->io_rate_write_bps)
- : -1;
+ double totalRate = NAN;
+ if (!isnan(lp->io_rate_read_bps) && !isnan(lp->io_rate_write_bps))
+ totalRate = lp->io_rate_read_bps + lp->io_rate_write_bps;
+ else if (!isnan(lp->io_rate_read_bps))
+ totalRate = lp->io_rate_read_bps;
+ else if (!isnan(lp->io_rate_write_bps))
+ totalRate = lp->io_rate_write_bps;
+ else
+ totalRate = NAN;
Process_outputRate(str, buffer, n, totalRate, coloring); return;
}
- #endif
#ifdef HAVE_OPENVZ
- case CTID: xSnprintf(buffer, n, "%7u ", lp->ctid); break;
+ case CTID: xSnprintf(buffer, n, "%-8s ", lp->ctid ? lp->ctid : ""); break;
case VPID: xSnprintf(buffer, n, Process_pidFormat, lp->vpid); break;
#endif
#ifdef HAVE_VSERVER
case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break;
#endif
- #ifdef HAVE_CGROUP
- case CGROUP: xSnprintf(buffer, n, "%-10s ", lp->cgroup); break;
- #endif
+ case CGROUP: xSnprintf(buffer, n, "%-10s ", lp->cgroup ? lp->cgroup : ""); break;
case OOM: xSnprintf(buffer, n, "%4u ", lp->oom); break;
case IO_PRIORITY: {
int klass = IOPriority_class(lp->ioPriority);
@@ -276,96 +704,175 @@ void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field)
case PERCENT_IO_DELAY: LinuxProcess_printDelay(lp->blkio_delay_percent, buffer, n); break;
case PERCENT_SWAP_DELAY: LinuxProcess_printDelay(lp->swapin_delay_percent, buffer, n); break;
#endif
+ case CTXT:
+ if (lp->ctxt_diff > 1000) {
+ attr |= A_BOLD;
+ }
+ xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff);
+ break;
+ case SECATTR: snprintf(buffer, n, "%-30s ", lp->secattr ? lp->secattr : "?"); break;
+ case COMM: {
+ if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !lp->mergedCommand.str) {
+ Process_writeField(this, str, field);
+ } else {
+ LinuxProcess_writeCommandField(this, str, buffer, n, attr);
+ }
+ return;
+ }
+ case PROC_COMM: {
+ if (lp->procComm) {
+ attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
+ /* 15 being (TASK_COMM_LEN - 1) */
+ xSnprintf(buffer, n, "%-15.15s ", lp->procComm);
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, "%-15.15s ", Process_isKernelThread(lp) ? kthreadID : "N/A");
+ }
+ break;
+ }
+ case PROC_EXE: {
+ if (lp->procExe) {
+ attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME];
+ if (lp->procExeDeleted)
+ attr = CRT_colors[FAILED_READ];
+ xSnprintf(buffer, n, "%-15.15s ", lp->procExe + lp->procExeBasenameOffset);
+ } else {
+ attr = CRT_colors[PROCESS_SHADOW];
+ xSnprintf(buffer, n, "%-15.15s ", Process_isKernelThread(lp) ? kthreadID : "N/A");
+ }
+ break;
+ }
+ case CWD:
+ if (!lp->cwd) {
+ xSnprintf(buffer, n, "%-25s ", "N/A");
+ attr = CRT_colors[PROCESS_SHADOW];
+ } else if (String_startsWith(lp->cwd, "/proc/") && strstr(lp->cwd, " (deleted)") != NULL) {
+ xSnprintf(buffer, n, "%-25s ", "main thread terminated");
+ attr = CRT_colors[PROCESS_SHADOW];
+ } else {
+ xSnprintf(buffer, n, "%-25.25s ", lp->cwd);
+ }
+ break;
default:
- Process_writeField((Process*)this, str, field);
+ Process_writeField(this, str, field);
return;
}
RichString_append(str, attr, buffer);
}
-long LinuxProcess_compare(const void* v1, const void* v2) {
- LinuxProcess *p1, *p2;
- Settings *settings = ((Process*)v1)->settings;
+static long LinuxProcess_compare(const void* v1, const void* v2) {
+ const LinuxProcess *p1, *p2;
+ const Settings *settings = ((const Process*)v1)->settings;
+
if (settings->direction == 1) {
- p1 = (LinuxProcess*)v1;
- p2 = (LinuxProcess*)v2;
+ p1 = (const LinuxProcess*)v1;
+ p2 = (const LinuxProcess*)v2;
} else {
- p2 = (LinuxProcess*)v1;
- p1 = (LinuxProcess*)v2;
+ p2 = (const LinuxProcess*)v1;
+ p1 = (const LinuxProcess*)v2;
}
- long long diff;
+
switch ((int)settings->sortKey) {
case M_DRS:
- return (p2->m_drs - p1->m_drs);
+ return SPACESHIP_NUMBER(p2->m_drs, p1->m_drs);
case M_DT:
- return (p2->m_dt - p1->m_dt);
+ return SPACESHIP_NUMBER(p2->m_dt, p1->m_dt);
case M_LRS:
- return (p2->m_lrs - p1->m_lrs);
+ return SPACESHIP_NUMBER(p2->m_lrs, p1->m_lrs);
case M_TRS:
- return (p2->m_trs - p1->m_trs);
+ return SPACESHIP_NUMBER(p2->m_trs, p1->m_trs);
case M_SHARE:
- return (p2->m_share - p1->m_share);
+ return SPACESHIP_NUMBER(p2->m_share, p1->m_share);
case M_PSS:
- return (p2->m_pss - p1->m_pss);
+ return SPACESHIP_NUMBER(p2->m_pss, p1->m_pss);
case M_SWAP:
- return (p2->m_swap - p1->m_swap);
+ return SPACESHIP_NUMBER(p2->m_swap, p1->m_swap);
case M_PSSWP:
- return (p2->m_psswp - p1->m_psswp);
- case UTIME: diff = p2->utime - p1->utime; goto test_diff;
- case CUTIME: diff = p2->cutime - p1->cutime; goto test_diff;
- case STIME: diff = p2->stime - p1->stime; goto test_diff;
- case CSTIME: diff = p2->cstime - p1->cstime; goto test_diff;
- case STARTTIME: {
- if (p1->starttime == p2->starttime)
- return (p1->super.pid - p2->super.pid);
- else
- return (p1->starttime - p2->starttime);
- }
- #ifdef HAVE_TASKSTATS
- case RCHAR: diff = p2->io_rchar - p1->io_rchar; goto test_diff;
- case WCHAR: diff = p2->io_wchar - p1->io_wchar; goto test_diff;
- case SYSCR: diff = p2->io_syscr - p1->io_syscr; goto test_diff;
- case SYSCW: diff = p2->io_syscw - p1->io_syscw; goto test_diff;
- case RBYTES: diff = p2->io_read_bytes - p1->io_read_bytes; goto test_diff;
- case WBYTES: diff = p2->io_write_bytes - p1->io_write_bytes; goto test_diff;
- case CNCLWB: diff = p2->io_cancelled_write_bytes - p1->io_cancelled_write_bytes; goto test_diff;
- case IO_READ_RATE: diff = p2->io_rate_read_bps - p1->io_rate_read_bps; goto test_diff;
- case IO_WRITE_RATE: diff = p2->io_rate_write_bps - p1->io_rate_write_bps; goto test_diff;
- case IO_RATE: diff = (p2->io_rate_read_bps + p2->io_rate_write_bps) - (p1->io_rate_read_bps + p1->io_rate_write_bps); goto test_diff;
- #endif
+ return SPACESHIP_NUMBER(p2->m_psswp, p1->m_psswp);
+ case UTIME:
+ return SPACESHIP_NUMBER(p2->utime, p1->utime);
+ case CUTIME:
+ return SPACESHIP_NUMBER(p2->cutime, p1->cutime);
+ case STIME:
+ return SPACESHIP_NUMBER(p2->stime, p1->stime);
+ case CSTIME:
+ return SPACESHIP_NUMBER(p2->cstime, p1->cstime);
+ case RCHAR:
+ return SPACESHIP_NUMBER(p2->io_rchar, p1->io_rchar);
+ case WCHAR:
+ return SPACESHIP_NUMBER(p2->io_wchar, p1->io_wchar);
+ case SYSCR:
+ return SPACESHIP_NUMBER(p2->io_syscr, p1->io_syscr);
+ case SYSCW:
+ return SPACESHIP_NUMBER(p2->io_syscw, p1->io_syscw);
+ case RBYTES:
+ return SPACESHIP_NUMBER(p2->io_read_bytes, p1->io_read_bytes);
+ case WBYTES:
+ return SPACESHIP_NUMBER(p2->io_write_bytes, p1->io_write_bytes);
+ case CNCLWB:
+ return SPACESHIP_NUMBER(p2->io_cancelled_write_bytes, p1->io_cancelled_write_bytes);
+ case IO_READ_RATE:
+ return SPACESHIP_NUMBER(p2->io_rate_read_bps, p1->io_rate_read_bps);
+ case IO_WRITE_RATE:
+ return SPACESHIP_NUMBER(p2->io_rate_write_bps, p1->io_rate_write_bps);
+ case IO_RATE:
+ return SPACESHIP_NUMBER(p2->io_rate_read_bps + p2->io_rate_write_bps, p1->io_rate_read_bps + p1->io_rate_write_bps);
#ifdef HAVE_OPENVZ
case CTID:
- return (p2->ctid - p1->ctid);
+ return SPACESHIP_NULLSTR(p1->ctid, p2->ctid);
case VPID:
- return (p2->vpid - p1->vpid);
+ return SPACESHIP_NUMBER(p2->vpid, p1->vpid);
#endif
#ifdef HAVE_VSERVER
case VXID:
- return (p2->vxid - p1->vxid);
+ return SPACESHIP_NUMBER(p2->vxid, p1->vxid);
#endif
- #ifdef HAVE_CGROUP
case CGROUP:
- return strcmp(p1->cgroup ? p1->cgroup : "", p2->cgroup ? p2->cgroup : "");
- #endif
+ return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);
case OOM:
- return ((int)p2->oom - (int)p1->oom);
+ return SPACESHIP_NUMBER(p2->oom, p1->oom);
#ifdef HAVE_DELAYACCT
case PERCENT_CPU_DELAY:
- return (p2->cpu_delay_percent > p1->cpu_delay_percent ? 1 : -1);
+ return SPACESHIP_NUMBER(p2->cpu_delay_percent, p1->cpu_delay_percent);
case PERCENT_IO_DELAY:
- return (p2->blkio_delay_percent > p1->blkio_delay_percent ? 1 : -1);
+ return SPACESHIP_NUMBER(p2->blkio_delay_percent, p1->blkio_delay_percent);
case PERCENT_SWAP_DELAY:
- return (p2->swapin_delay_percent > p1->swapin_delay_percent ? 1 : -1);
+ return SPACESHIP_NUMBER(p2->swapin_delay_percent, p1->swapin_delay_percent);
#endif
case IO_PRIORITY:
- return LinuxProcess_effectiveIOPriority(p1) - LinuxProcess_effectiveIOPriority(p2);
+ return SPACESHIP_NUMBER(LinuxProcess_effectiveIOPriority(p1), LinuxProcess_effectiveIOPriority(p2));
+ case CTXT:
+ return SPACESHIP_NUMBER(p2->ctxt_diff, p1->ctxt_diff);
+ case SECATTR:
+ return SPACESHIP_NULLSTR(p1->secattr, p2->secattr);
+ case PROC_COMM: {
+ const char *comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : "");
+ const char *comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : "");
+ return strcmp(comm1, comm2);
+ }
+ case PROC_EXE: {
+ const char *exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : "");
+ const char *exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : "");
+ return strcmp(exe1, exe2);
+ }
+ case CWD:
+ return SPACESHIP_NULLSTR(p1->cwd, p2->cwd);
default:
return Process_compare(v1, v2);
}
- test_diff:
- return (diff > 0) ? 1 : (diff < 0 ? -1 : 0);
}
-bool Process_isThread(Process* this) {
+bool Process_isThread(const Process* this) {
return (Process_isUserlandThread(this) || Process_isKernelThread(this));
}
+
+const ProcessClass LinuxProcess_class = {
+ .super = {
+ .extends = Class(Process),
+ .display = Process_display,
+ .delete = Process_delete,
+ .compare = LinuxProcess_compare
+ },
+ .writeField = LinuxProcess_writeField,
+ .getCommandStr = LinuxProcess_getCommandStr
+};
diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h
index 021cae7..ad396fb 100644
--- a/linux/LinuxProcess.h
+++ b/linux/LinuxProcess.h
@@ -4,16 +4,30 @@
htop - LinuxProcess.h
(C) 2014 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
-#define PROCESS_FLAG_LINUX_IOPRIO 0x0100
-#define PROCESS_FLAG_LINUX_OPENVZ 0x0200
-#define PROCESS_FLAG_LINUX_VSERVER 0x0400
-#define PROCESS_FLAG_LINUX_CGROUP 0x0800
-#define PROCESS_FLAG_LINUX_OOM 0x1000
-#define PROCESS_FLAG_LINUX_SMAPS 0x2000
+#include "config.h" // IWYU pragma: keep
+
+#include <stdbool.h>
+
+#include "IOPriority.h"
+#include "Object.h"
+#include "Process.h"
+#include "Settings.h"
+
+
+#define PROCESS_FLAG_LINUX_IOPRIO 0x00000100
+#define PROCESS_FLAG_LINUX_OPENVZ 0x00000200
+#define PROCESS_FLAG_LINUX_VSERVER 0x00000400
+#define PROCESS_FLAG_LINUX_CGROUP 0x00000800
+#define PROCESS_FLAG_LINUX_OOM 0x00001000
+#define PROCESS_FLAG_LINUX_SMAPS 0x00002000
+#define PROCESS_FLAG_LINUX_CTXT 0x00004000
+#define PROCESS_FLAG_LINUX_SECATTR 0x00008000
+#define PROCESS_FLAG_LINUX_LRS_FIX 0x00010000
+#define PROCESS_FLAG_LINUX_CWD 0x00020000
typedef enum UnsupportedProcessFields {
FLAGS = 9,
@@ -55,7 +69,6 @@ typedef enum LinuxProcessFields {
#ifdef HAVE_VSERVER
VXID = 102,
#endif
- #ifdef HAVE_TASKSTATS
RCHAR = 103,
WCHAR = 104,
SYSCR = 105,
@@ -66,10 +79,7 @@ typedef enum LinuxProcessFields {
IO_READ_RATE = 110,
IO_WRITE_RATE = 111,
IO_RATE = 112,
- #endif
- #ifdef HAVE_CGROUP
CGROUP = 113,
- #endif
OOM = 114,
IO_PRIORITY = 115,
#ifdef HAVE_DELAYACCT
@@ -80,13 +90,48 @@ typedef enum LinuxProcessFields {
M_PSS = 119,
M_SWAP = 120,
M_PSSWP = 121,
- LAST_PROCESSFIELD = 122,
+ CTXT = 122,
+ SECATTR = 123,
+ PROC_COMM = 124,
+ PROC_EXE = 125,
+ CWD = 126,
+ LAST_PROCESSFIELD = 127,
} LinuxProcessField;
-#include "IOPriority.h"
+/* LinuxProcessMergedCommand is populated by LinuxProcess_makeCommandStr: It
+ * contains the merged Command string, and the information needed by
+ * LinuxProcess_writeCommand to color the string. str will be NULL for kernel
+ * threads and zombies */
+typedef struct LinuxProcessMergedCommand_ {
+ char *str; /* merged Command string */
+ int maxLen; /* maximum expected length of Command string */
+ int baseStart; /* basename's start offset */
+ int baseEnd; /* basename's end offset */
+ int commStart; /* comm's start offset */
+ int commEnd; /* comm's end offset */
+ int sep1; /* first field separator, used if non-zero */
+ int sep2; /* second field separator, used if non-zero */
+ bool separateComm; /* whether comm is a separate field */
+ bool unmatchedExe; /* whether exe matched with cmdline */
+ bool cmdlineChanged; /* whether cmdline changed */
+ bool exeChanged; /* whether exe changed */
+ bool commChanged; /* whether comm changed */
+ bool prevMergeSet; /* whether showMergedCommand was set */
+ bool prevPathSet; /* whether showProgramPath was set */
+ bool prevCommSet; /* whether findCommInCmdline was set */
+ bool prevCmdlineSet; /* whether findCommInCmdline was set */
+} LinuxProcessMergedCommand;
typedef struct LinuxProcess_ {
Process super;
+ char *procComm;
+ char *procExe;
+ int procExeLen;
+ int procExeBasenameOffset;
+ bool procExeDeleted;
+ int procCmdlineBasenameOffset;
+ int procCmdlineBasenameEnd;
+ LinuxProcessMergedCommand mergedCommand;
bool isKernelThread;
IOPriority ioPriority;
unsigned long int cminflt;
@@ -103,8 +148,6 @@ typedef struct LinuxProcess_ {
long m_drs;
long m_lrs;
long m_dt;
- unsigned long long starttime;
- #ifdef HAVE_TASKSTATS
unsigned long long io_rchar;
unsigned long long io_wchar;
unsigned long long io_syscr;
@@ -116,17 +159,14 @@ typedef struct LinuxProcess_ {
unsigned long long io_rate_write_time;
double io_rate_read_bps;
double io_rate_write_bps;
- #endif
#ifdef HAVE_OPENVZ
- unsigned int ctid;
- unsigned int vpid;
+ char* ctid;
+ pid_t vpid;
#endif
#ifdef HAVE_VSERVER
unsigned int vxid;
#endif
- #ifdef HAVE_CGROUP
char* cgroup;
- #endif
unsigned int oom;
char* ttyDevice;
#ifdef HAVE_DELAYACCT
@@ -138,11 +178,18 @@ typedef struct LinuxProcess_ {
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;
+ char* cwd;
} LinuxProcess;
-#define Process_isKernelThread(_process) (((LinuxProcess*)(_process))->isKernelThread)
+#define Process_isKernelThread(_process) (((const LinuxProcess*)(_process))->isKernelThread)
-#define Process_isUserlandThread(_process) (_process->pid != _process->tgid)
+static inline bool Process_isUserlandThread(const Process* this) {
+ return this->pid != this->tgid;
+}
extern long long btime;
@@ -150,34 +197,20 @@ extern ProcessFieldData Process_fields[];
extern ProcessPidColumn Process_pidColumns[];
-extern ProcessClass LinuxProcess_class;
+extern const ProcessClass LinuxProcess_class;
-LinuxProcess* LinuxProcess_new(Settings* settings);
+Process* LinuxProcess_new(const Settings* settings);
void Process_delete(Object* cast);
-/*
-[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:
-extern io_priority;
-*/
-#define LinuxProcess_effectiveIOPriority(p_) (IOPriority_class(p_->ioPriority) == IOPRIO_CLASS_NONE ? IOPriority_tuple(IOPRIO_CLASS_BE, (p_->super.nice + 20) / 5) : p_->ioPriority)
-
IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this);
-bool LinuxProcess_setIOPriority(LinuxProcess* this, Arg ioprio);
-
-#ifdef HAVE_DELAYACCT
-void LinuxProcess_printDelay(float delay_percent, char* buffer, int n);
-#endif
-
-void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field);
+bool LinuxProcess_setIOPriority(Process* this, Arg ioprio);
-long LinuxProcess_compare(const void* v1, const void* v2);
+/* This function constructs the string that is displayed by
+ * LinuxProcess_writeCommand and also returned by LinuxProcess_getCommandStr */
+void LinuxProcess_makeCommandStr(Process *this);
-bool Process_isThread(Process* this);
+bool Process_isThread(const Process* this);
#endif
diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c
index 8dae13c..b9ba247 100644
--- a/linux/LinuxProcessList.c
+++ b/linux/LinuxProcessList.c
@@ -1,95 +1,98 @@
/*
htop - LinuxProcessList.c
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h" // IWYU pragma: keep
+
#include "LinuxProcessList.h"
-#include "LinuxProcess.h"
-#include "CRT.h"
-#include "StringUtils.h"
-#include <errno.h>
-#include <sys/time.h>
-#include <sys/utsname.h>
-#include <sys/stat.h>
-#include <unistd.h>
+
+#include <assert.h>
#include <dirent.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <stdarg.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
-#include <time.h>
-#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
#include <sys/types.h>
-#include <fcntl.h>
-#ifdef MAJOR_IN_MKDEV
-#include <sys/mkdev.h>
-#elif defined(MAJOR_IN_SYSMACROS)
-#include <sys/sysmacros.h>
-#endif
#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>
-#include <netlink/socket.h>
-#include <netlink/msg.h>
-#include <linux/taskstats.h>
#endif
-static ssize_t xread(int fd, void *buf, size_t count) {
- // Read some bytes. Retry on EINTR and when we don't get as many bytes as we requested.
- size_t alreadyRead = 0;
- for(;;) {
- ssize_t res = read(fd, buf, count);
- if (res == -1 && errno == EINTR) continue;
- if (res > 0) {
- buf = ((char*)buf)+res;
- count -= res;
- alreadyRead += res;
- }
- if (res == -1) return -1;
- if (count == 0 || res == 0) return alreadyRead;
- }
+#include "Compat.h"
+#include "CRT.h"
+#include "LinuxProcess.h"
+#include "Macros.h"
+#include "Object.h"
+#include "Process.h"
+#include "Settings.h"
+#include "XUtils.h"
+
+#ifdef MAJOR_IN_MKDEV
+#include <sys/mkdev.h>
+#elif defined(MAJOR_IN_SYSMACROS)
+#include <sys/sysmacros.h>
+#endif
+
+#ifdef HAVE_SENSORS_SENSORS_H
+#include "LibSensors.h"
+#endif
+
+
+static FILE* fopenat(openat_arg_t openatArg, const char* pathname, const char* mode) {
+ assert(String_eq(mode, "r")); /* only currently supported mode */
+
+ int fd = Compat_openat(openatArg, pathname, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ FILE* stream = fdopen(fd, mode);
+ if (!stream)
+ close(fd);
+
+ return stream;
}
static int sortTtyDrivers(const void* va, const void* vb) {
- TtyDriver* a = (TtyDriver*) va;
- TtyDriver* b = (TtyDriver*) vb;
- return (a->major == b->major) ? (a->minorFrom - b->minorFrom) : (a->major - b->major);
+ const TtyDriver* a = (const TtyDriver*) va;
+ const TtyDriver* b = (const TtyDriver*) vb;
+
+ int r = SPACESHIP_NUMBER(a->major, b->major);
+ if (r)
+ return r;
+
+ return SPACESHIP_NUMBER(a->minorFrom, b->minorFrom);
}
static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) {
TtyDriver* ttyDrivers;
- int fd = open(PROCTTYDRIVERSFILE, O_RDONLY);
- if (fd == -1)
- return;
- char* buf = NULL;
- int bufSize = MAX_READ;
- int bufLen = 0;
- for(;;) {
- buf = realloc(buf, bufSize);
- int size = xread(fd, buf + bufLen, MAX_READ);
- if (size <= 0) {
- buf[bufLen] = '\0';
- close(fd);
- break;
- }
- bufLen += size;
- bufSize += MAX_READ;
- }
- if (bufLen == 0) {
- free(buf);
+
+ char buf[16384];
+ ssize_t r = xReadfile(PROCTTYDRIVERSFILE, buf, sizeof(buf));
+ if (r < 0)
return;
- }
+
int numDrivers = 0;
int allocd = 10;
- ttyDrivers = malloc(sizeof(TtyDriver) * allocd);
+ ttyDrivers = xMalloc(sizeof(TtyDriver) * allocd);
char* at = buf;
while (*at != '\0') {
at = strchr(at, ' '); // skip first token
@@ -97,7 +100,7 @@ static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) {
char* token = at; // mark beginning of path
at = strchr(at, ' '); // find end of path
*at = '\0'; at++; // clear and skip
- ttyDrivers[numDrivers].path = strdup(token); // save
+ ttyDrivers[numDrivers].path = xStrdup(token); // save
while (*at == ' ') at++; // skip spaces
token = at; // mark beginning of major
at = strchr(at, ' '); // find end of major
@@ -123,12 +126,11 @@ static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) {
numDrivers++;
if (numDrivers == allocd) {
allocd += 10;
- ttyDrivers = realloc(ttyDrivers, sizeof(TtyDriver) * allocd);
+ ttyDrivers = xRealloc(ttyDrivers, sizeof(TtyDriver) * allocd);
}
}
- free(buf);
numDrivers++;
- ttyDrivers = realloc(ttyDrivers, sizeof(TtyDriver) * numDrivers);
+ ttyDrivers = xRealloc(ttyDrivers, sizeof(TtyDriver) * numDrivers);
ttyDrivers[numDrivers - 1].path = NULL;
qsort(ttyDrivers, numDrivers - 1, sizeof(TtyDriver), sortTtyDrivers);
this->ttyDrivers = ttyDrivers;
@@ -149,6 +151,46 @@ static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) {
#endif
+static int LinuxProcessList_computeCPUcount(void) {
+ FILE* file = fopen(PROCSTATFILE, "r");
+ if (file == NULL) {
+ CRT_fatalError("Cannot open " PROCSTATFILE);
+ }
+
+ int cpus = 0;
+ char buffer[PROC_LINE_LENGTH + 1];
+ while (fgets(buffer, sizeof(buffer), file)) {
+ if (String_startsWith(buffer, "cpu")) {
+ cpus++;
+ }
+ }
+
+ fclose(file);
+
+ /* subtract raw cpu entry */
+ if (cpus > 0) {
+ cpus--;
+ }
+
+ return cpus;
+}
+
+static void LinuxProcessList_updateCPUcount(LinuxProcessList* this) {
+ ProcessList* pl = &(this->super);
+ int cpus = LinuxProcessList_computeCPUcount();
+ if (cpus == 0 || cpus == pl->cpuCount)
+ return;
+
+ pl->cpuCount = cpus;
+ free(this->cpus);
+ this->cpus = xCalloc(cpus + 1, sizeof(CPUData));
+
+ for (int i = 0; i <= cpus; i++) {
+ this->cpus[i].totalTime = 1;
+ this->cpus[i].totalPeriod = 1;
+ }
+}
+
ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) {
LinuxProcessList* this = xCalloc(1, sizeof(LinuxProcessList));
ProcessList* pl = &(this->super);
@@ -162,41 +204,47 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
// Check for /proc/*/smaps_rollup availability (improves smaps parsing speed, Linux 4.14+)
FILE* file = fopen(PROCDIR "/self/smaps_rollup", "r");
- if(file != NULL) {
+ if (file != NULL) {
this->haveSmapsRollup = true;
fclose(file);
} else {
this->haveSmapsRollup = false;
}
- // Update CPU count:
- file = fopen(PROCSTATFILE, "r");
- if (file == NULL) {
- CRT_fatalError("Cannot open " PROCSTATFILE);
- }
- int cpus = 0;
- do {
- char buffer[PROC_LINE_LENGTH + 1];
- if (fgets(buffer, PROC_LINE_LENGTH + 1, file) == NULL) {
- CRT_fatalError("No btime in " PROCSTATFILE);
- } else if (String_startsWith(buffer, "cpu")) {
- cpus++;
- } else if (String_startsWith(buffer, "btime ")) {
- if (sscanf(buffer, "btime %lld\n", &btime) != 1)
- CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
- break;
+ // Read btime
+ {
+ FILE* statfile = fopen(PROCSTATFILE, "r");
+ if (statfile == NULL) {
+ CRT_fatalError("Cannot open " PROCSTATFILE);
}
- } while(true);
- fclose(file);
+ while (true) {
+ char buffer[PROC_LINE_LENGTH + 1];
+ if (fgets(buffer, sizeof(buffer), statfile) == NULL) {
+ CRT_fatalError("No btime in " PROCSTATFILE);
+ } else if (String_startsWith(buffer, "btime ")) {
+ if (sscanf(buffer, "btime %lld\n", &btime) != 1) {
+ CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
+ }
+ break;
+ }
+ }
- pl->cpuCount = MAXIMUM(cpus - 1, 1);
- this->cpus = xCalloc(cpus, sizeof(CPUData));
+ fclose(statfile);
+ }
- for (int i = 0; i < cpus; i++) {
- this->cpus[i].totalTime = 1;
- this->cpus[i].totalPeriod = 1;
+ // Initialize CPU count
+ {
+ int cpus = LinuxProcessList_computeCPUcount();
+ pl->cpuCount = MAXIMUM(cpus, 1);
+ this->cpus = xCalloc(cpus + 1, sizeof(CPUData));
+
+ for (int i = 0; i <= cpus; i++) {
+ this->cpus[i].totalTime = 1;
+ this->cpus[i].totalPeriod = 1;
+ }
}
+
return pl;
}
@@ -205,7 +253,7 @@ void ProcessList_delete(ProcessList* pl) {
ProcessList_done(pl);
free(this->cpus);
if (this->ttyDrivers) {
- for(int i = 0; this->ttyDrivers[i].path; i++) {
+ for (int i = 0; this->ttyDrivers[i].path; i++) {
free(this->ttyDrivers[i].path);
}
free(this->ttyDrivers);
@@ -219,38 +267,41 @@ void ProcessList_delete(ProcessList* pl) {
free(this);
}
-static double jiffy = 0.0;
-
static inline unsigned long long LinuxProcess_adjustTime(unsigned long long t) {
- if(jiffy == 0.0) jiffy = sysconf(_SC_CLK_TCK);
- double jiffytime = 1.0 / jiffy;
- return (unsigned long long) t * jiffytime * 100;
+ static long jiffy = -1;
+ if (jiffy == -1) {
+ errno = 0;
+ jiffy = sysconf(_SC_CLK_TCK);
+ if (errno || -1 == jiffy) {
+ jiffy = -1;
+ return t; // Assume 100Hz clock
+ }
+ }
+ return t * 100 / jiffy;
}
-static bool LinuxProcessList_readStatFile(Process *process, const char* dirname, const char* name, char* command, int* commLen) {
+static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, char* command, int* commLen) {
LinuxProcess* lp = (LinuxProcess*) process;
- char filename[MAX_NAME+1];
- xSnprintf(filename, MAX_NAME, "%s/%s/stat", dirname, name);
- int fd = open(filename, O_RDONLY);
- if (fd == -1)
- return false;
-
- static char buf[MAX_READ+1];
+ const int commLenIn = *commLen;
+ *commLen = 0;
- int size = xread(fd, buf, MAX_READ);
- close(fd);
- if (size <= 0) return false;
- buf[size] = '\0';
+ char buf[MAX_READ + 1];
+ ssize_t r = xReadfileat(procFd, "stat", buf, sizeof(buf));
+ if (r < 0)
+ return false;
assert(process->pid == atoi(buf));
- char *location = strchr(buf, ' ');
- if (!location) return false;
+ char* location = strchr(buf, ' ');
+ if (!location)
+ return false;
location += 2;
- char *end = strrchr(location, ')');
- if (!end) return false;
+ char* end = strrchr(location, ')');
+ if (!end)
+ return false;
- int commsize = end - location;
+ int commsize = MINIMUM(end - location, commLenIn - 1);
+ // deepcode ignore BufferOverflow: commsize is bounded by the allocated length passed in by commLen, saved into commLenIn
memcpy(command, location, commsize);
command[commsize] = '\0';
*commLen = commsize;
@@ -292,10 +343,16 @@ static bool LinuxProcessList_readStatFile(Process *process, const char* dirname,
location += 1;
process->nlwp = strtol(location, &location, 10);
location += 1;
- location = strchr(location, ' ')+1;
- lp->starttime = strtoll(location, &location, 10);
+ location = strchr(location, ' ') + 1;
+ if (process->starttime_ctime == 0) {
+ process->starttime_ctime = btime + LinuxProcess_adjustTime(strtoll(location, &location, 10)) / 100;
+ } else {
+ location = strchr(location, ' ') + 1;
+ }
location += 1;
- for (int i=0; i<15; i++) location = strchr(location, ' ')+1;
+ for (int i = 0; i < 15; i++) {
+ location = strchr(location, ' ') + 1;
+ }
process->exit_signal = strtol(location, &location, 10);
location += 1;
assert(location != NULL);
@@ -307,30 +364,25 @@ static bool LinuxProcessList_readStatFile(Process *process, const char* dirname,
}
-static bool LinuxProcessList_statProcessDir(Process* process, const char* dirname, char* name) {
- char filename[MAX_NAME+1];
- filename[MAX_NAME] = '\0';
-
- xSnprintf(filename, MAX_NAME, "%s/%s", dirname, name);
+static bool LinuxProcessList_statProcessDir(Process* process, openat_arg_t procFd) {
struct stat sstat;
- int statok = stat(filename, &sstat);
+#ifdef HAVE_OPENAT
+ int statok = fstat(procFd, &sstat);
+#else
+ int statok = stat(procFd, &sstat);
+#endif
if (statok == -1)
return false;
process->st_uid = sstat.st_uid;
return true;
}
-#ifdef HAVE_TASKSTATS
-
-static void LinuxProcessList_readIoFile(LinuxProcess* process, const char* dirname, char* name, unsigned long long now) {
- char filename[MAX_NAME+1];
- filename[MAX_NAME] = '\0';
-
- xSnprintf(filename, MAX_NAME, "%s/%s/io", dirname, name);
- int fd = open(filename, O_RDONLY);
- if (fd == -1) {
- process->io_rate_read_bps = -1;
- process->io_rate_write_bps = -1;
+static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t procFd, unsigned long long now) {
+ char buffer[1024];
+ ssize_t r = xReadfileat(procFd, "io", buffer, sizeof(buffer));
+ if (r < 0) {
+ process->io_rate_read_bps = NAN;
+ process->io_rate_write_bps = NAN;
process->io_rchar = -1LL;
process->io_wchar = -1LL;
process->io_syscr = -1LL;
@@ -343,171 +395,361 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, const char* dirna
return;
}
- char buffer[1024];
- ssize_t buflen = xread(fd, buffer, 1023);
- close(fd);
- if (buflen < 1) return;
- buffer[buflen] = '\0';
unsigned long long last_read = process->io_read_bytes;
unsigned long long last_write = process->io_write_bytes;
- char *buf = buffer;
- char *line = NULL;
+ char* buf = buffer;
+ char* line = NULL;
while ((line = strsep(&buf, "\n")) != NULL) {
switch (line[0]) {
case 'r':
- if (line[1] == 'c' && strncmp(line+2, "har: ", 5) == 0)
- process->io_rchar = strtoull(line+7, NULL, 10);
- else if (strncmp(line+1, "ead_bytes: ", 11) == 0) {
- process->io_read_bytes = strtoull(line+12, NULL, 10);
+ if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) {
+ process->io_rchar = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "ead_bytes: ")) {
+ process->io_read_bytes = strtoull(line + 12, NULL, 10);
process->io_rate_read_bps =
- ((double)(process->io_read_bytes - last_read))/(((double)(now - process->io_rate_read_time))/1000);
+ ((double)(process->io_read_bytes - last_read)) / (((double)(now - process->io_rate_read_time)) / 1000);
process->io_rate_read_time = now;
}
break;
case 'w':
- if (line[1] == 'c' && strncmp(line+2, "har: ", 5) == 0)
- process->io_wchar = strtoull(line+7, NULL, 10);
- else if (strncmp(line+1, "rite_bytes: ", 12) == 0) {
- process->io_write_bytes = strtoull(line+13, NULL, 10);
+ if (line[1] == 'c' && String_startsWith(line + 2, "har: ")) {
+ process->io_wchar = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "rite_bytes: ")) {
+ process->io_write_bytes = strtoull(line + 13, NULL, 10);
process->io_rate_write_bps =
- ((double)(process->io_write_bytes - last_write))/(((double)(now - process->io_rate_write_time))/1000);
+ ((double)(process->io_write_bytes - last_write)) / (((double)(now - process->io_rate_write_time)) / 1000);
process->io_rate_write_time = now;
}
break;
case 's':
- if (line[4] == 'r' && strncmp(line+1, "yscr: ", 6) == 0) {
- process->io_syscr = strtoull(line+7, NULL, 10);
- } else if (strncmp(line+1, "yscw: ", 6) == 0) {
- process->io_syscw = strtoull(line+7, NULL, 10);
+ if (line[4] == 'r' && String_startsWith(line + 1, "yscr: ")) {
+ process->io_syscr = strtoull(line + 7, NULL, 10);
+ } else if (String_startsWith(line + 1, "yscw: ")) {
+ process->io_syscw = strtoull(line + 7, NULL, 10);
}
break;
case 'c':
- if (strncmp(line+1, "ancelled_write_bytes: ", 22) == 0) {
- process->io_cancelled_write_bytes = strtoull(line+23, NULL, 10);
- }
+ if (String_startsWith(line + 1, "ancelled_write_bytes: ")) {
+ process->io_cancelled_write_bytes = strtoull(line + 23, NULL, 10);
+ }
}
}
}
-#endif
+typedef struct LibraryData_ {
+ uint64_t size;
+ bool exec;
+} LibraryData;
+
+static inline uint64_t fast_strtoull_dec(char **str, int maxlen) {
+ register uint64_t result = 0;
+
+ if (!maxlen)
+ --maxlen;
+
+ while (maxlen-- && **str >= '0' && **str <= '9') {
+ result *= 10;
+ result += **str - '0';
+ (*str)++;
+ }
+
+ return result;
+}
+
+static inline uint64_t fast_strtoull_hex(char **str, int maxlen) {
+ register uint64_t result = 0;
+ register int nibble, letter;
+ const long valid_mask = 0x03FF007E;
+
+ if (!maxlen)
+ --maxlen;
+
+ while (maxlen--) {
+ nibble = (unsigned char)**str;
+ if (!(valid_mask & (1 << (nibble & 0x1F))))
+ break;
+ if ((nibble < '0') || (nibble & ~0x20) > 'F')
+ break;
+ letter = (nibble & 0x40) ? 'A' - '9' - 1 : 0;
+ nibble &=~0x20; // to upper
+ nibble ^= 0x10; // switch letters and digits
+ nibble -= letter;
+ nibble &= 0x0f;
+ result <<= 4;
+ result += (uint64_t)nibble;
+ (*str)++;
+ }
+
+ return result;
+}
+
+static void LinuxProcessList_calcLibSize_helper(ATTR_UNUSED hkey_t key, void* value, void* data) {
+ if (!data)
+ return;
+
+ if (!value)
+ return;
+
+ LibraryData* v = (LibraryData *)value;
+ uint64_t* d = (uint64_t *)data;
+ if (!v->exec)
+ return;
+
+ *d += v->size;
+}
+
+static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) {
+ FILE* mapsfile = fopenat(procFd, "maps", "r");
+ if (!mapsfile)
+ return 0;
+ Hashtable* ht = Hashtable_new(64, true);
+ char buffer[1024];
+ while (fgets(buffer, sizeof(buffer), mapsfile)) {
+ uint64_t map_start;
+ uint64_t map_end;
+ char map_perm[5];
+ 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;
+
+ memcpy(map_perm, readptr, 4);
+ map_perm[4] = '\0';
+ 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;
-static bool LinuxProcessList_readStatmFile(LinuxProcess* process, const char* dirname, const char* name) {
- char filename[MAX_NAME+1];
- xSnprintf(filename, MAX_NAME, "%s/%s/statm", dirname, name);
- int fd = open(filename, O_RDONLY);
- if (fd == -1)
+ //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;
+
+ 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 |= 'x' == map_perm[2];
+ }
+
+ fclose(mapsfile);
+
+ uint64_t total_size = 0;
+ Hashtable_foreach(ht, LinuxProcessList_calcLibSize_helper, &total_size);
+
+ Hashtable_delete(ht);
+
+ return total_size / CRT_pageSize;
+}
+
+static bool LinuxProcessList_readStatmFile(LinuxProcess* process, openat_arg_t procFd, bool performLookup, unsigned long long now) {
+ FILE* statmfile = fopenat(procFd, "statm", "r");
+ if (!statmfile)
return false;
- char buf[PROC_LINE_LENGTH + 1];
- ssize_t rres = xread(fd, buf, PROC_LINE_LENGTH);
- close(fd);
- if (rres < 1) return false;
-
- char *p = buf;
- errno = 0;
- process->super.m_size = strtol(p, &p, 10); if (*p == ' ') p++;
- process->super.m_resident = strtol(p, &p, 10); if (*p == ' ') p++;
- process->m_share = strtol(p, &p, 10); if (*p == ' ') p++;
- process->m_trs = strtol(p, &p, 10); if (*p == ' ') p++;
- process->m_lrs = strtol(p, &p, 10); if (*p == ' ') p++;
- process->m_drs = strtol(p, &p, 10); if (*p == ' ') p++;
- process->m_dt = strtol(p, &p, 10);
- return (errno == 0);
+
+ long tmp_m_lrs = 0;
+ int r = fscanf(statmfile, "%ld %ld %ld %ld %ld %ld %ld",
+ &process->super.m_virt,
+ &process->super.m_resident,
+ &process->m_share,
+ &process->m_trs,
+ &tmp_m_lrs,
+ &process->m_drs,
+ &process->m_dt);
+ fclose(statmfile);
+
+ if (r == 7) {
+ if (tmp_m_lrs) {
+ process->m_lrs = tmp_m_lrs;
+ } else if (performLookup) {
+ // Check if we really should recalculate the M_LRS value for this process
+ uint64_t passedTimeInMs = now - process->last_mlrs_calctime;
+
+ uint64_t recheck = ((uint64_t)rand()) % 2048;
+
+ if(passedTimeInMs > 2000 || passedTimeInMs > recheck) {
+ process->last_mlrs_calctime = now;
+ process->m_lrs = LinuxProcessList_calcLibSize(procFd);
+ }
+ } else {
+ // Keep previous value
+ }
+ }
+
+ return r == 7;
}
-static bool LinuxProcessList_readSmapsFile(LinuxProcess* process, const char* dirname, const char* name, bool haveSmapsRollup) {
+static bool LinuxProcessList_readSmapsFile(LinuxProcess* process, openat_arg_t procFd, bool haveSmapsRollup) {
//http://elixir.free-electrons.com/linux/v4.10/source/fs/proc/task_mmu.c#L719
//kernel will return data in chunks of size PAGE_SIZE or less.
-
- char buffer[PAGE_SIZE];// 4k
- char *start,*end;
- ssize_t nread=0;
- int tmp=0;
- if(haveSmapsRollup) {// only available in Linux 4.14+
- snprintf(buffer, PAGE_SIZE-1, "%s/%s/smaps_rollup", dirname, name);
- } else {
- snprintf(buffer, PAGE_SIZE-1, "%s/%s/smaps", dirname, name);
- }
- int fd = open(buffer, O_RDONLY);
- if (fd == -1)
+ 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;
- while ( ( nread = read(fd,buffer, sizeof(buffer)) ) > 0 ){
- start = (char *)&buffer;
- end = start + nread;
- do{//parse 4k block
-
- if( (tmp = (end - start)) > 0 &&
- (start = memmem(start,tmp,"\nPss:",5)) != NULL )
- {
- process->m_pss += strtol(start+5, &start, 10);
- start += 3;//now we must be at the end of line "Pss: 0 kB"
- }else
- break; //read next 4k block
-
- if( (tmp = (end - start)) > 0 &&
- (start = memmem(start,tmp,"\nSwap:",6)) != NULL )
- {
- process->m_swap += strtol(start+6, &start, 10);
- start += 3;
- }else
- break;
-
- if( (tmp = (end - start)) > 0 &&
- (start = memmem(start,tmp,"\nSwapPss:",9)) != NULL )
- {
- process->m_psswp += strtol(start+9, &start, 10);
- start += 3;
- }else
- break;
-
- }while(1);
- }//while read
- close(fd);
+ char buffer[256];
+ while (fgets(buffer, sizeof(buffer), f)) {
+ if (!strchr(buffer, '\n')) {
+ // Partial line, skip to end of this line
+ while (fgets(buffer, sizeof(buffer), f)) {
+ if (strchr(buffer, '\n')) {
+ break;
+ }
+ }
+ continue;
+ }
+
+ if (String_startsWith(buffer, "Pss:")) {
+ process->m_pss += strtol(buffer + 4, NULL, 10);
+ } else if (String_startsWith(buffer, "Swap:")) {
+ process->m_swap += strtol(buffer + 5, NULL, 10);
+ } else if (String_startsWith(buffer, "SwapPss:")) {
+ process->m_psswp += strtol(buffer + 8, NULL, 10);
+ }
+ }
+
+ fclose(f);
return true;
}
#ifdef HAVE_OPENVZ
-static void LinuxProcessList_readOpenVZData(LinuxProcess* process, const char* dirname, const char* name) {
- if ( (access("/proc/vz", R_OK) != 0)) {
+static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) {
+ if ( (access(PROCDIR "/vz", R_OK) != 0)) {
+ free(process->ctid);
+ process->ctid = NULL;
process->vpid = process->super.pid;
- process->ctid = 0;
return;
}
- char filename[MAX_NAME+1];
- xSnprintf(filename, MAX_NAME, "%s/%s/stat", dirname, name);
- FILE* file = fopen(filename, "r");
- if (!file)
+
+ FILE* file = fopenat(procFd, "status", "r");
+ if (!file) {
+ free(process->ctid);
+ process->ctid = NULL;
+ process->vpid = process->super.pid;
return;
- (void)! fscanf(file,
- "%*32u %*32s %*1c %*32u %*32u %*32u %*32u %*32u %*32u %*32u "
- "%*32u %*32u %*32u %*32u %*32u %*32u %*32u %*32u "
- "%*32u %*32u %*32u %*32u %*32u %*32u %*32u %*32u "
- "%*32u %*32u %*32u %*32u %*32u %*32u %*32u %*32u "
- "%*32u %*32u %*32u %*32u %*32u %*32u %*32u %*32u "
- "%*32u %*32u %*32u %*32u %*32u %*32u %*32u "
- "%*32u %*32u %32u %32u",
- &process->vpid, &process->ctid);
+ }
+
+ 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(process->ctid);
+ process->ctid = xStrdup(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);
- return;
+
+ if (!foundEnvID) {
+ free(process->ctid);
+ process->ctid = NULL;
+ }
+
+ if (!foundVPid) {
+ process->vpid = process->super.pid;
+ }
}
#endif
-#ifdef HAVE_CGROUP
-
-static void LinuxProcessList_readCGroupFile(LinuxProcess* process, const char* dirname, const char* name) {
- char filename[MAX_NAME+1];
- xSnprintf(filename, MAX_NAME, "%s/%s/cgroup", dirname, name);
- FILE* file = fopen(filename, "r");
+static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "cgroup", "r");
if (!file) {
- process->cgroup = xStrdup("");
+ if (process->cgroup) {
+ free(process->cgroup);
+ process->cgroup = NULL;
+ }
return;
}
char output[PROC_LINE_LENGTH + 1];
@@ -516,10 +758,14 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, const char* d
int left = PROC_LINE_LENGTH;
while (!feof(file) && left > 0) {
char buffer[PROC_LINE_LENGTH + 1];
- char *ok = fgets(buffer, PROC_LINE_LENGTH, file);
- if (!ok) break;
+ char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
+ if (!ok)
+ break;
+
char* group = strchr(buffer, ':');
- if (!group) break;
+ if (!group)
+ break;
+
if (at != output) {
*at = ';';
at++;
@@ -533,16 +779,13 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, const char* d
process->cgroup = xStrdup(output);
}
-#endif
-
#ifdef HAVE_VSERVER
-static void LinuxProcessList_readVServerData(LinuxProcess* process, const char* dirname, const char* name) {
- char filename[MAX_NAME+1];
- xSnprintf(filename, MAX_NAME, "%s/%s/status", dirname, name);
- FILE* file = fopen(filename, "r");
+static void LinuxProcessList_readVServerData(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "status", "r");
if (!file)
return;
+
char buffer[PROC_LINE_LENGTH + 1];
process->vxid = 0;
while (fgets(buffer, PROC_LINE_LENGTH, file)) {
@@ -568,13 +811,11 @@ static void LinuxProcessList_readVServerData(LinuxProcess* process, const char*
#endif
-static void LinuxProcessList_readOomData(LinuxProcess* process, const char* dirname, const char* name) {
- char filename[MAX_NAME+1];
- xSnprintf(filename, MAX_NAME, "%s/%s/oom_score", dirname, name);
- FILE* file = fopen(filename, "r");
- if (!file) {
+static void LinuxProcessList_readOomData(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "oom_score", "r");
+ if (!file)
return;
- }
+
char buffer[PROC_LINE_LENGTH + 1];
if (fgets(buffer, PROC_LINE_LENGTH, file)) {
unsigned int oom;
@@ -586,15 +827,94 @@ static void LinuxProcessList_readOomData(LinuxProcess* process, const char* dirn
fclose(file);
}
+static void LinuxProcessList_readCtxtData(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "status", "r");
+ if (!file)
+ return;
+
+ char buffer[PROC_LINE_LENGTH + 1];
+ unsigned long ctxt = 0;
+ while (fgets(buffer, PROC_LINE_LENGTH, file)) {
+ if (String_startsWith(buffer, "voluntary_ctxt_switches:")) {
+ unsigned long vctxt;
+ int ok = sscanf(buffer, "voluntary_ctxt_switches:\t%lu", &vctxt);
+ if (ok >= 1) {
+ ctxt += vctxt;
+ }
+ } else if (String_startsWith(buffer, "nonvoluntary_ctxt_switches:")) {
+ unsigned long nvctxt;
+ int ok = sscanf(buffer, "nonvoluntary_ctxt_switches:\t%lu", &nvctxt);
+ if (ok >= 1) {
+ ctxt += nvctxt;
+ }
+ }
+ }
+ fclose(file);
+ process->ctxt_diff = (ctxt > process->ctxt_total) ? (ctxt - process->ctxt_total) : 0;
+ process->ctxt_total = ctxt;
+}
+
+static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t procFd) {
+ FILE* file = fopenat(procFd, "attr/current", "r");
+ if (!file) {
+ free(process->secattr);
+ process->secattr = NULL;
+ return;
+ }
+
+ char buffer[PROC_LINE_LENGTH + 1];
+ char* res = fgets(buffer, sizeof(buffer), file);
+ fclose(file);
+ if (!res) {
+ free(process->secattr);
+ process->secattr = NULL;
+ return;
+ }
+ char* newline = strchr(buffer, '\n');
+ if (newline) {
+ *newline = '\0';
+ }
+ if (process->secattr && String_eq(process->secattr, buffer)) {
+ return;
+ }
+ free(process->secattr);
+ process->secattr = xStrdup(buffer);
+}
+
+static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd) {
+ char pathBuffer[PATH_MAX + 1] = {0};
+
+#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)
+ ssize_t r = readlinkat(procFd, "cwd", pathBuffer, sizeof(pathBuffer) - 1);
+#else
+ char filename[MAX_NAME + 1];
+ xSnprintf(filename, sizeof(filename), "%s/cwd", procFd);
+ ssize_t r = readlink(filename, pathBuffer, sizeof(pathBuffer) - 1);
+#endif
+
+ if (r < 0) {
+ free(process->cwd);
+ process->cwd = NULL;
+ return;
+ }
+
+ pathBuffer[r] = '\0';
+
+ if (process->cwd && String_eq(process->cwd, pathBuffer))
+ return;
+
+ free(process->cwd);
+ process->cwd = xStrdup(pathBuffer);
+}
+
#ifdef HAVE_DELAYACCT
-static int handleNetlinkMsg(struct nl_msg *nlmsg, void *linuxProcess) {
- struct nlmsghdr *nlhdr;
- struct nlattr *nlattrs[TASKSTATS_TYPE_MAX + 1];
- struct nlattr *nlattr;
- struct taskstats *stats;
+static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {
+ struct nlmsghdr* nlhdr;
+ struct nlattr* nlattrs[TASKSTATS_TYPE_MAX + 1];
+ struct nlattr* nlattr;
+ struct taskstats stats;
int rem;
- unsigned long long int timeDelta;
LinuxProcess* lp = (LinuxProcess*) linuxProcess;
nlhdr = nlmsg_hdr(nlmsg);
@@ -604,26 +924,28 @@ static int handleNetlinkMsg(struct nl_msg *nlmsg, void *linuxProcess) {
}
if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) {
- stats = nla_data(nla_next(nla_data(nlattr), &rem));
- assert(lp->super.pid == stats->ac_pid);
- timeDelta = (stats->ac_etime*1000 - lp->delay_read_time);
- #define BOUNDS(x) isnan(x) ? 0.0 : (x > 100) ? 100.0 : x;
- #define DELTAPERC(x,y) BOUNDS((float) (x - y) / timeDelta * 100);
- lp->cpu_delay_percent = DELTAPERC(stats->cpu_delay_total, lp->cpu_delay_total);
- lp->blkio_delay_percent = DELTAPERC(stats->blkio_delay_total, lp->blkio_delay_total);
- lp->swapin_delay_percent = DELTAPERC(stats->swapin_delay_total, lp->swapin_delay_total);
+ memcpy(&stats, nla_data(nla_next(nla_data(nlattr), &rem)), sizeof(stats));
+ assert(lp->super.pid == (pid_t)stats.ac_pid);
+
+ unsigned long long int timeDelta = stats.ac_etime * 1000 - lp->delay_read_time;
+ #define BOUNDS(x) (isnan(x) ? 0.0 : ((x) > 100) ? 100.0 : (x))
+ #define DELTAPERC(x,y) BOUNDS((float) ((x) - (y)) / timeDelta * 100)
+ lp->cpu_delay_percent = DELTAPERC(stats.cpu_delay_total, lp->cpu_delay_total);
+ lp->blkio_delay_percent = DELTAPERC(stats.blkio_delay_total, lp->blkio_delay_total);
+ lp->swapin_delay_percent = DELTAPERC(stats.swapin_delay_total, lp->swapin_delay_total);
#undef DELTAPERC
#undef BOUNDS
- lp->swapin_delay_total = stats->swapin_delay_total;
- lp->blkio_delay_total = stats->blkio_delay_total;
- lp->cpu_delay_total = stats->cpu_delay_total;
- lp->delay_read_time = stats->ac_etime*1000;
+
+ lp->swapin_delay_total = stats.swapin_delay_total;
+ lp->blkio_delay_total = stats.blkio_delay_total;
+ lp->cpu_delay_total = stats.cpu_delay_total;
+ lp->delay_read_time = stats.ac_etime * 1000;
}
return NL_OK;
}
static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProcess* process) {
- struct nl_msg *msg;
+ struct nl_msg* msg;
if (nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) {
return;
@@ -642,9 +964,9 @@ static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProc
}
if (nl_send_sync(this->netlink_socket, msg) < 0) {
- process->swapin_delay_percent = -1LL;
- process->blkio_delay_percent = -1LL;
- process->cpu_delay_percent = -1LL;
+ process->swapin_delay_percent = NAN;
+ process->blkio_delay_percent = NAN;
+ process->cpu_delay_percent = NAN;
return;
}
@@ -665,18 +987,12 @@ static void setCommand(Process* process, const char* command, int len) {
process->commLen = len;
}
-static bool LinuxProcessList_readCmdlineFile(Process* process, const char* dirname, const char* name) {
- char filename[MAX_NAME+1];
- xSnprintf(filename, MAX_NAME, "%s/%s/cmdline", dirname, name);
- int fd = open(filename, O_RDONLY);
- if (fd == -1)
+static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t procFd) {
+ char command[4096 + 1]; // max cmdline length on Linux
+ ssize_t amtRead = xReadfileat(procFd, "cmdline", command, sizeof(command));
+ if (amtRead < 0)
return false;
- char command[4096+1]; // max cmdline length on Linux
- int amtRead = xread(fd, command, sizeof(command) - 1);
- close(fd);
- int tokenEnd = 0;
- int lastChar = 0;
if (amtRead == 0) {
if (process->state == 'Z') {
process->basenameOffset = 0;
@@ -684,25 +1000,183 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, const char* dirna
((LinuxProcess*)process)->isKernelThread = true;
}
return true;
- } else 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++) {
- if (command[i] == '\0' || command[i] == '\n') {
+ /* newline used as delimiter - when forming the mergedCommand, newline is
+ * converted to space by LinuxProcess_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;
}
- command[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;
+ }
+ }
+ }
+ }
+ }
+ }
+
if (tokenEnd == 0) {
- tokenEnd = amtRead;
+ tokenEnd = lastChar + 1;
+ }
+
+ LinuxProcess *lp = (LinuxProcess *)process;
+ lp->mergedCommand.maxLen = lastChar + 1; /* accommodate cmdline */
+ if (!process->comm || !String_eq(command, process->comm)) {
+ process->basenameOffset = tokenEnd;
+ setCommand(process, command, lastChar + 1);
+ lp->procCmdlineBasenameOffset = tokenStart;
+ lp->procCmdlineBasenameEnd = tokenEnd;
+ lp->mergedCommand.cmdlineChanged = true;
+ }
+
+ /* /proc/[pid]/comm could change, so should be updated */
+ if ((amtRead = xReadfileat(procFd, "comm", command, sizeof(command))) > 0) {
+ command[amtRead - 1] = '\0';
+ lp->mergedCommand.maxLen += amtRead - 1; /* accommodate comm */
+ if (!lp->procComm || !String_eq(command, lp->procComm)) {
+ free(lp->procComm);
+ lp->procComm = xStrdup(command);
+ lp->mergedCommand.commChanged = true;
+ }
+ } else if (lp->procComm) {
+ free(lp->procComm);
+ lp->procComm = NULL;
+ lp->mergedCommand.commChanged = true;
+ }
+
+ char filename[MAX_NAME + 1];
+
+ /* execve could change /proc/[pid]/exe, so procExe should be updated */
+#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)
+ amtRead = readlinkat(procFd, "exe", filename, sizeof(filename) - 1);
+#else
+ char path[4096];
+ xSnprintf(path, sizeof(path), "%s/exe", procFd);
+ amtRead = readlink(path, filename, sizeof(filename) - 1);
+#endif
+ if (amtRead > 0) {
+ filename[amtRead] = 0;
+ lp->mergedCommand.maxLen += amtRead; /* accommodate exe */
+ if (!lp->procExe || !String_eq(filename, lp->procExe)) {
+ free(lp->procExe);
+ lp->procExe = xStrdup(filename);
+ lp->procExeLen = amtRead;
+ /* exe is guaranteed to contain at least one /, but validate anyway */
+ while (amtRead && filename[--amtRead] != '/')
+ ;
+ lp->procExeBasenameOffset = amtRead + 1;
+ lp->mergedCommand.exeChanged = true;
+
+ const char* deletedMarker = " (deleted)";
+ if (strlen(lp->procExe) > strlen(deletedMarker)) {
+ lp->procExeDeleted = String_eq(lp->procExe + strlen(lp->procExe) - strlen(deletedMarker), deletedMarker);
+
+ if (lp->procExeDeleted && strlen(lp->procExe) - strlen(deletedMarker) == 1 && lp->procExe[0] == '/') {
+ lp->procExeBasenameOffset = 0;
+ }
+ }
+ }
+ } else if (lp->procExe) {
+ free(lp->procExe);
+ lp->procExe = NULL;
+ lp->procExeLen = 0;
+ lp->procExeBasenameOffset = 0;
+ lp->procExeDeleted = false;
+ lp->mergedCommand.exeChanged = true;
}
- command[lastChar + 1] = '\0';
- process->basenameOffset = tokenEnd;
- setCommand(process, command, lastChar + 1);
return true;
}
@@ -729,47 +1203,71 @@ static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned in
unsigned int idx = min - ttyDrivers[i].minorFrom;
struct stat sstat;
char* fullPath;
- for(;;) {
+ 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;
+ 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;
+ if (err == 0 && major(sstat.st_rdev) == maj && minor(sstat.st_rdev) == min) {
+ return fullPath;
+ }
free(fullPath);
- if (idx == min) break;
+
+ if (idx == min) {
+ break;
+ }
+
idx = min;
}
int err = stat(ttyDrivers[i].path, &sstat);
- if (err == 0 && tty_nr == sstat.st_rdev) return strdup(ttyDrivers[i].path);
+ if (err == 0 && tty_nr == sstat.st_rdev) {
+ return xStrdup(ttyDrivers[i].path);
+ }
}
char* out;
xAsprintf(&out, "/dev/%u:%u", maj, min);
return out;
}
-static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, const char* dirname, Process* parent, double period, struct timeval tv) {
+static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const char* dirname, const Process* parent, double period, unsigned long long now) {
ProcessList* pl = (ProcessList*) this;
- DIR* dir;
- struct dirent* entry;
- Settings* settings = pl->settings;
+ const struct dirent* entry;
+ const Settings* settings = pl->settings;
- #ifdef HAVE_TASKSTATS
- unsigned long long now = tv.tv_sec*1000LL+tv.tv_usec/1000LL;
- #endif
+#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;
+ }
- dir = opendir(dirname);
- if (!dir) return false;
int cpus = pl->cpuCount;
bool hideKernelThreads = settings->hideKernelThreads;
bool hideUserlandThreads = settings->hideUserlandThreads;
while ((entry = readdir(dir)) != NULL) {
- char* name = entry->d_name;
+ 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 ((!settings->hideThreads) && name[0] == '.') {
+ if (name[0] == '.') {
name++;
}
@@ -781,107 +1279,160 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, const char*
// filename is a number: process directory
int pid = atoi(name);
- if (parent && pid == parent->pid)
+ if (pid <= 0)
continue;
- if (pid <= 0)
+ if (parent && pid == parent->pid)
continue;
- bool preExisting = false;
- Process* proc = ProcessList_getProcess(pl, pid, &preExisting, (Process_New) LinuxProcess_new);
+ bool preExisting;
+ Process* proc = ProcessList_getProcess(pl, pid, &preExisting, LinuxProcess_new);
+ LinuxProcess* lp = (LinuxProcess*) proc;
+
proc->tgid = parent ? parent->pid : pid;
- LinuxProcess* lp = (LinuxProcess*) proc;
+#ifdef HAVE_OPENAT
+ int procFd = openat(dirFd, entry->d_name, O_PATH | O_DIRECTORY | O_NOFOLLOW);
+ if (procFd < 0)
+ goto errorReadingProcess;
+#else
+ char procFd[4096];
+ xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name);
+#endif
- char subdirname[MAX_NAME+1];
- xSnprintf(subdirname, MAX_NAME, "%s/%s/task", dirname, name);
- LinuxProcessList_recurseProcTree(this, subdirname, proc, period, tv);
+ LinuxProcessList_recurseProcTree(this, procFd, "task", proc, period, now);
+
+ /*
+ * These conditions will not trigger on first occurrence, cause we need to
+ * add the process to the ProcessList and do all one time scans
+ * (e.g. parsing the cmdline to detect a kernel thread)
+ * But it will short-circuit subsequent scans.
+ */
+ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {
+ proc->updated = true;
+ proc->show = false;
+ pl->kernelThreads++;
+ pl->totalTasks++;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
+ if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
+ proc->updated = true;
+ proc->show = false;
+ pl->userlandThreads++;
+ pl->totalTasks++;
+ Compat_openatArgClose(procFd);
+ continue;
+ }
- #ifdef HAVE_TASKSTATS
if (settings->flags & PROCESS_FLAG_IO)
- LinuxProcessList_readIoFile(lp, dirname, name, now);
- #endif
+ LinuxProcessList_readIoFile(lp, procFd, now);
- if (! LinuxProcessList_readStatmFile(lp, dirname, name))
+ if (!LinuxProcessList_readStatmFile(lp, procFd, !!(settings->flags & PROCESS_FLAG_LINUX_LRS_FIX), now))
goto errorReadingProcess;
- if ((settings->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)){
- if (!parent){
+ if ((settings->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
+ if (!parent) {
// Read smaps file of each process only every second pass to improve performance
static int smaps_flag = 0;
- if ((pid & 1) == smaps_flag){
- LinuxProcessList_readSmapsFile(lp, dirname, name, this->haveSmapsRollup);
+ if ((pid & 1) == smaps_flag) {
+ LinuxProcessList_readSmapsFile(lp, procFd, this->haveSmapsRollup);
}
if (pid == 1) {
smaps_flag = !smaps_flag;
}
- } else {
- lp->m_pss = ((LinuxProcess*)parent)->m_pss;
- }
+ } else {
+ lp->m_pss = ((const LinuxProcess*)parent)->m_pss;
+ }
}
- proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
-
- char command[MAX_NAME+1];
+ char command[MAX_NAME + 1];
unsigned long long int lasttimes = (lp->utime + lp->stime);
- int commLen = 0;
+ int commLen = sizeof(command);
unsigned int tty_nr = proc->tty_nr;
- if (! LinuxProcessList_readStatFile(proc, dirname, name, command, &commLen))
+ if (! LinuxProcessList_readStatFile(proc, procFd, command, &commLen))
goto errorReadingProcess;
+
if (tty_nr != proc->tty_nr && this->ttyDrivers) {
free(lp->ttyDevice);
lp->ttyDevice = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
}
- if (settings->flags & PROCESS_FLAG_LINUX_IOPRIO)
+
+ if (settings->flags & PROCESS_FLAG_LINUX_IOPRIO) {
LinuxProcess_updateIOPriority(lp);
- float percent_cpu = (lp->utime + lp->stime - lasttimes) / period * 100.0;
- proc->percent_cpu = CLAMP(percent_cpu, 0.0, cpus * 100.0);
- if (isnan(proc->percent_cpu)) proc->percent_cpu = 0.0;
- proc->percent_mem = (proc->m_resident * PAGE_SIZE_KB) / (double)(pl->totalMem) * 100.0;
+ }
+
+ /* period might be 0 after system sleep */
+ float percent_cpu = (period < 1e-6) ? 0.0f : ((lp->utime + lp->stime - lasttimes) / period * 100.0);
+ proc->percent_cpu = CLAMP(percent_cpu, 0.0f, cpus * 100.0f);
+ proc->percent_mem = (proc->m_resident * CRT_pageSizeKB) / (double)(pl->totalMem) * 100.0;
- if(!preExisting) {
+ if (!preExisting) {
- if (! LinuxProcessList_statProcessDir(proc, dirname, name))
+ if (! LinuxProcessList_statProcessDir(proc, procFd))
goto errorReadingProcess;
proc->user = UsersTable_getRef(pl->usersTable, proc->st_uid);
#ifdef HAVE_OPENVZ
if (settings->flags & PROCESS_FLAG_LINUX_OPENVZ) {
- LinuxProcessList_readOpenVZData(lp, dirname, name);
+ LinuxProcessList_readOpenVZData(lp, procFd);
}
#endif
#ifdef HAVE_VSERVER
if (settings->flags & PROCESS_FLAG_LINUX_VSERVER) {
- LinuxProcessList_readVServerData(lp, dirname, name);
+ LinuxProcessList_readVServerData(lp, procFd);
}
#endif
- if (! LinuxProcessList_readCmdlineFile(proc, dirname, name)) {
+ if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
goto errorReadingProcess;
}
+ Process_fillStarttimeBuffer(proc);
+
ProcessList_add(pl, proc);
} else {
if (settings->updateProcessNames && proc->state != 'Z') {
- if (! LinuxProcessList_readCmdlineFile(proc, dirname, name)) {
+ if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
goto errorReadingProcess;
}
}
}
+ /* (Re)Generate the Command string, but only if the process is:
+ * - not a kernel thread, and
+ * - not a zombie or it became zombie under htop's watch, and
+ * - not a user thread or if showThreadNames is not set */
+ if (!Process_isKernelThread(proc) &&
+ (proc->state != 'Z' || lp->mergedCommand.str) &&
+ (!Process_isUserlandThread(proc) || !settings->showThreadNames)) {
+ LinuxProcess_makeCommandStr(proc);
+ }
#ifdef HAVE_DELAYACCT
LinuxProcessList_readDelayAcctData(this, lp);
#endif
- #ifdef HAVE_CGROUP
- if (settings->flags & PROCESS_FLAG_LINUX_CGROUP)
- LinuxProcessList_readCGroupFile(lp, dirname, name);
- #endif
+ if (settings->flags & PROCESS_FLAG_LINUX_CGROUP) {
+ LinuxProcessList_readCGroupFile(lp, procFd);
+ }
+
+ if (settings->flags & PROCESS_FLAG_LINUX_OOM) {
+ LinuxProcessList_readOomData(lp, procFd);
+ }
- if (settings->flags & PROCESS_FLAG_LINUX_OOM)
- LinuxProcessList_readOomData(lp, dirname, name);
+ if (settings->flags & PROCESS_FLAG_LINUX_CTXT) {
+ LinuxProcessList_readCtxtData(lp, procFd);
+ }
+
+ if (settings->flags & PROCESS_FLAG_LINUX_SECATTR) {
+ LinuxProcessList_readSecattrData(lp, procFd);
+ }
+
+ if (settings->flags & PROCESS_FLAG_LINUX_CWD) {
+ LinuxProcessList_readCwd(lp, procFd);
+ }
if (proc->state == 'Z' && (proc->basenameOffset == 0)) {
proc->basenameOffset = -1;
@@ -891,8 +1442,9 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, const char*
proc->basenameOffset = -1;
setCommand(proc, command, commLen);
} else if (settings->showThreadNames) {
- if (! LinuxProcessList_readCmdlineFile(proc, dirname, name))
+ if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
goto errorReadingProcess;
+ }
}
if (Process_isKernelThread(proc)) {
pl->kernelThreads++;
@@ -901,14 +1453,25 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, const char*
}
}
+ /* Set at the end when we know if a new entry is a thread */
+ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
+
pl->totalTasks++;
if (proc->state == 'R')
pl->runningTasks++;
proc->updated = true;
+ Compat_openatArgClose(procFd);
continue;
// Exception handler.
- errorReadingProcess: {
+
+errorReadingProcess:
+ {
+#ifdef HAVE_OPENAT
+ if (procFd >= 0)
+ close(procFd);
+#endif
+
if (preExisting) {
ProcessList_remove(pl, proc);
} else {
@@ -921,6 +1484,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, const char*
}
static inline void LinuxProcessList_scanMemoryInfo(ProcessList* this) {
+ unsigned long long int freeMem = 0;
unsigned long long int swapFree = 0;
unsigned long long int shmem = 0;
unsigned long long int sreclaimable = 0;
@@ -932,12 +1496,16 @@ static inline void LinuxProcessList_scanMemoryInfo(ProcessList* this) {
char buffer[128];
while (fgets(buffer, 128, file)) {
- #define tryRead(label, variable) do { if (String_startsWith(buffer, label) && sscanf(buffer + strlen(label), " %32llu kB", variable)) { break; } } while(0)
+ #define tryRead(label, variable) \
+ if (String_startsWith(buffer, label)) { \
+ sscanf(buffer + strlen(label), " %32llu kB", variable); \
+ break; \
+ }
+
switch (buffer[0]) {
case 'M':
tryRead("MemTotal:", &this->totalMem);
- tryRead("MemFree:", &this->freeMem);
- tryRead("MemShared:", &this->sharedMem);
+ tryRead("MemFree:", &freeMem);
break;
case 'B':
tryRead("Buffers:", &this->buffersMem);
@@ -963,12 +1531,60 @@ static inline void LinuxProcessList_scanMemoryInfo(ProcessList* this) {
#undef tryRead
}
- this->usedMem = this->totalMem - this->freeMem;
+ this->usedMem = this->totalMem - freeMem;
this->cachedMem = this->cachedMem + sreclaimable - shmem;
this->usedSwap = this->totalSwap - swapFree;
fclose(file);
}
+static inline void LinuxProcessList_scanZramInfo(LinuxProcessList* this) {
+ unsigned long long int totalZram = 0;
+ unsigned long long int usedZramComp = 0;
+ unsigned long long int usedZramOrig = 0;
+
+ 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;
+ }
+ unsigned long long int size = 0;
+ unsigned long long int orig_data_size = 0;
+ unsigned long long int compr_data_size = 0;
+
+ if (!fscanf(disksize_file, "%llu\n", &size) ||
+ !fscanf(mm_stat_file, " %llu %llu", &orig_data_size, &compr_data_size)) {
+ fclose(disksize_file);
+ fclose(mm_stat_file);
+ break;
+ }
+
+ totalZram += size;
+ usedZramComp += compr_data_size;
+ usedZramOrig += orig_data_size;
+
+ fclose(disksize_file);
+ fclose(mm_stat_file);
+ }
+
+ this->zram.totalZram = totalZram / 1024;
+ this->zram.usedZramComp = usedZramComp / 1024;
+ this->zram.usedZramOrig = usedZramOrig / 1024;
+}
+
static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
unsigned long long int dbufSize = 0;
unsigned long long int dnodeSize = 0;
@@ -981,8 +1597,17 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
}
char buffer[128];
while (fgets(buffer, 128, file)) {
- #define tryRead(label, variable) do { if (String_startsWith(buffer, label) && sscanf(buffer + strlen(label), " %*2u %32llu", variable)) { break; } } while(0)
- #define tryReadFlag(label, variable, flag) do { if (String_startsWith(buffer, label) && sscanf(buffer + strlen(label), " %*2u %32llu", variable)) { flag = 1; break; } else { flag = 0; } } while(0)
+ #define tryRead(label, variable) \
+ if (String_startsWith(buffer, label)) { \
+ sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
+ break; \
+ }
+ #define tryReadFlag(label, variable, flag) \
+ if (String_startsWith(buffer, label)) { \
+ (flag) = sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
+ break; \
+ }
+
switch (buffer[0]) {
case 'c':
tryRead("c_max", &lpl->zfs.max);
@@ -1048,10 +1673,13 @@ static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) {
// 5, 7, 8 or 9 of these fields will be set.
// The rest will remain at zero.
char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
- if (!ok) buffer[0] = '\0';
- if (i == 0)
+ if (!ok) {
+ buffer[0] = '\0';
+ }
+
+ 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);
- else {
+ } else {
int cpuid;
(void) sscanf(buffer, "cpu%4d %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
assert(cpuid == i - 1);
@@ -1069,7 +1697,7 @@ static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) {
// Since we do a subtraction (usertime - guest) and cputime64_to_clock_t()
// used in /proc/stat rounds down numbers, it can lead to a case where the
// integer overflow.
- #define WRAP_SUBTRACT(a,b) (a > b) ? a - b : 0
+ #define WRAP_SUBTRACT(a,b) (((a) > (b)) ? (a) - (b) : 0)
cpuData->userPeriod = WRAP_SUBTRACT(usertime, cpuData->userTime);
cpuData->nicePeriod = WRAP_SUBTRACT(nicetime, cpuData->niceTime);
cpuData->systemPeriod = WRAP_SUBTRACT(systemtime, cpuData->systemTime);
@@ -1095,88 +1723,178 @@ static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) {
cpuData->stealTime = steal;
cpuData->guestTime = virtalltime;
cpuData->totalTime = totaltime;
-
}
+
double period = (double)this->cpus[0].totalPeriod / cpus;
fclose(file);
return period;
}
-static inline double LinuxProcessList_scanCPUFrequency(LinuxProcessList* this) {
- ProcessList* pl = (ProcessList*) this;
- Settings* settings = pl->settings;
-
+static int scanCPUFreqencyFromSysCPUFreq(LinuxProcessList* this) {
int cpus = this->super.cpuCount;
- assert(cpus > 0);
+ int numCPUsWithFrequency = 0;
+ unsigned long totalFrequency = 0;
+
+ for (int i = 0; i < cpus; ++i) {
+ char pathBuffer[64];
+ xSnprintf(pathBuffer, sizeof(pathBuffer), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i);
+
+ 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->cpus[i + 1].frequency = frequency;
+ numCPUsWithFrequency++;
+ totalFrequency += frequency;
+ }
- for (int i = 0; i <= cpus; i++) {
- CPUData* cpuData = &(this->cpus[i]);
- cpuData->frequency = -1;
+ fclose(file);
}
+ if (numCPUsWithFrequency > 0)
+ this->cpus[0].frequency = (double)totalFrequency / numCPUsWithFrequency;
+
+ return 0;
+}
+
+static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) {
+ FILE* file = fopen(PROCCPUINFOFILE, "r");
+ if (file == NULL)
+ return;
+
+ int cpus = this->super.cpuCount;
int numCPUsWithFrequency = 0;
double totalFrequency = 0;
+ int cpuid = -1;
- if (settings->showCPUFrequency) {
- FILE* file = fopen(PROCCPUINFOFILE, "r");
- if (file == NULL) {
- CRT_fatalError("Cannot open " PROCCPUINFOFILE);
- }
-
- int cpuid = -1;
+ while (!feof(file)) {
double frequency;
- while (!feof(file)) {
- char buffer[PROC_LINE_LENGTH];
- char *ok = fgets(buffer, PROC_LINE_LENGTH, file);
- if (!ok) break;
-
- if (
- (sscanf(buffer, "processor : %d", &cpuid) == 1) ||
- (sscanf(buffer, "processor: %d", &cpuid) == 1)
- ) {
- if (cpuid < 0 || cpuid > (cpus - 1)) {
- char tmpbuffer[64];
- xSnprintf(tmpbuffer, sizeof(tmpbuffer), PROCCPUINFOFILE " contains out-of-range CPU number %d", cpuid);
- CRT_fatalError(tmpbuffer);
- }
- } else if (
- (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
- (sscanf(buffer, "cpu MHz: %lf", &frequency) == 1)
- ) {
- if (cpuid < 0 || cpuid > (cpus - 1)) {
- CRT_fatalError(PROCCPUINFOFILE " is malformed: cpu MHz line without corresponding processor line");
- }
+ char buffer[PROC_LINE_LENGTH];
+
+ if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
+ break;
+
+ if (
+ (sscanf(buffer, "processor : %d", &cpuid) == 1) ||
+ (sscanf(buffer, "processor: %d", &cpuid) == 1)
+ ) {
+ continue;
+ } else if (
+ (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
+ (sscanf(buffer, "cpu MHz: %lf", &frequency) == 1)
+ ) {
+ if (cpuid < 0 || cpuid > (cpus - 1)) {
+ continue;
+ }
- int cpu = cpuid + 1;
- CPUData* cpuData = &(this->cpus[cpu]);
+ CPUData* cpuData = &(this->cpus[cpuid + 1]);
+ /* do not override sysfs data */
+ if (isnan(cpuData->frequency)) {
cpuData->frequency = frequency;
- numCPUsWithFrequency++;
- totalFrequency += frequency;
- } else if (buffer[0] == '\n') {
- cpuid = -1;
}
+ numCPUsWithFrequency++;
+ totalFrequency += frequency;
+ } else if (buffer[0] == '\n') {
+ cpuid = -1;
}
- fclose(file);
+ }
+ fclose(file);
+
+ if (numCPUsWithFrequency > 0) {
+ this->cpus[0].frequency = totalFrequency / numCPUsWithFrequency;
+ }
+}
+
+static void LinuxProcessList_scanCPUFrequency(LinuxProcessList* this) {
+ int cpus = this->super.cpuCount;
+ assert(cpus > 0);
- if (numCPUsWithFrequency > 0) {
- this->cpus[0].frequency = totalFrequency / numCPUsWithFrequency;
+ for (int i = 0; i <= cpus; i++) {
+ this->cpus[i].frequency = NAN;
+ }
+
+ if (scanCPUFreqencyFromSysCPUFreq(this) == 0) {
+ return;
+ }
+
+ scanCPUFreqencyFromCPUinfo(this);
+}
+
+#ifdef HAVE_SENSORS_SENSORS_H
+static void LinuxProcessList_scanCPUTemperature(LinuxProcessList* this) {
+ const int cpuCount = this->super.cpuCount;
+
+ for (int i = 0; i <= cpuCount; i++) {
+ this->cpus[i].temperature = NAN;
+ }
+
+ int r = LibSensors_getCPUTemperatures(this->cpus, cpuCount);
+
+ /* No temperature - nothing to do */
+ if (r <= 0)
+ return;
+
+ /* Only package temperature - copy to all cpus */
+ if (r == 1 && !isnan(this->cpus[0].temperature)) {
+ double packageTemp = this->cpus[0].temperature;
+ for (int i = 1; i <= cpuCount; i++) {
+ this->cpus[i].temperature = packageTemp;
}
+
+ return;
}
- double period = (double)this->cpus[0].totalPeriod / cpus;
- return period;
+ /* Half the temperatures, probably HT/SMT - copy to second half */
+ if (r >= 2 && (r - 1) == (cpuCount / 2)) {
+ for (int i = cpuCount / 2 + 1; i <= cpuCount; i++) {
+ this->cpus[i].temperature = this->cpus[i/2].temperature;
+ }
+
+ return;
+ }
}
+#endif
-void ProcessList_goThroughEntries(ProcessList* super) {
+void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
LinuxProcessList* this = (LinuxProcessList*) super;
+ const Settings* settings = super->settings;
LinuxProcessList_scanMemoryInfo(super);
LinuxProcessList_scanZfsArcstats(this);
+ LinuxProcessList_updateCPUcount(this);
+ LinuxProcessList_scanZramInfo(this);
+
double period = LinuxProcessList_scanCPUTime(this);
- LinuxProcessList_scanCPUFrequency(this);
+ if (settings->showCPUFrequency) {
+ LinuxProcessList_scanCPUFrequency(this);
+ }
+
+ #ifdef HAVE_SENSORS_SENSORS_H
+ if (settings->showCPUTemperature)
+ LinuxProcessList_scanCPUTemperature(this);
+ #endif
+
+ // in pause mode only gather global data for meters (CPU/memory/...)
+ if (pauseProcessUpdate) {
+ return;
+ }
struct timeval tv;
gettimeofday(&tv, NULL);
- LinuxProcessList_recurseProcTree(this, PROCDIR, NULL, period, tv);
+ unsigned long long now = tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL;
+
+ /* PROCDIR is an absolute path */
+ assert(PROCDIR[0] == '/');
+#ifdef HAVE_OPENAT
+ openat_arg_t rootFd = AT_FDCWD;
+#else
+ openat_arg_t rootFd = "";
+#endif
+
+ LinuxProcessList_recurseProcTree(this, rootFd, PROCDIR, NULL, period, now);
}
diff --git a/linux/LinuxProcessList.h b/linux/LinuxProcessList.h
index c484e75..09b84af 100644
--- a/linux/LinuxProcessList.h
+++ b/linux/LinuxProcessList.h
@@ -3,14 +3,21 @@
/*
htop - LinuxProcessList.h
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h"
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "Hashtable.h"
#include "ProcessList.h"
+#include "UsersTable.h"
+#include "ZramStats.h"
#include "zfs/ZfsArcStats.h"
-extern long long btime;
typedef struct CPUData_ {
unsigned long long int totalTime;
@@ -40,6 +47,10 @@ typedef struct CPUData_ {
unsigned long long int guestPeriod;
double frequency;
+
+ #ifdef HAVE_SENSORS_SENSORS_H
+ double temperature;
+ #endif
} CPUData;
typedef struct TtyDriver_ {
@@ -57,11 +68,12 @@ typedef struct LinuxProcessList_ {
bool haveSmapsRollup;
#ifdef HAVE_DELAYACCT
- struct nl_sock *netlink_socket;
+ struct nl_sock* netlink_socket;
int netlink_family;
#endif
ZfsArcStats zfs;
+ ZramStats zram;
} LinuxProcessList;
#ifndef PROCDIR
@@ -96,6 +108,6 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
void ProcessList_delete(ProcessList* pl);
-void ProcessList_goThroughEntries(ProcessList* super);
+void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate);
#endif
diff --git a/linux/Platform.c b/linux/Platform.c
index e82d8f8..590fc7a 100644
--- a/linux/Platform.c
+++ b/linux/Platform.c
@@ -1,41 +1,71 @@
/*
htop - linux/Platform.c
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include "config.h"
+
#include "Platform.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "BatteryMeter.h"
+#include "ClockMeter.h"
+#include "Compat.h"
+#include "CPUMeter.h"
+#include "DateMeter.h"
+#include "DateTimeMeter.h"
+#include "DiskIOMeter.h"
+#include "HostnameMeter.h"
#include "IOPriority.h"
#include "IOPriorityPanel.h"
#include "LinuxProcess.h"
#include "LinuxProcessList.h"
-#include "Battery.h"
-
+#include "LoadAverageMeter.h"
+#include "Macros.h"
+#include "MainPanel.h"
#include "Meter.h"
-#include "CPUMeter.h"
#include "MemoryMeter.h"
+#include "NetworkIOMeter.h"
+#include "Object.h"
+#include "Panel.h"
+#include "PressureStallMeter.h"
+#include "ProcessList.h"
+#include "ProvideCurses.h"
+#include "SELinuxMeter.h"
+#include "Settings.h"
#include "SwapMeter.h"
+#include "SystemdMeter.h"
#include "TasksMeter.h"
-#include "LoadAverageMeter.h"
#include "UptimeMeter.h"
-#include "PressureStallMeter.h"
-#include "ClockMeter.h"
-#include "HostnameMeter.h"
+#include "XUtils.h"
+#include "ZramMeter.h"
+#include "ZramStats.h"
+
#include "zfs/ZfsArcMeter.h"
+#include "zfs/ZfsArcStats.h"
#include "zfs/ZfsCompressedArcMeter.h"
-#include "LinuxProcess.h"
-
-#include <math.h>
-#include <assert.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
+#ifdef HAVE_SENSORS_SENSORS_H
+#include "LibSensors.h"
+#endif
-ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_SIZE, M_RESIDENT, (int)M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
-//static ProcessField defaultIoFields[] = { PID, IO_PRIORITY, USER, IO_READ_RATE, IO_WRITE_RATE, IO_RATE, COMM, 0 };
+ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, (int)M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
int Platform_numberOfFields = LAST_PROCESSFIELD;
@@ -76,21 +106,46 @@ const SignalItem Platform_signals[] = {
{ .name = "31 SIGSYS", .number = 31 },
};
-const unsigned int Platform_numberOfSignals = sizeof(Platform_signals)/sizeof(SignalItem);
+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;
+
+void Platform_init(void) {
+ if (access(PROCDIR, R_OK) != 0) {
+ fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR);
+ exit(1);
+ }
+
+#ifdef HAVE_SENSORS_SENSORS_H
+ LibSensors_init(NULL);
+#endif
+}
+
+void Platform_done(void) {
+#ifdef HAVE_SENSORS_SENSORS_H
+ LibSensors_cleanup();
+#endif
+}
static Htop_Reaction Platform_actionSetIOPriority(State* st) {
Panel* panel = st->panel;
LinuxProcess* p = (LinuxProcess*) Panel_getSelected(panel);
- if (!p) return HTOP_OK;
+ if (!p)
+ return HTOP_OK;
+
IOPriority ioprio1 = p->ioPriority;
Panel* ioprioPanel = IOPriorityPanel_new(ioprio1);
void* set = Action_pickFromVector(st, ioprioPanel, 21, true);
if (set) {
IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel);
- bool ok = MainPanel_foreachProcess((MainPanel*)panel, (MainPanel_ForeachProcessFn) LinuxProcess_setIOPriority, (Arg){ .i = ioprio2 }, NULL);
- if (!ok)
+ bool ok = MainPanel_foreachProcess((MainPanel*)panel, LinuxProcess_setIOPriority, (Arg) { .i = ioprio2 }, NULL);
+ if (!ok) {
beep();
+ }
}
Panel_delete((Object*)ioprioPanel);
return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
@@ -100,9 +155,11 @@ void Platform_setBindings(Htop_Action* keys) {
keys['i'] = Platform_actionSetIOPriority;
}
-MeterClass* Platform_meterTypes[] = {
+const MeterClass* const Platform_meterTypes[] = {
&CPUMeter_class,
&ClockMeter_class,
+ &DateMeter_class,
+ &DateTimeMeter_class,
&LoadAverageMeter_class,
&LoadMeter_class,
&MemoryMeter_class,
@@ -114,12 +171,15 @@ MeterClass* Platform_meterTypes[] = {
&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,
@@ -128,6 +188,11 @@ MeterClass* Platform_meterTypes[] = {
&PressureStallMemoryFullMeter_class,
&ZfsArcMeter_class,
&ZfsCompressedArcMeter_class,
+ &ZramMeter_class,
+ &DiskIOMeter_class,
+ &NetworkIOMeter_class,
+ &SELinuxMeter_class,
+ &SystemdMeter_class,
NULL
};
@@ -137,15 +202,20 @@ int Platform_getUptime() {
if (fd) {
int n = fscanf(fd, "%64lf", &uptime);
fclose(fd);
- if (n <= 0) return 0;
+ if (n <= 0) {
+ return 0;
+ }
}
- return (int) floor(uptime);
+ return floor(uptime);
}
void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
int activeProcs, totalProcs, lastProc;
- *one = 0; *five = 0; *fifteen = 0;
- FILE *fd = fopen(PROCDIR "/loadavg", "r");
+ *one = 0;
+ *five = 0;
+ *fifteen = 0;
+
+ FILE* fd = fopen(PROCDIR "/loadavg", "r");
if (fd) {
int total = fscanf(fd, "%32lf %32lf %32lf %32d/%32d %32d", one, five, fifteen,
&activeProcs, &totalProcs, &lastProc);
@@ -157,7 +227,9 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
int Platform_getMaxPid() {
FILE* file = fopen(PROCDIR "/sys/kernel/pid_max", "r");
- if (!file) return -1;
+ if (!file)
+ return -1;
+
int maxPid = 4194303;
int match = fscanf(file, "%32d", &maxPid);
(void) match;
@@ -166,8 +238,8 @@ int Platform_getMaxPid() {
}
double Platform_setCPUValues(Meter* this, int cpu) {
- LinuxProcessList* pl = (LinuxProcessList*) this->pl;
- CPUData* cpuData = &(pl->cpus[cpu]);
+ const LinuxProcessList* pl = (const LinuxProcessList*) this->pl;
+ const CPUData* cpuData = &(pl->cpus[cpu]);
double total = (double) ( cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod);
double percent;
double* v = this->values;
@@ -180,28 +252,38 @@ double Platform_setCPUValues(Meter* this, int cpu) {
v[CPU_METER_STEAL] = cpuData->stealPeriod / total * 100.0;
v[CPU_METER_GUEST] = cpuData->guestPeriod / total * 100.0;
v[CPU_METER_IOWAIT] = cpuData->ioWaitPeriod / total * 100.0;
- Meter_setItems(this, 8);
+ this->curItems = 8;
if (this->pl->settings->accountGuestInCPUMeter) {
- percent = v[0]+v[1]+v[2]+v[3]+v[4]+v[5]+v[6];
+ percent = v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6];
} else {
- percent = v[0]+v[1]+v[2]+v[3]+v[4];
+ percent = v[0] + v[1] + v[2] + v[3] + v[4];
}
} else {
v[2] = cpuData->systemAllPeriod / total * 100.0;
v[3] = (cpuData->stealPeriod + cpuData->guestPeriod) / total * 100.0;
- Meter_setItems(this, 4);
- percent = v[0]+v[1]+v[2]+v[3];
+ this->curItems = 4;
+ percent = v[0] + v[1] + v[2] + v[3];
}
percent = CLAMP(percent, 0.0, 100.0);
- if (isnan(percent)) percent = 0.0;
+ if (isnan(percent)) {
+ percent = 0.0;
+ }
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) {
- ProcessList* pl = (ProcessList*) this->pl;
+ const ProcessList* pl = this->pl;
+ const LinuxProcessList* lpl = (const LinuxProcessList*) pl;
+
long int usedMem = pl->usedMem;
long int buffersMem = pl->buffersMem;
long int cachedMem = pl->cachedMem;
@@ -210,55 +292,193 @@ void Platform_setMemoryValues(Meter* this) {
this->values[0] = usedMem;
this->values[1] = buffersMem;
this->values[2] = cachedMem;
+
+ if (lpl->zfs.enabled != 0) {
+ this->values[0] -= lpl->zfs.size;
+ this->values[2] += lpl->zfs.size;
+ }
}
void Platform_setSwapValues(Meter* this) {
- ProcessList* pl = (ProcessList*) this->pl;
+ const ProcessList* pl = this->pl;
this->total = pl->totalSwap;
this->values[0] = pl->usedSwap;
}
+void Platform_setZramValues(Meter* this) {
+ const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl;
+ this->total = lpl->zram.totalZram;
+ this->values[0] = lpl->zram.usedZramComp;
+ this->values[1] = lpl->zram.usedZramOrig;
+}
+
void Platform_setZfsArcValues(Meter* this) {
- LinuxProcessList* lpl = (LinuxProcessList*) this->pl;
+ const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl;
ZfsArcMeter_readStats(this, &(lpl->zfs));
}
void Platform_setZfsCompressedArcValues(Meter* this) {
- LinuxProcessList* lpl = (LinuxProcessList*) this->pl;
+ const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl;
ZfsCompressedArcMeter_readStats(this, &(lpl->zfs));
}
+
char* Platform_getProcessEnv(pid_t pid) {
- char procname[32+1];
- xSnprintf(procname, 32, "/proc/%d/environ", pid);
+ char procname[128];
+ xSnprintf(procname, sizeof(procname), PROCDIR "/%d/environ", pid);
FILE* fd = fopen(procname, "r");
- char *env = NULL;
- if (fd) {
- size_t capacity = 4096, size = 0, bytes;
- env = xMalloc(capacity);
- while ((bytes = fread(env+size, 1, capacity-size, fd)) > 0) {
- size += bytes;
- capacity *= 2;
- env = xRealloc(env, capacity);
- }
- fclose(fd);
- if (size < 2 || env[size-1] || env[size-2]) {
- if (size + 2 < capacity) {
- env = xRealloc(env, capacity+2);
- }
- env[size] = 0;
- env[size+1] = 0;
- }
+ 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;
}
-void Platform_getPressureStall(const char *file, bool some, double* ten, double* sixty, double* threehundred) {
+/*
+ * Return the absolute path of a file given its pid&inode number
+ *
+ * Based on implementation of lslocks from util-linux:
+ * https://sources.debian.org/src/util-linux/2.36-3/misc-utils/lslocks.c/#L162
+ */
+char* Platform_getInodeFilename(pid_t pid, ino_t inode) {
+ struct stat sb;
+ struct dirent *de;
+ DIR *dirp;
+ ssize_t len;
+ int fd;
+
+ char path[PATH_MAX];
+ char sym[PATH_MAX];
+ char* ret = NULL;
+
+ memset(path, 0, sizeof(path));
+ memset(sym, 0, sizeof(sym));
+
+ xSnprintf(path, sizeof(path), "%s/%d/fd/", PROCDIR, pid);
+ if (strlen(path) >= (sizeof(path) - 2))
+ return NULL;
+
+ if (!(dirp = opendir(path)))
+ return NULL;
+
+ if ((fd = dirfd(dirp)) < 0 )
+ goto out;
+
+ while ((de = readdir(dirp))) {
+ if (String_eq(de->d_name, ".") || String_eq(de->d_name, ".."))
+ continue;
+
+ /* care only for numerical descriptors */
+ if (!strtoull(de->d_name, (char **) NULL, 10))
+ continue;
+
+ if (!Compat_fstatat(fd, path, de->d_name, &sb, 0) && inode != sb.st_ino)
+ continue;
+
+ if ((len = Compat_readlinkat(fd, path, de->d_name, sym, sizeof(sym) - 1)) < 1)
+ goto out;
+
+ sym[len] = '\0';
+
+ ret = xStrdup(sym);
+ break;
+ }
+
+out:
+ closedir(dirp);
+ return ret;
+}
+
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {
+ FileLocks_ProcessData* pdata = xCalloc(1, sizeof(FileLocks_ProcessData));
+
+ FILE* f = fopen(PROCDIR "/locks", "r");
+ if (!f) {
+ pdata->error = true;
+ return pdata;
+ }
+
+ char buffer[1024];
+ FileLocks_LockData** data_ref = &pdata->locks;
+ while(fgets(buffer, sizeof(buffer), f)) {
+ if (!strchr(buffer, '\n'))
+ continue;
+
+ int lock_id;
+ char lock_type[16];
+ char lock_excl[16];
+ char lock_rw[16];
+ pid_t lock_pid;
+ unsigned int lock_dev[2];
+ uint64_t lock_inode;
+ char lock_start[25];
+ char lock_end[25];
+
+ if (10 != sscanf(buffer, "%d: %15s %15s %15s %d %x:%x:%"PRIu64" %24s %24s",
+ &lock_id, lock_type, lock_excl, lock_rw, &lock_pid,
+ &lock_dev[0], &lock_dev[1], &lock_inode,
+ lock_start, lock_end))
+ continue;
+
+ if (pid != lock_pid)
+ continue;
+
+ FileLocks_LockData* ldata = xCalloc(1, sizeof(FileLocks_LockData));
+ FileLocks_Data* data = &ldata->data;
+ data->id = lock_id;
+ data->locktype = xStrdup(lock_type);
+ data->exclusive = xStrdup(lock_excl);
+ data->readwrite = xStrdup(lock_rw);
+ data->filename = Platform_getInodeFilename(lock_pid, lock_inode);
+ data->dev[0] = lock_dev[0];
+ data->dev[1] = lock_dev[1];
+ data->inode = lock_inode;
+ data->start = strtoull(lock_start, NULL, 10);
+ if (!String_eq(lock_end, "EOF")) {
+ data->end = strtoull(lock_end, NULL, 10);
+ } else {
+ data->end = ULLONG_MAX;
+ }
+
+ *data_ref = ldata;
+ data_ref = &ldata->next;
+ }
+
+ fclose(f);
+ return pdata;
+}
+
+void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) {
*ten = *sixty = *threehundred = 0;
- char procname[128+1];
- xSnprintf(procname, 128, PROCDIR "/pressure/%s", file);
- FILE *fd = fopen(procname, "r");
+ char procname[128];
+ xSnprintf(procname, sizeof(procname), PROCDIR "/pressure/%s", file);
+ FILE* fd = fopen(procname, "r");
if (!fd) {
*ten = *sixty = *threehundred = NAN;
return;
@@ -271,3 +491,366 @@ void Platform_getPressureStall(const char *file, bool some, double* ten, double*
assert(total == 3);
fclose(fd);
}
+
+bool Platform_getDiskIO(DiskIOData* data) {
+ FILE* fd = fopen(PROCDIR "/diskstats", "r");
+ if (!fd)
+ return false;
+
+ unsigned long int read_sum = 0, write_sum = 0, timeSpend_sum = 0;
+ char lineBuffer[256];
+ while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ char diskname[32];
+ unsigned long int read_tmp, write_tmp, timeSpend_tmp;
+ if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %lu %*u %*u %*u %lu %*u %*u %lu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) {
+ if (String_startsWith(diskname, "dm-"))
+ continue;
+
+ /* only count root disks, e.g. do not count IO from sda and sda1 twice */
+ if ((diskname[0] == 's' || diskname[0] == 'h')
+ && diskname[1] == 'd'
+ && isalpha((unsigned char)diskname[2])
+ && isdigit((unsigned char)diskname[3]))
+ continue;
+
+ /* only count root disks, e.g. do not count IO from mmcblk0 and mmcblk0p1 twice */
+ if (diskname[0] == 'm'
+ && diskname[1] == 'm'
+ && diskname[2] == 'c'
+ && diskname[3] == 'b'
+ && diskname[4] == 'l'
+ && diskname[5] == 'k'
+ && isdigit((unsigned char)diskname[6])
+ && diskname[7] == 'p')
+ continue;
+
+ 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(unsigned long int* bytesReceived,
+ unsigned long int* packetsReceived,
+ unsigned long int* bytesTransmitted,
+ unsigned long int* packetsTransmitted) {
+ FILE* fd = fopen(PROCDIR "/net/dev", "r");
+ if (!fd)
+ return false;
+
+ unsigned long int bytesReceivedSum = 0, packetsReceivedSum = 0, bytesTransmittedSum = 0, packetsTransmittedSum = 0;
+ char lineBuffer[512];
+ while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
+ char interfaceName[32];
+ unsigned long int bytesReceivedParsed, packetsReceivedParsed, bytesTransmittedParsed, packetsTransmittedParsed;
+ if (sscanf(lineBuffer, "%31s %lu %lu %*u %*u %*u %*u %*u %*u %lu %lu",
+ interfaceName,
+ &bytesReceivedParsed,
+ &packetsReceivedParsed,
+ &bytesTransmittedParsed,
+ &packetsTransmittedParsed) != 5)
+ continue;
+
+ if (String_eq(interfaceName, "lo:"))
+ continue;
+
+ bytesReceivedSum += bytesReceivedParsed;
+ packetsReceivedSum += packetsReceivedParsed;
+ bytesTransmittedSum += bytesTransmittedParsed;
+ packetsTransmittedSum += packetsTransmittedParsed;
+ }
+
+ fclose(fd);
+
+ *bytesReceived = bytesReceivedSum;
+ *packetsReceived = packetsReceivedSum;
+ *bytesTransmitted = bytesTransmittedSum;
+ *packetsTransmitted = packetsTransmittedSum;
+ return true;
+}
+
+// Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
+
+#define MAX_BATTERIES 64
+#define PROC_BATTERY_DIR PROCDIR "/acpi/battery"
+#define PROC_POWERSUPPLY_DIR PROCDIR "/acpi/ac_adapter"
+#define SYS_POWERSUPPLY_DIR "/sys/class/power_supply"
+
+// ----------------------------------------
+// READ FROM /proc
+// ----------------------------------------
+
+static unsigned long int parseBatInfo(const char* fileName, const unsigned short int lineNum, const unsigned short int wordNum) {
+ const char batteryPath[] = PROC_BATTERY_DIR;
+ DIR* batteryDir = opendir(batteryPath);
+ if (!batteryDir)
+ return 0;
+
+ char* batteries[MAX_BATTERIES];
+ unsigned int nBatteries = 0;
+ memset(batteries, 0, MAX_BATTERIES * sizeof(char*));
+
+ while (nBatteries < MAX_BATTERIES) {
+ struct dirent* dirEntry = readdir(batteryDir);
+ if (!dirEntry)
+ break;
+
+ char* entryName = dirEntry->d_name;
+ if (!String_startsWith(entryName, "BAT"))
+ continue;
+
+ batteries[nBatteries] = xStrdup(entryName);
+ nBatteries++;
+ }
+ closedir(batteryDir);
+
+ unsigned long int total = 0;
+ for (unsigned int i = 0; i < nBatteries; i++) {
+ char infoPath[30];
+ xSnprintf(infoPath, sizeof infoPath, "%s%s/%s", batteryPath, batteries[i], fileName);
+
+ FILE* file = fopen(infoPath, "r");
+ if (!file)
+ break;
+
+ char* line = NULL;
+ for (unsigned short int j = 0; j < lineNum; j++) {
+ free(line);
+ line = String_readLine(file);
+ if (!line)
+ break;
+ }
+
+ fclose(file);
+
+ if (!line)
+ break;
+
+ char* foundNumStr = String_getToken(line, wordNum);
+ const unsigned long int foundNum = atoi(foundNumStr);
+ free(foundNumStr);
+ free(line);
+
+ total += foundNum;
+ }
+
+ for (unsigned int i = 0; i < nBatteries; i++)
+ free(batteries[i]);
+
+ return total;
+}
+
+static ACPresence procAcpiCheck(void) {
+ ACPresence isOn = AC_ERROR;
+ const char* power_supplyPath = PROC_POWERSUPPLY_DIR;
+ DIR* dir = opendir(power_supplyPath);
+ if (!dir)
+ return AC_ERROR;
+
+ for (;;) {
+ struct dirent* dirEntry = readdir(dir);
+ if (!dirEntry)
+ break;
+
+ const char* entryName = dirEntry->d_name;
+
+ if (entryName[0] != 'A')
+ continue;
+
+ char statePath[256];
+ xSnprintf(statePath, sizeof(statePath), "%s/%s/state", power_supplyPath, entryName);
+ FILE* file = fopen(statePath, "r");
+ if (!file) {
+ isOn = AC_ERROR;
+ continue;
+ }
+ char* line = String_readLine(file);
+
+ fclose(file);
+
+ if (!line)
+ continue;
+
+ char* isOnline = String_getToken(line, 2);
+ free(line);
+
+ if (String_eq(isOnline, "on-line"))
+ isOn = AC_PRESENT;
+ else
+ isOn = AC_ABSENT;
+ free(isOnline);
+ if (isOn == AC_PRESENT)
+ break;
+ }
+
+ if (dir)
+ closedir(dir);
+
+ return isOn;
+}
+
+static double Platform_Battery_getProcBatInfo(void) {
+ const unsigned long int totalFull = parseBatInfo("info", 3, 4);
+ if (totalFull == 0)
+ return NAN;
+
+ const unsigned long int totalRemain = parseBatInfo("state", 5, 3);
+ if (totalRemain == 0)
+ return NAN;
+
+ return totalRemain * 100.0 / (double) totalFull;
+}
+
+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;
+
+ unsigned long int totalFull = 0;
+ unsigned long int totalRemain = 0;
+
+ for (;;) {
+ struct dirent* dirEntry = readdir(dir);
+ if (!dirEntry)
+ break;
+
+ const char* entryName = dirEntry->d_name;
+ char filePath[256];
+
+ xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/type", entryName);
+
+ char type[8];
+ ssize_t r = xReadfile(filePath, type, sizeof(type));
+ if (r < 3)
+ continue;
+
+ if (type[0] == 'B' && type[1] == 'a' && type[2] == 't') {
+ xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/uevent", entryName);
+
+ char buffer[1024];
+ r = xReadfile(filePath, buffer, sizeof(buffer));
+ if (r < 0) {
+ closedir(dir);
+ return;
+ }
+
+ char* buf = buffer;
+ char* line = NULL;
+ bool full = false;
+ bool now = false;
+ int fullSize = 0;
+ double capacityLevel = NAN;
+
+ #define match(str,prefix) \
+ (String_startsWith(str,prefix) ? (str) + strlen(prefix) : NULL)
+
+ while ((line = strsep(&buf, "\n")) != NULL) {
+ const char* ps = match(line, "POWER_SUPPLY_");
+ if (!ps)
+ continue;
+ const char* capacity = match(ps, "CAPACITY=");
+ if (capacity)
+ capacityLevel = atoi(capacity) / 100.0;
+ const char* energy = match(ps, "ENERGY_");
+ if (!energy)
+ energy = match(ps, "CHARGE_");
+ if (!energy)
+ continue;
+ const char* value = (!full) ? match(energy, "FULL=") : NULL;
+ if (value) {
+ fullSize = atoi(value);
+ totalFull += fullSize;
+ full = true;
+ if (now)
+ break;
+ continue;
+ }
+ value = (!now) ? match(energy, "NOW=") : NULL;
+ if (value) {
+ totalRemain += atoi(value);
+ now = true;
+ if (full)
+ break;
+ continue;
+ }
+ }
+
+ #undef match
+
+ if (!now && full && !isnan(capacityLevel))
+ totalRemain += (capacityLevel * fullSize);
+
+ } else if (entryName[0] == 'A') {
+ if (*isOnAC != AC_ERROR)
+ continue;
+
+ xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/online", entryName);
+
+ char buffer[2];
+
+ r = xReadfile(filePath, buffer, sizeof(buffer));
+ if (r < 1) {
+ closedir(dir);
+ return;
+ }
+
+ if (buffer[0] == '0')
+ *isOnAC = AC_ABSENT;
+ else if (buffer[0] == '1')
+ *isOnAC = AC_PRESENT;
+ }
+ }
+ 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 (isnan(*percent))
+ Platform_Battery_method = BAT_SYS;
+ }
+ if (Platform_Battery_method == BAT_SYS) {
+ Platform_Battery_getSysData(percent, isOnAC);
+ if (isnan(*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;
+}
diff --git a/linux/Platform.h b/linux/Platform.h
index 9f0ee7f..280b997 100644
--- a/linux/Platform.h
+++ b/linux/Platform.h
@@ -3,14 +3,19 @@
/*
htop - linux/Platform.h
(C) 2014 Hisham H. Muhammad
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
+#include <stdbool.h>
+#include <sys/types.h>
+
#include "Action.h"
-#include "MainPanel.h"
#include "BatteryMeter.h"
-#include "LinuxProcess.h"
+#include "DiskIOMeter.h"
+#include "Meter.h"
+#include "Process.h"
+#include "ProcessLocksScreen.h"
#include "SignalsPanel.h"
extern ProcessField Platform_defaultFields[];
@@ -21,15 +26,19 @@ extern const SignalItem Platform_signals[];
extern const unsigned int Platform_numberOfSignals;
-void Platform_setBindings(Htop_Action* keys);
+extern const MeterClass* const Platform_meterTypes[];
+
+void Platform_init(void);
-extern MeterClass* Platform_meterTypes[];
+void Platform_done(void);
-int Platform_getUptime();
+void Platform_setBindings(Htop_Action* keys);
+
+int Platform_getUptime(void);
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
-int Platform_getMaxPid();
+int Platform_getMaxPid(void);
double Platform_setCPUValues(Meter* this, int cpu);
@@ -37,11 +46,27 @@ 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);
+char* Platform_getInodeFilename(pid_t pid, ino_t inode);
+
+FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);
+
void Platform_getPressureStall(const char *file, bool some, double* ten, double* sixty, double* threehundred);
+bool Platform_getDiskIO(DiskIOData* data);
+
+bool Platform_getNetworkIO(unsigned long int* bytesReceived,
+ unsigned long int* packetsReceived,
+ unsigned long int* bytesTransmitted,
+ unsigned long int* packetsTransmitted);
+
+void Platform_getBattery(double *percent, ACPresence *isOnAC);
+
#endif
diff --git a/linux/PressureStallMeter.c b/linux/PressureStallMeter.c
index 56055bf..745068c 100644
--- a/linux/PressureStallMeter.c
+++ b/linux/PressureStallMeter.c
@@ -2,47 +2,52 @@
htop - PressureStallMeter.c
(C) 2004-2011 Hisham H. Muhammad
(C) 2019 Ran Benita
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
#include "PressureStallMeter.h"
-#include "Platform.h"
-#include "CRT.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 int PressureStallMeter_attributes[] = {
- PRESSURE_STALL_TEN, PRESSURE_STALL_SIXTY, PRESSURE_STALL_THREEHUNDRED
+static const int PressureStallMeter_attributes[] = {
+ PRESSURE_STALL_TEN,
+ PRESSURE_STALL_SIXTY,
+ PRESSURE_STALL_THREEHUNDRED
};
-static void PressureStallMeter_updateValues(Meter* this, char* buffer, int len) {
- const char *file;
- if (strstr(Meter_name(this), "CPU")) {
- file = "cpu";
- } else if (strstr(Meter_name(this), "IO")) {
- file = "io";
- } else {
- file = "memory";
- }
+static void PressureStallMeter_updateValues(Meter* this, char* buffer, size_t len) {
+ const char* file;
+ if (strstr(Meter_name(this), "CPU")) {
+ file = "cpu";
+ } else if (strstr(Meter_name(this), "IO")) {
+ file = "io";
+ } else {
+ file = "memory";
+ }
- bool some;
- if (strstr(Meter_name(this), "Some")) {
- some = true;
- } else {
- some = false;
- }
+ 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]);
- xSnprintf(buffer, len, "xxxx %.2lf%% %.2lf%% %.2lf%%", this->values[0], this->values[1], this->values[2]);
+ Platform_getPressureStall(file, some, &this->values[0], &this->values[1], &this->values[2]);
+ xSnprintf(buffer, len, "%s %s %.2lf%% %.2lf%% %.2lf%%", some ? "some" : "full", file, this->values[0], this->values[1], this->values[2]);
}
-static void PressureStallMeter_display(Object* cast, RichString* out) {
- Meter* this = (Meter*)cast;
+static void PressureStallMeter_display(const Object* cast, RichString* out) {
+ const Meter* this = (const Meter*)cast;
char buffer[20];
xSnprintf(buffer, sizeof(buffer), "%.2lf%% ", this->values[0]);
RichString_write(out, CRT_colors[PRESSURE_STALL_TEN], buffer);
@@ -52,7 +57,7 @@ static void PressureStallMeter_display(Object* cast, RichString* out) {
RichString_append(out, CRT_colors[PRESSURE_STALL_THREEHUNDRED], buffer);
}
-MeterClass PressureStallCPUSomeMeter_class = {
+const MeterClass PressureStallCPUSomeMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
@@ -64,11 +69,12 @@ MeterClass PressureStallCPUSomeMeter_class = {
.total = 100.0,
.attributes = PressureStallMeter_attributes,
.name = "PressureStallCPUSome",
- .uiName = "Pressure Stall Information, some CPU",
- .caption = "Some CPU pressure: "
+ .uiName = "PSI some CPU",
+ .caption = "PSI some CPU: ",
+ .description = "Pressure Stall Information, some cpu"
};
-MeterClass PressureStallIOSomeMeter_class = {
+const MeterClass PressureStallIOSomeMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
@@ -80,11 +86,12 @@ MeterClass PressureStallIOSomeMeter_class = {
.total = 100.0,
.attributes = PressureStallMeter_attributes,
.name = "PressureStallIOSome",
- .uiName = "Pressure Stall Information, some IO",
- .caption = "Some IO pressure: "
+ .uiName = "PSI some IO",
+ .caption = "PSI some IO: ",
+ .description = "Pressure Stall Information, some io"
};
-MeterClass PressureStallIOFullMeter_class = {
+const MeterClass PressureStallIOFullMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
@@ -96,11 +103,12 @@ MeterClass PressureStallIOFullMeter_class = {
.total = 100.0,
.attributes = PressureStallMeter_attributes,
.name = "PressureStallIOFull",
- .uiName = "Pressure Stall Information, full IO",
- .caption = "Full IO pressure: "
+ .uiName = "PSI full IO",
+ .caption = "PSI full IO: ",
+ .description = "Pressure Stall Information, full io"
};
-MeterClass PressureStallMemorySomeMeter_class = {
+const MeterClass PressureStallMemorySomeMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
@@ -112,11 +120,12 @@ MeterClass PressureStallMemorySomeMeter_class = {
.total = 100.0,
.attributes = PressureStallMeter_attributes,
.name = "PressureStallMemorySome",
- .uiName = "Pressure Stall Information, some memory",
- .caption = "Some Mem pressure: "
+ .uiName = "PSI some memory",
+ .caption = "PSI some memory: ",
+ .description = "Pressure Stall Information, some memory"
};
-MeterClass PressureStallMemoryFullMeter_class = {
+const MeterClass PressureStallMemoryFullMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
@@ -128,6 +137,7 @@ MeterClass PressureStallMemoryFullMeter_class = {
.total = 100.0,
.attributes = PressureStallMeter_attributes,
.name = "PressureStallMemoryFull",
- .uiName = "Pressure Stall Information, full memory",
- .caption = "Full Mem pressure: "
+ .uiName = "PSI full memory",
+ .caption = "PSI full memory: ",
+ .description = "Pressure Stall Information, full memory"
};
diff --git a/linux/PressureStallMeter.h b/linux/PressureStallMeter.h
index 22b8b97..1a0ad58 100644
--- a/linux/PressureStallMeter.h
+++ b/linux/PressureStallMeter.h
@@ -6,20 +6,20 @@
htop - PressureStallMeter.h
(C) 2004-2011 Hisham H. Muhammad
(C) 2019 Ran Benita
-Released under the GNU GPL, see the COPYING file
+Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
#include "Meter.h"
-extern MeterClass PressureStallCPUSomeMeter_class;
+extern const MeterClass PressureStallCPUSomeMeter_class;
-extern MeterClass PressureStallIOSomeMeter_class;
+extern const MeterClass PressureStallIOSomeMeter_class;
-extern MeterClass PressureStallIOFullMeter_class;
+extern const MeterClass PressureStallIOFullMeter_class;
-extern MeterClass PressureStallMemorySomeMeter_class;
+extern const MeterClass PressureStallMemorySomeMeter_class;
-extern MeterClass PressureStallMemoryFullMeter_class;
+extern const MeterClass PressureStallMemoryFullMeter_class;
#endif
diff --git a/linux/SELinuxMeter.c b/linux/SELinuxMeter.c
new file mode 100644
index 0000000..64a3f2a
--- /dev/null
+++ b/linux/SELinuxMeter.c
@@ -0,0 +1,94 @@
+/*
+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 "SELinuxMeter.h"
+
+#include "CRT.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <linux/magic.h>
+#include <sys/statfs.h>
+#include <sys/statvfs.h>
+
+#include "Macros.h"
+#include "Object.h"
+#include "XUtils.h"
+
+
+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 (sfbuf.f_type != SELINUX_MAGIC) {
+ 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(ATTR_UNUSED Meter* this, char* buffer, size_t len) {
+ enabled = isSelinuxEnabled();
+ enforcing = isSelinuxEnforcing();
+
+ xSnprintf(buffer, len, "%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..d79ad01
--- /dev/null
+++ b/linux/SELinuxMeter.h
@@ -0,0 +1,14 @@
+#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..4350d26
--- /dev/null
+++ b/linux/SystemdMeter.c
@@ -0,0 +1,335 @@
+/*
+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 "SystemdMeter.h"
+
+#include <dlfcn.h>
+#include <fcntl.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 "XUtils.h"
+
+
+#define INVALID_VALUE ((unsigned int)-1)
+
+typedef void sd_bus;
+typedef void sd_bus_error;
+static int (*sym_sd_bus_open_system)(sd_bus**);
+static int (*sym_sd_bus_get_property_string)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char**);
+static int (*sym_sd_bus_get_property_trivial)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char, void*);
+static sd_bus* (*sym_sd_bus_unref)(sd_bus*);
+
+static char* systemState = NULL;
+static unsigned int nFailedUnits = INVALID_VALUE;
+static unsigned int nInstalledJobs = INVALID_VALUE;
+static unsigned int nNames = INVALID_VALUE;
+static unsigned int nJobs = INVALID_VALUE;
+static void* dlopenHandle = NULL;
+static sd_bus* bus = NULL;
+
+static void SystemdMeter_done(ATTR_UNUSED Meter* this) {
+ free(systemState);
+ systemState = NULL;
+
+ if (bus && dlopenHandle) {
+ sym_sd_bus_unref(bus);
+ }
+ bus = NULL;
+
+ if (dlopenHandle) {
+ dlclose(dlopenHandle);
+ dlopenHandle = NULL;
+ }
+}
+
+static int updateViaLib(void) {
+ 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_get_property_string);
+ resolve(sd_bus_get_property_trivial);
+ resolve(sd_bus_unref);
+
+ #undef resolve
+ }
+
+ int r;
+
+ /* Connect to the system bus */
+ if (!bus) {
+ r = sym_sd_bus_open_system(&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(bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "SystemState", /* property name */
+ NULL, /* object to return error in */
+ &systemState);
+ if (r < 0)
+ goto busfailure;
+
+ r = sym_sd_bus_get_property_trivial(bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "NFailedUnits", /* property name */
+ NULL, /* object to return error in */
+ 'u', /* property type */
+ &nFailedUnits);
+ if (r < 0)
+ goto busfailure;
+
+ r = sym_sd_bus_get_property_trivial(bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "NInstalledJobs", /* property name */
+ NULL, /* object to return error in */
+ 'u', /* property type */
+ &nInstalledJobs);
+ if (r < 0)
+ goto busfailure;
+
+ r = sym_sd_bus_get_property_trivial(bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "NNames", /* property name */
+ NULL, /* object to return error in */
+ 'u', /* property type */
+ &nNames);
+ if (r < 0)
+ goto busfailure;
+
+ r = sym_sd_bus_get_property_trivial(bus,
+ busServiceName, /* service to contact */
+ busObjectPath, /* object path */
+ busInterfaceName, /* interface name */
+ "NJobs", /* property name */
+ NULL, /* object to return error in */
+ 'u', /* property type */
+ &nJobs);
+ if (r < 0)
+ goto busfailure;
+
+ /* success */
+ return 0;
+
+busfailure:
+ sym_sd_bus_unref(bus);
+ bus = NULL;
+ return -2;
+
+dlfailure:
+ if (dlopenHandle) {
+ dlclose(dlopenHandle);
+ dlopenHandle = NULL;
+ }
+ return -1;
+}
+
+static void updateViaExec(void) {
+ 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);
+ execl("/bin/systemctl",
+ "/bin/systemctl",
+ "show",
+ "--property=SystemState",
+ "--property=NFailedUnits",
+ "--property=NNames",
+ "--property=NJobs",
+ "--property=NInstalledJobs",
+ 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(systemState);
+ systemState = xStrdup(lineBuffer + strlen("SystemState="));
+ } else if (String_startsWith(lineBuffer, "NFailedUnits=")) {
+ nFailedUnits = strtoul(lineBuffer + strlen("NFailedUnits="), NULL, 10);
+ } else if (String_startsWith(lineBuffer, "NNames=")) {
+ nNames = strtoul(lineBuffer + strlen("NNames="), NULL, 10);
+ } else if (String_startsWith(lineBuffer, "NJobs=")) {
+ nJobs = strtoul(lineBuffer + strlen("NJobs="), NULL, 10);
+ } else if (String_startsWith(lineBuffer, "NInstalledJobs=")) {
+ nInstalledJobs = strtoul(lineBuffer + strlen("NInstalledJobs="), NULL, 10);
+ }
+ }
+
+ fclose(commandOutput);
+}
+
+static void SystemdMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t size) {
+ free(systemState);
+ systemState = NULL;
+ nFailedUnits = nInstalledJobs = nNames = nJobs = INVALID_VALUE;
+
+ if (updateViaLib() < 0)
+ updateViaExec();
+
+ xSnprintf(buffer, size, "%s", systemState ? 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) {
+ char buffer[16];
+
+ int color = (systemState && 0 == strcmp(systemState, "running")) ? METER_VALUE_OK : METER_VALUE_ERROR;
+ RichString_write(out, CRT_colors[color], systemState ? systemState : "???");
+
+ RichString_append(out, CRT_colors[METER_TEXT], " (");
+
+ if (nFailedUnits == INVALID_VALUE) {
+ buffer[0] = '?';
+ buffer[1] = '\0';
+ } else {
+ xSnprintf(buffer, sizeof(buffer), "%u", nFailedUnits);
+ }
+ RichString_append(out, zeroDigitColor(nFailedUnits), buffer);
+
+ RichString_append(out, CRT_colors[METER_TEXT], "/");
+
+ if (nNames == INVALID_VALUE) {
+ buffer[0] = '?';
+ buffer[1] = '\0';
+ } else {
+ xSnprintf(buffer, sizeof(buffer), "%u", nNames);
+ }
+ RichString_append(out, valueDigitColor(nNames), buffer);
+
+ RichString_append(out, CRT_colors[METER_TEXT], " failed) (");
+
+ if (nJobs == INVALID_VALUE) {
+ buffer[0] = '?';
+ buffer[1] = '\0';
+ } else {
+ xSnprintf(buffer, sizeof(buffer), "%u", nJobs);
+ }
+ RichString_append(out, zeroDigitColor(nJobs), buffer);
+
+ RichString_append(out, CRT_colors[METER_TEXT], "/");
+
+ if (nInstalledJobs == INVALID_VALUE) {
+ buffer[0] = '?';
+ buffer[1] = '\0';
+ } else {
+ xSnprintf(buffer, sizeof(buffer), "%u", nInstalledJobs);
+ }
+ RichString_append(out, valueDigitColor(nInstalledJobs), buffer);
+
+ RichString_append(out, CRT_colors[METER_TEXT], " jobs)");
+}
+
+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: ",
+};
diff --git a/linux/SystemdMeter.h b/linux/SystemdMeter.h
new file mode 100644
index 0000000..0f226d6
--- /dev/null
+++ b/linux/SystemdMeter.h
@@ -0,0 +1,15 @@
+#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;
+
+#endif /* HEADER_SystemdMeter */
diff --git a/linux/ZramMeter.c b/linux/ZramMeter.c
new file mode 100644
index 0000000..e6b6937
--- /dev/null
+++ b/linux/ZramMeter.c
@@ -0,0 +1,67 @@
+#include "ZramMeter.h"
+
+#include "CRT.h"
+#include "Meter.h"
+#include "Object.h"
+#include "Platform.h"
+#include "RichString.h"
+
+
+static const int ZramMeter_attributes[] = {
+ ZRAM
+};
+
+static void ZramMeter_updateValues(Meter* this, char* buffer, size_t size) {
+ int written;
+
+ Platform_setZramValues(this);
+
+ /* on print bar for compressed data size, not uncompressed */
+ this->curItems = 1;
+
+ written = Meter_humanUnit(buffer, this->values[0], size);
+ METER_BUFFER_CHECK(buffer, size, written);
+
+ METER_BUFFER_APPEND_CHR(buffer, size, '(');
+
+ written = Meter_humanUnit(buffer, this->values[1], 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_write(out, CRT_colors[METER_TEXT], ":");
+ Meter_humanUnit(buffer, this->total, sizeof(buffer));
+
+ RichString_append(out, CRT_colors[METER_VALUE], buffer);
+ Meter_humanUnit(buffer, this->values[0], sizeof(buffer));
+ RichString_append(out, CRT_colors[METER_TEXT], " used:");
+ RichString_append(out, CRT_colors[METER_VALUE], buffer);
+
+ Meter_humanUnit(buffer, this->values[1], sizeof(buffer));
+ RichString_append(out, CRT_colors[METER_TEXT], " uncompressed:");
+ RichString_append(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 = 2,
+ .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..7cf7861
--- /dev/null
+++ b/linux/ZramMeter.h
@@ -0,0 +1,8 @@
+#ifndef HEADER_ZramMeter
+#define HEADER_ZramMeter
+
+#include "Meter.h"
+
+extern const MeterClass ZramMeter_class;
+
+#endif
diff --git a/linux/ZramStats.h b/linux/ZramStats.h
new file mode 100644
index 0000000..2305cfd
--- /dev/null
+++ b/linux/ZramStats.h
@@ -0,0 +1,10 @@
+#ifndef HEADER_ZramStats
+#define HEADER_ZramStats
+
+typedef struct ZramStats_ {
+ unsigned long long int totalZram;
+ unsigned long long int usedZramComp;
+ unsigned long long int usedZramOrig;
+} ZramStats;
+
+#endif

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