Table.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. /*
  2. htop - Table.c
  3. (C) 2004,2005 Hisham H. Muhammad
  4. (C) 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 "Table.h"
  10. #include <assert.h>
  11. #include <stdint.h>
  12. #include <stdlib.h>
  13. #include "CRT.h"
  14. #include "Hashtable.h"
  15. #include "Machine.h"
  16. #include "Macros.h"
  17. #include "Panel.h"
  18. #include "RowField.h"
  19. #include "Vector.h"
  20. Table* Table_init(Table* this, const ObjectClass* klass, Machine* host) {
  21. this->rows = Vector_new(klass, true, DEFAULT_SIZE);
  22. this->displayList = Vector_new(klass, false, DEFAULT_SIZE);
  23. this->table = Hashtable_new(200, false);
  24. this->needsSort = true;
  25. this->following = -1;
  26. this->host = host;
  27. return this;
  28. }
  29. void Table_done(Table* this) {
  30. Hashtable_delete(this->table);
  31. Vector_delete(this->displayList);
  32. Vector_delete(this->rows);
  33. }
  34. static void Table_delete(Object* cast) {
  35. Table* this = (Table*) cast;
  36. Table_done(this);
  37. free(this);
  38. }
  39. void Table_setPanel(Table* this, Panel* panel) {
  40. this->panel = panel;
  41. }
  42. void Table_add(Table* this, Row* row) {
  43. assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) == -1);
  44. assert(Hashtable_get(this->table, row->id) == NULL);
  45. // highlighting row found in first scan by first scan marked "far in the past"
  46. row->seenStampMs = this->host->monotonicMs;
  47. Vector_add(this->rows, row);
  48. Hashtable_put(this->table, row->id, row);
  49. assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) != -1);
  50. assert(Hashtable_get(this->table, row->id) != NULL);
  51. assert(Vector_countEquals(this->rows, Hashtable_count(this->table)));
  52. }
  53. // Table_removeIndex removes a given row from the lists map and soft deletes
  54. // it from its vector. Vector_compact *must* be called once the caller is done
  55. // removing items.
  56. // Note: for processes should only be called from ProcessTable_iterate to avoid
  57. // breaking dying process highlighting.
  58. static void Table_removeIndex(Table* this, const Row* row, int idx) {
  59. int rowid = row->id;
  60. assert(row == (Row*)Vector_get(this->rows, idx));
  61. assert(Hashtable_get(this->table, rowid) != NULL);
  62. Hashtable_remove(this->table, rowid);
  63. Vector_softRemove(this->rows, idx);
  64. if (this->following != -1 && this->following == rowid) {
  65. this->following = -1;
  66. Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS);
  67. }
  68. assert(Hashtable_get(this->table, rowid) == NULL);
  69. assert(Vector_countEquals(this->rows, Hashtable_count(this->table)));
  70. }
  71. static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, int32_t indent, bool show) {
  72. // Do not treat zero as root of any tree.
  73. // (e.g. on OpenBSD the kernel thread 'swapper' has pid 0.)
  74. if (rowid == 0)
  75. return;
  76. // The vector is sorted by parent, find the start of the range by bisection
  77. int vsize = Vector_size(this->rows);
  78. int l = 0;
  79. int r = vsize;
  80. while (l < r) {
  81. int c = (l + r) / 2;
  82. Row* row = (Row*)Vector_get(this->rows, c);
  83. int parent = row->isRoot ? 0 : Row_getGroupOrParent(row);
  84. if (parent < rowid) {
  85. l = c + 1;
  86. } else {
  87. r = c;
  88. }
  89. }
  90. // Find the end to know the last line for indent handling purposes
  91. int lastShown = r;
  92. while (r < vsize) {
  93. Row* row = (Row*)Vector_get(this->rows, r);
  94. if (!Row_isChildOf(row, rowid))
  95. break;
  96. if (row->show)
  97. lastShown = r;
  98. r++;
  99. }
  100. for (int i = l; i < r; i++) {
  101. Row* row = (Row*)Vector_get(this->rows, i);
  102. if (!show)
  103. row->show = false;
  104. Vector_add(this->displayList, row);
  105. int32_t nextIndent = indent | ((int32_t)1 << MINIMUM(level, sizeof(row->indent) * 8 - 2));
  106. Table_buildTreeBranch(this, row->id, level + 1, (i < lastShown) ? nextIndent : indent, row->show && row->showChildren);
  107. if (i == lastShown)
  108. row->indent = -nextIndent;
  109. else
  110. row->indent = nextIndent;
  111. row->tree_depth = level + 1;
  112. }
  113. }
  114. static int compareRowByKnownParentThenNatural(const void* v1, const void* v2) {
  115. return Row_compareByParent((const Row*) v1, (const Row*) v2);
  116. }
  117. // Builds a sorted tree from scratch, without relying on previously gathered information
  118. static void Table_buildTree(Table* this) {
  119. Vector_prune(this->displayList);
  120. // Mark root processes
  121. int vsize = Vector_size(this->rows);
  122. for (int i = 0; i < vsize; i++) {
  123. Row* row = (Row*) Vector_get(this->rows, i);
  124. int parent = Row_getGroupOrParent(row);
  125. row->isRoot = false;
  126. if (row->id == parent) {
  127. row->isRoot = true;
  128. continue;
  129. }
  130. if (!parent) {
  131. row->isRoot = true;
  132. continue;
  133. }
  134. // We don't know about its parent for whatever reason
  135. if (Table_findRow(this, parent) == NULL)
  136. row->isRoot = true;
  137. }
  138. // Sort by known parent (roots first), then row ID
  139. Vector_quickSortCustomCompare(this->rows, compareRowByKnownParentThenNatural);
  140. // Find all processes whose parent is not visible
  141. for (int i = 0; i < vsize; i++) {
  142. Row* row = (Row*)Vector_get(this->rows, i);
  143. // If parent not found, then construct the tree with this node as root
  144. if (row->isRoot) {
  145. row = (Row*)Vector_get(this->rows, i);
  146. row->indent = 0;
  147. row->tree_depth = 0;
  148. Vector_add(this->displayList, row);
  149. Table_buildTreeBranch(this, row->id, 0, 0, row->showChildren);
  150. continue;
  151. }
  152. }
  153. this->needsSort = false;
  154. // Check consistency of the built structures
  155. assert(Vector_size(this->displayList) == vsize); (void)vsize;
  156. }
  157. void Table_updateDisplayList(Table* this) {
  158. const Settings* settings = this->host->settings;
  159. if (settings->ss->treeView) {
  160. if (this->needsSort)
  161. Table_buildTree(this);
  162. } else {
  163. if (this->needsSort)
  164. Vector_insertionSort(this->rows);
  165. Vector_prune(this->displayList);
  166. int size = Vector_size(this->rows);
  167. for (int i = 0; i < size; i++)
  168. Vector_add(this->displayList, Vector_get(this->rows, i));
  169. }
  170. this->needsSort = false;
  171. }
  172. void Table_expandTree(Table* this) {
  173. int size = Vector_size(this->rows);
  174. for (int i = 0; i < size; i++) {
  175. Row* row = (Row*) Vector_get(this->rows, i);
  176. row->showChildren = true;
  177. }
  178. }
  179. // Called on collapse-all toggle and on startup, possibly in non-tree mode
  180. void Table_collapseAllBranches(Table* this) {
  181. Table_buildTree(this); // Update `tree_depth` fields of the rows
  182. this->needsSort = true; // Table is sorted by parent now, force new sort
  183. int size = Vector_size(this->rows);
  184. for (int i = 0; i < size; i++) {
  185. Row* row = (Row*) Vector_get(this->rows, i);
  186. // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1
  187. if (row->tree_depth > 0 && row->id > 1)
  188. row->showChildren = false;
  189. }
  190. }
  191. void Table_rebuildPanel(Table* this) {
  192. Table_updateDisplayList(this);
  193. const int currPos = Panel_getSelectedIndex(this->panel);
  194. const int currScrollV = this->panel->scrollV;
  195. const int currSize = Panel_size(this->panel);
  196. Panel_prune(this->panel);
  197. /* Follow main group row instead if following a row that is occluded (hidden) */
  198. if (this->following != -1) {
  199. const Row* followed = (const Row*) Hashtable_get(this->table, this->following);
  200. if (followed != NULL
  201. && Hashtable_get(this->table, followed->group)
  202. && Row_isVisible(followed, this) == false ) {
  203. this->following = followed->group;
  204. }
  205. }
  206. const int rowCount = Vector_size(this->displayList);
  207. bool foundFollowed = false;
  208. int idx = 0;
  209. for (int i = 0; i < rowCount; i++) {
  210. Row* row = (Row*) Vector_get(this->displayList, i);
  211. if ( !row->show || (Row_matchesFilter(row, this) == true) )
  212. continue;
  213. Panel_set(this->panel, idx, (Object*)row);
  214. if (this->following != -1 && row->id == this->following) {
  215. foundFollowed = true;
  216. Panel_setSelected(this->panel, idx);
  217. /* Keep scroll position relative to followed row */
  218. this->panel->scrollV = idx - (currPos - currScrollV);
  219. }
  220. idx++;
  221. }
  222. if (this->following != -1 && !foundFollowed) {
  223. /* Reset if current followed row not found */
  224. this->following = -1;
  225. Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS);
  226. }
  227. if (this->following == -1) {
  228. /* If the last item was selected, keep the new last item selected */
  229. if (currPos > 0 && currPos == currSize - 1)
  230. Panel_setSelected(this->panel, Panel_size(this->panel) - 1);
  231. else
  232. Panel_setSelected(this->panel, currPos);
  233. this->panel->scrollV = currScrollV;
  234. }
  235. }
  236. void Table_printHeader(const Settings* settings, RichString* header) {
  237. RichString_rewind(header, RichString_size(header));
  238. const ScreenSettings* ss = settings->ss;
  239. const RowField* fields = ss->fields;
  240. RowField key = ScreenSettings_getActiveSortKey(ss);
  241. for (int i = 0; fields[i]; i++) {
  242. int color;
  243. if (ss->treeView && ss->treeViewAlwaysByPID) {
  244. color = CRT_colors[PANEL_HEADER_FOCUS];
  245. } else if (key == fields[i]) {
  246. color = CRT_colors[PANEL_SELECTION_FOCUS];
  247. } else {
  248. color = CRT_colors[PANEL_HEADER_FOCUS];
  249. }
  250. RichString_appendWide(header, color, RowField_alignedTitle(settings, fields[i]));
  251. if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') {
  252. bool ascending = ScreenSettings_getActiveDirection(ss) == 1;
  253. RichString_rewind(header, 1); // rewind to override space
  254. RichString_appendWide(header,
  255. CRT_colors[PANEL_SELECTION_FOCUS],
  256. CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC]);
  257. }
  258. if (COMM == fields[i] && settings->showMergedCommand) {
  259. RichString_appendAscii(header, color, "(merged)");
  260. }
  261. }
  262. }
  263. // set flags on an existing rows before refreshing table
  264. void Table_prepareEntries(Table* this) {
  265. for (int i = 0; i < Vector_size(this->rows); i++) {
  266. Row* row = (struct Row_*) Vector_get(this->rows, i);
  267. row->updated = false;
  268. row->wasShown = row->show;
  269. row->show = true;
  270. }
  271. }
  272. // tidy up Row state after refreshing the table
  273. void Table_cleanupRow(Table* table, Row* row, int idx) {
  274. Machine* host = table->host;
  275. const Settings* settings = host->settings;
  276. if (row->tombStampMs > 0) {
  277. // remove tombed process
  278. if (host->monotonicMs >= row->tombStampMs) {
  279. Table_removeIndex(table, row, idx);
  280. }
  281. } else if (row->updated == false) {
  282. // process no longer exists
  283. if (settings->highlightChanges && row->wasShown) {
  284. // mark tombed
  285. row->tombStampMs = host->monotonicMs + 1000 * settings->highlightDelaySecs;
  286. } else {
  287. // immediately remove
  288. Table_removeIndex(table, row, idx);
  289. }
  290. }
  291. }
  292. void Table_cleanupEntries(Table* this) {
  293. // Finish process table update, culling any removed rows
  294. for (int i = Vector_size(this->rows) - 1; i >= 0; i--) {
  295. Row* row = (Row*) Vector_get(this->rows, i);
  296. Table_cleanupRow(this, row, i);
  297. }
  298. // compact the table in case of any earlier row removals
  299. Table_compact(this);
  300. }
  301. const TableClass Table_class = {
  302. .super = {
  303. .extends = Class(Object),
  304. .delete = Table_delete,
  305. },
  306. .prepare = Table_prepareEntries,
  307. .cleanup = Table_cleanupEntries,
  308. };