diff --git a/CRT.c b/CRT.c index e4d2655eb..665283eb1 100644 --- a/CRT.c +++ b/CRT.c @@ -155,6 +155,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [TASKS_RUNNING] = A_BOLD | ColorPair(Green, Black), [PROCESS] = A_NORMAL, [PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack, + [PROCESS_SUM] = A_BOLD | ColorPairGrayBlack, [PROCESS_TAG] = A_BOLD | ColorPair(Yellow, Black), [PROCESS_MEGABYTES] = ColorPair(Cyan, Black), [PROCESS_GIGABYTES] = ColorPair(Green, Black), @@ -273,6 +274,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [TASKS_RUNNING] = A_BOLD, [PROCESS] = A_NORMAL, [PROCESS_SHADOW] = A_DIM, + [PROCESS_SUM] = A_DIM, [PROCESS_TAG] = A_BOLD, [PROCESS_MEGABYTES] = A_BOLD, [PROCESS_GIGABYTES] = A_BOLD, @@ -391,6 +393,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [TASKS_RUNNING] = ColorPair(Green, White), [PROCESS] = ColorPair(Black, White), [PROCESS_SHADOW] = A_BOLD | ColorPair(Black, White), + [PROCESS_SUM] = A_BOLD | ColorPair(Black, White), [PROCESS_TAG] = ColorPair(White, Blue), [PROCESS_MEGABYTES] = ColorPair(Blue, White), [PROCESS_GIGABYTES] = ColorPair(Green, White), @@ -509,6 +512,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [TASKS_RUNNING] = ColorPair(Green, Black), [PROCESS] = ColorPair(Black, Black), [PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack, + [PROCESS_SUM] = A_BOLD | ColorPairGrayBlack, [PROCESS_TAG] = ColorPair(White, Blue), [PROCESS_MEGABYTES] = ColorPair(Blue, Black), [PROCESS_GIGABYTES] = ColorPair(Green, Black), @@ -627,6 +631,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [TASKS_RUNNING] = A_BOLD | ColorPair(Green, Blue), [PROCESS] = ColorPair(White, Blue), [PROCESS_SHADOW] = A_BOLD | ColorPair(Black, Blue), + [PROCESS_SUM] = A_BOLD | ColorPair(Black, Blue), [PROCESS_TAG] = A_BOLD | ColorPair(Yellow, Blue), [PROCESS_MEGABYTES] = ColorPair(Cyan, Blue), [PROCESS_GIGABYTES] = ColorPair(Green, Blue), @@ -745,6 +750,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [TASKS_RUNNING] = A_BOLD | ColorPair(Green, Black), [PROCESS] = ColorPair(Cyan, Black), [PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack, + [PROCESS_SUM] = A_BOLD | ColorPairGrayBlack, [PROCESS_TAG] = A_BOLD | ColorPair(Yellow, Black), [PROCESS_MEGABYTES] = A_BOLD | ColorPair(Green, Black), [PROCESS_GIGABYTES] = A_BOLD | ColorPair(Yellow, Black), @@ -862,6 +868,7 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [TASKS_RUNNING] = A_BOLD, [PROCESS] = A_NORMAL, [PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack, + [PROCESS_SUM] = A_BOLD | ColorPairGrayBlack, [PROCESS_TAG] = A_BOLD | ColorPair(Cyan, Black), [PROCESS_MEGABYTES] = A_BOLD | ColorPair(White, Black), [PROCESS_GIGABYTES] = A_BOLD | ColorPair(Cyan, Black), diff --git a/CRT.h b/CRT.h index c072b7452..7db6923dd 100644 --- a/CRT.h +++ b/CRT.h @@ -73,6 +73,7 @@ typedef enum ColorElements_ { SWAP_FRONTSWAP, PROCESS, PROCESS_SHADOW, + PROCESS_SUM, PROCESS_TAG, PROCESS_MEGABYTES, PROCESS_GIGABYTES, diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index 292f6b6ea..08b121d2b 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -271,6 +271,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->ss->treeViewAlwaysByPID))); Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->ss->allBranchesCollapsed))); Panel_add(super, (Object*) NumberItem_newByRef("- Tree view is kept visually stable (0 - off, 1 - soft, 2 - hard)", &(settings->ss->stableTreeView), 0, 0, 2)); + Panel_add(super, (Object*) CheckItem_newByRef("- Sum CPU/memory usage of collapsed subtrees", &(settings->collapsedSubtreeSum))); Panel_add(super, (Object*) TextItem_new("Global options:")); Panel_add(super, (Object*) CheckItem_newByRef("Show tabs for screens", &(settings->screenTabs))); Panel_add(super, (Object*) CheckItem_newByRef("Shadow other users' processes", &(settings->shadowOtherUsers))); diff --git a/Process.c b/Process.c index ee6df701e..44fe4132d 100644 --- a/Process.c +++ b/Process.c @@ -603,6 +603,10 @@ void Process_writeField(const Process* this, RichString* str, RowField field) { int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; + /* When the row is a collapsed tree node whose subtree is being summed, + * additive fields are rendered from the aggregate and tinted accordingly. */ + const bool useAgg = super->aggregated; + switch (field) { case COMM: { int baseattr = CRT_colors[PROCESS_BASENAME]; @@ -695,10 +699,30 @@ void Process_writeField(const Process* this, RichString* str, RowField field) { Row_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring); return; } - case MAJFLT: Row_printCount(str, this->majflt, coloring); return; - case MINFLT: Row_printCount(str, this->minflt, coloring); return; - case M_RESIDENT: Row_printKBytes(str, this->m_resident, coloring); return; - case M_VIRT: Row_printKBytes(str, this->m_virt, coloring); return; + case MAJFLT: { + size_t start = RichString_size(str); + Row_printCount(str, useAgg ? this->aggregate.majflt : this->majflt, coloring); + if (useAgg) Process_aggregateRecolor(str, start); + return; + } + case MINFLT: { + size_t start = RichString_size(str); + Row_printCount(str, useAgg ? this->aggregate.minflt : this->minflt, coloring); + if (useAgg) Process_aggregateRecolor(str, start); + return; + } + case M_RESIDENT: { + size_t start = RichString_size(str); + Row_printKBytes(str, useAgg ? (unsigned long long)this->aggregate.m_resident : (unsigned long long)this->m_resident, coloring); + if (useAgg) Process_aggregateRecolor(str, start); + return; + } + case M_VIRT: { + size_t start = RichString_size(str); + Row_printKBytes(str, useAgg ? (unsigned long long)this->aggregate.m_virt : (unsigned long long)this->m_virt, coloring); + if (useAgg) Process_aggregateRecolor(str, start); + return; + } case NICE: if (this->nice == PROCESS_NICE_UNKNOWN) { xSnprintf(buffer, n, "N/A "); @@ -716,13 +740,20 @@ void Process_writeField(const Process* this, RichString* str, RowField field) { xSnprintf(buffer, n, "%4ld ", this->nlwp); break; - case PERCENT_CPU: Row_printPercentage(this->percent_cpu, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); break; + case PERCENT_CPU: + Row_printPercentage(useAgg ? this->aggregate.percent_cpu : this->percent_cpu, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); + if (useAgg) attr = CRT_colors[PROCESS_SUM]; + break; case PERCENT_NORM_CPU: { - float cpuPercentage = this->percent_cpu / host->activeCPUs; + float cpuPercentage = (useAgg ? this->aggregate.percent_cpu : this->percent_cpu) / host->activeCPUs; Row_printPercentage(cpuPercentage, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); + if (useAgg) attr = CRT_colors[PROCESS_SUM]; break; } - case PERCENT_MEM: Row_printPercentage(this->percent_mem, buffer, n, 4, &attr); break; + case PERCENT_MEM: + Row_printPercentage(useAgg ? this->aggregate.percent_mem : this->percent_mem, buffer, n, 4, &attr); + if (useAgg) attr = CRT_colors[PROCESS_SUM]; + break; case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break; case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getPid(this)); break; case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getParent(this)); break; @@ -774,7 +805,12 @@ void Process_writeField(const Process* this, RichString* str, RowField field) { } break; case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break; - case TIME: Row_printTime(str, this->time, coloring); return; + case TIME: { + size_t start = RichString_size(str); + Row_printTime(str, useAgg ? this->aggregate.time : this->time, coloring); + if (useAgg) Process_aggregateRecolor(str, start); + return; + } case TGID: if (Process_getThreadGroup(this) == Process_getPid(this)) attr = CRT_colors[PROCESS_SHADOW]; @@ -815,6 +851,46 @@ void Process_writeField(const Process* this, RichString* str, RowField field) { RichString_appendAscii(str, attr, buffer); } +void Process_aggregateRecolor(RichString* str, size_t start) { + size_t end = RichString_size(str); + if (end > start) + RichString_setAttrn(str, CRT_colors[PROCESS_SUM], start, end - start); +} + +void Process_rowAggregateClear(Row* super) { + Process* this = (Process*) super; + this->aggregate.percent_cpu = this->percent_cpu; + this->aggregate.percent_mem = this->percent_mem; + this->aggregate.m_virt = this->m_virt; + this->aggregate.m_resident = this->m_resident; + this->aggregate.minflt = this->minflt; + this->aggregate.majflt = this->majflt; + this->aggregate.time = this->time; +} + +void Process_rowAggregateAdd(Row* super, const Row* child) { + Process* this = (Process*) super; + const Process* cp = (const Process*) child; + + // Threads share their process' resources, which are already counted in the + // process row; summing them would multiply memory and CPU. Skip them. + if (Process_isThread(cp)) + return; + + this->aggregate.percent_cpu += cp->aggregate.percent_cpu; + this->aggregate.percent_mem += cp->aggregate.percent_mem; + this->aggregate.m_virt += cp->aggregate.m_virt; + this->aggregate.m_resident += cp->aggregate.m_resident; + this->aggregate.minflt += cp->aggregate.minflt; + this->aggregate.majflt += cp->aggregate.majflt; + this->aggregate.time += cp->aggregate.time; + + // A collapsed node will display this running total, which can be wider than + // any single process; grow the CPU% columns so the sum does not overflow. + if (!super->showChildren) + Process_updateCPUFieldWidths(this->aggregate.percent_cpu); +} + void Process_done(Process* this) { assert(this != NULL); free(this->cmdline); @@ -1146,6 +1222,8 @@ const ProcessClass Process_class = { .matchesFilter = Process_rowMatchesFilter, .sortKeyString = Process_rowGetSortKey, .compareByParent = Process_compareByParent, - .writeField = Process_rowWriteField + .writeField = Process_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd }, }; diff --git a/Process.h b/Process.h index 5bfdfe990..159225a84 100644 --- a/Process.h +++ b/Process.h @@ -78,10 +78,26 @@ typedef struct ProcessMergedCommand_ { ProcessCmdlineHighlight highlights[8]; /* which portions of cmdline to highlight */ } ProcessMergedCommand; +/* Subtree totals of the additive fields common to all platforms. Populated + * during tree building when the "sum collapsed subtrees" setting is enabled, + * and displayed in place of the node's own values while it is collapsed. */ +typedef struct ProcessAggregate_ { + float percent_cpu; + float percent_mem; + long m_virt; + long m_resident; + unsigned long int minflt; + unsigned long int majflt; + unsigned long long int time; +} ProcessAggregate; + typedef struct Process_ { /* Super object for emulated OOP */ Row super; + /* Subtree totals for collapsed tree nodes (see ProcessAggregate) */ + ProcessAggregate aggregate; + /* Process group identifier */ int pgrp; @@ -234,6 +250,15 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field int Process_compare(const void* v1, const void* v2); int Process_compareByParent(const Row* r1, const Row* r2); void Process_delete(Object* cast); + +/* Row vtable hooks for subtree summation; platform classes may wrap these to + * additionally aggregate their own additive fields. */ +void Process_rowAggregateClear(Row* super); +void Process_rowAggregateAdd(Row* super, const Row* child); + +/* Recolors the most recently appended span of `str` (from `start` to the + * current end) using the aggregate (summed value) color. */ +void Process_aggregateRecolor(RichString* str, size_t start); extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; #define Process_pidDigits Row_pidDigits #define Process_uidDigits Row_uidDigits diff --git a/Row.c b/Row.c index 222737300..dfa8dd082 100644 --- a/Row.c +++ b/Row.c @@ -36,6 +36,7 @@ void Row_init(Row* this, const Machine* host) { this->host = host; this->tag = false; this->showChildren = true; + this->aggregated = false; this->show = true; this->wasShown = false; this->updated = false; diff --git a/Row.h b/Row.h index 78629a3a5..749494b0b 100644 --- a/Row.h +++ b/Row.h @@ -60,6 +60,10 @@ typedef struct Row_ { /* Whether to show children of this row in tree-mode */ bool showChildren; + /* Whether this row displays aggregated (summed) values for its collapsed + subtree (tree-mode only, when the corresponding setting is enabled) */ + bool aggregated; + /* Whether the row was updated during the last scan */ bool updated; @@ -83,6 +87,8 @@ typedef bool (*Row_IsVisible)(const Row*, const struct Table_*); typedef bool (*Row_MatchesFilter)(const Row*, const struct Table_*); typedef const char* (*Row_SortKeyString)(Row*); typedef int (*Row_CompareByParent)(const Row*, const Row*); +typedef void (*Row_AggregateClear)(Row*); +typedef void (*Row_AggregateAdd)(Row*, const Row*); int Row_compare(const void* v1, const void* v2); @@ -94,6 +100,8 @@ typedef struct RowClass_ { const Row_MatchesFilter matchesFilter; const Row_SortKeyString sortKeyString; const Row_CompareByParent compareByParent; + const Row_AggregateClear aggregateClear; + const Row_AggregateAdd aggregateAdd; } RowClass; #define As_Row(this_) ((const RowClass*)((this_)->super.klass)) @@ -104,6 +112,11 @@ typedef struct RowClass_ { #define Row_sortKeyString(r_) (As_Row(r_)->sortKeyString ? (As_Row(r_)->sortKeyString(r_)) : "") #define Row_compareByParent(r1_, r2_) (As_Row(r1_)->compareByParent ? (As_Row(r1_)->compareByParent(r1_, r2_)) : Row_compareByParent_Base(r1_, r2_)) +/* Reset a row's aggregate to its own values; no-op if unsupported by the class */ +#define Row_aggregateClear(r_) do { if (As_Row(r_)->aggregateClear) As_Row(r_)->aggregateClear(r_); } while (0) +/* Add a child's subtree total into a row's aggregate; no-op if unsupported */ +#define Row_aggregateAdd(r_, c_) do { if (As_Row(r_)->aggregateAdd) As_Row(r_)->aggregateAdd((r_), (c_)); } while (0) + #define ONE_K 1024UL #define ONE_M (ONE_K * ONE_K) #define ONE_G (ONE_M * ONE_K) diff --git a/Settings.c b/Settings.c index 683abac34..4aeb1d0a7 100644 --- a/Settings.c +++ b/Settings.c @@ -479,6 +479,8 @@ static bool Settings_read(Settings* this, const char* fileName, const Machine* h this->showCPUFrequency = atoi(option[1]); } else if (String_eq(option[0], "show_cached_memory")) { this->showCachedMemory = atoi(option[1]); + } else if (String_eq(option[0], "tree_sum_collapsed_subtree")) { + this->collapsedSubtreeSum = atoi(option[1]); #ifdef BUILD_WITH_CPU_TEMP } else if (String_eq(option[0], "show_cpu_temperature")) { this->showCPUTemperature = atoi(option[1]); @@ -722,6 +724,7 @@ int Settings_write(const Settings* this, bool onCrash) { printSettingInteger("degree_fahrenheit", this->degreeFahrenheit); #endif printSettingInteger("show_cached_memory", this->showCachedMemory); + printSettingInteger("tree_sum_collapsed_subtree", this->collapsedSubtreeSum); printSettingInteger("update_process_names", this->updateProcessNames); printSettingInteger("account_guest_in_cpu_meter", this->accountGuestInCPUMeter); printSettingInteger("color_scheme", this->colorScheme); @@ -830,6 +833,7 @@ Settings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable* this->degreeFahrenheit = false; #endif this->showCachedMemory = true; + this->collapsedSubtreeSum = false; this->updateProcessNames = false; this->showProgramPath = true; this->highlightThreads = true; diff --git a/Settings.h b/Settings.h index e1642ab0b..4b9244978 100644 --- a/Settings.h +++ b/Settings.h @@ -105,6 +105,7 @@ typedef struct Settings_ { bool headerMargin; bool screenTabs; bool showCachedMemory; + bool collapsedSubtreeSum; #ifdef HAVE_GETMOUSE bool enableMouse; #endif diff --git a/Table.c b/Table.c index 8f0903c52..5e3205d22 100644 --- a/Table.c +++ b/Table.c @@ -107,6 +107,14 @@ static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, in if (rowid == 0) return; + // When enabled, sum the additive fields of the whole subtree into this node, + // so a collapsed node can display its subtree totals. + const Settings* settings = this->host->settings; + const bool aggregating = settings->collapsedSubtreeSum && settings->ss->treeView; + Row* node = aggregating ? Table_findRow(this, rowid) : NULL; + if (node) + Row_aggregateClear(node); + // The vector is sorted by parent, find the start of the range by bisection int vsize = Vector_size(this->rows); int l = 0; @@ -142,6 +150,8 @@ static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, in 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 (node) + Row_aggregateAdd(node, row); if (i == lastShown) row->indent = -nextIndent; else @@ -149,6 +159,10 @@ static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, in row->tree_depth = level + 1; } + + // This node displays subtree totals only while it is collapsed and non-empty. + if (node) + node->aggregated = (l < r) && !node->showChildren; } static int compareRowByKnownParentThenNatural(const void* v1, const void* v2) { @@ -165,6 +179,7 @@ static void Table_buildTree(Table* this) { Row* row = (Row*) Vector_get(this->rows, i); int parent = Row_getGroupOrParent(row); row->isRoot = false; + row->aggregated = false; if (row->id == parent) { row->isRoot = true; diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index 063ac1fba..90bac8321 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -538,7 +538,9 @@ const ProcessClass DarwinProcess_class = { .matchesFilter = Process_rowMatchesFilter, .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, - .writeField = DarwinProcess_rowWriteField + .writeField = DarwinProcess_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd }, .compareByKey = DarwinProcess_compareByKey }; diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c index 1c58434f7..2deb1f70f 100644 --- a/dragonflybsd/DragonFlyBSDProcess.c +++ b/dragonflybsd/DragonFlyBSDProcess.c @@ -115,7 +115,9 @@ const ProcessClass DragonFlyBSDProcess_class = { .matchesFilter = Process_rowMatchesFilter, .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, - .writeField = DragonFlyBSDProcess_rowWriteField + .writeField = DragonFlyBSDProcess_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd }, .compareByKey = DragonFlyBSDProcess_compareByKey }; diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c index bdbf02527..03a8bc0c7 100644 --- a/freebsd/FreeBSDProcess.c +++ b/freebsd/FreeBSDProcess.c @@ -149,7 +149,9 @@ const ProcessClass FreeBSDProcess_class = { .matchesFilter = Process_rowMatchesFilter, .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, - .writeField = FreeBSDProcess_rowWriteField + .writeField = FreeBSDProcess_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd }, .compareByKey = FreeBSDProcess_compareByKey }; diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 348894f10..aab23b369 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -232,6 +232,121 @@ static double LinuxProcess_totalIORate(const LinuxProcess* lp) { return totalRate; } +/* Print helpers that optionally re-tint the just-written span as a subtree sum. */ +static void LinuxProcess_writeBytes(RichString* str, unsigned long long bytes, bool coloring, bool aggregated) { + size_t start = RichString_size(str); + Row_printBytes(str, bytes, coloring); + if (aggregated) + Process_aggregateRecolor(str, start); +} + +static void LinuxProcess_writeKBytes(RichString* str, unsigned long long kbytes, bool coloring, bool aggregated) { + size_t start = RichString_size(str); + Row_printKBytes(str, kbytes, coloring); + if (aggregated) + Process_aggregateRecolor(str, start); +} + +static void LinuxProcess_writeCount(RichString* str, unsigned long long count, bool coloring, bool aggregated) { + size_t start = RichString_size(str); + Row_printCount(str, count, coloring); + if (aggregated) + Process_aggregateRecolor(str, start); +} + +static void LinuxProcess_writeTime(RichString* str, unsigned long long hundredths, bool coloring, bool aggregated) { + size_t start = RichString_size(str); + Row_printTime(str, hundredths, coloring); + if (aggregated) + Process_aggregateRecolor(str, start); +} + +static void LinuxProcess_writeRate(RichString* str, double rate, bool coloring, bool aggregated) { + size_t start = RichString_size(str); + Row_printRate(str, rate, coloring); + if (aggregated) + Process_aggregateRecolor(str, start); +} + +static void LinuxProcess_writeNanoseconds(RichString* str, unsigned long long nanoseconds, bool coloring, bool aggregated) { + size_t start = RichString_size(str); + Row_printNanoseconds(str, nanoseconds, coloring); + if (aggregated) + Process_aggregateRecolor(str, start); +} + +static void LinuxProcess_rowAggregateClear(Row* super) { + LinuxProcess* lp = (LinuxProcess*) super; + LinuxProcessAggregate* a = &lp->aggregate; + a->m_share = lp->m_share; + a->m_priv = lp->m_priv; + a->m_pss = lp->m_pss; + a->m_swap = lp->m_swap; + a->m_psswp = lp->m_psswp; + a->m_trs = lp->m_trs; + a->m_drs = lp->m_drs; + a->m_lrs = lp->m_lrs; + a->utime = lp->utime; + a->stime = lp->stime; + a->io_rchar = lp->io_rchar; + a->io_wchar = lp->io_wchar; + a->io_syscr = lp->io_syscr; + a->io_syscw = lp->io_syscw; + a->io_read_bytes = lp->io_read_bytes; + a->io_write_bytes = lp->io_write_bytes; + a->io_cancelled_write_bytes = lp->io_cancelled_write_bytes; + a->io_rate_read_bps = isNonnegative(lp->io_rate_read_bps) ? lp->io_rate_read_bps : 0.0; + a->io_rate_write_bps = isNonnegative(lp->io_rate_write_bps) ? lp->io_rate_write_bps : 0.0; + #ifdef HAVE_DELAYACCT + a->cpu_delay_percent = isNonnegative(lp->cpu_delay_percent) ? lp->cpu_delay_percent : 0.0f; + a->blkio_delay_percent = isNonnegative(lp->blkio_delay_percent) ? lp->blkio_delay_percent : 0.0f; + a->swapin_delay_percent = isNonnegative(lp->swapin_delay_percent) ? lp->swapin_delay_percent : 0.0f; + #endif + a->ctxt_diff = lp->ctxt_diff; + a->gpu_time = lp->gpu_time; + a->gpu_percent = isNonnegative(lp->gpu_percent) ? lp->gpu_percent : 0.0f; + + Process_rowAggregateClear(super); +} + +static void LinuxProcess_rowAggregateAdd(Row* super, const Row* child) { + // Threads share their process' resources (see Process_rowAggregateAdd). + if (Process_isThread((const Process*) child)) + return; + + LinuxProcessAggregate* a = &((LinuxProcess*) super)->aggregate; + const LinuxProcessAggregate* c = &((const LinuxProcess*) child)->aggregate; + a->m_share += c->m_share; + a->m_priv += c->m_priv; + a->m_pss += c->m_pss; + a->m_swap += c->m_swap; + a->m_psswp += c->m_psswp; + a->m_trs += c->m_trs; + a->m_drs += c->m_drs; + a->m_lrs += c->m_lrs; + a->utime += c->utime; + a->stime += c->stime; + a->io_rchar += c->io_rchar; + a->io_wchar += c->io_wchar; + a->io_syscr += c->io_syscr; + a->io_syscw += c->io_syscw; + a->io_read_bytes += c->io_read_bytes; + a->io_write_bytes += c->io_write_bytes; + a->io_cancelled_write_bytes += c->io_cancelled_write_bytes; + a->io_rate_read_bps += c->io_rate_read_bps; + a->io_rate_write_bps += c->io_rate_write_bps; + #ifdef HAVE_DELAYACCT + a->cpu_delay_percent += c->cpu_delay_percent; + a->blkio_delay_percent += c->blkio_delay_percent; + a->swapin_delay_percent += c->swapin_delay_percent; + #endif + a->ctxt_diff += c->ctxt_diff; + a->gpu_time += c->gpu_time; + a->gpu_percent += c->gpu_percent; + + Process_rowAggregateAdd(super, child); +} + static void LinuxProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { const Process* this = (const Process*) super; const LinuxProcess* lp = (const LinuxProcess*) super; @@ -243,41 +358,48 @@ static void LinuxProcess_rowWriteField(const Row* super, RichString* str, Proces int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; + /* Render additive fields from the subtree sum when this node is collapsed. */ + const bool useAgg = super->aggregated; + #define LP_AGG(f_) (useAgg ? lp->aggregate.f_ : lp->f_) + switch (field) { case CMINFLT: Row_printCount(str, lp->cminflt, coloring); return; case CMAJFLT: Row_printCount(str, lp->cmajflt, coloring); return; - case GPU_PERCENT: Row_printPercentage(lp->gpu_percent, buffer, n, 5, &attr); break; - case GPU_TIME: Row_printNanoseconds(str, lp->gpu_time, coloring); return; - case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; + case GPU_PERCENT: + Row_printPercentage(LP_AGG(gpu_percent), buffer, n, 5, &attr); + if (useAgg) attr = CRT_colors[PROCESS_SUM]; + break; + case GPU_TIME: LinuxProcess_writeNanoseconds(str, LP_AGG(gpu_time), coloring, useAgg); return; + case M_DRS: LinuxProcess_writeBytes(str, (unsigned long long)LP_AGG(m_drs) * lhost->pageSize, coloring, useAgg); return; case M_LRS: - if (lp->m_lrs) { - Row_printBytes(str, lp->m_lrs * lhost->pageSize, coloring); + if (LP_AGG(m_lrs)) { + LinuxProcess_writeBytes(str, (unsigned long long)LP_AGG(m_lrs) * lhost->pageSize, coloring, useAgg); return; } attr = CRT_colors[PROCESS_SHADOW]; xSnprintf(buffer, n, " N/A "); break; - case M_TRS: Row_printBytes(str, lp->m_trs * lhost->pageSize, coloring); return; - case M_SHARE: Row_printBytes(str, lp->m_share * lhost->pageSize, coloring); return; - case M_PRIV: Row_printKBytes(str, lp->m_priv, coloring); return; - case M_PSS: Row_printKBytes(str, lp->m_pss, coloring); return; - case M_SWAP: Row_printKBytes(str, lp->m_swap, coloring); return; - case M_PSSWP: Row_printKBytes(str, lp->m_psswp, coloring); return; - case UTIME: Row_printTime(str, lp->utime, coloring); return; - case STIME: Row_printTime(str, lp->stime, coloring); return; + case M_TRS: LinuxProcess_writeBytes(str, (unsigned long long)LP_AGG(m_trs) * lhost->pageSize, coloring, useAgg); return; + case M_SHARE: LinuxProcess_writeBytes(str, (unsigned long long)LP_AGG(m_share) * lhost->pageSize, coloring, useAgg); return; + case M_PRIV: LinuxProcess_writeKBytes(str, LP_AGG(m_priv), coloring, useAgg); return; + case M_PSS: LinuxProcess_writeKBytes(str, LP_AGG(m_pss), coloring, useAgg); return; + case M_SWAP: LinuxProcess_writeKBytes(str, LP_AGG(m_swap), coloring, useAgg); return; + case M_PSSWP: LinuxProcess_writeKBytes(str, LP_AGG(m_psswp), coloring, useAgg); return; + case UTIME: LinuxProcess_writeTime(str, LP_AGG(utime), coloring, useAgg); return; + case STIME: LinuxProcess_writeTime(str, LP_AGG(stime), coloring, useAgg); return; case CUTIME: Row_printTime(str, lp->cutime, coloring); return; case CSTIME: Row_printTime(str, lp->cstime, coloring); return; - case RCHAR: Row_printBytes(str, lp->io_rchar, coloring); return; - case WCHAR: Row_printBytes(str, lp->io_wchar, coloring); return; - case SYSCR: Row_printCount(str, lp->io_syscr, coloring); return; - case SYSCW: Row_printCount(str, lp->io_syscw, coloring); return; - case RBYTES: Row_printBytes(str, lp->io_read_bytes, coloring); return; - case WBYTES: Row_printBytes(str, lp->io_write_bytes, coloring); return; - case CNCLWB: Row_printBytes(str, lp->io_cancelled_write_bytes, coloring); return; - case IO_READ_RATE: Row_printRate(str, lp->io_rate_read_bps, coloring); return; - case IO_WRITE_RATE: Row_printRate(str, lp->io_rate_write_bps, coloring); return; - case IO_RATE: Row_printRate(str, LinuxProcess_totalIORate(lp), coloring); return; + case RCHAR: LinuxProcess_writeBytes(str, LP_AGG(io_rchar), coloring, useAgg); return; + case WCHAR: LinuxProcess_writeBytes(str, LP_AGG(io_wchar), coloring, useAgg); return; + case SYSCR: LinuxProcess_writeCount(str, LP_AGG(io_syscr), coloring, useAgg); return; + case SYSCW: LinuxProcess_writeCount(str, LP_AGG(io_syscw), coloring, useAgg); return; + case RBYTES: LinuxProcess_writeBytes(str, LP_AGG(io_read_bytes), coloring, useAgg); return; + case WBYTES: LinuxProcess_writeBytes(str, LP_AGG(io_write_bytes), coloring, useAgg); return; + case CNCLWB: LinuxProcess_writeBytes(str, LP_AGG(io_cancelled_write_bytes), coloring, useAgg); return; + case IO_READ_RATE: LinuxProcess_writeRate(str, useAgg ? lp->aggregate.io_rate_read_bps : lp->io_rate_read_bps, coloring, useAgg); return; + case IO_WRITE_RATE: LinuxProcess_writeRate(str, useAgg ? lp->aggregate.io_rate_write_bps : lp->io_rate_write_bps, coloring, useAgg); return; + case IO_RATE: LinuxProcess_writeRate(str, useAgg ? (lp->aggregate.io_rate_read_bps + lp->aggregate.io_rate_write_bps) : LinuxProcess_totalIORate(lp), coloring, useAgg); return; #ifdef HAVE_OPENVZ case CTID: xSnprintf(buffer, n, "%-8s ", lp->ctid ? lp->ctid : ""); break; case VPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, lp->vpid); break; @@ -324,15 +446,26 @@ static void LinuxProcess_rowWriteField(const Row* super, RichString* str, Proces break; } #ifdef HAVE_DELAYACCT - case PERCENT_CPU_DELAY: Row_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break; - case PERCENT_IO_DELAY: Row_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break; - case PERCENT_SWAP_DELAY: Row_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break; + case PERCENT_CPU_DELAY: + Row_printPercentage(LP_AGG(cpu_delay_percent), buffer, n, 5, &attr); + if (useAgg) attr = CRT_colors[PROCESS_SUM]; + break; + case PERCENT_IO_DELAY: + Row_printPercentage(LP_AGG(blkio_delay_percent), buffer, n, 5, &attr); + if (useAgg) attr = CRT_colors[PROCESS_SUM]; + break; + case PERCENT_SWAP_DELAY: + Row_printPercentage(LP_AGG(swapin_delay_percent), buffer, n, 5, &attr); + if (useAgg) attr = CRT_colors[PROCESS_SUM]; + break; #endif case CTXT: - if (lp->ctxt_diff > 1000) { + if (useAgg) { + attr = CRT_colors[PROCESS_SUM]; + } else if (lp->ctxt_diff > 1000) { attr |= A_BOLD; } - xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff); + xSnprintf(buffer, n, "%5lu ", LP_AGG(ctxt_diff)); break; case SECATTR: snprintf(buffer, n, "%-*.*s ", Row_fieldWidths[SECATTR], Row_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); @@ -375,6 +508,8 @@ static void LinuxProcess_rowWriteField(const Row* super, RichString* str, Proces return; } + #undef LP_AGG + RichString_appendAscii(str, attr, buffer); } @@ -492,7 +627,9 @@ const ProcessClass LinuxProcess_class = { .matchesFilter = Process_rowMatchesFilter, .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, - .writeField = LinuxProcess_rowWriteField + .writeField = LinuxProcess_rowWriteField, + .aggregateClear = LinuxProcess_rowAggregateClear, + .aggregateAdd = LinuxProcess_rowAggregateAdd }, .compareByKey = LinuxProcess_compareByKey }; diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index fafd7d004..d4093f057 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -32,8 +32,41 @@ in the source distribution for its full text. #define PROCESS_FLAG_LINUX_GPU 0x00100000 #define PROCESS_FLAG_LINUX_CONTAINER 0x00200000 +/* Subtree totals of the Linux-specific additive fields; populated alongside the + * common ProcessAggregate when the "sum collapsed subtrees" setting is on. */ +typedef struct LinuxProcessAggregate_ { + long m_share; + long m_priv; + long m_pss; + long m_swap; + long m_psswp; + long m_trs; + long m_drs; + long m_lrs; + unsigned long long int utime; + unsigned long long int stime; + unsigned long long io_rchar; + unsigned long long io_wchar; + unsigned long long io_syscr; + unsigned long long io_syscw; + unsigned long long io_read_bytes; + unsigned long long io_write_bytes; + unsigned long long io_cancelled_write_bytes; + double io_rate_read_bps; + double io_rate_write_bps; + #ifdef HAVE_DELAYACCT + float cpu_delay_percent; + float blkio_delay_percent; + float swapin_delay_percent; + #endif + unsigned long ctxt_diff; + unsigned long long int gpu_time; + float gpu_percent; +} LinuxProcessAggregate; + typedef struct LinuxProcess_ { Process super; + LinuxProcessAggregate aggregate; IOPriority ioPriority; unsigned long int cminflt; unsigned long int cmajflt; diff --git a/netbsd/NetBSDProcess.c b/netbsd/NetBSDProcess.c index 8d4a931a4..f1c75594f 100644 --- a/netbsd/NetBSDProcess.c +++ b/netbsd/NetBSDProcess.c @@ -272,7 +272,9 @@ const ProcessClass NetBSDProcess_class = { .matchesFilter = Process_rowMatchesFilter, .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, - .writeField = NetBSDProcess_rowWriteField + .writeField = NetBSDProcess_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd }, .compareByKey = NetBSDProcess_compareByKey }; diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c index ec6530ef5..6aa4cb04e 100644 --- a/openbsd/OpenBSDProcess.c +++ b/openbsd/OpenBSDProcess.c @@ -264,7 +264,9 @@ const ProcessClass OpenBSDProcess_class = { .matchesFilter = Process_rowMatchesFilter, .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, - .writeField = OpenBSDProcess_rowWriteField + .writeField = OpenBSDProcess_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd }, .compareByKey = OpenBSDProcess_compareByKey }; diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c index ae4a3e0dc..f94a8d4f4 100644 --- a/pcp/PCPProcess.c +++ b/pcp/PCPProcess.c @@ -305,6 +305,8 @@ const ProcessClass PCPProcess_class = { .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, .writeField = PCPProcess_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd, }, .compareByKey = PCPProcess_compareByKey, }; diff --git a/solaris/SolarisProcess.c b/solaris/SolarisProcess.c index 007fd1938..eaf8d7b58 100644 --- a/solaris/SolarisProcess.c +++ b/solaris/SolarisProcess.c @@ -142,7 +142,9 @@ const ProcessClass SolarisProcess_class = { .matchesFilter = Process_rowMatchesFilter, .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, - .writeField = SolarisProcess_rowWriteField + .writeField = SolarisProcess_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd }, .compareByKey = SolarisProcess_compareByKey }; diff --git a/unsupported/UnsupportedProcess.c b/unsupported/UnsupportedProcess.c index acb807847..0a4053120 100644 --- a/unsupported/UnsupportedProcess.c +++ b/unsupported/UnsupportedProcess.c @@ -106,7 +106,9 @@ const ProcessClass UnsupportedProcess_class = { .matchesFilter = Process_rowMatchesFilter, .compareByParent = Process_compareByParent, .sortKeyString = Process_rowGetSortKey, - .writeField = UnsupportedProcess_rowWriteField + .writeField = UnsupportedProcess_rowWriteField, + .aggregateClear = Process_rowAggregateClear, + .aggregateAdd = Process_rowAggregateAdd }, .compareByKey = UnsupportedProcess_compareByKey };