You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
407 lines
13 KiB
407 lines
13 KiB
/* |
|
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)); |
|
} |
|
}
|
|
|