123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- /*
- 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/Metric.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, CONFIGDIR "/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++)
- Metric_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 = Metric_desc(metric->id);
- pmAtomValue atom, raw;
- if (!Metric_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, (double) atom.l, size - bytes) :
- xSnprintf(buffer + bytes, size - bytes, "%d", atom.l);
- break;
- case PM_TYPE_U32:
- bytes += conv.dimSpace ?
- Meter_humanUnit(buffer + bytes, (double) atom.ul, size - bytes) :
- xSnprintf(buffer + bytes, size - bytes, "%u", atom.ul);
- break;
- case PM_TYPE_64:
- bytes += conv.dimSpace ?
- Meter_humanUnit(buffer + bytes, (double) 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, (double) 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, (double) 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 = Metric_desc(metric->id);
- pmAtomValue atom, raw;
- char buffer[64];
- if (!Metric_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, (double) atom.l, sizeof(buffer)) :
- xSnprintf(buffer, sizeof(buffer), "%d", atom.l);
- break;
- case PM_TYPE_U32:
- len = conv.dimSpace ?
- Meter_humanUnit(buffer, (double) atom.ul, sizeof(buffer)) :
- xSnprintf(buffer, sizeof(buffer), "%u", atom.ul);
- break;
- case PM_TYPE_64:
- len = conv.dimSpace ?
- Meter_humanUnit(buffer, (double) atom.ll, sizeof(buffer)) :
- xSnprintf(buffer, sizeof(buffer), "%lld", (long long) atom.ll);
- break;
- case PM_TYPE_U64:
- len = conv.dimSpace ?
- Meter_humanUnit(buffer, (double) 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, (double) 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");
- }
|