diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:58:07 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:58:07 +0000 |
commit | 10eea2ab1bae2a8ec159d81c0446fd8061b33e2b (patch) | |
tree | e8270dd60ec096bee8157dbadf029e15ed584592 /Table.c | |
parent | Initial commit. (diff) | |
download | htop-10eea2ab1bae2a8ec159d81c0446fd8061b33e2b.tar.xz htop-10eea2ab1bae2a8ec159d81c0446fd8061b33e2b.zip |
Adding upstream version 3.3.0.upstream/3.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'Table.c')
-rw-r--r-- | Table.c | 372 |
1 files changed, 372 insertions, 0 deletions
@@ -0,0 +1,372 @@ +/* +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 <assert.h> +#include <stdint.h> +#include <stdlib.h> + +#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_appendnWide(header, + CRT_colors[PANEL_SELECTION_FOCUS], + CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC], + 1); + } + 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, +}; |