PCPDynamicColumn.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. /*
  2. htop - PCPDynamicColumn.c
  3. (C) 2021-2023 Sohaib Mohammed
  4. (C) 2021-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/PCPDynamicColumn.h"
  10. #include <ctype.h>
  11. #include <dirent.h>
  12. #include <errno.h>
  13. #include <pwd.h>
  14. #include <stdbool.h>
  15. #include <stdio.h>
  16. #include <stdlib.h>
  17. #include <string.h>
  18. #include "CRT.h"
  19. #include "Macros.h"
  20. #include "Platform.h"
  21. #include "Process.h"
  22. #include "ProcessTable.h"
  23. #include "RichString.h"
  24. #include "XUtils.h"
  25. #include "linux/CGroupUtils.h"
  26. #include "pcp/Metric.h"
  27. #include "pcp/PCPProcess.h"
  28. static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) {
  29. if (!column->super.name[0])
  30. return false;
  31. size_t bytes = 16 + strlen(column->super.name);
  32. char* metricName = xMalloc(bytes);
  33. xSnprintf(metricName, bytes, "htop.column.%s", column->super.name);
  34. column->metricName = metricName;
  35. column->id = columns->offset + columns->cursor;
  36. columns->cursor++;
  37. Platform_addMetric(column->id, metricName);
  38. return true;
  39. }
  40. static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) {
  41. /* pmLookupText */
  42. if (!column->super.description)
  43. Metric_lookupText(value, &column->super.description);
  44. /* lookup a dynamic metric with this name, else create */
  45. if (PCPDynamicColumn_addMetric(columns, column) == false)
  46. return;
  47. /* derived metrics in all dynamic columns for simplicity */
  48. char* error;
  49. if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) {
  50. char* note;
  51. xAsprintf(&note,
  52. "%s: failed to parse expression in %s at line %u\n%s\n",
  53. pmGetProgname(), path, line, error);
  54. free(error);
  55. errno = EINVAL;
  56. CRT_fatalError(note);
  57. free(note);
  58. }
  59. }
  60. // Ensure a valid name for use in a PCP metric name and in htoprc
  61. static bool PCPDynamicColumn_validateColumnName(char* key, const char* path, unsigned int line) {
  62. char* p = key;
  63. char* end = strrchr(key, ']');
  64. if (end) {
  65. *end = '\0';
  66. } else {
  67. fprintf(stderr,
  68. "%s: no closing brace on column name at %s line %u\n\"%s\"",
  69. pmGetProgname(), path, line, key);
  70. return false;
  71. }
  72. while (*p) {
  73. if (p == key) {
  74. if (!isalpha(*p) && *p != '_')
  75. break;
  76. } else {
  77. if (!isalnum(*p) && *p != '_')
  78. break;
  79. }
  80. p++;
  81. }
  82. if (*p != '\0') { /* badness */
  83. fprintf(stderr,
  84. "%s: invalid column name at %s line %u\n\"%s\"",
  85. pmGetProgname(), path, line, key);
  86. return false;
  87. }
  88. return true;
  89. }
  90. // Ensure a column name has not been defined previously
  91. static bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) {
  92. return DynamicColumn_search(columns->table, key, NULL) == NULL;
  93. }
  94. static PCPDynamicColumn* PCPDynamicColumn_new(PCPDynamicColumns* columns, const char* name) {
  95. PCPDynamicColumn* column = xCalloc(1, sizeof(*column));
  96. String_safeStrncpy(column->super.name, name, sizeof(column->super.name));
  97. column->super.enabled = false;
  98. column->percent = false;
  99. column->instances = false;
  100. column->defaultEnabled = true;
  101. size_t id = columns->count + LAST_PROCESSFIELD;
  102. Hashtable_put(columns->table, id, column);
  103. columns->count++;
  104. return column;
  105. }
  106. static void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* path) {
  107. FILE* file = fopen(path, "r");
  108. if (!file)
  109. return;
  110. PCPDynamicColumn* column = NULL;
  111. unsigned int lineno = 0;
  112. bool ok = true;
  113. for (;;) {
  114. char* line = String_readLine(file);
  115. if (!line)
  116. break;
  117. lineno++;
  118. /* cleanup whitespace, skip comment lines */
  119. char* trimmed = String_trim(line);
  120. free(line);
  121. if (!trimmed || !trimmed[0] || trimmed[0] == '#') {
  122. free(trimmed);
  123. continue;
  124. }
  125. size_t n;
  126. char** config = String_split(trimmed, '=', &n);
  127. free(trimmed);
  128. if (config == NULL)
  129. continue;
  130. char* key = String_trim(config[0]);
  131. char* value = n > 1 ? String_trim(config[1]) : NULL;
  132. if (key[0] == '[') { /* new section heading - i.e. new column */
  133. ok = PCPDynamicColumn_validateColumnName(key + 1, path, lineno);
  134. if (ok)
  135. ok = PCPDynamicColumn_uniqueName(key + 1, columns);
  136. if (ok)
  137. column = PCPDynamicColumn_new(columns, key + 1);
  138. } else if (value && column && String_eq(key, "caption")) {
  139. free_and_xStrdup(&column->super.caption, value);
  140. } else if (value && column && String_eq(key, "heading")) {
  141. free_and_xStrdup(&column->super.heading, value);
  142. } else if (value && column && String_eq(key, "description")) {
  143. free_and_xStrdup(&column->super.description, value);
  144. } else if (value && column && String_eq(key, "width")) {
  145. column->super.width = strtoul(value, NULL, 10);
  146. } else if (value && column && String_eq(key, "format")) {
  147. free_and_xStrdup(&column->format, value);
  148. } else if (value && column && String_eq(key, "instances")) {
  149. if (String_eq(value, "True") || String_eq(value, "true"))
  150. column->instances = true;
  151. } else if (value && column && (String_eq(key, "default") || String_eq(key, "enabled"))) {
  152. if (String_eq(value, "False") || String_eq(value, "false"))
  153. column->defaultEnabled = false;
  154. } else if (value && column && String_eq(key, "metric")) {
  155. PCPDynamicColumn_parseMetric(columns, column, path, lineno, value);
  156. }
  157. String_freeArray(config);
  158. free(value);
  159. free(key);
  160. }
  161. fclose(file);
  162. }
  163. static void PCPDynamicColumn_scanDir(PCPDynamicColumns* columns, char* path) {
  164. DIR* dir = opendir(path);
  165. if (!dir)
  166. return;
  167. struct dirent* dirent;
  168. while ((dirent = readdir(dir)) != NULL) {
  169. if (dirent->d_name[0] == '.')
  170. continue;
  171. char* file = String_cat(path, dirent->d_name);
  172. PCPDynamicColumn_parseFile(columns, file);
  173. free(file);
  174. }
  175. closedir(dir);
  176. }
  177. void PCPDynamicColumns_init(PCPDynamicColumns* columns) {
  178. const char* share = pmGetConfig("PCP_SHARE_DIR");
  179. const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR");
  180. const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
  181. const char* override = getenv("PCP_HTOP_DIR");
  182. const char* home = getenv("HOME");
  183. char* path;
  184. if (!xdgConfigHome && !home) {
  185. const struct passwd* pw = getpwuid(getuid());
  186. if (pw)
  187. home = pw->pw_dir;
  188. }
  189. columns->table = Hashtable_new(0, true);
  190. /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */
  191. if (override) {
  192. path = String_cat(override, "/columns/");
  193. PCPDynamicColumn_scanDir(columns, path);
  194. free(path);
  195. }
  196. /* next, search in home directory alongside htoprc */
  197. if (xdgConfigHome)
  198. path = String_cat(xdgConfigHome, "/htop/columns/");
  199. else if (home)
  200. path = String_cat(home, CONFIGDIR "/htop/columns/");
  201. else
  202. path = NULL;
  203. if (path) {
  204. PCPDynamicColumn_scanDir(columns, path);
  205. free(path);
  206. }
  207. /* next, search in the system columns directory */
  208. path = String_cat(sysconf, "/htop/columns/");
  209. PCPDynamicColumn_scanDir(columns, path);
  210. free(path);
  211. /* next, try the readonly system columns directory */
  212. path = String_cat(share, "/htop/columns/");
  213. PCPDynamicColumn_scanDir(columns, path);
  214. free(path);
  215. }
  216. void PCPDynamicColumn_done(PCPDynamicColumn* this) {
  217. DynamicColumn_done(&this->super);
  218. free(this->metricName);
  219. free(this->format);
  220. }
  221. static void PCPDynamicColumns_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {
  222. PCPDynamicColumn* column = (PCPDynamicColumn*) value;
  223. PCPDynamicColumn_done(column);
  224. }
  225. void PCPDynamicColumns_done(Hashtable* table) {
  226. Hashtable_foreach(table, PCPDynamicColumns_free, NULL);
  227. }
  228. static void PCPDynamicColumn_setupWidth(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {
  229. PCPDynamicColumn* column = (PCPDynamicColumn*) value;
  230. /* calculate column size based on config file and metric units */
  231. const pmDesc* desc = Metric_desc(column->id);
  232. if (column->instances || desc->type == PM_TYPE_STRING) {
  233. column->super.width = column->width;
  234. if (column->super.width == 0)
  235. column->super.width = -16;
  236. return;
  237. }
  238. if (column->format) {
  239. if (strcmp(column->format, "percent") == 0) {
  240. column->super.width = 5;
  241. return;
  242. }
  243. if (strcmp(column->format, "process") == 0) {
  244. column->super.width = Process_pidDigits;
  245. return;
  246. }
  247. }
  248. if (column->width) {
  249. column->super.width = column->width;
  250. return;
  251. }
  252. pmUnits units = desc->units;
  253. if (units.dimSpace && units.dimTime)
  254. column->super.width = 11; // Row_printRate
  255. else if (units.dimSpace)
  256. column->super.width = 5; // Row_printBytes
  257. else if (units.dimCount && units.dimTime)
  258. column->super.width = 11; // Row_printCount
  259. else if (units.dimTime)
  260. column->super.width = 8; // Row_printTime
  261. else
  262. column->super.width = 11; // Row_printCount
  263. }
  264. void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns) {
  265. Hashtable_foreach(columns->table, PCPDynamicColumn_setupWidth, NULL);
  266. }
  267. /* normalize output units to bytes and seconds */
  268. static int PCPDynamicColumn_normalize(const pmDesc* desc, const pmAtomValue* ap, double* value) {
  269. /* form normalized units based on the original metric units */
  270. pmUnits units = desc->units;
  271. if (units.dimTime)
  272. units.scaleTime = PM_TIME_SEC;
  273. if (units.dimSpace)
  274. units.scaleSpace = PM_SPACE_BYTE;
  275. if (units.dimCount)
  276. units.scaleCount = PM_COUNT_ONE;
  277. pmAtomValue atom;
  278. int sts, type = desc->type;
  279. if ((sts = pmConvScale(type, ap, &desc->units, &atom, &units)) < 0)
  280. return sts;
  281. switch (type) {
  282. case PM_TYPE_32:
  283. *value = (double) atom.l;
  284. break;
  285. case PM_TYPE_U32:
  286. *value = (double) atom.ul;
  287. break;
  288. case PM_TYPE_64:
  289. *value = (double) atom.ll;
  290. break;
  291. case PM_TYPE_U64:
  292. *value = (double) atom.ull;
  293. break;
  294. case PM_TYPE_FLOAT:
  295. *value = (double) atom.f;
  296. break;
  297. case PM_TYPE_DOUBLE:
  298. *value = atom.d;
  299. break;
  300. default:
  301. return PM_ERR_CONV;
  302. }
  303. return 0;
  304. }
  305. void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const pmDesc* desc, const void* atom) {
  306. const pmAtomValue* atomvalue = (const pmAtomValue*) atom;
  307. char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /*space*/ 1 + /*null*/ 1];
  308. int attr = CRT_colors[DEFAULT_COLOR];
  309. int width = column->super.width;
  310. int n;
  311. if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
  312. width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
  313. int abswidth = abs(width);
  314. if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) {
  315. abswidth = DYNAMIC_MAX_COLUMN_WIDTH;
  316. width = -abswidth;
  317. }
  318. if (atomvalue == NULL) {
  319. n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A");
  320. RichString_appendnAscii(str, CRT_colors[PROCESS_SHADOW], buffer, n);
  321. return;
  322. }
  323. /* deal with instance names and metrics with string values first */
  324. if (column->instances || desc->type == PM_TYPE_STRING) {
  325. char* value = NULL;
  326. char* dupd1 = NULL;
  327. if (column->instances) {
  328. attr = CRT_colors[DYNAMIC_GRAY];
  329. Metric_externalName(metric, instance, &dupd1);
  330. value = dupd1;
  331. } else {
  332. attr = CRT_colors[DYNAMIC_GREEN];
  333. value = atomvalue->cp;
  334. }
  335. if (column->format && value) {
  336. char* dupd2 = NULL;
  337. if (strcmp(column->format, "command") == 0)
  338. attr = CRT_colors[PROCESS_COMM];
  339. else if (strcmp(column->format, "process") == 0)
  340. attr = CRT_colors[PROCESS_SHADOW];
  341. else if (strcmp(column->format, "device") == 0 && strncmp(value, "/dev/", 5) == 0)
  342. value += 5;
  343. else if (strcmp(column->format, "cgroup") == 0 && (dupd2 = CGroup_filterName(value)))
  344. value = dupd2;
  345. n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value);
  346. if (dupd2)
  347. free(dupd2);
  348. } else if (value) {
  349. n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value);
  350. } else {
  351. n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A");
  352. }
  353. if (dupd1)
  354. free(dupd1);
  355. RichString_appendnAscii(str, attr, buffer, n);
  356. return;
  357. }
  358. /* deal with any numeric value - first, normalize units to bytes/seconds */
  359. double value;
  360. if (PCPDynamicColumn_normalize(desc, atomvalue, &value) < 0) {
  361. n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "no conv");
  362. RichString_appendnAscii(str, CRT_colors[METER_VALUE_ERROR], buffer, n);
  363. return;
  364. }
  365. if (column->format) {
  366. if (strcmp(column->format, "percent") == 0) {
  367. n = Row_printPercentage(value, buffer, sizeof(buffer), width, &attr);
  368. RichString_appendnAscii(str, attr, buffer, n);
  369. return;
  370. }
  371. if (strcmp(column->format, "process") == 0) {
  372. n = xSnprintf(buffer, sizeof(buffer), "%*d ", Row_pidDigits, (int)value);
  373. RichString_appendnAscii(str, attr, buffer, n);
  374. return;
  375. }
  376. }
  377. /* width overrides unit suffix and coloring; too complex for a corner case */
  378. if (column->width) {
  379. if (value - (unsigned long long)value > 0) /* display floating point */
  380. n = xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, value);
  381. else /* display as integer */
  382. n = xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long)value);
  383. RichString_appendnAscii(str, CRT_colors[PROCESS], buffer, n);
  384. return;
  385. }
  386. bool coloring = settings->highlightMegabytes;
  387. pmUnits units = desc->units;
  388. if (units.dimSpace && units.dimTime)
  389. Row_printRate(str, value, coloring);
  390. else if (units.dimSpace)
  391. Row_printBytes(str, value, coloring);
  392. else if (units.dimCount)
  393. Row_printCount(str, value, coloring);
  394. else if (units.dimTime)
  395. Row_printTime(str, value / 10 /* hundreds of a second */, coloring);
  396. else
  397. Row_printCount(str, value, 0); /* e.g. PID */
  398. }
  399. void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) {
  400. const Settings* settings = proc->super.host->settings;
  401. const PCPProcess* pp = (const PCPProcess*) proc;
  402. const pmDesc* desc = Metric_desc(this->id);
  403. pid_t pid = Process_getPid(proc);
  404. pmAtomValue atom;
  405. pmAtomValue* ap = &atom;
  406. if (!Metric_instance(this->id, pid, pp->offset, ap, desc->type))
  407. ap = NULL;
  408. PCPDynamicColumn_writeAtomValue(this, str, settings, this->id, pid, desc, ap);
  409. }
  410. int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) {
  411. const Process* proc = &p1->super;
  412. const Settings* settings = proc->super.host->settings;
  413. const PCPDynamicColumn* column = Hashtable_get(settings->dynamicColumns, key);
  414. if (!column)
  415. return -1;
  416. size_t metric = column->id;
  417. unsigned int type = Metric_type(metric);
  418. pmAtomValue atom1 = {0}, atom2 = {0};
  419. if (!Metric_instance(metric, Process_getPid(&p1->super), p1->offset, &atom1, type) ||
  420. !Metric_instance(metric, Process_getPid(&p2->super), p2->offset, &atom2, type)) {
  421. if (type == PM_TYPE_STRING) {
  422. free(atom1.cp);
  423. free(atom2.cp);
  424. }
  425. return -1;
  426. }
  427. switch (type) {
  428. case PM_TYPE_STRING: {
  429. int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp);
  430. free(atom2.cp);
  431. free(atom1.cp);
  432. return cmp;
  433. }
  434. case PM_TYPE_32:
  435. return SPACESHIP_NUMBER(atom2.l, atom1.l);
  436. case PM_TYPE_U32:
  437. return SPACESHIP_NUMBER(atom2.ul, atom1.ul);
  438. case PM_TYPE_64:
  439. return SPACESHIP_NUMBER(atom2.ll, atom1.ll);
  440. case PM_TYPE_U64:
  441. return SPACESHIP_NUMBER(atom2.ull, atom1.ull);
  442. case PM_TYPE_FLOAT:
  443. return compareRealNumbers(atom2.f, atom1.f);
  444. case PM_TYPE_DOUBLE:
  445. return compareRealNumbers(atom2.d, atom1.d);
  446. default:
  447. break;
  448. }
  449. return -1;
  450. }