Row.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. /*
  2. htop - Row.c
  3. (C) 2004-2015 Hisham H. Muhammad
  4. (C) 2020-2023 Red Hat, Inc. All Rights Reserved.
  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 "Row.h"
  10. #include <assert.h>
  11. #include <limits.h>
  12. #include <math.h>
  13. #include <stdbool.h>
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #include "CRT.h"
  18. #include "DynamicColumn.h"
  19. #include "Hashtable.h"
  20. #include "Machine.h"
  21. #include "Macros.h"
  22. #include "Process.h"
  23. #include "RichString.h"
  24. #include "Settings.h"
  25. #include "XUtils.h"
  26. int Row_pidDigits = ROW_MIN_PID_DIGITS;
  27. int Row_uidDigits = ROW_MIN_UID_DIGITS;
  28. void Row_init(Row* this, const Machine* host) {
  29. this->host = host;
  30. this->tag = false;
  31. this->showChildren = true;
  32. this->show = true;
  33. this->wasShown = false;
  34. this->updated = false;
  35. }
  36. void Row_done(Row* this) {
  37. assert(this != NULL);
  38. (void) this;
  39. }
  40. static inline bool Row_isNew(const Row* this) {
  41. const Machine* host = this->host;
  42. if (host->monotonicMs < this->seenStampMs)
  43. return false;
  44. const Settings* settings = host->settings;
  45. return host->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)settings->highlightDelaySecs;
  46. }
  47. static inline bool Row_isTomb(const Row* this) {
  48. return this->tombStampMs > 0;
  49. }
  50. void Row_display(const Object* cast, RichString* out) {
  51. const Row* this = (const Row*) cast;
  52. const Settings* settings = this->host->settings;
  53. const RowField* fields = settings->ss->fields;
  54. for (int i = 0; fields[i]; i++)
  55. As_Row(this)->writeField(this, out, fields[i]);
  56. if (Row_isHighlighted(this))
  57. RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]);
  58. if (this->tag == true)
  59. RichString_setAttr(out, CRT_colors[PROCESS_TAG]);
  60. if (settings->highlightChanges) {
  61. if (Row_isTomb(this))
  62. out->highlightAttr = CRT_colors[PROCESS_TOMB];
  63. else if (Row_isNew(this))
  64. out->highlightAttr = CRT_colors[PROCESS_NEW];
  65. }
  66. assert(RichString_size(out) > 0);
  67. }
  68. void Row_setPidColumnWidth(pid_t maxPid) {
  69. if (maxPid < (int)pow(10, ROW_MIN_PID_DIGITS)) {
  70. Row_pidDigits = ROW_MIN_PID_DIGITS;
  71. return;
  72. }
  73. Row_pidDigits = (int)log10(maxPid) + 1;
  74. assert(Row_pidDigits <= ROW_MAX_PID_DIGITS);
  75. }
  76. void Row_setUidColumnWidth(uid_t maxUid) {
  77. if (maxUid < (uid_t)pow(10, ROW_MIN_UID_DIGITS)) {
  78. Row_uidDigits = ROW_MIN_UID_DIGITS;
  79. return;
  80. }
  81. Row_uidDigits = (int)log10(maxUid) + 1;
  82. assert(Row_uidDigits <= ROW_MAX_UID_DIGITS);
  83. }
  84. uint8_t Row_fieldWidths[LAST_PROCESSFIELD] = { 0 };
  85. void Row_resetFieldWidths(void) {
  86. for (size_t i = 0; i < LAST_PROCESSFIELD; i++) {
  87. if (!Process_fields[i].autoWidth)
  88. continue;
  89. size_t len = strlen(Process_fields[i].title);
  90. assert(len <= UINT8_MAX);
  91. Row_fieldWidths[i] = (uint8_t)len;
  92. }
  93. }
  94. void Row_updateFieldWidth(RowField key, size_t width) {
  95. if (width > UINT8_MAX)
  96. Row_fieldWidths[key] = UINT8_MAX;
  97. else if (width > Row_fieldWidths[key])
  98. Row_fieldWidths[key] = (uint8_t)width;
  99. }
  100. // helper function to fill an aligned title string for a dynamic column
  101. static const char* alignedTitleDynamicColumn(const Settings* settings, int key, char* titleBuffer, size_t titleBufferSize) {
  102. const DynamicColumn* column = Hashtable_get(settings->dynamicColumns, key);
  103. if (column == NULL)
  104. return "- ";
  105. int width = column->width;
  106. if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
  107. width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
  108. xSnprintf(titleBuffer, titleBufferSize, "%*s ", width, column->heading);
  109. return titleBuffer;
  110. }
  111. // helper function to fill an aligned title string for a process field
  112. static const char* alignedTitleProcessField(ProcessField field, char* titleBuffer, size_t titleBufferSize) {
  113. const char* title = Process_fields[field].title;
  114. if (!title)
  115. return "- ";
  116. if (Process_fields[field].pidColumn) {
  117. xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_pidDigits, title);
  118. return titleBuffer;
  119. }
  120. if (field == ST_UID) {
  121. xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_uidDigits, title);
  122. return titleBuffer;
  123. }
  124. if (Process_fields[field].autoWidth) {
  125. if (Process_fields[field].autoTitleRightAlign)
  126. xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_fieldWidths[field], title);
  127. else
  128. xSnprintf(titleBuffer, titleBufferSize, "%-*.*s ", Row_fieldWidths[field], Row_fieldWidths[field], title);
  129. return titleBuffer;
  130. }
  131. return title;
  132. }
  133. // helper function to create an aligned title string for a given field
  134. const char* RowField_alignedTitle(const Settings* settings, RowField field) {
  135. static char titleBuffer[UINT8_MAX + sizeof(" ")];
  136. assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" "));
  137. assert(sizeof(titleBuffer) >= ROW_MAX_PID_DIGITS + sizeof(" "));
  138. assert(sizeof(titleBuffer) >= ROW_MAX_UID_DIGITS + sizeof(" "));
  139. if (field < LAST_PROCESSFIELD)
  140. return alignedTitleProcessField((ProcessField)field, titleBuffer, sizeof(titleBuffer));
  141. return alignedTitleDynamicColumn(settings, field, titleBuffer, sizeof(titleBuffer));
  142. }
  143. RowField RowField_keyAt(const Settings* settings, int at) {
  144. const RowField* fields = (const RowField*) settings->ss->fields;
  145. RowField field;
  146. int x = 0;
  147. for (int i = 0; (field = fields[i]); i++) {
  148. int len = strlen(RowField_alignedTitle(settings, field));
  149. if (at >= x && at <= x + len) {
  150. return field;
  151. }
  152. x += len;
  153. }
  154. return COMM;
  155. }
  156. void Row_printKBytes(RichString* str, unsigned long long number, bool coloring) {
  157. char buffer[16];
  158. int len;
  159. int color = CRT_colors[PROCESS];
  160. int nextUnitColor = CRT_colors[PROCESS];
  161. const int colors[4] = {
  162. [0] = CRT_colors[PROCESS],
  163. [1] = CRT_colors[PROCESS_MEGABYTES],
  164. [2] = CRT_colors[PROCESS_GIGABYTES],
  165. [3] = CRT_colors[LARGE_NUMBER]
  166. };
  167. if (number == ULLONG_MAX)
  168. goto invalidNumber;
  169. if (coloring) {
  170. color = colors[0];
  171. nextUnitColor = colors[1];
  172. }
  173. if (number < 1000) {
  174. // Plain number, no markings
  175. len = xSnprintf(buffer, sizeof(buffer), "%5u ", (unsigned int)number);
  176. RichString_appendnAscii(str, color, buffer, len);
  177. return;
  178. }
  179. if (number < 100000) {
  180. // 2 digits for M, 3 digits for K
  181. len = xSnprintf(buffer, sizeof(buffer), "%2u", (unsigned int)(number / 1000));
  182. RichString_appendnAscii(str, nextUnitColor, buffer, len);
  183. len = xSnprintf(buffer, sizeof(buffer), "%03u ", (unsigned int)(number % 1000));
  184. RichString_appendnAscii(str, color, buffer, len);
  185. return;
  186. }
  187. // 100000 KiB (97.6 MiB) or greater. A unit prefix would be added.
  188. const size_t maxUnitIndex = (sizeof(number) * CHAR_BIT - 1) / 10 + 1;
  189. const bool canOverflow = maxUnitIndex >= ARRAYSIZE(unitPrefixes);
  190. size_t i = 1;
  191. int prevUnitColor;
  192. // Convert KiB to (1/100) of MiB
  193. unsigned long long hundredths = (number / 256) * 25 + (number % 256) * 25 / 256;
  194. while (true) {
  195. if (canOverflow && i >= ARRAYSIZE(unitPrefixes))
  196. goto invalidNumber;
  197. prevUnitColor = color;
  198. color = nextUnitColor;
  199. if (coloring && i + 1 < ARRAYSIZE(colors))
  200. nextUnitColor = colors[i + 1];
  201. if (hundredths < 1000000)
  202. break;
  203. hundredths /= ONE_K;
  204. i++;
  205. }
  206. number = hundredths / 100;
  207. hundredths %= 100;
  208. if (number < 100) {
  209. if (number < 10) {
  210. // 1 digit + decimal point + 2 digits
  211. // "9.76G", "9.99G", "9.76T", "9.99T", etc.
  212. len = xSnprintf(buffer, sizeof(buffer), "%1u", (unsigned int)number);
  213. RichString_appendnAscii(str, color, buffer, len);
  214. len = xSnprintf(buffer, sizeof(buffer), ".%02u", (unsigned int)hundredths);
  215. } else {
  216. // 2 digits + decimal point + 1 digit
  217. // "97.6M", "99.9M", "10.0G", "99.9G", etc.
  218. len = xSnprintf(buffer, sizeof(buffer), "%2u", (unsigned int)number);
  219. RichString_appendnAscii(str, color, buffer, len);
  220. len = xSnprintf(buffer, sizeof(buffer), ".%1u", (unsigned int)hundredths / 10);
  221. }
  222. RichString_appendnAscii(str, prevUnitColor, buffer, len);
  223. len = xSnprintf(buffer, sizeof(buffer), "%c ", unitPrefixes[i]);
  224. } else if (number < 1000) {
  225. // 3 digits
  226. // "100M", "999M", "100G", "999G", etc.
  227. len = xSnprintf(buffer, sizeof(buffer), "%4u%c ", (unsigned int)number, unitPrefixes[i]);
  228. } else {
  229. // 1 digit + 3 digits
  230. // "1000M", "9999M", "1000G", "9999G", etc.
  231. assert(number < 10000);
  232. len = xSnprintf(buffer, sizeof(buffer), "%1u", (unsigned int)number / 1000);
  233. RichString_appendnAscii(str, nextUnitColor, buffer, len);
  234. len = xSnprintf(buffer, sizeof(buffer), "%03u%c ", (unsigned int)number % 1000, unitPrefixes[i]);
  235. }
  236. RichString_appendnAscii(str, color, buffer, len);
  237. return;
  238. invalidNumber:
  239. if (coloring)
  240. color = CRT_colors[PROCESS_SHADOW];
  241. RichString_appendAscii(str, color, " N/A ");
  242. }
  243. void Row_printBytes(RichString* str, unsigned long long number, bool coloring) {
  244. if (number == ULLONG_MAX)
  245. Row_printKBytes(str, ULLONG_MAX, coloring);
  246. else
  247. Row_printKBytes(str, number / ONE_K, coloring);
  248. }
  249. void Row_printCount(RichString* str, unsigned long long number, bool coloring) {
  250. char buffer[13];
  251. int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
  252. int megabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
  253. int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
  254. int baseColor = CRT_colors[PROCESS];
  255. if (number == ULLONG_MAX) {
  256. RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A ");
  257. } else if (number >= 100000LL * ONE_DECIMAL_T) {
  258. xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G);
  259. RichString_appendnAscii(str, largeNumberColor, buffer, 12);
  260. } else if (number >= 100LL * ONE_DECIMAL_T) {
  261. xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M);
  262. RichString_appendnAscii(str, largeNumberColor, buffer, 8);
  263. RichString_appendnAscii(str, megabytesColor, buffer + 8, 4);
  264. } else if (number >= 10LL * ONE_DECIMAL_G) {
  265. xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_K);
  266. RichString_appendnAscii(str, largeNumberColor, buffer, 5);
  267. RichString_appendnAscii(str, megabytesColor, buffer + 5, 3);
  268. RichString_appendnAscii(str, baseColor, buffer + 8, 4);
  269. } else {
  270. xSnprintf(buffer, sizeof(buffer), "%11llu ", number);
  271. RichString_appendnAscii(str, largeNumberColor, buffer, 2);
  272. RichString_appendnAscii(str, megabytesColor, buffer + 2, 3);
  273. RichString_appendnAscii(str, baseColor, buffer + 5, 3);
  274. RichString_appendnAscii(str, shadowColor, buffer + 8, 4);
  275. }
  276. }
  277. void Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) {
  278. char buffer[10];
  279. int len;
  280. if (totalHundredths == 0) {
  281. int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
  282. RichString_appendAscii(str, shadowColor, " 0:00.00 ");
  283. return;
  284. }
  285. int yearColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
  286. int dayColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];
  287. int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
  288. int baseColor = CRT_colors[PROCESS];
  289. unsigned long long totalSeconds = totalHundredths / 100;
  290. unsigned long long totalMinutes = totalSeconds / 60;
  291. unsigned long long totalHours = totalMinutes / 60;
  292. unsigned int seconds = totalSeconds % 60;
  293. unsigned int minutes = totalMinutes % 60;
  294. if (totalMinutes < 60) {
  295. unsigned int hundredths = totalHundredths % 100;
  296. len = xSnprintf(buffer, sizeof(buffer), "%2u:%02u.%02u ", (unsigned int)totalMinutes, seconds, hundredths);
  297. RichString_appendnAscii(str, baseColor, buffer, len);
  298. return;
  299. }
  300. if (totalHours < 24) {
  301. len = xSnprintf(buffer, sizeof(buffer), "%2uh", (unsigned int)totalHours);
  302. RichString_appendnAscii(str, hourColor, buffer, len);
  303. len = xSnprintf(buffer, sizeof(buffer), "%02u:%02u ", minutes, seconds);
  304. RichString_appendnAscii(str, baseColor, buffer, len);
  305. return;
  306. }
  307. unsigned long long totalDays = totalHours / 24;
  308. unsigned int hours = totalHours % 24;
  309. if (totalDays < 10) {
  310. len = xSnprintf(buffer, sizeof(buffer), "%1ud", (unsigned int)totalDays);
  311. RichString_appendnAscii(str, dayColor, buffer, len);
  312. len = xSnprintf(buffer, sizeof(buffer), "%02uh", hours);
  313. RichString_appendnAscii(str, hourColor, buffer, len);
  314. len = xSnprintf(buffer, sizeof(buffer), "%02um ", minutes);
  315. RichString_appendnAscii(str, baseColor, buffer, len);
  316. return;
  317. }
  318. if (totalDays < /* Ignore leap years */365) {
  319. len = xSnprintf(buffer, sizeof(buffer), "%4ud", (unsigned int)totalDays);
  320. RichString_appendnAscii(str, dayColor, buffer, len);
  321. len = xSnprintf(buffer, sizeof(buffer), "%02uh ", hours);
  322. RichString_appendnAscii(str, hourColor, buffer, len);
  323. return;
  324. }
  325. unsigned long long years = totalDays / 365;
  326. unsigned int days = totalDays % 365;
  327. if (years < 1000) {
  328. len = xSnprintf(buffer, sizeof(buffer), "%3uy", (unsigned int)years);
  329. RichString_appendnAscii(str, yearColor, buffer, len);
  330. len = xSnprintf(buffer, sizeof(buffer), "%03ud ", days);
  331. RichString_appendnAscii(str, dayColor, buffer, len);
  332. } else if (years < 10000000) {
  333. len = xSnprintf(buffer, sizeof(buffer), "%7luy ", (unsigned long)years);
  334. RichString_appendnAscii(str, yearColor, buffer, len);
  335. } else {
  336. RichString_appendAscii(str, yearColor, "eternity ");
  337. }
  338. }
  339. void Row_printNanoseconds(RichString* str, unsigned long long totalNanoseconds, bool coloring) {
  340. if (totalNanoseconds == 0) {
  341. int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
  342. RichString_appendAscii(str, shadowColor, " 0ns ");
  343. return;
  344. }
  345. char buffer[10];
  346. int len;
  347. int baseColor = CRT_colors[PROCESS];
  348. if (totalNanoseconds < 1000000) {
  349. len = xSnprintf(buffer, sizeof(buffer), "%6luns ", (unsigned long)totalNanoseconds);
  350. RichString_appendnAscii(str, baseColor, buffer, len);
  351. return;
  352. }
  353. unsigned long long totalMicroseconds = totalNanoseconds / 1000;
  354. if (totalMicroseconds < 1000000) {
  355. len = xSnprintf(buffer, sizeof(buffer), ".%06lus ", (unsigned long)totalMicroseconds);
  356. RichString_appendnAscii(str, baseColor, buffer, len);
  357. return;
  358. }
  359. unsigned long long totalSeconds = totalMicroseconds / 1000000;
  360. unsigned long microseconds = totalMicroseconds % 1000000;
  361. if (totalSeconds < 60) {
  362. int width = 5;
  363. unsigned long fraction = microseconds / 10;
  364. if (totalSeconds >= 10) {
  365. width--;
  366. fraction /= 10;
  367. }
  368. len = xSnprintf(buffer, sizeof(buffer), "%u.%0*lus ", (unsigned int)totalSeconds, width, fraction);
  369. RichString_appendnAscii(str, baseColor, buffer, len);
  370. return;
  371. }
  372. if (totalSeconds < 600) {
  373. unsigned int minutes = totalSeconds / 60;
  374. unsigned int seconds = totalSeconds % 60;
  375. unsigned int milliseconds = microseconds / 1000;
  376. len = xSnprintf(buffer, sizeof(buffer), "%u:%02u.%03u ", minutes, seconds, milliseconds);
  377. RichString_appendnAscii(str, baseColor, buffer, len);
  378. return;
  379. }
  380. unsigned long long totalHundredths = totalMicroseconds / 1000 / 10;
  381. Row_printTime(str, totalHundredths, coloring);
  382. }
  383. void Row_printRate(RichString* str, double rate, bool coloring) {
  384. char buffer[16];
  385. int largeNumberColor = CRT_colors[LARGE_NUMBER];
  386. int megabytesColor = CRT_colors[PROCESS_MEGABYTES];
  387. int shadowColor = CRT_colors[PROCESS_SHADOW];
  388. int baseColor = CRT_colors[PROCESS];
  389. if (!coloring) {
  390. largeNumberColor = CRT_colors[PROCESS];
  391. megabytesColor = CRT_colors[PROCESS];
  392. }
  393. if (!isNonnegative(rate)) {
  394. RichString_appendAscii(str, shadowColor, " N/A ");
  395. } else if (rate < 0.005) {
  396. int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
  397. RichString_appendnAscii(str, shadowColor, buffer, len);
  398. } else if (rate < ONE_K) {
  399. int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
  400. RichString_appendnAscii(str, baseColor, buffer, len);
  401. } else if (rate < ONE_M) {
  402. int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K);
  403. RichString_appendnAscii(str, baseColor, buffer, len);
  404. } else if (rate < ONE_G) {
  405. int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M);
  406. RichString_appendnAscii(str, megabytesColor, buffer, len);
  407. } else if (rate < ONE_T) {
  408. int len = snprintf(buffer, sizeof(buffer), "%7.2f G/s ", rate / ONE_G);
  409. RichString_appendnAscii(str, largeNumberColor, buffer, len);
  410. } else if (rate < ONE_P) {
  411. int len = snprintf(buffer, sizeof(buffer), "%7.2f T/s ", rate / ONE_T);
  412. RichString_appendnAscii(str, largeNumberColor, buffer, len);
  413. } else {
  414. int len = snprintf(buffer, sizeof(buffer), "%7.2f P/s ", rate / ONE_P);
  415. RichString_appendnAscii(str, largeNumberColor, buffer, len);
  416. }
  417. }
  418. void Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) {
  419. int columns = width;
  420. RichString_appendnWideColumns(str, attr, content, strlen(content), &columns);
  421. RichString_appendChr(str, attr, ' ', width + 1 - columns);
  422. }
  423. int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) {
  424. assert(n >= 6 && width >= 4 && "Invalid width in Row_printPercentage()");
  425. // truncate in favour of abort in xSnprintf()
  426. width = (uint8_t)CLAMP(width, 4, n - 2);
  427. assert(width < n - 1 && "Insufficient space to print column");
  428. if (isNonnegative(val)) {
  429. if (val < 0.05F)
  430. *attr = CRT_colors[PROCESS_SHADOW];
  431. else if (val >= 99.9F)
  432. *attr = CRT_colors[PROCESS_MEGABYTES];
  433. int precision = 1;
  434. // Display "val" as "100" for columns like "MEM%".
  435. if (width == 4 && val > 99.9F) {
  436. precision = 0;
  437. val = 100.0F;
  438. }
  439. return xSnprintf(buffer, n, "%*.*f ", width, precision, val);
  440. }
  441. *attr = CRT_colors[PROCESS_SHADOW];
  442. return xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
  443. }
  444. void Row_toggleTag(Row* this) {
  445. this->tag = !this->tag;
  446. }
  447. int Row_compare(const void* v1, const void* v2) {
  448. const Row* r1 = (const Row*)v1;
  449. const Row* r2 = (const Row*)v2;
  450. return SPACESHIP_NUMBER(r1->id, r2->id);
  451. }
  452. int Row_compareByParent_Base(const void* v1, const void* v2) {
  453. const Row* r1 = (const Row*)v1;
  454. const Row* r2 = (const Row*)v2;
  455. int result = SPACESHIP_NUMBER(
  456. r1->isRoot ? 0 : Row_getGroupOrParent(r1),
  457. r2->isRoot ? 0 : Row_getGroupOrParent(r2)
  458. );
  459. if (result != 0)
  460. return result;
  461. return Row_compare(v1, v2);
  462. }
  463. const RowClass Row_class = {
  464. .super = {
  465. .extends = Class(Object),
  466. .compare = Row_compare
  467. },
  468. };