From 69f439eff387a6ecb52734e400b297a3c85f2285 Mon Sep 17 00:00:00 2001 From: Daniel Lange Date: Tue, 21 Sep 2021 08:35:19 +0200 Subject: New upstream version 3.1.0 --- linux/HugePageMeter.c | 106 +++++ linux/HugePageMeter.h | 15 + linux/IOPriority.h | 2 - linux/IOPriorityPanel.c | 3 +- linux/IOPriorityPanel.h | 2 +- linux/LibSensors.c | 218 +++++++--- linux/LibSensors.h | 11 +- linux/LinuxProcess.c | 756 +++++++---------------------------- linux/LinuxProcess.h | 82 ++-- linux/LinuxProcessList.c | 970 ++++++++++++++++++++++++++++----------------- linux/LinuxProcessList.h | 17 +- linux/Platform.c | 387 +++++++++++++----- linux/Platform.h | 75 +++- linux/PressureStallMeter.c | 20 +- linux/PressureStallMeter.h | 1 + linux/ProcessField.h | 6 +- linux/SELinuxMeter.c | 11 +- linux/SELinuxMeter.h | 1 + linux/SystemdMeter.c | 98 +++-- linux/SystemdMeter.h | 1 + linux/ZramMeter.c | 8 +- linux/ZramMeter.h | 1 + linux/ZramStats.h | 6 +- 23 files changed, 1545 insertions(+), 1252 deletions(-) create mode 100644 linux/HugePageMeter.c create mode 100644 linux/HugePageMeter.h (limited to 'linux') diff --git a/linux/HugePageMeter.c b/linux/HugePageMeter.c new file mode 100644 index 0000000..8c637fd --- /dev/null +++ b/linux/HugePageMeter.c @@ -0,0 +1,106 @@ +/* +htop - HugePageMeter.c +(C) 2021 htop dev team +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + +#include "linux/HugePageMeter.h" + +#include +#include +#include + +#include "CRT.h" +#include "Macros.h" +#include "Object.h" +#include "ProcessList.h" +#include "RichString.h" +#include "linux/LinuxProcessList.h" + + +static const char* HugePageMeter_active_labels[4] = { NULL, NULL, NULL, NULL }; + +static const int HugePageMeter_attributes[] = { + HUGEPAGE_1, + HUGEPAGE_2, + HUGEPAGE_3, + HUGEPAGE_4, +}; + +static const char* const HugePageMeter_labels[] = { + " 64K:", " 128K:", " 256K:", " 512K:", + " 1M:", " 2M:", " 4M:", " 8M:", " 16M:", " 32M:", " 64M:", " 128M:", " 256M:", " 512M:", + " 1G:", " 2G:", " 4G:", " 8G:", " 16G:", " 32G:", " 64G:", " 128G:", " 256G:", " 512G:", +}; + +static void HugePageMeter_updateValues(Meter* this) { + assert(ARRAYSIZE(HugePageMeter_labels) == HTOP_HUGEPAGE_COUNT); + + char* buffer = this->txtBuffer; + size_t size = sizeof(this->txtBuffer); + int written; + memory_t usedTotal = 0; + unsigned nextUsed = 0; + + const LinuxProcessList* lpl = (const LinuxProcessList*) this->pl; + this->total = lpl->totalHugePageMem; + this->values[0] = 0; + HugePageMeter_active_labels[0] = " used:"; + for (unsigned i = 1; i < ARRAYSIZE(HugePageMeter_active_labels); i++) { + this->values[i] = NAN; + HugePageMeter_active_labels[i] = NULL; + } + for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) { + memory_t value = lpl->usedHugePageMem[i]; + if (value != MEMORY_MAX) { + this->values[nextUsed] = value; + usedTotal += value; + HugePageMeter_active_labels[nextUsed] = HugePageMeter_labels[i]; + if (++nextUsed == ARRAYSIZE(HugePageMeter_active_labels)) { + break; + } + } + } + + written = Meter_humanUnit(buffer, usedTotal, size); + METER_BUFFER_CHECK(buffer, size, written); + + METER_BUFFER_APPEND_CHR(buffer, size, '/'); + + Meter_humanUnit(buffer, this->total, size); +} + +static void HugePageMeter_display(const Object* cast, RichString* out) { + char buffer[50]; + const Meter* this = (const Meter*)cast; + + RichString_writeAscii(out, CRT_colors[METER_TEXT], ":"); + Meter_humanUnit(buffer, this->total, sizeof(buffer)); + RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); + + for (unsigned i = 0; i < ARRAYSIZE(HugePageMeter_active_labels); i++) { + if (isnan(this->values[i])) { + break; + } + RichString_appendAscii(out, CRT_colors[METER_TEXT], HugePageMeter_active_labels[i]); + Meter_humanUnit(buffer, this->values[i], sizeof(buffer)); + RichString_appendAscii(out, CRT_colors[HUGEPAGE_1 + i], buffer); + } +} + +const MeterClass HugePageMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = HugePageMeter_display, + }, + .updateValues = HugePageMeter_updateValues, + .defaultMode = BAR_METERMODE, + .maxItems = ARRAYSIZE(HugePageMeter_active_labels), + .total = 100.0, + .attributes = HugePageMeter_attributes, + .name = "HugePages", + .uiName = "HugePages", + .caption = "HP" +}; diff --git a/linux/HugePageMeter.h b/linux/HugePageMeter.h new file mode 100644 index 0000000..e3b867d --- /dev/null +++ b/linux/HugePageMeter.h @@ -0,0 +1,15 @@ +#ifndef HEADER_HugePageMeter +#define HEADER_HugePageMeter +/* +htop - HugePageMeter.h +(C) 2021 htop dev team +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Meter.h" + + +extern const MeterClass HugePageMeter_class; + +#endif /* HEADER_HugePageMeter */ diff --git a/linux/IOPriority.h b/linux/IOPriority.h index 551a7d5..2b37cb0 100644 --- a/linux/IOPriority.h +++ b/linux/IOPriority.h @@ -30,8 +30,6 @@ typedef int IOPriority; #define IOPriority_tuple(class_, data_) (((class_) << IOPRIO_CLASS_SHIFT) | (data_)) -#define IOPriority_error 0xffffffff - #define IOPriority_None IOPriority_tuple(IOPRIO_CLASS_NONE, 0) #define IOPriority_Idle IOPriority_tuple(IOPRIO_CLASS_IDLE, 7) diff --git a/linux/IOPriorityPanel.c b/linux/IOPriorityPanel.c index 25d2199..1bcbf4b 100644 --- a/linux/IOPriorityPanel.c +++ b/linux/IOPriorityPanel.c @@ -5,7 +5,7 @@ Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ -#include "IOPriorityPanel.h" +#include "linux/IOPriorityPanel.h" #include #include @@ -14,6 +14,7 @@ in the source distribution for its full text. #include "ListItem.h" #include "Object.h" #include "XUtils.h" +#include "IOPriority.h" Panel* IOPriorityPanel_new(IOPriority currPrio) { diff --git a/linux/IOPriorityPanel.h b/linux/IOPriorityPanel.h index 2ac4b31..3aa7610 100644 --- a/linux/IOPriorityPanel.h +++ b/linux/IOPriorityPanel.h @@ -8,7 +8,7 @@ in the source distribution for its full text. */ #include "Panel.h" -#include "IOPriority.h" +#include "linux/IOPriority.h" Panel* IOPriorityPanel_new(IOPriority currPrio); diff --git a/linux/LibSensors.c b/linux/LibSensors.c index 158829a..9a27fe5 100644 --- a/linux/LibSensors.c +++ b/linux/LibSensors.c @@ -1,27 +1,53 @@ -#include "LibSensors.h" +#include "linux/LibSensors.h" + +#include "config.h" #ifdef HAVE_SENSORS_SENSORS_H +#include #include #include +#include #include +#include +#include +#include #include +#include "Macros.h" #include "XUtils.h" +#include "linux/LinuxProcessList.h" + + +#ifdef BUILD_STATIC +#define sym_sensors_init sensors_init +#define sym_sensors_cleanup sensors_cleanup +#define sym_sensors_get_detected_chips sensors_get_detected_chips +#define sym_sensors_get_features sensors_get_features +#define sym_sensors_get_subfeature sensors_get_subfeature +#define sym_sensors_get_value sensors_get_value + +#else static int (*sym_sensors_init)(FILE*); static void (*sym_sensors_cleanup)(void); static const sensors_chip_name* (*sym_sensors_get_detected_chips)(const sensors_chip_name*, int*); -static int (*sym_sensors_snprintf_chip_name)(char*, size_t, const sensors_chip_name*); static const sensors_feature* (*sym_sensors_get_features)(const sensors_chip_name*, int*); static const sensors_subfeature* (*sym_sensors_get_subfeature)(const sensors_chip_name*, const sensors_feature*, sensors_subfeature_type); static int (*sym_sensors_get_value)(const sensors_chip_name*, int, double*); -static char* (*sym_sensors_get_label)(const sensors_chip_name*, const sensors_feature*); static void* dlopenHandle = NULL; -int LibSensors_init(FILE* input) { +#endif /* BUILD_STATIC */ + +int LibSensors_init(void) { +#ifdef BUILD_STATIC + + return sym_sensors_init(NULL); + +#else + if (!dlopenHandle) { /* Find the unversioned libsensors.so (symlink) and prefer that, but Debian has .so.5 and Fedora .so.4 without matching symlinks (unless people install the -dev packages) */ @@ -45,16 +71,15 @@ int LibSensors_init(FILE* input) { resolve(sensors_init); resolve(sensors_cleanup); resolve(sensors_get_detected_chips); - resolve(sensors_snprintf_chip_name); resolve(sensors_get_features); resolve(sensors_get_subfeature); resolve(sensors_get_value); - resolve(sensors_get_label); #undef resolve } - return sym_sensors_init(input); + return sym_sensors_init(NULL); + dlfailure: if (dlopenHandle) { @@ -62,108 +87,183 @@ dlfailure: dlopenHandle = NULL; } return -1; + +#endif /* BUILD_STATIC */ } void LibSensors_cleanup(void) { +#ifdef BUILD_STATIC + + sym_sensors_cleanup(); + +#else + if (dlopenHandle) { sym_sensors_cleanup(); dlclose(dlopenHandle); dlopenHandle = NULL; } + +#endif /* BUILD_STATIC */ } -void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int cpuCount) { - for (unsigned int i = 0; i <= cpuCount; i++) - cpus[i].temperature = NAN; +int LibSensors_reload(void) { +#ifndef BUILD_STATIC + if (!dlopenHandle) { + errno = ENOTSUP; + return -1; + } +#endif /* !BUILD_STATIC */ + + sym_sensors_cleanup(); + return sym_sensors_init(NULL); +} + +static int tempDriverPriority(const sensors_chip_name* chip) { + static const struct TempDriverDefs { + const char* prefix; + int priority; + } tempDrivers[] = { + { "coretemp", 0 }, + { "via_cputemp", 0 }, + { "cpu_thermal", 0 }, + { "k10temp", 0 }, + { "zenpower", 0 }, + /* Low priority drivers */ + { "acpitz", 1 }, + }; + + for (size_t i = 0; i < ARRAYSIZE(tempDrivers); i++) + if (String_eq(chip->prefix, tempDrivers[i].prefix)) + return tempDrivers[i].priority; + + return -1; +} +void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs) { + assert(existingCPUs > 0 && existingCPUs < 16384); + double data[existingCPUs + 1]; + for (size_t i = 0; i < existingCPUs + 1; i++) + data[i] = NAN; + +#ifndef BUILD_STATIC if (!dlopenHandle) - return; + goto out; +#endif /* !BUILD_STATIC */ unsigned int coreTempCount = 0; + int topPriority = 99; int n = 0; - for (const sensors_chip_name *chip = sym_sensors_get_detected_chips(NULL, &n); chip; chip = sym_sensors_get_detected_chips(NULL, &n)) { - char buffer[32]; - sym_sensors_snprintf_chip_name(buffer, sizeof(buffer), chip); - if (!String_startsWith(buffer, "coretemp") && - !String_startsWith(buffer, "cpu_thermal") && - !String_startsWith(buffer, "k10temp") && - !String_startsWith(buffer, "zenpower")) + for (const sensors_chip_name* chip = sym_sensors_get_detected_chips(NULL, &n); chip; chip = sym_sensors_get_detected_chips(NULL, &n)) { + const int priority = tempDriverPriority(chip); + if (priority < 0) + continue; + + if (priority > topPriority) continue; + if (priority < topPriority) { + /* Clear data from lower priority sensor */ + for (size_t i = 0; i < existingCPUs + 1; i++) + data[i] = NAN; + } + + topPriority = priority; + int m = 0; - for (const sensors_feature *feature = sym_sensors_get_features(chip, &m); feature; feature = sym_sensors_get_features(chip, &m)) { + for (const sensors_feature* feature = sym_sensors_get_features(chip, &m); feature; feature = sym_sensors_get_features(chip, &m)) { if (feature->type != SENSORS_FEATURE_TEMP) continue; - char* label = sym_sensors_get_label(chip, feature); - if (!label) + if (!feature->name || !String_startsWith(feature->name, "temp")) continue; - unsigned int tempId; - if (String_startsWith(label, "Package ")) { - tempId = 0; - } else if (String_startsWith(label, "temp")) { - /* Raspberry Pi has only temp1 */ - tempId = 0; - } else if (String_startsWith(label, "Tdie")) { - tempId = 0; - } else if (String_startsWith(label, "Core ")) { - tempId = 1 + atoi(label + strlen("Core ")); - } else { - tempId = UINT_MAX; - } + unsigned long int tempID = strtoul(feature->name + strlen("temp"), NULL, 10); + if (tempID == 0 || tempID == ULONG_MAX) + continue; - free(label); + /* Feature name IDs start at 1, adjust to start at 0 to match data indices */ + tempID--; - if (tempId > cpuCount) + if (tempID > existingCPUs) 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; + const sensors_subfeature* subFeature = sym_sensors_get_subfeature(chip, feature, SENSORS_SUBFEATURE_TEMP_INPUT); + if (!subFeature) + continue; - cpus[tempId].temperature = temp; - if (tempId > 0) + double temp; + int r = sym_sensors_get_value(chip, subFeature->number, &temp); + if (r != 0) + continue; + + /* If already set, e.g. Ryzen reporting platform temperature for each die, use the bigger one */ + if (isnan(data[tempID])) { + data[tempID] = temp; + if (tempID > 0) coreTempCount++; + } else { + data[tempID] = MAXIMUM(data[tempID], temp); } } } - const double packageTemp = cpus[0].temperature; + /* Adjust data for chips not providing a platform temperature */ + if (coreTempCount + 1 == activeCPUs || coreTempCount + 1 == activeCPUs / 2) { + memmove(&data[1], &data[0], existingCPUs * sizeof(*data)); + data[0] = NAN; + coreTempCount++; - /* Only package temperature - copy to all cpus */ - if (coreTempCount == 0 && !isnan(packageTemp)) { - for (unsigned int i = 1; i <= cpuCount; i++) - cpus[i].temperature = packageTemp; + /* Check for further adjustments */ + } - return; + /* Only package temperature - copy to all cores */ + if (coreTempCount == 0 && !isnan(data[0])) { + for (unsigned int i = 1; i <= existingCPUs; i++) + data[i] = data[0]; + + /* No further adjustments */ + goto out; } /* No package temperature - set to max core temperature */ - if (isnan(packageTemp) && coreTempCount != 0) { + if (isnan(data[0]) && coreTempCount != 0) { double maxTemp = NAN; - for (unsigned int i = 1; i <= cpuCount; i++) { - const double coreTemp = cpus[i].temperature; - if (isnan(coreTemp)) + for (unsigned int i = 1; i <= existingCPUs; i++) { + if (isnan(data[i])) continue; - maxTemp = MAXIMUM(maxTemp, coreTemp); + maxTemp = MAXIMUM(maxTemp, data[i]); } - cpus[0].temperature = maxTemp; + data[0] = maxTemp; + + /* Check for further adjustments */ + } + + /* Only temperature for core 0, maybe Ryzen - copy to all other cores */ + if (coreTempCount == 1 && !isnan(data[1])) { + for (unsigned int i = 2; i <= existingCPUs; i++) + data[i] = data[1]; + + /* No further adjustments */ + goto out; } /* Half the temperatures, probably HT/SMT - copy to second half */ - const unsigned int delta = cpuCount / 2; + const unsigned int delta = activeCPUs / 2; if (coreTempCount == delta) { - for (unsigned int i = 1; i <= delta; i++) - cpus[i + delta].temperature = cpus[i].temperature; + memcpy(&data[delta + 1], &data[1], delta * sizeof(*data)); + + /* No further adjustments */ + goto out; } + +out: + for (unsigned int i = 0; i <= existingCPUs; i++) + cpus[i].temperature = data[i]; } #endif /* HAVE_SENSORS_SENSORS_H */ diff --git a/linux/LibSensors.h b/linux/LibSensors.h index cceeedb..aa89979 100644 --- a/linux/LibSensors.h +++ b/linux/LibSensors.h @@ -1,16 +1,13 @@ #ifndef HEADER_LibSensors #define HEADER_LibSensors -#include "config.h" // IWYU pragma: keep +#include "linux/LinuxProcessList.h" -#include -#include "LinuxProcessList.h" - - -int LibSensors_init(FILE* input); +int LibSensors_init(void); void LibSensors_cleanup(void); +int LibSensors_reload(void); -void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int cpuCount); +void LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs); #endif /* HEADER_LibSensors */ diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index ce6d34d..75638e4 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -6,12 +6,11 @@ Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ -#include "LinuxProcess.h" +#include "linux/LinuxProcess.h" #include #include #include -#include #include #include @@ -21,15 +20,13 @@ in the source distribution for its full text. #include "ProvideCurses.h" #include "RichString.h" #include "XUtils.h" +#include "linux/IOPriority.h" /* semi-global */ int pageSize; int pageSizeKB; -/* Used to identify kernel threads in Comm and Exe columns */ -static const char *const kthreadID = "KTHREAD"; - const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, }, [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, }, @@ -38,34 +35,34 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, .pidColumn = true, }, [PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, .pidColumn = true, }, [SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .flags = 0, .pidColumn = true, }, - [TTY_NR] = { .name = "TTY_NR", .title = "TTY ", .description = "Controlling terminal", .flags = 0, }, + [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, }, [TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, .pidColumn = true, }, - [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, }, - [CMINFLT] = { .name = "CMINFLT", .title = " CMINFLT ", .description = "Children processes' minor faults", .flags = 0, }, - [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, }, - [CMAJFLT] = { .name = "CMAJFLT", .title = " CMAJFLT ", .description = "Children processes' major faults", .flags = 0, }, - [UTIME] = { .name = "UTIME", .title = " UTIME+ ", .description = "User CPU time - time the process spent executing in user mode", .flags = 0, }, - [STIME] = { .name = "STIME", .title = " STIME+ ", .description = "System CPU time - time the kernel spent running system calls for this process", .flags = 0, }, - [CUTIME] = { .name = "CUTIME", .title = " CUTIME+ ", .description = "Children processes' user CPU time", .flags = 0, }, - [CSTIME] = { .name = "CSTIME", .title = " CSTIME+ ", .description = "Children processes' system CPU time", .flags = 0, }, + [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, }, + [CMINFLT] = { .name = "CMINFLT", .title = " CMINFLT ", .description = "Children processes' minor faults", .flags = 0, .defaultSortDesc = true, }, + [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, }, + [CMAJFLT] = { .name = "CMAJFLT", .title = " CMAJFLT ", .description = "Children processes' major faults", .flags = 0, .defaultSortDesc = true, }, + [UTIME] = { .name = "UTIME", .title = " UTIME+ ", .description = "User CPU time - time the process spent executing in user mode", .flags = 0, .defaultSortDesc = true, }, + [STIME] = { .name = "STIME", .title = " STIME+ ", .description = "System CPU time - time the kernel spent running system calls for this process", .flags = 0, .defaultSortDesc = true, }, + [CUTIME] = { .name = "CUTIME", .title = " CUTIME+ ", .description = "Children processes' user CPU time", .flags = 0, .defaultSortDesc = true, }, + [CSTIME] = { .name = "CSTIME", .title = " CSTIME+ ", .description = "Children processes' system CPU time", .flags = 0, .defaultSortDesc = true, }, [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, }, [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, }, [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, }, + [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, }, [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, }, - [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, }, - [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, }, - [M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, }, - [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, }, - [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, }, - [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, }, - [M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process (unused since Linux 2.6; always 0)", .flags = 0, }, + [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, + [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, + [M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, .defaultSortDesc = true, }, + [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, .defaultSortDesc = true, }, + [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, }, + [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, .defaultSortDesc = true, }, [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, }, - [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, }, - [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, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, }, + [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, }, + [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, - [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, }, - [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, }, + [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, + [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, }, [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, }, #ifdef HAVE_OPENVZ [CTID] = { .name = "CTID", .title = " CTID ", .description = "OpenVZ container ID (a.k.a. virtual environment ID)", .flags = PROCESS_FLAG_LINUX_OPENVZ, }, @@ -74,45 +71,36 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { #ifdef HAVE_VSERVER [VXID] = { .name = "VXID", .title = " VXID ", .description = "VServer process ID", .flags = PROCESS_FLAG_LINUX_VSERVER, }, #endif - [RCHAR] = { .name = "RCHAR", .title = " RD_CHAR ", .description = "Number of bytes the process has read", .flags = PROCESS_FLAG_IO, }, - [WCHAR] = { .name = "WCHAR", .title = " WR_CHAR ", .description = "Number of bytes the process has written", .flags = PROCESS_FLAG_IO, }, - [SYSCR] = { .name = "SYSCR", .title = " RD_SYSC ", .description = "Number of read(2) syscalls for the process", .flags = PROCESS_FLAG_IO, }, - [SYSCW] = { .name = "SYSCW", .title = " WR_SYSC ", .description = "Number of write(2) syscalls for the process", .flags = PROCESS_FLAG_IO, }, - [RBYTES] = { .name = "RBYTES", .title = " IO_RBYTES ", .description = "Bytes of read(2) I/O for the process", .flags = PROCESS_FLAG_IO, }, - [WBYTES] = { .name = "WBYTES", .title = " IO_WBYTES ", .description = "Bytes of write(2) I/O for the process", .flags = PROCESS_FLAG_IO, }, - [CNCLWB] = { .name = "CNCLWB", .title = " IO_CANCEL ", .description = "Bytes of cancelled write(2) I/O", .flags = PROCESS_FLAG_IO, }, - [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, }, - [IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, }, - [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, }, + [RCHAR] = { .name = "RCHAR", .title = "RCHAR ", .description = "Number of bytes the process has read", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [WCHAR] = { .name = "WCHAR", .title = "WCHAR ", .description = "Number of bytes the process has written", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [SYSCR] = { .name = "SYSCR", .title = " READ_SYSC ", .description = "Number of read(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [SYSCW] = { .name = "SYSCW", .title = " WRITE_SYSC ", .description = "Number of write(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [RBYTES] = { .name = "RBYTES", .title = " IO_R ", .description = "Bytes of read(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [WBYTES] = { .name = "WBYTES", .title = " IO_W ", .description = "Bytes of write(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [CNCLWB] = { .name = "CNCLWB", .title = " IO_C ", .description = "Bytes of cancelled write(2) I/O", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [CGROUP] = { .name = "CGROUP", .title = " CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, }, - [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, }, + [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, }, [IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, }, #ifdef HAVE_DELAYACCT - [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, }, - [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, }, - [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWAPD% ", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, }, + [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD%", .description = "CPU delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, + [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, + [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWPD%", .description = "Swapin delay %", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, }, #endif - [M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it.", .flags = PROCESS_FLAG_LINUX_SMAPS, }, - [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, }, + [M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, + [M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, + [M_PSSWP] = { .name = "M_PSSWP", .title = " PSSWP ", .description = "shows proportional swap share of this mapping, unlike \"Swap\", this does not take into account swapped out page of underlying shmem objects", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, + [CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, .defaultSortDesc = true, }, [SECATTR] = { .name = "SECATTR", .title = " Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, }, [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process 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, }, + [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, + [AUTOGROUP_ID] = { .name = "AUTOGROUP_ID", .title = "AGRP", .description = "The autogroup identifier of the process", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, + [AUTOGROUP_NICE] = { .name = "AUTOGROUP_NICE", .title = " ANI", .description = "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, }; -/* This function returns the string displayed in Command column, so that sorting - * happens on what is displayed - whether comm, full path, basename, etc.. So - * this follows LinuxProcess_writeField(COMM) and LinuxProcess_writeCommand */ -static const char* LinuxProcess_getCommandStr(const Process *this) { - const LinuxProcess *lp = (const LinuxProcess *)this; - if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !lp->mergedCommand.str) { - return this->comm; - } - return lp->mergedCommand.str; -} - Process* LinuxProcess_new(const Settings* settings) { LinuxProcess* this = xCalloc(1, sizeof(LinuxProcess)); Object_setClass(this, Class(LinuxProcess)); @@ -127,12 +115,7 @@ void Process_delete(Object* cast) { #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); } @@ -175,429 +158,35 @@ bool LinuxProcess_setIOPriority(Process* this, Arg ioprio) { return (LinuxProcess_updateIOPriority((LinuxProcess*)this) == ioprio.i); } -#ifdef HAVE_DELAYACCT -static void LinuxProcess_printDelay(float delay_percent, char* buffer, int n) { - if (isnan(delay_percent)) { - xSnprintf(buffer, n, " N/A "); - } else { - xSnprintf(buffer, n, "%4.1f ", delay_percent); - } -} -#endif - -/* -TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is -not available in an userspace header - so define it. Note: when colorizing a -basename with the comm prefix, the entire basename (not just the comm prefix) is -colorized for better readability, and it is implicit that only upto -(TASK_COMM_LEN - 1) could be comm -*/ -#define TASK_COMM_LEN 16 - -static bool findCommInCmdline(const char *comm, const char *cmdline, int cmdlineBasenameOffset, int *pCommStart, int *pCommEnd) { - /* Try to find procComm in tokenized cmdline - this might in rare cases - * mis-identify a string or fail, if comm or cmdline had been unsuitably - * modified by the process */ - const char *tokenBase; - size_t tokenLen; - const size_t commLen = strlen(comm); - - for (const char *token = cmdline + cmdlineBasenameOffset; *token; ) { - for (tokenBase = token; *token && *token != '\n'; ++token) { - if (*token == '/') { - tokenBase = token + 1; - } - } - tokenLen = token - tokenBase; - - if ((tokenLen == commLen || (tokenLen > commLen && commLen == (TASK_COMM_LEN - 1))) && - strncmp(tokenBase, comm, commLen) == 0) { - *pCommStart = tokenBase - cmdline; - *pCommEnd = token - cmdline; - return true; - } - if (*token) { - do { - ++token; - } while ('\n' == *token); - } - } - return false; -} - -static int matchCmdlinePrefixWithExeSuffix(const char *cmdline, int cmdlineBaseOffset, const char *exe, int exeBaseOffset, int exeBaseLen) { - int matchLen; /* matching length to be returned */ - char delim; /* delimiter following basename */ - - /* cmdline prefix is an absolute path: it must match whole exe. */ - if (cmdline[0] == '/') { - matchLen = exeBaseLen + exeBaseOffset; - if (strncmp(cmdline, exe, matchLen) == 0) { - delim = cmdline[matchLen]; - if (delim == 0 || delim == '\n' || delim == ' ') { - return matchLen; - } - } - return 0; - } - - /* cmdline prefix is a relative path: We need to first match the basename at - * cmdlineBaseOffset and then reverse match the cmdline prefix with the exe - * suffix. But there is a catch: Some processes modify their cmdline in ways - * that make htop's identification of the basename in cmdline unreliable. - * For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to - * "gdm-session-worker [pam/gdm-autologin]" and htop ends up with - * procCmdlineBasenameOffset at "gdm-autologin]". This issue could arise with - * chrome as well as it stores in cmdline its concatenated argument vector, - * without NUL delimiter between the arguments (which may contain a '/') - * - * So if needed, we adjust cmdlineBaseOffset to the previous (if any) - * component of the cmdline relative path, and retry the procedure. */ - bool delimFound; /* if valid basename delimiter found */ - do { - /* match basename */ - matchLen = exeBaseLen + cmdlineBaseOffset; - if (cmdlineBaseOffset < exeBaseOffset && - strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) { - delim = cmdline[matchLen]; - if (delim == 0 || delim == '\n' || delim == ' ') { - int i, j; - /* reverse match the cmdline prefix and exe suffix */ - for (i = cmdlineBaseOffset - 1, j = exeBaseOffset - 1; - i >= 0 && cmdline[i] == exe[j]; --i, --j) - ; - /* full match, with exe suffix being a valid relative path */ - if (i < 0 && exe[j] == '/') { - return matchLen; - } - } - } - /* Try to find the previous potential cmdlineBaseOffset - it would be - * preceded by '/' or nothing, and delimited by ' ' or '\n' */ - for (delimFound = false, cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) { - if (delimFound) { - if (cmdline[cmdlineBaseOffset - 1] == '/') { - break; - } - } else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\n') { - delimFound = true; - } - } - } while (delimFound); - - return 0; -} - -/* stpcpy, but also converts newlines to spaces */ -static inline char *stpcpyWithNewlineConversion(char *dstStr, const char *srcStr) { - for (; *srcStr; ++srcStr) { - *dstStr++ = (*srcStr == '\n') ? ' ' : *srcStr; - } - *dstStr = 0; - return dstStr; +bool LinuxProcess_isAutogroupEnabled(void) { + char buf[16]; + if (xReadfile(PROCDIR "/sys/kernel/sched_autogroup_enabled", buf, sizeof(buf)) < 0) + return false; + return buf[0] == '1'; } -/* -This function makes the merged Command string. It also stores the offsets of the -basename, comm w.r.t the merged Command string - these offsets will be used by -LinuxProcess_writeCommand() for coloring. The merged Command string is also -returned by LinuxProcess_getCommandStr() for searching, sorting and filtering. -*/ -void LinuxProcess_makeCommandStr(Process* this) { - LinuxProcess *lp = (LinuxProcess *)this; - LinuxProcessMergedCommand *mc = &lp->mergedCommand; - - bool showMergedCommand = this->settings->showMergedCommand; - bool showProgramPath = this->settings->showProgramPath; - bool searchCommInCmdline = this->settings->findCommInCmdline; - bool stripExeFromCmdline = this->settings->stripExeFromCmdline; - - /* lp->mergedCommand.str needs updating only if its state or contents changed. - * Its content is based on the fields cmdline, comm, and exe. */ - if ( - mc->prevMergeSet == showMergedCommand && - mc->prevPathSet == showProgramPath && - mc->prevCommSet == searchCommInCmdline && - mc->prevCmdlineSet == stripExeFromCmdline && - !mc->cmdlineChanged && - !mc->commChanged && - !mc->exeChanged - ) { - return; - } - - /* The field separtor "│" has been chosen such that it will not match any - * valid string used for searching or filtering */ - const char *SEPARATOR = CRT_treeStr[TREE_STR_VERT]; - const int SEPARATOR_LEN = strlen(SEPARATOR); - - /* Check for any changed fields since we last built this string */ - if (mc->cmdlineChanged || mc->commChanged || mc->exeChanged) { - free(mc->str); - /* Accommodate the column text, two field separators and terminating NUL */ - mc->str = xCalloc(1, mc->maxLen + 2*SEPARATOR_LEN + 1); - } - - /* Preserve the settings used in this run */ - mc->prevMergeSet = showMergedCommand; - mc->prevPathSet = showProgramPath; - mc->prevCommSet = searchCommInCmdline; - mc->prevCmdlineSet = stripExeFromCmdline; - - /* Mark everything as unchanged */ - mc->cmdlineChanged = false; - mc->commChanged = false; - mc->exeChanged = false; - - /* Clear any separators */ - mc->sep1 = 0; - mc->sep2 = 0; - - /* Clear any highlighting locations */ - mc->baseStart = 0; - mc->baseEnd = 0; - mc->commStart = 0; - mc->commEnd = 0; - - const char *cmdline = this->comm; - const char *procExe = lp->procExe; - const char *procComm = lp->procComm; - - char *strStart = mc->str; - char *str = strStart; - - int cmdlineBasenameOffset = lp->procCmdlineBasenameOffset; - int cmdlineBasenameEnd = lp->procCmdlineBasenameEnd; - - if (!cmdline) { - cmdlineBasenameOffset = 0; - cmdlineBasenameEnd = 0; - cmdline = "(zombie)"; - } - - assert(cmdlineBasenameOffset >= 0); - assert(cmdlineBasenameOffset <= (int)strlen(cmdline)); - - 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 = cmdlineBasenameEnd; - } else { - (void) stpcpyWithNewlineConversion(str, cmdline + cmdlineBasenameOffset); - mc->baseStart = 0; - mc->baseEnd = cmdlineBasenameEnd - 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; - - assert(exeBasenameOffset >= 0); - assert(exeBasenameOffset <= (int)strlen(procExe)); - - /* 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 */ +bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) { + char buffer[256]; + xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", this->pid); + + FILE* file = fopen(buffer, "r+"); + if (!file) + return false; + + long int identity; + int nice; + int ok = fscanf(file, "/autogroup-%ld nice %d", &identity, &nice); + bool success; + if (ok == 2) { + rewind(file); + xSnprintf(buffer, sizeof(buffer), "%d", nice + delta.i); + success = fputs(buffer, file) > 0; } else { - 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_appendWide(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 - baseStart); - RichString_setAttrn(str, A_BOLD | commAttr, baseEnd, commEnd - baseEnd); - } else if (commEnd < baseEnd) { - RichString_setAttrn(str, A_BOLD | commAttr, commStart, commEnd - commStart); - RichString_setAttrn(str, A_BOLD | baseAttr, commEnd, baseEnd - commEnd); - } else { - // Actually should be highlighted commAttr, but marked baseAttr to reduce visual noise - RichString_setAttrn(str, A_BOLD | baseAttr, commStart, commEnd - commStart); - } - - baseStart = baseEnd; - } else { - RichString_setAttrn(str, commAttr, commStart, commEnd - commStart); - } + success = false; } - if (baseStart < baseEnd && highlightBaseName) { - RichString_setAttrn(str, baseAttr, baseStart, baseEnd - baseStart); - } - - if (mc->sep1) - RichString_setAttrn(str, CRT_colors[FAILED_READ], strStart + mc->sep1, 1); - if (mc->sep2) - RichString_setAttrn(str, CRT_colors[FAILED_READ], strStart + mc->sep2, 1); -} - -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 ? TREE_STR_BEND : TREE_STR_RTEE]; - xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] ); - RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer); - LinuxProcess_writeCommand(this, attr, baseattr, str); - } + fclose(file); + return success; } static void LinuxProcess_writeField(const Process* this, RichString* str, ProcessField field) { @@ -607,48 +196,38 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; switch (field) { - case TTY_NR: { - if (lp->ttyDevice) { - xSnprintf(buffer, n, "%-9s", lp->ttyDevice + 5 /* skip "/dev/" */); - } else { - attr = CRT_colors[PROCESS_SHADOW]; - xSnprintf(buffer, n, "? "); - } - break; - } - case CMINFLT: Process_colorNumber(str, lp->cminflt, coloring); return; - case CMAJFLT: Process_colorNumber(str, lp->cmajflt, coloring); return; - case M_DRS: Process_humanNumber(str, lp->m_drs * pageSizeKB, coloring); return; - case M_DT: Process_humanNumber(str, lp->m_dt * pageSizeKB, coloring); return; + case CMINFLT: Process_printCount(str, lp->cminflt, coloring); return; + case CMAJFLT: Process_printCount(str, lp->cmajflt, coloring); return; + case M_DRS: Process_printBytes(str, lp->m_drs * pageSize, coloring); return; case M_LRS: if (lp->m_lrs) { - Process_humanNumber(str, lp->m_lrs * pageSizeKB, coloring); + Process_printBytes(str, lp->m_lrs * pageSize, coloring); return; } attr = CRT_colors[PROCESS_SHADOW]; xSnprintf(buffer, n, " N/A "); break; - case M_TRS: Process_humanNumber(str, lp->m_trs * pageSizeKB, coloring); return; - case M_SHARE: Process_humanNumber(str, lp->m_share * pageSizeKB, coloring); return; - case M_PSS: Process_humanNumber(str, lp->m_pss, coloring); return; - case M_SWAP: Process_humanNumber(str, lp->m_swap, coloring); return; - case M_PSSWP: Process_humanNumber(str, lp->m_psswp, coloring); return; - case UTIME: Process_printTime(str, lp->utime); return; - case STIME: Process_printTime(str, lp->stime); return; - case CUTIME: Process_printTime(str, lp->cutime); return; - case CSTIME: Process_printTime(str, lp->cstime); return; - case RCHAR: Process_colorNumber(str, lp->io_rchar, coloring); return; - case WCHAR: Process_colorNumber(str, lp->io_wchar, coloring); return; - case SYSCR: Process_colorNumber(str, lp->io_syscr, coloring); return; - case SYSCW: Process_colorNumber(str, lp->io_syscw, coloring); return; - case RBYTES: Process_colorNumber(str, lp->io_read_bytes, coloring); return; - case WBYTES: Process_colorNumber(str, lp->io_write_bytes, coloring); return; - case CNCLWB: Process_colorNumber(str, lp->io_cancelled_write_bytes, coloring); return; - case IO_READ_RATE: Process_outputRate(str, buffer, n, lp->io_rate_read_bps, coloring); return; - case IO_WRITE_RATE: Process_outputRate(str, buffer, n, lp->io_rate_write_bps, coloring); return; + case M_TRS: Process_printBytes(str, lp->m_trs * pageSize, coloring); return; + case M_SHARE: Process_printBytes(str, lp->m_share * pageSize, coloring); return; + case M_PSS: Process_printKBytes(str, lp->m_pss, coloring); return; + case M_SWAP: Process_printKBytes(str, lp->m_swap, coloring); return; + case M_PSSWP: Process_printKBytes(str, lp->m_psswp, coloring); return; + case UTIME: Process_printTime(str, lp->utime, coloring); return; + case STIME: Process_printTime(str, lp->stime, coloring); return; + case CUTIME: Process_printTime(str, lp->cutime, coloring); return; + case CSTIME: Process_printTime(str, lp->cstime, coloring); return; + case RCHAR: Process_printBytes(str, lp->io_rchar, coloring); return; + case WCHAR: Process_printBytes(str, lp->io_wchar, coloring); return; + case SYSCR: Process_printCount(str, lp->io_syscr, coloring); return; + case SYSCW: Process_printCount(str, lp->io_syscw, coloring); return; + case RBYTES: Process_printBytes(str, lp->io_read_bytes, coloring); return; + case WBYTES: Process_printBytes(str, lp->io_write_bytes, coloring); return; + case CNCLWB: Process_printBytes(str, lp->io_cancelled_write_bytes, coloring); return; + case IO_READ_RATE: Process_printRate(str, lp->io_rate_read_bps, coloring); return; + case IO_WRITE_RATE: Process_printRate(str, lp->io_rate_write_bps, coloring); return; case IO_RATE: { - double totalRate = NAN; + double totalRate; if (!isnan(lp->io_rate_read_bps) && !isnan(lp->io_rate_write_bps)) totalRate = lp->io_rate_read_bps + lp->io_rate_write_bps; else if (!isnan(lp->io_rate_read_bps)) @@ -657,7 +236,8 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces totalRate = lp->io_rate_write_bps; else totalRate = NAN; - Process_outputRate(str, buffer, n, totalRate, coloring); return; + Process_printRate(str, totalRate, coloring); + return; } #ifdef HAVE_OPENVZ case CTID: xSnprintf(buffer, n, "%-8s ", lp->ctid ? lp->ctid : ""); break; @@ -687,9 +267,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces break; } #ifdef HAVE_DELAYACCT - case PERCENT_CPU_DELAY: LinuxProcess_printDelay(lp->cpu_delay_percent, buffer, n); break; - case PERCENT_IO_DELAY: LinuxProcess_printDelay(lp->blkio_delay_percent, buffer, n); break; - case PERCENT_SWAP_DELAY: LinuxProcess_printDelay(lp->swapin_delay_percent, buffer, n); break; + case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, &attr); break; + case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, &attr); break; + case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, &attr); break; #endif case CTXT: if (lp->ctxt_diff > 1000) { @@ -698,60 +278,37 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff); break; case SECATTR: snprintf(buffer, n, "%-30s ", lp->secattr ? lp->secattr : "?"); break; - case COMM: { - if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !lp->mergedCommand.str) { - Process_writeField(this, str, field); - } else { - LinuxProcess_writeCommandField(this, str, buffer, n, attr); - } - return; - } - case PROC_COMM: { - const char* procComm; - if (lp->procComm) { - attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM]; - procComm = lp->procComm; + case AUTOGROUP_ID: + if (lp->autogroup_id != -1) { + xSnprintf(buffer, n, "%4ld ", lp->autogroup_id); } else { attr = CRT_colors[PROCESS_SHADOW]; - procComm = Process_isKernelThread(lp) ? kthreadID : "N/A"; + xSnprintf(buffer, n, " N/A "); } - /* 15 being (TASK_COMM_LEN - 1) */ - Process_printLeftAlignedField(str, attr, procComm, 15); - return; - } - case PROC_EXE: { - const char* procExe; - if (lp->procExe) { - attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME]; - if (lp->procExeDeleted) - attr = CRT_colors[FAILED_READ]; - procExe = lp->procExe + lp->procExeBasenameOffset; + break; + case AUTOGROUP_NICE: + if (lp->autogroup_id != -1) { + xSnprintf(buffer, n, "%3d ", lp->autogroup_nice); + attr = lp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY] + : lp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] + : CRT_colors[PROCESS_SHADOW]; } else { attr = CRT_colors[PROCESS_SHADOW]; - procExe = Process_isKernelThread(lp) ? kthreadID : "N/A"; + xSnprintf(buffer, n, "N/A "); } - Process_printLeftAlignedField(str, attr, procExe, 15); - return; - } - case CWD: { - const char* cwd; - if (!lp->cwd) { - attr = CRT_colors[PROCESS_SHADOW]; - cwd = "N/A"; - } else if (String_startsWith(lp->cwd, "/proc/") && strstr(lp->cwd, " (deleted)") != NULL) { - attr = CRT_colors[PROCESS_SHADOW]; - cwd = "main thread terminated"; - } else { - cwd = lp->cwd; - } - Process_printLeftAlignedField(str, attr, cwd, 25); - return; - } + break; default: Process_writeField(this, str, field); return; } - RichString_appendWide(str, attr, buffer); + RichString_appendAscii(str, attr, buffer); +} + +static double adjustNaN(double num) { + if (isnan(num)) + return -0.0005; + + return num; } static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) { @@ -760,98 +317,84 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce switch (key) { case M_DRS: - return SPACESHIP_NUMBER(p2->m_drs, p1->m_drs); - case M_DT: - return SPACESHIP_NUMBER(p2->m_dt, p1->m_dt); + return SPACESHIP_NUMBER(p1->m_drs, p2->m_drs); case M_LRS: - return SPACESHIP_NUMBER(p2->m_lrs, p1->m_lrs); + return SPACESHIP_NUMBER(p1->m_lrs, p2->m_lrs); case M_TRS: - return SPACESHIP_NUMBER(p2->m_trs, p1->m_trs); + return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs); case M_SHARE: - return SPACESHIP_NUMBER(p2->m_share, p1->m_share); + return SPACESHIP_NUMBER(p1->m_share, p2->m_share); case M_PSS: - return SPACESHIP_NUMBER(p2->m_pss, p1->m_pss); + return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss); case M_SWAP: - return SPACESHIP_NUMBER(p2->m_swap, p1->m_swap); + return SPACESHIP_NUMBER(p1->m_swap, p2->m_swap); case M_PSSWP: - return SPACESHIP_NUMBER(p2->m_psswp, p1->m_psswp); + return SPACESHIP_NUMBER(p1->m_psswp, p2->m_psswp); case UTIME: - return SPACESHIP_NUMBER(p2->utime, p1->utime); + return SPACESHIP_NUMBER(p1->utime, p2->utime); case CUTIME: - return SPACESHIP_NUMBER(p2->cutime, p1->cutime); + return SPACESHIP_NUMBER(p1->cutime, p2->cutime); case STIME: - return SPACESHIP_NUMBER(p2->stime, p1->stime); + return SPACESHIP_NUMBER(p1->stime, p2->stime); case CSTIME: - return SPACESHIP_NUMBER(p2->cstime, p1->cstime); + return SPACESHIP_NUMBER(p1->cstime, p2->cstime); case RCHAR: - return SPACESHIP_NUMBER(p2->io_rchar, p1->io_rchar); + return SPACESHIP_NUMBER(p1->io_rchar, p2->io_rchar); case WCHAR: - return SPACESHIP_NUMBER(p2->io_wchar, p1->io_wchar); + return SPACESHIP_NUMBER(p1->io_wchar, p2->io_wchar); case SYSCR: - return SPACESHIP_NUMBER(p2->io_syscr, p1->io_syscr); + return SPACESHIP_NUMBER(p1->io_syscr, p2->io_syscr); case SYSCW: - return SPACESHIP_NUMBER(p2->io_syscw, p1->io_syscw); + return SPACESHIP_NUMBER(p1->io_syscw, p2->io_syscw); case RBYTES: - return SPACESHIP_NUMBER(p2->io_read_bytes, p1->io_read_bytes); + return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes); case WBYTES: - return SPACESHIP_NUMBER(p2->io_write_bytes, p1->io_write_bytes); + return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes); case CNCLWB: - return SPACESHIP_NUMBER(p2->io_cancelled_write_bytes, p1->io_cancelled_write_bytes); + return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes); case IO_READ_RATE: - return SPACESHIP_NUMBER(p2->io_rate_read_bps, p1->io_rate_read_bps); + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps), adjustNaN(p2->io_rate_read_bps)); case IO_WRITE_RATE: - return SPACESHIP_NUMBER(p2->io_rate_write_bps, p1->io_rate_write_bps); + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_write_bps)); case IO_RATE: - return SPACESHIP_NUMBER(p2->io_rate_read_bps + p2->io_rate_write_bps, p1->io_rate_read_bps + p1->io_rate_write_bps); + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps) + adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_read_bps) + adjustNaN(p2->io_rate_write_bps)); #ifdef HAVE_OPENVZ case CTID: return SPACESHIP_NULLSTR(p1->ctid, p2->ctid); case VPID: - return SPACESHIP_NUMBER(p2->vpid, p1->vpid); + return SPACESHIP_NUMBER(p1->vpid, p2->vpid); #endif #ifdef HAVE_VSERVER case VXID: - return SPACESHIP_NUMBER(p2->vxid, p1->vxid); + return SPACESHIP_NUMBER(p1->vxid, p2->vxid); #endif case CGROUP: return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup); case OOM: - return SPACESHIP_NUMBER(p2->oom, p1->oom); + return SPACESHIP_NUMBER(p1->oom, p2->oom); #ifdef HAVE_DELAYACCT case PERCENT_CPU_DELAY: - return SPACESHIP_NUMBER(p2->cpu_delay_percent, p1->cpu_delay_percent); + return SPACESHIP_NUMBER(p1->cpu_delay_percent, p2->cpu_delay_percent); case PERCENT_IO_DELAY: - return SPACESHIP_NUMBER(p2->blkio_delay_percent, p1->blkio_delay_percent); + return SPACESHIP_NUMBER(p1->blkio_delay_percent, p2->blkio_delay_percent); case PERCENT_SWAP_DELAY: - return SPACESHIP_NUMBER(p2->swapin_delay_percent, p1->swapin_delay_percent); + return SPACESHIP_NUMBER(p1->swapin_delay_percent, p2->swapin_delay_percent); #endif case IO_PRIORITY: return SPACESHIP_NUMBER(LinuxProcess_effectiveIOPriority(p1), LinuxProcess_effectiveIOPriority(p2)); case CTXT: - return SPACESHIP_NUMBER(p2->ctxt_diff, p1->ctxt_diff); + return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff); case SECATTR: return SPACESHIP_NULLSTR(p1->secattr, p2->secattr); - case PROC_COMM: { - const char *comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : ""); - const char *comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : ""); - return strcmp(comm1, comm2); - } - case PROC_EXE: { - const char *exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : ""); - const char *exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : ""); - return strcmp(exe1, exe2); - } - case CWD: - return SPACESHIP_NULLSTR(p1->cwd, p2->cwd); + case AUTOGROUP_ID: + return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id); + case AUTOGROUP_NICE: + return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice); default: return Process_compareByKey_Base(v1, v2, key); } } -bool Process_isThread(const Process* this) { - return (Process_isUserlandThread(this) || Process_isKernelThread(this)); -} - const ProcessClass LinuxProcess_class = { .super = { .extends = Class(Process), @@ -860,6 +403,5 @@ const ProcessClass LinuxProcess_class = { .compare = Process_compare }, .writeField = LinuxProcess_writeField, - .getCommandStr = LinuxProcess_getCommandStr, .compareByKey = LinuxProcess_compareByKey }; diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index 2e9a9d1..20399d9 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -11,8 +11,9 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep #include +#include -#include "IOPriority.h" +#include "linux/IOPriority.h" #include "Object.h" #include "Process.h" #include "Settings.h" @@ -27,45 +28,11 @@ in the source distribution for its full text. #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 #define PROCESS_FLAG_LINUX_DELAYACCT 0x00040000 - - -/* LinuxProcessMergedCommand is populated by LinuxProcess_makeCommandStr: It - * contains the merged Command string, and the information needed by - * LinuxProcess_writeCommand to color the string. str will be NULL for kernel - * threads and zombies */ -typedef struct LinuxProcessMergedCommand_ { - char *str; /* merged Command string */ - int maxLen; /* maximum expected length of Command string */ - int baseStart; /* basename's start offset */ - int baseEnd; /* basename's end offset */ - int commStart; /* comm's start offset */ - int commEnd; /* comm's end offset */ - int sep1; /* first field separator, used if non-zero */ - int sep2; /* second field separator, used if non-zero */ - bool separateComm; /* whether comm is a separate field */ - bool unmatchedExe; /* whether exe matched with cmdline */ - bool cmdlineChanged; /* whether cmdline changed */ - bool exeChanged; /* whether exe changed */ - bool commChanged; /* whether comm changed */ - bool prevMergeSet; /* whether showMergedCommand was set */ - bool prevPathSet; /* whether showProgramPath was set */ - bool prevCommSet; /* whether findCommInCmdline was set */ - bool prevCmdlineSet; /* whether findCommInCmdline was set */ -} LinuxProcessMergedCommand; +#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000 typedef struct LinuxProcess_ { Process super; - char *procComm; - char *procExe; - int procExeLen; - int procExeBasenameOffset; - bool procExeDeleted; - int procCmdlineBasenameOffset; - int procCmdlineBasenameEnd; - LinuxProcessMergedCommand mergedCommand; - bool isKernelThread; IOPriority ioPriority; unsigned long int cminflt; unsigned long int cmajflt; @@ -80,18 +47,37 @@ typedef struct LinuxProcess_ { long m_trs; long m_drs; long m_lrs; - long m_dt; + + /* Data read (in bytes) */ unsigned long long io_rchar; + + /* Data written (in bytes) */ unsigned long long io_wchar; + + /* Number of read(2) syscalls */ unsigned long long io_syscr; + + /* Number of write(2) syscalls */ unsigned long long io_syscw; + + /* Storage data read (in bytes) */ unsigned long long io_read_bytes; + + /* Storage data written (in bytes) */ unsigned long long io_write_bytes; + + /* Storage data cancelled (in bytes) */ unsigned long long io_cancelled_write_bytes; - unsigned long long io_rate_read_time; - unsigned long long io_rate_write_time; + + /* Point in time of last io scan (in milliseconds elapsed since the Epoch) */ + unsigned long long io_last_scan_time_ms; + + /* Storage data read (in bytes per second) */ double io_rate_read_bps; + + /* Storage data written (in bytes per second) */ double io_rate_write_bps; + #ifdef HAVE_OPENVZ char* ctid; pid_t vpid; @@ -101,7 +87,6 @@ typedef struct LinuxProcess_ { #endif char* cgroup; unsigned int oom; - char* ttyDevice; #ifdef HAVE_DELAYACCT unsigned long long int delay_read_time; unsigned long long cpu_delay_total; @@ -115,14 +100,11 @@ typedef struct LinuxProcess_ { unsigned long ctxt_diff; char* secattr; unsigned long long int last_mlrs_calctime; - char* cwd; -} LinuxProcess; - -#define Process_isKernelThread(_process) (((const LinuxProcess*)(_process))->isKernelThread) -static inline bool Process_isUserlandThread(const Process* this) { - return this->pid != this->tgid; -} + /* Autogroup scheduling (CFS) information */ + long int autogroup_id; + int autogroup_nice; +} LinuxProcess; extern int pageSize; @@ -140,9 +122,9 @@ IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this); bool LinuxProcess_setIOPriority(Process* this, Arg ioprio); -/* This function constructs the string that is displayed by - * LinuxProcess_writeCommand and also returned by LinuxProcess_getCommandStr */ -void LinuxProcess_makeCommandStr(Process *this); +bool LinuxProcess_isAutogroupEnabled(void); + +bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta); bool Process_isThread(const Process* this); diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index 434d070..8d82b00 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -7,7 +7,7 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep -#include "LinuxProcessList.h" +#include "linux/LinuxProcessList.h" #include #include @@ -20,9 +20,10 @@ in the source distribution for its full text. #include #include #include +#include +#include #include #include -#include #include #ifdef HAVE_DELAYACCT @@ -39,14 +40,15 @@ in the source distribution for its full text. #include "Compat.h" #include "CRT.h" -#include "LinuxProcess.h" #include "Macros.h" #include "Object.h" #include "Process.h" #include "Settings.h" #include "XUtils.h" +#include "linux/LinuxProcess.h" +#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep -#ifdef MAJOR_IN_MKDEV +#if defined(MAJOR_IN_MKDEV) #include #elif defined(MAJOR_IN_SYSMACROS) #include @@ -57,13 +59,7 @@ in the source distribution for its full text. #endif -// CentOS 6's kernel doesn't provide a definition of O_PATH -// based on definition taken from uapi/asm-generic/fcnth.h in Linux kernel tree -#ifndef O_PATH -# define O_PATH 010000000 -#endif - -static long long btime; +static long long btime = -1; static long jiffy; @@ -107,7 +103,7 @@ static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) { while (*at != '\0') { at = strchr(at, ' '); // skip first token while (*at == ' ') at++; // skip spaces - char* token = at; // mark beginning of path + const char* token = at; // mark beginning of path at = strchr(at, ' '); // find end of path *at = '\0'; at++; // clear and skip ttyDrivers[numDrivers].path = xStrdup(token); // save @@ -161,51 +157,90 @@ static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) { #endif -static int LinuxProcessList_computeCPUcount(void) { - FILE* file = fopen(PROCSTATFILE, "r"); - if (file == NULL) { - CRT_fatalError("Cannot open " PROCSTATFILE); - } +static void LinuxProcessList_updateCPUcount(ProcessList* super) { + /* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF + * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD + */ - int cpus = 0; - char buffer[PROC_LINE_LENGTH + 1]; - while (fgets(buffer, sizeof(buffer), file)) { - if (String_startsWith(buffer, "cpu")) { - cpus++; - } + LinuxProcessList* this = (LinuxProcessList*) super; + unsigned int existing = 0, active = 0; + + DIR* dir = opendir("/sys/devices/system/cpu"); + if (!dir) { + this->cpuData = xReallocArrayZero(this->cpuData, super->existingCPUs ? (super->existingCPUs + 1) : 0, 2, sizeof(CPUData)); + this->cpuData[0].online = true; /* average is always "online" */ + this->cpuData[1].online = true; + super->activeCPUs = 1; + super->existingCPUs = 1; + return; } - fclose(file); + unsigned int currExisting = super->existingCPUs; - /* subtract raw cpu entry */ - if (cpus > 0) { - cpus--; - } + const struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type != DT_DIR) + continue; - return cpus; -} + if (!String_startsWith(entry->d_name, "cpu")) + continue; -static void LinuxProcessList_updateCPUcount(LinuxProcessList* this) { - ProcessList* pl = &(this->super); - int cpus = LinuxProcessList_computeCPUcount(); - if (cpus == 0 || cpus == pl->cpuCount) - return; + char *endp; + unsigned long int id = strtoul(entry->d_name + 3, &endp, 10); + if (id == ULONG_MAX || endp == entry->d_name + 3 || *endp != '\0') + continue; + +#ifdef HAVE_OPENAT + int cpuDirFd = openat(dirfd(dir), entry->d_name, O_DIRECTORY | O_PATH | O_NOFOLLOW); + if (cpuDirFd < 0) + continue; +#else + char cpuDirFd[4096]; + xSnprintf(cpuDirFd, sizeof(cpuDirFd), "/sys/devices/system/cpu/%s", entry->d_name); +#endif - pl->cpuCount = cpus; - free(this->cpus); - this->cpus = xCalloc(cpus + 1, sizeof(CPUData)); + existing++; - for (int i = 0; i <= cpus; i++) { - this->cpus[i].totalTime = 1; - this->cpus[i].totalPeriod = 1; + /* readdir() iterates with no specific order */ + unsigned int max = MAXIMUM(existing, id + 1); + if (max > currExisting) { + this->cpuData = xReallocArrayZero(this->cpuData, currExisting ? (currExisting + 1) : 0, max + /* aggregate */ 1, sizeof(CPUData)); + this->cpuData[0].online = true; /* average is always "online" */ + currExisting = max; + } + + char buffer[8]; + ssize_t res = xReadfileat(cpuDirFd, "online", buffer, sizeof(buffer)); + /* If the file "online" does not exist or on failure count as active */ + if (res < 1 || buffer[0] != '0') { + active++; + this->cpuData[id + 1].online = true; + } else { + this->cpuData[id + 1].online = false; + } + + Compat_openatArgClose(cpuDirFd); } + + closedir(dir); + +#ifdef HAVE_SENSORS_SENSORS_H + /* When started with offline CPUs, libsensors does not monitor those, + * even when they become online. */ + if (super->existingCPUs != 0 && (active > super->activeCPUs || currExisting > super->existingCPUs)) + LibSensors_reload(); +#endif + + super->activeCPUs = active; + assert(existing == currExisting); + super->existingCPUs = currExisting; } -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { LinuxProcessList* this = xCalloc(1, sizeof(LinuxProcessList)); ProcessList* pl = &(this->super); - ProcessList_init(pl, Class(LinuxProcess), usersTable, pidMatchList, userId); + ProcessList_init(pl, Class(LinuxProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); LinuxProcessList_initTtyDrivers(this); // Initialize page size @@ -223,37 +258,27 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui this->haveSmapsRollup = (access(PROCDIR "/self/smaps_rollup", R_OK) == 0); // Read btime (the kernel boot time, as number of seconds since the epoch) - { - FILE* statfile = fopen(PROCSTATFILE, "r"); - if (statfile == NULL) - CRT_fatalError("Cannot open " PROCSTATFILE); - while (true) { - char buffer[PROC_LINE_LENGTH + 1]; - if (fgets(buffer, sizeof(buffer), statfile) == NULL) - break; - if (String_startsWith(buffer, "btime ") == false) - continue; - if (sscanf(buffer, "btime %lld\n", &btime) == 1) - break; - CRT_fatalError("Failed to parse btime from " PROCSTATFILE); - } - fclose(statfile); - - if (!btime) - CRT_fatalError("No btime in " PROCSTATFILE); + FILE* statfile = fopen(PROCSTATFILE, "r"); + if (statfile == NULL) + CRT_fatalError("Cannot open " PROCSTATFILE); + while (true) { + char buffer[PROC_LINE_LENGTH + 1]; + if (fgets(buffer, sizeof(buffer), statfile) == NULL) + break; + if (String_startsWith(buffer, "btime ") == false) + continue; + if (sscanf(buffer, "btime %lld\n", &btime) == 1) + break; + CRT_fatalError("Failed to parse btime from " PROCSTATFILE); } - // Initialize CPU count - { - int cpus = LinuxProcessList_computeCPUcount(); - pl->cpuCount = MAXIMUM(cpus, 1); - this->cpus = xCalloc(cpus + 1, sizeof(CPUData)); + fclose(statfile); - for (int i = 0; i <= cpus; i++) { - this->cpus[i].totalTime = 1; - this->cpus[i].totalPeriod = 1; - } - } + if (btime == -1) + CRT_fatalError("No btime in " PROCSTATFILE); + + // Initialize CPU count + LinuxProcessList_updateCPUcount(pl); return pl; } @@ -261,7 +286,7 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui void ProcessList_delete(ProcessList* pl) { LinuxProcessList* this = (LinuxProcessList*) pl; ProcessList_done(pl); - free(this->cpus); + free(this->cpuData); if (this->ttyDrivers) { for (int i = 0; this->ttyDrivers[i].path; i++) { free(this->ttyDrivers[i].path); @@ -281,91 +306,131 @@ static inline unsigned long long LinuxProcessList_adjustTime(unsigned long long return t * 100 / jiffy; } -static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, char* command, int* commLen) { +static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, char* command, size_t commLen) { LinuxProcess* lp = (LinuxProcess*) process; - const int commLenIn = *commLen; - *commLen = 0; char buf[MAX_READ + 1]; ssize_t r = xReadfileat(procFd, "stat", buf, sizeof(buf)); if (r < 0) return false; + /* (1) pid - %d */ assert(process->pid == atoi(buf)); char* location = strchr(buf, ' '); if (!location) return false; + /* (2) comm - (%s) */ location += 2; char* end = strrchr(location, ')'); if (!end) return false; - int commsize = MINIMUM(end - location, commLenIn - 1); - // deepcode ignore BufferOverflow: commsize is bounded by the allocated length passed in by commLen, saved into commLenIn - memcpy(command, location, commsize); - command[commsize] = '\0'; - *commLen = commsize; + String_safeStrncpy(command, location, MINIMUM((size_t)(end - location + 1), commLen)); + location = end + 2; + /* (3) state - %c */ process->state = location[0]; location += 2; + + /* (4) ppid - %d */ process->ppid = strtol(location, &location, 10); location += 1; - process->pgrp = strtoul(location, &location, 10); + + /* (5) pgrp - %d */ + process->pgrp = strtol(location, &location, 10); location += 1; - process->session = strtoul(location, &location, 10); + + /* (6) session - %d */ + process->session = strtol(location, &location, 10); location += 1; + + /* (7) tty_nr - %d */ process->tty_nr = strtoul(location, &location, 10); location += 1; + + /* (8) tpgid - %d */ process->tpgid = strtol(location, &location, 10); location += 1; - process->flags = strtoul(location, &location, 10); - location += 1; + + /* Skip (9) flags - %u */ + location = strchr(location, ' ') + 1; + + /* (10) minflt - %lu */ process->minflt = strtoull(location, &location, 10); location += 1; + + /* (11) cminflt - %lu */ lp->cminflt = strtoull(location, &location, 10); location += 1; + + /* (12) majflt - %lu */ process->majflt = strtoull(location, &location, 10); location += 1; + + /* (13) cmajflt - %lu */ lp->cmajflt = strtoull(location, &location, 10); location += 1; + + /* (14) utime - %lu */ lp->utime = LinuxProcessList_adjustTime(strtoull(location, &location, 10)); location += 1; + + /* (15) stime - %lu */ lp->stime = LinuxProcessList_adjustTime(strtoull(location, &location, 10)); location += 1; + + /* (16) cutime - %ld */ lp->cutime = LinuxProcessList_adjustTime(strtoull(location, &location, 10)); location += 1; + + /* (17) cstime - %ld */ lp->cstime = LinuxProcessList_adjustTime(strtoull(location, &location, 10)); location += 1; + + /* (18) priority - %ld */ process->priority = strtol(location, &location, 10); location += 1; + + /* (19) nice - %ld */ process->nice = strtol(location, &location, 10); location += 1; + + /* (20) num_threads - %ld */ process->nlwp = strtol(location, &location, 10); location += 1; + + /* Skip (21) itrealvalue - %ld */ location = strchr(location, ' ') + 1; + + /* (22) starttime - %llu */ if (process->starttime_ctime == 0) { process->starttime_ctime = btime + LinuxProcessList_adjustTime(strtoll(location, &location, 10)) / 100; } else { - location = strchr(location, ' ') + 1; + location = strchr(location, ' '); } location += 1; - for (int i = 0; i < 15; i++) { + + /* Skip (23) - (38) */ + for (int i = 0; i < 16; i++) { location = strchr(location, ' ') + 1; } - process->exit_signal = strtol(location, &location, 10); - location += 1; + assert(location != NULL); + + /* (39) processor - %d */ process->processor = strtol(location, &location, 10); + /* Ignore further fields */ + process->time = lp->utime + lp->stime; return true; } -static bool LinuxProcessList_statProcessDir(Process* process, openat_arg_t procFd) { +static bool LinuxProcessList_updateUser(ProcessList* processList, Process* process, openat_arg_t procFd) { struct stat sstat; #ifdef HAVE_OPENAT int statok = fstat(procFd, &sstat); @@ -374,32 +439,36 @@ static bool LinuxProcessList_statProcessDir(Process* process, openat_arg_t procF #endif if (statok == -1) return false; - process->st_uid = sstat.st_uid; + + if (process->st_uid != sstat.st_uid) { + process->st_uid = sstat.st_uid; + process->user = UsersTable_getRef(processList->usersTable, sstat.st_uid); + } + return true; } -static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t procFd, unsigned long long now) { +static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t procFd, unsigned long long realtimeMs) { char buffer[1024]; ssize_t r = xReadfileat(procFd, "io", buffer, sizeof(buffer)); if (r < 0) { process->io_rate_read_bps = NAN; process->io_rate_write_bps = NAN; - process->io_rchar = -1LL; - process->io_wchar = -1LL; - process->io_syscr = -1LL; - process->io_syscw = -1LL; - process->io_read_bytes = -1LL; - process->io_write_bytes = -1LL; - process->io_cancelled_write_bytes = -1LL; - process->io_rate_read_time = -1LL; - process->io_rate_write_time = -1LL; + process->io_rchar = ULLONG_MAX; + process->io_wchar = ULLONG_MAX; + process->io_syscr = ULLONG_MAX; + process->io_syscw = ULLONG_MAX; + process->io_read_bytes = ULLONG_MAX; + process->io_write_bytes = ULLONG_MAX; + process->io_cancelled_write_bytes = ULLONG_MAX; + process->io_last_scan_time_ms = realtimeMs; return; } unsigned long long last_read = process->io_read_bytes; unsigned long long last_write = process->io_write_bytes; char* buf = buffer; - char* line = NULL; + const char* line; while ((line = strsep(&buf, "\n")) != NULL) { switch (line[0]) { case 'r': @@ -407,9 +476,7 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc process->io_rchar = strtoull(line + 7, NULL, 10); } else if (String_startsWith(line + 1, "ead_bytes: ")) { process->io_read_bytes = strtoull(line + 12, NULL, 10); - process->io_rate_read_bps = - ((double)(process->io_read_bytes - last_read)) / (((double)(now - process->io_rate_read_time)) / 1000); - process->io_rate_read_time = now; + process->io_rate_read_bps = (process->io_read_bytes - last_read) * /*ms to s*/1000 / (realtimeMs - process->io_last_scan_time_ms); } break; case 'w': @@ -417,9 +484,7 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc process->io_wchar = strtoull(line + 7, NULL, 10); } else if (String_startsWith(line + 1, "rite_bytes: ")) { process->io_write_bytes = strtoull(line + 13, NULL, 10); - process->io_rate_write_bps = - ((double)(process->io_write_bytes - last_write)) / (((double)(now - process->io_rate_write_time)) / 1000); - process->io_rate_write_time = now; + process->io_rate_write_bps = (process->io_write_bytes - last_write) * /*ms to s*/1000 / (realtimeMs - process->io_last_scan_time_ms); } break; case 's': @@ -435,14 +500,16 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc } } } + + process->io_last_scan_time_ms = realtimeMs; } typedef struct LibraryData_ { - uint64_t size; - bool exec; + uint64_t size; + bool exec; } LibraryData; -static inline uint64_t fast_strtoull_dec(char **str, int maxlen) { +static inline uint64_t fast_strtoull_dec(char** str, int maxlen) { register uint64_t result = 0; if (!maxlen) @@ -457,7 +524,7 @@ static inline uint64_t fast_strtoull_dec(char **str, int maxlen) { return result; } -static inline uint64_t fast_strtoull_hex(char **str, int maxlen) { +static inline uint64_t fast_strtoull_hex(char** str, int maxlen) { register uint64_t result = 0; register int nibble, letter; const long valid_mask = 0x03FF007E; @@ -491,26 +558,32 @@ static void LinuxProcessList_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* if (!value) return; - LibraryData* v = (LibraryData *)value; - uint64_t* d = (uint64_t *)data; + const LibraryData* v = (const LibraryData*)value; + uint64_t* d = (uint64_t*)data; if (!v->exec) return; *d += v->size; } -static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) { +static void LinuxProcessList_readMaps(LinuxProcess* process, openat_arg_t procFd, bool calcSize, bool checkDeletedLib) { + Process* proc = (Process*)process; + + proc->usesDeletedLib = false; + FILE* mapsfile = fopenat(procFd, "maps", "r"); if (!mapsfile) - return 0; + return; - Hashtable* ht = Hashtable_new(64, true); + Hashtable* ht = NULL; + if (calcSize) + ht = Hashtable_new(64, true); char buffer[1024]; while (fgets(buffer, sizeof(buffer), mapsfile)) { uint64_t map_start; uint64_t map_end; - char map_perm[5]; + bool map_execute; unsigned int map_devmaj; unsigned int map_devmin; uint64_t map_inode; @@ -520,7 +593,7 @@ static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) { continue; // Parse format: "%Lx-%Lx %4s %x %2x:%2x %Ld" - char *readptr = buffer; + char* readptr = buffer; map_start = fast_strtoull_hex(&readptr, 16); if ('-' != *readptr++) @@ -530,8 +603,7 @@ static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) { if (' ' != *readptr++) continue; - memcpy(map_perm, readptr, 4); - map_perm[4] = '\0'; + map_execute = (readptr[2] == 'x'); readptr += 4; if (' ' != *readptr++) continue; @@ -557,61 +629,67 @@ static uint64_t LinuxProcessList_calcLibSize(openat_arg_t procFd) { if (!map_inode) continue; - LibraryData* libdata = Hashtable_get(ht, map_inode); - if (!libdata) { - libdata = xCalloc(1, sizeof(LibraryData)); - Hashtable_put(ht, map_inode, libdata); + if (calcSize) { + LibraryData* libdata = Hashtable_get(ht, map_inode); + if (!libdata) { + libdata = xCalloc(1, sizeof(LibraryData)); + Hashtable_put(ht, map_inode, libdata); + } + + libdata->size += map_end - map_start; + libdata->exec |= map_execute; } - libdata->size += map_end - map_start; - libdata->exec |= 'x' == map_perm[2]; + if (checkDeletedLib && map_execute && !proc->usesDeletedLib) { + while (*readptr == ' ') + readptr++; + + if (*readptr != '/') + continue; + + if (String_startsWith(readptr, "/memfd:")) + continue; + + if (strstr(readptr, " (deleted)\n")) { + proc->usesDeletedLib = true; + if (!calcSize) + break; + } + } } fclose(mapsfile); - uint64_t total_size = 0; - Hashtable_foreach(ht, LinuxProcessList_calcLibSize_helper, &total_size); + if (calcSize) { + uint64_t total_size = 0; + Hashtable_foreach(ht, LinuxProcessList_calcLibSize_helper, &total_size); - Hashtable_delete(ht); + Hashtable_delete(ht); - return total_size / pageSize; + process->m_lrs = total_size / pageSize; + } } -static bool LinuxProcessList_readStatmFile(LinuxProcess* process, openat_arg_t procFd, bool performLookup, unsigned long long now) { +static bool LinuxProcessList_readStatmFile(LinuxProcess* process, openat_arg_t procFd) { FILE* statmfile = fopenat(procFd, "statm", "r"); if (!statmfile) return false; - long tmp_m_lrs = 0; + long int dummy, dummy2; + int r = fscanf(statmfile, "%ld %ld %ld %ld %ld %ld %ld", &process->super.m_virt, &process->super.m_resident, &process->m_share, &process->m_trs, - &tmp_m_lrs, + &dummy, /* unused since Linux 2.6; always 0 */ &process->m_drs, - &process->m_dt); + &dummy2); /* unused since Linux 2.6; always 0 */ fclose(statmfile); if (r == 7) { process->super.m_virt *= pageSizeKB; process->super.m_resident *= pageSizeKB; - - 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; @@ -656,7 +734,7 @@ static bool LinuxProcessList_readSmapsFile(LinuxProcess* process, openat_arg_t p #ifdef HAVE_OPENVZ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) { - if ( (access(PROCDIR "/vz", R_OK) != 0)) { + if (access(PROCDIR "/vz", R_OK) != 0) { free(process->ctid); process->ctid = NULL; process->vpid = process->super.pid; @@ -718,10 +796,8 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t switch(field) { case 1: foundEnvID = true; - if (!String_eq(name_value_sep, process->ctid ? process->ctid : "")) { - free(process->ctid); - process->ctid = xStrdup(name_value_sep); - } + if (!String_eq(name_value_sep, process->ctid ? process->ctid : "")) + free_and_xStrdup(&process->ctid, name_value_sep); break; case 2: foundVPid = true; @@ -762,7 +838,7 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t int left = PROC_LINE_LENGTH; while (!feof(file) && left > 0) { char buffer[PROC_LINE_LENGTH + 1]; - char* ok = fgets(buffer, PROC_LINE_LENGTH, file); + const char* ok = fgets(buffer, PROC_LINE_LENGTH, file); if (!ok) break; @@ -779,8 +855,7 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t left -= wrote; } fclose(file); - free(process->cgroup); - process->cgroup = xStrdup(output); + free_and_xStrdup(&process->cgroup, output); } #ifdef HAVE_VSERVER @@ -831,6 +906,23 @@ static void LinuxProcessList_readOomData(LinuxProcess* process, openat_arg_t pro fclose(file); } +static void LinuxProcessList_readAutogroup(LinuxProcess* process, openat_arg_t procFd) { + process->autogroup_id = -1; + + char autogroup[64]; // space for two numeric values and fixed length strings + ssize_t amtRead = xReadfileat(procFd, "autogroup", autogroup, sizeof(autogroup)); + if (amtRead < 0) + return; + + long int identity; + int nice; + int ok = sscanf(autogroup, "/autogroup-%ld nice %d", &identity, &nice); + if (ok == 2) { + process->autogroup_id = identity; + process->autogroup_nice = nice; + } +} + static void LinuxProcessList_readCtxtData(LinuxProcess* process, openat_arg_t procFd) { FILE* file = fopenat(procFd, "status", "r"); if (!file) @@ -867,7 +959,7 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t } char buffer[PROC_LINE_LENGTH + 1]; - char* res = fgets(buffer, sizeof(buffer), file); + const char* res = fgets(buffer, sizeof(buffer), file); fclose(file); if (!res) { free(process->secattr); @@ -881,8 +973,7 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t if (process->secattr && String_eq(process->secattr, buffer)) { return; } - free(process->secattr); - process->secattr = xStrdup(buffer); + free_and_xStrdup(&process->secattr, buffer); } static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd) { @@ -897,18 +988,17 @@ static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd) #endif if (r < 0) { - free(process->cwd); - process->cwd = NULL; + free(process->super.procCwd); + process->super.procCwd = NULL; return; } pathBuffer[r] = '\0'; - if (process->cwd && String_eq(process->cwd, pathBuffer)) + if (process->super.procCwd && String_eq(process->super.procCwd, pathBuffer)) return; - free(process->cwd); - process->cwd = xStrdup(pathBuffer); + free_and_xStrdup(&process->super.procCwd, pathBuffer); } #ifdef HAVE_DELAYACCT @@ -916,7 +1006,7 @@ static void LinuxProcessList_readCwd(LinuxProcess* process, openat_arg_t procFd) static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) { struct nlmsghdr* nlhdr; struct nlattr* nlattrs[TASKSTATS_TYPE_MAX + 1]; - struct nlattr* nlattr; + const struct nlattr* nlattr; struct taskstats stats; int rem; LinuxProcess* lp = (LinuxProcess*) linuxProcess; @@ -952,10 +1042,10 @@ static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProc struct nl_msg* msg; if (!this->netlink_socket) { - LinuxProcessList_initNetlinkSocket(this); - if (!this->netlink_socket) { - goto delayacct_failure; - } + LinuxProcessList_initNetlinkSocket(this); + if (!this->netlink_socket) { + goto delayacct_failure; + } } if (nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) { @@ -992,16 +1082,6 @@ delayacct_failure: #endif -static void setCommand(Process* process, const char* command, int len) { - if (process->comm && process->commLen >= len) { - strncpy(process->comm, command, len + 1); - } else { - free(process->comm); - process->comm = xStrdup(command); - } - process->commLen = len; -} - 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)); @@ -1009,11 +1089,10 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc return false; if (amtRead == 0) { - if (process->state == 'Z') { - process->basenameOffset = 0; - } else { - ((LinuxProcess*)process)->isKernelThread = true; + if (process->state != 'Z') { + process->isKernelThread = true; } + Process_updateCmdline(process, NULL, 0, 0); return true; } @@ -1025,7 +1104,7 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc for (int i = 0; i < amtRead; i++) { /* newline used as delimiter - when forming the mergedCommand, newline is - * converted to space by LinuxProcess_makeCommandStr */ + * converted to space by Process_makeCommandStr */ if (command[i] == '\0') { command[i] = '\n'; } else { @@ -1121,35 +1200,27 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc } } } + + /* Some command lines are hard to parse, like + * file.so [kdeinit5] file local:/run/user/1000/klauncherdqbouY.1.slave-socket local:/run/user/1000/kded5TwsDAx.1.slave-socket + * Reset if start is behind end. + */ + if (tokenStart >= tokenEnd) + tokenStart = tokenEnd = 0; } if (tokenEnd == 0) { tokenEnd = lastChar + 1; } - LinuxProcess *lp = (LinuxProcess *)process; - lp->mergedCommand.maxLen = lastChar + 1; /* accommodate cmdline */ - if (!process->comm || !String_eq(command, process->comm)) { - process->basenameOffset = tokenEnd; - setCommand(process, command, lastChar + 1); - lp->procCmdlineBasenameOffset = tokenStart; - lp->procCmdlineBasenameEnd = tokenEnd; - lp->mergedCommand.cmdlineChanged = true; - } + Process_updateCmdline(process, command, tokenStart, tokenEnd); /* /proc/[pid]/comm could change, so should be updated */ if ((amtRead = xReadfileat(procFd, "comm", command, sizeof(command))) > 0) { command[amtRead - 1] = '\0'; - lp->mergedCommand.maxLen += amtRead - 1; /* accommodate comm */ - if (!lp->procComm || !String_eq(command, lp->procComm)) { - free(lp->procComm); - lp->procComm = xStrdup(command); - lp->mergedCommand.commChanged = true; - } - } else if (lp->procComm) { - free(lp->procComm); - lp->procComm = NULL; - lp->mergedCommand.commChanged = true; + Process_updateComm(process, command); + } else { + Process_updateComm(process, NULL); } char filename[MAX_NAME + 1]; @@ -1164,39 +1235,36 @@ static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t proc #endif if (amtRead > 0) { filename[amtRead] = 0; - lp->mergedCommand.maxLen += amtRead; /* accommodate exe */ - if (!lp->procExe || !String_eq(filename, lp->procExe)) { - free(lp->procExe); - lp->procExe = xStrdup(filename); - lp->procExeLen = amtRead; - /* exe is guaranteed to contain at least one /, but validate anyway */ - while (amtRead && filename[--amtRead] != '/') - ; - lp->procExeBasenameOffset = amtRead + 1; - lp->mergedCommand.exeChanged = true; + if (!process->procExe || + (!process->procExeDeleted && !String_eq(filename, process->procExe)) || + (process->procExeDeleted && !String_startsWith(filename, process->procExe))) { const char* deletedMarker = " (deleted)"; - if (strlen(lp->procExe) > strlen(deletedMarker)) { - lp->procExeDeleted = String_eq(lp->procExe + strlen(lp->procExe) - strlen(deletedMarker), deletedMarker); + const size_t markerLen = strlen(deletedMarker); + const size_t filenameLen = strlen(filename); - if (lp->procExeDeleted && strlen(lp->procExe) - strlen(deletedMarker) == 1 && lp->procExe[0] == '/') { - lp->procExeBasenameOffset = 0; - } + if (filenameLen > markerLen) { + bool oldExeDeleted = process->procExeDeleted; + + process->procExeDeleted = String_eq(filename + filenameLen - markerLen, deletedMarker); + + if (process->procExeDeleted) + filename[filenameLen - markerLen] = '\0'; + + process->mergedCommand.exeChanged |= oldExeDeleted ^ process->procExeDeleted; } + + Process_updateExe(process, filename); } - } else if (lp->procExe) { - free(lp->procExe); - lp->procExe = NULL; - lp->procExeLen = 0; - lp->procExeBasenameOffset = 0; - lp->procExeDeleted = false; - lp->mergedCommand.exeChanged = true; + } else if (process->procExe) { + Process_updateExe(process, NULL); + process->procExeDeleted = false; } return true; } -static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned int tty_nr) { +static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned long int tty_nr) { unsigned int maj = major(tty_nr); unsigned int min = minor(tty_nr); @@ -1249,7 +1317,7 @@ static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned in return out; } -static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const char* dirname, const Process* parent, double period, unsigned long long now) { +static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_t parentFd, const char* dirname, const Process* parent, double period) { ProcessList* pl = (ProcessList*) this; const struct dirent* entry; const Settings* settings = pl->settings; @@ -1269,9 +1337,9 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ return false; } - int cpus = pl->cpuCount; - bool hideKernelThreads = settings->hideKernelThreads; - bool hideUserlandThreads = settings->hideUserlandThreads; + const unsigned int activeCPUs = pl->activeCPUs; + const bool hideKernelThreads = settings->hideKernelThreads; + const bool hideUserlandThreads = settings->hideUserlandThreads; while ((entry = readdir(dir)) != NULL) { const char* name = entry->d_name; @@ -1292,11 +1360,16 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } // filename is a number: process directory - int pid = atoi(name); - - if (pid <= 0) - continue; + int pid; + { + char* endptr; + unsigned long parsedPid = strtoul(name, &endptr, 10); + if (parsedPid == 0 || parsedPid == ULONG_MAX || *endptr != '\0') + continue; + pid = parsedPid; + } + // Skip task directory of main thread if (parent && pid == parent->pid) continue; @@ -1305,9 +1378,10 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ LinuxProcess* lp = (LinuxProcess*) proc; proc->tgid = parent ? parent->pid : pid; + proc->isUserlandThread = proc->pid != proc->tgid; #ifdef HAVE_OPENAT - int procFd = openat(dirFd, entry->d_name, O_PATH | O_DIRECTORY | O_NOFOLLOW); + int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); if (procFd < 0) goto errorReadingProcess; #else @@ -1315,7 +1389,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ xSnprintf(procFd, sizeof(procFd), "%s/%s", dirFd, entry->d_name); #endif - LinuxProcessList_recurseProcTree(this, procFd, "task", proc, period, now); + LinuxProcessList_recurseProcTree(this, procFd, "task", proc, period); /* * These conditions will not trigger on first occurrence, cause we need to @@ -1341,11 +1415,33 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } if (settings->flags & PROCESS_FLAG_IO) - LinuxProcessList_readIoFile(lp, procFd, now); + LinuxProcessList_readIoFile(lp, procFd, pl->realtimeMs); - if (!LinuxProcessList_readStatmFile(lp, procFd, !!(settings->flags & PROCESS_FLAG_LINUX_LRS_FIX), now)) + if (!LinuxProcessList_readStatmFile(lp, procFd)) goto errorReadingProcess; + { + bool prev = proc->usesDeletedLib; + + if ((settings->flags & PROCESS_FLAG_LINUX_LRS_FIX) || + (settings->highlightDeletedExe && !proc->procExeDeleted && !proc->isKernelThread && !proc->isUserlandThread)) { + // Check if we really should recalculate the M_LRS value for this process + uint64_t passedTimeInMs = pl->realtimeMs - lp->last_mlrs_calctime; + + uint64_t recheck = ((uint64_t)rand()) % 2048; + + if (passedTimeInMs > recheck) { + lp->last_mlrs_calctime = pl->realtimeMs; + LinuxProcessList_readMaps(lp, procFd, settings->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe); + } + } else { + /* Copy from process structure in threads and reset if setting got disabled */ + proc->usesDeletedLib = (proc->isUserlandThread && parent) ? parent->usesDeletedLib : false; + } + + proc->mergedCommand.exeChanged |= prev ^ proc->usesDeletedLib; + } + 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 @@ -1361,16 +1457,15 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } } - char command[MAX_NAME + 1]; + char statCommand[MAX_NAME + 1]; unsigned long long int lasttimes = (lp->utime + lp->stime); - int commLen = sizeof(command); - unsigned int tty_nr = proc->tty_nr; - if (! LinuxProcessList_readStatFile(proc, procFd, command, &commLen)) + unsigned long int tty_nr = proc->tty_nr; + if (! LinuxProcessList_readStatFile(proc, procFd, statCommand, sizeof(statCommand))) goto errorReadingProcess; if (tty_nr != proc->tty_nr && this->ttyDrivers) { - free(lp->ttyDevice); - lp->ttyDevice = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr); + free(proc->tty_name); + proc->tty_name = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr); } if (settings->flags & PROCESS_FLAG_LINUX_IOPRIO) { @@ -1379,15 +1474,13 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ /* 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_cpu = CLAMP(percent_cpu, 0.0F, activeCPUs * 100.0F); proc->percent_mem = proc->m_resident / (double)(pl->totalMem) * 100.0; - if (!preExisting) { - - if (! LinuxProcessList_statProcessDir(proc, procFd)) - goto errorReadingProcess; + if (! LinuxProcessList_updateUser(pl, proc, procFd)) + goto errorReadingProcess; - proc->user = UsersTable_getRef(pl->usersTable, proc->st_uid); + if (!preExisting) { #ifdef HAVE_OPENVZ if (settings->flags & PROCESS_FLAG_LINUX_OPENVZ) { @@ -1415,15 +1508,6 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } } } - /* (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 if (settings->flags & PROCESS_FLAG_LINUX_DELAYACCT) { @@ -1447,35 +1531,30 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ LinuxProcessList_readSecattrData(lp, procFd); } - if (settings->flags & PROCESS_FLAG_LINUX_CWD) { + if (settings->flags & PROCESS_FLAG_CWD) { LinuxProcessList_readCwd(lp, procFd); } - if (proc->state == 'Z' && (proc->basenameOffset == 0)) { - proc->basenameOffset = -1; - setCommand(proc, command, commLen); - } else if (Process_isThread(proc)) { - if (settings->showThreadNames || Process_isKernelThread(proc) || (proc->state == 'Z' && proc->basenameOffset == 0)) { - proc->basenameOffset = -1; - setCommand(proc, command, commLen); - } else if (settings->showThreadNames) { - if (! LinuxProcessList_readCmdlineFile(proc, procFd)) { - goto errorReadingProcess; - } - } - if (Process_isKernelThread(proc)) { - pl->kernelThreads++; - } else { - pl->userlandThreads++; - } + if ((settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) { + LinuxProcessList_readAutogroup(lp, procFd); + } + + if (!proc->cmdline && statCommand[0] && + (proc->state == 'Z' || Process_isKernelThread(proc) || settings->showThreadNames)) { + Process_updateCmdline(proc, statCommand, 0, strlen(statCommand)); + } + + if (Process_isKernelThread(proc)) { + pl->kernelThreads++; + } else if (Process_isUserlandThread(proc)) { + pl->userlandThreads++; } /* Set at the end when we know if a new entry is a thread */ proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); pl->totalTasks++; - if (proc->state == 'R') - pl->runningTasks++; + /* runningTasks is set in LinuxProcessList_scanCPUTime() from /proc/stat */ proc->updated = true; Compat_openatArgClose(procFd); continue; @@ -1501,63 +1580,147 @@ errorReadingProcess: } static inline void LinuxProcessList_scanMemoryInfo(ProcessList* this) { - unsigned long long int freeMem = 0; - unsigned long long int swapFree = 0; - unsigned long long int shmem = 0; - unsigned long long int sreclaimable = 0; + memory_t availableMem = 0; + memory_t freeMem = 0; + memory_t totalMem = 0; + memory_t buffersMem = 0; + memory_t cachedMem = 0; + memory_t sharedMem = 0; + memory_t swapTotalMem = 0; + memory_t swapCacheMem = 0; + memory_t swapFreeMem = 0; + memory_t sreclaimableMem = 0; FILE* file = fopen(PROCMEMINFOFILE, "r"); - if (file == NULL) { + if (!file) CRT_fatalError("Cannot open " PROCMEMINFOFILE); - } + char buffer[128]; - while (fgets(buffer, 128, file)) { + while (fgets(buffer, sizeof(buffer), file)) { - #define tryRead(label, variable) \ - if (String_startsWith(buffer, label)) { \ - sscanf(buffer + strlen(label), " %32llu kB", variable); \ - break; \ + #define tryRead(label, variable) \ + if (String_startsWith(buffer, label)) { \ + memory_t parsed_; \ + if (sscanf(buffer + strlen(label), "%llu kB", &parsed_) == 1) { \ + (variable) = parsed_; \ + } \ + break; \ } switch (buffer[0]) { case 'M': - tryRead("MemTotal:", &this->totalMem); - tryRead("MemFree:", &freeMem); + tryRead("MemAvailable:", availableMem); + tryRead("MemFree:", freeMem); + tryRead("MemTotal:", totalMem); break; case 'B': - tryRead("Buffers:", &this->buffersMem); + tryRead("Buffers:", buffersMem); break; case 'C': - tryRead("Cached:", &this->cachedMem); + tryRead("Cached:", cachedMem); break; case 'S': switch (buffer[1]) { - case 'w': - tryRead("SwapTotal:", &this->totalSwap); - tryRead("SwapFree:", &swapFree); - break; case 'h': - tryRead("Shmem:", &shmem); + tryRead("Shmem:", sharedMem); + break; + case 'w': + tryRead("SwapTotal:", swapTotalMem); + tryRead("SwapCached:", swapCacheMem); + tryRead("SwapFree:", swapFreeMem); break; case 'R': - tryRead("SReclaimable:", &sreclaimable); + tryRead("SReclaimable:", sreclaimableMem); break; } break; } + #undef tryRead } - this->usedMem = this->totalMem - freeMem; - this->cachedMem = this->cachedMem + sreclaimable - shmem; - this->usedSwap = this->totalSwap - swapFree; fclose(file); + + /* + * Compute memory partition like procps(free) + * https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c + * + * Adjustments: + * - Shmem in part of Cached (see https://lore.kernel.org/patchwork/patch/648763/), + * do not show twice by subtracting from Cached and do not subtract twice from used. + */ + this->totalMem = totalMem; + this->cachedMem = cachedMem + sreclaimableMem - sharedMem; + this->sharedMem = sharedMem; + const memory_t usedDiff = freeMem + cachedMem + sreclaimableMem + buffersMem; + this->usedMem = (totalMem >= usedDiff) ? totalMem - usedDiff : totalMem - freeMem; + this->buffersMem = buffersMem; + this->availableMem = availableMem != 0 ? MINIMUM(availableMem, totalMem) : freeMem; + this->totalSwap = swapTotalMem; + this->usedSwap = swapTotalMem - swapFreeMem - swapCacheMem; + this->cachedSwap = swapCacheMem; +} + +static void LinuxProcessList_scanHugePages(LinuxProcessList* this) { + this->totalHugePageMem = 0; + for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) { + this->usedHugePageMem[i] = MEMORY_MAX; + } + + DIR* dir = opendir("/sys/kernel/mm/hugepages"); + if (!dir) + return; + + const struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + const char* name = entry->d_name; + + /* Ignore all non-directories */ + if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) + continue; + + if (!String_startsWith(name, "hugepages-")) + continue; + + char* endptr; + unsigned long int hugePageSize = strtoul(name + strlen("hugepages-"), &endptr, 10); + if (!endptr || *endptr != 'k') + continue; + + char content[64]; + char hugePagePath[128]; + ssize_t r; + + xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/nr_hugepages", name); + r = xReadfile(hugePagePath, content, sizeof(content)); + if (r <= 0) + continue; + + memory_t total = strtoull(content, NULL, 10); + if (total == 0) + continue; + + xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/free_hugepages", name); + r = xReadfile(hugePagePath, content, sizeof(content)); + if (r <= 0) + continue; + + memory_t free = strtoull(content, NULL, 10); + + int shift = ffsl(hugePageSize) - 1 - (HTOP_HUGEPAGE_BASE_SHIFT - 10); + assert(shift >= 0 && shift < HTOP_HUGEPAGE_COUNT); + + this->totalHugePageMem += total * hugePageSize; + this->usedHugePageMem[shift] = (total - free) * hugePageSize; + } + + closedir(dir); } static inline void LinuxProcessList_scanZramInfo(LinuxProcessList* this) { - unsigned long long int totalZram = 0; - unsigned long long int usedZramComp = 0; - unsigned long long int usedZramOrig = 0; + memory_t totalZram = 0; + memory_t usedZramComp = 0; + memory_t usedZramOrig = 0; char mm_stat[34]; char disksize[34]; @@ -1578,9 +1741,9 @@ static inline void LinuxProcessList_scanZramInfo(LinuxProcessList* this) { } break; } - unsigned long long int size = 0; - unsigned long long int orig_data_size = 0; - unsigned long long int compr_data_size = 0; + memory_t size = 0; + memory_t orig_data_size = 0; + memory_t compr_data_size = 0; if (!fscanf(disksize_file, "%llu\n", &size) || !fscanf(mm_stat_file, " %llu %llu", &orig_data_size, &compr_data_size)) { @@ -1603,9 +1766,9 @@ static inline void LinuxProcessList_scanZramInfo(LinuxProcessList* this) { } static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) { - unsigned long long int dbufSize = 0; - unsigned long long int dnodeSize = 0; - unsigned long long int bonusSize = 0; + memory_t dbufSize = 0; + memory_t dnodeSize = 0; + memory_t bonusSize = 0; FILE* file = fopen(PROCARCSTATSFILE, "r"); if (file == NULL) { @@ -1673,61 +1836,78 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) { } } -static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) { +static inline double LinuxProcessList_scanCPUTime(ProcessList* super) { + LinuxProcessList* this = (LinuxProcessList*) super; + + LinuxProcessList_updateCPUcount(super); FILE* file = fopen(PROCSTATFILE, "r"); - if (file == NULL) { + if (!file) CRT_fatalError("Cannot open " PROCSTATFILE); - } - int cpus = this->super.cpuCount; - assert(cpus > 0); - for (int i = 0; i <= cpus; i++) { + + unsigned int existingCPUs = super->existingCPUs; + unsigned int lastAdjCpuId = 0; + + for (unsigned int i = 0; i <= existingCPUs; i++) { char buffer[PROC_LINE_LENGTH + 1]; unsigned long long int usertime, nicetime, systemtime, idletime; - unsigned long long int ioWait, irq, softIrq, steal, guest, guestnice; - ioWait = irq = softIrq = steal = guest = guestnice = 0; + unsigned long long int ioWait = 0, irq = 0, softIrq = 0, steal = 0, guest = 0, guestnice = 0; + + const char* ok = fgets(buffer, sizeof(buffer), file); + if (!ok) + break; + + // cpu fields are sorted first + if (!String_startsWith(buffer, "cpu")) + break; + // Depending on your kernel version, // 5, 7, 8 or 9 of these fields will be set. // The rest will remain at zero. - char* ok = fgets(buffer, PROC_LINE_LENGTH, file); - if (!ok) { - buffer[0] = '\0'; - } - + unsigned int adjCpuId; if (i == 0) { (void) sscanf(buffer, "cpu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice); + adjCpuId = 0; } else { - int cpuid; - (void) sscanf(buffer, "cpu%4d %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice); - assert(cpuid == i - 1); + unsigned int cpuid; + (void) sscanf(buffer, "cpu%4u %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice); + adjCpuId = cpuid + 1; + } + + if (adjCpuId > super->existingCPUs) + break; + + for (unsigned int j = lastAdjCpuId + 1; j < adjCpuId; j++) { + // Skipped an ID, but /proc/stat is ordered => got offline CPU + memset(&(this->cpuData[j]), '\0', sizeof(CPUData)); } + lastAdjCpuId = adjCpuId; + // Guest time is already accounted in usertime - usertime = usertime - guest; - nicetime = nicetime - guestnice; + usertime -= guest; + nicetime -= guestnice; // Fields existing on kernels >= 2.6 // (and RHEL's patched kernel 2.4...) unsigned long long int idlealltime = idletime + ioWait; unsigned long long int systemalltime = systemtime + irq + softIrq; unsigned long long int virtalltime = guest + guestnice; unsigned long long int totaltime = usertime + nicetime + systemalltime + idlealltime + steal + virtalltime; - CPUData* cpuData = &(this->cpus[i]); + CPUData* cpuData = &(this->cpuData[adjCpuId]); // Since we do a subtraction (usertime - guest) and cputime64_to_clock_t() // used in /proc/stat rounds down numbers, it can lead to a case where the // integer overflow. - #define WRAP_SUBTRACT(a,b) (((a) > (b)) ? (a) - (b) : 0) - cpuData->userPeriod = WRAP_SUBTRACT(usertime, cpuData->userTime); - cpuData->nicePeriod = WRAP_SUBTRACT(nicetime, cpuData->niceTime); - cpuData->systemPeriod = WRAP_SUBTRACT(systemtime, cpuData->systemTime); - cpuData->systemAllPeriod = WRAP_SUBTRACT(systemalltime, cpuData->systemAllTime); - cpuData->idleAllPeriod = WRAP_SUBTRACT(idlealltime, cpuData->idleAllTime); - cpuData->idlePeriod = WRAP_SUBTRACT(idletime, cpuData->idleTime); - cpuData->ioWaitPeriod = WRAP_SUBTRACT(ioWait, cpuData->ioWaitTime); - cpuData->irqPeriod = WRAP_SUBTRACT(irq, cpuData->irqTime); - cpuData->softIrqPeriod = WRAP_SUBTRACT(softIrq, cpuData->softIrqTime); - cpuData->stealPeriod = WRAP_SUBTRACT(steal, cpuData->stealTime); - cpuData->guestPeriod = WRAP_SUBTRACT(virtalltime, cpuData->guestTime); - cpuData->totalPeriod = WRAP_SUBTRACT(totaltime, cpuData->totalTime); - #undef WRAP_SUBTRACT + cpuData->userPeriod = saturatingSub(usertime, cpuData->userTime); + cpuData->nicePeriod = saturatingSub(nicetime, cpuData->niceTime); + cpuData->systemPeriod = saturatingSub(systemtime, cpuData->systemTime); + cpuData->systemAllPeriod = saturatingSub(systemalltime, cpuData->systemAllTime); + cpuData->idleAllPeriod = saturatingSub(idlealltime, cpuData->idleAllTime); + cpuData->idlePeriod = saturatingSub(idletime, cpuData->idleTime); + cpuData->ioWaitPeriod = saturatingSub(ioWait, cpuData->ioWaitTime); + cpuData->irqPeriod = saturatingSub(irq, cpuData->irqTime); + cpuData->softIrqPeriod = saturatingSub(softIrq, cpuData->softIrqTime); + cpuData->stealPeriod = saturatingSub(steal, cpuData->stealTime); + cpuData->guestPeriod = saturatingSub(virtalltime, cpuData->guestTime); + cpuData->totalPeriod = saturatingSub(totaltime, cpuData->totalTime); cpuData->userTime = usertime; cpuData->niceTime = nicetime; cpuData->systemTime = systemtime; @@ -1742,19 +1922,50 @@ static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) { cpuData->totalTime = totaltime; } - double period = (double)this->cpus[0].totalPeriod / cpus; + double period = (double)this->cpuData[0].totalPeriod / super->activeCPUs; + + char buffer[PROC_LINE_LENGTH + 1]; + while (fgets(buffer, sizeof(buffer), file)) { + if (String_startsWith(buffer, "procs_running")) { + super->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10); + break; + } + } + fclose(file); + return period; } static int scanCPUFreqencyFromSysCPUFreq(LinuxProcessList* this) { - int cpus = this->super.cpuCount; + unsigned int existingCPUs = this->super.existingCPUs; int numCPUsWithFrequency = 0; unsigned long totalFrequency = 0; - for (int i = 0; i < cpus; ++i) { + /* + * On some AMD and Intel CPUs read()ing scaling_cur_freq is quite slow (> 1ms). This delay + * accumulates for every core. For details see issue#471. + * If the read on CPU 0 takes longer than 500us bail out and fall back to reading the + * frequencies from /proc/cpuinfo. + * Once the condition has been met, bail out early for the next couple of scans. + */ + static int timeout = 0; + + if (timeout > 0) { + timeout--; + return -1; + } + + for (unsigned int i = 0; i < existingCPUs; ++i) { + if (!ProcessList_isCPUonline(&this->super, i)) + continue; + char pathBuffer[64]; - xSnprintf(pathBuffer, sizeof(pathBuffer), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i); + xSnprintf(pathBuffer, sizeof(pathBuffer), "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i); + + struct timespec start; + if (i == 0) + clock_gettime(CLOCK_MONOTONIC, &start); FILE* file = fopen(pathBuffer, "r"); if (!file) @@ -1764,16 +1975,27 @@ static int scanCPUFreqencyFromSysCPUFreq(LinuxProcessList* this) { if (fscanf(file, "%lu", &frequency) == 1) { /* convert kHz to MHz */ frequency = frequency / 1000; - this->cpus[i + 1].frequency = frequency; + this->cpuData[i + 1].frequency = frequency; numCPUsWithFrequency++; totalFrequency += frequency; } fclose(file); + + if (i == 0) { + struct timespec end; + clock_gettime(CLOCK_MONOTONIC, &end); + const time_t timeTakenUs = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000; + if (timeTakenUs > 500) { + timeout = 30; + return -1; + } + } + } if (numCPUsWithFrequency > 0) - this->cpus[0].frequency = (double)totalFrequency / numCPUsWithFrequency; + this->cpuData[0].frequency = (double)totalFrequency / numCPUsWithFrequency; return 0; } @@ -1783,7 +2005,7 @@ static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) { if (file == NULL) return; - int cpus = this->super.cpuCount; + unsigned int existingCPUs = this->super.existingCPUs; int numCPUsWithFrequency = 0; double totalFrequency = 0; int cpuid = -1; @@ -1806,11 +2028,11 @@ static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) { (sscanf(buffer, "clock : %lfMHz", &frequency) == 1) || (sscanf(buffer, "clock: %lfMHz", &frequency) == 1) ) { - if (cpuid < 0 || cpuid > (cpus - 1)) { + if (cpuid < 0 || (unsigned int)cpuid > (existingCPUs - 1)) { continue; } - CPUData* cpuData = &(this->cpus[cpuid + 1]); + CPUData* cpuData = &(this->cpuData[cpuid + 1]); /* do not override sysfs data */ if (isnan(cpuData->frequency)) { cpuData->frequency = frequency; @@ -1824,16 +2046,15 @@ static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) { fclose(file); if (numCPUsWithFrequency > 0) { - this->cpus[0].frequency = totalFrequency / numCPUsWithFrequency; + this->cpuData[0].frequency = totalFrequency / numCPUsWithFrequency; } } static void LinuxProcessList_scanCPUFrequency(LinuxProcessList* this) { - int cpus = this->super.cpuCount; - assert(cpus > 0); + unsigned int existingCPUs = this->super.existingCPUs; - for (int i = 0; i <= cpus; i++) { - this->cpus[i].frequency = NAN; + for (unsigned int i = 0; i <= existingCPUs; i++) { + this->cpuData[i].frequency = NAN; } if (scanCPUFreqencyFromSysCPUFreq(this) == 0) { @@ -1848,11 +2069,11 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { const Settings* settings = super->settings; LinuxProcessList_scanMemoryInfo(super); + LinuxProcessList_scanHugePages(this); LinuxProcessList_scanZfsArcstats(this); - LinuxProcessList_updateCPUcount(this); LinuxProcessList_scanZramInfo(this); - double period = LinuxProcessList_scanCPUTime(this); + double period = LinuxProcessList_scanCPUTime(super); if (settings->showCPUFrequency) { LinuxProcessList_scanCPUFrequency(this); @@ -1860,7 +2081,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { #ifdef HAVE_SENSORS_SENSORS_H if (settings->showCPUTemperature) - LibSensors_getCPUTemperatures(this->cpus, this->super.cpuCount); + LibSensors_getCPUTemperatures(this->cpuData, this->super.existingCPUs, this->super.activeCPUs); #endif // in pause mode only gather global data for meters (CPU/memory/...) @@ -1868,9 +2089,15 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { return; } - struct timeval tv; - gettimeofday(&tv, NULL); - unsigned long long now = tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; + if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) { + // Refer to sched(7) 'autogroup feature' section + // The kernel feature can be enabled/disabled through procfs at + // any time, so check for it at the start of each sample - only + // read from per-process procfs files if it's globally enabled. + this->haveAutogroup = LinuxProcess_isAutogroupEnabled(); + } else { + this->haveAutogroup = false; + } /* PROCDIR is an absolute path */ assert(PROCDIR[0] == '/'); @@ -1880,5 +2107,12 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { openat_arg_t rootFd = ""; #endif - LinuxProcessList_recurseProcTree(this, rootFd, PROCDIR, NULL, period, now); + LinuxProcessList_recurseProcTree(this, rootFd, PROCDIR, NULL, period); +} + +bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) { + assert(id < super->existingCPUs); + + const LinuxProcessList* this = (const LinuxProcessList*) super; + return this->cpuData[id + 1].online; } diff --git a/linux/LinuxProcessList.h b/linux/LinuxProcessList.h index 09b84af..a5640e2 100644 --- a/linux/LinuxProcessList.h +++ b/linux/LinuxProcessList.h @@ -18,6 +18,8 @@ in the source distribution for its full text. #include "ZramStats.h" #include "zfs/ZfsArcStats.h" +#define HTOP_HUGEPAGE_BASE_SHIFT 16 +#define HTOP_HUGEPAGE_COUNT 24 typedef struct CPUData_ { unsigned long long int totalTime; @@ -51,6 +53,8 @@ typedef struct CPUData_ { #ifdef HAVE_SENSORS_SENSORS_H double temperature; #endif + + bool online; } CPUData; typedef struct TtyDriver_ { @@ -63,15 +67,22 @@ typedef struct TtyDriver_ { typedef struct LinuxProcessList_ { ProcessList super; - CPUData* cpus; + CPUData* cpuData; + TtyDriver* ttyDrivers; bool haveSmapsRollup; + bool haveAutogroup; #ifdef HAVE_DELAYACCT struct nl_sock* netlink_socket; int netlink_family; #endif + memory_t totalHugePageMem; + memory_t usedHugePageMem[HTOP_HUGEPAGE_COUNT]; + + memory_t availableMem; + ZfsArcStats zfs; ZramStats zram; } LinuxProcessList; @@ -104,10 +115,12 @@ typedef struct LinuxProcessList_ { #define PROC_LINE_LENGTH 4096 #endif -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* pl); void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate); +bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id); + #endif diff --git a/linux/Platform.c b/linux/Platform.c index dd80ded..05023d5 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -7,14 +7,13 @@ in the source distribution for its full text. #include "config.h" -#include "Platform.h" +#include "linux/Platform.h" #include #include #include #include #include -#include #include #include #include @@ -31,40 +30,55 @@ in the source distribution for its full text. #include "DateTimeMeter.h" #include "DiskIOMeter.h" #include "HostnameMeter.h" -#include "IOPriority.h" -#include "IOPriorityPanel.h" -#include "LinuxProcess.h" -#include "LinuxProcessList.h" +#include "HugePageMeter.h" #include "LoadAverageMeter.h" #include "Macros.h" #include "MainPanel.h" #include "Meter.h" #include "MemoryMeter.h" +#include "MemorySwapMeter.h" #include "NetworkIOMeter.h" #include "Object.h" #include "Panel.h" #include "PressureStallMeter.h" #include "ProcessList.h" #include "ProvideCurses.h" -#include "SELinuxMeter.h" +#include "linux/SELinuxMeter.h" #include "Settings.h" #include "SwapMeter.h" -#include "SystemdMeter.h" +#include "SysArchMeter.h" #include "TasksMeter.h" #include "UptimeMeter.h" #include "XUtils.h" -#include "ZramMeter.h" -#include "ZramStats.h" - +#include "linux/IOPriority.h" +#include "linux/IOPriorityPanel.h" +#include "linux/LinuxProcess.h" +#include "linux/LinuxProcessList.h" +#include "linux/SystemdMeter.h" +#include "linux/ZramMeter.h" +#include "linux/ZramStats.h" #include "zfs/ZfsArcMeter.h" #include "zfs/ZfsArcStats.h" #include "zfs/ZfsCompressedArcMeter.h" +#ifdef HAVE_LIBCAP +#include +#include +#endif + #ifdef HAVE_SENSORS_SENSORS_H #include "LibSensors.h" #endif +#ifdef HAVE_LIBCAP +enum CapMode { + CAP_MODE_OFF, + CAP_MODE_BASIC, + CAP_MODE_STRICT +}; +#endif + const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; const SignalItem Platform_signals[] = { @@ -111,36 +125,24 @@ static time_t Platform_Battery_cacheTime; static double Platform_Battery_cachePercent = NAN; static ACPresence Platform_Battery_cacheIsOnAC; -void Platform_init(void) { - if (access(PROCDIR, R_OK) != 0) { - fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR); - exit(1); - } - -#ifdef HAVE_SENSORS_SENSORS_H - LibSensors_init(NULL); -#endif -} - -void Platform_done(void) { -#ifdef HAVE_SENSORS_SENSORS_H - LibSensors_cleanup(); +#ifdef HAVE_LIBCAP +static enum CapMode Platform_capabilitiesMode = CAP_MODE_BASIC; #endif -} static Htop_Reaction Platform_actionSetIOPriority(State* st) { - Panel* panel = st->panel; + if (Settings_isReadonly()) + return HTOP_OK; - LinuxProcess* p = (LinuxProcess*) Panel_getSelected(panel); + const LinuxProcess* p = (const LinuxProcess*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; IOPriority ioprio1 = p->ioPriority; Panel* ioprioPanel = IOPriorityPanel_new(ioprio1); - void* set = Action_pickFromVector(st, ioprioPanel, 21, true); + const void* set = Action_pickFromVector(st, ioprioPanel, 20, true); if (set) { IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel); - bool ok = MainPanel_foreachProcess((MainPanel*)panel, LinuxProcess_setIOPriority, (Arg) { .i = ioprio2 }, NULL); + bool ok = MainPanel_foreachProcess(st->mainPanel, LinuxProcess_setIOPriority, (Arg) { .i = ioprio2 }, NULL); if (!ok) { beep(); } @@ -149,8 +151,40 @@ static Htop_Reaction Platform_actionSetIOPriority(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } +static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) { + if (LinuxProcess_isAutogroupEnabled() == false) { + beep(); + return false; + } + bool anyTagged; + bool ok = MainPanel_foreachProcess(panel, LinuxProcess_changeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged); + if (!ok) + beep(); + return anyTagged; +} + +static Htop_Reaction Platform_actionHigherAutogroupPriority(State* st) { + if (Settings_isReadonly()) + return HTOP_OK; + + bool changed = Platform_changeAutogroupPriority(st->mainPanel, -1); + return changed ? HTOP_REFRESH : HTOP_OK; +} + +static Htop_Reaction Platform_actionLowerAutogroupPriority(State* st) { + if (Settings_isReadonly()) + return HTOP_OK; + + bool changed = Platform_changeAutogroupPriority(st->mainPanel, 1); + return changed ? HTOP_REFRESH : HTOP_OK; +} + void Platform_setBindings(Htop_Action* keys) { keys['i'] = Platform_actionSetIOPriority; + keys['{'] = Platform_actionLowerAutogroupPriority; + keys['}'] = Platform_actionHigherAutogroupPriority; + keys[KEY_F(19)] = Platform_actionLowerAutogroupPriority; // Shift-F7 + keys[KEY_F(20)] = Platform_actionHigherAutogroupPriority; // Shift-F8 } const MeterClass* const Platform_meterTypes[] = { @@ -162,6 +196,9 @@ const MeterClass* const Platform_meterTypes[] = { &LoadMeter_class, &MemoryMeter_class, &SwapMeter_class, + &MemorySwapMeter_class, + &SysArchMeter_class, + &HugePageMeter_class, &TasksMeter_class, &UptimeMeter_class, &BatteryMeter_class, @@ -208,19 +245,25 @@ int Platform_getUptime() { } void Platform_getLoadAverage(double* one, double* five, double* fifteen) { - int activeProcs, totalProcs, lastProc; - *one = 0; - *five = 0; - *fifteen = 0; - FILE* fd = fopen(PROCDIR "/loadavg", "r"); - if (fd) { - int total = fscanf(fd, "%32lf %32lf %32lf %32d/%32d %32d", one, five, fifteen, - &activeProcs, &totalProcs, &lastProc); - (void) total; - assert(total == 6); - fclose(fd); - } + if (!fd) + goto err; + + double scanOne, scanFive, scanFifteen; + int r = fscanf(fd, "%lf %lf %lf", &scanOne, &scanFive, &scanFifteen); + fclose(fd); + if (r != 3) + goto err; + + *one = scanOne; + *five = scanFive; + *fifteen = scanFifteen; + return; + +err: + *one = NAN; + *five = NAN; + *fifteen = NAN; } int Platform_getMaxPid() { @@ -235,12 +278,18 @@ int Platform_getMaxPid() { return maxPid; } -double Platform_setCPUValues(Meter* this, int cpu) { +double Platform_setCPUValues(Meter* this, unsigned int cpu) { const LinuxProcessList* pl = (const LinuxProcessList*) this->pl; - const CPUData* cpuData = &(pl->cpus[cpu]); + const CPUData* cpuData = &(pl->cpuData[cpu]); double total = (double) ( cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod); double percent; double* v = this->values; + + if (!cpuData->online) { + this->curItems = 0; + return NAN; + } + v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0; v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0; if (this->pl->settings->detailedCPUTime) { @@ -282,18 +331,16 @@ void Platform_setMemoryValues(Meter* this) { const ProcessList* pl = this->pl; const LinuxProcessList* lpl = (const LinuxProcessList*) pl; - long int usedMem = pl->usedMem; - long int buffersMem = pl->buffersMem; - long int cachedMem = pl->cachedMem; - usedMem -= buffersMem + cachedMem; - this->total = pl->totalMem; - this->values[0] = usedMem; - this->values[1] = buffersMem; - this->values[2] = cachedMem; + this->total = pl->totalMem > lpl->totalHugePageMem ? pl->totalMem - lpl->totalHugePageMem : pl->totalMem; + this->values[0] = pl->usedMem > lpl->totalHugePageMem ? pl->usedMem - lpl->totalHugePageMem : pl->usedMem; + this->values[1] = pl->buffersMem; + this->values[2] = pl->sharedMem; + this->values[3] = pl->cachedMem; + this->values[4] = pl->availableMem; if (lpl->zfs.enabled != 0) { this->values[0] -= lpl->zfs.size; - this->values[2] += lpl->zfs.size; + this->values[3] += lpl->zfs.size; } } @@ -301,6 +348,7 @@ void Platform_setSwapValues(Meter* this) { const ProcessList* pl = this->pl; this->total = pl->totalSwap; this->values[0] = pl->usedSwap; + this->values[1] = pl->cachedSwap; } void Platform_setZramValues(Meter* this) { @@ -366,8 +414,8 @@ char* Platform_getProcessEnv(pid_t pid) { */ char* Platform_getInodeFilename(pid_t pid, ino_t inode) { struct stat sb; - struct dirent *de; - DIR *dirp; + const struct dirent* de; + DIR* dirp; ssize_t len; int fd; @@ -495,33 +543,27 @@ bool Platform_getDiskIO(DiskIOData* data) { if (!fd) return false; - unsigned long int read_sum = 0, write_sum = 0, timeSpend_sum = 0; + char lastTopDisk[32] = { '\0' }; + + unsigned long long int read_sum = 0, write_sum = 0, timeSpend_sum = 0; char lineBuffer[256]; while (fgets(lineBuffer, sizeof(lineBuffer), fd)) { char diskname[32]; - unsigned long int read_tmp, write_tmp, timeSpend_tmp; - if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %lu %*u %*u %*u %lu %*u %*u %lu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) { + unsigned long long int read_tmp, write_tmp, timeSpend_tmp; + if (sscanf(lineBuffer, "%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu %*u %*u %llu", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) { if (String_startsWith(diskname, "dm-")) continue; - /* only count root disks, e.g. do not count IO from sda and sda1 twice */ - if ((diskname[0] == 's' || diskname[0] == 'h') - && diskname[1] == 'd' - && isalpha((unsigned char)diskname[2]) - && isdigit((unsigned char)diskname[3])) + if (String_startsWith(diskname, "zram")) continue; - /* only count root disks, e.g. do not count IO from mmcblk0 and mmcblk0p1 twice */ - if (diskname[0] == 'm' - && diskname[1] == 'm' - && diskname[2] == 'c' - && diskname[3] == 'b' - && diskname[4] == 'l' - && diskname[5] == 'k' - && isdigit((unsigned char)diskname[6]) - && diskname[7] == 'p') + /* only count root disks, e.g. do not count IO from sda and sda1 twice */ + if (lastTopDisk[0] && String_startsWith(diskname, lastTopDisk)) continue; + /* This assumes disks are listed directly before any of their partitions */ + String_safeStrncpy(lastTopDisk, diskname, sizeof(lastTopDisk)); + read_sum += read_tmp; write_sum += write_tmp; timeSpend_sum += timeSpend_tmp; @@ -535,42 +577,35 @@ bool Platform_getDiskIO(DiskIOData* data) { return true; } -bool Platform_getNetworkIO(unsigned long int* bytesReceived, - unsigned long int* packetsReceived, - unsigned long int* bytesTransmitted, - unsigned long int* packetsTransmitted) { +bool Platform_getNetworkIO(NetworkIOData* data) { FILE* fd = fopen(PROCDIR "/net/dev", "r"); if (!fd) return false; - unsigned long int bytesReceivedSum = 0, packetsReceivedSum = 0, bytesTransmittedSum = 0, packetsTransmittedSum = 0; + memset(data, 0, sizeof(NetworkIOData)); char lineBuffer[512]; while (fgets(lineBuffer, sizeof(lineBuffer), fd)) { char interfaceName[32]; - unsigned long int bytesReceivedParsed, packetsReceivedParsed, bytesTransmittedParsed, packetsTransmittedParsed; - if (sscanf(lineBuffer, "%31s %lu %lu %*u %*u %*u %*u %*u %*u %lu %lu", + unsigned long long int bytesReceived, packetsReceived, bytesTransmitted, packetsTransmitted; + if (sscanf(lineBuffer, "%31s %llu %llu %*u %*u %*u %*u %*u %*u %llu %llu", interfaceName, - &bytesReceivedParsed, - &packetsReceivedParsed, - &bytesTransmittedParsed, - &packetsTransmittedParsed) != 5) + &bytesReceived, + &packetsReceived, + &bytesTransmitted, + &packetsTransmitted) != 5) continue; if (String_eq(interfaceName, "lo:")) continue; - bytesReceivedSum += bytesReceivedParsed; - packetsReceivedSum += packetsReceivedParsed; - bytesTransmittedSum += bytesTransmittedParsed; - packetsTransmittedSum += packetsTransmittedParsed; + data->bytesReceived += bytesReceived; + data->packetsReceived += packetsReceived; + data->bytesTransmitted += bytesTransmitted; + data->packetsTransmitted += packetsTransmitted; } fclose(fd); - *bytesReceived = bytesReceivedSum; - *packetsReceived = packetsReceivedSum; - *bytesTransmitted = bytesTransmittedSum; - *packetsTransmitted = packetsTransmittedSum; return true; } @@ -596,11 +631,11 @@ static unsigned long int parseBatInfo(const char* fileName, const unsigned short memset(batteries, 0, MAX_BATTERIES * sizeof(char*)); while (nBatteries < MAX_BATTERIES) { - struct dirent* dirEntry = readdir(batteryDir); + const struct dirent* dirEntry = readdir(batteryDir); if (!dirEntry) break; - char* entryName = dirEntry->d_name; + const char* entryName = dirEntry->d_name; if (!String_startsWith(entryName, "BAT")) continue; @@ -653,7 +688,7 @@ static ACPresence procAcpiCheck(void) { return AC_ERROR; for (;;) { - struct dirent* dirEntry = readdir(dir); + const struct dirent* dirEntry = readdir(dir); if (!dirEntry) break; @@ -688,8 +723,7 @@ static ACPresence procAcpiCheck(void) { break; } - if (dir) - closedir(dir); + closedir(dir); return isOn; } @@ -728,7 +762,7 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { unsigned long int totalRemain = 0; for (;;) { - struct dirent* dirEntry = readdir(dir); + const struct dirent* dirEntry = readdir(dir); if (!dirEntry) break; @@ -753,7 +787,7 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { } char* buf = buffer; - char* line = NULL; + const char* line; bool full = false; bool now = false; int fullSize = 0; @@ -852,3 +886,160 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { Platform_Battery_cacheIsOnAC = *isOnAC; Platform_Battery_cacheTime = now; } + +void Platform_longOptionsUsage(const char* name) +{ +#ifdef HAVE_LIBCAP + printf( +" --drop-capabilities[=off|basic|strict] Drop Linux capabilities when running as root\n" +" off - do not drop any capabilities\n" +" basic (default) - drop all capabilities not needed by %s\n" +" strict - drop all capabilities except those needed for\n" +" core functionality\n", name); +#else + (void) name; +#endif +} + +bool Platform_getLongOption(int opt, int argc, char** argv) { +#ifndef HAVE_LIBCAP + (void) argc; + (void) argv; +#endif + + switch (opt) { +#ifdef HAVE_LIBCAP + case 160: { + const char* mode = optarg; + if (!mode && optind < argc && argv[optind] != NULL && + (argv[optind][0] != '\0' && argv[optind][0] != '-')) { + mode = argv[optind++]; + } + + if (!mode || String_eq(mode, "basic")) { + Platform_capabilitiesMode = CAP_MODE_BASIC; + } else if (String_eq(mode, "off")) { + Platform_capabilitiesMode = CAP_MODE_OFF; + } else if (String_eq(mode, "strict")) { + Platform_capabilitiesMode = CAP_MODE_STRICT; + } else { + fprintf(stderr, "Error: invalid capabilities mode \"%s\".\n", mode); + exit(1); + } + return true; + } +#endif + + default: + break; + } + return false; +} + +#ifdef HAVE_LIBCAP +static int dropCapabilities(enum CapMode mode) { + + if (mode == CAP_MODE_OFF) + return 0; + + /* capabilities we keep to operate */ + const cap_value_t keepcapsStrict[] = { + CAP_DAC_READ_SEARCH, + CAP_SYS_PTRACE, + }; + const cap_value_t keepcapsBasic[] = { + CAP_DAC_READ_SEARCH, /* read non world-readable process files of other users, like /proc/[pid]/io */ + CAP_KILL, /* send signals to processes of other users */ + CAP_SYS_NICE, /* lower process nice value / change nice value for arbitrary processes */ + CAP_SYS_PTRACE, /* read /proc/[pid]/exe */ +#ifdef HAVE_DELAYACCT + CAP_NET_ADMIN, /* communicate over netlink socket for delay accounting */ +#endif + }; + const cap_value_t* const keepcaps = (mode == CAP_MODE_BASIC) ? keepcapsBasic : keepcapsStrict; + const size_t ncap = (mode == CAP_MODE_BASIC) ? ARRAYSIZE(keepcapsBasic) : ARRAYSIZE(keepcapsStrict); + + cap_t caps = cap_init(); + if (caps == NULL) { + fprintf(stderr, "Error: can not initialize capabilities: %s\n", strerror(errno)); + return -1; + } + + if (cap_clear(caps) < 0) { + fprintf(stderr, "Error: can not clear capabilities: %s\n", strerror(errno)); + cap_free(caps); + return -1; + } + + cap_t currCaps = cap_get_proc(); + if (currCaps == NULL) { + fprintf(stderr, "Error: can not get current process capabilities: %s\n", strerror(errno)); + cap_free(caps); + return -1; + } + + for (size_t i = 0; i < ncap; i++) { + if (!CAP_IS_SUPPORTED(keepcaps[i])) + continue; + + cap_flag_value_t current; + if (cap_get_flag(currCaps, keepcaps[i], CAP_PERMITTED, ¤t) < 0) { + fprintf(stderr, "Error: can not get current value of capability %d: %s\n", keepcaps[i], strerror(errno)); + cap_free(currCaps); + cap_free(caps); + return -1; + } + + if (current != CAP_SET) + continue; + + if (cap_set_flag(caps, CAP_PERMITTED, 1, &keepcaps[i], CAP_SET) < 0) { + fprintf(stderr, "Error: can not set permitted capability %d: %s\n", keepcaps[i], strerror(errno)); + cap_free(currCaps); + cap_free(caps); + return -1; + } + + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &keepcaps[i], CAP_SET) < 0) { + fprintf(stderr, "Error: can not set effective capability %d: %s\n", keepcaps[i], strerror(errno)); + cap_free(currCaps); + cap_free(caps); + return -1; + } + } + + if (cap_set_proc(caps) < 0) { + fprintf(stderr, "Error: can not set process capabilities: %s\n", strerror(errno)); + cap_free(currCaps); + cap_free(caps); + return -1; + } + + cap_free(currCaps); + cap_free(caps); + + return 0; +} +#endif + +void Platform_init(void) { +#ifdef HAVE_LIBCAP + if (dropCapabilities(Platform_capabilitiesMode) < 0) + exit(1); +#endif + + 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(); +#endif +} + +void Platform_done(void) { +#ifdef HAVE_SENSORS_SENSORS_H + LibSensors_cleanup(); +#endif +} diff --git a/linux/Platform.h b/linux/Platform.h index fe81448..0d6d4a9 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -7,16 +7,35 @@ Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ +#include "config.h" + +#include #include +#include +#include +#include #include #include "Action.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" +#include "Hashtable.h" +#include "Macros.h" #include "Meter.h" +#include "NetworkIOMeter.h" #include "Process.h" #include "ProcessLocksScreen.h" +#include "RichString.h" #include "SignalsPanel.h" +#include "generic/gettime.h" +#include "generic/hostname.h" +#include "generic/uname.h" + +/* GNU/Hurd does not have PATH_MAX in limits.h */ +#ifndef PATH_MAX + #define PATH_MAX 4096 +#endif + extern const ProcessField Platform_defaultFields[]; @@ -38,7 +57,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen); int Platform_getMaxPid(void); -double Platform_setCPUValues(Meter* this, int cpu); +double Platform_setCPUValues(Meter* this, unsigned int cpu); void Platform_setMemoryValues(Meter* this); @@ -56,15 +75,57 @@ char* Platform_getInodeFilename(pid_t pid, ino_t inode); FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid); -void Platform_getPressureStall(const char *file, bool some, double* ten, double* sixty, double* threehundred); +void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred); bool Platform_getDiskIO(DiskIOData* data); -bool Platform_getNetworkIO(unsigned long int* bytesReceived, - unsigned long int* packetsReceived, - unsigned long int* bytesTransmitted, - unsigned long int* packetsTransmitted); +bool Platform_getNetworkIO(NetworkIOData* data); + +void Platform_getBattery(double* percent, ACPresence* isOnAC); + +static inline void Platform_getHostname(char* buffer, size_t size) { + Generic_hostname(buffer, size); +} + +static inline void Platform_getRelease(char** string) { + *string = Generic_uname(); +} + +#ifdef HAVE_LIBCAP + #define PLATFORM_LONG_OPTIONS \ + {"drop-capabilities", optional_argument, 0, 160}, +#else + #define PLATFORM_LONG_OPTIONS +#endif + +void Platform_longOptionsUsage(const char* name); + +bool Platform_getLongOption(int opt, int argc, char** argv); + +static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { + Generic_gettime_realtime(tv, msec); +} + +static inline void Platform_gettime_monotonic(uint64_t* msec) { + Generic_gettime_monotonic(msec); +} + +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } + +static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { } + +static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } + +static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { } + +static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } + +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } -void Platform_getBattery(double *percent, ACPresence *isOnAC); +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } #endif diff --git a/linux/PressureStallMeter.c b/linux/PressureStallMeter.c index 7486968..ff34f9b 100644 --- a/linux/PressureStallMeter.c +++ b/linux/PressureStallMeter.c @@ -6,7 +6,7 @@ Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ -#include "PressureStallMeter.h" +#include "linux/PressureStallMeter.h" #include #include @@ -25,7 +25,7 @@ static const int PressureStallMeter_attributes[] = { PRESSURE_STALL_THREEHUNDRED }; -static void PressureStallMeter_updateValues(Meter* this, char* buffer, size_t len) { +static void PressureStallMeter_updateValues(Meter* this) { const char* file; if (strstr(Meter_name(this), "CPU")) { file = "cpu"; @@ -47,18 +47,20 @@ static void PressureStallMeter_updateValues(Meter* this, char* buffer, size_t le /* only print bar for ten (not sixty and threehundred), cause the sum is meaningless */ this->curItems = 1; - xSnprintf(buffer, len, "%s %s %5.2lf%% %5.2lf%% %5.2lf%%", some ? "some" : "full", file, this->values[0], this->values[1], this->values[2]); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s %s %5.2lf%% %5.2lf%% %5.2lf%%", some ? "some" : "full", file, this->values[0], this->values[1], this->values[2]); } static void PressureStallMeter_display(const Object* cast, RichString* out) { const Meter* this = (const Meter*)cast; char buffer[20]; - xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[0]); - RichString_writeAscii(out, CRT_colors[PRESSURE_STALL_TEN], buffer); - xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[1]); - RichString_appendAscii(out, CRT_colors[PRESSURE_STALL_SIXTY], buffer); - xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[2]); - RichString_appendAscii(out, CRT_colors[PRESSURE_STALL_THREEHUNDRED], buffer); + int len; + + len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[0]); + RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_TEN], buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[1]); + RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_SIXTY], buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%5.2lf%% ", this->values[2]); + RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_THREEHUNDRED], buffer, len); } const MeterClass PressureStallCPUSomeMeter_class = { diff --git a/linux/PressureStallMeter.h b/linux/PressureStallMeter.h index 1a0ad58..7317b35 100644 --- a/linux/PressureStallMeter.h +++ b/linux/PressureStallMeter.h @@ -12,6 +12,7 @@ in the source distribution for its full text. #include "Meter.h" + extern const MeterClass PressureStallCPUSomeMeter_class; extern const MeterClass PressureStallIOSomeMeter_class; diff --git a/linux/ProcessField.h b/linux/ProcessField.h index 6e2eff3..69bdff0 100644 --- a/linux/ProcessField.h +++ b/linux/ProcessField.h @@ -19,7 +19,6 @@ in the source distribution for its full text. M_TRS = 42, \ M_DRS = 43, \ M_LRS = 44, \ - M_DT = 45, \ CTID = 100, \ VPID = 101, \ VXID = 102, \ @@ -44,9 +43,8 @@ in the source distribution for its full text. M_PSSWP = 121, \ CTXT = 122, \ SECATTR = 123, \ - PROC_COMM = 124, \ - PROC_EXE = 125, \ - CWD = 126, \ + AUTOGROUP_ID = 127, \ + AUTOGROUP_NICE = 128, \ // End of list diff --git a/linux/SELinuxMeter.c b/linux/SELinuxMeter.c index 892f1e8..e3b076d 100644 --- a/linux/SELinuxMeter.c +++ b/linux/SELinuxMeter.c @@ -5,18 +5,17 @@ Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ -#include "SELinuxMeter.h" +#include "linux/SELinuxMeter.h" #include "CRT.h" #include +#include #include #include -#include #include #include -#include "Macros.h" #include "Object.h" #include "XUtils.h" @@ -35,7 +34,7 @@ static bool hasSELinuxMount(void) { return false; } - if ((uint32_t)sfbuf.f_type != (uint32_t)SELINUX_MAGIC) { + if ((uint32_t)sfbuf.f_type != /* SELINUX_MAGIC */ 0xf97cff8cU) { return false; } @@ -70,11 +69,11 @@ static bool isSelinuxEnforcing(void) { return !!enforce; } -static void SELinuxMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t len) { +static void SELinuxMeter_updateValues(Meter* this) { enabled = isSelinuxEnabled(); enforcing = isSelinuxEnforcing(); - xSnprintf(buffer, len, "%s%s", enabled ? "enabled" : "disabled", enabled ? (enforcing ? "; mode: enforcing" : "; mode: permissive") : ""); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s%s", enabled ? "enabled" : "disabled", enabled ? (enforcing ? "; mode: enforcing" : "; mode: permissive") : ""); } const MeterClass SELinuxMeter_class = { diff --git a/linux/SELinuxMeter.h b/linux/SELinuxMeter.h index d79ad01..453940c 100644 --- a/linux/SELinuxMeter.h +++ b/linux/SELinuxMeter.h @@ -9,6 +9,7 @@ in the source distribution for its full text. #include "Meter.h" + extern const MeterClass SELinuxMeter_class; #endif /* HEADER_SELinuxMeter */ diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c index 61bb59b..245bb7b 100644 --- a/linux/SystemdMeter.c +++ b/linux/SystemdMeter.c @@ -5,7 +5,7 @@ Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ -#include "SystemdMeter.h" +#include "linux/SystemdMeter.h" #include #include @@ -19,10 +19,22 @@ in the source distribution for its full text. #include "Macros.h" #include "Object.h" #include "RichString.h" +#include "Settings.h" #include "XUtils.h" +#if defined(BUILD_STATIC) && defined(HAVE_LIBSYSTEMD) +#include +#endif -#define INVALID_VALUE ((unsigned int)-1) + +#ifdef BUILD_STATIC + +#define sym_sd_bus_open_system sd_bus_open_system +#define sym_sd_bus_get_property_string sd_bus_get_property_string +#define sym_sd_bus_get_property_trivial sd_bus_get_property_trivial +#define sym_sd_bus_unref sd_bus_unref + +#else typedef void sd_bus; typedef void sd_bus_error; @@ -30,19 +42,35 @@ static int (*sym_sd_bus_open_system)(sd_bus**); static int (*sym_sd_bus_get_property_string)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char**); static int (*sym_sd_bus_get_property_trivial)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char, void*); static sd_bus* (*sym_sd_bus_unref)(sd_bus*); +static void* dlopenHandle = NULL; + +#endif /* BUILD_STATIC */ + +#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD) +static sd_bus* bus = NULL; +#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */ + + +#define INVALID_VALUE ((unsigned int)-1) static char* systemState = NULL; static unsigned int nFailedUnits = INVALID_VALUE; static unsigned int nInstalledJobs = INVALID_VALUE; static unsigned int nNames = INVALID_VALUE; static unsigned int nJobs = INVALID_VALUE; -static void* dlopenHandle = NULL; -static sd_bus* bus = NULL; static void SystemdMeter_done(ATTR_UNUSED Meter* this) { free(systemState); systemState = NULL; +#ifdef BUILD_STATIC +# ifdef HAVE_LIBSYSTEMD + if (bus) { + sym_sd_bus_unref(bus); + } + bus = NULL; +# endif /* HAVE_LIBSYSTEMD */ +#else /* BUILD_STATIC */ if (bus && dlopenHandle) { sym_sd_bus_unref(bus); } @@ -52,9 +80,12 @@ static void SystemdMeter_done(ATTR_UNUSED Meter* this) { dlclose(dlopenHandle); dlopenHandle = NULL; } +#endif /* BUILD_STATIC */ } +#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD) static int updateViaLib(void) { +#ifndef BUILD_STATIC if (!dlopenHandle) { dlopenHandle = dlopen("libsystemd.so.0", RTLD_LAZY); if (!dlopenHandle) @@ -76,6 +107,7 @@ static int updateViaLib(void) { #undef resolve } +#endif /* !BUILD_STATIC */ int r; @@ -152,15 +184,21 @@ busfailure: bus = NULL; return -2; +#ifndef BUILD_STATIC dlfailure: if (dlopenHandle) { dlclose(dlopenHandle); dlopenHandle = NULL; } return -1; +#endif /* !BUILD_STATIC */ } +#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */ static void updateViaExec(void) { + if (Settings_isReadonly()) + return; + int fdpair[2]; if (pipe(fdpair) < 0) return; @@ -181,15 +219,15 @@ static void updateViaExec(void) { exit(1); dup2(fdnull, STDERR_FILENO); close(fdnull); - execl("/bin/systemctl", - "/bin/systemctl", - "show", - "--property=SystemState", - "--property=NFailedUnits", - "--property=NNames", - "--property=NJobs", - "--property=NInstalledJobs", - NULL); + execlp("systemctl", + "systemctl", + "show", + "--property=SystemState", + "--property=NFailedUnits", + "--property=NNames", + "--property=NJobs", + "--property=NInstalledJobs", + NULL); exit(127); } close(fdpair[1]); @@ -213,8 +251,7 @@ static void updateViaExec(void) { if (newline) { *newline = '\0'; } - free(systemState); - systemState = xStrdup(lineBuffer + strlen("SystemState=")); + free_and_xStrdup(&systemState, lineBuffer + strlen("SystemState=")); } else if (String_startsWith(lineBuffer, "NFailedUnits=")) { nFailedUnits = strtoul(lineBuffer + strlen("NFailedUnits="), NULL, 10); } else if (String_startsWith(lineBuffer, "NNames=")) { @@ -229,15 +266,19 @@ static void updateViaExec(void) { fclose(commandOutput); } -static void SystemdMeter_updateValues(ATTR_UNUSED Meter* this, char* buffer, size_t size) { +static void SystemdMeter_updateValues(Meter* this) { free(systemState); systemState = NULL; nFailedUnits = nInstalledJobs = nNames = nJobs = INVALID_VALUE; +#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD) if (updateViaLib() < 0) updateViaExec(); +#else + updateViaExec(); +#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */ - xSnprintf(buffer, size, "%s", systemState ? systemState : "???"); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s", systemState ? systemState : "???"); } static int zeroDigitColor(unsigned int value) { @@ -265,8 +306,9 @@ static int valueDigitColor(unsigned int value) { static void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { char buffer[16]; + int len; - int color = (systemState && 0 == strcmp(systemState, "running")) ? METER_VALUE_OK : METER_VALUE_ERROR; + int color = (systemState && String_eq(systemState, "running")) ? METER_VALUE_OK : METER_VALUE_ERROR; RichString_writeAscii(out, CRT_colors[color], systemState ? systemState : "N/A"); RichString_appendAscii(out, CRT_colors[METER_TEXT], " ("); @@ -274,40 +316,44 @@ static void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out if (nFailedUnits == INVALID_VALUE) { buffer[0] = '?'; buffer[1] = '\0'; + len = 1; } else { - xSnprintf(buffer, sizeof(buffer), "%u", nFailedUnits); + len = xSnprintf(buffer, sizeof(buffer), "%u", nFailedUnits); } - RichString_appendAscii(out, zeroDigitColor(nFailedUnits), buffer); + RichString_appendnAscii(out, zeroDigitColor(nFailedUnits), buffer, len); RichString_appendAscii(out, CRT_colors[METER_TEXT], "/"); if (nNames == INVALID_VALUE) { buffer[0] = '?'; buffer[1] = '\0'; + len = 1; } else { - xSnprintf(buffer, sizeof(buffer), "%u", nNames); + len = xSnprintf(buffer, sizeof(buffer), "%u", nNames); } - RichString_appendAscii(out, valueDigitColor(nNames), buffer); + RichString_appendnAscii(out, valueDigitColor(nNames), buffer, len); RichString_appendAscii(out, CRT_colors[METER_TEXT], " failed) ("); if (nJobs == INVALID_VALUE) { buffer[0] = '?'; buffer[1] = '\0'; + len = 1; } else { - xSnprintf(buffer, sizeof(buffer), "%u", nJobs); + len = xSnprintf(buffer, sizeof(buffer), "%u", nJobs); } - RichString_appendAscii(out, zeroDigitColor(nJobs), buffer); + RichString_appendnAscii(out, zeroDigitColor(nJobs), buffer, len); RichString_appendAscii(out, CRT_colors[METER_TEXT], "/"); if (nInstalledJobs == INVALID_VALUE) { buffer[0] = '?'; buffer[1] = '\0'; + len = 1; } else { - xSnprintf(buffer, sizeof(buffer), "%u", nInstalledJobs); + len = xSnprintf(buffer, sizeof(buffer), "%u", nInstalledJobs); } - RichString_appendAscii(out, valueDigitColor(nInstalledJobs), buffer); + RichString_appendnAscii(out, valueDigitColor(nInstalledJobs), buffer, len); RichString_appendAscii(out, CRT_colors[METER_TEXT], " jobs)"); } diff --git a/linux/SystemdMeter.h b/linux/SystemdMeter.h index 0f226d6..98ce6f7 100644 --- a/linux/SystemdMeter.h +++ b/linux/SystemdMeter.h @@ -10,6 +10,7 @@ in the source distribution for its full text. #include "Meter.h" + extern const MeterClass SystemdMeter_class; #endif /* HEADER_SystemdMeter */ diff --git a/linux/ZramMeter.c b/linux/ZramMeter.c index 723de0a..e1e27b7 100644 --- a/linux/ZramMeter.c +++ b/linux/ZramMeter.c @@ -1,4 +1,6 @@ -#include "ZramMeter.h" +#include "linux/ZramMeter.h" + +#include #include "CRT.h" #include "Meter.h" @@ -11,7 +13,9 @@ static const int ZramMeter_attributes[] = { ZRAM }; -static void ZramMeter_updateValues(Meter* this, char* buffer, size_t size) { +static void ZramMeter_updateValues(Meter* this) { + char* buffer = this->txtBuffer; + size_t size = sizeof(this->txtBuffer); int written; Platform_setZramValues(this); diff --git a/linux/ZramMeter.h b/linux/ZramMeter.h index 7cf7861..ddba1ba 100644 --- a/linux/ZramMeter.h +++ b/linux/ZramMeter.h @@ -3,6 +3,7 @@ #include "Meter.h" + extern const MeterClass ZramMeter_class; #endif diff --git a/linux/ZramStats.h b/linux/ZramStats.h index 2305cfd..67aadcc 100644 --- a/linux/ZramStats.h +++ b/linux/ZramStats.h @@ -2,9 +2,9 @@ #define HEADER_ZramStats typedef struct ZramStats_ { - unsigned long long int totalZram; - unsigned long long int usedZramComp; - unsigned long long int usedZramOrig; + memory_t totalZram; + memory_t usedZramComp; + memory_t usedZramOrig; } ZramStats; #endif -- cgit v1.2.3