Skip to content

Commit

Permalink
Add option to merge processes of same application
Browse files Browse the repository at this point in the history
Experimental - Linux only

Closes: htop-dev#301
  • Loading branch information
cgzones committed Mar 19, 2021
1 parent aacbfef commit 4d496be
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 64 deletions.
7 changes: 7 additions & 0 deletions Action.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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" },
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions DisplayOptionsPanel.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down
67 changes: 67 additions & 0 deletions Process.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
11 changes: 11 additions & 0 deletions Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_ {
Expand All @@ -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];
Expand All @@ -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))
Expand Down
Loading

0 comments on commit 4d496be

Please sign in to comment.