summaryrefslogtreecommitdiffstats
path: root/pcp/PCPDynamicScreen.c
diff options
context:
space:
mode:
Diffstat (limited to 'pcp/PCPDynamicScreen.c')
-rw-r--r--pcp/PCPDynamicScreen.c407
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(&note,
+ "%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));
+ }
+}