PCPDynamicMeter.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /*
  2. htop - PCPDynamicMeter.c
  3. (C) 2021 htop dev team
  4. (C) 2021 Red Hat, Inc.
  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/PCPDynamicMeter.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 <pcp/pmapi.h>
  19. #include "Macros.h"
  20. #include "Platform.h"
  21. #include "RichString.h"
  22. #include "XUtils.h"
  23. #include "pcp/Metric.h"
  24. static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) {
  25. size_t bytes = 16 + strlen(meter->super.name) + strlen(name);
  26. char* metricName = xMalloc(bytes);
  27. xSnprintf(metricName, bytes, "htop.meter.%s.%s", meter->super.name, name);
  28. PCPDynamicMetric* metric;
  29. for (size_t i = 0; i < meter->totalMetrics; i++) {
  30. metric = &meter->metrics[i];
  31. if (String_eq(metric->name, metricName)) {
  32. free(metricName);
  33. return metric;
  34. }
  35. }
  36. /* not an existing metric in this meter - add it */
  37. size_t n = meter->totalMetrics + 1;
  38. meter->metrics = xReallocArray(meter->metrics, n, sizeof(PCPDynamicMetric));
  39. meter->totalMetrics = n;
  40. metric = &meter->metrics[n - 1];
  41. memset(metric, 0, sizeof(PCPDynamicMetric));
  42. metric->name = metricName;
  43. metric->label = String_cat(name, ": ");
  44. metric->id = meters->offset + meters->cursor;
  45. meters->cursor++;
  46. Platform_addMetric(metric->id, metricName);
  47. return metric;
  48. }
  49. static void PCPDynamicMeter_parseMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* path, unsigned int line, char* key, char* value) {
  50. PCPDynamicMetric* metric;
  51. char* p;
  52. if ((p = strchr(key, '.')) == NULL)
  53. return;
  54. *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */
  55. if (String_eq(p, "metric")) {
  56. /* lookup a dynamic metric with this name, else create */
  57. metric = PCPDynamicMeter_lookupMetric(meters, meter, key);
  58. /* use derived metrics in dynamic meters for simplicity */
  59. char* error;
  60. if (pmRegisterDerivedMetric(metric->name, value, &error) < 0) {
  61. char* note;
  62. xAsprintf(&note,
  63. "%s: failed to parse expression in %s at line %u\n%s\n%s",
  64. pmGetProgname(), path, line, error, pmGetProgname());
  65. free(error);
  66. errno = EINVAL;
  67. CRT_fatalError(note);
  68. free(note);
  69. }
  70. } else {
  71. /* this is a property of a dynamic metric - the metric expression */
  72. /* may not have been observed yet - i.e. we allow for any ordering */
  73. metric = PCPDynamicMeter_lookupMetric(meters, meter, key);
  74. if (String_eq(p, "color")) {
  75. if (String_eq(value, "gray"))
  76. metric->color = DYNAMIC_GRAY;
  77. else if (String_eq(value, "darkgray"))
  78. metric->color = DYNAMIC_DARKGRAY;
  79. else if (String_eq(value, "red"))
  80. metric->color = DYNAMIC_RED;
  81. else if (String_eq(value, "green"))
  82. metric->color = DYNAMIC_GREEN;
  83. else if (String_eq(value, "blue"))
  84. metric->color = DYNAMIC_BLUE;
  85. else if (String_eq(value, "cyan"))
  86. metric->color = DYNAMIC_CYAN;
  87. else if (String_eq(value, "magenta"))
  88. metric->color = DYNAMIC_MAGENTA;
  89. else if (String_eq(value, "yellow"))
  90. metric->color = DYNAMIC_YELLOW;
  91. else if (String_eq(value, "white"))
  92. metric->color = DYNAMIC_WHITE;
  93. } else if (String_eq(p, "label")) {
  94. char* label = String_cat(value, ": ");
  95. free_and_xStrdup(&metric->label, label);
  96. free(label);
  97. } else if (String_eq(p, "suffix")) {
  98. free_and_xStrdup(&metric->suffix, value);
  99. }
  100. }
  101. }
  102. // Ensure a valid name for use in a PCP metric name and in htoprc
  103. static bool PCPDynamicMeter_validateMeterName(char* key, const char* path, unsigned int line) {
  104. char* p = key;
  105. char* end = strrchr(key, ']');
  106. if (end) {
  107. *end = '\0';
  108. } else {
  109. fprintf(stderr,
  110. "%s: no closing brace on meter name at %s line %u\n\"%s\"\n",
  111. pmGetProgname(), path, line, key);
  112. return false;
  113. }
  114. while (*p) {
  115. if (p == key) {
  116. if (!isalpha(*p) && *p != '_')
  117. break;
  118. } else {
  119. if (!isalnum(*p) && *p != '_')
  120. break;
  121. }
  122. p++;
  123. }
  124. if (*p != '\0') { /* badness */
  125. fprintf(stderr,
  126. "%s: invalid meter name at %s line %u\n\"%s\"\n",
  127. pmGetProgname(), path, line, key);
  128. return false;
  129. }
  130. return true;
  131. }
  132. // Ensure a meter name has not been defined previously
  133. static bool PCPDynamicMeter_uniqueName(char* key, PCPDynamicMeters* meters) {
  134. return !DynamicMeter_search(meters->table, key, NULL);
  135. }
  136. static PCPDynamicMeter* PCPDynamicMeter_new(PCPDynamicMeters* meters, const char* name) {
  137. PCPDynamicMeter* meter = xCalloc(1, sizeof(*meter));
  138. String_safeStrncpy(meter->super.name, name, sizeof(meter->super.name));
  139. Hashtable_put(meters->table, ++meters->count, meter);
  140. return meter;
  141. }
  142. static void PCPDynamicMeter_parseFile(PCPDynamicMeters* meters, const char* path) {
  143. FILE* file = fopen(path, "r");
  144. if (!file)
  145. return;
  146. PCPDynamicMeter* meter = NULL;
  147. unsigned int lineno = 0;
  148. bool ok = true;
  149. for (;;) {
  150. char* line = String_readLine(file);
  151. if (!line)
  152. break;
  153. lineno++;
  154. /* cleanup whitespace, skip comment lines */
  155. char* trimmed = String_trim(line);
  156. free(line);
  157. if (trimmed[0] == '#' || trimmed[0] == '\0') {
  158. free(trimmed);
  159. continue;
  160. }
  161. size_t n;
  162. char** config = String_split(trimmed, '=', &n);
  163. free(trimmed);
  164. if (config == NULL)
  165. continue;
  166. char* key = String_trim(config[0]);
  167. char* value = n > 1 ? String_trim(config[1]) : NULL;
  168. if (key[0] == '[') { /* new section heading - i.e. new meter */
  169. ok = PCPDynamicMeter_validateMeterName(key + 1, path, lineno);
  170. if (ok)
  171. ok = PCPDynamicMeter_uniqueName(key + 1, meters);
  172. if (ok)
  173. meter = PCPDynamicMeter_new(meters, key + 1);
  174. } else if (!ok) {
  175. ; /* skip this one, we're looking for a new header */
  176. } else if (value && meter && String_eq(key, "caption")) {
  177. char* caption = String_cat(value, ": ");
  178. if (caption) {
  179. free_and_xStrdup(&meter->super.caption, caption);
  180. free(caption);
  181. caption = NULL;
  182. }
  183. } else if (value && meter && String_eq(key, "description")) {
  184. free_and_xStrdup(&meter->super.description, value);
  185. } else if (value && meter && String_eq(key, "type")) {
  186. if (String_eq(config[1], "bar"))
  187. meter->super.type = BAR_METERMODE;
  188. else if (String_eq(config[1], "text"))
  189. meter->super.type = TEXT_METERMODE;
  190. else if (String_eq(config[1], "graph"))
  191. meter->super.type = GRAPH_METERMODE;
  192. else if (String_eq(config[1], "led"))
  193. meter->super.type = LED_METERMODE;
  194. } else if (value && meter && String_eq(key, "maximum")) {
  195. meter->super.maximum = strtod(value, NULL);
  196. } else if (value && meter) {
  197. PCPDynamicMeter_parseMetric(meters, meter, path, lineno, key, value);
  198. }
  199. String_freeArray(config);
  200. free(value);
  201. free(key);
  202. }
  203. fclose(file);
  204. }
  205. static void PCPDynamicMeter_scanDir(PCPDynamicMeters* meters, char* path) {
  206. DIR* dir = opendir(path);
  207. if (!dir)
  208. return;
  209. struct dirent* dirent;
  210. while ((dirent = readdir(dir)) != NULL) {
  211. if (dirent->d_name[0] == '.')
  212. continue;
  213. char* file = String_cat(path, dirent->d_name);
  214. PCPDynamicMeter_parseFile(meters, file);
  215. free(file);
  216. }
  217. closedir(dir);
  218. }
  219. void PCPDynamicMeters_init(PCPDynamicMeters* meters) {
  220. const char* share = pmGetConfig("PCP_SHARE_DIR");
  221. const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR");
  222. const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
  223. const char* override = getenv("PCP_HTOP_DIR");
  224. const char* home = getenv("HOME");
  225. char* path;
  226. if (!xdgConfigHome && !home) {
  227. const struct passwd* pw = getpwuid(getuid());
  228. if (pw)
  229. home = pw->pw_dir;
  230. }
  231. meters->table = Hashtable_new(0, true);
  232. /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */
  233. if (override) {
  234. path = String_cat(override, "/meters/");
  235. PCPDynamicMeter_scanDir(meters, path);
  236. free(path);
  237. }
  238. /* next, search in home directory alongside htoprc */
  239. if (xdgConfigHome)
  240. path = String_cat(xdgConfigHome, "/htop/meters/");
  241. else if (home)
  242. path = String_cat(home, CONFIGDIR "/htop/meters/");
  243. else
  244. path = NULL;
  245. if (path) {
  246. PCPDynamicMeter_scanDir(meters, path);
  247. free(path);
  248. }
  249. /* next, search in the system meters directory */
  250. path = String_cat(sysconf, "/htop/meters/");
  251. PCPDynamicMeter_scanDir(meters, path);
  252. free(path);
  253. /* next, try the readonly system meters directory */
  254. path = String_cat(share, "/htop/meters/");
  255. PCPDynamicMeter_scanDir(meters, path);
  256. free(path);
  257. }
  258. static void PCPDynamicMeter_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {
  259. PCPDynamicMeter* meter = (PCPDynamicMeter*) value;
  260. for (size_t i = 0; i < meter->totalMetrics; i++) {
  261. free(meter->metrics[i].name);
  262. free(meter->metrics[i].label);
  263. free(meter->metrics[i].suffix);
  264. }
  265. free(meter->metrics);
  266. free(meter->super.caption);
  267. free(meter->super.description);
  268. }
  269. void PCPDynamicMeters_done(Hashtable* table) {
  270. Hashtable_foreach(table, PCPDynamicMeter_free, NULL);
  271. }
  272. void PCPDynamicMeter_enable(PCPDynamicMeter* this) {
  273. for (size_t i = 0; i < this->totalMetrics; i++)
  274. Metric_enable(this->metrics[i].id, true);
  275. }
  276. void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) {
  277. char* buffer = meter->txtBuffer;
  278. size_t size = sizeof(meter->txtBuffer);
  279. size_t bytes = 0;
  280. for (size_t i = 0; i < this->totalMetrics; i++) {
  281. if (i > 0 && bytes < size - 1)
  282. buffer[bytes++] = '/'; /* separator */
  283. PCPDynamicMetric* metric = &this->metrics[i];
  284. const pmDesc* desc = Metric_desc(metric->id);
  285. pmAtomValue atom, raw;
  286. if (!Metric_values(metric->id, &raw, 1, desc->type)) {
  287. bytes--; /* clear the separator */
  288. continue;
  289. }
  290. pmUnits conv = desc->units; /* convert to canonical units */
  291. if (conv.dimSpace)
  292. conv.scaleSpace = PM_SPACE_KBYTE;
  293. if (conv.dimTime)
  294. conv.scaleTime = PM_TIME_SEC;
  295. if (desc->type == PM_TYPE_STRING)
  296. atom = raw;
  297. else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0) {
  298. bytes--; /* clear the separator */
  299. continue;
  300. }
  301. size_t saved = bytes;
  302. switch (desc->type) {
  303. case PM_TYPE_STRING:
  304. bytes += xSnprintf(buffer + bytes, size - bytes, "%s", atom.cp);
  305. free(atom.cp);
  306. break;
  307. case PM_TYPE_32:
  308. bytes += conv.dimSpace ?
  309. Meter_humanUnit(buffer + bytes, (double) atom.l, size - bytes) :
  310. xSnprintf(buffer + bytes, size - bytes, "%d", atom.l);
  311. break;
  312. case PM_TYPE_U32:
  313. bytes += conv.dimSpace ?
  314. Meter_humanUnit(buffer + bytes, (double) atom.ul, size - bytes) :
  315. xSnprintf(buffer + bytes, size - bytes, "%u", atom.ul);
  316. break;
  317. case PM_TYPE_64:
  318. bytes += conv.dimSpace ?
  319. Meter_humanUnit(buffer + bytes, (double) atom.ll, size - bytes) :
  320. xSnprintf(buffer + bytes, size - bytes, "%lld", (long long) atom.ll);
  321. break;
  322. case PM_TYPE_U64:
  323. bytes += conv.dimSpace ?
  324. Meter_humanUnit(buffer + bytes, (double) atom.ull, size - bytes) :
  325. xSnprintf(buffer + bytes, size - bytes, "%llu", (unsigned long long) atom.ull);
  326. break;
  327. case PM_TYPE_FLOAT:
  328. bytes += conv.dimSpace ?
  329. Meter_humanUnit(buffer + bytes, (double) atom.f, size - bytes) :
  330. xSnprintf(buffer + bytes, size - bytes, "%.2f", (double) atom.f);
  331. break;
  332. case PM_TYPE_DOUBLE:
  333. bytes += conv.dimSpace ?
  334. Meter_humanUnit(buffer + bytes, atom.d, size - bytes) :
  335. xSnprintf(buffer + bytes, size - bytes, "%.2f", atom.d);
  336. break;
  337. default:
  338. break;
  339. }
  340. if (saved != bytes && metric->suffix)
  341. bytes += xSnprintf(buffer + bytes, size - bytes, "%s", metric->suffix);
  342. }
  343. if (!bytes)
  344. xSnprintf(buffer, size, "no data");
  345. }
  346. void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* meter, RichString* out) {
  347. int nodata = 1;
  348. for (size_t i = 0; i < this->totalMetrics; i++) {
  349. PCPDynamicMetric* metric = &this->metrics[i];
  350. const pmDesc* desc = Metric_desc(metric->id);
  351. pmAtomValue atom, raw;
  352. char buffer[64];
  353. if (!Metric_values(metric->id, &raw, 1, desc->type))
  354. continue;
  355. pmUnits conv = desc->units; /* convert to canonical units */
  356. if (conv.dimSpace)
  357. conv.scaleSpace = PM_SPACE_KBYTE;
  358. if (conv.dimTime)
  359. conv.scaleTime = PM_TIME_SEC;
  360. if (desc->type == PM_TYPE_STRING)
  361. atom = raw;
  362. else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0)
  363. continue;
  364. nodata = 0; /* we will use this metric so *some* data will be added */
  365. if (i > 0)
  366. RichString_appendnAscii(out, CRT_colors[metric->color], " ", 1);
  367. if (metric->label)
  368. RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->label);
  369. int len = 0;
  370. switch (desc->type) {
  371. case PM_TYPE_STRING:
  372. len = xSnprintf(buffer, sizeof(buffer), "%s", atom.cp);
  373. free(atom.cp);
  374. break;
  375. case PM_TYPE_32:
  376. len = conv.dimSpace ?
  377. Meter_humanUnit(buffer, (double) atom.l, sizeof(buffer)) :
  378. xSnprintf(buffer, sizeof(buffer), "%d", atom.l);
  379. break;
  380. case PM_TYPE_U32:
  381. len = conv.dimSpace ?
  382. Meter_humanUnit(buffer, (double) atom.ul, sizeof(buffer)) :
  383. xSnprintf(buffer, sizeof(buffer), "%u", atom.ul);
  384. break;
  385. case PM_TYPE_64:
  386. len = conv.dimSpace ?
  387. Meter_humanUnit(buffer, (double) atom.ll, sizeof(buffer)) :
  388. xSnprintf(buffer, sizeof(buffer), "%lld", (long long) atom.ll);
  389. break;
  390. case PM_TYPE_U64:
  391. len = conv.dimSpace ?
  392. Meter_humanUnit(buffer, (double) atom.ull, sizeof(buffer)) :
  393. xSnprintf(buffer, sizeof(buffer), "%llu", (unsigned long long) atom.ull);
  394. break;
  395. case PM_TYPE_FLOAT:
  396. len = conv.dimSpace ?
  397. Meter_humanUnit(buffer, (double) atom.f, sizeof(buffer)) :
  398. xSnprintf(buffer, sizeof(buffer), "%.2f", (double) atom.f);
  399. break;
  400. case PM_TYPE_DOUBLE:
  401. len = conv.dimSpace ?
  402. Meter_humanUnit(buffer, atom.d, sizeof(buffer)) :
  403. xSnprintf(buffer, sizeof(buffer), "%.2f", atom.d);
  404. break;
  405. default:
  406. break;
  407. }
  408. if (len) {
  409. RichString_appendnAscii(out, CRT_colors[metric->color], buffer, len);
  410. if (metric->suffix)
  411. RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->suffix);
  412. }
  413. }
  414. if (nodata)
  415. RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
  416. }