PCPDynamicScreen.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /*
  2. htop - PCPDynamicScreen.c
  3. (C) 2022 Sohaib Mohammed
  4. (C) 2022-2023 htop dev team
  5. Released under the GNU GPLv2+, see the COPYING file
  6. in the source distribution for its full text.
  7. */
  8. #include "config.h" // IWYU pragma: keep
  9. #include "pcp/PCPDynamicScreen.h"
  10. #include <ctype.h>
  11. #include <dirent.h>
  12. #include <stdbool.h>
  13. #include <pcp/pmapi.h>
  14. #include "AvailableColumnsPanel.h"
  15. #include "Macros.h"
  16. #include "Platform.h"
  17. #include "Settings.h"
  18. #include "XUtils.h"
  19. #include "pcp/InDomTable.h"
  20. #include "pcp/PCPDynamicColumn.h"
  21. static char* formatFields(PCPDynamicScreen* screen) {
  22. char* columns = strdup("");
  23. for (size_t j = 0; j < screen->totalColumns; j++) {
  24. const PCPDynamicColumn* column = screen->columns[j];
  25. if (column->super.enabled == false)
  26. continue;
  27. char* prefix = columns;
  28. xAsprintf(&columns, "%s Dynamic(%s)", prefix, column->super.name);
  29. free(prefix);
  30. }
  31. return columns;
  32. }
  33. static void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, PCPDynamicColumns* columns) {
  34. for (size_t i = 0; i < screens->count; i++) {
  35. PCPDynamicScreen* screen = Hashtable_get(screens->table, i);
  36. if (!screen)
  37. return;
  38. /* setup default fields (columns) based on configuration */
  39. for (size_t j = 0; j < screen->totalColumns; j++) {
  40. PCPDynamicColumn* column = screen->columns[j];
  41. column->id = columns->offset + columns->cursor;
  42. columns->cursor++;
  43. Platform_addMetric(column->id, column->metricName);
  44. size_t id = columns->count + LAST_PROCESSFIELD;
  45. Hashtable_put(columns->table, id, column);
  46. columns->count++;
  47. if (j == 0) {
  48. const pmDesc* desc = Metric_desc(column->id);
  49. assert(desc->indom != PM_INDOM_NULL);
  50. screen->indom = desc->indom;
  51. screen->key = column->id;
  52. }
  53. }
  54. screen->super.columnKeys = formatFields(screen);
  55. }
  56. }
  57. static PCPDynamicColumn* PCPDynamicScreen_lookupMetric(PCPDynamicScreen* screen, const char* name) {
  58. PCPDynamicColumn* column = NULL;
  59. size_t bytes = strlen(name) + strlen(screen->super.name) + 1; /* colon */
  60. if (bytes >= sizeof(column->super.name))
  61. return NULL;
  62. bytes += 16; /* prefix, dots and terminator */
  63. char* metricName = xMalloc(bytes);
  64. xSnprintf(metricName, bytes, "htop.screen.%s.%s", screen->super.name, name);
  65. for (size_t i = 0; i < screen->totalColumns; i++) {
  66. column = screen->columns[i];
  67. if (String_eq(column->metricName, metricName)) {
  68. free(metricName);
  69. return column;
  70. }
  71. }
  72. /* not an existing column in this screen - create it and add to the list */
  73. column = xCalloc(1, sizeof(PCPDynamicColumn));
  74. xSnprintf(column->super.name, sizeof(column->super.name), "%s:%s", screen->super.name, name);
  75. column->super.table = &screen->table->super;
  76. column->metricName = metricName;
  77. column->super.enabled = true;
  78. size_t n = screen->totalColumns + 1;
  79. screen->columns = xReallocArray(screen->columns, n, sizeof(PCPDynamicColumn*));
  80. screen->columns[n - 1] = column;
  81. screen->totalColumns = n;
  82. return column;
  83. }
  84. static void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* path, unsigned int line, char* key, char* value) {
  85. PCPDynamicColumn* column;
  86. char* p;
  87. if ((p = strchr(key, '.')) == NULL)
  88. return;
  89. *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */
  90. /* lookup a dynamic column with this name, else create */
  91. column = PCPDynamicScreen_lookupMetric(screen, key);
  92. if (String_eq(p, "metric")) {
  93. char* error;
  94. if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) {
  95. char* note;
  96. xAsprintf(&note,
  97. "%s: failed to parse expression in %s at line %u\n%s\n",
  98. pmGetProgname(), path, line, error);
  99. free(error);
  100. errno = EINVAL;
  101. CRT_fatalError(note);
  102. free(note);
  103. }
  104. /* pmLookupText - add optional metric help text */
  105. if (!column->super.description && !column->instances)
  106. Metric_lookupText(value, &column->super.description);
  107. } else {
  108. /* this is a property of a dynamic column - the column expression */
  109. /* may not have been observed yet; i.e. we allow for any ordering */
  110. if (String_eq(p, "caption")) {
  111. free_and_xStrdup(&column->super.caption, value);
  112. } else if (String_eq(p, "heading")) {
  113. free_and_xStrdup(&column->super.heading, value);
  114. } else if (String_eq(p, "description")) {
  115. free_and_xStrdup(&column->super.description, value);
  116. } else if (String_eq(p, "width")) {
  117. column->width = strtoul(value, NULL, 10);
  118. } else if (String_eq(p, "format")) {
  119. free_and_xStrdup(&column->format, value);
  120. } else if (String_eq(p, "instances")) {
  121. if (String_eq(value, "True") || String_eq(value, "true"))
  122. column->instances = true;
  123. free_and_xStrdup(&column->super.description, screen->super.caption);
  124. } else if (String_eq(p, "default")) { /* displayed by default */
  125. if (String_eq(value, "False") || String_eq(value, "false"))
  126. column->defaultEnabled = column->super.enabled = false;
  127. }
  128. }
  129. }
  130. static bool PCPDynamicScreen_validateScreenName(char* key, const char* path, unsigned int line) {
  131. char* p = key;
  132. char* end = strrchr(key, ']');
  133. if (end) {
  134. *end = '\0';
  135. } else {
  136. fprintf(stderr,
  137. "%s: no closing brace on screen name at %s line %u\n\"%s\"",
  138. pmGetProgname(), path, line, key);
  139. return false;
  140. }
  141. while (*p) {
  142. if (p == key) {
  143. if (!isalpha(*p) && *p != '_')
  144. break;
  145. } else {
  146. if (!isalnum(*p) && *p != '_')
  147. break;
  148. }
  149. p++;
  150. }
  151. if (*p != '\0') { /* badness */
  152. fprintf(stderr,
  153. "%s: invalid screen name at %s line %u\n\"%s\"",
  154. pmGetProgname(), path, line, key);
  155. return false;
  156. }
  157. return true;
  158. }
  159. /* Ensure a screen name has not been defined previously */
  160. static bool PCPDynamicScreen_uniqueName(char* key, PCPDynamicScreens* screens) {
  161. return !DynamicScreen_search(screens->table, key, NULL);
  162. }
  163. static PCPDynamicScreen* PCPDynamicScreen_new(PCPDynamicScreens* screens, const char* name) {
  164. PCPDynamicScreen* screen = xCalloc(1, sizeof(*screen));
  165. String_safeStrncpy(screen->super.name, name, sizeof(screen->super.name));
  166. screen->defaultEnabled = true;
  167. size_t id = screens->count;
  168. Hashtable_put(screens->table, id, screen);
  169. screens->count++;
  170. return screen;
  171. }
  172. static void PCPDynamicScreen_parseFile(PCPDynamicScreens* screens, const char* path) {
  173. FILE* file = fopen(path, "r");
  174. if (!file)
  175. return;
  176. PCPDynamicScreen* screen = NULL;
  177. unsigned int lineno = 0;
  178. bool ok = true;
  179. for (;;) {
  180. char* line = String_readLine(file);
  181. if (!line)
  182. break;
  183. lineno++;
  184. /* cleanup whitespace, skip comment lines */
  185. char* trimmed = String_trim(line);
  186. free(line);
  187. if (!trimmed || !trimmed[0] || trimmed[0] == '#') {
  188. free(trimmed);
  189. continue;
  190. }
  191. size_t n;
  192. char** config = String_split(trimmed, '=', &n);
  193. free(trimmed);
  194. if (config == NULL)
  195. continue;
  196. char* key = String_trim(config[0]);
  197. char* value = n > 1 ? String_trim(config[1]) : NULL;
  198. if (key[0] == '[') { /* new section name - i.e. new screen */
  199. ok = PCPDynamicScreen_validateScreenName(key + 1, path, lineno);
  200. if (ok)
  201. ok = PCPDynamicScreen_uniqueName(key + 1, screens);
  202. if (ok)
  203. screen = PCPDynamicScreen_new(screens, key + 1);
  204. if (pmDebugOptions.appl0)
  205. fprintf(stderr, "[%s] screen: %s\n", path, key + 1);
  206. } else if (!ok) {
  207. ; /* skip this one, we're looking for a new header */
  208. } else if (!value || !screen) {
  209. ; /* skip this one as we always need value strings */
  210. } else if (String_eq(key, "heading")) {
  211. free_and_xStrdup(&screen->super.heading, value);
  212. } else if (String_eq(key, "caption")) {
  213. free_and_xStrdup(&screen->super.caption, value);
  214. } else if (String_eq(key, "sortKey")) {
  215. free_and_xStrdup(&screen->super.sortKey, value);
  216. } else if (String_eq(key, "sortDirection")) {
  217. screen->super.direction = strtoul(value, NULL, 10);
  218. } else if (String_eq(key, "default") || String_eq(key, "enabled")) {
  219. if (String_eq(value, "False") || String_eq(value, "false"))
  220. screen->defaultEnabled = false;
  221. else if (String_eq(value, "True") || String_eq(value, "true"))
  222. screen->defaultEnabled = true; /* also default */
  223. } else {
  224. PCPDynamicScreen_parseColumn(screen, path, lineno, key, value);
  225. }
  226. String_freeArray(config);
  227. free(value);
  228. free(key);
  229. }
  230. fclose(file);
  231. }
  232. static void PCPDynamicScreen_scanDir(PCPDynamicScreens* screens, char* path) {
  233. DIR* dir = opendir(path);
  234. if (!dir)
  235. return;
  236. struct dirent* dirent;
  237. while ((dirent = readdir(dir)) != NULL) {
  238. if (dirent->d_name[0] == '.')
  239. continue;
  240. char* file = String_cat(path, dirent->d_name);
  241. PCPDynamicScreen_parseFile(screens, file);
  242. free(file);
  243. }
  244. closedir(dir);
  245. }
  246. void PCPDynamicScreens_init(PCPDynamicScreens* screens, PCPDynamicColumns* columns) {
  247. const char* share = pmGetConfig("PCP_SHARE_DIR");
  248. const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR");
  249. const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
  250. const char* override = getenv("PCP_HTOP_DIR");
  251. const char* home = getenv("HOME");
  252. char* path;
  253. screens->table = Hashtable_new(0, true);
  254. /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */
  255. if (override) {
  256. path = String_cat(override, "/screens/");
  257. PCPDynamicScreen_scanDir(screens, path);
  258. free(path);
  259. }
  260. /* next, search in home directory alongside htoprc */
  261. if (xdgConfigHome)
  262. path = String_cat(xdgConfigHome, "/htop/screens/");
  263. else if (home)
  264. path = String_cat(home, CONFIGDIR "/htop/screens/");
  265. else
  266. path = NULL;
  267. if (path) {
  268. PCPDynamicScreen_scanDir(screens, path);
  269. free(path);
  270. }
  271. /* next, search in the system screens directory */
  272. path = String_cat(sysconf, "/htop/screens/");
  273. PCPDynamicScreen_scanDir(screens, path);
  274. free(path);
  275. /* next, try the readonly system screens directory */
  276. path = String_cat(share, "/htop/screens/");
  277. PCPDynamicScreen_scanDir(screens, path);
  278. free(path);
  279. /* establish internal metric identifier mappings */
  280. PCPDynamicScreens_appendDynamicColumns(screens, columns);
  281. }
  282. static void PCPDynamicScreen_done(PCPDynamicScreen* ds) {
  283. DynamicScreen_done(&ds->super);
  284. Object_delete(ds->table);
  285. free(ds->columns);
  286. }
  287. static void PCPDynamicScreens_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {
  288. PCPDynamicScreen* ds = (PCPDynamicScreen*) value;
  289. PCPDynamicScreen_done(ds);
  290. }
  291. void PCPDynamicScreens_done(Hashtable* table) {
  292. Hashtable_foreach(table, PCPDynamicScreens_free, NULL);
  293. }
  294. void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host) {
  295. PCPDynamicScreen* ds;
  296. for (size_t i = 0; i < screens->count; i++) {
  297. if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)
  298. continue;
  299. ds->table = InDomTable_new(host, ds->indom, ds->key);
  300. }
  301. }
  302. void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings) {
  303. PCPDynamicScreen* ds;
  304. for (size_t i = 0; i < screens->count; i++) {
  305. if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)
  306. continue;
  307. if (ds->defaultEnabled == false)
  308. continue;
  309. const char* tab = ds->super.heading;
  310. Settings_newDynamicScreen(settings, tab, &ds->super, &ds->table->super);
  311. }
  312. }
  313. /* called when htoprc .dynamic line is parsed for a dynamic screen */
  314. void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss) {
  315. PCPDynamicScreen* ds;
  316. for (size_t i = 0; i < screens->count; i++) {
  317. if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)
  318. continue;
  319. if (String_eq(ss->dynamic, ds->super.name) == false)
  320. continue;
  321. ss->table = &ds->table->super;
  322. }
  323. }
  324. void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen) {
  325. Vector_prune(availableColumns->items);
  326. bool success;
  327. unsigned int key;
  328. success = DynamicScreen_search(screens, screen, &key);
  329. if (!success)
  330. return;
  331. PCPDynamicScreen* dynamicScreen = Hashtable_get(screens, key);
  332. if (!screen)
  333. return;
  334. for (unsigned int j = 0; j < dynamicScreen->totalColumns; j++) {
  335. PCPDynamicColumn* column = dynamicScreen->columns[j];
  336. const char* title = column->super.heading ? column->super.heading : column->super.name;
  337. const char* text = column->super.description ? column->super.description : column->super.caption;
  338. char description[256];
  339. if (text)
  340. xSnprintf(description, sizeof(description), "%s - %s", title, text);
  341. else
  342. xSnprintf(description, sizeof(description), "%s", title);
  343. Panel_add(availableColumns, (Object*) ListItem_new(description, j));
  344. }
  345. }