diff options
Diffstat (limited to 'pcp')
-rw-r--r-- | pcp/PCPDynamicColumn.c | 348 | ||||
-rw-r--r-- | pcp/PCPDynamicColumn.h | 35 | ||||
-rw-r--r-- | pcp/PCPDynamicMeter.c | 468 | ||||
-rw-r--r-- | pcp/PCPDynamicMeter.h | 44 | ||||
-rw-r--r-- | pcp/PCPMetric.c | 180 | ||||
-rw-r--r-- | pcp/PCPMetric.h | 180 | ||||
-rw-r--r-- | pcp/PCPProcess.c | 291 | ||||
-rw-r--r-- | pcp/PCPProcess.h | 102 | ||||
-rw-r--r-- | pcp/PCPProcessList.c | 727 | ||||
-rw-r--r-- | pcp/PCPProcessList.h | 74 | ||||
-rw-r--r-- | pcp/Platform.c | 842 | ||||
-rw-r--r-- | pcp/Platform.h | 156 | ||||
-rw-r--r-- | pcp/ProcessField.h | 51 | ||||
-rw-r--r-- | pcp/columns/container | 10 | ||||
-rw-r--r-- | pcp/columns/delayacct | 10 | ||||
-rw-r--r-- | pcp/columns/fdcount | 10 | ||||
-rw-r--r-- | pcp/columns/guest | 17 | ||||
-rw-r--r-- | pcp/columns/memory | 39 | ||||
-rw-r--r-- | pcp/columns/sched | 10 | ||||
-rw-r--r-- | pcp/columns/swap | 15 | ||||
-rw-r--r-- | pcp/columns/tcp | 31 | ||||
-rw-r--r-- | pcp/columns/udp | 31 | ||||
-rw-r--r-- | pcp/columns/wchan | 17 | ||||
-rw-r--r-- | pcp/meters/entropy | 9 | ||||
-rw-r--r-- | pcp/meters/freespace | 11 | ||||
-rw-r--r-- | pcp/meters/ipc | 13 | ||||
-rw-r--r-- | pcp/meters/locks | 15 | ||||
-rw-r--r-- | pcp/meters/memcache | 11 | ||||
-rw-r--r-- | pcp/meters/mysql | 71 | ||||
-rw-r--r-- | pcp/meters/postfix | 20 | ||||
-rw-r--r-- | pcp/meters/redis | 39 | ||||
-rw-r--r-- | pcp/meters/tcp | 21 |
32 files changed, 3898 insertions, 0 deletions
diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c new file mode 100644 index 0000000..33c6d72 --- /dev/null +++ b/pcp/PCPDynamicColumn.c @@ -0,0 +1,348 @@ +/* +htop - PCPDynamicColumn.c +(C) 2021 Sohaib Mohammed +(C) 2021 htop dev team +(C) 2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPDynamicColumn.h" + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "CRT.h" +#include "Macros.h" +#include "Platform.h" +#include "Process.h" +#include "ProcessList.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/PCPProcess.h" +#include "pcp/PCPMetric.h" + + +static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) { + if (!column->super.name[0]) + return false; + + size_t bytes = 16 + strlen(column->super.name); + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.column.%s", column->super.name); + + column->metricName = metricName; + column->id = columns->offset + columns->cursor; + columns->cursor++; + + Platform_addMetric(column->id, metricName); + return true; +} + +static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) { + /* lookup a dynamic metric with this name, else create */ + if (PCPDynamicColumn_addMetric(columns, column) == false) + return; + + /* derived metrics in all dynamic columns for simplicity */ + char* error; + if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) { + char* note; + xAsprintf(¬e, + "%s: failed to parse expression in %s at line %u\n%s\n", + pmGetProgname(), path, line, error); + free(error); + errno = EINVAL; + CRT_fatalError(note); + free(note); + } +} + +// Ensure a valid name for use in a PCP metric name and in htoprc +static bool PCPDynamicColumn_validateColumnName(char* key, const char* path, unsigned int line) { + char* p = key; + char* end = strrchr(key, ']'); + + if (end) { + *end = '\0'; + } else { + fprintf(stderr, + "%s: no closing brace on column name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + + while (*p) { + if (p == key) { + if (!isalpha(*p) && *p != '_') + break; + } else { + if (!isalnum(*p) && *p != '_') + break; + } + p++; + } + if (*p != '\0') { /* badness */ + fprintf(stderr, + "%s: invalid column name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +// Ensure a column name has not been defined previously +static bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) { + return DynamicColumn_search(columns->table, key, NULL) == NULL; +} + +static PCPDynamicColumn* PCPDynamicColumn_new(PCPDynamicColumns* columns, const char* name) { + PCPDynamicColumn* column = xCalloc(1, sizeof(*column)); + String_safeStrncpy(column->super.name, name, sizeof(column->super.name)); + + size_t id = columns->count + LAST_PROCESSFIELD; + Hashtable_put(columns->table, id, column); + columns->count++; + + return column; +} + +static void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicColumn* column = NULL; + unsigned int lineno = 0; + bool ok = true; + for (;;) { + char* line = String_readLine(file); + if (!line) + break; + lineno++; + + /* cleanup whitespace, skip comment lines */ + char* trimmed = String_trim(line); + free(line); + if (!trimmed || !trimmed[0] || trimmed[0] == '#') { + free(trimmed); + continue; + } + + size_t n; + char** config = String_split(trimmed, '=', &n); + free(trimmed); + if (config == NULL) + continue; + + char* key = String_trim(config[0]); + char* value = n > 1 ? String_trim(config[1]) : NULL; + if (key[0] == '[') { /* new section heading - i.e. new column */ + ok = PCPDynamicColumn_validateColumnName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicColumn_uniqueName(key + 1, columns); + if (ok) + column = PCPDynamicColumn_new(columns, key + 1); + } else if (value && column && String_eq(key, "caption")) { + free_and_xStrdup(&column->super.caption, value); + } else if (value && column && String_eq(key, "heading")) { + free_and_xStrdup(&column->super.heading, value); + } else if (value && column && String_eq(key, "description")) { + free_and_xStrdup(&column->super.description, value); + } else if (value && column && String_eq(key, "width")) { + column->super.width = strtoul(value, NULL, 10); + } else if (value && column && String_eq(key, "metric")) { + PCPDynamicColumn_parseMetric(columns, column, path, lineno, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicColumn_scanDir(PCPDynamicColumns* columns, char* path) { + DIR* dir = opendir(path); + if (!dir) + return; + + struct dirent* dirent; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + + char* file = String_cat(path, dirent->d_name); + PCPDynamicColumn_parseFile(columns, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicColumns_init(PCPDynamicColumns* columns) { + const char* share = pmGetConfig("PCP_SHARE_DIR"); + const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR"); + const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + const char* override = getenv("PCP_HTOP_DIR"); + const char* home = getenv("HOME"); + char* path; + + if (!xdgConfigHome && !home) { + const struct passwd* pw = getpwuid(getuid()); + if (pw) + home = pw->pw_dir; + } + + columns->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/columns/"); + else if (home) + path = String_cat(home, "/.config/htop/columns/"); + else + path = NULL; + if (path) { + PCPDynamicColumn_scanDir(columns, path); + free(path); + } + + /* next, search in the system columns directory */ + path = String_cat(sysconf, "/htop/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); + + /* next, try the readonly system columns directory */ + path = String_cat(share, "/htop/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); +} + +static void PCPDynamicColumns_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicColumn* column = (PCPDynamicColumn*) value; + free(column->metricName); + free(column->super.heading); + free(column->super.caption); + free(column->super.description); +} + +void PCPDynamicColumns_done(Hashtable* table) { + Hashtable_foreach(table, PCPDynamicColumns_free, NULL); +} + +void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) { + const PCPProcess* pp = (const PCPProcess*) proc; + unsigned int type = PCPMetric_type(this->id); + + pmAtomValue atom; + if (!PCPMetric_instance(this->id, proc->pid, pp->offset, &atom, type)) { + RichString_appendAscii(str, CRT_colors[METER_VALUE_ERROR], "no data"); + return; + } + + int width = this->super.width; + if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) + width = DYNAMIC_DEFAULT_COLUMN_WIDTH; + int abswidth = abs(width); + if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) { + abswidth = DYNAMIC_MAX_COLUMN_WIDTH; + width = -abswidth; + } + + char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1]; + int attr = CRT_colors[DEFAULT_COLOR]; + switch (type) { + case PM_TYPE_STRING: + attr = CRT_colors[PROCESS_SHADOW]; + Process_printLeftAlignedField(str, attr, atom.cp, abswidth); + free(atom.cp); + break; + case PM_TYPE_32: + xSnprintf(buffer, sizeof(buffer), "%*d ", width, atom.l); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_U32: + xSnprintf(buffer, sizeof(buffer), "%*u ", width, atom.ul); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_64: + xSnprintf(buffer, sizeof(buffer), "%*lld ", width, (long long) atom.ll); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_U64: + xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long) atom.ull); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_FLOAT: + xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, (double) atom.f); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_DOUBLE: + xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, atom.d); + RichString_appendAscii(str, attr, buffer); + break; + default: + attr = CRT_colors[METER_VALUE_ERROR]; + RichString_appendAscii(str, attr, "no type"); + break; + } +} + +int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) { + const PCPDynamicColumn* column = Hashtable_get(p1->super.processList->dynamicColumns, key); + + if (!column) + return -1; + + size_t metric = column->id; + unsigned int type = PCPMetric_type(metric); + + pmAtomValue atom1 = {0}, atom2 = {0}; + if (!PCPMetric_instance(metric, p1->super.pid, p1->offset, &atom1, type) || + !PCPMetric_instance(metric, p2->super.pid, p2->offset, &atom2, type)) { + if (type == PM_TYPE_STRING) { + free(atom1.cp); + free(atom2.cp); + } + return -1; + } + + switch (type) { + case PM_TYPE_STRING: { + int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp); + free(atom2.cp); + free(atom1.cp); + return cmp; + } + case PM_TYPE_32: + return SPACESHIP_NUMBER(atom2.l, atom1.l); + case PM_TYPE_U32: + return SPACESHIP_NUMBER(atom2.ul, atom1.ul); + case PM_TYPE_64: + return SPACESHIP_NUMBER(atom2.ll, atom1.ll); + case PM_TYPE_U64: + return SPACESHIP_NUMBER(atom2.ull, atom1.ull); + case PM_TYPE_FLOAT: + return SPACESHIP_NUMBER(atom2.f, atom1.f); + case PM_TYPE_DOUBLE: + return SPACESHIP_NUMBER(atom2.d, atom1.d); + default: + break; + } + return -1; +} diff --git a/pcp/PCPDynamicColumn.h b/pcp/PCPDynamicColumn.h new file mode 100644 index 0000000..d0ffe71 --- /dev/null +++ b/pcp/PCPDynamicColumn.h @@ -0,0 +1,35 @@ +#ifndef HEADER_PCPDynamicColumn +#define HEADER_PCPDynamicColumn + +#include <stddef.h> + +#include "DynamicColumn.h" +#include "Hashtable.h" +#include "Process.h" +#include "RichString.h" + +#include "pcp/PCPProcess.h" + + +typedef struct PCPDynamicColumn_ { + DynamicColumn super; + char* metricName; + size_t id; /* identifier for metric array lookups */ +} PCPDynamicColumn; + +typedef struct PCPDynamicColumns_ { + Hashtable* table; + size_t count; /* count of dynamic meters discovered by scan */ + size_t offset; /* start offset into the Platform metric array */ + size_t cursor; /* identifier allocator for each new metric used */ +} PCPDynamicColumns; + +void PCPDynamicColumns_init(PCPDynamicColumns* columns); + +void PCPDynamicColumns_done(Hashtable* table); + +void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str); + +int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key); + +#endif diff --git a/pcp/PCPDynamicMeter.c b/pcp/PCPDynamicMeter.c new file mode 100644 index 0000000..e899988 --- /dev/null +++ b/pcp/PCPDynamicMeter.c @@ -0,0 +1,468 @@ +/* +htop - PCPDynamicMeter.c +(C) 2021 htop dev team +(C) 2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPDynamicMeter.h" + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pcp/pmapi.h> + +#include "Macros.h" +#include "Platform.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/PCPMetric.h" + + +static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) { + size_t bytes = 16 + strlen(meter->super.name) + strlen(name); + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.meter.%s.%s", meter->super.name, name); + + PCPDynamicMetric* metric; + for (size_t i = 0; i < meter->totalMetrics; i++) { + metric = &meter->metrics[i]; + if (String_eq(metric->name, metricName)) { + free(metricName); + return metric; + } + } + + /* not an existing metric in this meter - add it */ + size_t n = meter->totalMetrics + 1; + meter->metrics = xReallocArray(meter->metrics, n, sizeof(PCPDynamicMetric)); + meter->totalMetrics = n; + metric = &meter->metrics[n - 1]; + memset(metric, 0, sizeof(PCPDynamicMetric)); + metric->name = metricName; + metric->label = String_cat(name, ": "); + metric->id = meters->offset + meters->cursor; + meters->cursor++; + + Platform_addMetric(metric->id, metricName); + + return metric; +} + +static void PCPDynamicMeter_parseMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* path, unsigned int line, char* key, char* value) { + PCPDynamicMetric* metric; + char* p; + + if ((p = strchr(key, '.')) == NULL) + return; + *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */ + + if (String_eq(p, "metric")) { + /* lookup a dynamic metric with this name, else create */ + metric = PCPDynamicMeter_lookupMetric(meters, meter, key); + + /* use derived metrics in dynamic meters for simplicity */ + char* error; + if (pmRegisterDerivedMetric(metric->name, value, &error) < 0) { + char* note; + xAsprintf(¬e, + "%s: failed to parse expression in %s at line %u\n%s\n%s", + pmGetProgname(), path, line, error, pmGetProgname()); + free(error); + errno = EINVAL; + CRT_fatalError(note); + free(note); + } + } else { + /* this is a property of a dynamic metric - the metric expression */ + /* may not have been observed yet - i.e. we allow for any ordering */ + metric = PCPDynamicMeter_lookupMetric(meters, meter, key); + if (String_eq(p, "color")) { + if (String_eq(value, "gray")) + metric->color = DYNAMIC_GRAY; + else if (String_eq(value, "darkgray")) + metric->color = DYNAMIC_DARKGRAY; + else if (String_eq(value, "red")) + metric->color = DYNAMIC_RED; + else if (String_eq(value, "green")) + metric->color = DYNAMIC_GREEN; + else if (String_eq(value, "blue")) + metric->color = DYNAMIC_BLUE; + else if (String_eq(value, "cyan")) + metric->color = DYNAMIC_CYAN; + else if (String_eq(value, "magenta")) + metric->color = DYNAMIC_MAGENTA; + else if (String_eq(value, "yellow")) + metric->color = DYNAMIC_YELLOW; + else if (String_eq(value, "white")) + metric->color = DYNAMIC_WHITE; + } else if (String_eq(p, "label")) { + char* label = String_cat(value, ": "); + free_and_xStrdup(&metric->label, label); + free(label); + } else if (String_eq(p, "suffix")) { + free_and_xStrdup(&metric->suffix, value); + } + } +} + +// Ensure a valid name for use in a PCP metric name and in htoprc +static bool PCPDynamicMeter_validateMeterName(char* key, const char* path, unsigned int line) { + char* p = key; + char* end = strrchr(key, ']'); + + if (end) { + *end = '\0'; + } else { + fprintf(stderr, + "%s: no closing brace on meter name at %s line %u\n\"%s\"\n", + pmGetProgname(), path, line, key); + return false; + } + + while (*p) { + if (p == key) { + if (!isalpha(*p) && *p != '_') + break; + } else { + if (!isalnum(*p) && *p != '_') + break; + } + p++; + } + if (*p != '\0') { /* badness */ + fprintf(stderr, + "%s: invalid meter name at %s line %u\n\"%s\"\n", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +// Ensure a meter name has not been defined previously +static bool PCPDynamicMeter_uniqueName(char* key, PCPDynamicMeters* meters) { + return !DynamicMeter_search(meters->table, key, NULL); +} + +static PCPDynamicMeter* PCPDynamicMeter_new(PCPDynamicMeters* meters, const char* name) { + PCPDynamicMeter* meter = xCalloc(1, sizeof(*meter)); + String_safeStrncpy(meter->super.name, name, sizeof(meter->super.name)); + Hashtable_put(meters->table, ++meters->count, meter); + return meter; +} + +static void PCPDynamicMeter_parseFile(PCPDynamicMeters* meters, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicMeter* meter = NULL; + unsigned int lineno = 0; + bool ok = true; + for (;;) { + char* line = String_readLine(file); + if (!line) + break; + lineno++; + + /* cleanup whitespace, skip comment lines */ + char* trimmed = String_trim(line); + free(line); + if (trimmed[0] == '#' || trimmed[0] == '\0') { + free(trimmed); + continue; + } + + size_t n; + char** config = String_split(trimmed, '=', &n); + free(trimmed); + if (config == NULL) + continue; + + char* key = String_trim(config[0]); + char* value = n > 1 ? String_trim(config[1]) : NULL; + if (key[0] == '[') { /* new section heading - i.e. new meter */ + ok = PCPDynamicMeter_validateMeterName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicMeter_uniqueName(key + 1, meters); + if (ok) + meter = PCPDynamicMeter_new(meters, key + 1); + } else if (!ok) { + ; /* skip this one, we're looking for a new header */ + } else if (value && meter && String_eq(key, "caption")) { + char* caption = String_cat(value, ": "); + if (caption) { + free_and_xStrdup(&meter->super.caption, caption); + free(caption); + caption = NULL; + } + } else if (value && meter && String_eq(key, "description")) { + free_and_xStrdup(&meter->super.description, value); + } else if (value && meter && String_eq(key, "type")) { + if (String_eq(config[1], "bar")) + meter->super.type = BAR_METERMODE; + else if (String_eq(config[1], "text")) + meter->super.type = TEXT_METERMODE; + else if (String_eq(config[1], "graph")) + meter->super.type = GRAPH_METERMODE; + else if (String_eq(config[1], "led")) + meter->super.type = LED_METERMODE; + } else if (value && meter && String_eq(key, "maximum")) { + meter->super.maximum = strtod(value, NULL); + } else if (value && meter) { + PCPDynamicMeter_parseMetric(meters, meter, path, lineno, key, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicMeter_scanDir(PCPDynamicMeters* meters, char* path) { + DIR* dir = opendir(path); + if (!dir) + return; + + struct dirent* dirent; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + + char* file = String_cat(path, dirent->d_name); + PCPDynamicMeter_parseFile(meters, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicMeters_init(PCPDynamicMeters* meters) { + const char* share = pmGetConfig("PCP_SHARE_DIR"); + const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR"); + const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + const char* override = getenv("PCP_HTOP_DIR"); + const char* home = getenv("HOME"); + char* path; + + if (!xdgConfigHome && !home) { + const struct passwd* pw = getpwuid(getuid()); + if (pw) + home = pw->pw_dir; + } + + meters->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/meters/"); + PCPDynamicMeter_scanDir(meters, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/meters/"); + else if (home) + path = String_cat(home, "/.config/htop/meters/"); + else + path = NULL; + if (path) { + PCPDynamicMeter_scanDir(meters, path); + free(path); + } + + /* next, search in the system meters directory */ + path = String_cat(sysconf, "/htop/meters/"); + PCPDynamicMeter_scanDir(meters, path); + free(path); + + /* next, try the readonly system meters directory */ + path = String_cat(share, "/htop/meters/"); + PCPDynamicMeter_scanDir(meters, path); + free(path); +} + +static void PCPDynamicMeter_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicMeter* meter = (PCPDynamicMeter*) value; + for (size_t i = 0; i < meter->totalMetrics; i++) { + free(meter->metrics[i].name); + free(meter->metrics[i].label); + free(meter->metrics[i].suffix); + } + free(meter->metrics); + free(meter->super.caption); + free(meter->super.description); +} + +void PCPDynamicMeters_done(Hashtable* table) { + Hashtable_foreach(table, PCPDynamicMeter_free, NULL); +} + +void PCPDynamicMeter_enable(PCPDynamicMeter* this) { + for (size_t i = 0; i < this->totalMetrics; i++) + PCPMetric_enable(this->metrics[i].id, true); +} + +void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { + char* buffer = meter->txtBuffer; + size_t size = sizeof(meter->txtBuffer); + size_t bytes = 0; + + for (size_t i = 0; i < this->totalMetrics; i++) { + if (i > 0 && bytes < size - 1) + buffer[bytes++] = '/'; /* separator */ + + PCPDynamicMetric* metric = &this->metrics[i]; + const pmDesc* desc = PCPMetric_desc(metric->id); + pmAtomValue atom, raw; + + if (!PCPMetric_values(metric->id, &raw, 1, desc->type)) { + bytes--; /* clear the separator */ + continue; + } + + pmUnits conv = desc->units; /* convert to canonical units */ + if (conv.dimSpace) + conv.scaleSpace = PM_SPACE_KBYTE; + if (conv.dimTime) + conv.scaleTime = PM_TIME_SEC; + if (desc->type == PM_TYPE_STRING) + atom = raw; + else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0) { + bytes--; /* clear the separator */ + continue; + } + + size_t saved = bytes; + switch (desc->type) { + case PM_TYPE_STRING: + bytes += xSnprintf(buffer + bytes, size - bytes, "%s", atom.cp); + free(atom.cp); + break; + case PM_TYPE_32: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, atom.l, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%d", atom.l); + break; + case PM_TYPE_U32: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, atom.ul, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%u", atom.ul); + break; + case PM_TYPE_64: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, atom.ll, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%lld", (long long) atom.ll); + break; + case PM_TYPE_U64: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, atom.ull, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%llu", (unsigned long long) atom.ull); + break; + case PM_TYPE_FLOAT: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, atom.f, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%.2f", (double) atom.f); + break; + case PM_TYPE_DOUBLE: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, atom.d, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%.2f", atom.d); + break; + default: + break; + } + if (saved != bytes && metric->suffix) + bytes += xSnprintf(buffer + bytes, size - bytes, "%s", metric->suffix); + } + if (!bytes) + xSnprintf(buffer, size, "no data"); +} + +void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* meter, RichString* out) { + int nodata = 1; + + for (size_t i = 0; i < this->totalMetrics; i++) { + PCPDynamicMetric* metric = &this->metrics[i]; + const pmDesc* desc = PCPMetric_desc(metric->id); + pmAtomValue atom, raw; + char buffer[64]; + + if (!PCPMetric_values(metric->id, &raw, 1, desc->type)) + continue; + + pmUnits conv = desc->units; /* convert to canonical units */ + if (conv.dimSpace) + conv.scaleSpace = PM_SPACE_KBYTE; + if (conv.dimTime) + conv.scaleTime = PM_TIME_SEC; + if (desc->type == PM_TYPE_STRING) + atom = raw; + else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0) + continue; + + nodata = 0; /* we will use this metric so *some* data will be added */ + + if (i > 0) + RichString_appendnAscii(out, CRT_colors[metric->color], " ", 1); + + if (metric->label) + RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->label); + + int len = 0; + switch (desc->type) { + case PM_TYPE_STRING: + len = xSnprintf(buffer, sizeof(buffer), "%s", atom.cp); + free(atom.cp); + break; + case PM_TYPE_32: + len = conv.dimSpace ? + Meter_humanUnit(buffer, atom.l, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%d", atom.l); + break; + case PM_TYPE_U32: + len = conv.dimSpace ? + Meter_humanUnit(buffer, atom.ul, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%u", atom.ul); + break; + case PM_TYPE_64: + len = conv.dimSpace ? + Meter_humanUnit(buffer, atom.ll, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%lld", (long long) atom.ll); + break; + case PM_TYPE_U64: + len = conv.dimSpace ? + Meter_humanUnit(buffer, atom.ull, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%llu", (unsigned long long) atom.ull); + break; + case PM_TYPE_FLOAT: + len = conv.dimSpace ? + Meter_humanUnit(buffer, atom.f, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%.2f", (double) atom.f); + break; + case PM_TYPE_DOUBLE: + len = conv.dimSpace ? + Meter_humanUnit(buffer, atom.d, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%.2f", atom.d); + break; + default: + break; + } + if (len) { + RichString_appendnAscii(out, CRT_colors[metric->color], buffer, len); + if (metric->suffix) + RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->suffix); + } + } + if (nodata) + RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); +} diff --git a/pcp/PCPDynamicMeter.h b/pcp/PCPDynamicMeter.h new file mode 100644 index 0000000..0e5ddd2 --- /dev/null +++ b/pcp/PCPDynamicMeter.h @@ -0,0 +1,44 @@ +#ifndef HEADER_PCPDynamicMeter +#define HEADER_PCPDynamicMeter + +#include <stddef.h> + +#include "CRT.h" +#include "DynamicMeter.h" +#include "Hashtable.h" +#include "Meter.h" +#include "RichString.h" + + +typedef struct PCPDynamicMetric_ { + size_t id; /* index into metric array */ + ColorElements color; + char* name; /* derived metric name */ + char* label; + char* suffix; +} PCPDynamicMetric; + +typedef struct PCPDynamicMeter_ { + DynamicMeter super; + PCPDynamicMetric* metrics; + size_t totalMetrics; +} PCPDynamicMeter; + +typedef struct PCPDynamicMeters_ { + Hashtable* table; + size_t count; /* count of dynamic meters discovered by scan */ + size_t offset; /* start offset into the Platform metric array */ + size_t cursor; /* identifier allocator for each new metric used */ +} PCPDynamicMeters; + +void PCPDynamicMeters_init(PCPDynamicMeters* meters); + +void PCPDynamicMeters_done(Hashtable* table); + +void PCPDynamicMeter_enable(PCPDynamicMeter* this); + +void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter); + +void PCPDynamicMeter_display(PCPDynamicMeter* this, const Meter* meter, RichString* out); + +#endif diff --git a/pcp/PCPMetric.c b/pcp/PCPMetric.c new file mode 100644 index 0000000..606a5df --- /dev/null +++ b/pcp/PCPMetric.c @@ -0,0 +1,180 @@ +/* +htop - PCPMetric.c +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPMetric.h" + +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "XUtils.h" + +#include "pcp/Platform.h" + + +extern Platform* pcp; + +const pmDesc* PCPMetric_desc(PCPMetric metric) { + return &pcp->descs[metric]; +} + +int PCPMetric_type(PCPMetric metric) { + return pcp->descs[metric].type; +} + +pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type) { + if (pcp->result == NULL) + return NULL; + + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return NULL; + + /* extract requested number of values as requested type */ + const pmDesc* desc = &pcp->descs[metric]; + for (int i = 0; i < vset->numval; i++) { + if (i == count) + break; + const pmValue* value = &vset->vlist[i]; + int sts = pmExtractValue(vset->valfmt, value, desc->type, &atom[i], type); + if (sts < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot extract metric value: %s\n", + pmErrStr(sts)); + memset(&atom[i], 0, sizeof(pmAtomValue)); + } + } + return atom; +} + +int PCPMetric_instanceCount(PCPMetric metric) { + pmValueSet* vset = pcp->result->vset[metric]; + if (vset) + return vset->numval; + return 0; +} + +int PCPMetric_instanceOffset(PCPMetric metric, int inst) { + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return 0; + + /* search for optimal offset for subsequent inst lookups to begin */ + for (int i = 0; i < vset->numval; i++) { + if (inst == vset->vlist[i].inst) + return i; + } + return 0; +} + +static pmAtomValue* PCPMetric_extract(PCPMetric metric, int inst, int offset, pmValueSet* vset, pmAtomValue* atom, int type) { + + /* extract value (using requested type) of given metric instance */ + const pmDesc* desc = &pcp->descs[metric]; + const pmValue* value = &vset->vlist[offset]; + int sts = pmExtractValue(vset->valfmt, value, desc->type, atom, type); + if (sts < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot extract %s instance %d value: %s\n", + pcp->names[metric], inst, pmErrStr(sts)); + memset(atom, 0, sizeof(pmAtomValue)); + } + return atom; +} + +pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type) { + + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return NULL; + + /* fast-path using heuristic offset based on expected location */ + if (offset >= 0 && offset < vset->numval && inst == vset->vlist[offset].inst) + return PCPMetric_extract(metric, inst, offset, vset, atom, type); + + /* slow-path using a linear search for the requested instance */ + for (int i = 0; i < vset->numval; i++) { + if (inst == vset->vlist[i].inst) + return PCPMetric_extract(metric, inst, i, vset, atom, type); + } + return NULL; +} + +/* + * Iterate over a set of instances (incl PM_IN_NULL) + * returning the next instance identifier and offset. + * + * Start it off by passing offset -1 into the routine. + */ +bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp) { + if (!pcp->result) + return false; + + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return false; + + int offset = *offsetp; + offset = (offset < 0) ? 0 : offset + 1; + if (offset > vset->numval - 1) + return false; + + *offsetp = offset; + *instp = vset->vlist[offset].inst; + return true; +} + +/* Switch on/off a metric for value fetching (sampling) */ +void PCPMetric_enable(PCPMetric metric, bool enable) { + pcp->fetch[metric] = enable ? pcp->pmids[metric] : PM_ID_NULL; +} + +bool PCPMetric_enabled(PCPMetric metric) { + return pcp->fetch[metric] != PM_ID_NULL; +} + +void PCPMetric_enableThreads(void) { + pmValueSet* vset = xCalloc(1, sizeof(pmValueSet)); + vset->vlist[0].inst = PM_IN_NULL; + vset->vlist[0].value.lval = 1; + vset->valfmt = PM_VAL_INSITU; + vset->numval = 1; + vset->pmid = pcp->pmids[PCP_CONTROL_THREADS]; + + pmResult* result = xCalloc(1, sizeof(pmResult)); + result->vset[0] = vset; + result->numpmid = 1; + + int sts = pmStore(result); + if (sts < 0 && pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot enable threads: %s\n", pmErrStr(sts)); + + pmFreeResult(result); +} + +bool PCPMetric_fetch(struct timeval* timestamp) { + if (pcp->result) { + pmFreeResult(pcp->result); + pcp->result = NULL; + } + int sts, count = 0; + do { + sts = pmFetch(pcp->totalMetrics, pcp->fetch, &pcp->result); + } while (sts == PM_ERR_IPC && ++count < 3); + if (sts < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot fetch metric values: %s\n", + pmErrStr(sts)); + return false; + } + if (timestamp) + *timestamp = pcp->result->timestamp; + return true; +} diff --git a/pcp/PCPMetric.h b/pcp/PCPMetric.h new file mode 100644 index 0000000..84ccbb9 --- /dev/null +++ b/pcp/PCPMetric.h @@ -0,0 +1,180 @@ +#ifndef HEADER_PCPMetric +#define HEADER_PCPMetric +/* +htop - PCPMetric.h +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <ctype.h> +#include <stdbool.h> +#include <pcp/pmapi.h> +#include <sys/time.h> + +/* use htop config.h values for these macros, not pcp values */ +#undef PACKAGE_URL +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_BUGREPORT + + +typedef enum PCPMetric_ { + PCP_CONTROL_THREADS, /* proc.control.perclient.threads */ + + PCP_HINV_NCPU, /* hinv.ncpu */ + PCP_HINV_CPUCLOCK, /* hinv.cpu.clock */ + PCP_UNAME_SYSNAME, /* kernel.uname.sysname */ + PCP_UNAME_RELEASE, /* kernel.uname.release */ + PCP_UNAME_MACHINE, /* kernel.uname.machine */ + PCP_UNAME_DISTRO, /* kernel.uname.distro */ + PCP_LOAD_AVERAGE, /* kernel.all.load */ + PCP_PID_MAX, /* kernel.all.pid_max */ + PCP_UPTIME, /* kernel.all.uptime */ + PCP_BOOTTIME, /* kernel.all.boottime */ + PCP_CPU_USER, /* kernel.all.cpu.user */ + PCP_CPU_NICE, /* kernel.all.cpu.nice */ + PCP_CPU_SYSTEM, /* kernel.all.cpu.sys */ + PCP_CPU_IDLE, /* kernel.all.cpu.idle */ + PCP_CPU_IOWAIT, /* kernel.all.cpu.wait.total */ + PCP_CPU_IRQ, /* kernel.all.cpu.intr */ + PCP_CPU_SOFTIRQ, /* kernel.all.cpu.irq.soft */ + PCP_CPU_STEAL, /* kernel.all.cpu.steal */ + PCP_CPU_GUEST, /* kernel.all.cpu.guest */ + PCP_CPU_GUESTNICE, /* kernel.all.cpu.guest_nice */ + PCP_PERCPU_USER, /* kernel.percpu.cpu.user */ + PCP_PERCPU_NICE, /* kernel.percpu.cpu.nice */ + PCP_PERCPU_SYSTEM, /* kernel.percpu.cpu.sys */ + PCP_PERCPU_IDLE, /* kernel.percpu.cpu.idle */ + PCP_PERCPU_IOWAIT, /* kernel.percpu.cpu.wait.total */ + PCP_PERCPU_IRQ, /* kernel.percpu.cpu.intr */ + PCP_PERCPU_SOFTIRQ, /* kernel.percpu.cpu.irq.soft */ + PCP_PERCPU_STEAL, /* kernel.percpu.cpu.steal */ + PCP_PERCPU_GUEST, /* kernel.percpu.cpu.guest */ + PCP_PERCPU_GUESTNICE, /* kernel.percpu.cpu.guest_nice */ + PCP_MEM_TOTAL, /* mem.physmem */ + PCP_MEM_FREE, /* mem.util.free */ + PCP_MEM_BUFFERS, /* mem.util.bufmem */ + PCP_MEM_CACHED, /* mem.util.cached */ + PCP_MEM_SHARED, /* mem.util.shared */ + PCP_MEM_AVAILABLE, /* mem.util.available */ + PCP_MEM_SRECLAIM, /* mem.util.slabReclaimable */ + PCP_MEM_SWAPCACHED, /* mem.util.swapCached */ + PCP_MEM_SWAPTOTAL, /* mem.util.swapTotal */ + PCP_MEM_SWAPFREE, /* mem.util.swapFree */ + PCP_DISK_READB, /* disk.all.read_bytes */ + PCP_DISK_WRITEB, /* disk.all.write_bytes */ + PCP_DISK_ACTIVE, /* disk.all.avactive */ + PCP_NET_RECVB, /* network.all.in.bytes */ + PCP_NET_SENDB, /* network.all.out.bytes */ + PCP_NET_RECVP, /* network.all.in.packets */ + PCP_NET_SENDP, /* network.all.out.packets */ + PCP_PSI_CPUSOME, /* kernel.all.pressure.cpu.some.avg */ + PCP_PSI_IOSOME, /* kernel.all.pressure.io.some.avg */ + PCP_PSI_IOFULL, /* kernel.all.pressure.io.full.avg */ + PCP_PSI_MEMSOME, /* kernel.all.pressure.memory.some.avg */ + PCP_PSI_MEMFULL, /* kernel.all.pressure.memory.full.avg */ + PCP_ZFS_ARC_ANON_SIZE, /* zfs.arc.anon_size */ + PCP_ZFS_ARC_BONUS_SIZE, /* zfs.arc.bonus_size */ + PCP_ZFS_ARC_COMPRESSED_SIZE, /* zfs.arc.compressed_size */ + PCP_ZFS_ARC_UNCOMPRESSED_SIZE, /* zfs.arc.uncompressed_size */ + PCP_ZFS_ARC_C_MIN, /* zfs.arc.c_min */ + PCP_ZFS_ARC_C_MAX, /* zfs.arc.c_max */ + PCP_ZFS_ARC_DBUF_SIZE, /* zfs.arc.dbuf_size */ + PCP_ZFS_ARC_DNODE_SIZE, /* zfs.arc.dnode_size */ + PCP_ZFS_ARC_HDR_SIZE, /* zfs.arc.hdr_size */ + PCP_ZFS_ARC_MFU_SIZE, /* zfs.arc.mfu_size */ + PCP_ZFS_ARC_MRU_SIZE, /* zfs.arc.mru_size */ + PCP_ZFS_ARC_SIZE, /* zfs.arc.size */ + PCP_ZRAM_CAPACITY, /* zram.capacity */ + PCP_ZRAM_ORIGINAL, /* zram.mm_stat.data_size.original */ + PCP_ZRAM_COMPRESSED, /* zram.mm_stat.data_size.compressed */ + + PCP_PROC_PID, /* proc.psinfo.pid */ + PCP_PROC_PPID, /* proc.psinfo.ppid */ + PCP_PROC_TGID, /* proc.psinfo.tgid */ + PCP_PROC_PGRP, /* proc.psinfo.pgrp */ + PCP_PROC_SESSION, /* proc.psinfo.session */ + PCP_PROC_STATE, /* proc.psinfo.sname */ + PCP_PROC_TTY, /* proc.psinfo.tty */ + PCP_PROC_TTYPGRP, /* proc.psinfo.tty_pgrp */ + PCP_PROC_MINFLT, /* proc.psinfo.minflt */ + PCP_PROC_MAJFLT, /* proc.psinfo.maj_flt */ + PCP_PROC_CMINFLT, /* proc.psinfo.cmin_flt */ + PCP_PROC_CMAJFLT, /* proc.psinfo.cmaj_flt */ + PCP_PROC_UTIME, /* proc.psinfo.utime */ + PCP_PROC_STIME, /* proc.psinfo.stime */ + PCP_PROC_CUTIME, /* proc.psinfo.cutime */ + PCP_PROC_CSTIME, /* proc.psinfo.cstime */ + PCP_PROC_PRIORITY, /* proc.psinfo.priority */ + PCP_PROC_NICE, /* proc.psinfo.nice */ + PCP_PROC_THREADS, /* proc.psinfo.threads */ + PCP_PROC_STARTTIME, /* proc.psinfo.start_time */ + PCP_PROC_PROCESSOR, /* proc.psinfo.processor */ + PCP_PROC_CMD, /* proc.psinfo.cmd */ + PCP_PROC_PSARGS, /* proc.psinfo.psargs */ + PCP_PROC_CGROUPS, /* proc.psinfo.cgroups */ + PCP_PROC_OOMSCORE, /* proc.psinfo.oom_score */ + PCP_PROC_VCTXSW, /* proc.psinfo.vctxsw */ + PCP_PROC_NVCTXSW, /* proc.psinfo.nvctxsw */ + PCP_PROC_LABELS, /* proc.psinfo.labels */ + PCP_PROC_ENVIRON, /* proc.psinfo.environ */ + PCP_PROC_TTYNAME, /* proc.psinfo.ttyname */ + PCP_PROC_EXE, /* proc.psinfo.exe */ + PCP_PROC_CWD, /* proc.psinfo.cwd */ + + PCP_PROC_AUTOGROUP_ID, /* proc.autogroup.id */ + PCP_PROC_AUTOGROUP_NICE, /* proc.autogroup.nice */ + + PCP_PROC_ID_UID, /* proc.id.uid */ + PCP_PROC_ID_USER, /* proc.id.uid_nm */ + + PCP_PROC_IO_RCHAR, /* proc.io.rchar */ + PCP_PROC_IO_WCHAR, /* proc.io.wchar */ + PCP_PROC_IO_SYSCR, /* proc.io.syscr */ + PCP_PROC_IO_SYSCW, /* proc.io.syscw */ + PCP_PROC_IO_READB, /* proc.io.read_bytes */ + PCP_PROC_IO_WRITEB, /* proc.io.write_bytes */ + PCP_PROC_IO_CANCELLED, /* proc.io.cancelled_write_bytes */ + + PCP_PROC_MEM_SIZE, /* proc.memory.size */ + PCP_PROC_MEM_RSS, /* proc.memory.rss */ + PCP_PROC_MEM_SHARE, /* proc.memory.share */ + PCP_PROC_MEM_TEXTRS, /* proc.memory.textrss */ + PCP_PROC_MEM_LIBRS, /* proc.memory.librss */ + PCP_PROC_MEM_DATRS, /* proc.memory.datrss */ + PCP_PROC_MEM_DIRTY, /* proc.memory.dirty */ + + PCP_PROC_SMAPS_PSS, /* proc.smaps.pss */ + PCP_PROC_SMAPS_SWAP, /* proc.smaps.swap */ + PCP_PROC_SMAPS_SWAPPSS, /* proc.smaps.swappss */ + + PCP_METRIC_COUNT /* total metric count */ +} PCPMetric; + +void PCPMetric_enable(PCPMetric metric, bool enable); + +bool PCPMetric_enabled(PCPMetric metric); + +void PCPMetric_enableThreads(void); + +bool PCPMetric_fetch(struct timeval* timestamp); + +bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp); + +pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type); + +const pmDesc* PCPMetric_desc(PCPMetric metric); + +int PCPMetric_type(PCPMetric metric); + +int PCPMetric_instanceCount(PCPMetric metric); + +int PCPMetric_instanceOffset(PCPMetric metric, int inst); + +pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type); + +#endif diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c new file mode 100644 index 0000000..b8b87ca --- /dev/null +++ b/pcp/PCPProcess.c @@ -0,0 +1,291 @@ +/* +htop - PCPProcess.c +(C) 2014 Hisham H. Muhammad +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "pcp/PCPProcess.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> + +#include "CRT.h" +#include "Macros.h" +#include "Process.h" +#include "ProvideCurses.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/PCPDynamicColumn.h" + + +const ProcessFieldData Process_fields[] = { + [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, }, + [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, }, + [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, }, + [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging, I idle)", .flags = 0, }, + [PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, }, + [PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, }, + [SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .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, }, + [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 = "If of the CPU the process last executed on", .flags = 0, }, + [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, + [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, + [M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, .defaultSortDesc = true, }, + [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, .defaultSortDesc = true, }, + [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, }, + [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, }, + [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, .defaultSortDesc = true, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, + [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, }, + [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, }, + [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, .defaultSortDesc = true, }, + [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = 0, .defaultSortDesc = true, }, + [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = " IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, }, + [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWAPD% ", .description = "Swapin delay %", .flags = 0, .defaultSortDesc = true, }, + [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", .flags = 0, }, + [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, }, + [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, + [AUTOGROUP_ID] = { .name = "AUTOGROUP_ID", .title = "AGRP", .description = "The autogroup identifier of the process", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, + [AUTOGROUP_NICE] = { .name = "AUTOGROUP_NICE", .title = " ANI", .description = "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, +}; + +Process* PCPProcess_new(const Settings* settings) { + PCPProcess* this = xCalloc(1, sizeof(PCPProcess)); + Object_setClass(this, Class(PCPProcess)); + Process_init(&this->super, settings); + return &this->super; +} + +void Process_delete(Object* cast) { + PCPProcess* this = (PCPProcess*) cast; + Process_done((Process*)cast); + free(this->cgroup); + free(this->secattr); + free(this); +} + +static void PCPProcess_printDelay(float delay_percent, char* buffer, int n) { + if (isnan(delay_percent)) { + xSnprintf(buffer, n, " N/A "); + } else { + xSnprintf(buffer, n, "%4.1f ", delay_percent); + } +} + +static void PCPProcess_writeField(const Process* this, RichString* str, ProcessField field) { + const PCPProcess* pp = (const PCPProcess*) this; + bool coloring = this->settings->highlightMegabytes; + char buffer[256]; buffer[255] = '\0'; + int attr = CRT_colors[DEFAULT_COLOR]; + int n = sizeof(buffer) - 1; + switch ((int)field) { + case CMINFLT: Process_printCount(str, pp->cminflt, coloring); return; + case CMAJFLT: Process_printCount(str, pp->cmajflt, coloring); return; + case M_DRS: Process_printBytes(str, pp->m_drs, coloring); return; + case M_DT: Process_printBytes(str, pp->m_dt, coloring); return; + case M_LRS: Process_printBytes(str, pp->m_lrs, coloring); return; + case M_TRS: Process_printBytes(str, pp->m_trs, coloring); return; + case M_SHARE: Process_printBytes(str, pp->m_share, coloring); return; + case M_PSS: Process_printKBytes(str, pp->m_pss, coloring); return; + case M_SWAP: Process_printKBytes(str, pp->m_swap, coloring); return; + case M_PSSWP: Process_printKBytes(str, pp->m_psswp, coloring); return; + case UTIME: Process_printTime(str, pp->utime, coloring); return; + case STIME: Process_printTime(str, pp->stime, coloring); return; + case CUTIME: Process_printTime(str, pp->cutime, coloring); return; + case CSTIME: Process_printTime(str, pp->cstime, coloring); return; + case RCHAR: Process_printBytes(str, pp->io_rchar, coloring); return; + case WCHAR: Process_printBytes(str, pp->io_wchar, coloring); return; + case SYSCR: Process_printCount(str, pp->io_syscr, coloring); return; + case SYSCW: Process_printCount(str, pp->io_syscw, coloring); return; + case RBYTES: Process_printBytes(str, pp->io_read_bytes, coloring); return; + case WBYTES: Process_printBytes(str, pp->io_write_bytes, coloring); return; + case CNCLWB: Process_printBytes(str, pp->io_cancelled_write_bytes, coloring); return; + case IO_READ_RATE: Process_printRate(str, pp->io_rate_read_bps, coloring); return; + case IO_WRITE_RATE: Process_printRate(str, pp->io_rate_write_bps, coloring); return; + case IO_RATE: { + double totalRate = NAN; + if (!isnan(pp->io_rate_read_bps) && !isnan(pp->io_rate_write_bps)) + totalRate = pp->io_rate_read_bps + pp->io_rate_write_bps; + else if (!isnan(pp->io_rate_read_bps)) + totalRate = pp->io_rate_read_bps; + else if (!isnan(pp->io_rate_write_bps)) + totalRate = pp->io_rate_write_bps; + else + totalRate = NAN; + Process_printRate(str, totalRate, coloring); + return; + } + case CGROUP: xSnprintf(buffer, n, "%-10s ", pp->cgroup ? pp->cgroup : ""); break; + case OOM: xSnprintf(buffer, n, "%4u ", pp->oom); break; + case PERCENT_CPU_DELAY: + PCPProcess_printDelay(pp->cpu_delay_percent, buffer, n); + break; + case PERCENT_IO_DELAY: + PCPProcess_printDelay(pp->blkio_delay_percent, buffer, n); + break; + case PERCENT_SWAP_DELAY: + PCPProcess_printDelay(pp->swapin_delay_percent, buffer, n); + break; + case CTXT: + if (pp->ctxt_diff > 1000) { + attr |= A_BOLD; + } + xSnprintf(buffer, n, "%5lu ", pp->ctxt_diff); + break; + case SECATTR: snprintf(buffer, n, "%-30s ", pp->secattr ? pp->secattr : "?"); break; + case AUTOGROUP_ID: + if (pp->autogroup_id != -1) { + xSnprintf(buffer, n, "%4ld ", pp->autogroup_id); + } else { + attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, " N/A "); + } + break; + case AUTOGROUP_NICE: + if (pp->autogroup_id != -1) { + xSnprintf(buffer, n, "%3d ", pp->autogroup_nice); + attr = pp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY] + : pp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] + : CRT_colors[PROCESS_SHADOW]; + } else { + attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, "N/A "); + } + break; + default: + Process_writeField(this, str, field); + return; + } + RichString_appendWide(str, attr, buffer); +} + +static double adjustNaN(double num) { + if (isnan(num)) + return -0.0005; + + return num; +} + +static int PCPProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) { + const PCPProcess* p1 = (const PCPProcess*)v1; + const PCPProcess* p2 = (const PCPProcess*)v2; + + switch (key) { + case M_DRS: + return SPACESHIP_NUMBER(p1->m_drs, p2->m_drs); + case M_DT: + return SPACESHIP_NUMBER(p1->m_dt, p2->m_dt); + case M_LRS: + return SPACESHIP_NUMBER(p1->m_lrs, p2->m_lrs); + case M_TRS: + return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs); + case M_SHARE: + return SPACESHIP_NUMBER(p1->m_share, p2->m_share); + case M_PSS: + return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss); + case M_SWAP: + return SPACESHIP_NUMBER(p1->m_swap, p2->m_swap); + case M_PSSWP: + return SPACESHIP_NUMBER(p1->m_psswp, p2->m_psswp); + case UTIME: + return SPACESHIP_NUMBER(p1->utime, p2->utime); + case CUTIME: + return SPACESHIP_NUMBER(p1->cutime, p2->cutime); + case STIME: + return SPACESHIP_NUMBER(p1->stime, p2->stime); + case CSTIME: + return SPACESHIP_NUMBER(p1->cstime, p2->cstime); + case RCHAR: + return SPACESHIP_NUMBER(p1->io_rchar, p2->io_rchar); + case WCHAR: + return SPACESHIP_NUMBER(p1->io_wchar, p2->io_wchar); + case SYSCR: + return SPACESHIP_NUMBER(p1->io_syscr, p2->io_syscr); + case SYSCW: + return SPACESHIP_NUMBER(p1->io_syscw, p2->io_syscw); + case RBYTES: + return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes); + case WBYTES: + return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes); + case CNCLWB: + return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes); + case IO_READ_RATE: + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps), adjustNaN(p2->io_rate_read_bps)); + case IO_WRITE_RATE: + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_write_bps)); + case IO_RATE: + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps) + adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_read_bps) + adjustNaN(p2->io_rate_write_bps)); + case CGROUP: + return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup); + case OOM: + return SPACESHIP_NUMBER(p1->oom, p2->oom); + case PERCENT_CPU_DELAY: + return SPACESHIP_NUMBER(p1->cpu_delay_percent, p2->cpu_delay_percent); + case PERCENT_IO_DELAY: + return SPACESHIP_NUMBER(p1->blkio_delay_percent, p2->blkio_delay_percent); + case PERCENT_SWAP_DELAY: + return SPACESHIP_NUMBER(p1->swapin_delay_percent, p2->swapin_delay_percent); + case CTXT: + return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff); + case SECATTR: + return SPACESHIP_NULLSTR(p1->secattr, p2->secattr); + case AUTOGROUP_ID: + return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id); + case AUTOGROUP_NICE: + return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice); + default: + if (key < LAST_PROCESSFIELD) + return Process_compareByKey_Base(v1, v2, key); + return PCPDynamicColumn_compareByKey(p1, p2, key); + } +} + +const ProcessClass PCPProcess_class = { + .super = { + .extends = Class(Process), + .display = Process_display, + .delete = Process_delete, + .compare = Process_compare + }, + .writeField = PCPProcess_writeField, + .compareByKey = PCPProcess_compareByKey +}; diff --git a/pcp/PCPProcess.h b/pcp/PCPProcess.h new file mode 100644 index 0000000..46ba07f --- /dev/null +++ b/pcp/PCPProcess.h @@ -0,0 +1,102 @@ +#ifndef HEADER_PCPProcess +#define HEADER_PCPProcess +/* +htop - PCPProcess.h +(C) 2014 Hisham H. Muhammad +(C) 2020 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include <stdbool.h> + +#include "Object.h" +#include "Process.h" +#include "Settings.h" + + +#define PROCESS_FLAG_LINUX_CGROUP 0x00000800 +#define PROCESS_FLAG_LINUX_OOM 0x00001000 +#define PROCESS_FLAG_LINUX_SMAPS 0x00002000 +#define PROCESS_FLAG_LINUX_CTXT 0x00004000 +#define PROCESS_FLAG_LINUX_SECATTR 0x00008000 +#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000 + +typedef struct PCPProcess_ { + Process super; + + /* default result offset to use for searching proc metrics */ + unsigned int offset; + + unsigned long int cminflt; + unsigned long int cmajflt; + unsigned long long int utime; + unsigned long long int stime; + unsigned long long int cutime; + unsigned long long int cstime; + long m_share; + long m_pss; + long m_swap; + long m_psswp; + long m_trs; + long m_drs; + long m_lrs; + long m_dt; + + /* Data read (in kilobytes) */ + unsigned long long io_rchar; + + /* Data written (in kilobytes) */ + 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 kilobytes) */ + unsigned long long io_read_bytes; + + /* Storage data written (in kilobytes) */ + unsigned long long io_write_bytes; + + /* Storage data cancelled (in kilobytes) */ + unsigned long long io_cancelled_write_bytes; + + /* Point in time of last io scan (in seconds elapsed since the Epoch) */ + unsigned long long io_last_scan_time; + + double io_rate_read_bps; + double io_rate_write_bps; + char* cgroup; + long int autogroup_id; + int autogroup_nice; + unsigned int oom; + unsigned long long int delay_read_time; + unsigned long long cpu_delay_total; + unsigned long long blkio_delay_total; + unsigned long long swapin_delay_total; + float cpu_delay_percent; + float blkio_delay_percent; + float swapin_delay_percent; + unsigned long ctxt_total; + unsigned long ctxt_diff; + char* secattr; + unsigned long long int last_mlrs_calctime; +} PCPProcess; + +extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; + +extern const ProcessClass PCPProcess_class; + +Process* PCPProcess_new(const Settings* settings); + +void Process_delete(Object* cast); + +bool Process_isThread(const Process* this); + +#endif diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c new file mode 100644 index 0000000..f893689 --- /dev/null +++ b/pcp/PCPProcessList.c @@ -0,0 +1,727 @@ +/* +htop - PCPProcessList.c +(C) 2014 Hisham H. Muhammad +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPProcessList.h" + +#include <assert.h> +#include <limits.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#include "Macros.h" +#include "Object.h" +#include "Platform.h" +#include "Process.h" +#include "Settings.h" +#include "XUtils.h" + +#include "pcp/PCPMetric.h" +#include "pcp/PCPProcess.h" + + +static void PCPProcessList_updateCPUcount(PCPProcessList* this) { + ProcessList* pl = &(this->super); + pl->activeCPUs = PCPMetric_instanceCount(PCP_PERCPU_SYSTEM); + unsigned int cpus = Platform_getMaxCPU(); + if (cpus == pl->existingCPUs) + return; + if (cpus == 0) + cpus = pl->activeCPUs; + if (cpus <= 1) + cpus = pl->activeCPUs = 1; + pl->existingCPUs = cpus; + + free(this->percpu); + free(this->values); + + this->percpu = xCalloc(cpus, sizeof(pmAtomValue *)); + for (unsigned int i = 0; i < cpus; i++) + this->percpu[i] = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue)); + this->values = xCalloc(cpus, sizeof(pmAtomValue)); +} + +static char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) { + char* name = Hashtable_get(this->users, uid); + if (name) + return name; + + pmAtomValue value; + if (PCPMetric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) { + Hashtable_put(this->users, uid, value.cp); + name = value.cp; + } + return name; +} + +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { + PCPProcessList* this = xCalloc(1, sizeof(PCPProcessList)); + ProcessList* super = &(this->super); + + ProcessList_init(super, Class(PCPProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); + + struct timeval timestamp; + gettimeofday(×tamp, NULL); + this->timestamp = pmtimevalToReal(×tamp); + + this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue)); + PCPProcessList_updateCPUcount(this); + + return super; +} + +void ProcessList_delete(ProcessList* pl) { + PCPProcessList* this = (PCPProcessList*) pl; + ProcessList_done(pl); + free(this->values); + for (unsigned int i = 0; i < pl->existingCPUs; i++) + free(this->percpu[i]); + free(this->percpu); + free(this->cpu); + free(this); +} + +static inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) { + pmAtomValue value; + if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_32)) + return value.l; + return fallback; +} + +static inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) { + pmAtomValue value; + if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_64)) + return value.l; + return fallback; +} + +static inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) { + pmAtomValue value; + if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U32)) + return value.ul; + return fallback; +} + +static inline unsigned long long Metric_instance_u64(int metric, int pid, int offset, unsigned long long fallback) { + pmAtomValue value; + if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + return value.ull; + return fallback; +} + +static inline unsigned long long Metric_instance_time(int metric, int pid, int offset) { + pmAtomValue value; + if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + return value.ull / 10; + return 0; +} + +static inline unsigned long long Metric_instance_ONE_K(int metric, int pid, int offset) { + pmAtomValue value; + if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + return value.ull / ONE_K; + return ULLONG_MAX; +} + +static inline char Metric_instance_char(int metric, int pid, int offset, char fallback) { + pmAtomValue value; + if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) { + char uchar = value.cp[0]; + free(value.cp); + return uchar; + } + return fallback; +} + +static inline ProcessState PCPProcessList_getProcessState(char state) { + switch (state) { + case '?': return UNKNOWN; + case 'R': return RUNNING; + case 'W': return WAITING; + case 'D': return UNINTERRUPTIBLE_WAIT; + case 'P': return PAGING; + case 'T': return STOPPED; + case 't': return TRACED; + case 'Z': return ZOMBIE; + case 'X': return DEFUNCT; + case 'I': return IDLE; + case 'S': return SLEEPING; + default: return UNKNOWN; + } +} + +static void PCPProcessList_updateID(Process* process, int pid, int offset) { + process->tgid = Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1); + process->ppid = Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1); + process->state = PCPProcessList_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?')); +} + +static void PCPProcessList_updateInfo(Process* process, int pid, int offset, char* command, size_t commLen) { + PCPProcess* pp = (PCPProcess*) process; + pmAtomValue value; + + if (!PCPMetric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING)) + value.cp = xStrdup("<unknown>"); + String_safeStrncpy(command, value.cp, commLen); + free(value.cp); + + process->pgrp = Metric_instance_u32(PCP_PROC_PGRP, pid, offset, 0); + process->session = Metric_instance_u32(PCP_PROC_SESSION, pid, offset, 0); + process->tty_nr = Metric_instance_u32(PCP_PROC_TTY, pid, offset, 0); + process->tpgid = Metric_instance_u32(PCP_PROC_TTYPGRP, pid, offset, 0); + process->minflt = Metric_instance_u32(PCP_PROC_MINFLT, pid, offset, 0); + pp->cminflt = Metric_instance_u32(PCP_PROC_CMINFLT, pid, offset, 0); + process->majflt = Metric_instance_u32(PCP_PROC_MAJFLT, pid, offset, 0); + pp->cmajflt = Metric_instance_u32(PCP_PROC_CMAJFLT, pid, offset, 0); + pp->utime = Metric_instance_time(PCP_PROC_UTIME, pid, offset); + pp->stime = Metric_instance_time(PCP_PROC_STIME, pid, offset); + pp->cutime = Metric_instance_time(PCP_PROC_CUTIME, pid, offset); + pp->cstime = Metric_instance_time(PCP_PROC_CSTIME, pid, offset); + process->priority = Metric_instance_u32(PCP_PROC_PRIORITY, pid, offset, 0); + process->nice = Metric_instance_s32(PCP_PROC_NICE, pid, offset, 0); + process->nlwp = Metric_instance_u32(PCP_PROC_THREADS, pid, offset, 0); + process->starttime_ctime = Metric_instance_time(PCP_PROC_STARTTIME, pid, offset); + process->processor = Metric_instance_u32(PCP_PROC_PROCESSOR, pid, offset, 0); + + process->time = pp->utime + pp->stime; +} + +static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigned long long now) { + pmAtomValue value; + + pp->io_rchar = Metric_instance_ONE_K(PCP_PROC_IO_RCHAR, pid, offset); + pp->io_wchar = Metric_instance_ONE_K(PCP_PROC_IO_WCHAR, pid, offset); + pp->io_syscr = Metric_instance_u64(PCP_PROC_IO_SYSCR, pid, offset, ULLONG_MAX); + pp->io_syscw = Metric_instance_u64(PCP_PROC_IO_SYSCW, pid, offset, ULLONG_MAX); + pp->io_cancelled_write_bytes = Metric_instance_ONE_K(PCP_PROC_IO_CANCELLED, pid, offset); + + if (PCPMetric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) { + unsigned long long last_read = pp->io_read_bytes; + pp->io_read_bytes = value.ull / ONE_K; + pp->io_rate_read_bps = ONE_K * (pp->io_read_bytes - last_read) / + (now - pp->io_last_scan_time); + } else { + pp->io_read_bytes = ULLONG_MAX; + pp->io_rate_read_bps = NAN; + } + + if (PCPMetric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) { + unsigned long long last_write = pp->io_write_bytes; + pp->io_write_bytes = value.ull; + pp->io_rate_write_bps = ONE_K * (pp->io_write_bytes - last_write) / + (now - pp->io_last_scan_time); + } else { + pp->io_write_bytes = ULLONG_MAX; + pp->io_rate_write_bps = NAN; + } + + pp->io_last_scan_time = now; +} + +static void PCPProcessList_updateMemory(PCPProcess* pp, int pid, int offset) { + pp->super.m_virt = Metric_instance_u32(PCP_PROC_MEM_SIZE, pid, offset, 0); + pp->super.m_resident = Metric_instance_u32(PCP_PROC_MEM_RSS, pid, offset, 0); + pp->m_share = Metric_instance_u32(PCP_PROC_MEM_SHARE, pid, offset, 0); + pp->m_trs = Metric_instance_u32(PCP_PROC_MEM_TEXTRS, pid, offset, 0); + pp->m_lrs = Metric_instance_u32(PCP_PROC_MEM_LIBRS, pid, offset, 0); + pp->m_drs = Metric_instance_u32(PCP_PROC_MEM_DATRS, pid, offset, 0); + pp->m_dt = Metric_instance_u32(PCP_PROC_MEM_DIRTY, pid, offset, 0); +} + +static void PCPProcessList_updateSmaps(PCPProcess* pp, pid_t pid, int offset) { + pp->m_pss = Metric_instance_u64(PCP_PROC_SMAPS_PSS, pid, offset, 0); + pp->m_swap = Metric_instance_u64(PCP_PROC_SMAPS_SWAP, pid, offset, 0); + pp->m_psswp = Metric_instance_u64(PCP_PROC_SMAPS_SWAPPSS, pid, offset, 0); +} + +static void PCPProcessList_readOomData(PCPProcess* pp, int pid, int offset) { + pp->oom = Metric_instance_u32(PCP_PROC_OOMSCORE, pid, offset, 0); +} + +static void PCPProcessList_readAutogroup(PCPProcess* pp, int pid, int offset) { + pp->autogroup_id = Metric_instance_s64(PCP_PROC_AUTOGROUP_ID, pid, offset, -1); + pp->autogroup_nice = Metric_instance_s32(PCP_PROC_AUTOGROUP_NICE, pid, offset, 0); +} + +static void PCPProcessList_readCtxtData(PCPProcess* pp, int pid, int offset) { + pmAtomValue value; + unsigned long ctxt = 0; + + if (PCPMetric_instance(PCP_PROC_VCTXSW, pid, offset, &value, PM_TYPE_U32)) + ctxt += value.ul; + if (PCPMetric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32)) + ctxt += value.ul; + + pp->ctxt_diff = ctxt > pp->ctxt_total ? ctxt - pp->ctxt_total : 0; + pp->ctxt_total = ctxt; +} + +static char* setString(PCPMetric metric, int pid, int offset, char* string) { + if (string) + free(string); + pmAtomValue value; + if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) + string = value.cp; + else + string = NULL; + return string; +} + +static void PCPProcessList_updateTTY(Process* process, int pid, int offset) { + process->tty_name = setString(PCP_PROC_TTYNAME, pid, offset, process->tty_name); +} + +static void PCPProcessList_readCGroups(PCPProcess* pp, int pid, int offset) { + pp->cgroup = setString(PCP_PROC_CGROUPS, pid, offset, pp->cgroup); +} + +static void PCPProcessList_readSecattrData(PCPProcess* pp, int pid, int offset) { + pp->secattr = setString(PCP_PROC_LABELS, pid, offset, pp->secattr); +} + +static void PCPProcessList_readCwd(PCPProcess* pp, int pid, int offset) { + pp->super.procCwd = setString(PCP_PROC_CWD, pid, offset, pp->super.procCwd); +} + +static void PCPProcessList_updateUsername(Process* process, int pid, int offset, UsersTable* users) { + process->st_uid = Metric_instance_u32(PCP_PROC_ID_UID, pid, offset, 0); + process->user = setUser(users, process->st_uid, pid, offset); +} + +static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, const char* comm) { + pmAtomValue value; + if (!PCPMetric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) { + if (process->state != ZOMBIE) + process->isKernelThread = true; + Process_updateCmdline(process, NULL, 0, 0); + return; + } + + char* command = value.cp; + int length = strlen(command); + if (command[0] != '(') { + process->isKernelThread = false; + } else { + ++command; + --length; + if (command[length - 1] == ')') + command[--length] = '\0'; + process->isKernelThread = true; + } + + int tokenStart = 0; + for (int i = 0; i < length; i++) { + /* htop considers the next character after the last / that is before + * basenameOffset, as the start of the basename in cmdline - see + * Process_writeCommand */ + if (command[i] == '/') + tokenStart = i + 1; + } + int tokenEnd = length; + + Process_updateCmdline(process, command, tokenStart, tokenEnd); + free(value.cp); + + Process_updateComm(process, comm); + + if (PCPMetric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) { + Process_updateExe(process, value.cp[0] ? value.cp : NULL); + free(value.cp); + } +} + +static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, struct timeval* tv) { + ProcessList* pl = (ProcessList*) this; + const Settings* settings = pl->settings; + + bool hideKernelThreads = settings->hideKernelThreads; + bool hideUserlandThreads = settings->hideUserlandThreads; + + unsigned long long now = tv->tv_sec * 1000LL + tv->tv_usec / 1000LL; + int pid = -1, offset = -1; + + /* for every process ... */ + while (PCPMetric_iterate(PCP_PROC_PID, &pid, &offset)) { + + bool preExisting; + Process* proc = ProcessList_getProcess(pl, pid, &preExisting, PCPProcess_new); + PCPProcess* pp = (PCPProcess*) proc; + PCPProcessList_updateID(proc, pid, offset); + proc->isUserlandThread = proc->pid != proc->tgid; + pp->offset = offset >= 0 ? offset : 0; + + /* + * These conditions will not trigger on first occurrence, cause we need to + * add the process to the ProcessList and do all one time scans + * (e.g. parsing the cmdline to detect a kernel thread) + * But it will short-circuit subsequent scans. + */ + if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) { + proc->updated = true; + proc->show = false; + if (proc->state == RUNNING) + pl->runningTasks++; + pl->kernelThreads++; + pl->totalTasks++; + continue; + } + if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) { + proc->updated = true; + proc->show = false; + if (proc->state == RUNNING) + pl->runningTasks++; + pl->userlandThreads++; + pl->totalTasks++; + continue; + } + + if (settings->ss->flags & PROCESS_FLAG_IO) + PCPProcessList_updateIO(pp, pid, offset, now); + + PCPProcessList_updateMemory(pp, pid, offset); + + if ((settings->ss->flags & PROCESS_FLAG_LINUX_SMAPS) && + (Process_isKernelThread(proc) == false)) { + if (PCPMetric_enabled(PCP_PROC_SMAPS_PSS)) + PCPProcessList_updateSmaps(pp, pid, offset); + } + + char command[MAX_NAME + 1]; + unsigned int tty_nr = proc->tty_nr; + unsigned long long int lasttimes = pp->utime + pp->stime; + + PCPProcessList_updateInfo(proc, pid, offset, command, sizeof(command)); + proc->starttime_ctime += Platform_getBootTime(); + if (tty_nr != proc->tty_nr) + PCPProcessList_updateTTY(proc, pid, offset); + + float percent_cpu = (pp->utime + pp->stime - lasttimes) / period * 100.0; + proc->percent_cpu = isnan(percent_cpu) ? + 0.0 : CLAMP(percent_cpu, 0.0, pl->activeCPUs * 100.0); + proc->percent_mem = proc->m_resident / (double)pl->totalMem * 100.0; + Process_updateCPUFieldWidths(proc->percent_cpu); + + PCPProcessList_updateUsername(proc, pid, offset, pl->usersTable); + + if (!preExisting) { + PCPProcessList_updateCmdline(proc, pid, offset, command); + Process_fillStarttimeBuffer(proc); + ProcessList_add(pl, proc); + } else if (settings->updateProcessNames && proc->state != ZOMBIE) { + PCPProcessList_updateCmdline(proc, pid, offset, command); + } + + if (settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP) + PCPProcessList_readCGroups(pp, pid, offset); + + if (settings->ss->flags & PROCESS_FLAG_LINUX_OOM) + PCPProcessList_readOomData(pp, pid, offset); + + if (settings->ss->flags & PROCESS_FLAG_LINUX_CTXT) + PCPProcessList_readCtxtData(pp, pid, offset); + + if (settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR) + PCPProcessList_readSecattrData(pp, pid, offset); + + if (settings->ss->flags & PROCESS_FLAG_CWD) + PCPProcessList_readCwd(pp, pid, offset); + + if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) + PCPProcessList_readAutogroup(pp, pid, offset); + + if (proc->state == ZOMBIE && !proc->cmdline && command[0]) { + Process_updateCmdline(proc, command, 0, strlen(command)); + } else if (Process_isThread(proc)) { + if ((settings->showThreadNames || Process_isKernelThread(proc)) && command[0]) { + Process_updateCmdline(proc, command, 0, strlen(command)); + } + + if (Process_isKernelThread(proc)) { + pl->kernelThreads++; + } else { + 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 == RUNNING) + pl->runningTasks++; + proc->updated = true; + } + return true; +} + +static void PCPProcessList_updateMemoryInfo(ProcessList* super) { + unsigned long long int freeMem = 0; + unsigned long long int swapFreeMem = 0; + unsigned long long int sreclaimableMem = 0; + super->totalMem = super->usedMem = super->cachedMem = 0; + super->usedSwap = super->totalSwap = super->sharedMem = 0; + + pmAtomValue value; + if (PCPMetric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL) + super->totalMem = value.ull; + if (PCPMetric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL) + freeMem = value.ull; + if (PCPMetric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL) + super->buffersMem = value.ull; + if (PCPMetric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL) + sreclaimableMem = value.ull; + if (PCPMetric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL) + super->sharedMem = value.ull; + if (PCPMetric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL) + super->cachedMem = value.ull + sreclaimableMem - super->sharedMem; + const memory_t usedDiff = freeMem + super->cachedMem + sreclaimableMem + super->buffersMem; + super->usedMem = (super->totalMem >= usedDiff) ? + super->totalMem - usedDiff : super->totalMem - freeMem; + if (PCPMetric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL) + super->availableMem = MINIMUM(value.ull, super->totalMem); + else + super->availableMem = freeMem; + if (PCPMetric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL) + swapFreeMem = value.ull; + if (PCPMetric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL) + super->totalSwap = value.ull; + if (PCPMetric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL) + super->cachedSwap = value.ull; + super->usedSwap = super->totalSwap - swapFreeMem - super->cachedSwap; +} + +/* make copies of previously sampled values to avoid overwrite */ +static inline void PCPProcessList_backupCPUTime(pmAtomValue* values) { + /* the PERIOD fields (must) mirror the TIME fields */ + for (int metric = CPU_TOTAL_TIME; metric < CPU_TOTAL_PERIOD; metric++) { + values[metric + CPU_TOTAL_PERIOD] = values[metric]; + } +} + +static inline void PCPProcessList_saveCPUTimePeriod(pmAtomValue* values, CPUMetric previous, pmAtomValue* latest) { + pmAtomValue* value; + + /* new value for period */ + value = &values[previous]; + if (latest->ull > value->ull) + value->ull = latest->ull - value->ull; + else + value->ull = 0; + + /* new value for time */ + value = &values[previous - CPU_TOTAL_PERIOD]; + value->ull = latest->ull; +} + +/* using copied sampled values and new values, calculate derivations */ +static void PCPProcessList_deriveCPUTime(pmAtomValue* values) { + + pmAtomValue* usertime = &values[CPU_USER_TIME]; + pmAtomValue* guesttime = &values[CPU_GUEST_TIME]; + usertime->ull -= guesttime->ull; + + pmAtomValue* nicetime = &values[CPU_NICE_TIME]; + pmAtomValue* guestnicetime = &values[CPU_GUESTNICE_TIME]; + nicetime->ull -= guestnicetime->ull; + + pmAtomValue* idletime = &values[CPU_IDLE_TIME]; + pmAtomValue* iowaittime = &values[CPU_IOWAIT_TIME]; + pmAtomValue* idlealltime = &values[CPU_IDLE_ALL_TIME]; + idlealltime->ull = idletime->ull + iowaittime->ull; + + pmAtomValue* systemtime = &values[CPU_SYSTEM_TIME]; + pmAtomValue* irqtime = &values[CPU_IRQ_TIME]; + pmAtomValue* softirqtime = &values[CPU_SOFTIRQ_TIME]; + pmAtomValue* systalltime = &values[CPU_SYSTEM_ALL_TIME]; + systalltime->ull = systemtime->ull + irqtime->ull + softirqtime->ull; + + pmAtomValue* virtalltime = &values[CPU_GUEST_TIME]; + virtalltime->ull = guesttime->ull + guestnicetime->ull; + + pmAtomValue* stealtime = &values[CPU_STEAL_TIME]; + pmAtomValue* totaltime = &values[CPU_TOTAL_TIME]; + totaltime->ull = usertime->ull + nicetime->ull + systalltime->ull + + idlealltime->ull + stealtime->ull + virtalltime->ull; + + PCPProcessList_saveCPUTimePeriod(values, CPU_USER_PERIOD, usertime); + PCPProcessList_saveCPUTimePeriod(values, CPU_NICE_PERIOD, nicetime); + PCPProcessList_saveCPUTimePeriod(values, CPU_SYSTEM_PERIOD, systemtime); + PCPProcessList_saveCPUTimePeriod(values, CPU_SYSTEM_ALL_PERIOD, systalltime); + PCPProcessList_saveCPUTimePeriod(values, CPU_IDLE_ALL_PERIOD, idlealltime); + PCPProcessList_saveCPUTimePeriod(values, CPU_IDLE_PERIOD, idletime); + PCPProcessList_saveCPUTimePeriod(values, CPU_IOWAIT_PERIOD, iowaittime); + PCPProcessList_saveCPUTimePeriod(values, CPU_IRQ_PERIOD, irqtime); + PCPProcessList_saveCPUTimePeriod(values, CPU_SOFTIRQ_PERIOD, softirqtime); + PCPProcessList_saveCPUTimePeriod(values, CPU_STEAL_PERIOD, stealtime); + PCPProcessList_saveCPUTimePeriod(values, CPU_GUEST_PERIOD, virtalltime); + PCPProcessList_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime); +} + +static void PCPProcessList_updateAllCPUTime(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric) +{ + pmAtomValue* value = &this->cpu[cpumetric]; + if (PCPMetric_values(metric, value, 1, PM_TYPE_U64) == NULL) + memset(value, 0, sizeof(pmAtomValue)); +} + +static void PCPProcessList_updatePerCPUTime(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric) +{ + int cpus = this->super.existingCPUs; + if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL) + memset(this->values, 0, cpus * sizeof(pmAtomValue)); + for (int i = 0; i < cpus; i++) + this->percpu[i][cpumetric].ull = this->values[i].ull; +} + +static void PCPProcessList_updatePerCPUReal(PCPProcessList* this, PCPMetric metric, CPUMetric cpumetric) +{ + int cpus = this->super.existingCPUs; + if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL) + memset(this->values, 0, cpus * sizeof(pmAtomValue)); + for (int i = 0; i < cpus; i++) + this->percpu[i][cpumetric].d = this->values[i].d; +} + +static inline void PCPProcessList_scanZfsArcstats(PCPProcessList* this) { + unsigned long long int dbufSize = 0; + unsigned long long int dnodeSize = 0; + unsigned long long int bonusSize = 0; + pmAtomValue value; + + memset(&this->zfs, 0, sizeof(ZfsArcStats)); + if (PCPMetric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.anon = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64)) + this->zfs.min = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64)) + this->zfs.max = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64)) + bonusSize = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_DBUF_SIZE, &value, 1, PM_TYPE_U64)) + dbufSize = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_DNODE_SIZE, &value, 1, PM_TYPE_U64)) + dnodeSize = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_COMPRESSED_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.compressed = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_UNCOMPRESSED_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.uncompressed = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_HDR_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.header = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_MFU_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.MFU = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_MRU_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.MRU = value.ull / ONE_K; + if (PCPMetric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.size = value.ull / ONE_K; + + this->zfs.other = (dbufSize + dnodeSize + bonusSize) / ONE_K; + this->zfs.enabled = (this->zfs.size > 0); + this->zfs.isCompressed = (this->zfs.compressed > 0); +} + +static void PCPProcessList_updateHeader(ProcessList* super, const Settings* settings) { + PCPProcessList_updateMemoryInfo(super); + + PCPProcessList* this = (PCPProcessList*) super; + PCPProcessList_updateCPUcount(this); + + PCPProcessList_backupCPUTime(this->cpu); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_USER, CPU_USER_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_NICE, CPU_NICE_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_SYSTEM, CPU_SYSTEM_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_IDLE, CPU_IDLE_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_IOWAIT, CPU_IOWAIT_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_IRQ, CPU_IRQ_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_SOFTIRQ, CPU_SOFTIRQ_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_STEAL, CPU_STEAL_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_GUEST, CPU_GUEST_TIME); + PCPProcessList_deriveCPUTime(this->cpu); + + for (unsigned int i = 0; i < super->existingCPUs; i++) + PCPProcessList_backupCPUTime(this->percpu[i]); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_USER, CPU_USER_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_NICE, CPU_NICE_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_SYSTEM, CPU_SYSTEM_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IDLE, CPU_IDLE_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IOWAIT, CPU_IOWAIT_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IRQ, CPU_IRQ_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_SOFTIRQ, CPU_SOFTIRQ_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_STEAL, CPU_STEAL_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_GUEST, CPU_GUEST_TIME); + for (unsigned int i = 0; i < super->existingCPUs; i++) + PCPProcessList_deriveCPUTime(this->percpu[i]); + + if (settings->showCPUFrequency) + PCPProcessList_updatePerCPUReal(this, PCP_HINV_CPUCLOCK, CPU_FREQUENCY); + + PCPProcessList_scanZfsArcstats(this); +} + +void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { + PCPProcessList* this = (PCPProcessList*) super; + const Settings* settings = super->settings; + bool enabled = !pauseProcessUpdate; + + bool flagged = settings->showCPUFrequency; + PCPMetric_enable(PCP_HINV_CPUCLOCK, flagged); + + /* In pause mode do not sample per-process metric values at all */ + for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++) + PCPMetric_enable(metric, enabled); + + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP; + PCPMetric_enable(PCP_PROC_CGROUPS, flagged && enabled); + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_OOM; + PCPMetric_enable(PCP_PROC_OOMSCORE, flagged && enabled); + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CTXT; + PCPMetric_enable(PCP_PROC_VCTXSW, flagged && enabled); + PCPMetric_enable(PCP_PROC_NVCTXSW, flagged && enabled); + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR; + PCPMetric_enable(PCP_PROC_LABELS, flagged && enabled); + flagged = settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP; + PCPMetric_enable(PCP_PROC_AUTOGROUP_ID, flagged && enabled); + PCPMetric_enable(PCP_PROC_AUTOGROUP_NICE, flagged && enabled); + + /* Sample smaps metrics on every second pass to improve performance */ + static int smaps_flag; + smaps_flag = !!smaps_flag; + PCPMetric_enable(PCP_PROC_SMAPS_PSS, smaps_flag && enabled); + PCPMetric_enable(PCP_PROC_SMAPS_SWAP, smaps_flag && enabled); + PCPMetric_enable(PCP_PROC_SMAPS_SWAPPSS, smaps_flag && enabled); + + struct timeval timestamp; + if (PCPMetric_fetch(×tamp) != true) + return; + + double sample = this->timestamp; + this->timestamp = pmtimevalToReal(×tamp); + + PCPProcessList_updateHeader(super, settings); + + /* In pause mode only update global data for meters (CPU, memory, etc) */ + if (pauseProcessUpdate) + return; + + double period = (this->timestamp - sample) * 100; + PCPProcessList_updateProcesses(this, period, ×tamp); +} + +bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) { + assert(id < super->existingCPUs); + (void) super; + + pmAtomValue value; + if (PCPMetric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32)) + return true; + return false; +} diff --git a/pcp/PCPProcessList.h b/pcp/PCPProcessList.h new file mode 100644 index 0000000..a3a7372 --- /dev/null +++ b/pcp/PCPProcessList.h @@ -0,0 +1,74 @@ +#ifndef HEADER_PCPProcessList +#define HEADER_PCPProcessList +/* +htop - PCPProcessList.h +(C) 2014 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include <stdbool.h> +#include <sys/types.h> + +#include "Hashtable.h" +#include "ProcessList.h" +#include "UsersTable.h" + +#include "pcp/Platform.h" +#include "zfs/ZfsArcStats.h" + + +typedef enum CPUMetric_ { + CPU_TOTAL_TIME, + CPU_USER_TIME, + CPU_SYSTEM_TIME, + CPU_SYSTEM_ALL_TIME, + CPU_IDLE_ALL_TIME, + CPU_IDLE_TIME, + CPU_NICE_TIME, + CPU_IOWAIT_TIME, + CPU_IRQ_TIME, + CPU_SOFTIRQ_TIME, + CPU_STEAL_TIME, + CPU_GUEST_TIME, + CPU_GUESTNICE_TIME, + + CPU_TOTAL_PERIOD, + CPU_USER_PERIOD, + CPU_SYSTEM_PERIOD, + CPU_SYSTEM_ALL_PERIOD, + CPU_IDLE_ALL_PERIOD, + CPU_IDLE_PERIOD, + CPU_NICE_PERIOD, + CPU_IOWAIT_PERIOD, + CPU_IRQ_PERIOD, + CPU_SOFTIRQ_PERIOD, + CPU_STEAL_PERIOD, + CPU_GUEST_PERIOD, + CPU_GUESTNICE_PERIOD, + + CPU_FREQUENCY, + + CPU_METRIC_COUNT +} CPUMetric; + +typedef struct PCPProcessList_ { + ProcessList super; + double timestamp; /* previous sample timestamp */ + pmAtomValue* cpu; /* aggregate values for each metric */ + pmAtomValue** percpu; /* per-processor values for each metric */ + pmAtomValue* values; /* per-processor buffer for just one metric */ + ZfsArcStats zfs; +} PCPProcessList; + +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/pcp/Platform.c b/pcp/Platform.c new file mode 100644 index 0000000..994cef3 --- /dev/null +++ b/pcp/Platform.c @@ -0,0 +1,842 @@ +/* +htop - linux/Platform.c +(C) 2014 Hisham H. Muhammad +(C) 2020-2022 htop dev team +(C) 2020-2022 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/Platform.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "BatteryMeter.h" +#include "CPUMeter.h" +#include "ClockMeter.h" +#include "DateMeter.h" +#include "DateTimeMeter.h" +#include "DiskIOMeter.h" +#include "DynamicColumn.h" +#include "DynamicMeter.h" +#include "HostnameMeter.h" +#include "LoadAverageMeter.h" +#include "Macros.h" +#include "MemoryMeter.h" +#include "MemorySwapMeter.h" +#include "Meter.h" +#include "NetworkIOMeter.h" +#include "ProcessList.h" +#include "Settings.h" +#include "SwapMeter.h" +#include "SysArchMeter.h" +#include "TasksMeter.h" +#include "UptimeMeter.h" +#include "XUtils.h" + +#include "linux/PressureStallMeter.h" +#include "linux/ZramMeter.h" +#include "linux/ZramStats.h" +#include "pcp/PCPDynamicColumn.h" +#include "pcp/PCPDynamicMeter.h" +#include "pcp/PCPMetric.h" +#include "pcp/PCPProcessList.h" +#include "zfs/ZfsArcMeter.h" +#include "zfs/ZfsArcStats.h" +#include "zfs/ZfsCompressedArcMeter.h" + + +Platform* pcp; + +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, + { + .name = "I/O", + .columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command", + .sortKey = "IO_RATE", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); + +const SignalItem Platform_signals[] = { + { .name = " 0 Cancel", .number = 0 }, +}; + +const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals); + +const MeterClass* const Platform_meterTypes[] = { + &CPUMeter_class, + &DynamicMeter_class, + &ClockMeter_class, + &DateMeter_class, + &DateTimeMeter_class, + &LoadAverageMeter_class, + &LoadMeter_class, + &MemoryMeter_class, + &SwapMeter_class, + &MemorySwapMeter_class, + &TasksMeter_class, + &UptimeMeter_class, + &BatteryMeter_class, + &HostnameMeter_class, + &AllCPUsMeter_class, + &AllCPUs2Meter_class, + &AllCPUs4Meter_class, + &AllCPUs8Meter_class, + &LeftCPUsMeter_class, + &RightCPUsMeter_class, + &LeftCPUs2Meter_class, + &RightCPUs2Meter_class, + &LeftCPUs4Meter_class, + &RightCPUs4Meter_class, + &LeftCPUs8Meter_class, + &RightCPUs8Meter_class, + &BlankMeter_class, + &PressureStallCPUSomeMeter_class, + &PressureStallIOSomeMeter_class, + &PressureStallIOFullMeter_class, + &PressureStallMemorySomeMeter_class, + &PressureStallMemoryFullMeter_class, + &ZfsArcMeter_class, + &ZfsCompressedArcMeter_class, + &ZramMeter_class, + &DiskIOMeter_class, + &NetworkIOMeter_class, + &SysArchMeter_class, + NULL +}; + +static const char* Platform_metricNames[] = { + [PCP_CONTROL_THREADS] = "proc.control.perclient.threads", + + [PCP_HINV_NCPU] = "hinv.ncpu", + [PCP_HINV_CPUCLOCK] = "hinv.cpu.clock", + [PCP_UNAME_SYSNAME] = "kernel.uname.sysname", + [PCP_UNAME_RELEASE] = "kernel.uname.release", + [PCP_UNAME_MACHINE] = "kernel.uname.machine", + [PCP_UNAME_DISTRO] = "kernel.uname.distro", + [PCP_LOAD_AVERAGE] = "kernel.all.load", + [PCP_PID_MAX] = "kernel.all.pid_max", + [PCP_UPTIME] = "kernel.all.uptime", + [PCP_BOOTTIME] = "kernel.all.boottime", + [PCP_CPU_USER] = "kernel.all.cpu.user", + [PCP_CPU_NICE] = "kernel.all.cpu.nice", + [PCP_CPU_SYSTEM] = "kernel.all.cpu.sys", + [PCP_CPU_IDLE] = "kernel.all.cpu.idle", + [PCP_CPU_IOWAIT] = "kernel.all.cpu.wait.total", + [PCP_CPU_IRQ] = "kernel.all.cpu.intr", + [PCP_CPU_SOFTIRQ] = "kernel.all.cpu.irq.soft", + [PCP_CPU_STEAL] = "kernel.all.cpu.steal", + [PCP_CPU_GUEST] = "kernel.all.cpu.guest", + [PCP_CPU_GUESTNICE] = "kernel.all.cpu.guest_nice", + [PCP_PERCPU_USER] = "kernel.percpu.cpu.user", + [PCP_PERCPU_NICE] = "kernel.percpu.cpu.nice", + [PCP_PERCPU_SYSTEM] = "kernel.percpu.cpu.sys", + [PCP_PERCPU_IDLE] = "kernel.percpu.cpu.idle", + [PCP_PERCPU_IOWAIT] = "kernel.percpu.cpu.wait.total", + [PCP_PERCPU_IRQ] = "kernel.percpu.cpu.intr", + [PCP_PERCPU_SOFTIRQ] = "kernel.percpu.cpu.irq.soft", + [PCP_PERCPU_STEAL] = "kernel.percpu.cpu.steal", + [PCP_PERCPU_GUEST] = "kernel.percpu.cpu.guest", + [PCP_PERCPU_GUESTNICE] = "kernel.percpu.cpu.guest_nice", + [PCP_MEM_TOTAL] = "mem.physmem", + [PCP_MEM_FREE] = "mem.util.free", + [PCP_MEM_AVAILABLE] = "mem.util.available", + [PCP_MEM_BUFFERS] = "mem.util.bufmem", + [PCP_MEM_CACHED] = "mem.util.cached", + [PCP_MEM_SHARED] = "mem.util.shmem", + [PCP_MEM_SRECLAIM] = "mem.util.slabReclaimable", + [PCP_MEM_SWAPCACHED] = "mem.util.swapCached", + [PCP_MEM_SWAPTOTAL] = "mem.util.swapTotal", + [PCP_MEM_SWAPFREE] = "mem.util.swapFree", + [PCP_DISK_READB] = "disk.all.read_bytes", + [PCP_DISK_WRITEB] = "disk.all.write_bytes", + [PCP_DISK_ACTIVE] = "disk.all.avactive", + [PCP_NET_RECVB] = "network.all.in.bytes", + [PCP_NET_SENDB] = "network.all.out.bytes", + [PCP_NET_RECVP] = "network.all.in.packets", + [PCP_NET_SENDP] = "network.all.out.packets", + + [PCP_PSI_CPUSOME] = "kernel.all.pressure.cpu.some.avg", + [PCP_PSI_IOSOME] = "kernel.all.pressure.io.some.avg", + [PCP_PSI_IOFULL] = "kernel.all.pressure.io.full.avg", + [PCP_PSI_MEMSOME] = "kernel.all.pressure.memory.some.avg", + [PCP_PSI_MEMFULL] = "kernel.all.pressure.memory.full.avg", + + [PCP_ZFS_ARC_ANON_SIZE] = "zfs.arc.anon_size", + [PCP_ZFS_ARC_BONUS_SIZE] = "zfs.arc.bonus_size", + [PCP_ZFS_ARC_COMPRESSED_SIZE] = "zfs.arc.compressed_size", + [PCP_ZFS_ARC_UNCOMPRESSED_SIZE] = "zfs.arc.uncompressed_size", + [PCP_ZFS_ARC_C_MIN] = "zfs.arc.c_min", + [PCP_ZFS_ARC_C_MAX] = "zfs.arc.c_max", + [PCP_ZFS_ARC_DBUF_SIZE] = "zfs.arc.dbuf_size", + [PCP_ZFS_ARC_DNODE_SIZE] = "zfs.arc.dnode_size", + [PCP_ZFS_ARC_HDR_SIZE] = "zfs.arc.hdr_size", + [PCP_ZFS_ARC_MFU_SIZE] = "zfs.arc.mfu.size", + [PCP_ZFS_ARC_MRU_SIZE] = "zfs.arc.mru.size", + [PCP_ZFS_ARC_SIZE] = "zfs.arc.size", + + [PCP_ZRAM_CAPACITY] = "zram.capacity", + [PCP_ZRAM_ORIGINAL] = "zram.mm_stat.data_size.original", + [PCP_ZRAM_COMPRESSED] = "zram.mm_stat.data_size.compressed", + + [PCP_PROC_PID] = "proc.psinfo.pid", + [PCP_PROC_PPID] = "proc.psinfo.ppid", + [PCP_PROC_TGID] = "proc.psinfo.tgid", + [PCP_PROC_PGRP] = "proc.psinfo.pgrp", + [PCP_PROC_SESSION] = "proc.psinfo.session", + [PCP_PROC_STATE] = "proc.psinfo.sname", + [PCP_PROC_TTY] = "proc.psinfo.tty", + [PCP_PROC_TTYPGRP] = "proc.psinfo.tty_pgrp", + [PCP_PROC_MINFLT] = "proc.psinfo.minflt", + [PCP_PROC_MAJFLT] = "proc.psinfo.maj_flt", + [PCP_PROC_CMINFLT] = "proc.psinfo.cmin_flt", + [PCP_PROC_CMAJFLT] = "proc.psinfo.cmaj_flt", + [PCP_PROC_UTIME] = "proc.psinfo.utime", + [PCP_PROC_STIME] = "proc.psinfo.stime", + [PCP_PROC_CUTIME] = "proc.psinfo.cutime", + [PCP_PROC_CSTIME] = "proc.psinfo.cstime", + [PCP_PROC_PRIORITY] = "proc.psinfo.priority", + [PCP_PROC_NICE] = "proc.psinfo.nice", + [PCP_PROC_THREADS] = "proc.psinfo.threads", + [PCP_PROC_STARTTIME] = "proc.psinfo.start_time", + [PCP_PROC_PROCESSOR] = "proc.psinfo.processor", + [PCP_PROC_CMD] = "proc.psinfo.cmd", + [PCP_PROC_PSARGS] = "proc.psinfo.psargs", + [PCP_PROC_CGROUPS] = "proc.psinfo.cgroups", + [PCP_PROC_OOMSCORE] = "proc.psinfo.oom_score", + [PCP_PROC_VCTXSW] = "proc.psinfo.vctxsw", + [PCP_PROC_NVCTXSW] = "proc.psinfo.nvctxsw", + [PCP_PROC_LABELS] = "proc.psinfo.labels", + [PCP_PROC_ENVIRON] = "proc.psinfo.environ", + [PCP_PROC_TTYNAME] = "proc.psinfo.ttyname", + [PCP_PROC_EXE] = "proc.psinfo.exe", + [PCP_PROC_CWD] = "proc.psinfo.cwd", + [PCP_PROC_AUTOGROUP_ID] = "proc.autogroup.id", + [PCP_PROC_AUTOGROUP_NICE] = "proc.autogroup.nice", + [PCP_PROC_ID_UID] = "proc.id.uid", + [PCP_PROC_ID_USER] = "proc.id.uid_nm", + [PCP_PROC_IO_RCHAR] = "proc.io.rchar", + [PCP_PROC_IO_WCHAR] = "proc.io.wchar", + [PCP_PROC_IO_SYSCR] = "proc.io.syscr", + [PCP_PROC_IO_SYSCW] = "proc.io.syscw", + [PCP_PROC_IO_READB] = "proc.io.read_bytes", + [PCP_PROC_IO_WRITEB] = "proc.io.write_bytes", + [PCP_PROC_IO_CANCELLED] = "proc.io.cancelled_write_bytes", + [PCP_PROC_MEM_SIZE] = "proc.memory.size", + [PCP_PROC_MEM_RSS] = "proc.memory.rss", + [PCP_PROC_MEM_SHARE] = "proc.memory.share", + [PCP_PROC_MEM_TEXTRS] = "proc.memory.textrss", + [PCP_PROC_MEM_LIBRS] = "proc.memory.librss", + [PCP_PROC_MEM_DATRS] = "proc.memory.datrss", + [PCP_PROC_MEM_DIRTY] = "proc.memory.dirty", + [PCP_PROC_SMAPS_PSS] = "proc.smaps.pss", + [PCP_PROC_SMAPS_SWAP] = "proc.smaps.swap", + [PCP_PROC_SMAPS_SWAPPSS] = "proc.smaps.swappss", + + [PCP_METRIC_COUNT] = NULL +}; + +#ifndef HAVE_PMLOOKUPDESCS +/* + * pmLookupDescs(3) exists in latest versions of libpcp (5.3.6+), + * but for older versions we provide an implementation here. This + * involves multiple round trips to pmcd though, which the latest + * libpcp version avoids by using a protocol extension. In time, + * perhaps in a few years, we could remove this back-compat code. + */ +int pmLookupDescs(int numpmid, pmID* pmids, pmDesc* descs) { + int count = 0; + + for (int i = 0; i < numpmid; i++) { + /* expect some metrics to be missing - e.g. PMDA not available */ + if (pmids[i] == PM_ID_NULL) + continue; + + int sts = pmLookupDesc(pmids[i], &descs[i]); + if (sts < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot lookup metric %s(%s): %s\n", + pcp->names[i], pmIDStr(pcp->pmids[i]), pmErrStr(sts)); + pmids[i] = PM_ID_NULL; + continue; + } + + count++; + } + return count; +} +#endif + +size_t Platform_addMetric(PCPMetric id, const char* name) { + unsigned int i = (unsigned int)id; + + if (i >= PCP_METRIC_COUNT && i >= pcp->totalMetrics) { + /* added via configuration files */ + size_t j = pcp->totalMetrics + 1; + pcp->fetch = xRealloc(pcp->fetch, j * sizeof(pmID)); + pcp->pmids = xRealloc(pcp->pmids, j * sizeof(pmID)); + pcp->names = xRealloc(pcp->names, j * sizeof(char*)); + pcp->descs = xRealloc(pcp->descs, j * sizeof(pmDesc)); + memset(&pcp->descs[i], 0, sizeof(pmDesc)); + } + + pcp->pmids[i] = pcp->fetch[i] = PM_ID_NULL; + pcp->names[i] = name; + return ++pcp->totalMetrics; +} + +/* global state from the environment and command line arguments */ +pmOptions opts; + +bool Platform_init(void) { + const char* source; + if (opts.context == PM_CONTEXT_ARCHIVE) { + source = opts.archives[0]; + } else if (opts.context == PM_CONTEXT_HOST) { + source = opts.nhosts > 0 ? opts.hosts[0] : "local:"; + } else { + opts.context = PM_CONTEXT_HOST; + source = "local:"; + } + + int sts; + sts = pmNewContext(opts.context, source); + /* with no host requested, fallback to PM_CONTEXT_LOCAL shared libraries */ + if (sts < 0 && opts.context == PM_CONTEXT_HOST && opts.nhosts == 0) { + opts.context = PM_CONTEXT_LOCAL; + sts = pmNewContext(opts.context, NULL); + } + if (sts < 0) { + fprintf(stderr, "Cannot setup PCP metric source: %s\n", pmErrStr(sts)); + return false; + } + /* setup timezones and other general startup preparation completion */ + if (pmGetContextOptions(sts, &opts) < 0 || opts.errors) { + pmflush(); + return false; + } + + pcp = xCalloc(1, sizeof(Platform)); + pcp->context = sts; + pcp->fetch = xCalloc(PCP_METRIC_COUNT, sizeof(pmID)); + pcp->pmids = xCalloc(PCP_METRIC_COUNT, sizeof(pmID)); + pcp->names = xCalloc(PCP_METRIC_COUNT, sizeof(char*)); + pcp->descs = xCalloc(PCP_METRIC_COUNT, sizeof(pmDesc)); + + if (opts.context == PM_CONTEXT_ARCHIVE) { + gettimeofday(&pcp->offset, NULL); + pmtimevalDec(&pcp->offset, &opts.start); + } + + for (unsigned int i = 0; i < PCP_METRIC_COUNT; i++) + Platform_addMetric(i, Platform_metricNames[i]); + pcp->meters.offset = PCP_METRIC_COUNT; + + PCPDynamicMeters_init(&pcp->meters); + + pcp->columns.offset = PCP_METRIC_COUNT + pcp->meters.cursor; + PCPDynamicColumns_init(&pcp->columns); + + sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids); + if (sts < 0) { + fprintf(stderr, "Error: cannot lookup metric names: %s\n", pmErrStr(sts)); + Platform_done(); + return false; + } + + sts = pmLookupDescs(pcp->totalMetrics, pcp->pmids, pcp->descs); + if (sts < 1) { + if (sts < 0) + fprintf(stderr, "Error: cannot lookup descriptors: %s\n", pmErrStr(sts)); + else /* ensure we have at least one valid metric to work with */ + fprintf(stderr, "Error: cannot find a single valid metric, exiting\n"); + Platform_done(); + return false; + } + + /* set proc.control.perclient.threads to 1 for live contexts */ + PCPMetric_enableThreads(); + + /* extract values needed for setup - e.g. cpu count, pid_max */ + PCPMetric_enable(PCP_PID_MAX, true); + PCPMetric_enable(PCP_BOOTTIME, true); + PCPMetric_enable(PCP_HINV_NCPU, true); + PCPMetric_enable(PCP_PERCPU_SYSTEM, true); + PCPMetric_enable(PCP_UNAME_SYSNAME, true); + PCPMetric_enable(PCP_UNAME_RELEASE, true); + PCPMetric_enable(PCP_UNAME_MACHINE, true); + PCPMetric_enable(PCP_UNAME_DISTRO, true); + + for (size_t i = pcp->columns.offset; i < pcp->columns.offset + pcp->columns.count; i++) + PCPMetric_enable(i, true); + + PCPMetric_fetch(NULL); + + for (PCPMetric metric = 0; metric < PCP_PROC_PID; metric++) + PCPMetric_enable(metric, true); + PCPMetric_enable(PCP_PID_MAX, false); /* needed one time only */ + PCPMetric_enable(PCP_BOOTTIME, false); + PCPMetric_enable(PCP_UNAME_SYSNAME, false); + PCPMetric_enable(PCP_UNAME_RELEASE, false); + PCPMetric_enable(PCP_UNAME_MACHINE, false); + PCPMetric_enable(PCP_UNAME_DISTRO, false); + + /* first sample (fetch) performed above, save constants */ + Platform_getBootTime(); + Platform_getRelease(0); + Platform_getMaxCPU(); + Platform_getMaxPid(); + + return true; +} + +void Platform_dynamicColumnsDone(Hashtable* columns) { + PCPDynamicColumns_done(columns); +} + +void Platform_dynamicMetersDone(Hashtable* meters) { + PCPDynamicMeters_done(meters); +} + +void Platform_done(void) { + pmDestroyContext(pcp->context); + if (pcp->result) + pmFreeResult(pcp->result); + free(pcp->release); + free(pcp->fetch); + free(pcp->pmids); + free(pcp->names); + free(pcp->descs); + free(pcp); +} + +void Platform_setBindings(Htop_Action* keys) { + /* no platform-specific key bindings */ + (void)keys; +} + +int Platform_getUptime(void) { + pmAtomValue value; + if (PCPMetric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL) + return 0; + return value.l; +} + +void Platform_getLoadAverage(double* one, double* five, double* fifteen) { + *one = *five = *fifteen = 0.0; + + pmAtomValue values[3] = {0}; + if (PCPMetric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) { + *one = values[0].d; + *five = values[1].d; + *fifteen = values[2].d; + } +} + +unsigned int Platform_getMaxCPU(void) { + if (pcp->ncpu) + return pcp->ncpu; + + pmAtomValue value; + if (PCPMetric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL) + pcp->ncpu = value.ul; + else + pcp->ncpu = 1; + return pcp->ncpu; +} + +int Platform_getMaxPid(void) { + if (pcp->pidmax) + return pcp->pidmax; + + pmAtomValue value; + if (PCPMetric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL) + return -1; + pcp->pidmax = value.l; + return pcp->pidmax; +} + +long long Platform_getBootTime(void) { + if (pcp->btime) + return pcp->btime; + + pmAtomValue value; + if (PCPMetric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL) + pcp->btime = value.ll; + return pcp->btime; +} + +static double Platform_setOneCPUValues(Meter* this, pmAtomValue* values) { + + unsigned long long value = values[CPU_TOTAL_PERIOD].ull; + double total = (double) (value == 0 ? 1 : value); + double percent; + + double* v = this->values; + v[CPU_METER_NICE] = values[CPU_NICE_PERIOD].ull / total * 100.0; + v[CPU_METER_NORMAL] = values[CPU_USER_PERIOD].ull / total * 100.0; + if (this->pl->settings->detailedCPUTime) { + v[CPU_METER_KERNEL] = values[CPU_SYSTEM_PERIOD].ull / total * 100.0; + v[CPU_METER_IRQ] = values[CPU_IRQ_PERIOD].ull / total * 100.0; + v[CPU_METER_SOFTIRQ] = values[CPU_SOFTIRQ_PERIOD].ull / total * 100.0; + v[CPU_METER_STEAL] = values[CPU_STEAL_PERIOD].ull / total * 100.0; + v[CPU_METER_GUEST] = values[CPU_GUEST_PERIOD].ull / total * 100.0; + v[CPU_METER_IOWAIT] = values[CPU_IOWAIT_PERIOD].ull / total * 100.0; + this->curItems = 8; + if (this->pl->settings->accountGuestInCPUMeter) + percent = v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6]; + else + percent = v[0] + v[1] + v[2] + v[3] + v[4]; + } else { + v[2] = values[CPU_SYSTEM_ALL_PERIOD].ull / total * 100.0; + value = values[CPU_STEAL_PERIOD].ull + values[CPU_GUEST_PERIOD].ull; + v[3] = value / total * 100.0; + this->curItems = 4; + percent = v[0] + v[1] + v[2] + v[3]; + } + percent = CLAMP(percent, 0.0, 100.0); + if (isnan(percent)) + percent = 0.0; + + v[CPU_METER_FREQUENCY] = values[CPU_FREQUENCY].d; + v[CPU_METER_TEMPERATURE] = NAN; + + return percent; +} + +double Platform_setCPUValues(Meter* this, int cpu) { + const PCPProcessList* pl = (const PCPProcessList*) this->pl; + if (cpu <= 0) /* use aggregate values */ + return Platform_setOneCPUValues(this, pl->cpu); + return Platform_setOneCPUValues(this, pl->percpu[cpu - 1]); +} + +void Platform_setMemoryValues(Meter* this) { + const ProcessList* pl = this->pl; + const PCPProcessList* ppl = (const PCPProcessList*) pl; + + this->total = pl->totalMem; + this->values[MEMORY_METER_USED] = pl->usedMem; + this->values[MEMORY_METER_BUFFERS] = pl->buffersMem; + this->values[MEMORY_METER_SHARED] = pl->sharedMem; + this->values[MEMORY_METER_CACHE] = pl->cachedMem; + this->values[MEMORY_METER_AVAILABLE] = pl->availableMem; + + if (ppl->zfs.enabled != 0) { + // ZFS does not shrink below the value of zfs_arc_min. + unsigned long long int shrinkableSize = 0; + if (ppl->zfs.size > ppl->zfs.min) + shrinkableSize = ppl->zfs.size - ppl->zfs.min; + this->values[MEMORY_METER_USED] -= shrinkableSize; + this->values[MEMORY_METER_CACHE] += shrinkableSize; + this->values[MEMORY_METER_AVAILABLE] += shrinkableSize; + } +} + +void Platform_setSwapValues(Meter* this) { + const ProcessList* pl = this->pl; + this->total = pl->totalSwap; + this->values[SWAP_METER_USED] = pl->usedSwap; + this->values[SWAP_METER_CACHE] = pl->cachedSwap; +} + +void Platform_setZramValues(Meter* this) { + int i, count = PCPMetric_instanceCount(PCP_ZRAM_CAPACITY); + if (!count) { + this->total = 0; + this->values[0] = 0; + this->values[1] = 0; + return; + } + + pmAtomValue* values = xCalloc(count, sizeof(pmAtomValue)); + ZramStats stats = {0}; + + if (PCPMetric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) { + for (i = 0; i < count; i++) + stats.totalZram += values[i].ull; + } + if (PCPMetric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) { + for (i = 0; i < count; i++) + stats.usedZramOrig += values[i].ull; + } + if (PCPMetric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) { + for (i = 0; i < count; i++) + stats.usedZramComp += values[i].ull; + } + + free(values); + + this->total = stats.totalZram; + this->values[0] = stats.usedZramComp; + this->values[1] = stats.usedZramOrig; +} + +void Platform_setZfsArcValues(Meter* this) { + const PCPProcessList* ppl = (const PCPProcessList*) this->pl; + + ZfsArcMeter_readStats(this, &(ppl->zfs)); +} + +void Platform_setZfsCompressedArcValues(Meter* this) { + const PCPProcessList* ppl = (const PCPProcessList*) this->pl; + + ZfsCompressedArcMeter_readStats(this, &(ppl->zfs)); +} + +void Platform_getHostname(char* buffer, size_t size) { + const char* hostname = pmGetContextHostName(pcp->context); + String_safeStrncpy(buffer, hostname, size); +} + +void Platform_getRelease(char** string) { + /* fast-path - previously-formatted string */ + if (string) { + *string = pcp->release; + return; + } + + /* first call, extract just-sampled values */ + pmAtomValue sysname, release, machine, distro; + if (!PCPMetric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING)) + sysname.cp = NULL; + if (!PCPMetric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING)) + release.cp = NULL; + if (!PCPMetric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING)) + machine.cp = NULL; + if (!PCPMetric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING)) + distro.cp = NULL; + + size_t length = 16; /* padded for formatting characters */ + if (sysname.cp) + length += strlen(sysname.cp); + if (release.cp) + length += strlen(release.cp); + if (machine.cp) + length += strlen(machine.cp); + if (distro.cp) + length += strlen(distro.cp); + pcp->release = xCalloc(1, length); + + if (sysname.cp) { + strcat(pcp->release, sysname.cp); + strcat(pcp->release, " "); + } + if (release.cp) { + strcat(pcp->release, release.cp); + strcat(pcp->release, " "); + } + if (machine.cp) { + strcat(pcp->release, "["); + strcat(pcp->release, machine.cp); + strcat(pcp->release, "] "); + } + if (distro.cp) { + if (pcp->release[0] != '\0') { + strcat(pcp->release, "@ "); + strcat(pcp->release, distro.cp); + } else { + strcat(pcp->release, distro.cp); + } + strcat(pcp->release, " "); + } + + if (pcp->release) /* cull trailing space */ + pcp->release[strlen(pcp->release)] = '\0'; + + free(distro.cp); + free(machine.cp); + free(release.cp); + free(sysname.cp); +} + +char* Platform_getProcessEnv(pid_t pid) { + pmAtomValue value; + if (!PCPMetric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING)) + return NULL; + return value.cp; +} + +FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) { + (void)pid; + return NULL; +} + +void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) { + *ten = *sixty = *threehundred = 0; + + PCPMetric metric; + if (String_eq(file, "cpu")) + metric = PCP_PSI_CPUSOME; + else if (String_eq(file, "io")) + metric = some ? PCP_PSI_IOSOME : PCP_PSI_IOFULL; + else if (String_eq(file, "mem")) + metric = some ? PCP_PSI_MEMSOME : PCP_PSI_MEMFULL; + else + return; + + pmAtomValue values[3] = {0}; + if (PCPMetric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) { + *ten = values[0].d; + *sixty = values[1].d; + *threehundred = values[2].d; + } +} + +bool Platform_getDiskIO(DiskIOData* data) { + memset(data, 0, sizeof(*data)); + + pmAtomValue value; + if (PCPMetric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL) + data->totalBytesRead = value.ull; + if (PCPMetric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL) + data->totalBytesWritten = value.ull; + if (PCPMetric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL) + data->totalMsTimeSpend = value.ull; + return true; +} + +bool Platform_getNetworkIO(NetworkIOData* data) { + memset(data, 0, sizeof(*data)); + + pmAtomValue value; + if (PCPMetric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL) + data->bytesReceived = value.ull; + if (PCPMetric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL) + data->bytesTransmitted = value.ull; + if (PCPMetric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL) + data->packetsReceived = value.ull; + if (PCPMetric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL) + data->packetsTransmitted = value.ull; + return true; +} + +void Platform_getBattery(double* level, ACPresence* isOnAC) { + *level = NAN; + *isOnAC = AC_ERROR; +} + +void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { + printf( +" --host=HOSTSPEC metrics source is PMCD at HOSTSPEC [see PCPIntro(1)]\n" +" --hostzone set reporting timezone to local time of metrics source\n" +" --timezone=TZ set reporting timezone\n"); +} + +CommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) { + /* libpcp export without a header definition */ + extern void __pmAddOptHost(pmOptions*, char*); + + switch (opt) { + case PLATFORM_LONGOPT_HOST: /* --host=HOSTSPEC */ + if (argv[optind][0] == '\0') + return STATUS_ERROR_EXIT; + __pmAddOptHost(&opts, optarg); + return STATUS_OK; + + case PLATFORM_LONGOPT_HOSTZONE: /* --hostzone */ + if (opts.timezone) { + pmprintf("%s: at most one of -Z and -z allowed\n", pmGetProgname()); + opts.errors++; + } else { + opts.tzflag = 1; + } + return STATUS_OK; + + case PLATFORM_LONGOPT_TIMEZONE: /* --timezone=TZ */ + if (argv[optind][0] == '\0') + return STATUS_ERROR_EXIT; + if (opts.tzflag) { + pmprintf("%s: at most one of -Z and -z allowed\n", pmGetProgname()); + opts.errors++; + } else { + opts.timezone = optarg; + } + return STATUS_OK; + + default: + break; + } + return STATUS_ERROR_EXIT; +} + +void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { + if (gettimeofday(tv, NULL) == 0) { + /* shift by start offset to stay in lock-step with realtime (archives) */ + if (pcp->offset.tv_sec || pcp->offset.tv_usec) + pmtimevalDec(tv, &pcp->offset); + *msec = ((uint64_t)tv->tv_sec * 1000) + ((uint64_t)tv->tv_usec / 1000); + } else { + memset(tv, 0, sizeof(struct timeval)); + *msec = 0; + } +} + +void Platform_gettime_monotonic(uint64_t* msec) { + if (pcp->result) { + struct timeval* tv = &pcp->result->timestamp; + *msec = ((uint64_t)tv->tv_sec * 1000) + ((uint64_t)tv->tv_usec / 1000); + } else { + *msec = 0; + } +} + +Hashtable* Platform_dynamicMeters(void) { + return pcp->meters.table; +} + +void Platform_dynamicMeterInit(Meter* meter) { + PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param); + if (this) + PCPDynamicMeter_enable(this); +} + +void Platform_dynamicMeterUpdateValues(Meter* meter) { + PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param); + if (this) + PCPDynamicMeter_updateValues(this, meter); +} + +void Platform_dynamicMeterDisplay(const Meter* meter, RichString* out) { + PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param); + if (this) + PCPDynamicMeter_display(this, meter, out); +} + +Hashtable* Platform_dynamicColumns(void) { + return pcp->columns.table; +} + +const char* Platform_dynamicColumnInit(unsigned int key) { + PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); + if (this) { + PCPMetric_enable(this->id, true); + if (this->super.caption) + return this->super.caption; + if (this->super.heading) + return this->super.heading; + return this->super.name; + } + return NULL; +} + +bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key) { + PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); + if (this) { + PCPDynamicColumn_writeField(this, proc, str); + return true; + } + return false; +} diff --git a/pcp/Platform.h b/pcp/Platform.h new file mode 100644 index 0000000..f06f226 --- /dev/null +++ b/pcp/Platform.h @@ -0,0 +1,156 @@ +#ifndef HEADER_Platform +#define HEADER_Platform +/* +htop - pcp/Platform.h +(C) 2014 Hisham H. Muhammad +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <pcp/pmapi.h> +#include <sys/time.h> +#include <sys/types.h> + +/* use htop config.h values for these macros, not pcp values */ +#undef PACKAGE_URL +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_BUGREPORT + +#include "Action.h" +#include "BatteryMeter.h" +#include "DiskIOMeter.h" +#include "Hashtable.h" +#include "Meter.h" +#include "NetworkIOMeter.h" +#include "Process.h" +#include "ProcessLocksScreen.h" +#include "RichString.h" +#include "SignalsPanel.h" +#include "CommandLine.h" + +#include "pcp/PCPDynamicColumn.h" +#include "pcp/PCPDynamicMeter.h" +#include "pcp/PCPMetric.h" + + +typedef struct Platform_ { + int context; /* PMAPI(3) context identifier */ + size_t totalMetrics; /* total number of all metrics */ + const char** names; /* name array indexed by Metric */ + pmID* pmids; /* all known metric identifiers */ + pmID* fetch; /* enabled identifiers for sampling */ + pmDesc* descs; /* metric desc array indexed by Metric */ + pmResult* result; /* sample values result indexed by Metric */ + PCPDynamicMeters meters; /* dynamic meters via configuration files */ + PCPDynamicColumns columns; /* dynamic columns via configuration files */ + struct timeval offset; /* time offset used in archive mode only */ + long long btime; /* boottime in seconds since the epoch */ + char* release; /* uname and distro from this context */ + int pidmax; /* maximum platform process identifier */ + unsigned int ncpu; /* maximum processor count configured */ +} Platform; + +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; + +extern const SignalItem Platform_signals[]; + +extern const unsigned int Platform_numberOfSignals; + +extern const MeterClass* const Platform_meterTypes[]; + +bool Platform_init(void); + +void Platform_done(void); + +void Platform_setBindings(Htop_Action* keys); + +int Platform_getUptime(void); + +void Platform_getLoadAverage(double* one, double* five, double* fifteen); + +long long Platform_getBootTime(void); + +unsigned int Platform_getMaxCPU(void); + +int Platform_getMaxPid(void); + +double Platform_setCPUValues(Meter* this, int cpu); + +void Platform_setMemoryValues(Meter* this); + +void Platform_setSwapValues(Meter* this); + +void Platform_setZramValues(Meter* this); + +void Platform_setZfsArcValues(Meter* this); + +void Platform_setZfsCompressedArcValues(Meter* this); + +char* Platform_getProcessEnv(pid_t pid); + +FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid); + +void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred); + +bool Platform_getDiskIO(DiskIOData* data); + +bool Platform_getNetworkIO(NetworkIOData* data); + +void Platform_getBattery(double* percent, ACPresence* isOnAC); + +void Platform_getHostname(char* buffer, size_t size); + +void Platform_getRelease(char** string); + +enum { + PLATFORM_LONGOPT_HOST = 128, + PLATFORM_LONGOPT_TIMEZONE, + PLATFORM_LONGOPT_HOSTZONE, +}; + +#define PLATFORM_LONG_OPTIONS \ + {PMLONGOPT_HOST, optional_argument, 0, PLATFORM_LONGOPT_HOST}, \ + {PMLONGOPT_TIMEZONE, optional_argument, 0, PLATFORM_LONGOPT_TIMEZONE}, \ + {PMLONGOPT_HOSTZONE, optional_argument, 0, PLATFORM_LONGOPT_HOSTZONE}, \ + +void Platform_longOptionsUsage(const char* name); + +CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv); + +extern pmOptions opts; + +size_t Platform_addMetric(PCPMetric id, const char* name); + +void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec); + +void Platform_gettime_monotonic(uint64_t* msec); + +Hashtable* Platform_dynamicMeters(void); + +void Platform_dynamicMetersDone(Hashtable* meters); + +void Platform_dynamicMeterInit(Meter* meter); + +void Platform_dynamicMeterUpdateValues(Meter* meter); + +void Platform_dynamicMeterDisplay(const Meter* meter, RichString* out); + +Hashtable* Platform_dynamicColumns(void); + +void Platform_dynamicColumnsDone(Hashtable* columns); + +const char* Platform_dynamicColumnInit(unsigned int key); + +bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key); + +#endif diff --git a/pcp/ProcessField.h b/pcp/ProcessField.h new file mode 100644 index 0000000..b3ba265 --- /dev/null +++ b/pcp/ProcessField.h @@ -0,0 +1,51 @@ +#ifndef HEADER_PCPProcessField +#define HEADER_PCPProcessField +/* +htop - pcp/ProcessField.h +(C) 2014 Hisham H. Muhammad +(C) 2021 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + + +#define PLATFORM_PROCESS_FIELDS \ + CMINFLT = 11, \ + CMAJFLT = 13, \ + UTIME = 14, \ + STIME = 15, \ + CUTIME = 16, \ + CSTIME = 17, \ + M_SHARE = 41, \ + M_TRS = 42, \ + M_DRS = 43, \ + M_LRS = 44, \ + M_DT = 45, \ + CTID = 100, \ + RCHAR = 103, \ + WCHAR = 104, \ + SYSCR = 105, \ + SYSCW = 106, \ + RBYTES = 107, \ + WBYTES = 108, \ + CNCLWB = 109, \ + IO_READ_RATE = 110, \ + IO_WRITE_RATE = 111, \ + IO_RATE = 112, \ + CGROUP = 113, \ + OOM = 114, \ + PERCENT_CPU_DELAY = 116, \ + PERCENT_IO_DELAY = 117, \ + PERCENT_SWAP_DELAY = 118, \ + M_PSS = 119, \ + M_SWAP = 120, \ + M_PSSWP = 121, \ + CTXT = 122, \ + SECATTR = 123, \ + AUTOGROUP_ID = 127, \ + AUTOGROUP_NICE = 128, \ + // End of list + + +#endif /* HEADER_PCPProcessField */ diff --git a/pcp/columns/container b/pcp/columns/container new file mode 100644 index 0000000..519288f --- /dev/null +++ b/pcp/columns/container @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[container] +heading = Container +caption = CONTAINER +width = -12 +metric = proc.id.container +description = Name of processes container via cgroup heuristics diff --git a/pcp/columns/delayacct b/pcp/columns/delayacct new file mode 100644 index 0000000..016904c --- /dev/null +++ b/pcp/columns/delayacct @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[blkio] +heading = BLKIOD +caption = BLKIO_TIME +width = 6 +metric = proc.psinfo.delayacct_blkio_time +description = Aggregated block I/O delays diff --git a/pcp/columns/fdcount b/pcp/columns/fdcount new file mode 100644 index 0000000..e679480 --- /dev/null +++ b/pcp/columns/fdcount @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[fds] +heading = FDS +caption = FDCOUNT +width = 4 +metric = proc.fd.count +description = Open file descriptors diff --git a/pcp/columns/guest b/pcp/columns/guest new file mode 100644 index 0000000..89bb926 --- /dev/null +++ b/pcp/columns/guest @@ -0,0 +1,17 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[guest] +heading = GUEST +caption = GUEST_TIME +width = 6 +metric = proc.psinfo.guest_time +description = Guest time for the process + +[cguest] +heading = CGUEST +caption = CGUEST_TIME +width = 6 +metric = proc.psinfo.guest_time + proc.psinfo.cguest_time +description = Cumulative guest time for the process and its children diff --git a/pcp/columns/memory b/pcp/columns/memory new file mode 100644 index 0000000..305a654 --- /dev/null +++ b/pcp/columns/memory @@ -0,0 +1,39 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[vmdata] +heading = VDATA +width = 6 +metric = proc.memory.vmdata +description = Virtual memory used for data + +[vmstack] +heading = VSTACK +width = -6 +metric = proc.memory.vmstack +description = Virtual memory used for stack + +[vmexe] +heading = VEXEC +width = 6 +metric = proc.memory.vmexe +description = Virtual memory used for non-library executable code + +[vmlib] +heading = VLIBS +width = 6 +metric = proc.memory.vmlib +description = Virtual memory used for libraries + +[vmswap] +heading = VSWAP +width = 6 +metric = proc.memory.vmswap +description = Virtual memory size currently swapped out + +[vmlock] +heading = VLOCK +width = 6 +metric = proc.memory.vmlock +description = Locked virtual memory diff --git a/pcp/columns/sched b/pcp/columns/sched new file mode 100644 index 0000000..36b8b55 --- /dev/null +++ b/pcp/columns/sched @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[rundelay] +heading = RUNQ +caption = RUN_DELAY +width = 4 +metric = proc.schedstat.run_delay +description = Run queue time diff --git a/pcp/columns/swap b/pcp/columns/swap new file mode 100644 index 0000000..234b3db --- /dev/null +++ b/pcp/columns/swap @@ -0,0 +1,15 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[swap] +heading = SWAP +width = 5 +metric = proc.psinfo.nswap +description = Count of swap operations for the process + +[cswap] +heading = CSWAP +width = 5 +metric = proc.psinfo.nswap + proc.psinfo.cnswap +description = Cumulative swap operations for the process and its children diff --git a/pcp/columns/tcp b/pcp/columns/tcp new file mode 100644 index 0000000..f9a1819 --- /dev/null +++ b/pcp/columns/tcp @@ -0,0 +1,31 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[tcp_send_calls] +heading = TCPS +caption = TCP_SEND +width = 6 +metric = bcc.proc.net.tcp.send.calls +description = Count of TCP send calls + +[tcp_send_bytes] +heading = TCPSB +caption = TCP_SEND_BYTES +width = 6 +metric = bcc.proc.net.tcp.send.bytes +description = Cumulative bytes sent via TCP + +[tcp_recv_calls] +heading = TCPR +caption = TCP_RECV +width = 6 +metric = bcc.proc.net.tcp.recv.calls +description = Count of TCP recv calls + +[tcp_recv_bytes] +heading = TCPRB +caption = TCP_RECV_BYTES +width = 6 +metric = bcc.proc.net.tcp.recv.bytes +description = Cumulative bytes received via TCP diff --git a/pcp/columns/udp b/pcp/columns/udp new file mode 100644 index 0000000..060f048 --- /dev/null +++ b/pcp/columns/udp @@ -0,0 +1,31 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[udp_send_calls] +heading = UDPS +caption = UDP_SEND +width = 6 +metric = bcc.proc.net.udp.send.calls +description = Count of UDP send calls + +[udp_send_bytes] +heading = UDPSB +caption = UDP_SEND_BYTES +width = 6 +metric = bcc.proc.net.udp.send.bytes +description = Cumulative bytes sent via UDP + +[udp_recv_calls] +heading = UDPR +caption = UDP_RECV +width = 6 +metric = bcc.proc.net.udp.recv.calls +description = Count of UDP recv calls + +[udp_recv_bytes] +heading = UDPRB +caption = UDP_RECV_BYTES +width = 6 +metric = bcc.proc.net.udp.recv.bytes +description = Cumulative bytes received via UDP diff --git a/pcp/columns/wchan b/pcp/columns/wchan new file mode 100644 index 0000000..893de58 --- /dev/null +++ b/pcp/columns/wchan @@ -0,0 +1,17 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[wchan] +heading = WCHAN +caption = WCHAN_ADDRESS +width = 8 +metric = proc.psinfo.wchan +description = Wait channel, kernel address process is blocked or sleeping on + +[wchans] +heading = WCHANS +caption = WCHAN_SYMBOL +width = -12 +metric = proc.psinfo.wchan_s +description = Wait channel, kernel symbol process is blocked or sleeping on diff --git a/pcp/meters/entropy b/pcp/meters/entropy new file mode 100644 index 0000000..0bef0cf --- /dev/null +++ b/pcp/meters/entropy @@ -0,0 +1,9 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[entropy] +caption = Entropy +avail.metric = kernel.all.entropy.avail / kernel.all.entropy.poolsize * 100 +avail.label = avail +avail.suffix = % diff --git a/pcp/meters/freespace b/pcp/meters/freespace new file mode 100644 index 0000000..074af6d --- /dev/null +++ b/pcp/meters/freespace @@ -0,0 +1,11 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[freespace] +caption = Freespace +description = Filesystem space +used.metric = sum(filesys.used) +used.color = blue +free.metric = sum(filesys.free) +free.color = green diff --git a/pcp/meters/ipc b/pcp/meters/ipc new file mode 100644 index 0000000..4162f39 --- /dev/null +++ b/pcp/meters/ipc @@ -0,0 +1,13 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[ipc] +caption = SysV IPC +description = SysV IPC counts +msg.metric = ipc.msg.used_queues +msg.color = blue +sem.metric = ipc.sem.used_sem +sem.color = green +shm.metric = ipc.shm.used_ids +shm.color = cyan diff --git a/pcp/meters/locks b/pcp/meters/locks new file mode 100644 index 0000000..5d09510 --- /dev/null +++ b/pcp/meters/locks @@ -0,0 +1,15 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[locks] +caption = File locks +description = VFS file locks +posix.metric = vfs.locks.posix.count +posix.color = blue +flock.metric = vfs.locks.flock.count +flock.color = green +readlock.metric = vfs.locks.posix.read + vfs.locks.flock.read +readlock.color = red +writelock.metric = vfs.locks.posix.write + vfs.locks.flock.write +writelock.color = yellow diff --git a/pcp/meters/memcache b/pcp/meters/memcache new file mode 100644 index 0000000..0f2fac2 --- /dev/null +++ b/pcp/meters/memcache @@ -0,0 +1,11 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[memcache] +caption = Memcache +description = Memcache Hits +hit.metric = sum(memcache.hits) +hit.color = green +miss.metric = sum(memcache.misses) +miss.color = blue diff --git a/pcp/meters/mysql b/pcp/meters/mysql new file mode 100644 index 0000000..a9e75e4 --- /dev/null +++ b/pcp/meters/mysql @@ -0,0 +1,71 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[mysql_io] +caption = MySQL I/O +recv.metric = mysql.status.bytes_received +recv.color = green +sent.metric = mysql.status.bytes_sent +sent.color = blue + +[mysql_keys] +caption = MySQL keys +description = MySQL key status +key_blocks_used.metric = mysql.status.key_blocks_used +key_blocks_used.label = color +key_blocks_used.label = used +key_reads.metric = mysql.status.key_reads +key_reads.label = read +key_reads.color = green +key_writes.metric = mysql.status.key_writes +key_writes.label = writ +key_writes.color = blue +key_read_requests.metric = mysql.status.key_read_requests +key_read_requests.label = rreq +key_read_requests.color = green +key_write_requests.metric = mysql.status.key_write_requests +key_write_requests.label = wreq +key_write_requests.color = blue + +[innodb_buffer] +caption = InnoDB pool +description = InnoDB buffer pool +created.metric = mysql.status.innodb_pages_created +created.label = cr +created.color = yellow +read.metric = mysql.status.innodb_pages_read +read.label = rd +read.color = greed +written.metric = mysql.status.innodb_pages_written +written.label = wr +written.color = red + +[innodb_io] +caption = InnoDB I/O +description = InnoDB I/O operations +read.metric = mysql.status.innodb_data_read +read.label = rd +read.color = green +written.metric = mysql.status.innodb_data.writes +written.label = wr +written.color = blue +sync.metric = mysql.status.innodb_data_fsyncs +sync.label = sync +sync.color = cyan + +[innodb_ops] +caption = InnoDB ops +description = InnoDB operations +inserted.metric = mysql.status.innodb_rows_inserted +inserted.label = ins +inserted.color = blue +updated.metric = mysql.status.innodb_rows_updated +updated.label = upd +updated.color = cyan +deleted.metric = mysql.status.innodb_rows_deleted +deleted.label = del +deleted.color = red +read.metric = mysql.status.innodb_rows_read +read.label = rd +read.color = green diff --git a/pcp/meters/postfix b/pcp/meters/postfix new file mode 100644 index 0000000..cda6837 --- /dev/null +++ b/pcp/meters/postfix @@ -0,0 +1,20 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[postfix] +caption = Postfix +incoming.metric = sum(postfix.queues.incoming) +incoming.color = green +incoming.label = in +active.metric = sum(postfix.queues.active) +active.color = blue +active.label = act +deferred.metric = sum(postfix.queues.deferred) +deferred.color = cyan +deferred.label = dfr +bounce.metric = sum(postfix.queues.maildrop) +bounce.color = red +bounce.label = bnc +hold.metric = sum(postfix.queues.hold) +hold.color = yellow diff --git a/pcp/meters/redis b/pcp/meters/redis new file mode 100644 index 0000000..a72f727 --- /dev/null +++ b/pcp/meters/redis @@ -0,0 +1,39 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[redisxact] +caption = Redis xact +description = Redis transactions +tps.metric = redis.instantaneous_ops_per_sec +tps.color = green + +[redismem] +caption = Redis mem +description = Redis memory +lua.metric = redis.used_memory_lua +lua.color = magenta +used.metric = redis.used_memory +used.color = blue + +[redisclient] +caption = Redis clients +description = Redis clients +type = bar +blocked.metric = redis.blocked_clients +blocked.color = blue +blocked.label = blk +clients.metric = redis.connected_clients +clients.color = green +clients.label = conn + +[redisconn] +caption = Redis conn +description = Redis connections +type = bar +reject.metric = redis.rejected_connections +reject.color = magenta +reject.label = fail/s +total.metric = redis.total_connections_received +total.color = blue +total.label = conn/s diff --git a/pcp/meters/tcp b/pcp/meters/tcp new file mode 100644 index 0000000..c95736f --- /dev/null +++ b/pcp/meters/tcp @@ -0,0 +1,21 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[tcp] +caption = TCP +description = TCP sockets +listen.metric = network.tcpconn.listen +listen.color = green +listen.label = lis +active.metric = network.tcpconn.established +active.color = blue +active.label = act +syn.metric = network.tcpconn.syn_sent + network.tcpconn.syn_recv + network.tcpconn.last_ack +syn.color = cyan +wait.metric = network.tcpconn.time_wait +wait.color = red +wait.label = tim +close.metric = network.tcpconn.fin_wait1 + network.tcpconn.fin_wait2 + network.tcpconn.close + network.tcpconn.close_wait + network.tcpconn.closing +close.color = yellow +close.label = clo |