/* htop - Table.c (C) 2004,2005 Hisham H. Muhammad (C) 2023 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ #include "config.h" // IWYU pragma: keep #include "Table.h" #include #include #include #include "CRT.h" #include "Hashtable.h" #include "Machine.h" #include "Macros.h" #include "Panel.h" #include "RowField.h" #include "Vector.h" Table* Table_init(Table* this, const ObjectClass* klass, Machine* host) { this->rows = Vector_new(klass, true, DEFAULT_SIZE); this->displayList = Vector_new(klass, false, DEFAULT_SIZE); this->table = Hashtable_new(200, false); this->needsSort = true; this->following = -1; this->host = host; return this; } void Table_done(Table* this) { Hashtable_delete(this->table); Vector_delete(this->displayList); Vector_delete(this->rows); } static void Table_delete(Object* cast) { Table* this = (Table*) cast; Table_done(this); free(this); } void Table_setPanel(Table* this, Panel* panel) { this->panel = panel; } void Table_add(Table* this, Row* row) { assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) == -1); assert(Hashtable_get(this->table, row->id) == NULL); // highlighting row found in first scan by first scan marked "far in the past" row->seenStampMs = this->host->monotonicMs; Vector_add(this->rows, row); Hashtable_put(this->table, row->id, row); assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) != -1); assert(Hashtable_get(this->table, row->id) != NULL); assert(Vector_countEquals(this->rows, Hashtable_count(this->table))); } // Table_removeIndex removes a given row from the lists map and soft deletes // it from its vector. Vector_compact *must* be called once the caller is done // removing items. // Note: for processes should only be called from ProcessTable_iterate to avoid // breaking dying process highlighting. void Table_removeIndex(Table* this, const Row* row, int idx) { int rowid = row->id; assert(row == (Row*)Vector_get(this->rows, idx)); assert(Hashtable_get(this->table, rowid) != NULL); Hashtable_remove(this->table, rowid); Vector_softRemove(this->rows, idx); if (this->following != -1 && this->following == rowid) { this->following = -1; Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); } assert(Hashtable_get(this->table, rowid) == NULL); assert(Vector_countEquals(this->rows, Hashtable_count(this->table))); } static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, int32_t indent, bool show) { // Do not treat zero as root of any tree. // (e.g. on OpenBSD the kernel thread 'swapper' has pid 0.) if (rowid == 0) return; // The vector is sorted by parent, find the start of the range by bisection int vsize = Vector_size(this->rows); int l = 0; int r = vsize; while (l < r) { int c = (l + r) / 2; Row* row = (Row*)Vector_get(this->rows, c); int parent = row->isRoot ? 0 : Row_getGroupOrParent(row); if (parent < rowid) { l = c + 1; } else { r = c; } } // Find the end to know the last line for indent handling purposes int lastShown = r; while (r < vsize) { Row* row = (Row*)Vector_get(this->rows, r); if (!Row_isChildOf(row, rowid)) break; if (row->show) lastShown = r; r++; } for (int i = l; i < r; i++) { Row* row = (Row*)Vector_get(this->rows, i); if (!show) row->show = false; Vector_add(this->displayList, row); int32_t nextIndent = indent | ((int32_t)1 << MINIMUM(level, sizeof(row->indent) * 8 - 2)); Table_buildTreeBranch(this, row->id, level + 1, (i < lastShown) ? nextIndent : indent, row->show && row->showChildren); if (i == lastShown) row->indent = -nextIndent; else row->indent = nextIndent; row->tree_depth = level + 1; } } static int compareRowByKnownParentThenNatural(const void* v1, const void* v2) { return Row_compareByParent((const Row*) v1, (const Row*) v2); } // Builds a sorted tree from scratch, without relying on previously gathered information static void Table_buildTree(Table* this) { Vector_prune(this->displayList); // Mark root processes int vsize = Vector_size(this->rows); for (int i = 0; i < vsize; i++) { Row* row = (Row*) Vector_get(this->rows, i); int parent = Row_getGroupOrParent(row); row->isRoot = false; if (row->id == parent) { row->isRoot = true; continue; } if (!parent) { row->isRoot = true; continue; } // We don't know about its parent for whatever reason if (Table_findRow(this, parent) == NULL) row->isRoot = true; } // Sort by known parent (roots first), then row ID Vector_quickSortCustomCompare(this->rows, compareRowByKnownParentThenNatural); // Find all processes whose parent is not visible for (int i = 0; i < vsize; i++) { Row* row = (Row*)Vector_get(this->rows, i); // If parent not found, then construct the tree with this node as root if (row->isRoot) { row = (Row*)Vector_get(this->rows, i); row->indent = 0; row->tree_depth = 0; Vector_add(this->displayList, row); Table_buildTreeBranch(this, row->id, 0, 0, row->showChildren); continue; } } this->needsSort = false; // Check consistency of the built structures assert(Vector_size(this->displayList) == vsize); (void)vsize; } void Table_updateDisplayList(Table* this) { const Settings* settings = this->host->settings; if (settings->ss->treeView) { if (this->needsSort) Table_buildTree(this); } else { if (this->needsSort) Vector_insertionSort(this->rows); Vector_prune(this->displayList); int size = Vector_size(this->rows); for (int i = 0; i < size; i++) Vector_add(this->displayList, Vector_get(this->rows, i)); } this->needsSort = false; } void Table_expandTree(Table* this) { int size = Vector_size(this->rows); for (int i = 0; i < size; i++) { Row* row = (Row*) Vector_get(this->rows, i); row->showChildren = true; } } // Called on collapse-all toggle and on startup, possibly in non-tree mode void Table_collapseAllBranches(Table* this) { Table_buildTree(this); // Update `tree_depth` fields of the rows this->needsSort = true; // Table is sorted by parent now, force new sort int size = Vector_size(this->rows); for (int i = 0; i < size; i++) { Row* row = (Row*) Vector_get(this->rows, i); // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1 if (row->tree_depth > 0 && row->id > 1) row->showChildren = false; } } void Table_rebuildPanel(Table* this) { Table_updateDisplayList(this); const int currPos = Panel_getSelectedIndex(this->panel); const int currScrollV = this->panel->scrollV; const int currSize = Panel_size(this->panel); Panel_prune(this->panel); /* Follow main group row instead if following a row that is occluded (hidden) */ if (this->following != -1) { const Row* followed = (const Row*) Hashtable_get(this->table, this->following); if (followed != NULL && Hashtable_get(this->table, followed->group) && Row_isVisible(followed, this) == false ) { this->following = followed->group; } } const int rowCount = Vector_size(this->displayList); bool foundFollowed = false; int idx = 0; for (int i = 0; i < rowCount; i++) { Row* row = (Row*) Vector_get(this->displayList, i); if ( !row->show || (Row_matchesFilter(row, this) == true) ) continue; Panel_set(this->panel, idx, (Object*)row); if (this->following != -1 && row->id == this->following) { foundFollowed = true; Panel_setSelected(this->panel, idx); /* Keep scroll position relative to followed row */ this->panel->scrollV = idx - (currPos - currScrollV); } idx++; } if (this->following != -1 && !foundFollowed) { /* Reset if current followed row not found */ this->following = -1; Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); } if (this->following == -1) { /* If the last item was selected, keep the new last item selected */ if (currPos > 0 && currPos == currSize - 1) Panel_setSelected(this->panel, Panel_size(this->panel) - 1); else Panel_setSelected(this->panel, currPos); this->panel->scrollV = currScrollV; } } void Table_printHeader(const Settings* settings, RichString* header) { RichString_rewind(header, RichString_size(header)); const ScreenSettings* ss = settings->ss; const RowField* fields = ss->fields; RowField key = ScreenSettings_getActiveSortKey(ss); for (int i = 0; fields[i]; i++) { int color; if (ss->treeView && ss->treeViewAlwaysByPID) { color = CRT_colors[PANEL_HEADER_FOCUS]; } else if (key == fields[i]) { color = CRT_colors[PANEL_SELECTION_FOCUS]; } else { color = CRT_colors[PANEL_HEADER_FOCUS]; } RichString_appendWide(header, color, RowField_alignedTitle(settings, fields[i])); if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') { bool ascending = ScreenSettings_getActiveDirection(ss) == 1; RichString_rewind(header, 1); // rewind to override space RichString_appendWide(header, CRT_colors[PANEL_SELECTION_FOCUS], CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC]); } if (COMM == fields[i] && settings->showMergedCommand) { RichString_appendAscii(header, color, "(merged)"); } } } // set flags on an existing rows before refreshing table void Table_prepareEntries(Table* this) { for (int i = 0; i < Vector_size(this->rows); i++) { Row* row = (struct Row_*) Vector_get(this->rows, i); row->updated = false; row->wasShown = row->show; row->show = true; } } // tidy up Row state after refreshing the table void Table_cleanupRow(Table* table, Row* row, int idx) { Machine* host = table->host; const Settings* settings = host->settings; if (row->tombStampMs > 0) { // remove tombed process if (host->monotonicMs >= row->tombStampMs) { Table_removeIndex(table, row, idx); } } else if (row->updated == false) { // process no longer exists if (settings->highlightChanges && row->wasShown) { // mark tombed row->tombStampMs = host->monotonicMs + 1000 * settings->highlightDelaySecs; } else { // immediately remove Table_removeIndex(table, row, idx); } } } void Table_cleanupEntries(Table* this) { // Finish process table update, culling any removed rows for (int i = Vector_size(this->rows) - 1; i >= 0; i--) { Row* row = (Row*) Vector_get(this->rows, i); Table_cleanupRow(this, row, i); } // compact the table in case of any earlier row removals Table_compact(this); } const TableClass Table_class = { .super = { .extends = Class(Object), .delete = Table_delete, }, .prepare = Table_prepareEntries, .cleanup = Table_cleanupEntries, };