diff options
Diffstat (limited to '')
-rw-r--r-- | pcp/PCPDynamicMeter.c | 468 |
1 files changed, 468 insertions, 0 deletions
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"); +} |