From 65357c8c46154de4e4eca14075bfe5523bb5fc14 Mon Sep 17 00:00:00 2001 From: Daniel Lange Date: Mon, 7 Dec 2020 10:26:01 +0100 Subject: New upstream version 3.0.3 --- linux/Battery.c | 322 --------- linux/Battery.h | 14 - linux/IOPriority.h | 4 +- linux/IOPriorityPanel.c | 32 +- linux/IOPriorityPanel.h | 3 +- linux/LibSensors.c | 104 +++ linux/LibSensors.h | 16 + linux/LinuxCRT.c | 36 - linux/LinuxCRT.h | 12 - linux/LinuxProcess.c | 739 +++++++++++++++++---- linux/LinuxProcess.h | 119 ++-- linux/LinuxProcessList.c | 1564 ++++++++++++++++++++++++++++++++------------ linux/LinuxProcessList.h | 20 +- linux/Platform.c | 707 ++++++++++++++++++-- linux/Platform.h | 39 +- linux/PressureStallMeter.c | 92 +-- linux/PressureStallMeter.h | 12 +- linux/SELinuxMeter.c | 94 +++ linux/SELinuxMeter.h | 14 + linux/SystemdMeter.c | 335 ++++++++++ linux/SystemdMeter.h | 15 + linux/ZramMeter.c | 67 ++ linux/ZramMeter.h | 8 + linux/ZramStats.h | 10 + 24 files changed, 3281 insertions(+), 1097 deletions(-) delete mode 100644 linux/Battery.c delete mode 100644 linux/Battery.h create mode 100644 linux/LibSensors.c create mode 100644 linux/LibSensors.h delete mode 100644 linux/LinuxCRT.c delete mode 100644 linux/LinuxCRT.h create mode 100644 linux/SELinuxMeter.c create mode 100644 linux/SELinuxMeter.h create mode 100644 linux/SystemdMeter.c create mode 100644 linux/SystemdMeter.h create mode 100644 linux/ZramMeter.c create mode 100644 linux/ZramMeter.h create mode 100644 linux/ZramStats.h (limited to 'linux') 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 -#include -#include -#include -#include -#include -#include -#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 +#include + +#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 +#include +#include + +#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 + +#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 -#include -#ifdef HAVE_EXECINFO_H -#include -#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 +#include #include -#include #include -#include -#include +#include +#include + +#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 + +#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 -#include -#include -#include -#include + +#include #include -#include -#include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include +#include #include -#include -#include +#include +#include +#include #include -#include -#ifdef MAJOR_IN_MKDEV -#include -#elif defined(MAJOR_IN_SYSMACROS) -#include -#endif #ifdef HAVE_DELAYACCT +#include +#include #include +#include +#include #include +#include #include #include -#include -#include -#include #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 +#elif defined(MAJOR_IN_SYSMACROS) +#include +#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 +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 -#include -#include -#include -#include +#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 +#include + #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 #include -/*{ +#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 +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 -- cgit v1.2.3