diff options
Diffstat (limited to 'pcp/PCPDynamicScreen.c')
-rw-r--r-- | pcp/PCPDynamicScreen.c | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/pcp/PCPDynamicScreen.c b/pcp/PCPDynamicScreen.c new file mode 100644 index 0000000..2222822 --- /dev/null +++ b/pcp/PCPDynamicScreen.c @@ -0,0 +1,407 @@ +/* +htop - PCPDynamicScreen.c +(C) 2022 Sohaib Mohammed +(C) 2022-2023 htop dev team +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/PCPDynamicScreen.h" + +#include <ctype.h> +#include <dirent.h> +#include <stdbool.h> +#include <pcp/pmapi.h> + +#include "AvailableColumnsPanel.h" +#include "Macros.h" +#include "Platform.h" +#include "Settings.h" +#include "XUtils.h" + +#include "pcp/InDomTable.h" +#include "pcp/PCPDynamicColumn.h" + + +static char* formatFields(PCPDynamicScreen* screen) { + char* columns = strdup(""); + + for (size_t j = 0; j < screen->totalColumns; j++) { + const PCPDynamicColumn* column = screen->columns[j]; + if (column->super.enabled == false) + continue; + char* prefix = columns; + xAsprintf(&columns, "%s Dynamic(%s)", prefix, column->super.name); + free(prefix); + } + + return columns; +} + +static void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { + for (size_t i = 0; i < screens->count; i++) { + PCPDynamicScreen* screen = Hashtable_get(screens->table, i); + if (!screen) + return; + + /* setup default fields (columns) based on configuration */ + for (size_t j = 0; j < screen->totalColumns; j++) { + PCPDynamicColumn* column = screen->columns[j]; + + column->id = columns->offset + columns->cursor; + columns->cursor++; + Platform_addMetric(column->id, column->metricName); + + size_t id = columns->count + LAST_PROCESSFIELD; + Hashtable_put(columns->table, id, column); + columns->count++; + + if (j == 0) { + const pmDesc* desc = Metric_desc(column->id); + assert(desc->indom != PM_INDOM_NULL); + screen->indom = desc->indom; + screen->key = column->id; + } + } + screen->super.columnKeys = formatFields(screen); + } +} + +static PCPDynamicColumn* PCPDynamicScreen_lookupMetric(PCPDynamicScreen* screen, const char* name) { + PCPDynamicColumn* column = NULL; + size_t bytes = strlen(name) + strlen(screen->super.name) + 1; /* colon */ + if (bytes >= sizeof(column->super.name)) + return NULL; + + bytes += 16; /* prefix, dots and terminator */ + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.screen.%s.%s", screen->super.name, name); + + for (size_t i = 0; i < screen->totalColumns; i++) { + column = screen->columns[i]; + if (String_eq(column->metricName, metricName)) { + free(metricName); + return column; + } + } + + /* not an existing column in this screen - create it and add to the list */ + column = xCalloc(1, sizeof(PCPDynamicColumn)); + xSnprintf(column->super.name, sizeof(column->super.name), "%s:%s", screen->super.name, name); + column->super.table = &screen->table->super; + column->metricName = metricName; + column->super.enabled = true; + + size_t n = screen->totalColumns + 1; + screen->columns = xReallocArray(screen->columns, n, sizeof(PCPDynamicColumn*)); + screen->columns[n - 1] = column; + screen->totalColumns = n; + + return column; +} + +static void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* path, unsigned int line, char* key, char* value) { + PCPDynamicColumn* column; + char* p; + + if ((p = strchr(key, '.')) == NULL) + return; + *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */ + + /* lookup a dynamic column with this name, else create */ + column = PCPDynamicScreen_lookupMetric(screen, key); + + if (String_eq(p, "metric")) { + 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); + } + + /* pmLookupText - add optional metric help text */ + if (!column->super.description && !column->instances) + Metric_lookupText(value, &column->super.description); + + } else { + /* this is a property of a dynamic column - the column expression */ + /* may not have been observed yet; i.e. we allow for any ordering */ + + if (String_eq(p, "caption")) { + free_and_xStrdup(&column->super.caption, value); + } else if (String_eq(p, "heading")) { + free_and_xStrdup(&column->super.heading, value); + } else if (String_eq(p, "description")) { + free_and_xStrdup(&column->super.description, value); + } else if (String_eq(p, "width")) { + column->width = strtoul(value, NULL, 10); + } else if (String_eq(p, "format")) { + free_and_xStrdup(&column->format, value); + } else if (String_eq(p, "instances")) { + if (String_eq(value, "True") || String_eq(value, "true")) + column->instances = true; + free_and_xStrdup(&column->super.description, screen->super.caption); + } else if (String_eq(p, "default")) { /* displayed by default */ + if (String_eq(value, "False") || String_eq(value, "false")) + column->defaultEnabled = column->super.enabled = false; + } + } +} + +static bool PCPDynamicScreen_validateScreenName(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 screen 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 screen name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +/* Ensure a screen name has not been defined previously */ +static bool PCPDynamicScreen_uniqueName(char* key, PCPDynamicScreens* screens) { + return !DynamicScreen_search(screens->table, key, NULL); +} + +static PCPDynamicScreen* PCPDynamicScreen_new(PCPDynamicScreens* screens, const char* name) { + PCPDynamicScreen* screen = xCalloc(1, sizeof(*screen)); + String_safeStrncpy(screen->super.name, name, sizeof(screen->super.name)); + screen->defaultEnabled = true; + + size_t id = screens->count; + Hashtable_put(screens->table, id, screen); + screens->count++; + + return screen; +} + +static void PCPDynamicScreen_parseFile(PCPDynamicScreens* screens, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicScreen* screen = 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 name - i.e. new screen */ + ok = PCPDynamicScreen_validateScreenName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicScreen_uniqueName(key + 1, screens); + if (ok) + screen = PCPDynamicScreen_new(screens, key + 1); + if (pmDebugOptions.appl0) + fprintf(stderr, "[%s] screen: %s\n", path, key + 1); + } else if (!ok) { + ; /* skip this one, we're looking for a new header */ + } else if (!value || !screen) { + ; /* skip this one as we always need value strings */ + } else if (String_eq(key, "heading")) { + free_and_xStrdup(&screen->super.heading, value); + } else if (String_eq(key, "caption")) { + free_and_xStrdup(&screen->super.caption, value); + } else if (String_eq(key, "sortKey")) { + free_and_xStrdup(&screen->super.sortKey, value); + } else if (String_eq(key, "sortDirection")) { + screen->super.direction = strtoul(value, NULL, 10); + } else if (String_eq(key, "default") || String_eq(key, "enabled")) { + if (String_eq(value, "False") || String_eq(value, "false")) + screen->defaultEnabled = false; + else if (String_eq(value, "True") || String_eq(value, "true")) + screen->defaultEnabled = true; /* also default */ + } else { + PCPDynamicScreen_parseColumn(screen, path, lineno, key, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicScreen_scanDir(PCPDynamicScreens* screens, 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); + PCPDynamicScreen_parseFile(screens, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicScreens_init(PCPDynamicScreens* screens, 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; + + screens->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/screens/"); + else if (home) + path = String_cat(home, "/.config/htop/screens/"); + else + path = NULL; + if (path) { + PCPDynamicScreen_scanDir(screens, path); + free(path); + } + + /* next, search in the system screens directory */ + path = String_cat(sysconf, "/htop/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + + /* next, try the readonly system screens directory */ + path = String_cat(share, "/htop/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + + /* establish internal metric identifier mappings */ + PCPDynamicScreens_appendDynamicColumns(screens, columns); +} + +static void PCPDynamicScreen_done(PCPDynamicScreen* ds) { + DynamicScreen_done(&ds->super); + Object_delete(ds->table); + free(ds->columns); +} + +static void PCPDynamicScreens_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicScreen* ds = (PCPDynamicScreen*) value; + PCPDynamicScreen_done(ds); +} + +void PCPDynamicScreens_done(Hashtable* table) { + Hashtable_foreach(table, PCPDynamicScreens_free, NULL); +} + +void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + ds->table = InDomTable_new(host, ds->indom, ds->key); + } +} + +void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (ds->defaultEnabled == false) + continue; + const char* tab = ds->super.heading; + Settings_newDynamicScreen(settings, tab, &ds->super, &ds->table->super); + } +} + +/* called when htoprc .dynamic line is parsed for a dynamic screen */ +void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (String_eq(ss->dynamic, ds->super.name) == false) + continue; + ss->table = &ds->table->super; + } +} + +void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen) { + Vector_prune(availableColumns->items); + + bool success; + unsigned int key; + success = DynamicScreen_search(screens, screen, &key); + if (!success) + return; + + PCPDynamicScreen* dynamicScreen = Hashtable_get(screens, key); + if (!screen) + return; + + for (unsigned int j = 0; j < dynamicScreen->totalColumns; j++) { + PCPDynamicColumn* column = dynamicScreen->columns[j]; + const char* title = column->super.heading ? column->super.heading : column->super.name; + const char* text = column->super.description ? column->super.description : column->super.caption; + char description[256]; + if (text) + xSnprintf(description, sizeof(description), "%s - %s", title, text); + else + xSnprintf(description, sizeof(description), "%s", title); + Panel_add(availableColumns, (Object*) ListItem_new(description, j)); + } +} |