From 4d496bec256992e40c2ef143b64464f6431edc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Tue, 3 Nov 2020 23:00:29 +0100 Subject: [PATCH] Add option to merge processes of same application Experimental - Linux only Closes: #301 --- Action.c | 7 ++ DisplayOptionsPanel.c | 1 + Process.c | 67 +++++++++++++++++ Process.h | 11 +++ ProcessList.c | 170 ++++++++++++++++++++++++++++-------------- ProcessList.h | 17 +++-- Settings.c | 4 + Settings.h | 1 + Vector.c | 11 +++ Vector.h | 2 + linux/LinuxProcess.c | 85 ++++++++++++++++++++- 11 files changed, 312 insertions(+), 64 deletions(-) diff --git a/Action.c b/Action.c index a838570831..40212e3e31 100644 --- a/Action.c +++ b/Action.c @@ -240,6 +240,11 @@ static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) { return HTOP_REFRESH | HTOP_SAVE_SETTINGS; } +static Htop_Reaction actionToggleMergeApplication(State* st) { + st->settings->mergeApplications = !st->settings->mergeApplications; + return HTOP_REFRESH | HTOP_SAVE_SETTINGS; +} + static Htop_Reaction actionIncFilter(State* st) { IncSet* inc = (st->mainPanel)->inc; IncSet_activate(inc, INC_FILTER, (Panel*)st->mainPanel); @@ -471,6 +476,7 @@ static const struct { { .key = " x: ", .info = "list file locks of process" }, { .key = " s: ", .info = "trace syscalls with strace" }, { .key = " w: ", .info = "wrap process command in multiple lines" }, + { .key = " A: ", .info = "Merge processes of same application" }, { .key = " F2 C S: ", .info = "setup" }, { .key = " F1 h: ", .info = "show this help screen" }, { .key = " F10 q: ", .info = "quit" }, @@ -638,6 +644,7 @@ void Action_setBindings(Htop_Action* keys) { keys['='] = actionExpandOrCollapse; keys['>'] = actionSetSortColumn; keys['?'] = actionHelp; + keys['A'] = actionToggleMergeApplication; keys['C'] = actionSetup; keys['F'] = Action_follow; keys['H'] = actionToggleUserlandThreads; diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index 884045f3a0..06dac597f1 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -100,6 +100,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->treeView))); Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->treeViewAlwaysByPID))); Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->allBranchesCollapsed))); + Panel_add(super, (Object*) CheckItem_newByRef("Merge processes of same applications", &(settings->mergeApplications))); Panel_add(super, (Object*) CheckItem_newByRef("Shadow other users' processes", &(settings->shadowOtherUsers))); Panel_add(super, (Object*) CheckItem_newByRef("Hide kernel threads", &(settings->hideKernelThreads))); Panel_add(super, (Object*) CheckItem_newByRef("Hide userland process threads", &(settings->hideUserlandThreads))); diff --git a/Process.c b/Process.c index b67ad445f3..074cdb0d6c 100644 --- a/Process.c +++ b/Process.c @@ -257,6 +257,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field baseattr = CRT_colors[PROCESS_THREAD_BASENAME]; } if (!this->settings->treeView || this->indent == 0) { + if (this->merged > 1) { + char merged[16]; + xSnprintf(merged, sizeof(merged), "[%u] ", this->merged); + RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], merged); + } Process_writeCommand(this, attr, baseattr, str); return; } @@ -291,6 +296,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE]; xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] ); RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer); + if (this->merged > 1) { + char merged[16]; + xSnprintf(merged, sizeof(merged), "[%u] ", this->merged); + RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], merged); + } Process_writeCommand(this, attr, baseattr, str); return; } @@ -502,6 +512,33 @@ int Process_pidCompare(const void* v1, const void* v2) { return SPACESHIP_NUMBER(p1->pid, p2->pid); } +static bool isTransitiveChildOf(const Process* child, const Process* parent) { + assert(child->processList == parent->processList); + + for (const Process* tChild = child; tChild; tChild = ProcessList_findProcess(parent->processList, Process_getParentPid(tChild))) + if (Process_isChildOf(tChild, parent->pid)) + return true; + + return false; +} + +int Process_sameApplication(const Process* v1, const Process* v2) { + if (v1->session != v2->session) + return 0; + + // we can compare pointers since the field user points to a hashtable entry + if (v1->user != v2->user) + return 0; + + if (isTransitiveChildOf(v1, v2)) + return 2; + + if (isTransitiveChildOf(v2, v1)) + return 1; + + return 0; +} + int Process_compare(const void* v1, const void* v2) { const Process *p1 = (const Process*)v1; const Process *p2 = (const Process*)v2; @@ -613,3 +650,33 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField return SPACESHIP_NUMBER(p1->pid, p2->pid); } } + +void Process_mergeData(Process* p1, const Process* p2) { + + p1->percent_cpu += p2->percent_cpu; + p1->percent_mem += p2->percent_mem; + // keep COMM + p1->majflt += p2->majflt; + p1->minflt += p2->minflt; + p1->m_resident += p2->m_resident; + p1->m_virt += p2->m_virt; + // store min NICE + p1->nice = MINIMUM(p1->nice, p2->nice); + p1->nlwp += p2->nlwp; + // keep PGRP + // keep PID + // keep PPID + p1->priority = MAXIMUM(p1->priority, p2->priority); + // keep PROCESSOR + // keep SESSION + p1->starttime_ctime = MINIMUM(p1->starttime_ctime, p2->starttime_ctime); + // keep STATE + // keep ST_UID + p1->time += p2->time; + // keep TGID + // keep TPGID + // keep TTY_NR + // keep USER + + p1->merged += p2->merged; +} diff --git a/Process.h b/Process.h index e9c42622d4..dffe5b48d6 100644 --- a/Process.h +++ b/Process.h @@ -185,6 +185,8 @@ typedef struct Process_ { unsigned int tree_right; unsigned int tree_depth; unsigned int tree_index; + + unsigned int merged; } Process; typedef struct ProcessFieldData_ { @@ -210,6 +212,11 @@ typedef struct ProcessFieldData_ { // Implemented in platform-specific code: void Process_writeField(const Process* this, RichString* str, ProcessField field); int Process_compare(const void* v1, const void* v2); +/* returns 1 on match and if v1 is the master process, + * 2 on match and if v2 is the master process, + * 0 else */ +int Process_sameApplication(const Process* p1, const Process* p2); +void Process_mergeData(Process* p1, const Process* p2); void Process_delete(Object* cast); bool Process_isThread(const Process* this); extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; @@ -220,12 +227,16 @@ 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 int (*Process_SameApplication)(const Process*, const Process*); +typedef void (*Process_MergeData)(Process*, const Process*); typedef struct ProcessClass_ { const ObjectClass super; const Process_WriteField writeField; const Process_CompareByKey compareByKey; const Process_GetCommandStr getCommandStr; + const Process_SameApplication sameApplication; + const Process_MergeData mergeData; } ProcessClass; #define As_Process(this_) ((const ProcessClass*)((this_)->super.klass)) diff --git a/ProcessList.c b/ProcessList.c index e1db8b314b..f5e93abfad 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -20,10 +20,10 @@ in the source distribution for its full text. ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, 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->rawProcesses = Vector_new(klass, true, 200); + this->mergedProcesses = Vector_new(klass, false, 200); this->processTable = Hashtable_new(200, false); + this->displayTreeSet = Hashtable_new(200, false); this->draftingTreeSet = Hashtable_new(200, false); @@ -71,8 +71,8 @@ void ProcessList_done(ProcessList* this) { Hashtable_delete(this->displayTreeSet); Hashtable_delete(this->processTable); - Vector_delete(this->processes2); - Vector_delete(this->processes); + Vector_delete(this->mergedProcesses); + Vector_delete(this->rawProcesses); } void ProcessList_setPanel(ProcessList* this, Panel* panel) { @@ -126,34 +126,34 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header) { } void ProcessList_add(ProcessList* this, Process* p) { - assert(Vector_indexOf(this->processes, p, Process_pidCompare) == -1); + assert(Vector_indexOf(this->rawProcesses, p, Process_pidCompare) == -1); assert(Hashtable_get(this->processTable, p->pid) == NULL); p->processList = this; // highlighting processes found in first scan by first scan marked "far in the past" p->seenTs = this->scanTs; - Vector_add(this->processes, p); + Vector_add(this->rawProcesses, p); Hashtable_put(this->processTable, p->pid, p); - assert(Vector_indexOf(this->processes, p, Process_pidCompare) != -1); + assert(Vector_indexOf(this->rawProcesses, p, Process_pidCompare) != -1); assert(Hashtable_get(this->processTable, p->pid) != NULL); - assert(Hashtable_count(this->processTable) == Vector_count(this->processes)); + assert(Hashtable_count(this->processTable) == Vector_count(this->rawProcesses)); } void ProcessList_remove(ProcessList* this, const Process* p) { - assert(Vector_indexOf(this->processes, p, Process_pidCompare) != -1); + assert(Vector_indexOf(this->rawProcesses, p, Process_pidCompare) != -1); assert(Hashtable_get(this->processTable, p->pid) != NULL); const Process* pp = Hashtable_remove(this->processTable, p->pid); assert(pp == p); (void)pp; pid_t pid = p->pid; - int idx = Vector_indexOf(this->processes, p, Process_pidCompare); + int idx = Vector_indexOf(this->rawProcesses, p, Process_pidCompare); assert(idx != -1); if (idx >= 0) { - Vector_remove(this->processes, idx); + Vector_remove(this->rawProcesses, idx); } if (this->following != -1 && this->following == pid) { @@ -162,7 +162,7 @@ void ProcessList_remove(ProcessList* this, const Process* p) { } assert(Hashtable_get(this->processTable, pid) == NULL); - assert(Hashtable_count(this->processTable) == Vector_count(this->processes)); + assert(Hashtable_count(this->processTable) == Vector_count(this->rawProcesses)); } // ProcessList_updateTreeSetLayer sorts this->displayTreeSet, @@ -194,7 +194,7 @@ static void ProcessList_updateTreeSetLayer(ProcessList* this, unsigned int leftB if (layerSize == 0) return; - Vector* layer = Vector_new(Vector_type(this->processes), false, layerSize); + Vector* layer = Vector_new(Vector_type(this->rawProcesses), false, layerSize); // Find all processes on the same layer (process with the same `deep` value // and included in a range from `leftBound` to `rightBound`). @@ -283,11 +283,11 @@ static void ProcessList_updateTreeSetLayer(ProcessList* this, unsigned int leftB Vector_delete(layer); } -static void ProcessList_updateTreeSet(ProcessList* this) { +static void ProcessList_updateTreeSet(ProcessList* this, Vector* processesToDisplay) { unsigned int index = 0; unsigned int tree_index = 1; - const int vsize = Vector_size(this->processes); + const int vsize = Vector_size(processesToDisplay); assert(Hashtable_count(this->draftingTreeSet) == 0); assert((int)Hashtable_count(this->displayTreeSet) == vsize); @@ -302,13 +302,13 @@ static void ProcessList_updateTreeSet(ProcessList* this) { assert((int)Hashtable_count(this->displayTreeSet) == vsize); } -static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, int direction, bool show, int* node_counter, int* node_index) { +static void ProcessList_buildTreeBranch(ProcessList* this, Vector* processesToDisplay, Vector* draftingProcesses, pid_t pid, int level, int indent, int direction, bool show, int* node_counter, int* node_index) { Vector* children = Vector_new(Class(Process), false, DEFAULT_SIZE); - for (int i = Vector_size(this->processes) - 1; i >= 0; i--) { - Process* process = (Process*)Vector_get(this->processes, i); + for (int i = Vector_size(processesToDisplay) - 1; i >= 0; i--) { + Process* process = (Process*)Vector_get(processesToDisplay, i); if (process->show && Process_isChildOf(process, pid)) { - process = (Process*)Vector_take(this->processes, i); + process = (Process*)Vector_take(processesToDisplay, i); Vector_add(children, process); } } @@ -324,17 +324,17 @@ static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, process->show = false; } - int s = Vector_size(this->processes2); + int s = Vector_size(draftingProcesses); if (direction == 1) { - Vector_add(this->processes2, process); + Vector_add(draftingProcesses, process); } else { - Vector_insert(this->processes2, 0, process); + Vector_insert(draftingProcesses, 0, process); } - assert(Vector_size(this->processes2) == s + 1); (void)s; + assert(Vector_size(draftingProcesses) == s + 1); (void)s; int nextIndent = indent | (1 << level); - ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < size - 1) ? nextIndent : indent, direction, show ? process->showChildren : false, node_counter, node_index); + ProcessList_buildTreeBranch(this, processesToDisplay, draftingProcesses, process->pid, level + 1, (i < size - 1) ? nextIndent : indent, direction, show ? process->showChildren : false, node_counter, node_index); if (i == size - 1) { process->indent = -nextIndent; } else { @@ -366,32 +366,41 @@ static int ProcessList_treeProcessCompareByPID(const void* v1, const void* v2) { return SPACESHIP_NUMBER(p1->pid, p2->pid); } +static int ProcessList_ProcessSessionCompare(const void* v1, const void* v2) { + const Process *p1 = (const Process*)v1; + const Process *p2 = (const Process*)v2; + + return SPACESHIP_NUMBER(p1->session, p2->session); +} + // Builds a sorted tree from scratch, without relying on previously gathered information -static void ProcessList_buildTree(ProcessList* this) { +static void ProcessList_buildTree(ProcessList* this, Vector** processesToDisplay) { int node_counter = 1; int node_index = 0; int direction = Settings_getActiveDirection(this->settings); // Sort by PID - Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompareByPID); - int vsize = Vector_size(this->processes); + Vector_quickSortCustomCompare(*processesToDisplay, ProcessList_treeProcessCompareByPID); + int vsize = Vector_size(*processesToDisplay); + + Vector* draftingProcesses = Vector_new((*processesToDisplay)->type, (*processesToDisplay)->owner, Vector_size(*processesToDisplay)); // Find all processes whose parent is not visible int size; - while ((size = Vector_size(this->processes))) { + while ((size = Vector_size(*processesToDisplay))) { int i; for (i = 0; i < size; i++) { - Process* process = (Process*)Vector_get(this->processes, i); + Process* process = (Process*)Vector_get(*processesToDisplay, i); // Immediately consume processes hidden from view if (!process->show) { - process = (Process*)Vector_take(this->processes, i); + process = (Process*)Vector_take(*processesToDisplay, i); process->indent = 0; process->tree_depth = 0; process->tree_left = node_counter++; process->tree_index = node_index++; - Vector_add(this->processes2, process); - ProcessList_buildTreeBranch(this, process->pid, 0, 0, direction, false, &node_counter, &node_index); + Vector_add(draftingProcesses, process); + ProcessList_buildTreeBranch(this, *processesToDisplay, draftingProcesses, process->pid, 0, 0, direction, false, &node_counter, &node_index); process->tree_right = node_counter++; Hashtable_put(this->displayTreeSet, process->tree_index, process); break; @@ -416,7 +425,7 @@ static void ProcessList_buildTree(ProcessList* this) { while (l < r) { int c = (l + r) / 2; - pid_t pid = ((Process*)Vector_get(this->processes, c))->pid; + pid_t pid = ((Process*)Vector_get(*processesToDisplay, c))->pid; if (ppid == pid) { break; } else if (ppid < pid) { @@ -428,14 +437,14 @@ static void ProcessList_buildTree(ProcessList* this) { // If parent not found, then construct the tree with this node as root if (l >= r) { - process = (Process*)Vector_take(this->processes, i); + process = (Process*)Vector_take(*processesToDisplay, i); process->indent = 0; process->tree_depth = 0; process->tree_left = node_counter++; process->tree_index = node_index++; - Vector_add(this->processes2, process); + Vector_add(draftingProcesses, process); Hashtable_put(this->displayTreeSet, process->tree_index, process); - ProcessList_buildTreeBranch(this, process->pid, 0, 0, direction, process->showChildren, &node_counter, &node_index); + ProcessList_buildTreeBranch(this, *processesToDisplay, draftingProcesses, process->pid, 0, 0, direction, process->showChildren, &node_counter, &node_index); process->tree_right = node_counter++; break; } @@ -446,21 +455,23 @@ static void ProcessList_buildTree(ProcessList* this) { } // Swap listings around - Vector* t = this->processes; - this->processes = this->processes2; - this->processes2 = t; + Vector_swap(processesToDisplay, &draftingProcesses); // Check consistency of the built structures - assert(Vector_size(this->processes) == vsize); (void)vsize; - assert(Vector_size(this->processes2) == 0); + assert(Vector_size(*processesToDisplay) == vsize); (void)vsize; + assert(Vector_size(draftingProcesses) == 0); + + Vector_delete(draftingProcesses); } void ProcessList_sort(ProcessList* this) { + Vector* processesToDisplay = this->settings->mergeApplications ? this->mergedProcesses : this->rawProcesses; + if (this->settings->treeView) { - ProcessList_updateTreeSet(this); - Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompare); + ProcessList_updateTreeSet(this, processesToDisplay); + Vector_quickSortCustomCompare(processesToDisplay, ProcessList_treeProcessCompare); } else { - Vector_insertionSort(this->processes); + Vector_insertionSort(processesToDisplay); } } @@ -479,17 +490,18 @@ ProcessField ProcessList_keyAt(const ProcessList* this, int at) { } void ProcessList_expandTree(ProcessList* this) { - int size = Vector_size(this->processes); + int size = Vector_size(this->rawProcesses); for (int i = 0; i < size; i++) { - Process* process = (Process*) Vector_get(this->processes, i); + Process* process = (Process*) Vector_get(this->rawProcesses, i); process->showChildren = true; } } void ProcessList_collapseAllBranches(ProcessList* this) { - int size = Vector_size(this->processes); + Vector* processesToDisplay = this->settings->mergeApplications ? this->mergedProcesses : this->rawProcesses; + int size = Vector_size(processesToDisplay); for (int i = 0; i < size; i++) { - Process* process = (Process*) Vector_get(this->processes, i); + Process* process = (Process*) Vector_get(processesToDisplay, i); // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1 if (process->tree_depth > 0 && process->pid > 1) process->showChildren = false; @@ -498,6 +510,7 @@ void ProcessList_collapseAllBranches(ProcessList* this) { void ProcessList_rebuildPanel(ProcessList* this) { const char* incFilter = this->incFilter; + Vector* processesToDisplay = this->settings->mergeApplications ? this->mergedProcesses : this->rawProcesses; const int currPos = Panel_getSelectedIndex(this->panel); const int currScrollV = this->panel->scrollV; @@ -514,12 +527,12 @@ void ProcessList_rebuildPanel(ProcessList* this) { } } - const int processCount = Vector_size(this->processes); + const int processCount = Vector_size(processesToDisplay); int idx = 0; bool foundFollowed = false; for (int i = 0; i < processCount; i++) { - Process* p = (Process*) Vector_get(this->processes, i); + Process* p = (Process*) Vector_get(processesToDisplay, i); if ( (!p->show) || (this->userId != (uid_t) -1 && (p->st_uid != this->userId)) @@ -558,7 +571,7 @@ Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process* proc = (Process*) Hashtable_get(this->processTable, pid); *preExisting = proc; if (proc) { - assert(Vector_indexOf(this->processes, proc, Process_pidCompare) != -1); + assert(Vector_indexOf(this->rawProcesses, proc, Process_pidCompare) != -1); assert(proc->pid == pid); } else { proc = constructor(this->settings); @@ -578,11 +591,12 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { } // mark all process as "dirty" - for (int i = 0; i < Vector_size(this->processes); i++) { - Process* p = (Process*) Vector_get(this->processes, i); + for (int i = 0; i < Vector_size(this->rawProcesses); i++) { + Process* p = (Process*) Vector_get(this->rawProcesses, i); p->updated = false; p->wasShown = p->show; p->show = true; + p->merged = 1; } this->totalTasks = 0; @@ -604,8 +618,8 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { ProcessList_goThroughEntries(this, false); - for (int i = Vector_size(this->processes) - 1; i >= 0; i--) { - Process* p = (Process*) Vector_get(this->processes, i); + for (int i = Vector_size(this->rawProcesses) - 1; i >= 0; i--) { + Process* p = (Process*) Vector_get(this->rawProcesses, i); if (p->tombTs > 0) { // remove tombed process if (this->scanTs >= p->tombTs) { @@ -625,6 +639,48 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { } } + if (this->settings->mergeApplications) { + Vector_prune(this->mergedProcesses); + + // Sort by session + Vector_insertionSortCustomCompare(this->rawProcesses, ProcessList_ProcessSessionCompare); + + for (int i = 0; i < Vector_size(this->rawProcesses); i++) { + Process* curProc = (Process*) Vector_get(this->rawProcesses, i); + curProc->merged = 1; + + if (Process_isThread(curProc)) + continue; + + const int size = Vector_size(this->mergedProcesses); + if (size == 0 || ((const Process*)Vector_get(this->mergedProcesses, size - 1))->session < curProc->session) { + // first process with this session id -> just insert + Vector_add(this->mergedProcesses, curProc); + continue; + } + + bool inserted = false; + // check in reverse if merge with already inserted process + for (int idx = size - 1; idx >= 0 && ((const Process*)Vector_get(this->mergedProcesses, idx))->session == curProc->session; idx--) { + Process* testProc = (Process*) Vector_get(this->mergedProcesses, idx); + int r = As_Process(testProc)->sameApplication(testProc, curProc); + if (r == 1) { + As_Process(testProc)->mergeData(testProc, curProc); + inserted = true; + break; + } + if (r == 2) { + As_Process(testProc)->mergeData(curProc, testProc); + Vector_add(this->mergedProcesses, curProc); + inserted = true; + break; + } + } + if (!inserted) + Vector_add(this->mergedProcesses, curProc); + } + } + if (this->settings->treeView) { // Clear out the hashtable to avoid any left-over processes from previous build // @@ -632,6 +688,6 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { // len(this->displayTreeSet) == len(this->processes) Hashtable_clear(this->displayTreeSet); - ProcessList_buildTree(this); + ProcessList_buildTree(this, this->settings->mergeApplications ? &this->mergedProcesses : &this->rawProcesses); } } diff --git a/ProcessList.h b/ProcessList.h index 68d15a24d9..057e205cf4 100644 --- a/ProcessList.h +++ b/ProcessList.h @@ -40,13 +40,14 @@ typedef unsigned long long int memory_t; typedef struct ProcessList_ { const Settings* settings; - Vector* processes; - Vector* processes2; - Hashtable* processTable; - UsersTable* usersTable; + Vector* rawProcesses; /**< actually existing processes */ + Vector* mergedProcesses; /**< merged processes to display */ + Hashtable* processTable; /**< hashtable of actually existing processes for quick search */ + + Hashtable* displayTreeSet; /**< auxiliary hashtable for sorted tree view */ + Hashtable* draftingTreeSet; /**< auxiliary hashtable for sorted tree view */ - Hashtable* displayTreeSet; - Hashtable* draftingTreeSet; + UsersTable* usersTable; Panel* panel; int following; @@ -107,6 +108,10 @@ void ProcessList_collapseAllBranches(ProcessList* this); void ProcessList_rebuildPanel(ProcessList* this); +static inline const Process* ProcessList_findProcess(const ProcessList* this, pid_t pid) { + return (Process*) Hashtable_get(this->processTable, pid); +} + Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process_New constructor); void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate); diff --git a/Settings.c b/Settings.c index 3880bb1668..6243e1fea2 100644 --- a/Settings.c +++ b/Settings.c @@ -163,6 +163,8 @@ static bool Settings_read(Settings* this, const char* fileName, int initialCpuCo this->treeViewAlwaysByPID = atoi(option[1]); } else if (String_eq(option[0], "all_branches_collapsed")) { this->allBranchesCollapsed = atoi(option[1]); + } else if (String_eq(option[0], "merge_applications")) { + this->mergeApplications = atoi(option[1]); } else if (String_eq(option[0], "hide_kernel_threads")) { this->hideKernelThreads = atoi(option[1]); } else if (String_eq(option[0], "hide_userland_threads")) { @@ -310,6 +312,7 @@ int Settings_write(const Settings* this) { fprintf(fd, "tree_view=%d\n", (int) this->treeView); fprintf(fd, "tree_view_always_by_pid=%d\n", (int) this->treeViewAlwaysByPID); fprintf(fd, "all_branches_collapsed=%d\n", (int) this->allBranchesCollapsed); + fprintf(fd, "merge_applications=%d\n", (int) this->mergeApplications); fprintf(fd, "header_margin=%d\n", (int) this->headerMargin); fprintf(fd, "detailed_cpu_time=%d\n", (int) this->detailedCPUTime); fprintf(fd, "cpu_count_from_one=%d\n", (int) this->countCPUsFromOne); @@ -357,6 +360,7 @@ Settings* Settings_new(int initialCpuCount) { this->hideUserlandThreads = false; this->treeView = false; this->allBranchesCollapsed = false; + this->mergeApplications = false; this->highlightBaseName = false; this->highlightMegabytes = false; this->detailedCPUTime = false; diff --git a/Settings.h b/Settings.h index 3e7abdee51..1176bcd4bb 100644 --- a/Settings.h +++ b/Settings.h @@ -48,6 +48,7 @@ typedef struct Settings_ { bool treeView; bool treeViewAlwaysByPID; bool allBranchesCollapsed; + bool mergeApplications; bool showProgramPath; bool shadowOtherUsers; bool showThreadNames; diff --git a/Vector.c b/Vector.c index a2c628200a..8fe616a03c 100644 --- a/Vector.c +++ b/Vector.c @@ -338,3 +338,14 @@ void Vector_splice(Vector* this, Vector* from) { this->array[olditems + j] = from->array[j]; } } + +void Vector_swap(Vector** a, Vector** b) { + assert(Vector_isConsistent(*a)); + assert(Vector_isConsistent(*b)); + assert((*a)->type == (*b)->type); + assert((*a)->owner == (*b)->owner); + + Vector* tmp = *a; + *a = *b; + *b = tmp; +} diff --git a/Vector.h b/Vector.h index f3de1636b5..85829b0699 100644 --- a/Vector.h +++ b/Vector.h @@ -81,4 +81,6 @@ int Vector_indexOf(const Vector* this, const void* search_, Object_Compare compa void Vector_splice(Vector* this, Vector* from); +void Vector_swap(Vector** a, Vector** b); + #endif diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 764f015110..bdae1a2610 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -567,6 +567,11 @@ static void LinuxProcess_writeCommandField(const Process *this, RichString *str, baseattr = CRT_colors[PROCESS_THREAD_BASENAME]; } if (!this->settings->treeView || this->indent == 0) { + if (this->merged > 1) { + char merged[16]; + xSnprintf(merged, sizeof(merged), "[%u] ", this->merged); + RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], merged); + } LinuxProcess_writeCommand(this, attr, baseattr, str); } else { char* buf = buffer; @@ -599,6 +604,11 @@ static void LinuxProcess_writeCommandField(const Process *this, RichString *str, const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE]; xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] ); RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer); + if (this->merged > 1) { + char merged[16]; + xSnprintf(merged, sizeof(merged), "[%u] ", this->merged); + RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], merged); + } LinuxProcess_writeCommand(this, attr, baseattr, str); } } @@ -861,6 +871,77 @@ bool Process_isThread(const Process* this) { return (Process_isUserlandThread(this) || Process_isKernelThread(this)); } +static int LinuxProcess_sameApplication(const Process* p1, const Process* p2) { + const LinuxProcess* lp1 = (const LinuxProcess*) p1; + const LinuxProcess* lp2 = (const LinuxProcess*) p2; + const char* exe1 = lp1->procExe; + const char* exe2 = lp2->procExe; + + if (!exe1 || !exe2 || !String_eq(exe1, exe2)) + return 0; + + return Process_sameApplication(p1, p2); +} + +static void LinuxProcess_mergeData(Process* p1, const Process* p2) { + //LinuxProcess* lp1 = (LinuxProcess*) p1; + //const LinuxProcess* lp2 = (const LinuxProcess*) p2; + + // TODO + + /* + * TTY_NR + * CMINFLT + * CMAJFLT + * M_DRS + * M_DT + * M_LRS + * M_TRS + * M_SHARE + * M_PSS + * M_SWAP + * M_PSSWP + * UTIME + * STIME + * CUTIME + * CSTIME + * #ifdef HAVE_TASKSTATS + * RCHAR + * WCHAR + * SYSCR + * SYSCW + * RBYTES + * WBYTES + * CNCLWB + * IO_READ_RATE + * IO_WRITE_RATE + * IO_RATE + * #endif + * #ifdef HAVE_OPENVZ + * CTID + * VPID + * #endif + * #ifdef HAVE_VSERVER + * VXID + * #endif + * #ifdef HAVE_CGROUP + * CGROUP + * #endif + * OOM + * IO_PRIORITY + * #ifdef HAVE_DELAYACCT + * PERCENT_CPU_DELAY + * PERCENT_IO_DELAY + * PERCENT_SWAP_DELAY + * #endif + * CTXT + * SECATTR + * EXE + */ + + Process_mergeData(p1, p2); +} + const ProcessClass LinuxProcess_class = { .super = { .extends = Class(Process), @@ -870,5 +951,7 @@ const ProcessClass LinuxProcess_class = { }, .writeField = LinuxProcess_writeField, .getCommandStr = LinuxProcess_getCommandStr, - .compareByKey = LinuxProcess_compareByKey + .compareByKey = LinuxProcess_compareByKey, + .sameApplication = LinuxProcess_sameApplication, + .mergeData = LinuxProcess_mergeData };