diff options
61 files changed, 438 insertions, 476 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 617ed305..af8ed883 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,6 +113,10 @@ jobs: build-ubuntu-latest-pcp: # Turns out 'ubuntu-latest' can be older than 20.04, we want PCP v5+ runs-on: ubuntu-20.04 + env: + # Until Ubuntu catches up with pcp-5.2.3+: + # pcp/Platform.c:309:45: warning: passing argument 2 of ‘pmLookupName’ from incompatible pointer type [-Wincompatible-pointer-types] + CFLAGS: -Wno-error=incompatible-pointer-types steps: - uses: actions/checkout@v2 - name: Install Dependencies @@ -120,9 +124,7 @@ jobs: - name: Bootstrap run: ./autogen.sh - name: Configure - # Until Ubuntu catches up with pcp-5.2.3+, cannot use -werror due to: - # passing argument 2 of ‘pmLookupName’ from incompatible pointer type - run: ./configure --enable-pcp --enable-unicode + run: ./configure --enable-werror --enable-pcp --enable-unicode - name: Build run: make -k @@ -189,8 +189,7 @@ static Htop_Reaction actionSetSortColumn(State* st) { } Object_delete(sortPanel); - if (st->pauseProcessUpdate) - ProcessList_sort(st->pl); + st->pl->needsSort = true; return reaction | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } @@ -281,8 +280,7 @@ static Htop_Reaction actionLowerPriority(State* st) { static Htop_Reaction actionInvertSortOrder(State* st) { ScreenSettings_invertSortOrder(st->settings->ss); - if (st->pauseProcessUpdate) - ProcessList_sort(st->pl); + st->pl->needsSort = true; return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING; } @@ -603,7 +601,7 @@ static Htop_Reaction actionHelp(State* st) { addattrstr(CRT_colors[BAR_BORDER], "["); addattrstr(CRT_colors[SWAP], "used"); #ifdef HTOP_LINUX - addattrstr(CRT_colors[BAR_SHADOW], "/"); + addstr("/"); addattrstr(CRT_colors[SWAP_CACHE], "cache"); addattrstr(CRT_colors[BAR_SHADOW], " used/total"); #else @@ -880,6 +880,7 @@ static void CRT_installSignalHandlers(void) { sigaction (SIGSYS, &act, &old_sig_handler[SIGSYS]); sigaction (SIGABRT, &act, &old_sig_handler[SIGABRT]); + signal(SIGCHLD, SIG_DFL); signal(SIGINT, CRT_handleSIGTERM); signal(SIGTERM, CRT_handleSIGTERM); signal(SIGQUIT, CRT_handleSIGTERM); diff --git a/Hashtable.c b/Hashtable.c index c880cf7f..a0cfc9e0 100644 --- a/Hashtable.c +++ b/Hashtable.c @@ -90,7 +90,7 @@ size_t Hashtable_count(const Hashtable* this) { /* https://oeis.org/A014234 */ static const uint64_t OEISprimes[] = { - 2, 3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, + 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, 134217689, 268435399, 536870909, 1073741789, @@ -191,10 +191,14 @@ void Hashtable_setSize(Hashtable* this, size_t size) { if (size <= this->items) return; + size_t newSize = nextPrime(size); + if (newSize == this->size) + return; + HashtableItem* oldBuckets = this->buckets; size_t oldSize = this->size; - this->size = nextPrime(size); + this->size = newSize; this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem)); this->items = 0; @@ -282,7 +286,7 @@ void* Hashtable_remove(Hashtable* this, ht_key_t key) { /* shrink on load-factor < 0.125 */ if (8 * this->items < this->size) - Hashtable_setSize(this, this->size / 2); + Hashtable_setSize(this, this->size / 3); /* account for nextPrime rounding up */ return res; } diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index 34367ebc..2d191692 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -9,6 +9,7 @@ in the source distribution for its full text. #include "OpenFilesScreen.h" +#include <errno.h> #include <fcntl.h> #include <inttypes.h> #include <stdio.h> @@ -197,10 +198,11 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { fclose(fd); int wstatus; - if (waitpid(child, &wstatus, 0) == -1) { - pdata->error = 1; - return pdata; - } + while (waitpid(child, &wstatus, 0) == -1) + if (errno != EINTR) { + pdata->error = 1; + return pdata; + } if (!WIFEXITED(wstatus)) { pdata->error = 1; @@ -397,7 +397,7 @@ static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr * This function makes the merged Command string. It also stores the offsets of the * basename, comm w.r.t the merged Command string - these offsets will be used by * Process_writeCommand() for coloring. The merged Command string is also - * returned by Process_getCommandStr() for searching, sorting and filtering. + * returned by Process_getCommand() for searching, sorting and filtering. */ void Process_makeCommandStr(Process* this) { ProcessMergedCommand* mc = &this->mergedCommand; @@ -417,7 +417,7 @@ void Process_makeCommandStr(Process* this) { return; if (this->state == ZOMBIE && !this->mergedCommand.str) return; - if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames)) + if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames) && (mc->prevMergeSet == showMergedCommand)) return; /* this->mergedCommand.str needs updating only if its state or contents changed. @@ -516,11 +516,14 @@ void Process_makeCommandStr(Process* this) { assert(cmdlineBasenameStart <= (int)strlen(cmdline)); if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */ - if (showMergedCommand && (!Process_isUserlandThread(this) || showThreadNames) && !procExe && procComm && strlen(procComm)) { /* Prefix column with comm */ + if ((showMergedCommand || (Process_isUserlandThread(this) && showThreadNames)) && procComm && strlen(procComm)) { /* set column to or prefix it with comm */ if (strncmp(cmdline + cmdlineBasenameStart, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) { WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM); str = stpcpy(str, procComm); + if(!showMergedCommand) + return; + WRITE_SEPARATOR; } } @@ -729,23 +732,22 @@ void Process_printLeftAlignedField(RichString* str, int attr, const char* conten RichString_appendChr(str, attr, ' ', width + 1 - columns); } -void Process_printPercentage(float val, char* buffer, int n, int* attr) { +void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr) { if (val >= 0) { if (val < 99.9F) { if (val < 0.05F) { *attr = CRT_colors[PROCESS_SHADOW]; } - xSnprintf(buffer, n, "%4.1f ", val); - } else if (val < 999) { - *attr = CRT_colors[PROCESS_MEGABYTES]; - xSnprintf(buffer, n, "%3d. ", (int)val); + xSnprintf(buffer, n, "%*.1f ", width, val); } else { *attr = CRT_colors[PROCESS_MEGABYTES]; - xSnprintf(buffer, n, "%4d ", (int)val); + if (val < 100.0F) + val = 100.0F; // Don't round down and display "val" as "99". + xSnprintf(buffer, n, "%*.0f ", width, val); } } else { *attr = CRT_colors[PROCESS_SHADOW]; - xSnprintf(buffer, n, " N/A "); + xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); } } @@ -886,13 +888,13 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field xSnprintf(buffer, n, "%4ld ", this->nlwp); break; - case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, &attr); break; + case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break; case PERCENT_NORM_CPU: { float cpuPercentage = this->percent_cpu / this->processList->activeCPUs; - Process_printPercentage(cpuPercentage, buffer, n, &attr); + Process_printPercentage(cpuPercentage, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break; } - case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, &attr); break; + case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, 4, &attr); break; case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break; case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pid); break; case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->ppid); break; @@ -1011,7 +1013,7 @@ void Process_done(Process* this) { /* This function returns the string displayed in Command column, so that sorting * happens on what is displayed - whether comm, full path, basename, etc.. So * this follows Process_writeField(COMM) and Process_writeCommand */ -const char* Process_getCommandStr(const Process* this) { +const char* Process_getCommand(const Process* this) { if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !this->mergedCommand.str) { return this->cmdline; } @@ -1027,7 +1029,6 @@ const ProcessClass Process_class = { .compare = Process_compare }, .writeField = Process_writeField, - .getCommandStr = Process_getCommandStr, }; void Process_init(Process* this, const Settings* settings) { @@ -1251,3 +1252,36 @@ void Process_updateExe(Process* this, const char* exe) { } this->mergedCommand.exeChanged = true; } + +uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 }; + +void Process_resetFieldWidths() { + for (size_t i = 0; i < LAST_PROCESSFIELD; i++) { + if (!Process_fields[i].autoWidth) + continue; + + size_t len = strlen(Process_fields[i].title); + assert(len <= UINT8_MAX); + Process_fieldWidths[i] = (uint8_t)len; + } +} + +void Process_updateFieldWidth(ProcessField key, size_t width) { + if (width > UINT8_MAX) + Process_fieldWidths[key] = UINT8_MAX; + else if (width > Process_fieldWidths[key]) + Process_fieldWidths[key] = (uint8_t)width; +} + +void Process_updateCPUFieldWidths(float percentage) { + if (percentage < 99.9) { + Process_updateFieldWidth(PERCENT_CPU, 4); + Process_updateFieldWidth(PERCENT_NORM_CPU, 4); + return; + } + + uint8_t width = ceil(log10(percentage + .2)); + + Process_updateFieldWidth(PERCENT_CPU, width); + Process_updateFieldWidth(PERCENT_NORM_CPU, width); +} @@ -250,10 +250,10 @@ typedef struct Process_ { * Internal state for tree-mode. */ int indent; - unsigned int tree_left; - unsigned int tree_right; unsigned int tree_depth; - unsigned int tree_index; + + /* Has no known parent process */ + bool isRoot; /* * Internal state for merged Command display @@ -279,6 +279,9 @@ typedef struct ProcessFieldData_ { /* Whether the column should be sorted in descending order by default */ bool defaultSortDesc; + + /* Whether the column width is dynamically adjusted (the minimum width is determined by the title length) */ + bool autoWidth; } ProcessFieldData; // Implemented in platform-specific code: @@ -286,6 +289,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field int Process_compare(const void* v1, const void* v2); void Process_delete(Object* cast); extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; +extern uint8_t Process_fieldWidths[LAST_PROCESSFIELD]; #define PROCESS_MIN_PID_DIGITS 5 #define PROCESS_MAX_PID_DIGITS 19 #define PROCESS_MIN_UID_DIGITS 5 @@ -296,18 +300,15 @@ extern int Process_uidDigits; typedef Process* (*Process_New)(const struct Settings_*); typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField); typedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField); -typedef const char* (*Process_GetCommandStr)(const Process*); typedef struct ProcessClass_ { const ObjectClass super; const Process_WriteField writeField; const Process_CompareByKey compareByKey; - const Process_GetCommandStr getCommandStr; } ProcessClass; #define As_Process(this_) ((const ProcessClass*)((this_)->super.klass)) -#define Process_getCommand(this_) (As_Process(this_)->getCommandStr ? As_Process(this_)->getCommandStr((const Process*)(this_)) : Process_getCommandStr((const Process*)(this_))) #define Process_compareByKey(p1_, p2_, key_) (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_)) static inline pid_t Process_getParentPid(const Process* this) { @@ -371,7 +372,7 @@ void Process_fillStarttimeBuffer(Process* this); void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width); -void Process_printPercentage(float val, char* buffer, int n, int* attr); +void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr); void Process_display(const Object* cast, RichString* out); @@ -397,17 +398,20 @@ int Process_pidCompare(const void* v1, const void* v2); int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key); -// Avoid direct calls, use Process_getCommand instead -const char* Process_getCommandStr(const Process* this); +const char* Process_getCommand(const Process* this); void Process_updateComm(Process* this, const char* comm); void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd); void Process_updateExe(Process* this, const char* exe); /* This function constructs the string that is displayed by - * Process_writeCommand and also returned by Process_getCommandStr */ + * Process_writeCommand and also returned by Process_getCommand */ void Process_makeCommandStr(Process* this); void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str); +void Process_resetFieldWidths(void); +void Process_updateFieldWidth(ProcessField key, size_t width); +void Process_updateCPUFieldWidths(float percentage); + #endif diff --git a/ProcessList.c b/ProcessList.c index 1df1990d..f3e184e8 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -22,11 +22,10 @@ in the source distribution for its full text. ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { this->processes = Vector_new(klass, true, DEFAULT_SIZE); - this->processes2 = Vector_new(klass, true, DEFAULT_SIZE); // tree-view auxiliary buffer + this->displayList = Vector_new(klass, false, DEFAULT_SIZE); this->processTable = Hashtable_new(200, false); - this->displayTreeSet = Hashtable_new(200, false); - this->draftingTreeSet = Hashtable_new(200, false); + this->needsSort = true; this->usersTable = usersTable; this->pidMatchList = pidMatchList; @@ -73,11 +72,9 @@ void ProcessList_done(ProcessList* this) { } #endif - Hashtable_delete(this->draftingTreeSet); - Hashtable_delete(this->displayTreeSet); Hashtable_delete(this->processTable); - Vector_delete(this->processes2); + Vector_delete(this->displayList); Vector_delete(this->processes); } @@ -117,6 +114,12 @@ static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessFiel return titleBuffer; } + if (Process_fields[field].autoWidth) { + static char titleBuffer[UINT8_MAX + 1]; + xSnprintf(titleBuffer, sizeof(titleBuffer), "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title); + return titleBuffer; + } + return title; } @@ -194,308 +197,140 @@ void ProcessList_remove(ProcessList* this, const Process* p) { assert(Hashtable_count(this->processTable) == Vector_count(this->processes)); } -// ProcessList_updateTreeSetLayer sorts this->displayTreeSet, -// relying only on itself. -// -// Algorithm -// -// The algorithm is based on `depth-first search`, -// even though `breadth-first search` approach may be more efficient on first glance, -// after comparison it may be not, as it's not safe to go deeper without first updating the tree structure. -// If it would be safe that approach would likely bring an advantage in performance. -// -// Each call of the function looks for a 'layer'. A 'layer' is a list of processes with the same depth. -// First it sorts a list. Then it runs the function recursively for each element of the sorted list. -// After that it updates the settings of processes. -// -// It relies on `leftBound` and `rightBound` as an optimization to cut the list size at the time it builds a 'layer'. -// -// It uses a temporary Hashtable `draftingTreeSet` because it's not safe to traverse a tree -// and at the same time make changes in it. -// -static void ProcessList_updateTreeSetLayer(ProcessList* this, unsigned int leftBound, unsigned int rightBound, unsigned int deep, unsigned int left, unsigned int right, unsigned int* index, unsigned int* treeIndex, int indent) { - - // It's guaranteed that layer_size is enough space - // but most likely it needs less. Specifically on first iteration. - int layerSize = (right - left) / 2; - - // check if we reach `children` of `leaves` - if (layerSize == 0) - return; - - Vector* layer = Vector_new(Vector_type(this->processes), false, layerSize); - - // Find all processes on the same layer (process with the same `deep` value - // and included in a range from `leftBound` to `rightBound`). - // - // This loop also keeps track of left_bound and right_bound of these processes - // in order not to lose this information once the list is sorted. - // - // The variables left_bound and right_bound are different from what the values lhs and rhs represent. - // While left_bound and right_bound define a range of processes to look at, the values given by lhs and rhs are indices into an array - // - // In the below example note how filtering a range of indices i is different from filtering for processes in the bounds left_bound < x < right_bound … - // - // The nested tree set is sorted by left value, which is guaranteed upon entry/exit of this function. - // - // i | l | r - // 1 | 1 | 9 - // 2 | 2 | 8 - // 3 | 4 | 5 - // 4 | 6 | 7 - for (unsigned int i = leftBound; i < rightBound; i++) { - Process* proc = (Process*)Hashtable_get(this->displayTreeSet, i); - assert(proc); - if (proc && proc->tree_depth == deep && proc->tree_left > left && proc->tree_right < right) { - if (Vector_size(layer) > 0) { - Process* previous_process = (Process*)Vector_get(layer, Vector_size(layer) - 1); - - // Make a 'right_bound' of previous_process in a layer the current process's index. - // - // Use 'tree_depth' as a temporal variable. - // It's safe to do as later 'tree_depth' will be renovated. - previous_process->tree_depth = proc->tree_index; - } - - Vector_add(layer, proc); - } - } - - // The loop above changes just up to process-1. - // So the last process of the layer isn't updated by the above code. - // - // Thus, if present, set the `rightBound` to the last process on the layer - if (Vector_size(layer) > 0) { - Process* previous_process = (Process*)Vector_get(layer, Vector_size(layer) - 1); - previous_process->tree_depth = rightBound; - } - - Vector_quickSort(layer); - - int size = Vector_size(layer); - for (int i = 0; i < size; i++) { - Process* proc = (Process*)Vector_get(layer, i); - - unsigned int idx = (*index)++; - int newLeft = (*treeIndex)++; - - int level = deep == 0 ? 0 : (int)deep - 1; - int currentIndent = indent == -1 ? 0 : inden |