Skip to content

Commit fa077f4

Browse files
committed
Add option to merge processes of same application
Experimental - Linux only Closes: htop-dev#301
1 parent fd86564 commit fa077f4

14 files changed

+341
-92
lines changed

Action.c

+7
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ static Htop_Reaction actionToggleTreeView(State* st) {
220220
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
221221
}
222222

223+
static Htop_Reaction actionToggleMergeApplication(State* st) {
224+
st->settings->mergeApplications = !st->settings->mergeApplications;
225+
return HTOP_REFRESH | HTOP_SAVE_SETTINGS;
226+
}
227+
223228
static Htop_Reaction actionIncFilter(State* st) {
224229
IncSet* inc = ((MainPanel*)st->panel)->inc;
225230
IncSet_activate(inc, INC_FILTER, st->panel);
@@ -437,6 +442,7 @@ static const struct { const char* key; const char* info; } helpRight[] = {
437442
{ .key = " l: ", .info = "list open files with lsof" },
438443
{ .key = " s: ", .info = "trace syscalls with strace" },
439444
{ .key = " w: ", .info = "wrap process command in multiple lines" },
445+
{ .key = " A: ", .info = "Merge processes of same application" },
440446
{ .key = " F2 C S: ", .info = "setup" },
441447
{ .key = " F1 h: ", .info = "show this help screen" },
442448
{ .key = " F10 q: ", .info = "quit" },
@@ -632,4 +638,5 @@ void Action_setBindings(Htop_Action* keys) {
632638
keys['e'] = actionShowEnvScreen;
633639
keys['w'] = actionShowCommandScreen;
634640
keys['Z'] = actionTogglePauseProcessUpdate;
641+
keys['A'] = actionToggleMergeApplication;
635642
}

DisplayOptionsPanel.c

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
7777

7878
Panel_setHeader(super, "Display options");
7979
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Tree view"), &(settings->treeView)));
80+
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Merge processes of same applications"), &(settings->mergeApplications)));
8081
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Shadow other users' processes"), &(settings->shadowOtherUsers)));
8182
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Hide kernel threads"), &(settings->hideKernelThreads)));
8283
Panel_add(super, (Object*) CheckItem_newByRef(xStrdup("Hide userland process threads"), &(settings->hideUserlandThreads)));

MainPanel.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
5959

6060
if (EVENT_IS_HEADER_CLICK(ch)) {
6161
int x = EVENT_HEADER_CLICK_GET_X(ch);
62-
ProcessList* pl = this->state->pl;
62+
const ProcessList* pl = this->state->pl;
6363
Settings* settings = this->state->settings;
6464
int hx = super->scrollH + x + 1;
6565
ProcessField field = ProcessList_keyAt(pl, hx);

Process.c

+76-2
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
277277
baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
278278
}
279279
if (!this->settings->treeView || this->indent == 0) {
280+
if (this->merged > 1) {
281+
char merged[16];
282+
xSnprintf(merged, sizeof(merged), "[%u] ", this->merged);
283+
RichString_append(str, CRT_colors[PROCESS_SHADOW], merged);
284+
}
280285
Process_writeCommand(this, attr, baseattr, str);
281286
return;
282287
} else {
@@ -305,6 +310,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
305310
const char* draw = CRT_treeStr[lastItem ? (this->settings->direction == 1 ? TREE_STR_BEND : TREE_STR_TEND) : TREE_STR_RTEE];
306311
xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
307312
RichString_append(str, CRT_colors[PROCESS_TREE], buffer);
313+
if (this->merged > 1) {
314+
char merged[16];
315+
xSnprintf(merged, sizeof(merged), "[%u] ", this->merged);
316+
RichString_append(str, CRT_colors[PROCESS_SHADOW], merged);
317+
}
308318
Process_writeCommand(this, attr, baseattr, str);
309319
return;
310320
}
@@ -441,6 +451,36 @@ long Process_pidCompare(const void* v1, const void* v2) {
441451
return (p1->pid - p2->pid);
442452
}
443453

454+
static bool isTransitiveChildOf(const Process* child, const Process* parent) {
455+
assert(child->pl == parent->pl);
456+
457+
for (const Process* tChild = child; tChild; tChild = ProcessList_findProcess(parent->pl, Process_getParentPid(tChild)))
458+
if (Process_isChildOf(tChild, parent->pid))
459+
return true;
460+
461+
return false;
462+
}
463+
464+
int Process_sameApplication(const Process* v1, const Process* v2) {
465+
if (v1->session != v2->session)
466+
return 0;
467+
468+
// we can compare pointers since the field user points to a hashtable entry
469+
if (v1->user != v2->user)
470+
return 0;
471+
472+
// TODO exe check
473+
474+
475+
if (isTransitiveChildOf(v1, v2))
476+
return 2;
477+
478+
if (isTransitiveChildOf(v2, v1))
479+
return 1;
480+
481+
return 0;
482+
}
483+
444484
long Process_compare(const void* v1, const void* v2) {
445485
const Process *p1, *p2;
446486
const Settings *settings = ((const Process*)v1)->settings;
@@ -451,6 +491,7 @@ long Process_compare(const void* v1, const void* v2) {
451491
p2 = (const Process*)v1;
452492
p1 = (const Process*)v2;
453493
}
494+
assert(p1->pl == p2->pl);
454495
switch (settings->sortKey) {
455496
case PERCENT_CPU:
456497
return (p2->percent_cpu > p1->percent_cpu ? 1 : -1);
@@ -471,7 +512,7 @@ long Process_compare(const void* v1, const void* v2) {
471512
case NLWP:
472513
return (p1->nlwp - p2->nlwp);
473514
case PGRP:
474-
return (p1->pgrp - p2->pgrp);
515+
return (long)p1->pgrp - (long)p2->pgrp;
475516
case PID:
476517
return (p1->pid - p2->pid);
477518
case PPID:
@@ -481,7 +522,7 @@ long Process_compare(const void* v1, const void* v2) {
481522
case PROCESSOR:
482523
return (p1->processor - p2->processor);
483524
case SESSION:
484-
return (p1->session - p2->session);
525+
return (long)p1->session - (long)p2->session;
485526
case STARTTIME: {
486527
if (p1->starttime_ctime == p2->starttime_ctime)
487528
return (p1->pid - p2->pid);
@@ -506,3 +547,36 @@ long Process_compare(const void* v1, const void* v2) {
506547
return (p1->pid - p2->pid);
507548
}
508549
}
550+
551+
void Process_mergeData(Process* p1, const Process* p2) {
552+
assert(p1->pl == p2->pl);
553+
554+
//TODO: handle thread (Process_isThread())
555+
556+
p1->percent_cpu += p2->percent_cpu;
557+
p1->percent_mem += p2->percent_mem;
558+
// keep COMM
559+
p1->majflt += p2->majflt;
560+
p1->minflt += p2->minflt;
561+
p1->m_resident += p2->m_resident;
562+
p1->m_size += p2->m_size;
563+
// store min NICE
564+
p1->nice = MINIMUM(p1->nice, p2->nice);
565+
p1->nlwp += p2->nlwp;
566+
// keep PGRP
567+
// keep PID
568+
// keep PPID
569+
p1->priority = MAXIMUM(p1->priority, p2->priority);
570+
// keep PROCESSOR
571+
// keep SESSION
572+
p1->starttime_ctime = MINIMUM(p1->starttime_ctime, p2->starttime_ctime);
573+
// keep STATE
574+
// keep ST_UID
575+
p1->time += p2->time;
576+
// keep TGID
577+
// keep TPGID
578+
// keep TTY_NR
579+
// keep USER
580+
581+
p1->merged += p2->merged;
582+
}

Process.h

+13
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ typedef struct ProcessPidColumn_ {
5555
} ProcessPidColumn;
5656

5757
struct Settings_;
58+
struct ProcessList_;
5859

5960
typedef struct Process_ {
6061
Object super;
6162

6263
const struct Settings_* settings;
64+
const struct ProcessList_* pl;
6365

6466
unsigned long long int time;
6567
pid_t pid;
@@ -101,6 +103,8 @@ typedef struct Process_ {
101103

102104
unsigned long int minflt;
103105
unsigned long int majflt;
106+
107+
unsigned int merged;
104108
} Process;
105109

106110
typedef struct ProcessFieldData_ {
@@ -113,6 +117,11 @@ typedef struct ProcessFieldData_ {
113117
// Implemented in platform-specific code:
114118
void Process_writeField(const Process* this, RichString* str, ProcessField field);
115119
long Process_compare(const void* v1, const void* v2);
120+
/* returns 1 on match and if v1 is the master process,
121+
* 2 on match and if v2 is the master process,
122+
* NULL else */
123+
int Process_sameApplication(const Process* p1, const Process* p2);
124+
void Process_mergeData(Process* p1, const Process* p2);
116125
void Process_delete(Object* cast);
117126
bool Process_isThread(const Process* this);
118127
extern ProcessFieldData Process_fields[];
@@ -121,10 +130,14 @@ extern char Process_pidFormat[20];
121130

122131
typedef Process*(*Process_New)(const struct Settings_*);
123132
typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField);
133+
typedef int (*Process_SameApplication)(const Process*, const Process*);
134+
typedef void (*Process_MergeData)(Process*, const Process*);
124135

125136
typedef struct ProcessClass_ {
126137
const ObjectClass super;
127138
const Process_WriteField writeField;
139+
const Process_SameApplication sameApplication;
140+
const Process_MergeData mergeData;
128141
} ProcessClass;
129142

130143
#define As_Process(this_) ((const ProcessClass*)((this_)->super.klass))

0 commit comments

Comments
 (0)