From 4c76488a8c8e7bcbff682d184f9ed23719fe0637 Mon Sep 17 00:00:00 2001 From: Nathan Scott Date: Tue, 22 Aug 2023 16:11:05 +1000 Subject: [PATCH 1/4] Introduce Row and Table classes for screens beyond top-processes This commit refactors the Process and ProcessList structures such they each have a new parent - Row and Table, respectively. These new classes handle screen updates relating to anything that could be represented in tabular format, e.g. cgroups, filesystems, etc, without us having to reimplement the display logic repeatedly for each new entity. --- Action.c | 104 +++-- Affinity.c | 34 +- Affinity.h | 6 +- AvailableColumnsPanel.c | 2 +- ColumnsPanel.c | 2 +- CommandLine.c | 17 +- CommandScreen.c | 2 +- DisplayOptionsPanel.c | 2 +- EnvScreen.c | 4 +- Machine.c | 52 ++- Machine.h | 22 +- MainPanel.c | 63 ++- MainPanel.h | 10 +- Makefile.am | 5 + OpenFilesScreen.c | 6 +- Process.c | 578 +++++++------------------ Process.h | 198 ++------- ProcessList.c | 461 ++------------------ ProcessList.h | 40 +- ProcessLocksScreen.c | 4 +- Row.c | 486 +++++++++++++++++++++ Row.h | 179 ++++++++ RowField.h | 56 +++ Scheduling.c | 12 +- Scheduling.h | 3 +- ScreenManager.c | 7 +- Settings.c | 14 +- Settings.h | 18 +- Table.c | 370 ++++++++++++++++ Table.h | 101 +++++ TasksMeter.c | 3 +- TraceScreen.c | 4 +- darwin/DarwinProcess.c | 41 +- darwin/DarwinProcessList.c | 10 +- dragonflybsd/DragonFlyBSDProcess.c | 24 +- dragonflybsd/DragonFlyBSDProcessList.c | 25 +- freebsd/FreeBSDProcess.c | 27 +- freebsd/FreeBSDProcessList.c | 21 +- linux/LinuxMachine.c | 3 +- linux/LinuxProcess.c | 111 +++-- linux/LinuxProcess.h | 6 +- linux/LinuxProcessList.c | 76 ++-- linux/Platform.c | 4 +- netbsd/NetBSDProcess.c | 20 +- netbsd/NetBSDProcessList.c | 23 +- openbsd/OpenBSDProcess.c | 22 +- openbsd/OpenBSDProcessList.c | 16 +- pcp/PCPDynamicColumn.c | 12 +- pcp/PCPProcess.c | 75 ++-- pcp/PCPProcessList.c | 29 +- solaris/SolarisProcess.c | 25 +- solaris/SolarisProcessList.c | 31 +- unsupported/UnsupportedProcess.c | 26 +- unsupported/UnsupportedProcessList.c | 21 +- 54 files changed, 2053 insertions(+), 1460 deletions(-) create mode 100644 Row.c create mode 100644 Row.h create mode 100644 RowField.h create mode 100644 Table.c create mode 100644 Table.h diff --git a/Action.c b/Action.c index 62308da72..f7fc4a438 100644 --- a/Action.c +++ b/Action.c @@ -56,22 +56,22 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool follow) { Panel* panelFocus; int ch; bool unfollow = false; - int pid = follow ? MainPanel_selectedPid(mainPanel) : -1; - if (follow && host->pl->following == -1) { - host->pl->following = pid; + int row = follow ? MainPanel_selectedRow(mainPanel) : -1; + if (follow && host->activeTable->following == -1) { + host->activeTable->following = row; unfollow = true; } ScreenManager_run(scr, &panelFocus, &ch, NULL); if (unfollow) { - host->pl->following = -1; + host->activeTable->following = -1; } ScreenManager_delete(scr); Panel_move((Panel*)mainPanel, 0, y); Panel_resize((Panel*)mainPanel, COLS, LINES - y - 1); if (panelFocus == list && ch == 13) { if (follow) { - const Process* selected = (const Process*)Panel_getSelected((Panel*)mainPanel); - if (selected && selected->pid == pid) + const Row* selected = (const Row*)Panel_getSelected((Panel*)mainPanel); + if (selected && selected->id == row) return Panel_getSelected(list); beep(); @@ -99,7 +99,7 @@ static void Action_runSetup(State* st) { static bool changePriority(MainPanel* panel, int delta) { bool anyTagged; - bool ok = MainPanel_foreachProcess(panel, Process_changePriorityBy, (Arg) { .i = delta }, &anyTagged); + bool ok = MainPanel_foreachRow(panel, Process_rowChangePriorityBy, (Arg) { .i = delta }, &anyTagged); if (!ok) beep(); return anyTagged; @@ -121,36 +121,36 @@ bool Action_setUserOnly(const char* userName, uid_t* userId) { return false; } -static void tagAllChildren(Panel* panel, Process* parent) { +static void tagAllChildren(Panel* panel, Row* parent) { parent->tag = true; - pid_t ppid = parent->pid; + int parent_id = parent->id; for (int i = 0; i < Panel_size(panel); i++) { - Process* p = (Process*) Panel_get(panel, i); - if (!p->tag && Process_isChildOf(p, ppid)) { - tagAllChildren(panel, p); + Row* row = (Row*) Panel_get(panel, i); + if (!row->tag && Row_isChildOf(row, parent_id)) { + tagAllChildren(panel, row); } } } static bool expandCollapse(Panel* panel) { - Process* p = (Process*) Panel_getSelected(panel); - if (!p) + Row* row = (Row*) Panel_getSelected(panel); + if (!row) return false; - p->showChildren = !p->showChildren; + row->showChildren = !row->showChildren; return true; } static bool collapseIntoParent(Panel* panel) { - const Process* p = (Process*) Panel_getSelected(panel); - if (!p) + const Row* r = (Row*) Panel_getSelected(panel); + if (!r) return false; - pid_t ppid = Process_getParentPid(p); + int parent_id = Row_getGroupOrParent(r); for (int i = 0; i < Panel_size(panel); i++) { - Process* q = (Process*) Panel_get(panel, i); - if (q->pid == ppid) { - q->showChildren = false; + Row* row = (Row*) Panel_get(panel, i); + if (row->id == parent_id) { + row->showChildren = false; Panel_setSelected(panel, i); return true; } @@ -159,7 +159,7 @@ static bool collapseIntoParent(Panel* panel) { } Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) { - ScreenSettings_setSortKey(settings->ss, sortKey); + ScreenSettings_setSortKey(settings->ss, (RowField) sortKey); return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_UPDATE_PANELHDR | HTOP_KEEP_FOLLOWING; } @@ -171,11 +171,11 @@ static Htop_Reaction actionSetSortColumn(State* st) { Panel_setHeader(sortPanel, "Sort by"); Machine* host = st->host; Settings* settings = host->settings; - const ProcessField* fields = settings->ss->fields; + const RowField* fields = settings->ss->fields; Hashtable* dynamicColumns = settings->dynamicColumns; for (int i = 0; fields[i]; i++) { char* name = NULL; - if (fields[i] >= LAST_PROCESSFIELD) { + if (fields[i] >= ROW_DYNAMIC_FIELDS) { DynamicColumn* column = Hashtable_get(dynamicColumns, fields[i]); if (!column) continue; @@ -195,7 +195,7 @@ static Htop_Reaction actionSetSortColumn(State* st) { } Object_delete(sortPanel); - host->pl->needsSort = true; + host->activeTable->needsSort = true; return reaction | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } @@ -262,9 +262,9 @@ static Htop_Reaction actionToggleTreeView(State* st) { ss->treeView = !ss->treeView; if (!ss->allBranchesCollapsed) - ProcessList_expandTree(host->pl); + Table_expandTree(host->activeTable); - host->pl->needsSort = true; + host->activeTable->needsSort = true; return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } @@ -282,9 +282,9 @@ static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) { } ss->allBranchesCollapsed = !ss->allBranchesCollapsed; if (ss->allBranchesCollapsed) - ProcessList_collapseAllBranches(host->pl); + Table_collapseAllBranches(host->activeTable); else - ProcessList_expandTree(host->pl); + Table_expandTree(host->activeTable); return HTOP_REFRESH | HTOP_SAVE_SETTINGS; } @@ -292,7 +292,7 @@ static Htop_Reaction actionIncFilter(State* st) { Machine* host = st->host; IncSet* inc = (st->mainPanel)->inc; IncSet_activate(inc, INC_FILTER, (Panel*)st->mainPanel); - host->pl->incFilter = IncSet_filter(inc); + host->activeTable->incFilter = IncSet_filter(inc); return HTOP_REFRESH | HTOP_KEEP_FOLLOWING; } @@ -321,7 +321,7 @@ static Htop_Reaction actionLowerPriority(State* st) { static Htop_Reaction actionInvertSortOrder(State* st) { Machine* host = st->host; ScreenSettings_invertSortOrder(host->settings->ss); - host->pl->needsSort = true; + host->activeTable->needsSort = true; return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_UPDATE_PANELHDR; } @@ -397,11 +397,11 @@ static Htop_Reaction actionSetAffinity(State* st) { return HTOP_OK; #if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)) - const Process* p = (const Process*) Panel_getSelected((Panel*)st->mainPanel); - if (!p) + const Row* row = (const Row*) Panel_getSelected((Panel*)st->mainPanel); + if (!row) return HTOP_OK; - Affinity* affinity1 = Affinity_get(p, host); + Affinity* affinity1 = Affinity_rowGet(row, host); if (!affinity1) return HTOP_OK; @@ -412,7 +412,7 @@ static Htop_Reaction actionSetAffinity(State* st) { const void* set = Action_pickFromVector(st, affinityPanel, width, true); if (set) { Affinity* affinity2 = AffinityPanel_getAffinity(affinityPanel, host); - bool ok = MainPanel_foreachProcess(st->mainPanel, Affinity_set, (Arg) { .v = affinity2 }, NULL); + bool ok = MainPanel_foreachRow(st->mainPanel, Affinity_rowSet, (Arg) { .v = affinity2 }, NULL); if (!ok) beep(); Affinity_delete(affinity2); @@ -422,7 +422,6 @@ static Htop_Reaction actionSetAffinity(State* st) { #else return HTOP_OK; #endif - } #ifdef SCHEDULER_SUPPORT @@ -459,7 +458,7 @@ static Htop_Reaction actionSetSchedPolicy(State* st) { SchedulingArg v = { .policy = preSelectedPolicy, .priority = preSelectedPriority }; - bool ok = MainPanel_foreachProcess(st->mainPanel, Scheduling_setPolicy, (Arg) { .v = &v }, NULL); + bool ok = MainPanel_foreachRow(st->mainPanel, Scheduling_rowSetPolicy, (Arg) { .v = &v }, NULL); if (!ok) beep(); } @@ -483,7 +482,7 @@ static Htop_Reaction actionKill(State* st) { Panel_setHeader((Panel*)st->mainPanel, "Sending..."); Panel_draw((Panel*)st->mainPanel, false, true, true, State_hideFunctionBar(st)); refresh(); - MainPanel_foreachProcess(st->mainPanel, Process_sendSignal, (Arg) { .i = sgn->key }, NULL); + MainPanel_foreachRow(st->mainPanel, Process_rowSendSignal, (Arg) { .i = sgn->key }, NULL); napms(500); } Panel_delete((Object*)signalsPanel); @@ -511,7 +510,7 @@ static Htop_Reaction actionFilterByUser(State* st) { } Htop_Reaction Action_follow(State* st) { - st->host->pl->following = MainPanel_selectedPid(st->mainPanel); + st->host->activeTable->following = MainPanel_selectedRow(st->mainPanel); Panel_setSelectionColor((Panel*)st->mainPanel, PANEL_SELECTION_FOLLOW); return HTOP_KEEP_FOLLOWING; } @@ -529,6 +528,8 @@ static Htop_Reaction actionLsof(State* st) { if (!p) return HTOP_OK; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + OpenFilesScreen* ofs = OpenFilesScreen_new(p); InfoScreen_run((InfoScreen*)ofs); OpenFilesScreen_delete((Object*)ofs); @@ -541,6 +542,9 @@ static Htop_Reaction actionShowLocks(State* st) { const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; + + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + ProcessLocksScreen* pls = ProcessLocksScreen_new(p); InfoScreen_run((InfoScreen*)pls); ProcessLocksScreen_delete((Object*)pls); @@ -557,6 +561,8 @@ static Htop_Reaction actionStrace(State* st) { if (!p) return HTOP_OK; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + TraceScreen* ts = TraceScreen_new(p); bool ok = TraceScreen_forkTracer(ts); if (ok) { @@ -569,11 +575,11 @@ static Htop_Reaction actionStrace(State* st) { } static Htop_Reaction actionTag(State* st) { - Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); - if (!p) + Row* r = (Row*) Panel_getSelected((Panel*)st->mainPanel); + if (!r) return HTOP_OK; - Process_toggleTag(p); + Row_toggleTag(r); Panel_onKey((Panel*)st->mainPanel, KEY_DOWN); return HTOP_OK; } @@ -783,18 +789,18 @@ static Htop_Reaction actionHelp(State* st) { static Htop_Reaction actionUntagAll(State* st) { for (int i = 0; i < Panel_size((Panel*)st->mainPanel); i++) { - Process* p = (Process*) Panel_get((Panel*)st->mainPanel, i); - p->tag = false; + Row* row = (Row*) Panel_get((Panel*)st->mainPanel, i); + row->tag = false; } return HTOP_REFRESH; } static Htop_Reaction actionTagAllChildren(State* st) { - Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); - if (!p) + Row* row = (Row*) Panel_getSelected((Panel*)st->mainPanel); + if (!row) return HTOP_OK; - tagAllChildren((Panel*)st->mainPanel, p); + tagAllChildren((Panel*)st->mainPanel, row); return HTOP_OK; } @@ -803,6 +809,8 @@ static Htop_Reaction actionShowEnvScreen(State* st) { if (!p) return HTOP_OK; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + EnvScreen* es = EnvScreen_new(p); InfoScreen_run((InfoScreen*)es); EnvScreen_delete((Object*)es); @@ -816,6 +824,8 @@ static Htop_Reaction actionShowCommandScreen(State* st) { if (!p) return HTOP_OK; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + CommandScreen* cmdScr = CommandScreen_new(p); InfoScreen_run((InfoScreen*)cmdScr); CommandScreen_delete((Object*)cmdScr); diff --git a/Affinity.c b/Affinity.c index f7c597bfc..546975d5d 100644 --- a/Affinity.c +++ b/Affinity.c @@ -12,6 +12,7 @@ in the source distribution for its full text. #include +#include "Process.h" #include "XUtils.h" #if defined(HAVE_LIBHWLOC) @@ -49,12 +50,11 @@ void Affinity_add(Affinity* this, unsigned int id) { this->used++; } - #if defined(HAVE_LIBHWLOC) -Affinity* Affinity_get(const Process* proc, Machine* host) { +static Affinity* Affinity_get(const Process* p, Machine* host) { hwloc_cpuset_t cpuset = hwloc_bitmap_alloc(); - bool ok = (hwloc_get_proc_cpubind(host->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0); + bool ok = (hwloc_get_proc_cpubind(host->topology, Process_getPid(p), cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0); Affinity* affinity = NULL; if (ok) { affinity = Affinity_new(host); @@ -73,22 +73,22 @@ Affinity* Affinity_get(const Process* proc, Machine* host) { return affinity; } -bool Affinity_set(Process* proc, Arg arg) { +static bool Affinity_set(Process* p, Arg arg) { Affinity* this = arg.v; hwloc_cpuset_t cpuset = hwloc_bitmap_alloc(); for (unsigned int i = 0; i < this->used; i++) { hwloc_bitmap_set(cpuset, this->cpus[i]); } - bool ok = (hwloc_set_proc_cpubind(this->host->topology, proc->pid, cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0); + bool ok = (hwloc_set_proc_cpubind(this->host->topology, Process_getPid(p), cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0); hwloc_bitmap_free(cpuset); return ok; } #elif defined(HAVE_AFFINITY) -Affinity* Affinity_get(const Process* proc, Machine* host) { +static Affinity* Affinity_get(const Process* p, Machine* host) { cpu_set_t cpuset; - bool ok = (sched_getaffinity(proc->pid, sizeof(cpu_set_t), &cpuset) == 0); + bool ok = (sched_getaffinity(Process_getPid(p), sizeof(cpu_set_t), &cpuset) == 0); if (!ok) return NULL; @@ -101,15 +101,31 @@ Affinity* Affinity_get(const Process* proc, Machine* host) { return affinity; } -bool Affinity_set(Process* proc, Arg arg) { +static bool Affinity_set(Process* p, Arg arg) { Affinity* this = arg.v; cpu_set_t cpuset; CPU_ZERO(&cpuset); for (unsigned int i = 0; i < this->used; i++) { CPU_SET(this->cpus[i], &cpuset); } - bool ok = (sched_setaffinity(proc->pid, sizeof(unsigned long), &cpuset) == 0); + bool ok = (sched_setaffinity(Process_getPid(p), sizeof(unsigned long), &cpuset) == 0); return ok; } #endif + +#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY) + +bool Affinity_rowSet(Row* row, Arg arg) { + Process* p = (Process*) row; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return Affinity_set(p, arg); +} + +Affinity* Affinity_rowGet(const Row* row, Machine* host) { + const Process* p = (const Process*) row; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return Affinity_get(p, host); +} + +#endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */ diff --git a/Affinity.h b/Affinity.h index 58d9bd733..341b0c04f 100644 --- a/Affinity.h +++ b/Affinity.h @@ -16,7 +16,7 @@ in the source distribution for its full text. #include #include "Object.h" -#include "Process.h" +#include "Row.h" #endif @@ -40,9 +40,9 @@ void Affinity_add(Affinity* this, unsigned int id); #if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY) -Affinity* Affinity_get(const Process* proc, Machine* host); +Affinity* Affinity_rowGet(const Row* row, Machine* host); -bool Affinity_set(Process* proc, Arg arg); +bool Affinity_rowSet(Row* row, Arg arg); #endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */ diff --git a/AvailableColumnsPanel.c b/AvailableColumnsPanel.c index b8c09c74c..f3c408430 100644 --- a/AvailableColumnsPanel.c +++ b/AvailableColumnsPanel.c @@ -34,7 +34,7 @@ static void AvailableColumnsPanel_delete(Object* object) { static void AvailableColumnsPanel_insert(AvailableColumnsPanel* this, int at, int key) { const char* name; - if (key >= LAST_PROCESSFIELD) + if (key >= ROW_DYNAMIC_FIELDS) name = DynamicColumn_init(key); else name = Process_fields[key].name; diff --git a/ColumnsPanel.c b/ColumnsPanel.c index d53fff258..ecae36f7a 100644 --- a/ColumnsPanel.c +++ b/ColumnsPanel.c @@ -141,7 +141,7 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns) { Panel* super = (Panel*) this; Panel_prune(super); - for (const ProcessField* fields = ss->fields; *fields; fields++) + for (const RowField* fields = ss->fields; *fields; fields++) ColumnsPanel_add(super, *fields, columns); this->ss = ss; } diff --git a/CommandLine.c b/CommandLine.c index 3d1f953fc..848c7ae74 100644 --- a/CommandLine.c +++ b/CommandLine.c @@ -303,11 +303,11 @@ static void CommandLine_delay(Machine* host, unsigned long millisec) { } static void setCommFilter(State* state, char** commFilter) { - ProcessList* pl = state->host->pl; + Table* table = state->host->activeTable; IncSet* inc = state->mainPanel->inc; IncSet_setFilter(inc, *commFilter); - pl->incFilter = IncSet_filter(inc); + table->incFilter = IncSet_filter(inc); free(*commFilter); *commFilter = NULL; @@ -334,8 +334,6 @@ int CommandLine_run(int argc, char** argv) { if (!Platform_init()) return 1; - Process_setupColumnWidths(); - UsersTable* ut = UsersTable_new(); Hashtable* dm = DynamicMeters_new(); Hashtable* dc = DynamicColumns_new(); @@ -347,7 +345,7 @@ int CommandLine_run(int argc, char** argv) { Settings* settings = Settings_new(host->activeCPUs, dm, dc); host->settings = settings; - Machine_addList(host, pl); + Machine_addTable(host, &pl->super, true); Header* header = Header_new(host, 2); Header_populateFromSettings(header); @@ -379,7 +377,7 @@ int CommandLine_run(int argc, char** argv) { CRT_init(settings, flags.allowUnicode, flags.iterationsRemaining != -1); MainPanel* panel = MainPanel_new(); - ProcessList_setPanel(pl, (Panel*) panel); + Table_setPanel(&pl->super, (Panel*) panel); MainPanel_updateLabels(panel, settings->ss->treeView, flags.commFilter); @@ -400,13 +398,13 @@ int CommandLine_run(int argc, char** argv) { ScreenManager_add(scr, (Panel*) panel, -1); Machine_scan(host); - ProcessList_scan(pl); + Machine_scanTables(host); CommandLine_delay(host, 75); Machine_scan(host); - ProcessList_scan(pl); + Machine_scanTables(host); if (settings->ss->allBranchesCollapsed) - ProcessList_collapseAllBranches(pl); + Table_collapseAllBranches(&pl->super); ScreenManager_run(scr, NULL, NULL, NULL); @@ -421,7 +419,6 @@ int CommandLine_run(int argc, char** argv) { } Header_delete(header); - ProcessList_delete(pl); Machine_delete(host); ScreenManager_delete(scr); diff --git a/CommandScreen.c b/CommandScreen.c index 6a87d1375..ecd823bdf 100644 --- a/CommandScreen.c +++ b/CommandScreen.c @@ -46,7 +46,7 @@ static void CommandScreen_scan(InfoScreen* this) { } static void CommandScreen_draw(InfoScreen* this) { - InfoScreen_drawTitled(this, "Command of process %d - %s", this->process->pid, Process_getCommand(this->process)); + InfoScreen_drawTitled(this, "Command of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process)); } const InfoScreenClass CommandScreen_class = { diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index f9fa9b12b..326d33b14 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -109,7 +109,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* #undef TABMSG Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->ss->treeView))); - 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 always sorted by ID (htop 2 behavior)", &(settings->ss->treeViewAlwaysByID))); Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->ss->allBranchesCollapsed))); Panel_add(super, (Object*) TextItem_new("Global options:")); Panel_add(super, (Object*) CheckItem_newByRef("Show tabs for screens", &(settings->screenTabs))); diff --git a/EnvScreen.c b/EnvScreen.c index 0fcee83a2..b27155e67 100644 --- a/EnvScreen.c +++ b/EnvScreen.c @@ -24,7 +24,7 @@ void EnvScreen_delete(Object* this) { } static void EnvScreen_draw(InfoScreen* this) { - InfoScreen_drawTitled(this, "Environment of process %d - %s", this->process->pid, Process_getCommand(this->process)); + InfoScreen_drawTitled(this, "Environment of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process)); } static void EnvScreen_scan(InfoScreen* this) { @@ -33,7 +33,7 @@ static void EnvScreen_scan(InfoScreen* this) { Panel_prune(panel); - char* env = Platform_getProcessEnv(this->process->pid); + char* env = Platform_getProcessEnv(Process_getPid(this->process)); if (env) { for (const char* p = env; *p; p = strrchr(p, 0) + 1) InfoScreen_addLine(this, p); diff --git a/Machine.c b/Machine.c index 63a996ef5..8846aed76 100644 --- a/Machine.c +++ b/Machine.c @@ -15,6 +15,7 @@ in the source distribution for its full text. #include "Hashtable.h" #include "Macros.h" #include "Platform.h" +#include "Row.h" #include "XUtils.h" @@ -22,6 +23,11 @@ void Machine_init(Machine* this, UsersTable* usersTable, uid_t userId) { this->usersTable = usersTable; this->userId = userId; + this->htopUserId = getuid(); + + // discover fixed column width limits + Row_setPidColumnWidth(Platform_getMaxPid()); + // always maintain valid realtime timestamps Platform_gettime_realtime(&this->realtime, &this->realtimeMs); @@ -49,12 +55,48 @@ void Machine_done(Machine* this) { if (this->topologyOk) { hwloc_topology_destroy(this->topology); } -#else - (void)this; #endif + for (size_t i = 0; i < this->tableCount; i++) { + Object_delete(&this->tables[i]->super); + } } -void Machine_addList(Machine* this, struct ProcessList_ *pl) { - // currently only process lists are supported - this->pl = pl; +void Machine_addTable(Machine* this, Table* table, bool processes) { + if (processes) + this->processTable = table; + this->activeTable = table; + + size_t nmemb = this->tableCount + 1; + Table** tables = xReallocArray(this->tables, nmemb, sizeof(Table*)); + tables[nmemb - 1] = table; + this->tables = tables; + this->tableCount++; +} + +void Machine_scanTables(Machine* this) { + // set scan timestamp + static bool firstScanDone = false; + + if (firstScanDone) + Platform_gettime_monotonic(&this->monotonicMs); + else + firstScanDone = true; + + this->maxUserId = 0; + Row_resetFieldWidths(); + + for (size_t i = 0; i < this->tableCount; i++) { + Table* table = this->tables[i]; + + // pre-processing of each row + Table_scanPrepare(table); + + // scan values for this table + Table_scanIterate(table); + + // post-process after scanning + Table_scanCleanup(table); + } + + Row_setUidColumnWidth(this->maxUserId); } diff --git a/Machine.h b/Machine.h index 64d6f6c29..a1c49e246 100644 --- a/Machine.h +++ b/Machine.h @@ -18,6 +18,7 @@ in the source distribution for its full text. #include "Hashtable.h" #include "Settings.h" +#include "Table.h" #include "UsersTable.h" #include "Vector.h" @@ -37,10 +38,10 @@ in the source distribution for its full text. typedef unsigned long long int memory_t; #define MEMORY_MAX ULLONG_MAX -struct ProcessList_; +struct Settings_; typedef struct Machine_ { - Settings* settings; + struct Settings_* settings; struct timeval realtime; /* time of the current sample */ uint64_t realtimeMs; /* current time in milliseconds */ @@ -68,11 +69,14 @@ typedef struct Machine_ { unsigned int existingCPUs; UsersTable* usersTable; - uid_t userId; - - /* To become an array of lists - processes, cgroups, filesystems,... etc */ - /* for now though, just point back to the one list we have at the moment */ - struct ProcessList_ *pl; + uid_t htopUserId; + uid_t maxUserId; /* recently observed */ + uid_t userId; /* selected row user ID */ + + size_t tableCount; + Table **tables; + Table *activeTable; + Table *processTable; } Machine; @@ -86,8 +90,10 @@ void Machine_done(Machine* this); bool Machine_isCPUonline(const Machine* this, unsigned int id); -void Machine_addList(Machine* this, struct ProcessList_ *pl); +void Machine_addTable(Machine* this, Table *table, bool processes); void Machine_scan(Machine* this); +void Machine_scanTables(Machine* this); + #endif diff --git a/MainPanel.c b/MainPanel.c index 14bd3bbdf..54127fe9c 100644 --- a/MainPanel.c +++ b/MainPanel.c @@ -14,10 +14,10 @@ in the source distribution for its full text. #include "CRT.h" #include "FunctionBar.h" #include "Platform.h" -#include "Process.h" -#include "ProcessList.h" #include "ProvideCurses.h" +#include "Row.h" #include "Settings.h" +#include "Table.h" #include "XUtils.h" @@ -30,25 +30,25 @@ void MainPanel_updateLabels(MainPanel* this, bool list, bool filter) { FunctionBar_setLabel(bar, KEY_F(4), filter ? "FILTER" : "Filter"); } -static void MainPanel_pidSearch(MainPanel* this, int ch) { +static void MainPanel_idSearch(MainPanel* this, int ch) { Panel* super = (Panel*) this; - pid_t pid = ch - 48 + this->pidSearch; + pid_t id = ch - 48 + this->idSearch; for (int i = 0; i < Panel_size(super); i++) { - const Process* p = (const Process*) Panel_get(super, i); - if (p && p->pid == pid) { + const Row* row = (const Row*) Panel_get(super, i); + if (row && row->id == id) { Panel_setSelected(super, i); break; } } - this->pidSearch = pid * 10; - if (this->pidSearch > 10000000) { - this->pidSearch = 0; + this->idSearch = id * 10; + if (this->idSearch > 10000000) { + this->idSearch = 0; } } static const char* MainPanel_getValue(Panel* this, int i) { - const Process* p = (const Process*) Panel_get(this, i); - return Process_getCommand(p); + const Row* row = (const Row*) Panel_get(this, i); + return Row_sortKeyString(row); } static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { @@ -77,8 +77,8 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { if (EVENT_IS_HEADER_CLICK(ch)) { int x = EVENT_HEADER_CLICK_GET_X(ch); int hx = super->scrollH + x + 1; - ProcessField field = ProcessList_keyAt(host->pl, hx); - if (ss->treeView && ss->treeViewAlwaysByPID) { + RowField field = RowField_keyAt(settings, hx); + if (ss->treeView && ss->treeViewAlwaysByID) { ss->treeView = false; ss->direction = 1; reaction |= Action_setSortKey(settings, field); @@ -96,7 +96,7 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { } else if (ch != ERR && this->inc->active) { bool filterChanged = IncSet_handleKey(this->inc, ch, super, MainPanel_getValue, NULL); if (filterChanged) { - host->pl->incFilter = IncSet_filter(this->inc); + host->activeTable->incFilter = IncSet_filter(this->inc); reaction = HTOP_REFRESH | HTOP_REDRAW_BAR; } if (this->inc->found) { @@ -111,17 +111,17 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { reaction |= (this->keys[ch])(this->state); result = HANDLED; } else if (0 < ch && ch < 255 && isdigit((unsigned char)ch)) { - MainPanel_pidSearch(this, ch); + MainPanel_idSearch(this, ch); } else { if (ch != ERR) { - this->pidSearch = 0; + this->idSearch = 0; } else { reaction |= HTOP_KEEP_FOLLOWING; } } if ((reaction & HTOP_REDRAW_BAR) == HTOP_REDRAW_BAR) { - MainPanel_updateLabels(this, settings->ss->treeView, host->pl->incFilter); + MainPanel_updateLabels(this, settings->ss->treeView, host->activeTable->incFilter); } if ((reaction & HTOP_RESIZE) == HTOP_RESIZE) { result |= RESIZE; @@ -142,35 +142,32 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { return BREAK_LOOP; } if ((reaction & HTOP_KEEP_FOLLOWING) != HTOP_KEEP_FOLLOWING) { - host->pl->following = -1; + host->activeTable->following = -1; Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); } return result; } -int MainPanel_selectedPid(MainPanel* this) { - const Process* p = (const Process*) Panel_getSelected((Panel*)this); - if (p) { - return p->pid; - } - return -1; +int MainPanel_selectedRow(MainPanel* this) { + const Row* row = (const Row*) Panel_getSelected((Panel*)this); + return row ? row->id : -1; } -bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged) { +bool MainPanel_foreachRow(MainPanel* this, MainPanel_foreachRowFn fn, Arg arg, bool* wasAnyTagged) { Panel* super = (Panel*) this; bool ok = true; bool anyTagged = false; for (int i = 0; i < Panel_size(super); i++) { - Process* p = (Process*) Panel_get(super, i); - if (p->tag) { - ok = fn(p, arg) && ok; + Row* row = (Row*) Panel_get(super, i); + if (row->tag) { + ok &= fn(row, arg); anyTagged = true; } } if (!anyTagged) { - Process* p = (Process*) Panel_getSelected(super); - if (p) { - ok &= fn(p, arg); + Row* row = (Row*) Panel_getSelected(super); + if (row) { + ok &= fn(row, arg); } } @@ -196,7 +193,7 @@ static void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) { static void MainPanel_printHeader(Panel* super) { MainPanel* this = (MainPanel*) super; Machine* host = this->state->host; - ProcessList_printHeader(host->pl, &super->header); + Table_printHeader(host->settings, &super->header); } const PanelClass MainPanel_class = { @@ -211,7 +208,7 @@ const PanelClass MainPanel_class = { MainPanel* MainPanel_new(void) { MainPanel* this = AllocThis(MainPanel); - Panel_init((Panel*) this, 1, 1, 1, 1, Class(Process), false, FunctionBar_new(Settings_isReadonly() ? MainFunctions_ro : MainFunctions, NULL, NULL)); + Panel_init((Panel*) this, 1, 1, 1, 1, Class(Row), false, FunctionBar_new(Settings_isReadonly() ? MainFunctions_ro : MainFunctions, NULL, NULL)); this->keys = xCalloc(KEY_MAX, sizeof(Htop_Action)); this->inc = IncSet_new(MainPanel_getFunctionBar(this)); diff --git a/MainPanel.h b/MainPanel.h index bd22acd0c..d062616d2 100644 --- a/MainPanel.h +++ b/MainPanel.h @@ -17,7 +17,7 @@ in the source distribution for its full text. #include "IncSet.h" #include "Object.h" #include "Panel.h" -#include "Process.h" +#include "Row.h" typedef struct MainPanel_ { @@ -25,19 +25,19 @@ typedef struct MainPanel_ { State* state; IncSet* inc; Htop_Action* keys; - pid_t pidSearch; + unsigned int idSearch; } MainPanel; -typedef bool(*MainPanel_ForeachProcessFn)(Process*, Arg); +typedef bool(*MainPanel_foreachRowFn)(Row*, Arg); #define MainPanel_getFunctionBar(this_) (((Panel*)(this_))->defaultBar) // update the Label Keys in the MainPanel bar, list: list / tree mode, filter: filter (inc) active / inactive void MainPanel_updateLabels(MainPanel* this, bool list, bool filter); -int MainPanel_selectedPid(MainPanel* this); +int MainPanel_selectedRow(MainPanel* this); -bool MainPanel_foreachProcess(MainPanel* this, MainPanel_ForeachProcessFn fn, Arg arg, bool* wasAnyTagged); +bool MainPanel_foreachRow(MainPanel* this, MainPanel_foreachRowFn fn, Arg arg, bool* wasAnyTagged); extern const PanelClass MainPanel_class; diff --git a/Makefile.am b/Makefile.am index e36994c13..a05c7ea9e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,6 +74,7 @@ myhtopsources = \ Process.c \ ProcessList.c \ ProcessLocksScreen.c \ + Row.c \ RichString.c \ Scheduling.c \ ScreenManager.c \ @@ -82,6 +83,7 @@ myhtopsources = \ SignalsPanel.c \ SwapMeter.c \ SysArchMeter.c \ + Table.c \ TasksMeter.c \ TraceScreen.c \ UptimeMeter.c \ @@ -141,6 +143,8 @@ myhtopheaders = \ ProvideCurses.h \ ProvideTerm.h \ RichString.h \ + Row.h \ + RowField.h \ Scheduling.h \ ScreenManager.h \ ScreensPanel.h \ @@ -148,6 +152,7 @@ myhtopheaders = \ SignalsPanel.h \ SwapMeter.h \ SysArchMeter.h \ + Table.h \ TasksMeter.h \ TraceScreen.h \ UptimeMeter.h \ diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index 2e782b986..3077490d2 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -72,12 +72,12 @@ static const char* getDataForType(const OpenFiles_Data* data, char type) { } OpenFilesScreen* OpenFilesScreen_new(const Process* process) { - OpenFilesScreen* this = xMalloc(sizeof(OpenFilesScreen)); + OpenFilesScreen* this = xCalloc(1, sizeof(OpenFilesScreen)); Object_setClass(this, Class(OpenFilesScreen)); if (Process_isThread(process)) { - this->pid = process->tgid; + this->pid = Process_getThreadGroup(process); } else { - this->pid = process->pid; + this->pid = Process_getPid(process); } return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE OFFSET NODE NAME"); } diff --git a/Process.c b/Process.c index 7244ad9f6..6f74e014e 100644 --- a/Process.c +++ b/Process.c @@ -40,239 +40,9 @@ in the source distribution for its full text. /* Used to identify kernel threads in Comm and Exe columns */ static const char* const kthreadID = "KTHREAD"; -static uid_t Process_getuid = (uid_t)-1; - -int Process_pidDigits = PROCESS_MIN_PID_DIGITS; -int Process_uidDigits = PROCESS_MIN_UID_DIGITS; - -void Process_setupColumnWidths(void) { - int maxPid = Platform_getMaxPid(); - if (maxPid == -1) - return; - - if (maxPid < (int)pow(10, PROCESS_MIN_PID_DIGITS)) { - Process_pidDigits = PROCESS_MIN_PID_DIGITS; - return; - } - - Process_pidDigits = (int)log10(maxPid) + 1; - assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS); -} - -void Process_setUidColumnWidth(uid_t maxUid) { - if (maxUid < (uid_t)pow(10, PROCESS_MIN_UID_DIGITS)) { - Process_uidDigits = PROCESS_MIN_UID_DIGITS; - return; - } - - Process_uidDigits = (int)log10(maxUid) + 1; - assert(Process_uidDigits <= PROCESS_MAX_UID_DIGITS); -} - -void Process_printBytes(RichString* str, unsigned long long number, bool coloring) { - char buffer[16]; - int len; - - int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; - int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; - int processGigabytesColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS]; - int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS]; - int processColor = CRT_colors[PROCESS]; - - if (number == ULLONG_MAX) { - //Invalid number - RichString_appendAscii(str, shadowColor, " N/A "); - return; - } - - number /= ONE_K; - - if (number < 1000) { - //Plain number, no markings - len = xSnprintf(buffer, sizeof(buffer), "%5llu ", number); - RichString_appendnAscii(str, processColor, buffer, len); - } else if (number < 100000) { - //2 digit MB, 3 digit KB - len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 1000); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - number %= 1000; - len = xSnprintf(buffer, sizeof(buffer), "%03llu ", number); - RichString_appendnAscii(str, processColor, buffer, len); - } else if (number < 1000 * ONE_K) { - //3 digit MB - number /= ONE_K; - len = xSnprintf(buffer, sizeof(buffer), "%4lluM ", number); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - } else if (number < 10000 * ONE_K) { - //1 digit GB, 3 digit MB - number /= ONE_K; - len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - number %= 1000; - len = xSnprintf(buffer, sizeof(buffer), "%03lluM ", number); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - } else if (number < 100 * ONE_M) { - //2 digit GB, 1 digit MB - len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / ONE_M); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - number = (number % ONE_M) * 10 / ONE_M; - len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - RichString_appendAscii(str, processGigabytesColor, "G "); - } else if (number < 1000 * ONE_M) { - //3 digit GB - number /= ONE_M; - len = xSnprintf(buffer, sizeof(buffer), "%4lluG ", number); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - } else if (number < 10000ULL * ONE_M) { - //1 digit TB, 3 digit GB - number /= ONE_M; - len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - number %= 1000; - len = xSnprintf(buffer, sizeof(buffer), "%03lluG ", number); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - } else if (number < 100ULL * ONE_G) { - //2 digit TB, 1 digit GB - len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / ONE_G); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - number = (number % ONE_G) * 10 / ONE_G; - len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number); - RichString_appendnAscii(str, processGigabytesColor, buffer, len); - RichString_appendAscii(str, largeNumberColor, "T "); - } else if (number < 10000ULL * ONE_G) { - //3 digit TB or 1 digit PB, 3 digit TB - number /= ONE_G; - len = xSnprintf(buffer, sizeof(buffer), "%4lluT ", number); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } else { - //2 digit PB and above - len = xSnprintf(buffer, sizeof(buffer), "%4.1lfP ", (double)number / ONE_T); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } -} - -void Process_printKBytes(RichString* str, unsigned long long number, bool coloring) { - if (number == ULLONG_MAX) - Process_printBytes(str, ULLONG_MAX, coloring); - else - Process_printBytes(str, number * ONE_K, coloring); -} - -void Process_printCount(RichString* str, unsigned long long number, bool coloring) { - char buffer[13]; - - int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; - int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; - int processColor = CRT_colors[PROCESS]; - int processShadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS]; - - if (number == ULLONG_MAX) { - RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A "); - } else if (number >= 100000LL * ONE_DECIMAL_T) { - xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G); - RichString_appendnAscii(str, largeNumberColor, buffer, 12); - } else if (number >= 100LL * ONE_DECIMAL_T) { - xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M); - RichString_appendnAscii(str, largeNumberColor, buffer, 8); - RichString_appendnAscii(str, processMegabytesColor, buffer + 8, 4); - } else if (number >= 10LL * ONE_DECIMAL_G) { - xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_K); - RichString_appendnAscii(str, largeNumberColor, buffer, 5); - RichString_appendnAscii(str, processMegabytesColor, buffer + 5, 3); - RichString_appendnAscii(str, processColor, buffer + 8, 4); - } else { - xSnprintf(buffer, sizeof(buffer), "%11llu ", number); - RichString_appendnAscii(str, largeNumberColor, buffer, 2); - RichString_appendnAscii(str, processMegabytesColor, buffer + 2, 3); - RichString_appendnAscii(str, processColor, buffer + 5, 3); - RichString_appendnAscii(str, processShadowColor, buffer + 8, 4); - } -} - -void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) { - char buffer[10]; - int len; - - unsigned long long totalSeconds = totalHundredths / 100; - unsigned long long hours = totalSeconds / 3600; - unsigned long long days = totalSeconds / 86400; - int minutes = (totalSeconds / 60) % 60; - int seconds = totalSeconds % 60; - int hundredths = totalHundredths - (totalSeconds * 100); - - int yearColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; - int dayColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS]; - int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; - int defColor = CRT_colors[PROCESS]; - - if (days >= /* Ignore leap years */365) { - int years = days / 365; - int daysLeft = days - 365 * years; - - if (years >= 10000000) { - RichString_appendnAscii(str, yearColor, "eternity ", 9); - } else if (years >= 1000) { - len = xSnprintf(buffer, sizeof(buffer), "%7dy ", years); - RichString_appendnAscii(str, yearColor, buffer, len); - } else if (daysLeft >= 100) { - len = xSnprintf(buffer, sizeof(buffer), "%3dy", years); - RichString_appendnAscii(str, yearColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft); - RichString_appendnAscii(str, dayColor, buffer, len); - } else if (daysLeft >= 10) { - len = xSnprintf(buffer, sizeof(buffer), "%4dy", years); - RichString_appendnAscii(str, yearColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%2dd ", daysLeft); - RichString_appendnAscii(str, dayColor, buffer, len); - } else { - len = xSnprintf(buffer, sizeof(buffer), "%5dy", years); - RichString_appendnAscii(str, yearColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%1dd ", daysLeft); - RichString_appendnAscii(str, dayColor, buffer, len); - } - } else if (days >= 100) { - int hoursLeft = hours - days * 24; - - if (hoursLeft >= 10) { - len = xSnprintf(buffer, sizeof(buffer), "%4llud", days); - RichString_appendnAscii(str, dayColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%2dh ", hoursLeft); - RichString_appendnAscii(str, hourColor, buffer, len); - } else { - len = xSnprintf(buffer, sizeof(buffer), "%5llud", days); - RichString_appendnAscii(str, dayColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%1dh ", hoursLeft); - RichString_appendnAscii(str, hourColor, buffer, len); - } - } else if (hours >= 100) { - int minutesLeft = totalSeconds / 60 - hours * 60; - - if (minutesLeft >= 10) { - len = xSnprintf(buffer, sizeof(buffer), "%4lluh", hours); - RichString_appendnAscii(str, hourColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%2dm ", minutesLeft); - RichString_appendnAscii(str, defColor, buffer, len); - } else { - len = xSnprintf(buffer, sizeof(buffer), "%5lluh", hours); - RichString_appendnAscii(str, hourColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%1dm ", minutesLeft); - RichString_appendnAscii(str, defColor, buffer, len); - } - } else if (hours > 0) { - len = xSnprintf(buffer, sizeof(buffer), "%2lluh", hours); - RichString_appendnAscii(str, hourColor, buffer, len); - len = xSnprintf(buffer, sizeof(buffer), "%02d:%02d ", minutes, seconds); - RichString_appendnAscii(str, defColor, buffer, len); - } else { - len = xSnprintf(buffer, sizeof(buffer), "%2d:%02d.%02d ", minutes, seconds, hundredths); - RichString_appendnAscii(str, defColor, buffer, len); - } -} - void Process_fillStarttimeBuffer(Process* this) { struct tm date; - time_t now = this->host->realtime.tv_sec; + time_t now = this->super.host->realtime.tv_sec; (void) localtime_r(&this->starttime_ctime, &date); strftime(this->starttime_show, @@ -408,9 +178,8 @@ static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr * Process_writeCommand() for coloring. The merged Command string is also * returned by Process_getCommand() for searching, sorting and filtering. */ -void Process_makeCommandStr(Process* this) { +void Process_makeCommandStr(Process* this, const Settings* settings) { ProcessMergedCommand* mc = &this->mergedCommand; - const Settings* settings = this->host->settings; bool showMergedCommand = settings->showMergedCommand; bool showProgramPath = settings->showProgramPath; @@ -678,7 +447,7 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin int strStart = RichString_size(str); - const Settings* settings = this->host->settings; + const Settings* settings = this->super.host->settings; const bool highlightBaseName = settings->highlightBaseName; const bool highlightSeparator = true; const bool highlightDeleted = settings->highlightDeletedExe; @@ -744,73 +513,6 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin } } -void Process_printRate(RichString* str, double rate, bool coloring) { - char buffer[16]; - - int largeNumberColor = CRT_colors[LARGE_NUMBER]; - int processMegabytesColor = CRT_colors[PROCESS_MEGABYTES]; - int processColor = CRT_colors[PROCESS]; - int shadowColor = CRT_colors[PROCESS_SHADOW]; - - if (!coloring) { - largeNumberColor = CRT_colors[PROCESS]; - processMegabytesColor = CRT_colors[PROCESS]; - } - - if (!isNonnegative(rate)) { - RichString_appendAscii(str, shadowColor, " N/A "); - } else if (rate < 0.005) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate); - RichString_appendnAscii(str, shadowColor, buffer, len); - } else if (rate < ONE_K) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate); - RichString_appendnAscii(str, processColor, buffer, len); - } else if (rate < ONE_M) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K); - RichString_appendnAscii(str, processColor, buffer, len); - } else if (rate < ONE_G) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M); - RichString_appendnAscii(str, processMegabytesColor, buffer, len); - } else if (rate < ONE_T) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f G/s ", rate / ONE_G); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } else if (rate < ONE_P) { - int len = snprintf(buffer, sizeof(buffer), "%7.2f T/s ", rate / ONE_T); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } else { - int len = snprintf(buffer, sizeof(buffer), "%7.2f P/s ", rate / ONE_P); - RichString_appendnAscii(str, largeNumberColor, buffer, len); - } -} - -void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) { - int columns = width; - RichString_appendnWideColumns(str, attr, content, strlen(content), &columns); - RichString_appendChr(str, attr, ' ', width + 1 - columns); -} - -void Process_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) { - if (isNonnegative(val)) { - if (val < 0.05F) - *attr = CRT_colors[PROCESS_SHADOW]; - else if (val >= 99.9F) - *attr = CRT_colors[PROCESS_MEGABYTES]; - - int precision = 1; - - // Display "val" as "100" for columns like "MEM%". - if (width == 4 && val > 99.9F) { - precision = 0; - val = 100.0F; - } - - xSnprintf(buffer, n, "%*.*f ", width, precision, val); - } else { - *attr = CRT_colors[PROCESS_SHADOW]; - xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); - } -} - static inline char processStateChar(ProcessState state) { switch (state) { case UNKNOWN: return '?'; @@ -833,11 +535,19 @@ static inline char processStateChar(ProcessState state) { } } -void Process_writeField(const Process* this, RichString* str, ProcessField field) { +static void Process_rowWriteField(const Row* super, RichString* str, RowField field) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + Process_writeField(this, str, field); +} + +void Process_writeField(const Process* this, RichString* str, RowField field) { char buffer[256]; size_t n = sizeof(buffer); int attr = CRT_colors[DEFAULT_COLOR]; - const Settings* settings = this->host->settings; + const Row* super = (const Row*) &this->super; + const Machine* host = super->host; + const Settings* settings = host->settings; bool coloring = settings->highlightMegabytes; switch (field) { @@ -848,15 +558,15 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field baseattr = CRT_colors[PROCESS_THREAD_BASENAME]; } const ScreenSettings* ss = settings->ss; - if (!ss->treeView || this->indent == 0) { + if (!ss->treeView || super->indent == 0) { Process_writeCommand(this, attr, baseattr, str); return; } char* buf = buffer; - const bool lastItem = (this->indent < 0); + const bool lastItem = (super->indent < 0); - for (uint32_t indent = (this->indent < 0 ? -this->indent : this->indent); indent > 1; indent >>= 1) { + for (uint32_t indent = (super->indent < 0 ? -super->indent : super->indent); indent > 1; indent >>= 1) { int written, ret; if (indent & 1U) { ret = xSnprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]); @@ -873,7 +583,7 @@ 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] ); + xSnprintf(buf, n, "%s%s ", draw, super->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] ); RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer); Process_writeCommand(this, attr, baseattr, str); return; @@ -888,7 +598,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field procComm = Process_isKernelThread(this) ? kthreadID : "N/A"; } - Process_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1); + Row_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1); return; } case PROC_EXE: { @@ -907,7 +617,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field procExe = Process_isKernelThread(this) ? kthreadID : "N/A"; } - Process_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1); + Row_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1); return; } case CWD: { @@ -921,22 +631,22 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field } else { cwd = this->procCwd; } - Process_printLeftAlignedField(str, attr, cwd, 25); + Row_printLeftAlignedField(str, attr, cwd, 25); return; } case ELAPSED: { - const uint64_t rt = this->host->realtimeMs; + const uint64_t rt = host->realtimeMs; const uint64_t st = this->starttime_ctime * 1000; const uint64_t dt = rt < st ? 0 : rt - st; - Process_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring); + Row_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring); return; } - case MAJFLT: Process_printCount(str, this->majflt, coloring); return; - case MINFLT: Process_printCount(str, this->minflt, coloring); return; - case M_RESIDENT: Process_printKBytes(str, this->m_resident, coloring); return; - case M_VIRT: Process_printKBytes(str, this->m_virt, 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 NICE: xSnprintf(buffer, n, "%3ld ", this->nice); attr = this->nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY] @@ -949,16 +659,16 @@ 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, Process_fieldWidths[PERCENT_CPU], &attr); break; + case PERCENT_CPU: Row_printPercentage(this->percent_cpu, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); break; case PERCENT_NORM_CPU: { - float cpuPercentage = this->percent_cpu / this->host->activeCPUs; - Process_printPercentage(cpuPercentage, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); + float cpuPercentage = this->percent_cpu / host->activeCPUs; + Row_printPercentage(cpuPercentage, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); break; } - case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, 4, &attr); break; + case PERCENT_MEM: Row_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; + case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getPid(this)); break; + case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getParent(this)); break; case PRIORITY: if (this->priority <= -100) xSnprintf(buffer, n, " RT "); @@ -1007,12 +717,12 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field } break; case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break; - case TIME: Process_printTime(str, this->time, coloring); return; + case TIME: Row_printTime(str, this->time, coloring); return; case TGID: - if (this->tgid == this->pid) + if (Process_getThreadGroup(this) == Process_getPid(this)) attr = CRT_colors[PROCESS_SHADOW]; - xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tgid); + xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getThreadGroup(this)); break; case TPGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tpgid); break; case TTY: @@ -1027,11 +737,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field case USER: if (this->elevated_priv) attr = CRT_colors[PROCESS_PRIV]; - else if (Process_getuid != this->st_uid) + else if (host->htopUserId != this->st_uid) attr = CRT_colors[PROCESS_SHADOW]; if (this->user) { - Process_printLeftAlignedField(str, attr, this->user, 10); + Row_printLeftAlignedField(str, attr, this->user, 10); return; } @@ -1047,32 +757,6 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field RichString_appendAscii(str, attr, buffer); } -void Process_display(const Object* cast, RichString* out) { - const Process* this = (const Process*) cast; - const Settings* settings = this->host->settings; - const ProcessField* fields = settings->ss->fields; - for (int i = 0; fields[i]; i++) - As_Process(this)->writeField(this, out, fields[i]); - - if (settings->shadowOtherUsers && this->st_uid != Process_getuid) { - RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]); - } - - if (this->tag == true) { - RichString_setAttr(out, CRT_colors[PROCESS_TAG]); - } - - if (settings->highlightChanges) { - if (Process_isTomb(this)) { - out->highlightAttr = CRT_colors[PROCESS_TOMB]; - } else if (Process_isNew(this)) { - out->highlightAttr = CRT_colors[PROCESS_NEW]; - } - } - - assert(RichString_size(out) > 0); -} - void Process_done(Process* this) { assert (this != NULL); free(this->cmdline); @@ -1087,7 +771,8 @@ void Process_done(Process* this) { * happens on what is displayed - whether comm, full path, basename, etc.. So * this follows Process_writeField(COMM) and Process_writeCommand */ const char* Process_getCommand(const Process* this) { - const Settings* settings = this->host->settings; + const Settings* settings = this->super.host->settings; + if ((Process_isUserlandThread(this) && settings->showThreadNames) || !this->mergedCommand.str) { return this->cmdline; } @@ -1095,75 +780,112 @@ const char* Process_getCommand(const Process* this) { return this->mergedCommand.str; } -const ProcessClass Process_class = { - .super = { - .extends = Class(Object), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare - }, - .writeField = Process_writeField, -}; +static const char* Process_getSortKey(const Process* this) { + return Process_getCommand(this); +} -void Process_init(Process* this, const Machine* host) { - this->host = host; - this->tag = false; - this->showChildren = true; - this->show = true; - this->updated = false; - this->cmdlineBasenameEnd = -1; - this->st_uid = (uid_t)-1; +const char* Process_rowGetSortKey(const Row* super) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_getSortKey(this); +} - if (Process_getuid == (uid_t)-1) { - Process_getuid = getuid(); - } +/* Test whether display must highlight this row (if the htop UID matches) */ +static bool Process_isHighlighted(const Process* this) { + const Machine* host = this->super.host; + const Settings* settings = host->settings; + return settings->shadowOtherUsers && this->st_uid != host->htopUserId; } -void Process_toggleTag(Process* this) { - this->tag = !this->tag; +bool Process_rowIsHighlighted(const Row* super) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_isHighlighted(this); } -bool Process_isNew(const Process* this) { - assert(this->host); - const Machine* host = this->host; - if (host->monotonicMs >= this->seenStampMs) { - const Settings* settings = host->settings; - return host->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)settings->highlightDelaySecs; - } +/* Test whether display must follow parent process (if this thread is hidden) */ +static bool Process_isVisible(const Process* p, const Settings* settings) { + if (settings->hideUserlandThreads) + return !Process_isThread(p); + return true; +} + +bool Process_rowIsVisible(const Row* super, const Table* table) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_isVisible(this, table->host->settings); +} + +/* Test whether display must filter out this process (various mechanisms) */ +static bool Process_matchesFilter(const Process* this, const Table* table) { + const Machine* host = table->host; + if (host->userId != (uid_t) -1 && this->st_uid != host->userId) + return true; + + const char* incFilter = table->incFilter; + if (incFilter && !String_contains_i(Process_getCommand(this), incFilter, true)) + return true; + + const ProcessList* pl = (const ProcessList*) host->activeTable; + assert(Object_isA((const Object*) pl, (const ObjectClass*) &ProcessList_class)); + if (pl->pidMatchList && !Hashtable_get(pl->pidMatchList, Process_getThreadGroup(this))) + return true; + return false; } -bool Process_isTomb(const Process* this) { - return this->tombStampMs > 0; +bool Process_rowMatchesFilter(const Row* super, const Table* table) { + const Process* this = (const Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_matchesFilter(this, table); +} + +void Process_init(Process* this, const Machine* host) { + Row_init(&this->super, host); + + this->cmdlineBasenameEnd = -1; } -bool Process_setPriority(Process* this, int priority) { +static bool Process_setPriority(Process* this, int priority) { if (Settings_isReadonly()) return false; - int old_prio = getpriority(PRIO_PROCESS, this->pid); - int err = setpriority(PRIO_PROCESS, this->pid, priority); + int old_prio = getpriority(PRIO_PROCESS, Process_getPid(this)); + int err = setpriority(PRIO_PROCESS, Process_getPid(this), priority); - if (err == 0 && old_prio != getpriority(PRIO_PROCESS, this->pid)) { + if (err == 0 && old_prio != getpriority(PRIO_PROCESS, Process_getPid(this))) { this->nice = priority; } return (err == 0); } -bool Process_changePriorityBy(Process* this, Arg delta) { +bool Process_rowSetPriority(Row* super, int priority) { + Process* this = (Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_setPriority(this, priority); +} + +bool Process_rowChangePriorityBy(Row* super, Arg delta) { + Process* this = (Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); return Process_setPriority(this, this->nice + delta.i); } -bool Process_sendSignal(Process* this, Arg sgn) { - return kill(this->pid, sgn.i) == 0; +static bool Process_sendSignal(Process* this, Arg sgn) { + return kill(Process_getPid(this), sgn.i) == 0; +} + +bool Process_rowSendSignal(Row* super, Arg sgn) { + Process* this = (Process*) super; + assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); + return Process_sendSignal(this, sgn); } int Process_compare(const void* v1, const void* v2) { const Process* p1 = (const Process*)v1; const Process* p2 = (const Process*)v2; - const Settings* settings = p1->host->settings; - const ScreenSettings* ss = settings->ss; + const ScreenSettings* ss = p1->super.host->settings->ss; ProcessField key = ScreenSettings_getActiveSortKey(ss); @@ -1171,11 +893,20 @@ int Process_compare(const void* v1, const void* v2) { // Implement tie-breaker (needed to make tree mode more stable) if (!result) - return SPACESHIP_NUMBER(p1->pid, p2->pid); + return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result; } +int Process_compareByParent(const Row* r1, const Row* r2) { + int result = Row_compareByParent_Base(r1, r2); + + if (result != 0) + return result; + + return Process_compare(r1, r2); +} + int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) { int r; @@ -1201,7 +932,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField return SPACESHIP_NULLSTR(p1->procCwd, p2->procCwd); case ELAPSED: r = -SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime); - return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid); + return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); case MAJFLT: return SPACESHIP_NUMBER(p1->majflt, p2->majflt); case MINFLT: @@ -1217,9 +948,9 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField case PGRP: return SPACESHIP_NUMBER(p1->pgrp, p2->pgrp); case PID: - return SPACESHIP_NUMBER(p1->pid, p2->pid); + return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); case PPID: - return SPACESHIP_NUMBER(p1->ppid, p2->ppid); + return SPACESHIP_NUMBER(Process_getParent(p1), Process_getParent(p2)); case PRIORITY: return SPACESHIP_NUMBER(p1->priority, p2->priority); case PROCESSOR: @@ -1230,7 +961,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField return SPACESHIP_NUMBER(p1->session, p2->session); case STARTTIME: r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime); - return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid); + return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); case STATE: return SPACESHIP_NUMBER(p1->state, p2->state); case ST_UID: @@ -1238,7 +969,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField case TIME: return SPACESHIP_NUMBER(p1->time, p2->time); case TGID: - return SPACESHIP_NUMBER(p1->tgid, p2->tgid); + return SPACESHIP_NUMBER(Process_getThreadGroup(p1), Process_getThreadGroup(p2)); case TPGID: return SPACESHIP_NUMBER(p1->tpgid, p2->tpgid); case TTY: @@ -1249,7 +980,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField default: CRT_debug("Process_compareByKey_Base() called with key %d", key); assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */ - return SPACESHIP_NUMBER(p1->pid, p2->pid); + return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2)); } } @@ -1327,36 +1058,33 @@ void Process_updateExe(Process* this, const char* exe) { this->mergedCommand.lastUpdate = 0; } -uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 }; - -void Process_resetFieldWidths(void) { - 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.9F) { - Process_updateFieldWidth(PERCENT_CPU, 4); - Process_updateFieldWidth(PERCENT_NORM_CPU, 4); + Row_updateFieldWidth(PERCENT_CPU, 4); + Row_updateFieldWidth(PERCENT_NORM_CPU, 4); return; } // Add additional two characters, one for "." and another for precision. uint8_t width = ceil(log10(percentage + 0.1)) + 2; - Process_updateFieldWidth(PERCENT_CPU, width); - Process_updateFieldWidth(PERCENT_NORM_CPU, width); + Row_updateFieldWidth(PERCENT_CPU, width); + Row_updateFieldWidth(PERCENT_NORM_CPU, width); } + +const ProcessClass Process_class = { + .super = { + .super = { + .extends = Class(Row), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .sortKeyString = Process_rowGetSortKey, + .compareByParent = Process_compareByParent, + .writeField = Process_rowWriteField + }, +}; diff --git a/Process.h b/Process.h index 884164789..c6cdb995e 100644 --- a/Process.h +++ b/Process.h @@ -13,8 +13,8 @@ in the source distribution for its full text. #include #include "Object.h" -#include "ProcessField.h" #include "RichString.h" +#include "Row.h" #define PROCESS_FLAG_IO 0x00000001 @@ -23,45 +23,6 @@ in the source distribution for its full text. #define DEFAULT_HIGHLIGHT_SECS 5 -typedef enum ProcessField_ { - NULL_PROCESSFIELD = 0, - PID = 1, - COMM = 2, - STATE = 3, - PPID = 4, - PGRP = 5, - SESSION = 6, - TTY = 7, - TPGID = 8, - MINFLT = 10, - MAJFLT = 12, - PRIORITY = 18, - NICE = 19, - STARTTIME = 21, - PROCESSOR = 38, - M_VIRT = 39, - M_RESIDENT = 40, - ST_UID = 46, - PERCENT_CPU = 47, - PERCENT_MEM = 48, - USER = 49, - TIME = 50, - NLWP = 51, - TGID = 52, - PERCENT_NORM_CPU = 53, - ELAPSED = 54, - SCHEDULERPOLICY = 55, - PROC_COMM = 124, - PROC_EXE = 125, - CWD = 126, - - /* Platform specific fields, defined in ${platform}/ProcessField.h */ - PLATFORM_PROCESS_FIELDS - - /* Do not add new fields after this entry (dynamic entries follow) */ - LAST_PROCESSFIELD -} ProcessField; - /* Core process states (shared by platforms) * NOTE: The enum has an ordering that is important! * See processStateChar in process.c for ProcessSate -> letter mapping */ @@ -83,6 +44,7 @@ typedef enum ProcessState_ { } ProcessState; struct Machine_; +struct Settings_; /* Holds information about regions of the cmdline that should be * highlighted (e.g. program basename, delimiter, comm). */ @@ -106,19 +68,7 @@ typedef struct ProcessMergedCommand_ { typedef struct Process_ { /* Super object for emulated OOP */ - Object super; - - /* Pointer to quasi-global data */ - const struct Machine_* host; - - /* Process identifier */ - pid_t pid; - - /* Parent process identifier */ - pid_t ppid; - - /* Thread group identifier */ - pid_t tgid; + Row super; /* Process group identifier */ int pgrp; @@ -232,36 +182,6 @@ typedef struct Process_ { /* Current scheduling policy */ int scheduling_policy; - /* Whether the process was updated during the current scan */ - bool updated; - - /* Whether the process was tagged by the user */ - bool tag; - - /* Whether to display this process */ - bool show; - - /* Whether this process was shown last cycle */ - bool wasShown; - - /* Whether to show children of this process in tree-mode */ - bool showChildren; - - /* - * Internal time counts for showing new and exited processes. - */ - uint64_t seenStampMs; - uint64_t tombStampMs; - - /* - * Internal state for tree-mode. - */ - int32_t indent; - unsigned int tree_depth; - - /* Has no known parent process */ - bool isRoot; - /* * Internal state for merged Command display */ @@ -291,39 +211,57 @@ typedef struct ProcessFieldData_ { bool autoWidth; } ProcessFieldData; +#define LAST_PROCESSFIELD LAST_RESERVED_FIELD +typedef int32_t ProcessField; /* see ReservedField list in RowField.h */ + // Implemented in platform-specific code: -void Process_writeField(const Process* this, RichString* str, ProcessField field); +void Process_writeField(const Process* row, RichString* str, ProcessField field); int Process_compare(const void* v1, const void* v2); +int Process_compareByParent(const Row* r1, const Row* 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 -#define PROCESS_MAX_UID_DIGITS 20 -extern int Process_pidDigits; -extern int Process_uidDigits; +#define Process_pidDigits Row_pidDigits +#define Process_uidDigits Row_uidDigits typedef Process* (*Process_New)(const struct Machine_*); -typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField); typedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField); typedef struct ProcessClass_ { - const ObjectClass super; - const Process_WriteField writeField; + const RowClass super; const Process_CompareByKey compareByKey; } ProcessClass; -#define As_Process(this_) ((const ProcessClass*)((this_)->super.klass)) +#define As_Process(this_) ((const ProcessClass*)((this_)->super.super.klass)) + +#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 void Process_setPid(Process* this, pid_t pid) { + this->super.id = pid; +} + +static inline pid_t Process_getPid(const Process* this) { + return (pid_t)this->super.id; +} -#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 void Process_setThreadGroup(Process* this, pid_t pid) { + this->super.group = pid; +} + +static inline pid_t Process_getThreadGroup(const Process* this) { + return (pid_t)this->super.group; +} -static inline pid_t Process_getParentPid(const Process* this) { - return this->tgid == this->pid ? this->ppid : this->tgid; +static inline void Process_setParent(Process* this, pid_t pid) { + this->super.parent = pid; } -static inline bool Process_isChildOf(const Process* this, pid_t pid) { - return pid == Process_getParentPid(this); +static inline pid_t Process_getParent(const Process* this) { + return (pid_t)this->super.parent; +} + +static inline pid_t Process_getGroupOrParent(const Process* this) { + return Row_getGroupOrParent(&this->super); } static inline bool Process_isKernelThread(const Process* this) { @@ -344,68 +282,30 @@ static inline bool Process_isThread(const Process* this) { #define CMDLINE_HIGHLIGHT_FLAG_DELETED 0x00000008 #define CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR 0x00000010 -#define ONE_K 1024UL -#define ONE_M (ONE_K * ONE_K) -#define ONE_G (ONE_M * ONE_K) -#define ONE_T (1ULL * ONE_G * ONE_K) -#define ONE_P (1ULL * ONE_T * ONE_K) - -#define ONE_DECIMAL_K 1000UL -#define ONE_DECIMAL_M (ONE_DECIMAL_K * ONE_DECIMAL_K) -#define ONE_DECIMAL_G (ONE_DECIMAL_M * ONE_DECIMAL_K) -#define ONE_DECIMAL_T (1ULL * ONE_DECIMAL_G * ONE_DECIMAL_K) -#define ONE_DECIMAL_P (1ULL * ONE_DECIMAL_T * ONE_DECIMAL_K) - -void Process_setupColumnWidths(void); - -/* Sets the size of the UID column based on the passed UID */ -void Process_setUidColumnWidth(uid_t maxUid); - -/* Takes number in bytes (base 1024). Prints 6 columns. */ -void Process_printBytes(RichString* str, unsigned long long number, bool coloring); - -/* Takes number in kilo bytes (base 1024). Prints 6 columns. */ -void Process_printKBytes(RichString* str, unsigned long long number, bool coloring); - -/* Takes number as count (base 1000). Prints 12 columns. */ -void Process_printCount(RichString* str, unsigned long long number, bool coloring); - -/* Takes time in hundredths of a seconds. Prints 9 columns. */ -void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring); - -/* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */ -void Process_printRate(RichString* str, double rate, bool coloring); - 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, size_t n, uint8_t width, int* attr); - -void Process_display(const Object* cast, RichString* out); - void Process_done(Process* this); extern const ProcessClass Process_class; void Process_init(Process* this, const struct Machine_* host); -void Process_toggleTag(Process* this); +const char* Process_rowGetSortKey(const Row* super); + +bool Process_rowSetPriority(Row* super, int priority); -bool Process_isNew(const Process* this); +bool Process_rowChangePriorityBy(Row* super, Arg delta); -bool Process_isTomb(const Process* this); +bool Process_rowSendSignal(Row* super, Arg sgn); -bool Process_setPriority(Process* this, int priority); +bool Process_rowIsHighlighted(const Row* super); -bool Process_changePriorityBy(Process* this, Arg delta); +bool Process_rowIsVisible(const Row* super, const struct Table_* table); -bool Process_sendSignal(Process* this, Arg sgn); +bool Process_rowMatchesFilter(const Row* super, const struct Table_* table); static inline int Process_pidEqualCompare(const void* v1, const void* v2) { - const pid_t p1 = ((const Process*)v1)->pid; - const pid_t p2 = ((const Process*)v2)->pid; - return p1 != p2; /* return zero when equal */ + return Row_idEqualCompare(v1, v2); } int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key); @@ -418,12 +318,10 @@ void Process_updateExe(Process* this, const char* exe); /* This function constructs the string that is displayed by * Process_writeCommand and also returned by Process_getCommand */ -void Process_makeCommandStr(Process* this); +void Process_makeCommandStr(Process* this, const struct Settings_ *settings); 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 58e63d030..516dcd7c5 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -21,453 +21,74 @@ in the source distribution for its full text. void ProcessList_init(ProcessList* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList) { - this->processes = Vector_new(klass, true, DEFAULT_SIZE); - this->displayList = Vector_new(klass, false, DEFAULT_SIZE); - this->processTable = Hashtable_new(200, false); + Table_init(&this->super, klass, host); + this->pidMatchList = pidMatchList; - this->needsSort = true; - this->following = -1; - this->host = host; } void ProcessList_done(ProcessList* this) { - Hashtable_delete(this->processTable); - Vector_delete(this->displayList); - Vector_delete(this->processes); -} - -void ProcessList_setPanel(ProcessList* this, Panel* panel) { - this->panel = panel; -} - -// helper function to fill an aligned title string for a dynamic column -static const char* alignedTitleDynamicColumn(const Settings* settings, int key, char* titleBuffer, size_t titleBufferSize) { - const DynamicColumn* column = Hashtable_get(settings->dynamicColumns, key); - if (column == NULL) - return "- "; - int width = column->width; - if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) - width = DYNAMIC_DEFAULT_COLUMN_WIDTH; - xSnprintf(titleBuffer, titleBufferSize, "%*s", width, column->heading); - return titleBuffer; -} - -// helper function to fill an aligned title string for a process field -static const char* alignedTitleProcessField(ProcessField field, char* titleBuffer, size_t titleBufferSize) { - const char* title = Process_fields[field].title; - if (!title) - return "- "; - - if (Process_fields[field].pidColumn) { - xSnprintf(titleBuffer, titleBufferSize, "%*s ", Process_pidDigits, title); - return titleBuffer; - } - - if (field == ST_UID) { - xSnprintf(titleBuffer, titleBufferSize, "%*s ", Process_uidDigits, title); - return titleBuffer; - } - - if (Process_fields[field].autoWidth) { - if (field == PERCENT_CPU) - xSnprintf(titleBuffer, titleBufferSize, "%*s ", Process_fieldWidths[field], title); - else - xSnprintf(titleBuffer, titleBufferSize, "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title); - return titleBuffer; - } - - return title; -} - -// helper function to create an aligned title string for a given field -static const char* ProcessField_alignedTitle(const Settings* settings, ProcessField field) { - static char titleBuffer[UINT8_MAX + sizeof(" ")]; - assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" ")); - assert(sizeof(titleBuffer) >= PROCESS_MAX_PID_DIGITS + sizeof(" ")); - assert(sizeof(titleBuffer) >= PROCESS_MAX_UID_DIGITS + sizeof(" ")); - - if (field < LAST_PROCESSFIELD) - return alignedTitleProcessField(field, titleBuffer, sizeof(titleBuffer)); - return alignedTitleDynamicColumn(settings, field, titleBuffer, sizeof(titleBuffer)); -} - -void ProcessList_printHeader(const ProcessList* this, RichString* header) { - RichString_rewind(header, RichString_size(header)); - - const Settings* settings = this->host->settings; - const ScreenSettings* ss = settings->ss; - const ProcessField* fields = ss->fields; - - ProcessField key = ScreenSettings_getActiveSortKey(ss); - - for (int i = 0; fields[i]; i++) { - int color; - if (ss->treeView && ss->treeViewAlwaysByPID) { - color = CRT_colors[PANEL_HEADER_FOCUS]; - } else if (key == fields[i]) { - color = CRT_colors[PANEL_SELECTION_FOCUS]; - } else { - color = CRT_colors[PANEL_HEADER_FOCUS]; - } - - RichString_appendWide(header, color, ProcessField_alignedTitle(settings, fields[i])); - if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') { - bool ascending = ScreenSettings_getActiveDirection(ss) == 1; - RichString_rewind(header, 1); // rewind to override space - RichString_appendnWide(header, - CRT_colors[PANEL_SELECTION_FOCUS], - CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC], - 1); - } - if (COMM == fields[i] && settings->showMergedCommand) { - RichString_appendAscii(header, color, "(merged)"); - } - } -} - -void ProcessList_add(ProcessList* this, Process* p) { - assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) == -1); - assert(Hashtable_get(this->processTable, p->pid) == NULL); - - // highlighting processes found in first scan by first scan marked "far in the past" - p->seenStampMs = this->host->monotonicMs; - - Vector_add(this->processes, p); - Hashtable_put(this->processTable, p->pid, p); - - assert(Vector_indexOf(this->processes, p, Process_pidEqualCompare) != -1); - assert(Hashtable_get(this->processTable, p->pid) != NULL); - assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable))); -} - -// ProcessList_removeIndex removes Process p from the list's map and soft deletes -// it from its vector. Vector_compact *must* be called once the caller is done -// removing items. -// Should only be called from ProcessList_scan to avoid breaking dying process highlighting. -static void ProcessList_removeIndex(ProcessList* this, const Process* p, int idx) { - pid_t pid = p->pid; - - assert(p == (Process*)Vector_get(this->processes, idx)); - assert(Hashtable_get(this->processTable, pid) != NULL); - - Hashtable_remove(this->processTable, pid); - Vector_softRemove(this->processes, idx); - - if (this->following != -1 && this->following == pid) { - this->following = -1; - Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); - } - - assert(Hashtable_get(this->processTable, pid) == NULL); - assert(Vector_countEquals(this->processes, Hashtable_count(this->processTable))); -} - -static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, unsigned int level, int32_t indent, bool show) { - // On OpenBSD the kernel thread 'swapper' has pid 0. - // Do not treat it as root of any tree. - if (pid == 0) - return; - - // The vector is sorted by parent PID, find the start of the range by bisection - int vsize = Vector_size(this->processes); - int l = 0; - int r = vsize; - while (l < r) { - int c = (l + r) / 2; - Process* process = (Process*)Vector_get(this->processes, c); - pid_t ppid = process->isRoot ? 0 : Process_getParentPid(process); - if (ppid < pid) { - l = c + 1; - } else { - r = c; - } - } - // Find the end to know the last line for indent handling purposes - int lastShown = r; - while (r < vsize) { - Process* process = (Process*)Vector_get(this->processes, r); - if (!Process_isChildOf(process, pid)) - break; - if (process->show) - lastShown = r; - r++; - } - - for (int i = l; i < r; i++) { - Process* process = (Process*)Vector_get(this->processes, i); - - if (!show) { - process->show = false; - } - - Vector_add(this->displayList, process); - - int32_t nextIndent = indent | ((int32_t)1 << MINIMUM(level, sizeof(process->indent) * 8 - 2)); - ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < lastShown) ? nextIndent : indent, process->show && process->showChildren); - if (i == lastShown) { - process->indent = -nextIndent; - } else { - process->indent = nextIndent; - } - - process->tree_depth = level + 1; - } -} - -static int compareProcessByKnownParentThenNatural(const void* v1, const void* v2) { - const Process* p1 = (const Process*)v1; - const Process* p2 = (const Process*)v2; - - int result = SPACESHIP_NUMBER( - p1->isRoot ? 0 : Process_getParentPid(p1), - p2->isRoot ? 0 : Process_getParentPid(p2) - ); - - if (result != 0) - return result; - - return Process_compare(v1, v2); -} - -// Builds a sorted tree from scratch, without relying on previously gathered information -static void ProcessList_buildTree(ProcessList* this) { - Vector_prune(this->displayList); - - // Mark root processes - int vsize = Vector_size(this->processes); - for (int i = 0; i < vsize; i++) { - Process* process = (Process*)Vector_get(this->processes, i); - pid_t ppid = Process_getParentPid(process); - process->isRoot = false; - - // If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0) - // on Mac OS X 10.11.6) regard this process as root. - if (process->pid == ppid) { - process->isRoot = true; - continue; - } - - // On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2) - // use a ppid of 0. As that PID can't exist, we can skip searching for it. - if (!ppid) { - process->isRoot = true; - continue; - } - - // We don't know about its parent for whatever reason - if (ProcessList_findProcess(this, ppid) == NULL) - process->isRoot = true; - } - - // Sort by known parent PID (roots first), then PID - Vector_quickSortCustomCompare(this->processes, compareProcessByKnownParentThenNatural); - - // Find all processes whose parent is not visible - for (int i = 0; i < vsize; i++) { - Process* process = (Process*)Vector_get(this->processes, i); - - // If parent not found, then construct the tree with this node as root - if (process->isRoot) { - process = (Process*)Vector_get(this->processes, i); - process->indent = 0; - process->tree_depth = 0; - Vector_add(this->displayList, process); - ProcessList_buildTreeBranch(this, process->pid, 0, 0, process->showChildren); - continue; - } - } - - this->needsSort = false; - - // Check consistency of the built structures - assert(Vector_size(this->displayList) == vsize); (void)vsize; -} - -void ProcessList_updateDisplayList(ProcessList* this) { - if (this->host->settings->ss->treeView) { - if (this->needsSort) - ProcessList_buildTree(this); - } else { - if (this->needsSort) - Vector_insertionSort(this->processes); - Vector_prune(this->displayList); - int size = Vector_size(this->processes); - for (int i = 0; i < size; i++) - Vector_add(this->displayList, Vector_get(this->processes, i)); - } - this->needsSort = false; -} - -ProcessField ProcessList_keyAt(const ProcessList* this, int at) { - int x = 0; - const Settings* settings = this->host->settings; - const ProcessField* fields = settings->ss->fields; - ProcessField field; - for (int i = 0; (field = fields[i]); i++) { - int len = strlen(ProcessField_alignedTitle(settings, field)); - if (at >= x && at <= x + len) { - return field; - } - x += len; - } - return COMM; -} - -void ProcessList_expandTree(ProcessList* this) { - int size = Vector_size(this->processes); - for (int i = 0; i < size; i++) { - Process* process = (Process*) Vector_get(this->processes, i); - process->showChildren = true; - } -} - -// Called on collapse-all toggle and on startup, possibly in non-tree mode -void ProcessList_collapseAllBranches(ProcessList* this) { - ProcessList_buildTree(this); // Update `tree_depth` fields of the processes - this->needsSort = true; // ProcessList is sorted by parent now, force new sort - int size = Vector_size(this->processes); - for (int i = 0; i < size; i++) { - Process* process = (Process*) Vector_get(this->processes, 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; - } -} - -void ProcessList_rebuildPanel(ProcessList* this) { - ProcessList_updateDisplayList(this); - - const char* incFilter = this->incFilter; - - const int currPos = Panel_getSelectedIndex(this->panel); - const int currScrollV = this->panel->scrollV; - const int currSize = Panel_size(this->panel); - - Panel_prune(this->panel); - - /* Follow main process if followed a userland thread and threads are now hidden */ - const Machine* host= this->host; - const Settings* settings = host->settings; - if (this->following != -1 && settings->hideUserlandThreads) { - const Process* followedProcess = (const Process*) Hashtable_get(this->processTable, this->following); - if (followedProcess && Process_isThread(followedProcess) && Hashtable_get(this->processTable, followedProcess->tgid) != NULL) { - this->following = followedProcess->tgid; - } - } - - const int processCount = Vector_size(this->displayList); - int idx = 0; - bool foundFollowed = false; - - for (int i = 0; i < processCount; i++) { - Process* p = (Process*) Vector_get(this->displayList, i); - - if ( (!p->show) - || (host->userId != (uid_t) -1 && (p->st_uid != host->userId)) - || (incFilter && !(String_contains_i(Process_getCommand(p), incFilter, true))) - || (this->pidMatchList && !Hashtable_get(this->pidMatchList, p->tgid)) ) - continue; - - Panel_set(this->panel, idx, (Object*)p); - - if (this->following != -1 && p->pid == this->following) { - foundFollowed = true; - Panel_setSelected(this->panel, idx); - /* Keep scroll position relative to followed process */ - this->panel->scrollV = idx - (currPos-currScrollV); - } - idx++; - } - - if (this->following != -1 && !foundFollowed) { - /* Reset if current followed pid not found */ - this->following = -1; - Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); - } - - if (this->following == -1) { - /* If the last item was selected, keep the new last item selected */ - if (currPos > 0 && currPos == currSize - 1) - Panel_setSelected(this->panel, Panel_size(this->panel) - 1); - else - Panel_setSelected(this->panel, currPos); - - this->panel->scrollV = currScrollV; - } + Table_done(&this->super); } Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process_New constructor) { - Process* proc = (Process*) Hashtable_get(this->processTable, pid); + const Table* table = &this->super; + Process* proc = (Process*) Hashtable_get(table->table, pid); *preExisting = proc != NULL; if (proc) { - assert(Vector_indexOf(this->processes, proc, Process_pidEqualCompare) != -1); - assert(proc->pid == pid); + assert(Vector_indexOf(table->rows, proc, Row_idEqualCompare) != -1); + assert(Process_getPid(proc) == pid); } else { - proc = constructor(this->host); + proc = constructor(table->host); assert(proc->cmdline == NULL); - proc->pid = pid; + Process_setPid(proc, pid); } return proc; } -void ProcessList_scan(ProcessList* this) { - // mark all process as "dirty" - for (int i = 0; i < Vector_size(this->processes); i++) { - Process* p = (Process*) Vector_get(this->processes, i); - p->updated = false; - p->wasShown = p->show; - p->show = true; - } - +static void ProcessList_prepareEntries(Table* super) { + ProcessList* this = (ProcessList*) super; this->totalTasks = 0; this->userlandThreads = 0; this->kernelThreads = 0; this->runningTasks = 0; - Process_resetFieldWidths(); - - // set scan timestamp - static bool firstScanDone = false; - Machine* host = this->host; - if (firstScanDone) { - Platform_gettime_monotonic(&host->monotonicMs); - } else { - host->monotonicMs = 0; - firstScanDone = true; - } + Table_prepareEntries(super); +} +static void ProcessList_iterateEntries(Table* super) { + ProcessList* this = (ProcessList*) super; + // calling into platform-specific code ProcessList_goThroughEntries(this); +} - uid_t maxUid = 0; +static void ProcessList_cleanupEntries(Table* super) { + Machine* host = super->host; const Settings* settings = host->settings; - for (int i = Vector_size(this->processes) - 1; i >= 0; i--) { - Process* p = (Process*) Vector_get(this->processes, i); - Process_makeCommandStr(p); + + // Finish process table update, culling any exit'd processes + for (int i = Vector_size(super->rows) - 1; i >= 0; i--) { + Process* p = (Process*) Vector_get(super->rows, i); + + // tidy up Process state after refreshing the ProcessList table + Process_makeCommandStr(p, settings); // keep track of the highest UID for column scaling - if (p->st_uid > maxUid) - maxUid = p->st_uid; + if (p->st_uid > host->maxUserId) + host->maxUserId = p->st_uid; - if (p->tombStampMs > 0) { - // remove tombed process - if (host->monotonicMs >= p->tombStampMs) { - ProcessList_removeIndex(this, p, i); - } - } else if (p->updated == false) { - // process no longer exists - if (settings->highlightChanges && p->wasShown) { - // mark tombed - p->tombStampMs = host->monotonicMs + 1000 * settings->highlightDelaySecs; - } else { - // immediately remove - ProcessList_removeIndex(this, p, i); - } - } + Table_cleanupRow(super, (Row*) p, i); } - // Compact the processes vector in case of any deletions - Vector_compact(this->processes); - - // Set UID column width based on max UID. - Process_setUidColumnWidth(maxUid); + // compact the table in case of deletions + Table_compact(super); } + +const TableClass ProcessList_class = { + .super = { + .extends = Class(Table), + .delete = ProcessList_delete, + }, + .prepare = ProcessList_prepareEntries, + .iterate = ProcessList_iterateEntries, + .cleanup = ProcessList_cleanupEntries, +}; diff --git a/ProcessList.h b/ProcessList.h index 0f0f7d517..9710a0a54 100644 --- a/ProcessList.h +++ b/ProcessList.h @@ -21,24 +21,12 @@ in the source distribution for its full text. #include "Panel.h" #include "Process.h" #include "RichString.h" -#include "Settings.h" -#include "UsersTable.h" -#include "Vector.h" +#include "Table.h" typedef struct ProcessList_ { - struct Machine_* host; + Table super; - Vector* processes; /* all known processes; sort order can vary and differ from display order */ - Vector* displayList; /* process tree flattened in display order (borrowed); - updated in ProcessList_updateDisplayList when rebuilding panel */ - Hashtable* processTable; /* fast known process lookup by PID */ - - bool needsSort; - - Panel* panel; - int following; - const char* incFilter; Hashtable* pidMatchList; unsigned int totalTasks; @@ -49,35 +37,23 @@ typedef struct ProcessList_ { /* Implemented by platforms */ ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList); -void ProcessList_delete(ProcessList* this); +void ProcessList_delete(Object* cast); void ProcessList_goThroughEntries(ProcessList* this); void ProcessList_init(ProcessList* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList); void ProcessList_done(ProcessList* this); -void ProcessList_setPanel(ProcessList* this, Panel* panel); - -void ProcessList_printHeader(const ProcessList* this, RichString* header); - -void ProcessList_add(ProcessList* this, Process* p); - -void ProcessList_updateDisplayList(ProcessList* this); +extern const TableClass ProcessList_class; -ProcessField ProcessList_keyAt(const ProcessList* this, int at); - -void ProcessList_expandTree(ProcessList* this); - -void ProcessList_collapseAllBranches(ProcessList* this); - -void ProcessList_rebuildPanel(ProcessList* this); +static inline void ProcessList_add(ProcessList* this, Process* process) { + Table_add(&this->super, &process->super); +} Process* ProcessList_getProcess(ProcessList* this, pid_t pid, bool* preExisting, Process_New constructor); -void ProcessList_scan(ProcessList* this); - static inline Process* ProcessList_findProcess(ProcessList* this, pid_t pid) { - return (Process*) Hashtable_get(this->processTable, pid); + return (Process*) Table_findRow(&this->super, pid); } #endif diff --git a/ProcessLocksScreen.c b/ProcessLocksScreen.c index 57c9ce75f..36a37f927 100644 --- a/ProcessLocksScreen.c +++ b/ProcessLocksScreen.c @@ -24,9 +24,9 @@ ProcessLocksScreen* ProcessLocksScreen_new(const Process* process) { ProcessLocksScreen* this = xMalloc(sizeof(ProcessLocksScreen)); Object_setClass(this, Class(ProcessLocksScreen)); if (Process_isThread(process)) - this->pid = process->tgid; + this->pid = Process_getThreadGroup(process); else - this->pid = process->pid; + this->pid = Process_getPid(process); return (ProcessLocksScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE EXCLUSION READ/WRITE DEVICE NODE START END FILENAME"); } diff --git a/Row.c b/Row.c new file mode 100644 index 000000000..09a320690 --- /dev/null +++ b/Row.c @@ -0,0 +1,486 @@ +/* +htop - Row.c +(C) 2004-2015 Hisham H. Muhammad +(C) 2020-2023 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "Row.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "DynamicColumn.h" +#include "Machine.h" +#include "Macros.h" +#include "RichString.h" +#include "Settings.h" +#include "Table.h" +#include "XUtils.h" + + +int Row_pidDigits = ROW_MIN_PID_DIGITS; +int Row_uidDigits = ROW_MIN_UID_DIGITS; + +void Row_init(Row* this, const Machine* host) { + this->host = host; + this->tag = false; + this->showChildren = true; + this->show = true; + this->wasShown = false; + this->updated = false; +} + +static inline bool Row_isNew(const Row* this) { + const Machine* host = this->host; + if (host->monotonicMs < this->seenStampMs) + return false; + + const Settings* settings = host->settings; + return host->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)settings->highlightDelaySecs; +} + +static inline bool Row_isTomb(const Row* this) { + return this->tombStampMs > 0; +} + +void Row_display(const Object* cast, RichString* out) { + const Row* this = (const Row*) cast; + const Settings* settings = this->host->settings; + const ProcessField* fields = settings->ss->fields; + + for (int i = 0; fields[i]; i++) + As_Row(this)->writeField(this, out, fields[i]); + + if (Row_isHighlighted(this)) + RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]); + + if (this->tag == true) + RichString_setAttr(out, CRT_colors[PROCESS_TAG]); + + if (settings->highlightChanges) { + if (Row_isTomb(this)) + out->highlightAttr = CRT_colors[PROCESS_TOMB]; + else if (Row_isNew(this)) + out->highlightAttr = CRT_colors[PROCESS_NEW]; + } + + assert(RichString_size(out) > 0); +} + +void Row_setPidColumnWidth(pid_t maxPid) { + if (maxPid < (int)pow(10, ROW_MIN_PID_DIGITS)) { + Row_pidDigits = ROW_MIN_PID_DIGITS; + return; + } + + Row_pidDigits = (int)log10(maxPid) + 1; + assert(Row_pidDigits <= ROW_MAX_PID_DIGITS); +} + +void Row_setUidColumnWidth(uid_t maxUid) { + if (maxUid < (uid_t)pow(10, ROW_MIN_UID_DIGITS)) { + Row_uidDigits = ROW_MIN_UID_DIGITS; + return; + } + + Row_uidDigits = (int)log10(maxUid) + 1; + assert(Row_uidDigits <= ROW_MAX_UID_DIGITS); +} + +uint8_t Row_fieldWidths[LAST_PROCESSFIELD] = { 0 }; + +void Row_resetFieldWidths(void) { + 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); + Row_fieldWidths[i] = (uint8_t)len; + } +} + +void Row_updateFieldWidth(RowField key, size_t width) { + if (width > UINT8_MAX) + Row_fieldWidths[key] = UINT8_MAX; + else if (width > Row_fieldWidths[key]) + Row_fieldWidths[key] = (uint8_t)width; +} + +// helper function to fill an aligned title string for a dynamic column +static const char* alignedTitleDynamicColumn(const Settings* settings, int key, char* titleBuffer, size_t titleBufferSize) { + const DynamicColumn* column = Hashtable_get(settings->dynamicColumns, key); + if (column == NULL) + return "- "; + + int width = column->width; + if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) + width = DYNAMIC_DEFAULT_COLUMN_WIDTH; + + xSnprintf(titleBuffer, titleBufferSize, "%*s", width, column->heading); + return titleBuffer; +} + +// helper function to fill an aligned title string for a process field +static const char* alignedTitleProcessField(ProcessField field, char* titleBuffer, size_t titleBufferSize) { + const char* title = Process_fields[field].title; + if (!title) + return "- "; + + if (Process_fields[field].pidColumn) { + xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_pidDigits, title); + return titleBuffer; + } + + if (field == ST_UID) { + xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_uidDigits, title); + return titleBuffer; + } + + if (Process_fields[field].autoWidth) { + if (field == PERCENT_CPU) + xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_fieldWidths[field], title); + else + xSnprintf(titleBuffer, titleBufferSize, "%-*.*s ", Row_fieldWidths[field], Row_fieldWidths[field], title); + return titleBuffer; + } + + return title; +} + +// helper function to create an aligned title string for a given field +const char* RowField_alignedTitle(const Settings* settings, RowField field) { + static char titleBuffer[UINT8_MAX + sizeof(" ")]; + assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" ")); + assert(sizeof(titleBuffer) >= ROW_MAX_PID_DIGITS + sizeof(" ")); + assert(sizeof(titleBuffer) >= ROW_MAX_UID_DIGITS + sizeof(" ")); + + if (field < LAST_PROCESSFIELD) + return alignedTitleProcessField((ProcessField)field, titleBuffer, sizeof(titleBuffer)); + return alignedTitleDynamicColumn(settings, field, titleBuffer, sizeof(titleBuffer)); +} + +RowField RowField_keyAt(const Settings* settings, int at) { + const RowField* fields = (const RowField*) settings->ss->fields; + RowField field; + int x = 0; + for (int i = 0; (field = fields[i]); i++) { + int len = strlen(RowField_alignedTitle(settings, field)); + if (at >= x && at <= x + len) { + return field; + } + x += len; + } + return COMM; +} + +void Row_printBytes(RichString* str, unsigned long long number, bool coloring) { + char buffer[16]; + int len; + + int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; + int megabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; + int gigabytesColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS]; + int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS]; + int baseColor = CRT_colors[PROCESS]; + + if (number == ULLONG_MAX) { + //Invalid number + RichString_appendAscii(str, shadowColor, " N/A "); + return; + } + + number /= ONE_K; + + if (number < 1000) { + //Plain number, no markings + len = xSnprintf(buffer, sizeof(buffer), "%5llu ", number); + RichString_appendnAscii(str, baseColor, buffer, len); + } else if (number < 100000) { + //2 digit MB, 3 digit KB + len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 1000); + RichString_appendnAscii(str, megabytesColor, buffer, len); + number %= 1000; + len = xSnprintf(buffer, sizeof(buffer), "%03llu ", number); + RichString_appendnAscii(str, baseColor, buffer, len); + } else if (number < 1000 * ONE_K) { + //3 digit MB + number /= ONE_K; + len = xSnprintf(buffer, sizeof(buffer), "%4lluM ", number); + RichString_appendnAscii(str, megabytesColor, buffer, len); + } else if (number < 10000 * ONE_K) { + //1 digit GB, 3 digit MB + number /= ONE_K; + len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000); + RichString_appendnAscii(str, gigabytesColor, buffer, len); + number %= 1000; + len = xSnprintf(buffer, sizeof(buffer), "%03lluM ", number); + RichString_appendnAscii(str, megabytesColor, buffer, len); + } else if (number < 100 * ONE_M) { + //2 digit GB, 1 digit MB + len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / ONE_M); + RichString_appendnAscii(str, gigabytesColor, buffer, len); + number = (number % ONE_M) * 10 / ONE_M; + len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number); + RichString_appendnAscii(str, megabytesColor, buffer, len); + RichString_appendAscii(str, gigabytesColor, "G "); + } else if (number < 1000 * ONE_M) { + //3 digit GB + number /= ONE_M; + len = xSnprintf(buffer, sizeof(buffer), "%4lluG ", number); + RichString_appendnAscii(str, gigabytesColor, buffer, len); + } else if (number < 10000ULL * ONE_M) { + //1 digit TB, 3 digit GB + number /= ONE_M; + len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + number %= 1000; + len = xSnprintf(buffer, sizeof(buffer), "%03lluG ", number); + RichString_appendnAscii(str, gigabytesColor, buffer, len); + } else if (number < 100ULL * ONE_G) { + //2 digit TB, 1 digit GB + len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / ONE_G); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + number = (number % ONE_G) * 10 / ONE_G; + len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number); + RichString_appendnAscii(str, gigabytesColor, buffer, len); + RichString_appendAscii(str, largeNumberColor, "T "); + } else if (number < 10000ULL * ONE_G) { + //3 digit TB or 1 digit PB, 3 digit TB + number /= ONE_G; + len = xSnprintf(buffer, sizeof(buffer), "%4lluT ", number); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + } else { + //2 digit PB and above + len = xSnprintf(buffer, sizeof(buffer), "%4.1lfP ", (double)number / ONE_T); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + } +} + +void Row_printKBytes(RichString* str, unsigned long long number, bool coloring) { + if (number == ULLONG_MAX) + Row_printBytes(str, ULLONG_MAX, coloring); + else + Row_printBytes(str, number * ONE_K, coloring); +} + +void Row_printCount(RichString* str, unsigned long long number, bool coloring) { + char buffer[13]; + + int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; + int megabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; + int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS]; + int baseColor = CRT_colors[PROCESS]; + + if (number == ULLONG_MAX) { + RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A "); + } else if (number >= 100000LL * ONE_DECIMAL_T) { + xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G); + RichString_appendnAscii(str, largeNumberColor, buffer, 12); + } else if (number >= 100LL * ONE_DECIMAL_T) { + xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M); + RichString_appendnAscii(str, largeNumberColor, buffer, 8); + RichString_appendnAscii(str, megabytesColor, buffer + 8, 4); + } else if (number >= 10LL * ONE_DECIMAL_G) { + xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_K); + RichString_appendnAscii(str, largeNumberColor, buffer, 5); + RichString_appendnAscii(str, megabytesColor, buffer + 5, 3); + RichString_appendnAscii(str, baseColor, buffer + 8, 4); + } else { + xSnprintf(buffer, sizeof(buffer), "%11llu ", number); + RichString_appendnAscii(str, largeNumberColor, buffer, 2); + RichString_appendnAscii(str, megabytesColor, buffer + 2, 3); + RichString_appendnAscii(str, baseColor, buffer + 5, 3); + RichString_appendnAscii(str, shadowColor, buffer + 8, 4); + } +} + +void Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) { + char buffer[10]; + int len; + + unsigned long long totalSeconds = totalHundredths / 100; + unsigned long long hours = totalSeconds / 3600; + unsigned long long days = totalSeconds / 86400; + int minutes = (totalSeconds / 60) % 60; + int seconds = totalSeconds % 60; + int hundredths = totalHundredths - (totalSeconds * 100); + + int yearColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS]; + int dayColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS]; + int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS]; + int baseColor = CRT_colors[PROCESS]; + + if (days >= /* Ignore leap years */365) { + int years = days / 365; + int daysLeft = days - 365 * years; + + if (years >= 10000000) { + RichString_appendnAscii(str, yearColor, "eternity ", 9); + } else if (years >= 1000) { + len = xSnprintf(buffer, sizeof(buffer), "%7dy ", years); + RichString_appendnAscii(str, yearColor, buffer, len); + } else if (daysLeft >= 100) { + len = xSnprintf(buffer, sizeof(buffer), "%3dy", years); + RichString_appendnAscii(str, yearColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft); + RichString_appendnAscii(str, dayColor, buffer, len); + } else if (daysLeft >= 10) { + len = xSnprintf(buffer, sizeof(buffer), "%4dy", years); + RichString_appendnAscii(str, yearColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%2dd ", daysLeft); + RichString_appendnAscii(str, dayColor, buffer, len); + } else { + len = xSnprintf(buffer, sizeof(buffer), "%5dy", years); + RichString_appendnAscii(str, yearColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%1dd ", daysLeft); + RichString_appendnAscii(str, dayColor, buffer, len); + } + } else if (days >= 100) { + int hoursLeft = hours - days * 24; + + if (hoursLeft >= 10) { + len = xSnprintf(buffer, sizeof(buffer), "%4llud", days); + RichString_appendnAscii(str, dayColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%2dh ", hoursLeft); + RichString_appendnAscii(str, hourColor, buffer, len); + } else { + len = xSnprintf(buffer, sizeof(buffer), "%5llud", days); + RichString_appendnAscii(str, dayColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%1dh ", hoursLeft); + RichString_appendnAscii(str, hourColor, buffer, len); + } + } else if (hours >= 100) { + int minutesLeft = totalSeconds / 60 - hours * 60; + + if (minutesLeft >= 10) { + len = xSnprintf(buffer, sizeof(buffer), "%4lluh", hours); + RichString_appendnAscii(str, hourColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%2dm ", minutesLeft); + RichString_appendnAscii(str, baseColor, buffer, len); + } else { + len = xSnprintf(buffer, sizeof(buffer), "%5lluh", hours); + RichString_appendnAscii(str, hourColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%1dm ", minutesLeft); + RichString_appendnAscii(str, baseColor, buffer, len); + } + } else if (hours > 0) { + len = xSnprintf(buffer, sizeof(buffer), "%2lluh", hours); + RichString_appendnAscii(str, hourColor, buffer, len); + len = xSnprintf(buffer, sizeof(buffer), "%02d:%02d ", minutes, seconds); + RichString_appendnAscii(str, baseColor, buffer, len); + } else { + len = xSnprintf(buffer, sizeof(buffer), "%2d:%02d.%02d ", minutes, seconds, hundredths); + RichString_appendnAscii(str, baseColor, buffer, len); + } +} + +void Row_printRate(RichString* str, double rate, bool coloring) { + char buffer[16]; + + int largeNumberColor = CRT_colors[LARGE_NUMBER]; + int megabytesColor = CRT_colors[PROCESS_MEGABYTES]; + int shadowColor = CRT_colors[PROCESS_SHADOW]; + int baseColor = CRT_colors[PROCESS]; + + if (!coloring) { + largeNumberColor = CRT_colors[PROCESS]; + megabytesColor = CRT_colors[PROCESS]; + } + + if (!isNonnegative(rate)) { + RichString_appendAscii(str, shadowColor, " N/A "); + } else if (rate < 0.005) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate); + RichString_appendnAscii(str, shadowColor, buffer, len); + } else if (rate < ONE_K) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate); + RichString_appendnAscii(str, baseColor, buffer, len); + } else if (rate < ONE_M) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K); + RichString_appendnAscii(str, baseColor, buffer, len); + } else if (rate < ONE_G) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M); + RichString_appendnAscii(str, megabytesColor, buffer, len); + } else if (rate < ONE_T) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f G/s ", rate / ONE_G); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + } else if (rate < ONE_P) { + int len = snprintf(buffer, sizeof(buffer), "%7.2f T/s ", rate / ONE_T); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + } else { + int len = snprintf(buffer, sizeof(buffer), "%7.2f P/s ", rate / ONE_P); + RichString_appendnAscii(str, largeNumberColor, buffer, len); + } +} + +void Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) { + int columns = width; + RichString_appendnWideColumns(str, attr, content, strlen(content), &columns); + RichString_appendChr(str, attr, ' ', width + 1 - columns); +} + +void Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) { + if (isNonnegative(val)) { + if (val < 0.05F) + *attr = CRT_colors[PROCESS_SHADOW]; + else if (val >= 99.9F) + *attr = CRT_colors[PROCESS_MEGABYTES]; + + int precision = 1; + + // Display "val" as "100" for columns like "MEM%". + if (width == 4 && val > 99.9F) { + precision = 0; + val = 100.0F; + } + + xSnprintf(buffer, n, "%*.*f ", width, precision, val); + } else { + *attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); + } +} + +void Row_toggleTag(Row* this) { + this->tag = !this->tag; +} + +int Row_compare(const void* v1, const void* v2) { + const Row* r1 = (const Row*)v1; + const Row* r2 = (const Row*)v2; + + return SPACESHIP_NUMBER(r1->id, r2->id); +} + +int Row_compareByParent_Base(const void* v1, const void* v2) { + const Row* r1 = (const Row*)v1; + const Row* r2 = (const Row*)v2; + + int result = SPACESHIP_NUMBER( + r1->isRoot ? 0 : Row_getGroupOrParent(r1), + r2->isRoot ? 0 : Row_getGroupOrParent(r2) + ); + + if (result != 0) + return result; + + return Row_compare(v1, v2); +} + +const RowClass Row_class = { + .super = { + .extends = Class(Object), + .compare = Row_compare + }, +}; diff --git a/Row.h b/Row.h new file mode 100644 index 000000000..5cdb3fe75 --- /dev/null +++ b/Row.h @@ -0,0 +1,179 @@ +#ifndef HEADER_Row +#define HEADER_Row +/* +htop - Row.h +(C) 2004-2015 Hisham H. Muhammad +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include +#include +#include + +#include "Object.h" +#include "RichString.h" +#include "RowField.h" + + +extern uint8_t Row_fieldWidths[LAST_RESERVED_FIELD]; +#define ROW_MIN_PID_DIGITS 5 +#define ROW_MAX_PID_DIGITS 19 +#define ROW_MIN_UID_DIGITS 5 +#define ROW_MAX_UID_DIGITS 20 +extern int Row_pidDigits; +extern int Row_uidDigits; + +struct Machine_; +struct Settings_; +struct Table_; + +/* Class representing entities (such as processes) that can be + * represented in a tabular form in the lower half of the htop + * display. */ + +typedef struct Row_ { + /* Super object for emulated OOP */ + Object super; + + /* Pointer to quasi-global data */ + const struct Machine_* host; + + int id; + int group; + int parent; + + /* Has no known parent */ + bool isRoot; + + /* Whether the row was tagged by the user */ + bool tag; + + /* Whether to display this row */ + bool show; + + /* Whether this row was shown last cycle */ + bool wasShown; + + /* Whether to show children of this row in tree-mode */ + bool showChildren; + + /* Whether the row was updated during the last scan */ + bool updated; + + /* + * Internal state for tree-mode. + */ + int32_t indent; + unsigned int tree_depth; + + /* + * Internal time counts for showing new and exited processes. + */ + uint64_t seenStampMs; + uint64_t tombStampMs; +} Row; + +typedef Row* (*Row_New)(const struct Machine_*); +typedef void (*Row_WriteField)(const Row*, RichString*, RowField); +typedef bool (*Row_IsHighlighted)(const Row*); +typedef bool (*Row_IsVisible)(const Row*, const struct Table_*); +typedef bool (*Row_MatchesFilter)(const Row*, const struct Table_*); +typedef const char* (*Row_SortKeyString)(const Row*); +typedef int (*Row_CompareByParent)(const Row*, const Row*); + +int Row_compare(const void* v1, const void* v2); + +typedef struct RowClass_ { + const ObjectClass super; + const Row_IsHighlighted isHighlighted; + const Row_IsVisible isVisible; + const Row_WriteField writeField; + const Row_MatchesFilter matchesFilter; + const Row_SortKeyString sortKeyString; + const Row_CompareByParent compareByParent; +} RowClass; + +#define As_Row(this_) ((const RowClass*)((this_)->super.klass)) + +#define Row_isHighlighted(r_) (As_Row(r_)->isHighlighted ? (As_Row(r_)->isHighlighted(r_)) : false) +#define Row_isVisible(r_, t_) (As_Row(r_)->isVisible ? (As_Row(r_)->isVisible(r_, t_)) : true) +#define Row_matchesFilter(r_, t_) (As_Row(r_)->matchesFilter ? (As_Row(r_)->matchesFilter(r_, t_)) : false) +#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_)) + +#define ONE_K 1024UL +#define ONE_M (ONE_K * ONE_K) +#define ONE_G (ONE_M * ONE_K) +#define ONE_T (1ULL * ONE_G * ONE_K) +#define ONE_P (1ULL * ONE_T * ONE_K) + +#define ONE_DECIMAL_K 1000UL +#define ONE_DECIMAL_M (ONE_DECIMAL_K * ONE_DECIMAL_K) +#define ONE_DECIMAL_G (ONE_DECIMAL_M * ONE_DECIMAL_K) +#define ONE_DECIMAL_T (1ULL * ONE_DECIMAL_G * ONE_DECIMAL_K) +#define ONE_DECIMAL_P (1ULL * ONE_DECIMAL_T * ONE_DECIMAL_K) + +extern const RowClass Row_class; + +void Row_init(Row* this, const struct Machine_* host); + +void Row_display(const Object* cast, RichString* out); + +void Row_toggleTag(Row* this); + +void Row_resetFieldWidths(void); + +void Row_updateFieldWidth(RowField key, size_t width); + +void Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width); + +const char* RowField_alignedTitle(const struct Settings_* settings, RowField field); + +RowField RowField_keyAt(const struct Settings_* settings, int at); + +/* Sets the size of the PID column based on the passed PID */ +void Row_setPidColumnWidth(pid_t maxPid); + +/* Sets the size of the UID column based on the passed UID */ +void Row_setUidColumnWidth(uid_t maxUid); + +/* Takes number in bytes (base 1024). Prints 6 columns. */ +void Row_printBytes(RichString* str, unsigned long long number, bool coloring); + +/* Takes number in kilo bytes (base 1024). Prints 6 columns. */ +void Row_printKBytes(RichString* str, unsigned long long number, bool coloring); + +/* Takes number as count (base 1000). Prints 12 columns. */ +void Row_printCount(RichString* str, unsigned long long number, bool coloring); + +/* Takes time in hundredths of a seconds. Prints 9 columns. */ +void Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring); + +/* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */ +void Row_printRate(RichString* str, double rate, bool coloring); + +void Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr); + +void Row_display(const Object* cast, RichString* out); + +static inline int Row_idEqualCompare(const void* v1, const void* v2) { + const int p1 = ((const Row*)v1)->id; + const int p2 = ((const Row*)v2)->id; + return p1 != p2; /* return zero when equal */ +} + +/* Routines used primarily with the tree view */ +static inline int Row_getGroupOrParent(const Row* this) { + return this->group == this->id ? this->parent : this->group; +} + +static inline bool Row_isChildOf(const Row* this, int id) { + return id == Row_getGroupOrParent(this); +} + +int Row_compareByParent_Base(const void* v1, const void* v2); + +#endif diff --git a/RowField.h b/RowField.h new file mode 100644 index 000000000..1e01ea3e9 --- /dev/null +++ b/RowField.h @@ -0,0 +1,56 @@ +#ifndef HEADER_RowField +#define HEADER_RowField +/* +htop - RowField.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "ProcessField.h" // platform-specific fields reserved for processes + + +typedef enum ReservedFields_ { + NULL_FIELD = 0, + PID = 1, + COMM = 2, + STATE = 3, + PPID = 4, + PGRP = 5, + SESSION = 6, + TTY = 7, + TPGID = 8, + MINFLT = 10, + MAJFLT = 12, + PRIORITY = 18, + NICE = 19, + STARTTIME = 21, + PROCESSOR = 38, + M_VIRT = 39, + M_RESIDENT = 40, + ST_UID = 46, + PERCENT_CPU = 47, + PERCENT_MEM = 48, + USER = 49, + TIME = 50, + NLWP = 51, + TGID = 52, + PERCENT_NORM_CPU = 53, + ELAPSED = 54, + SCHEDULERPOLICY = 55, + PROC_COMM = 124, + PROC_EXE = 125, + CWD = 126, + + /* Platform specific fields, defined in ${platform}/ProcessField.h */ + PLATFORM_PROCESS_FIELDS + + /* Do not add new fields after this entry (dynamic entries follow) */ + LAST_RESERVED_FIELD +} ReservedFields; + +/* Follow ReservedField entries with dynamic fields defined at runtime */ +#define ROW_DYNAMIC_FIELDS LAST_RESERVED_FIELD +typedef int32_t RowField; + +#endif diff --git a/Scheduling.c b/Scheduling.c index 5ca49ae2d..10846c0b1 100644 --- a/Scheduling.c +++ b/Scheduling.c @@ -97,7 +97,7 @@ Panel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority) { return this; } -bool Scheduling_setPolicy(Process* proc, Arg arg) { +static bool Scheduling_setPolicy(Process* p, Arg arg) { const SchedulingArg* sarg = arg.v; int policy = sarg->policy; @@ -112,13 +112,19 @@ bool Scheduling_setPolicy(Process* proc, Arg arg) { policy &= SCHED_RESET_ON_FORK; #endif - int r = sched_setscheduler(proc->pid, policy, ¶m); + int r = sched_setscheduler(Process_getPid(p), policy, ¶m); /* POSIX says on success the previous scheduling policy should be returned, * but Linux always returns 0. */ return r != -1; } +bool Scheduling_rowSetPolicy(Row* row, Arg arg) { + Process* p = (Process*) row; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return Scheduling_setPolicy(p, arg); +} + const char* Scheduling_formatPolicy(int policy) { #ifdef SCHED_RESET_ON_FORK policy = policy & ~SCHED_RESET_ON_FORK; @@ -149,6 +155,6 @@ const char* Scheduling_formatPolicy(int policy) { } void Scheduling_readProcessPolicy(Process* proc) { - proc->scheduling_policy = sched_getscheduler(proc->pid); + proc->scheduling_policy = sched_getscheduler(Process_getPid(proc)); } #endif /* SCHEDULER_SUPPORT */ diff --git a/Scheduling.h b/Scheduling.h index d91855ae4..e5952b007 100644 --- a/Scheduling.h +++ b/Scheduling.h @@ -13,6 +13,7 @@ in the source distribution for its full text. #include #include "Panel.h" +#include "Process.h" #if defined(HAVE_SCHED_SETSCHEDULER) && defined(HAVE_SCHED_GETSCHEDULER) @@ -38,7 +39,7 @@ typedef struct { int priority; } SchedulingArg; -bool Scheduling_setPolicy(Process* proc, Arg arg); +bool Scheduling_rowSetPolicy(Row* proc, Arg arg); const char* Scheduling_formatPolicy(int policy); diff --git a/ScreenManager.c b/ScreenManager.c index a089eda10..e39c4f069 100644 --- a/ScreenManager.c +++ b/ScreenManager.c @@ -133,13 +133,14 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi *oldTime = newTime; int oldUidDigits = Process_uidDigits; if (!this->state->pauseUpdate && (*sortTimeout == 0 || host->settings->ss->treeView)) { - host->pl->needsSort = true; + host->activeTable->needsSort = true; *sortTimeout = 1; } // sample current values for system metrics and processes if not paused Machine_scan(host); if (!this->state->pauseUpdate) - ProcessList_scan(host->pl); + Machine_scanTables(host); + // always update header, especially to avoid gaps in graph meters Header_updateData(this->header); // force redraw if the number of UID digits was changed @@ -149,7 +150,7 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi *redraw = true; } if (*redraw) { - ProcessList_rebuildPanel(host->pl); + Table_rebuildPanel(host->activeTable); if (!this->state->hideMeters) Header_draw(this->header); } diff --git a/Settings.c b/Settings.c index c712966e3..b772f0916 100644 --- a/Settings.c +++ b/Settings.c @@ -206,7 +206,7 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) static const char* toFieldName(Hashtable* columns, int id) { if (id < 0) return NULL; - if (id >= LAST_PROCESSFIELD) { + if (id >= ROW_DYNAMIC_FIELDS) { const DynamicColumn* column = DynamicColumn_lookup(columns, id); return column->name; } @@ -283,7 +283,7 @@ ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* default .sortKey = sortKey, .treeSortKey = PID, .treeView = false, - .treeViewAlwaysByPID = false, + .treeViewAlwaysByID = false, .allBranchesCollapsed = false, }; @@ -372,7 +372,7 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini } else if (String_eq(option[0], "tree_view_always_by_pid") && this->config_version <= 2) { // old (no screen) naming also supported for backwards compatibility screen = Settings_defaultScreens(this); - screen->treeViewAlwaysByPID = atoi(option[1]); + screen->treeViewAlwaysByID = atoi(option[1]); } else if (String_eq(option[0], "all_branches_collapsed") && this->config_version <= 2) { // old (no screen) naming also supported for backwards compatibility screen = Settings_defaultScreens(this); @@ -501,7 +501,7 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini screen->treeView = atoi(option[1]); } else if (String_eq(option[0], ".tree_view_always_by_pid")) { if (screen) - screen->treeViewAlwaysByPID = atoi(option[1]); + screen->treeViewAlwaysByID = atoi(option[1]); } else if (String_eq(option[0], ".all_branches_collapsed")) { if (screen) screen->allBranchesCollapsed = atoi(option[1]); @@ -634,7 +634,7 @@ int Settings_write(const Settings* this, bool onCrash) { printSettingInteger("tree_sort_key", this->screens[0]->treeSortKey - 1); printSettingInteger("sort_direction", this->screens[0]->direction); printSettingInteger("tree_sort_direction", this->screens[0]->treeDirection); - printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByPID); + printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByID); printSettingInteger("all_branches_collapsed", this->screens[0]->allBranchesCollapsed); for (unsigned int i = 0; i < this->nScreens; i++) { @@ -644,7 +644,7 @@ int Settings_write(const Settings* this, bool onCrash) { printSettingString(".sort_key", toFieldName(this->dynamicColumns, ss->sortKey)); printSettingString(".tree_sort_key", toFieldName(this->dynamicColumns, ss->treeSortKey)); printSettingInteger(".tree_view", ss->treeView); - printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID); + printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByID); printSettingInteger(".sort_direction", ss->direction); printSettingInteger(".tree_sort_direction", ss->treeDirection); printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed); @@ -787,7 +787,7 @@ void ScreenSettings_invertSortOrder(ScreenSettings* this) { } void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) { - if (this->treeViewAlwaysByPID || !this->treeView) { + if (this->treeViewAlwaysByID || !this->treeView) { this->sortKey = sortKey; this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1; this->treeView = false; diff --git a/Settings.h b/Settings.h index 48c62590a..3caaab282 100644 --- a/Settings.h +++ b/Settings.h @@ -14,7 +14,7 @@ in the source distribution for its full text. #include "Hashtable.h" #include "HeaderLayout.h" -#include "Process.h" +#include "Row.h" #define DEFAULT_DELAY 15 @@ -33,16 +33,16 @@ typedef struct { int* modes; } MeterColumnSetting; -typedef struct { +typedef struct ScreenSettings_ { char* name; - ProcessField* fields; + RowField* fields; uint32_t flags; int direction; int treeDirection; - ProcessField sortKey; - ProcessField treeSortKey; + RowField sortKey; + RowField treeSortKey; bool treeView; - bool treeViewAlwaysByPID; + bool treeViewAlwaysByID; bool allBranchesCollapsed; } ScreenSettings; @@ -104,9 +104,9 @@ typedef struct Settings_ { #define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu)) -static inline ProcessField ScreenSettings_getActiveSortKey(const ScreenSettings* this) { +static inline RowField ScreenSettings_getActiveSortKey(const ScreenSettings* this) { return (this->treeView) - ? (this->treeViewAlwaysByPID ? PID : this->treeSortKey) + ? (this->treeViewAlwaysByID ? 1 : this->treeSortKey) : this->sortKey; } @@ -126,7 +126,7 @@ void ScreenSettings_delete(ScreenSettings* this); void ScreenSettings_invertSortOrder(ScreenSettings* this); -void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey); +void ScreenSettings_setSortKey(ScreenSettings* this, RowField sortKey); void Settings_enableReadonly(void); diff --git a/Table.c b/Table.c new file mode 100644 index 000000000..dcb486777 --- /dev/null +++ b/Table.c @@ -0,0 +1,370 @@ +/* +htop - Table.c +(C) 2004,2005 Hisham H. Muhammad +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Table.h" + +#include +#include +#include + +#include "CRT.h" +#include "DynamicColumn.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Macros.h" +#include "Platform.h" +#include "Vector.h" +#include "XUtils.h" + + +Table* Table_init(Table* this, const ObjectClass* klass, Machine* host) { + this->rows = Vector_new(klass, true, DEFAULT_SIZE); + this->displayList = Vector_new(klass, false, DEFAULT_SIZE); + this->table = Hashtable_new(200, false); + this->needsSort = true; + this->following = -1; + this->host = host; + return this; +} + +void Table_done(Table* this) { + Hashtable_delete(this->table); + Vector_delete(this->displayList); + Vector_delete(this->rows); +} + +static void Table_delete(Object* cast) { + Table* this = (Table*) cast; + Table_done(this); + free(this); +} + +void Table_setPanel(Table* this, Panel* panel) { + this->panel = panel; +} + +void Table_add(Table* this, Row* row) { + assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) == -1); + assert(Hashtable_get(this->table, row->id) == NULL); + + // highlighting row found in first scan by first scan marked "far in the past" + row->seenStampMs = this->host->monotonicMs; + + Vector_add(this->rows, row); + Hashtable_put(this->table, row->id, row); + + assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) != -1); + assert(Hashtable_get(this->table, row->id) != NULL); + assert(Vector_countEquals(this->rows, Hashtable_count(this->table))); +} + +// Table_removeIndex removes a given row from the lists map and soft deletes +// it from its vector. Vector_compact *must* be called once the caller is done +// removing items. +// Note: for processes should only be called from ProcessList_iterate to avoid +// breaking dying process highlighting. +void Table_removeIndex(Table* this, const Row* row, int idx) { + int rowid = row->id; + + assert(row == (Row*)Vector_get(this->rows, idx)); + assert(Hashtable_get(this->table, rowid) != NULL); + + Hashtable_remove(this->table, rowid); + Vector_softRemove(this->rows, idx); + + if (this->following != -1 && this->following == rowid) { + this->following = -1; + Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); + } + + assert(Hashtable_get(this->table, rowid) == NULL); + assert(Vector_countEquals(this->rows, Hashtable_count(this->table))); +} + +static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, int32_t indent, bool show) { + // Do not treat zero as root of any tree. + // (e.g. on OpenBSD the kernel thread 'swapper' has pid 0.) + if (rowid == 0) + return; + + // The vector is sorted by parent, find the start of the range by bisection + int vsize = Vector_size(this->rows); + int l = 0; + int r = vsize; + while (l < r) { + int c = (l + r) / 2; + Row* row = (Row*)Vector_get(this->rows, c); + int parent = row->isRoot ? 0 : Row_getGroupOrParent(row); + if (parent < rowid) { + l = c + 1; + } else { + r = c; + } + } + // Find the end to know the last line for indent handling purposes + int lastShown = r; + while (r < vsize) { + Row* row = (Row*)Vector_get(this->rows, r); + if (!Row_isChildOf(row, rowid)) + break; + if (row->show) + lastShown = r; + r++; + } + + for (int i = l; i < r; i++) { + Row* row = (Row*)Vector_get(this->rows, i); + + if (!show) + row->show = false; + + Vector_add(this->displayList, row); + + 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 (i == lastShown) + row->indent = -nextIndent; + else + row->indent = nextIndent; + + row->tree_depth = level + 1; + } +} + +static int compareRowByKnownParentThenNatural(const void* v1, const void* v2) { + return Row_compareByParent((const Row*) v1, (const Row*) v2); +} + +// Builds a sorted tree from scratch, without relying on previously gathered information +static void Table_buildTree(Table* this) { + Vector_prune(this->displayList); + + // Mark root processes + int vsize = Vector_size(this->rows); + for (int i = 0; i < vsize; i++) { + Row* row = (Row*) Vector_get(this->rows, i); + int parent = Row_getGroupOrParent(row); + row->isRoot = false; + + if (row->id == parent) { + row->isRoot = true; + continue; + } + + if (!parent) { + row->isRoot = true; + continue; + } + + // We don't know about its parent for whatever reason + if (Table_findRow(this, parent) == NULL) + row->isRoot = true; + } + + // Sort by known parent (roots first), then row ID + Vector_quickSortCustomCompare(this->rows, compareRowByKnownParentThenNatural); + + // Find all processes whose parent is not visible + for (int i = 0; i < vsize; i++) { + Row* row = (Row*)Vector_get(this->rows, i); + + // If parent not found, then construct the tree with this node as root + if (row->isRoot) { + row = (Row*)Vector_get(this->rows, i); + row->indent = 0; + row->tree_depth = 0; + Vector_add(this->displayList, row); + Table_buildTreeBranch(this, row->id, 0, 0, row->showChildren); + continue; + } + } + + this->needsSort = false; + + // Check consistency of the built structures + assert(Vector_size(this->displayList) == vsize); (void)vsize; +} + +void Table_updateDisplayList(Table* this) { + const Settings* settings = this->host->settings; + if (settings->ss->treeView) { + if (this->needsSort) + Table_buildTree(this); + } else { + if (this->needsSort) + Vector_insertionSort(this->rows); + Vector_prune(this->displayList); + int size = Vector_size(this->rows); + for (int i = 0; i < size; i++) + Vector_add(this->displayList, Vector_get(this->rows, i)); + } + this->needsSort = false; +} + +void Table_expandTree(Table* this) { + int size = Vector_size(this->rows); + for (int i = 0; i < size; i++) { + Row* row = (Row*) Vector_get(this->rows, i); + row->showChildren = true; + } +} + +// Called on collapse-all toggle and on startup, possibly in non-tree mode +void Table_collapseAllBranches(Table* this) { + Table_buildTree(this); // Update `tree_depth` fields of the rows + this->needsSort = true; // Table is sorted by parent now, force new sort + int size = Vector_size(this->rows); + for (int i = 0; i < size; i++) { + Row* row = (Row*) Vector_get(this->rows, i); + // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1 + if (row->tree_depth > 0 && row->id > 1) + row->showChildren = false; + } +} + +void Table_rebuildPanel(Table* this) { + Table_updateDisplayList(this); + + const int currPos = Panel_getSelectedIndex(this->panel); + const int currScrollV = this->panel->scrollV; + const int currSize = Panel_size(this->panel); + + Panel_prune(this->panel); + + /* Follow main group row instead if following a row that is occluded (hidden) */ + if (this->following != -1) { + const Row* followed = (const Row*) Hashtable_get(this->table, this->following); + if (followed != NULL + && Hashtable_get(this->table, followed->group) + && Row_isVisible(followed, this) == false ) { + this->following = followed->group; + } + } + + const int rowCount = Vector_size(this->displayList); + bool foundFollowed = false; + int idx = 0; + + for (int i = 0; i < rowCount; i++) { + Row* row = (Row*) Vector_get(this->displayList, i); + + if ( !row->show || (Row_matchesFilter(row, this) == true) ) + continue; + + Panel_set(this->panel, idx, (Object*)row); + + if (this->following != -1 && row->id == this->following) { + foundFollowed = true; + Panel_setSelected(this->panel, idx); + /* Keep scroll position relative to followed row */ + this->panel->scrollV = idx - (currPos-currScrollV); + } + idx++; + } + + if (this->following != -1 && !foundFollowed) { + /* Reset if current followed row not found */ + this->following = -1; + Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS); + } + + if (this->following == -1) { + /* If the last item was selected, keep the new last item selected */ + if (currPos > 0 && currPos == currSize - 1) + Panel_setSelected(this->panel, Panel_size(this->panel) - 1); + else + Panel_setSelected(this->panel, currPos); + + this->panel->scrollV = currScrollV; + } +} + +void Table_printHeader(const Settings* settings, RichString* header) { + RichString_rewind(header, RichString_size(header)); + + const ScreenSettings* ss = settings->ss; + const RowField* fields = ss->fields; + + RowField key = ScreenSettings_getActiveSortKey(ss); + + for (int i = 0; fields[i]; i++) { + int color; + if (ss->treeView && ss->treeViewAlwaysByID) { + color = CRT_colors[PANEL_HEADER_FOCUS]; + } else if (key == fields[i]) { + color = CRT_colors[PANEL_SELECTION_FOCUS]; + } else { + color = CRT_colors[PANEL_HEADER_FOCUS]; + } + + RichString_appendWide(header, color, RowField_alignedTitle(settings, fields[i])); + if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') { + bool ascending = ScreenSettings_getActiveDirection(ss) == 1; + RichString_rewind(header, 1); // rewind to override space + RichString_appendnWide(header, + CRT_colors[PANEL_SELECTION_FOCUS], + CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC], + 1); + } + if (COMM == fields[i] && settings->showMergedCommand) { + RichString_appendAscii(header, color, "(merged)"); + } + } +} + +// set flags on an existing rows before refreshing table +void Table_prepareEntries(Table* this) { + for (int i = 0; i < Vector_size(this->rows); i++) { + Row* row = (struct Row_*) Vector_get(this->rows, i); + row->updated = false; + row->wasShown = row->show; + row->show = true; + } +} + +// tidy up Row state after refreshing the table +void Table_cleanupRow(Table* table, Row* row, int idx) { + Machine* host = table->host; + const Settings* settings = host->settings; + + if (row->tombStampMs > 0) { + // remove tombed process + if (host->monotonicMs >= row->tombStampMs) { + Table_removeIndex(table, row, idx); + } + } else if (row->updated == false) { + // process no longer exists + if (settings->highlightChanges && row->wasShown) { + // mark tombed + row->tombStampMs = host->monotonicMs + 1000 * settings->highlightDelaySecs; + } else { + // immediately remove + Table_removeIndex(table, row, idx); + } + } +} + +void Table_cleanupEntries(Table* this) { + // Finish process table update, culling any removed rows + for (int i = Vector_size(this->rows) - 1; i >= 0; i--) { + Row* row = (Row*) Vector_get(this->rows, i); + Table_cleanupRow(this, row, i); + } + + // compact the table in case of any earlier row removals + Table_compact(this); +} + +const TableClass Table_class = { + .super = { + .extends = Class(Object), + .delete = Table_delete, + }, + .prepare = Table_prepareEntries, + .cleanup = Table_cleanupEntries, +}; diff --git a/Table.h b/Table.h new file mode 100644 index 000000000..d16e47daa --- /dev/null +++ b/Table.h @@ -0,0 +1,101 @@ +#ifndef HEADER_Table +#define HEADER_Table +/* +htop - Table.h +(C) 2004,2005 Hisham H. Muhammad +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include +#include +#include +#include +#include + +#include "Hashtable.h" +#include "Object.h" +#include "RichString.h" +#include "Settings.h" +#include "Vector.h" + + +struct Machine_; +struct Panel_; +struct Row_; + +typedef struct Table_ { + /* Super object for emulated OOP */ + Object super; + + Vector* rows; /* all known; sort order can vary and differ from display order */ + Vector* displayList; /* row tree flattened in display order (borrowed); + updated in Table_updateDisplayList when rebuilding panel */ + Hashtable* table; /* fast known row lookup by identifier */ + + struct Machine_* host; + const char* incFilter; + bool needsSort; + int following; /* -1 or row being visually tracked in the user interface */ + + struct Panel_* panel; +} Table; + +typedef Table* (*Table_New)(const struct Machine_*); +typedef void (*Table_ScanPrepare)(Table* this); +typedef void (*Table_ScanIterate)(Table* this); +typedef void (*Table_ScanCleanup)(Table* this); + +typedef struct TableClass_ { + const ObjectClass super; + const Table_ScanPrepare prepare; + const Table_ScanIterate iterate; + const Table_ScanCleanup cleanup; +} TableClass; + +#define As_Table(this_) ((const TableClass*)((this_)->super.klass)) + +#define Table_scanPrepare(t_) (As_Table(t_)->prepare ? (As_Table(t_)->prepare(t_)) : Table_prepareEntries(t_)) +#define Table_scanIterate(t_) (As_Table(t_)->iterate(t_)) /* mandatory; must have a custom iterate method */ +#define Table_scanCleanup(t_) (As_Table(t_)->cleanup ? (As_Table(t_)->cleanup(t_)) : Table_cleanupEntries(t_)) + +Table* Table_init(Table* this, const ObjectClass* klass, struct Machine_* host); + +void Table_done(Table* this); + +extern const TableClass Table_class; + +void Table_setPanel(Table* this, struct Panel_* panel); + +void Table_printHeader(const Settings* settings, RichString* header); + +void Table_add(Table* this, struct Row_* row); + +void Table_removeIndex(Table* this, const struct Row_* row, int idx); + +void Table_updateDisplayList(Table* this); + +void Table_expandTree(Table* this); + +void Table_collapseAllBranches(Table* this); + +void Table_rebuildPanel(Table* this); + +static inline struct Row_* Table_findRow(Table* this, int id) { + return (struct Row_*) Hashtable_get(this->table, id); +} + +void Table_prepareEntries(Table* this); + +void Table_cleanupEntries(Table* this); + +void Table_cleanupRow(Table* this, Row* row, int idx); + +static inline void Table_compact(Table* this) { + Vector_compact(this->rows); +} + +#endif diff --git a/TasksMeter.c b/TasksMeter.c index b5563fcab..7dd6fdb41 100644 --- a/TasksMeter.c +++ b/TasksMeter.c @@ -25,7 +25,8 @@ static const int TasksMeter_attributes[] = { static void TasksMeter_updateValues(Meter* this) { const Machine* host = this->host; - const ProcessList* pl = host->pl; + const ProcessList* pl = (const ProcessList*) host->processTable; + this->values[0] = pl->kernelThreads; this->values[1] = pl->userlandThreads; this->values[2] = pl->totalTasks - pl->kernelThreads - pl->userlandThreads; diff --git a/TraceScreen.c b/TraceScreen.c index e8f55ff56..03315482b 100644 --- a/TraceScreen.c +++ b/TraceScreen.c @@ -62,7 +62,7 @@ void TraceScreen_delete(Object* cast) { } static void TraceScreen_draw(InfoScreen* this) { - InfoScreen_drawTitled(this, "Trace of process %d - %s", this->process->pid, Process_getCommand(this->process)); + InfoScreen_drawTitled(this, "Trace of process %d - %s", Process_getPid(this->process), Process_getCommand(this->process)); } bool TraceScreen_forkTracer(TraceScreen* this) { @@ -89,7 +89,7 @@ bool TraceScreen_forkTracer(TraceScreen* this) { close(fdpair[1]); char buffer[32] = {0}; - xSnprintf(buffer, sizeof(buffer), "%d", this->super.process->pid); + xSnprintf(buffer, sizeof(buffer), "%d", Process_getPid(this->super.process)); // Use of NULL in variadic functions must have a pointer cast. // The NULL constant is not required by standard to have a pointer type. execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (char*)NULL); diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index e6366d70f..ec98341e3 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -72,8 +72,8 @@ void Process_delete(Object* cast) { free(this); } -static void DarwinProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const DarwinProcess* dp = (const DarwinProcess*) this; +static void DarwinProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const DarwinProcess* dp = (const DarwinProcess*) super; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; int n = sizeof(buffer) - 1; @@ -81,7 +81,7 @@ static void DarwinProcess_writeField(const Process* this, RichString* str, Proce // add Platform-specific fields here case TRANSLATED: xSnprintf(buffer, n, "%c ", dp->translated ? 'T' : 'N'); break; default: - Process_writeField(this, str, field); + Process_writeField(&dp->super, str, field); return; } RichString_appendWide(str, attr, buffer); @@ -292,7 +292,7 @@ static char* DarwinProcess_getDevname(dev_t dev) { void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) { DarwinProcess* dp = (DarwinProcess*)proc; - const Settings* settings = proc->host->settings; + const Settings* settings = proc->super.host->settings; const struct extern_proc* ep = &ps->kp_proc; @@ -312,12 +312,12 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, /* First, the "immutable" parts */ if (!exists) { /* Set the PID/PGID/etc. */ - proc->pid = ep->p_pid; - proc->ppid = ps->kp_eproc.e_ppid; + Process_setPid(proc, ep->p_pid); + Process_setThreadGroup(proc, ep->p_pid); + Process_setParent(proc, ps->kp_eproc.e_ppid); proc->pgrp = ps->kp_eproc.e_pgid; proc->session = 0; /* TODO Get the session id */ proc->tpgid = ps->kp_eproc.e_tpgid; - proc->tgid = proc->pid; proc->isKernelThread = false; proc->isUserlandThread = false; dp->translated = ps->kp_proc.p_flag & P_TRANSLATED; @@ -359,14 +359,14 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, proc->state = (ep->p_stat == SZOMB) ? ZOMBIE : UNKNOWN; /* Make sure the updated flag is set */ - proc->updated = true; + proc->super.updated = true; } void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS) { struct proc_taskinfo pti; - if (sizeof(pti) == proc_pidinfo(proc->super.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { - const DarwinMachine* dhost = (const DarwinMachine*) proc->super.host; + if (sizeof(pti) == proc_pidinfo(Process_getPid(&proc->super), PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { + const DarwinMachine* dhost = (const DarwinMachine*) proc->super.super.host; uint64_t total_existing_time_ns = proc->stime + proc->utime; @@ -419,7 +419,7 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) { } task_t port; - ret = task_for_pid(mach_task_self(), proc->pid, &port); + ret = task_for_pid(mach_task_self(), Process_getPid(proc), &port); if (ret != KERN_SUCCESS) { dp->taskAccess = false; return; @@ -472,11 +472,18 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) { const ProcessClass DarwinProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = DarwinProcess_rowWriteField }, - .writeField = DarwinProcess_writeField, - .compareByKey = DarwinProcess_compareByKey, + .compareByKey = DarwinProcess_compareByKey }; diff --git a/darwin/DarwinProcessList.c b/darwin/DarwinProcessList.c index c10e772c9..1545600eb 100644 --- a/darwin/DarwinProcessList.c +++ b/darwin/DarwinProcessList.c @@ -55,20 +55,22 @@ static struct kinfo_proc* ProcessList_getKInfoProcs(size_t* count) { ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { DarwinProcessList* this = xCalloc(1, sizeof(DarwinProcessList)); - ProcessList* super = &this->super; + Object_setClass(this, Class(ProcessList)); + ProcessList* super = &this->super; ProcessList_init(super, Class(DarwinProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* this) { - ProcessList_done(this); +void ProcessList_delete(Object* cast) { + DarwinProcessList* this = (DarwinProcessList*) cast; + ProcessList_done(&this->super); free(this); } void ProcessList_goThroughEntries(ProcessList* super) { - const Machine* host = super->host; + const Machine* host = super->super.host; const DarwinMachine* dhost = (const DarwinMachine*) host; DarwinProcessList* dpl = (DarwinProcessList*) super; bool preExisting = true; diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c index 7cfc71be5..a85b6efb0 100644 --- a/dragonflybsd/DragonFlyBSDProcess.c +++ b/dragonflybsd/DragonFlyBSDProcess.c @@ -66,16 +66,17 @@ void Process_delete(Object* cast) { free(this); } -static void DragonFlyBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) { +static void DragonFlyBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const Process* this = (const Process*) super; const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) this; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; switch (field) { // add Platform-specific fields here - case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_isKernelThread(this) ? -1 : this->pid); break; + case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_isKernelThread(this) ? -1 : Process_getPid(this)); break; case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break; - case JAIL: Process_printLeftAlignedField(str, attr, fp->jname, 11); return; + case JAIL: Row_printLeftAlignedField(str, attr, fp->jname, 11); return; default: Process_writeField(this, str, field); return; @@ -100,11 +101,18 @@ static int DragonFlyBSDProcess_compareByKey(const Process* v1, const Process* v2 const ProcessClass DragonFlyBSDProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = DragonFlyBSDProcess_rowWriteField }, - .writeField = DragonFlyBSDProcess_writeField, .compareByKey = DragonFlyBSDProcess_compareByKey }; diff --git a/dragonflybsd/DragonFlyBSDProcessList.c b/dragonflybsd/DragonFlyBSDProcessList.c index 4ff17932c..6330e911d 100644 --- a/dragonflybsd/DragonFlyBSDProcessList.c +++ b/dragonflybsd/DragonFlyBSDProcessList.c @@ -28,16 +28,17 @@ in the source distribution for its full text. ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { DragonFlyBSDProcessList* this = xCalloc(1, sizeof(DragonFlyBSDProcessList)); - ProcessList* super = (ProcessList*) this; + Object_setClass(this, Class(ProcessList)); + ProcessList* super = (ProcessList*) this; ProcessList_init(super, Class(DragonFlyBSDProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* super) { - const DragonFlyBSDProcessList* this = (DragonFlyBSDProcessList*) super; - ProcessList_done(super); +void ProcessList_delete(Object* cast) { + const DragonFlyBSDProcessList* this = (DragonFlyBSDProcessList*) cast; + ProcessList_done(&this->super); free(this); } @@ -153,17 +154,17 @@ void ProcessList_goThroughEntries(ProcessList* super) { dfp->jid = kproc->kp_jailid; if (kproc->kp_ktaddr && kproc->kp_flags & P_SYSTEM) { // dfb kernel threads all have the same pid, so we misuse the kernel thread address to give them a unique identifier - proc->pid = (pid_t)kproc->kp_ktaddr; + Process_setPid(proc, (pid_t)kproc->kp_ktaddr); proc->isKernelThread = true; } else { - proc->pid = kproc->kp_pid; // process ID + Process_setPid(proc, kproc->kp_pid); // process ID proc->isKernelThread = false; } proc->isUserlandThread = kproc->kp_nthreads > 1; - proc->ppid = kproc->kp_ppid; // parent process id + Process_setParent(proc, kproc->kp_ppid); // parent process id proc->tpgid = kproc->kp_tpgid; // tty process group id - //proc->tgid = kproc->kp_lwp.kl_tid; // thread group id - proc->tgid = kproc->kp_pid; // thread group id + //Process_setThreadGroup(proc, kproc->kp_lwp.kl_tid); // thread group id + Process_setThreadGroup(proc, kproc->kp_pid); proc->pgrp = kproc->kp_pgid; // process group id proc->session = kproc->kp_sid; proc->st_uid = kproc->kp_uid; // user ID @@ -199,7 +200,7 @@ void ProcessList_goThroughEntries(ProcessList* super) { dfp->jname = DragonFlyBSDMachine_readJailName(dhost, kproc->kp_jailid); } // if there are reapers in the system, process can get reparented anytime - proc->ppid = kproc->kp_ppid; + Process_setParent(proc, kproc->kp_ppid); if (proc->st_uid != kproc->kp_uid) { // some processes change users (eg. to lower privs) proc->st_uid = kproc->kp_uid; proc->user = UsersTable_getRef(host->usersTable, proc->st_uid); @@ -303,7 +304,7 @@ void ProcessList_goThroughEntries(ProcessList* super) { if (proc->state == RUNNING) super->runningTasks++; - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); - proc->updated = true; + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.updated = true; } } diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c index 4970ff2cc..f6d3451da 100644 --- a/freebsd/FreeBSDProcess.c +++ b/freebsd/FreeBSDProcess.c @@ -71,8 +71,8 @@ void Process_delete(Object* cast) { free(this); } -static void FreeBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const FreeBSDProcess* fp = (const FreeBSDProcess*) this; +static void FreeBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const FreeBSDProcess* fp = (const FreeBSDProcess*) super; char buffer[256]; size_t n = sizeof(buffer); int attr = CRT_colors[DEFAULT_COLOR]; @@ -81,13 +81,13 @@ static void FreeBSDProcess_writeField(const Process* this, RichString* str, Proc // add FreeBSD-specific fields here case JID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, fp->jid); break; case JAIL: - Process_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11); + Row_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11); return; case EMULATION: - Process_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16); + Row_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16); return; default: - Process_writeField(this, str, field); + Process_writeField(&fp->super, str, field); return; } RichString_appendWide(str, attr, buffer); @@ -112,11 +112,18 @@ static int FreeBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro const ProcessClass FreeBSDProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = FreeBSDProcess_rowWriteField }, - .writeField = FreeBSDProcess_writeField, .compareByKey = FreeBSDProcess_compareByKey }; diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessList.c index d8d4bbe04..aabb61ccd 100644 --- a/freebsd/FreeBSDProcessList.c +++ b/freebsd/FreeBSDProcessList.c @@ -43,18 +43,17 @@ in the source distribution for its full text. ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { FreeBSDProcessList* this = xCalloc(1, sizeof(FreeBSDProcessList)); - ProcessList* super = &this->super; + Object_setClass(this, Class(ProcessList)); + ProcessList* super = &this->super; ProcessList_init(super, Class(FreeBSDProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* super) { - FreeBSDProcessList* this = (FreeBSDProcessList*) super; - - ProcessList_done(super); - +void ProcessList_delete(Object* cast) { + FreeBSDProcessList* this = (FreeBSDProcessList*) cast; + ProcessList_done(&this->super); free(this); } @@ -174,12 +173,12 @@ void ProcessList_goThroughEntries(ProcessList* super) { if (!preExisting) { fp->jid = kproc->ki_jid; - proc->pid = kproc->ki_pid; + Process_setPid(proc, kproc->ki_pid); + Process_setThreadGroup(proc, kproc->ki_pid); + Process_setParent(proc, kproc->ki_ppid); proc->isKernelThread = kproc->ki_pid != 1 && (kproc->ki_flag & P_SYSTEM); proc->isUserlandThread = false; - proc->ppid = kproc->ki_ppid; proc->tpgid = kproc->ki_tpgid; - proc->tgid = kproc->ki_pid; proc->session = kproc->ki_sid; proc->pgrp = kproc->ki_pgid; proc->st_uid = kproc->ki_uid; @@ -279,11 +278,11 @@ void ProcessList_goThroughEntries(ProcessList* super) { Scheduling_readProcessPolicy(proc); #endif - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); super->totalTasks++; if (proc->state == RUNNING) super->runningTasks++; - proc->updated = true; + proc->super.updated = true; } } diff --git a/linux/LinuxMachine.c b/linux/LinuxMachine.c index 21fd4bd16..d98edbe25 100644 --- a/linux/LinuxMachine.c +++ b/linux/LinuxMachine.c @@ -502,7 +502,8 @@ static void LinuxMachine_scanCPUTime(LinuxMachine* this) { char buffer[PROC_LINE_LENGTH + 1]; while (fgets(buffer, sizeof(buffer), file)) { if (String_startsWith(buffer, "procs_running")) { - this->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10); + ProcessList* pl = (ProcessList*) super->processTable; + pl->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10); break; } } diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 875618187..e348f9ab1 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -143,22 +143,29 @@ static int LinuxProcess_effectiveIOPriority(const LinuxProcess* this) { #define SYS_ioprio_set __NR_ioprio_set #endif -IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this) { +IOPriority LinuxProcess_updateIOPriority(Process* p) { IOPriority ioprio = 0; // Other OSes masquerading as Linux (NetBSD?) don't have this syscall #ifdef SYS_ioprio_get - ioprio = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, this->super.pid); + ioprio = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, Process_getPid(p)); #endif + LinuxProcess* this = (LinuxProcess*) p; this->ioPriority = ioprio; return ioprio; } -bool LinuxProcess_setIOPriority(Process* this, Arg ioprio) { +static bool LinuxProcess_setIOPriority(Process* p, Arg ioprio) { // Other OSes masquerading as Linux (NetBSD?) don't have this syscall #ifdef SYS_ioprio_set - syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, this->pid, ioprio.i); + syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, Process_getPid(p), ioprio.i); #endif - return (LinuxProcess_updateIOPriority((LinuxProcess*)this) == ioprio.i); + return LinuxProcess_updateIOPriority(p) == ioprio.i; +} + +bool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio) { + Process* p = (Process*) super; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return LinuxProcess_setIOPriority(p, ioprio); } bool LinuxProcess_isAutogroupEnabled(void) { @@ -168,9 +175,10 @@ bool LinuxProcess_isAutogroupEnabled(void) { return buf[0] == '1'; } -bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) { +static bool LinuxProcess_changeAutogroupPriorityBy(Process* p, Arg delta) { char buffer[256]; - xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", this->pid); + pid_t pid = Process_getPid(p); + xSnprintf(buffer, sizeof(buffer), PROCDIR "/%d/autogroup", pid); FILE* file = fopen(buffer, "r+"); if (!file) @@ -192,6 +200,12 @@ bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta) { return success; } +bool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta) { + Process* p = (Process*) super; + assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class)); + return LinuxProcess_changeAutogroupPriorityBy(p, delta); +} + static double LinuxProcess_totalIORate(const LinuxProcess* lp) { double totalRate = NAN; if (isNonnegative(lp->io_rate_read_bps)) { @@ -205,45 +219,47 @@ static double LinuxProcess_totalIORate(const LinuxProcess* lp) { return totalRate; } -static void LinuxProcess_writeField(const Process* this, RichString* str, ProcessField field) { +static void LinuxProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const Process* this = (const Process*) super; + const Machine* host = (const Machine*) super->host; + const LinuxMachine* lhost = (const LinuxMachine*) host; const LinuxProcess* lp = (const LinuxProcess*) this; - const LinuxMachine* lhost = (const LinuxMachine*) this->host; - bool coloring = this->host->settings->highlightMegabytes; + bool coloring = host->settings->highlightMegabytes; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; switch (field) { - case CMINFLT: Process_printCount(str, lp->cminflt, coloring); return; - case CMAJFLT: Process_printCount(str, lp->cmajflt, coloring); return; - case M_DRS: Process_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; + case CMINFLT: Row_printCount(str, lp->cminflt, coloring); return; + case CMAJFLT: Row_printCount(str, lp->cmajflt, coloring); return; + case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; case M_LRS: if (lp->m_lrs) { - Process_printBytes(str, lp->m_lrs * lhost->pageSize, coloring); + Row_printBytes(str, lp->m_lrs * lhost->pageSize, coloring); return; } attr = CRT_colors[PROCESS_SHADOW]; xSnprintf(buffer, n, " N/A "); break; - case M_TRS: Process_printBytes(str, lp->m_trs * lhost->pageSize, coloring); return; - case M_SHARE: Process_printBytes(str, lp->m_share * lhost->pageSize, coloring); return; - case M_PSS: Process_printKBytes(str, lp->m_pss, coloring); return; - case M_SWAP: Process_printKBytes(str, lp->m_swap, coloring); return; - case M_PSSWP: Process_printKBytes(str, lp->m_psswp, coloring); return; - case UTIME: Process_printTime(str, lp->utime, coloring); return; - case STIME: Process_printTime(str, lp->stime, coloring); return; - case CUTIME: Process_printTime(str, lp->cutime, coloring); return; - case CSTIME: Process_printTime(str, lp->cstime, coloring); return; - case RCHAR: Process_printBytes(str, lp->io_rchar, coloring); return; - case WCHAR: Process_printBytes(str, lp->io_wchar, coloring); return; - case SYSCR: Process_printCount(str, lp->io_syscr, coloring); return; - case SYSCW: Process_printCount(str, lp->io_syscw, coloring); return; - case RBYTES: Process_printBytes(str, lp->io_read_bytes, coloring); return; - case WBYTES: Process_printBytes(str, lp->io_write_bytes, coloring); return; - case CNCLWB: Process_printBytes(str, lp->io_cancelled_write_bytes, coloring); return; - case IO_READ_RATE: Process_printRate(str, lp->io_rate_read_bps, coloring); return; - case IO_WRITE_RATE: Process_printRate(str, lp->io_rate_write_bps, coloring); return; - case IO_RATE: Process_printRate(str, LinuxProcess_totalIORate(lp), coloring); return; + 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_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 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; #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; @@ -251,8 +267,8 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces #ifdef HAVE_VSERVER case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break; #endif - case CGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CGROUP], Process_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break; - case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CCGROUP], Process_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break; + case CGROUP: xSnprintf(buffer, n, "%-*.*s ", Row_fieldWidths[CGROUP], Row_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break; + case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Row_fieldWidths[CCGROUP], Row_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break; case OOM: xSnprintf(buffer, n, "%4u ", lp->oom); break; case IO_PRIORITY: { int klass = IOPriority_class(lp->ioPriority); @@ -273,9 +289,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces break; } #ifdef HAVE_DELAYACCT - case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break; - case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break; - case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break; + 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; #endif case CTXT: if (lp->ctxt_diff > 1000) { @@ -283,7 +299,7 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces } xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff); break; - case SECATTR: snprintf(buffer, n, "%-*.*s ", Process_fieldWidths[SECATTR], Process_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break; + case SECATTR: snprintf(buffer, n, "%-*.*s ", Row_fieldWidths[SECATTR], Row_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break; case AUTOGROUP_ID: if (lp->autogroup_id != -1) { xSnprintf(buffer, n, "%4ld ", lp->autogroup_id); @@ -398,11 +414,18 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce const ProcessClass LinuxProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = LinuxProcess_rowWriteField }, - .writeField = LinuxProcess_writeField, .compareByKey = LinuxProcess_compareByKey }; diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index 6c309893c..1cd0eaaff 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -122,13 +122,13 @@ Process* LinuxProcess_new(const Machine* host); void Process_delete(Object* cast); -IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this); +IOPriority LinuxProcess_updateIOPriority(Process* proc); -bool LinuxProcess_setIOPriority(Process* this, Arg ioprio); +bool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio); bool LinuxProcess_isAutogroupEnabled(void); -bool LinuxProcess_changeAutogroupPriorityBy(Process* this, Arg delta); +bool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta); bool Process_isThread(const Process* this); diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index a17b5e137..9be2433e9 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -202,9 +202,11 @@ static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) { ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { LinuxProcessList* this = xCalloc(1, sizeof(LinuxProcessList)); - ProcessList* super = &this->super; + Object_setClass(this, Class(ProcessList)); + ProcessList* super = &this->super; ProcessList_init(super, Class(LinuxProcess), host, pidMatchList); + LinuxProcessList_initTtyDrivers(this); // Test /proc/PID/smaps_rollup availability (faster to parse, Linux 4.14+) @@ -213,9 +215,9 @@ ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { return super; } -void ProcessList_delete(ProcessList* pl) { - LinuxProcessList* this = (LinuxProcessList*) pl; - ProcessList_done(pl); +void ProcessList_delete(Object* cast) { + LinuxProcessList* this = (LinuxProcessList*) cast; + ProcessList_done(&this->super); if (this->ttyDrivers) { for (int i = 0; this->ttyDrivers[i].path; i++) { free(this->ttyDrivers[i].path); @@ -257,14 +259,14 @@ static bool LinuxProcessList_readStatFile(LinuxProcess* lp, openat_arg_t procFd, char buf[MAX_READ + 1]; char path[22] = "stat"; if (scanMainThread) { - xSnprintf(path, sizeof(path), "task/%"PRIi32"/stat", (int32_t)process->pid); + xSnprintf(path, sizeof(path), "task/%"PRIi32"/stat", (int32_t)Process_getPid(process)); } ssize_t r = xReadfileat(procFd, path, buf, sizeof(buf)); if (r < 0) return false; /* (1) pid - %d */ - assert(process->pid == atoi(buf)); + assert(Process_getPid(process) == atoi(buf)); char* location = strchr(buf, ' '); if (!location) return false; @@ -284,7 +286,7 @@ static bool LinuxProcessList_readStatFile(LinuxProcess* lp, openat_arg_t procFd, location += 2; /* (4) ppid - %d */ - process->ppid = strtol(location, &location, 10); + Process_setParent(process, strtol(location, &location, 10)); location += 1; /* (5) pgrp - %d */ @@ -482,11 +484,11 @@ static bool LinuxProcessList_updateUser(const Machine* host, Process* process, o static void LinuxProcessList_readIoFile(LinuxProcess* lp, openat_arg_t procFd, bool scanMainThread) { Process* process = &lp->super; - const Machine* host = process->host; + const Machine* host = process->super.host; char path[20] = "io"; char buffer[1024]; if (scanMainThread) { - xSnprintf(path, sizeof(path), "task/%"PRIi32"/io", (int32_t)process->pid); + xSnprintf(path, sizeof(path), "task/%"PRIi32"/io", (int32_t)Process_getPid(process)); } ssize_t r = xReadfileat(procFd, path, buffer, sizeof(buffer)); if (r < 0) { @@ -743,7 +745,7 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t if (access(PROCDIR "/vz", R_OK) != 0) { free(process->ctid); process->ctid = NULL; - process->vpid = process->super.pid; + process->vpid = Process_getPid(&process->super); return; } @@ -751,7 +753,7 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t if (!file) { free(process->ctid); process->ctid = NULL; - process->vpid = process->super.pid; + process->vpid = Process_getPid(&process->super); return; } @@ -823,7 +825,7 @@ static void LinuxProcessList_readOpenVZData(LinuxProcess* process, openat_arg_t } if (!foundVPid) { - process->vpid = process->super.pid; + process->vpid = Process_getPid(&process->super); } } @@ -875,27 +877,27 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t bool changed = !process->cgroup || !String_eq(process->cgroup, output); - Process_updateFieldWidth(CGROUP, strlen(output)); + Row_updateFieldWidth(CGROUP, strlen(output)); free_and_xStrdup(&process->cgroup, output); if (!changed) { if (process->cgroup_short) { - Process_updateFieldWidth(CCGROUP, strlen(process->cgroup_short)); + Row_updateFieldWidth(CCGROUP, strlen(process->cgroup_short)); } else { //CCGROUP is alias to normal CGROUP if shortening fails - Process_updateFieldWidth(CCGROUP, strlen(process->cgroup)); + Row_updateFieldWidth(CCGROUP, strlen(process->cgroup)); } return; } char* cgroup_short = CGroup_filterName(process->cgroup); if (cgroup_short) { - Process_updateFieldWidth(CCGROUP, strlen(cgroup_short)); + Row_updateFieldWidth(CCGROUP, strlen(cgroup_short)); free_and_xStrdup(&process->cgroup_short, cgroup_short); free(cgroup_short); } else { //CCGROUP is alias to normal CGROUP if shortening fails - Process_updateFieldWidth(CCGROUP, strlen(process->cgroup)); + Row_updateFieldWidth(CCGROUP, strlen(process->cgroup)); free(process->cgroup_short); process->cgroup_short = NULL; } @@ -955,7 +957,7 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t *newline = '\0'; } - Process_updateFieldWidth(SECATTR, strlen(buffer)); + Row_updateFieldWidth(SECATTR, strlen(buffer)); if (process->secattr && String_eq(process->secattr, buffer)) { return; @@ -1004,7 +1006,7 @@ static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) { if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) { memcpy(&stats, nla_data(nla_next(nla_data(nlattr), &rem)), sizeof(stats)); - assert(lp->super.pid == (pid_t)stats.ac_pid); + assert(Process_getPid(&lp->super) == (pid_t)stats.ac_pid); // The xxx_delay_total values wrap around on overflow. // (Linux Kernel "Documentation/accounting/taskstats-struct.rst") @@ -1045,7 +1047,7 @@ static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProc nlmsg_free(msg); } - if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, process->super.pid) < 0) { + if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, Process_getPid(&process->super)) < 0) { nlmsg_free(msg); } @@ -1294,13 +1296,15 @@ static char* LinuxProcessList_updateTtyDevice(TtyDriver* ttyDrivers, unsigned lo } static bool isOlderThan(const Process* proc, unsigned int seconds) { - assert(proc->host->realtimeMs > 0); + const Machine* host = proc->super.host; + + assert(host->realtimeMs > 0); /* Starttime might not yet be parsed */ if (proc->starttime_ctime <= 0) return false; - uint64_t realtime = proc->host->realtimeMs / 1000; + uint64_t realtime = host->realtimeMs / 1000; if (realtime < (uint64_t)proc->starttime_ctime) return false; @@ -1366,7 +1370,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } // Skip task directory of main thread - if (parent && pid == parent->pid) + if (parent && pid == Process_getPid(parent)) continue; #ifdef HAVE_OPENAT @@ -1382,8 +1386,8 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ Process* proc = ProcessList_getProcess(pl, pid, &preExisting, LinuxProcess_new); LinuxProcess* lp = (LinuxProcess*) proc; - proc->tgid = parent ? parent->pid : pid; - proc->isUserlandThread = proc->pid != proc->tgid; + Process_setThreadGroup(proc, parent ? Process_getPid(parent) : pid); + proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); LinuxProcessList_recurseProcTree(this, procFd, lhost, "task", proc); @@ -1394,24 +1398,24 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ * But it will short-circuit subsequent scans. */ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) { - proc->updated = true; - proc->show = false; + proc->super.updated = true; + proc->super.show = false; pl->kernelThreads++; pl->totalTasks++; Compat_openatArgClose(procFd); continue; } if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) { - proc->updated = true; - proc->show = false; + proc->super.updated = true; + proc->super.show = false; pl->userlandThreads++; pl->totalTasks++; Compat_openatArgClose(procFd); continue; } if (preExisting && hideRunningInContainer && proc->isRunningInContainer) { - proc->updated = true; - proc->show = false; + proc->super.updated = true; + proc->super.show = false; Compat_openatArgClose(procFd); continue; } @@ -1479,7 +1483,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) { - LinuxProcess_updateIOPriority(lp); + LinuxProcess_updateIOPriority(proc); } proc->percent_cpu = NAN; @@ -1564,11 +1568,11 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ * Final section after all data has been gathered */ - proc->updated = true; + proc->super.updated = true; Compat_openatArgClose(procFd); if (hideRunningInContainer && proc->isRunningInContainer) { - proc->show = false; + proc->super.show = false; continue; } @@ -1579,7 +1583,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } /* Set at the end when we know if a new entry is a thread */ - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); pl->totalTasks++; /* runningTasks is set in Machine_scanCPUTime() from /proc/stat */ @@ -1612,7 +1616,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ void ProcessList_goThroughEntries(ProcessList* super) { LinuxProcessList* this = (LinuxProcessList*) super; - const Machine* host = super->host; + const Machine* host = super->super.host; const Settings* settings = host->settings; const LinuxMachine* lhost = (const LinuxMachine*) host; diff --git a/linux/Platform.c b/linux/Platform.c index c81b36971..f7fa0f7a5 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -164,7 +164,7 @@ static Htop_Reaction Platform_actionSetIOPriority(State* st) { const void* set = Action_pickFromVector(st, ioprioPanel, 20, true); if (set) { IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel); - bool ok = MainPanel_foreachProcess(st->mainPanel, LinuxProcess_setIOPriority, (Arg) { .i = ioprio2 }, NULL); + bool ok = MainPanel_foreachRow(st->mainPanel, LinuxProcess_rowSetIOPriority, (Arg) { .i = ioprio2 }, NULL); if (!ok) { beep(); } @@ -179,7 +179,7 @@ static bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) { return false; } bool anyTagged; - bool ok = MainPanel_foreachProcess(panel, LinuxProcess_changeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged); + bool ok = MainPanel_foreachRow(panel, LinuxProcess_rowChangeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged); if (!ok) beep(); return anyTagged; diff --git a/netbsd/NetBSDProcess.c b/netbsd/NetBSDProcess.c index bdb0f50c0..605b56c80 100644 --- a/netbsd/NetBSDProcess.c +++ b/netbsd/NetBSDProcess.c @@ -225,7 +225,8 @@ void Process_delete(Object* cast) { free(this); } -static void NetBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) { +static void NetBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const Process* this = (const Process*) super; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; @@ -254,11 +255,18 @@ static int NetBSDProcess_compareByKey(const Process* v1, const Process* v2, Proc const ProcessClass NetBSDProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = NetBSDProcess_rowWriteField }, - .writeField = NetBSDProcess_writeField, .compareByKey = NetBSDProcess_compareByKey }; diff --git a/netbsd/NetBSDProcessList.c b/netbsd/NetBSDProcessList.c index 9e5a9be4d..1327de706 100644 --- a/netbsd/NetBSDProcessList.c +++ b/netbsd/NetBSDProcessList.c @@ -37,17 +37,18 @@ in the source distribution for its full text. ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { NetBSDProcessList* this = xCalloc(1, sizeof(NetBSDProcessList)); - ProcessList* super = (ProcessList*) this; + Object_setClass(this, Class(ProcessList)); + ProcessList* super = (ProcessList*) this; ProcessList_init(super, Class(NetBSDProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* this) { - NetBSDProcessList* npl = (NetBSDProcessList*) this; - ProcessList_done(this); - free(npl); +void ProcessList_delete(Object* cast) { + NetBSDProcessList* this = (NetBSDProcessList*) cast; + ProcessList_done(&this->super); + free(this); } static void NetBSDProcessList_updateExe(const struct kinfo_proc2* kproc, Process* proc) { @@ -163,17 +164,17 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { bool preExisting = false; Process* proc = ProcessList_getProcess(&this->super, kproc->p_pid, &preExisting, NetBSDProcess_new); - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); if (!preExisting) { - proc->pid = kproc->p_pid; - proc->ppid = kproc->p_ppid; + Process_setPid(proc, kproc->p_pid); + Process_setParent(proc, kproc->p_ppid); + Process_setThreadGroup(proc, kproc->p_pid); proc->tpgid = kproc->p_tpgid; - proc->tgid = kproc->p_pid; proc->session = kproc->p_sid; proc->pgrp = kproc->p__pgid; proc->isKernelThread = !!(kproc->p_flag & P_SYSTEM); - proc->isUserlandThread = proc->pid != proc->tgid; + proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); // eh? proc->starttime_ctime = kproc->p_ustart_sec; Process_fillStarttimeBuffer(proc); ProcessList_add(&this->super, proc); @@ -261,7 +262,7 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { if (proc->state == RUNNING) { this->super.runningTasks++; } - proc->updated = true; + proc->super.updated = true; } } diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c index 0875dc00f..c54a1763b 100644 --- a/openbsd/OpenBSDProcess.c +++ b/openbsd/OpenBSDProcess.c @@ -217,8 +217,9 @@ void Process_delete(Object* cast) { free(this); } -static void OpenBSDProcess_writeField(const Process* this, RichString* str, ProcessField field) { - //const OpenBSDProcess* op = (const OpenBSDProcess*) this; +static void OpenBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + //const OpenBSDProcess* op = (const OpenBSDProcess*) super; + const Process* this = (const Process*) super; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; //int n = sizeof(buffer) - 1; @@ -247,11 +248,18 @@ static int OpenBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro const ProcessClass OpenBSDProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = OpenBSDProcess_rowWriteField }, - .writeField = OpenBSDProcess_writeField, .compareByKey = OpenBSDProcess_compareByKey }; diff --git a/openbsd/OpenBSDProcessList.c b/openbsd/OpenBSDProcessList.c index 84c833c3c..170663972 100644 --- a/openbsd/OpenBSDProcessList.c +++ b/openbsd/OpenBSDProcessList.c @@ -34,17 +34,17 @@ in the source distribution for its full text. ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { OpenBSDProcessList* this = xCalloc(1, sizeof(OpenBSDProcessList)); - ProcessList* super = (ProcessList*) this; + Object_setClass(this, Class(ProcessList)); + ProcessList* super = &this->super; ProcessList_init(super, Class(OpenBSDProcess), host, pidMatchList); return this; } -void ProcessList_delete(ProcessList* super) { +void ProcessList_delete(Object* cast) { OpenBSDProcessList* this = (OpenBSDProcessList*) super; - - ProcessList_done(super); + ProcessList_done(&this->super); free(this); } @@ -156,9 +156,9 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { OpenBSDProcess* op = (OpenBSDProcess*) proc; if (!preExisting) { - proc->ppid = kproc->p_ppid; + Process_setParent(proc, kproc->p_ppid); + Process_setThreadGroup(proc, kproc->p_pid); proc->tpgid = kproc->p_tpgid; - proc->tgid = kproc->p_pid; proc->session = kproc->p_sid; proc->pgrp = kproc->p__pgid; proc->isKernelThread = proc->pgrp == 0; @@ -231,8 +231,8 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { this->super.runningTasks++; } - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); - proc->updated = true; + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.updated = true; } } diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c index 2481d81d1..88b4dbd55 100644 --- a/pcp/PCPDynamicColumn.c +++ b/pcp/PCPDynamicColumn.c @@ -250,7 +250,7 @@ void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, Ri unsigned int type = PCPMetric_type(this->id); pmAtomValue atom; - if (!PCPMetric_instance(this->id, proc->pid, pp->offset, &atom, type)) { + if (!PCPMetric_instance(this->id, Process_getPid(proc), pp->offset, &atom, type)) { RichString_appendAscii(str, CRT_colors[METER_VALUE_ERROR], "no data"); return; } @@ -269,7 +269,7 @@ void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, Ri switch (type) { case PM_TYPE_STRING: attr = CRT_colors[PROCESS_SHADOW]; - Process_printLeftAlignedField(str, attr, atom.cp, abswidth); + Row_printLeftAlignedField(str, attr, atom.cp, abswidth); free(atom.cp); break; case PM_TYPE_32: @@ -304,7 +304,9 @@ void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, Ri } int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) { - const PCPDynamicColumn* column = Hashtable_get(p1->super.host->settings->dynamicColumns, key); + const Process* proc = &p1->super; + const Settings* settings = proc->super.host->settings; + const PCPDynamicColumn* column = Hashtable_get(settings->dynamicColumns, key); if (!column) return -1; @@ -313,8 +315,8 @@ int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, Pr unsigned int type = PCPMetric_type(metric); pmAtomValue atom1 = {0}, atom2 = {0}; - if (!PCPMetric_instance(metric, p1->super.pid, p1->offset, &atom1, type) || - !PCPMetric_instance(metric, p2->super.pid, p2->offset, &atom2, type)) { + if (!PCPMetric_instance(metric, Process_getPid(&p1->super), p1->offset, &atom1, type) || + !PCPMetric_instance(metric, Process_getPid(&p2->super), p2->offset, &atom2, type)) { if (type == PM_TYPE_STRING) { free(atom1.cp); free(atom2.cp); diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c index eadc9eb4a..b54834765 100644 --- a/pcp/PCPProcess.c +++ b/pcp/PCPProcess.c @@ -124,37 +124,37 @@ static double PCPProcess_totalIORate(const PCPProcess* pp) { return totalRate; } -static void PCPProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const PCPProcess* pp = (const PCPProcess*) this; - bool coloring = this->host->settings->highlightMegabytes; +static void PCPProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const PCPProcess* pp = (const PCPProcess*) super; + bool coloring = super->host->settings->highlightMegabytes; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; switch ((int)field) { - case CMINFLT: Process_printCount(str, pp->cminflt, coloring); return; - case CMAJFLT: Process_printCount(str, pp->cmajflt, coloring); return; - case M_DRS: Process_printBytes(str, pp->m_drs, coloring); return; - case M_DT: Process_printBytes(str, pp->m_dt, coloring); return; - case M_LRS: Process_printBytes(str, pp->m_lrs, coloring); return; - case M_TRS: Process_printBytes(str, pp->m_trs, coloring); return; - case M_SHARE: Process_printBytes(str, pp->m_share, coloring); return; - case M_PSS: Process_printKBytes(str, pp->m_pss, coloring); return; - case M_SWAP: Process_printKBytes(str, pp->m_swap, coloring); return; - case M_PSSWP: Process_printKBytes(str, pp->m_psswp, coloring); return; - case UTIME: Process_printTime(str, pp->utime, coloring); return; - case STIME: Process_printTime(str, pp->stime, coloring); return; - case CUTIME: Process_printTime(str, pp->cutime, coloring); return; - case CSTIME: Process_printTime(str, pp->cstime, coloring); return; - case RCHAR: Process_printBytes(str, pp->io_rchar, coloring); return; - case WCHAR: Process_printBytes(str, pp->io_wchar, coloring); return; - case SYSCR: Process_printCount(str, pp->io_syscr, coloring); return; - case SYSCW: Process_printCount(str, pp->io_syscw, coloring); return; - case RBYTES: Process_printBytes(str, pp->io_read_bytes, coloring); return; - case WBYTES: Process_printBytes(str, pp->io_write_bytes, coloring); return; - case CNCLWB: Process_printBytes(str, pp->io_cancelled_write_bytes, coloring); return; - case IO_READ_RATE: Process_printRate(str, pp->io_rate_read_bps, coloring); return; - case IO_WRITE_RATE: Process_printRate(str, pp->io_rate_write_bps, coloring); return; - case IO_RATE: Process_printRate(str, PCPProcess_totalIORate(pp), coloring); return; + case CMINFLT: Row_printCount(str, pp->cminflt, coloring); return; + case CMAJFLT: Row_printCount(str, pp->cmajflt, coloring); return; + case M_DRS: Row_printBytes(str, pp->m_drs, coloring); return; + case M_DT: Row_printBytes(str, pp->m_dt, coloring); return; + case M_LRS: Row_printBytes(str, pp->m_lrs, coloring); return; + case M_TRS: Row_printBytes(str, pp->m_trs, coloring); return; + case M_SHARE: Row_printBytes(str, pp->m_share, coloring); return; + case M_PSS: Row_printKBytes(str, pp->m_pss, coloring); return; + case M_SWAP: Row_printKBytes(str, pp->m_swap, coloring); return; + case M_PSSWP: Row_printKBytes(str, pp->m_psswp, coloring); return; + case UTIME: Row_printTime(str, pp->utime, coloring); return; + case STIME: Row_printTime(str, pp->stime, coloring); return; + case CUTIME: Row_printTime(str, pp->cutime, coloring); return; + case CSTIME: Row_printTime(str, pp->cstime, coloring); return; + case RCHAR: Row_printBytes(str, pp->io_rchar, coloring); return; + case WCHAR: Row_printBytes(str, pp->io_wchar, coloring); return; + case SYSCR: Row_printCount(str, pp->io_syscr, coloring); return; + case SYSCW: Row_printCount(str, pp->io_syscw, coloring); return; + case RBYTES: Row_printBytes(str, pp->io_read_bytes, coloring); return; + case WBYTES: Row_printBytes(str, pp->io_write_bytes, coloring); return; + case CNCLWB: Row_printBytes(str, pp->io_cancelled_write_bytes, coloring); return; + case IO_READ_RATE: Row_printRate(str, pp->io_rate_read_bps, coloring); return; + case IO_WRITE_RATE: Row_printRate(str, pp->io_rate_write_bps, coloring); return; + case IO_RATE: Row_printRate(str, PCPProcess_totalIORate(pp), coloring); return; case CGROUP: xSnprintf(buffer, n, "%-10s ", pp->cgroup ? pp->cgroup : ""); break; case OOM: xSnprintf(buffer, n, "%4u ", pp->oom); break; case PERCENT_CPU_DELAY: @@ -193,7 +193,7 @@ static void PCPProcess_writeField(const Process* this, RichString* str, ProcessF } break; default: - Process_writeField(this, str, field); + Process_writeField(&pp->super, str, field); return; } RichString_appendWide(str, attr, buffer); @@ -275,11 +275,18 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process const ProcessClass PCPProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = PCPProcess_rowWriteField, }, - .writeField = PCPProcess_writeField, - .compareByKey = PCPProcess_compareByKey + .compareByKey = PCPProcess_compareByKey, }; diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c index 1a2d89edf..f320ba61d 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessList.c @@ -33,16 +33,17 @@ in the source distribution for its full text. ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { PCPProcessList* this = xCalloc(1, sizeof(PCPProcessList)); - ProcessList* super = &(this->super); + Object_setClass(this, Class(ProcessList)); + ProcessList* super = &this->super; ProcessList_init(super, Class(PCPProcess), host, pidMatchList); return super; } -void ProcessList_delete(ProcessList* super) { - PCPProcessList* this = (PCPProcessList*) super; - ProcessList_done(super); +void ProcessList_delete(Object* cast) { + PCPProcessList* this = (PCPProcessList*) cast; + ProcessList_done(&this->super); free(this); } @@ -129,8 +130,8 @@ static inline ProcessState PCPProcessList_getProcessState(char state) { } static void PCPProcessList_updateID(Process* process, int pid, int offset) { - process->tgid = Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1); - process->ppid = Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1); + Process_setThreadGroup(process, Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1)); + Process_setParent(process, Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1)); process->state = PCPProcessList_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?')); } @@ -310,7 +311,7 @@ static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, static bool PCPProcessList_updateProcesses(PCPProcessList* this) { ProcessList* pl = (ProcessList*) this; - Machine* host = pl->host; + Machine* host = pl->super.host; PCPMachine* phost = (PCPMachine*) host; const Settings* settings = host->settings; @@ -328,7 +329,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this) { Process* proc = ProcessList_getProcess(pl, pid, &preExisting, PCPProcess_new); PCPProcess* pp = (PCPProcess*) proc; PCPProcessList_updateID(proc, pid, offset); - proc->isUserlandThread = proc->pid != proc->tgid; + proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); pp->offset = offset >= 0 ? offset : 0; /* @@ -338,8 +339,8 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this) { * But it will short-circuit subsequent scans. */ if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) { - proc->updated = true; - proc->show = false; + proc->super.updated = true; + proc->super.show = false; if (proc->state == RUNNING) pl->runningTasks++; pl->kernelThreads++; @@ -347,8 +348,8 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this) { continue; } if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) { - proc->updated = true; - proc->show = false; + proc->super.updated = true; + proc->super.show = false; if (proc->state == RUNNING) pl->runningTasks++; pl->userlandThreads++; @@ -427,13 +428,13 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this) { } /* Set at the end when we know if a new entry is a thread */ - proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); pl->totalTasks++; if (proc->state == RUNNING) pl->runningTasks++; - proc->updated = true; + proc->super.updated = true; } return true; } diff --git a/solaris/SolarisProcess.c b/solaris/SolarisProcess.c index 3b5ea1ae5..2ec3c1d52 100644 --- a/solaris/SolarisProcess.c +++ b/solaris/SolarisProcess.c @@ -73,8 +73,8 @@ void Process_delete(Object* cast) { free(sp); } -static void SolarisProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const SolarisProcess* sp = (const SolarisProcess*) this; +static void SolarisProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const SolarisProcess* sp = (const SolarisProcess*) super; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; int n = sizeof(buffer) - 1; @@ -85,13 +85,13 @@ static void SolarisProcess_writeField(const Process* this, RichString* str, Proc case TASKID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->taskid); break; case POOLID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->poolid); break; case CONTID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->contid); break; - case ZONE: Process_printLeftAlignedField(str, attr, sp->zname ? sp->zname : "global", ZONENAME_MAX/4); return; + case ZONE: Row_printLeftAlignedField(str, attr, sp->zname ? sp->zname : "global", ZONENAME_MAX/4); return; case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realpid); break; case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realppid); break; case TGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->realtgid); break; case LWPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, sp->lwpid); break; default: - Process_writeField(this, str, field); + Process_writeField(&sp->super, str, field); return; } RichString_appendWide(str, attr, buffer); @@ -127,11 +127,18 @@ static int SolarisProcess_compareByKey(const Process* v1, const Process* v2, Pro const ProcessClass SolarisProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = SolarisProcess_rowWriteField }, - .writeField = SolarisProcess_writeField, .compareByKey = SolarisProcess_compareByKey }; diff --git a/solaris/SolarisProcessList.c b/solaris/SolarisProcessList.c index e759b1877..46067a7ba 100644 --- a/solaris/SolarisProcessList.c +++ b/solaris/SolarisProcessList.c @@ -45,18 +45,19 @@ static char* SolarisProcessList_readZoneName(kstat_ctl_t* kd, SolarisProcess* sp } ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - SolarisProcessList* spl = xCalloc(1, sizeof(SolarisProcessList)); - ProcessList* pl = (ProcessList*) spl; + SolarisProcessList* this = xCalloc(1, sizeof(SolarisProcessList)); + Object_setClass(this, Class(ProcessList)); - ProcessList_init(pl, Class(SolarisProcess), host, pidMatchList); + ProcessList* super = &this->super; + ProcessList_init(super, Class(SolarisProcess), host, pidMatchList); - return pl; + return super; } -void ProcessList_delete(ProcessList* pl) { - SolarisProcessList* spl = (SolarisProcessList*) pl; - ProcessList_done(pl); - free(spl); +void ProcessList_delete(Object* cast) { + SolarisProcessList* this = (SolarisProcessList*) cast; + ProcessList_done(&this->super); + free(this); } static void SolarisProcessList_updateExe(pid_t pid, Process* proc) { @@ -183,8 +184,8 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, // End common code pass 1 if (onMasterLWP) { // Are we on the representative LWP? - proc->ppid = (_psinfo->pr_ppid * 1024); - proc->tgid = (_psinfo->pr_ppid * 1024); + Process_setParent(proc, (_psinfo->pr_ppid * 1024)); + Process_setThreadGroup(proc, (_psinfo->pr_ppid * 1024)); sproc->realppid = _psinfo->pr_ppid; sproc->realtgid = _psinfo->pr_ppid; @@ -224,8 +225,8 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, proc->time = _lwpsinfo->pr_time.tv_sec * 100 + _lwpsinfo->pr_time.tv_nsec / 10000000; if (!preExisting) { // Tasks done only for NEW LWPs proc->isUserlandThread = true; - proc->ppid = _psinfo->pr_pid * 1024; - proc->tgid = _psinfo->pr_pid * 1024; + Process_setParent(proc, _psinfo->pr_pid * 1024); + Process_setThreadGroup(proc, _psinfo->pr_pid * 1024); sproc->realppid = _psinfo->pr_pid; sproc->realtgid = _psinfo->pr_pid; proc->starttime_ctime = _lwpsinfo->pr_start.tv_sec; @@ -233,10 +234,10 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, // Top-level process only gets this for the representative LWP if (proc->isKernelThread && !settings->hideKernelThreads) { - proc->show = true; + proc->super.show = true; } if (!proc->isKernelThread && !settings->hideUserlandThreads) { - proc->show = true; + proc->super.show = true; } } // Top-level LWP or subordinate LWP @@ -253,7 +254,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, ProcessList_add(pl, proc); } - proc->updated = true; + proc->super.updated = true; // End common code pass 2 diff --git a/unsupported/UnsupportedProcess.c b/unsupported/UnsupportedProcess.c index 4d8cb0803..6df2e36a4 100644 --- a/unsupported/UnsupportedProcess.c +++ b/unsupported/UnsupportedProcess.c @@ -58,21 +58,20 @@ void Process_delete(Object* cast) { free(cast); } -static void UnsupportedProcess_writeField(const Process* this, RichString* str, ProcessField field) { - const UnsupportedProcess* up = (const UnsupportedProcess*) this; - bool coloring = this->host->settings->highlightMegabytes; +static void UnsupportedProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const UnsupportedProcess* up = (const UnsupportedProcess*) super; + bool coloring = super->host->settings->highlightMegabytes; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; - (void) up; (void) coloring; (void) n; switch (field) { /* Add platform specific fields */ default: - Process_writeField(this, str, field); + Process_writeField(&up->super, str, field); return; } RichString_appendWide(str, attr, buffer); @@ -94,11 +93,18 @@ static int UnsupportedProcess_compareByKey(const Process* v1, const Process* v2, const ProcessClass UnsupportedProcess_class = { .super = { - .extends = Class(Process), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = UnsupportedProcess_rowWriteField }, - .writeField = UnsupportedProcess_writeField, .compareByKey = UnsupportedProcess_compareByKey }; diff --git a/unsupported/UnsupportedProcessList.c b/unsupported/UnsupportedProcessList.c index e56f49782..1e29c17a1 100644 --- a/unsupported/UnsupportedProcessList.c +++ b/unsupported/UnsupportedProcessList.c @@ -15,15 +15,18 @@ in the source distribution for its full text. ProcessList* ProcessList_new(Machine* host, Hashtable* pidMatchList) { - ProcessList* this = xCalloc(1, sizeof(ProcessList)); + UnsupportedProcessList* this = xCalloc(1, sizeof(UnsupportedProcessList)); + Object_setClass(this, Class(ProcessList)); - ProcessList_init(this, Class(Process), host, pidMatchList); + ProcessList* super = &this->super; + ProcessList_init(super, Class(Process), host, pidMatchList); return this; } -void ProcessList_delete(ProcessList* this) { - ProcessList_done(this); +void ProcessList_delete(Object* cast) { + UnsupportedProcessList* this = (UnsupportedProcessList*) cast; + ProcessList_done(&this->super); free(this); } @@ -35,9 +38,9 @@ void ProcessList_goThroughEntries(ProcessList* super) { /* Empty values */ proc->time = proc->time + 10; - proc->pid = 1; - proc->ppid = 1; - proc->tgid = 0; + Process_setPid(proc, 1); + Process_setParent(proc, 1); + Process_setThreadGroup(proc, 0); Process_updateComm(proc, "commof16char"); Process_updateCmdline(proc, "", 0, 0); @@ -48,12 +51,12 @@ void ProcessList_goThroughEntries(ProcessList* super) { free_and_xStrdup(&proc->procCwd, "/current/working/directory"); } - proc->updated = true; + proc->super.updated = true; proc->state = RUNNING; proc->isKernelThread = false; proc->isUserlandThread = false; - proc->show = true; /* Reflected in settings-> "hideXXX" really */ + proc->super.show = true; /* Reflected in settings-> "hideXXX" really */ proc->pgrp = 0; proc->session = 0; proc->tty_nr = 0; From daa63fa94a75bcb484625bbf9d54406d9ebb1267 Mon Sep 17 00:00:00 2001 From: Sohaib Mohamed Date: Tue, 22 Aug 2023 16:46:59 +1000 Subject: [PATCH 2/4] Support dynamic screens with 'top-most' entities beyond processes This implements our concept of 'dynamic screens' in htop, with a first use-case of pcp-htop displaying things like top-filesystem and top-cgroups under new screen tabs. However the idea is more general than use in pcp-htop and we've paved the way here for us to collectively build mroe general tabular screens in core htop, as well. From the pcp-htop side of things, dynamic screens are configured using text-based configuration files that define the mapping for PCP metrics to columns (and metric instances to rows). Metrics are defined either directly (via metric names) or indirectly via PCP derived metric specifications. Value scaling and the units displayed is automatic based on PCP metric units and data types. This commit represents a collaborative effort of several months, primarily between myself, Nathan and BenBE. Signed-off-by: Sohaib Mohamed Signed-off-by: Nathan Scott --- Action.c | 61 ++++-- Action.h | 2 +- AvailableColumnsPanel.c | 48 +++-- AvailableColumnsPanel.h | 3 + CategoriesPanel.c | 18 +- ColumnsPanel.c | 5 +- CommandLine.c | 13 +- DisplayOptionsPanel.c | 4 +- DynamicColumn.c | 15 +- DynamicColumn.h | 25 ++- DynamicScreen.c | 65 +++++++ DynamicScreen.h | 39 ++++ Machine.c | 36 +++- Machine.h | 7 +- MainPanel.c | 20 +- MainPanel.h | 4 + Makefile.am | 12 ++ Process.c | 4 +- Process.h | 2 +- Row.c | 19 +- Row.h | 6 +- ScreenManager.c | 2 +- ScreenTabsPanel.c | 372 ++++++++++++++++++++++++++++++++++++ ScreenTabsPanel.h | 60 ++++++ ScreensPanel.c | 23 +-- ScreensPanel.h | 6 +- Settings.c | 107 ++++++++--- Settings.h | 17 +- Table.c | 3 +- darwin/Platform.h | 14 +- dragonflybsd/Platform.h | 14 +- freebsd/Platform.h | 14 +- linux/Platform.h | 14 +- netbsd/Platform.h | 14 +- openbsd/Platform.h | 14 +- pcp/InDomTable.c | 97 ++++++++++ pcp/InDomTable.h | 36 ++++ pcp/Instance.c | 160 ++++++++++++++++ pcp/Instance.h | 37 ++++ pcp/PCPDynamicColumn.c | 249 +++++++++++++++++++----- pcp/PCPDynamicColumn.h | 21 ++- pcp/PCPDynamicMeter.h | 6 + pcp/PCPDynamicScreen.c | 405 ++++++++++++++++++++++++++++++++++++++++ pcp/PCPDynamicScreen.h | 56 ++++++ pcp/PCPMachine.c | 2 + pcp/PCPMetric.c | 19 ++ pcp/PCPMetric.h | 4 + pcp/Platform.c | 32 +++- pcp/Platform.h | 16 +- pcp/screens/biosnoop | 41 ++++ pcp/screens/cgroups | 45 +++++ pcp/screens/cgroupsio | 49 +++++ pcp/screens/cgroupsmem | 48 +++++ pcp/screens/devices | 114 +++++++++++ pcp/screens/execsnoop | 37 ++++ pcp/screens/exitsnoop | 48 +++++ pcp/screens/filesystems | 50 +++++ pcp/screens/opensnoop | 27 +++ solaris/Platform.h | 14 +- unsupported/Platform.h | 14 +- 60 files changed, 2532 insertions(+), 177 deletions(-) create mode 100644 DynamicScreen.c create mode 100644 DynamicScreen.h create mode 100644 ScreenTabsPanel.c create mode 100644 ScreenTabsPanel.h create mode 100644 pcp/InDomTable.c create mode 100644 pcp/InDomTable.h create mode 100644 pcp/Instance.c create mode 100644 pcp/Instance.h create mode 100644 pcp/PCPDynamicScreen.c create mode 100644 pcp/PCPDynamicScreen.h create mode 100644 pcp/screens/biosnoop create mode 100644 pcp/screens/cgroups create mode 100644 pcp/screens/cgroupsio create mode 100644 pcp/screens/cgroupsmem create mode 100644 pcp/screens/devices create mode 100644 pcp/screens/execsnoop create mode 100644 pcp/screens/exitsnoop create mode 100644 pcp/screens/filesystems create mode 100644 pcp/screens/opensnoop diff --git a/Action.c b/Action.c index f7fc4a438..e00383c76 100644 --- a/Action.c +++ b/Action.c @@ -165,6 +165,17 @@ Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) { // ---------------------------------------- +static bool Action_writeableProcess(State* st) { + const Settings* settings = st->host->settings; + bool readonly = Settings_isReadonly() || settings->ss->dynamic; + return !readonly; +} + +static bool Action_readableProcess(State* st) { + const Settings* settings = st->host->settings; + return !settings->ss->dynamic; +} + static Htop_Reaction actionSetSortColumn(State* st) { Htop_Reaction reaction = HTOP_OK; Panel* sortPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Sort ", "Cancel ")); @@ -303,7 +314,7 @@ static Htop_Reaction actionIncSearch(State* st) { } static Htop_Reaction actionHigherPriority(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; bool changed = changePriority(st->mainPanel, -1); @@ -311,7 +322,7 @@ static Htop_Reaction actionHigherPriority(State* st) { } static Htop_Reaction actionLowerPriority(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; bool changed = changePriority(st->mainPanel, 1); @@ -345,13 +356,27 @@ static Htop_Reaction actionExpandCollapseOrSortColumn(State* st) { return st->host->settings->ss->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st); } +static inline void setActiveScreen(Settings* settings, State* st, unsigned int ssIdx) { + assert(settings->ssIndex == ssIdx); + Machine* host = st->host; + + settings->ss = settings->screens[ssIdx]; + if (!settings->ss->table) + settings->ss->table = host->processTable; + host->activeTable = settings->ss->table; + + // set correct functionBar - readonly if requested, and/or with non-process screens + bool readonly = Settings_isReadonly() || (host->activeTable != host->processTable); + MainPanel_setFunctionBar(st->mainPanel, readonly); +} + static Htop_Reaction actionNextScreen(State* st) { Settings* settings = st->host->settings; settings->ssIndex++; if (settings->ssIndex == settings->nScreens) { settings->ssIndex = 0; } - settings->ss = settings->screens[settings->ssIndex]; + setActiveScreen(settings, st, settings->ssIndex); return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR; } @@ -362,21 +387,22 @@ static Htop_Reaction actionPrevScreen(State* st) { } else { settings->ssIndex--; } - settings->ss = settings->screens[settings->ssIndex]; + setActiveScreen(settings, st, settings->ssIndex); return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR; } -Htop_Reaction Action_setScreenTab(Settings* settings, int x) { +Htop_Reaction Action_setScreenTab(State* st, int x) { + Settings* settings = st->host->settings; int s = 2; for (unsigned int i = 0; i < settings->nScreens; i++) { if (x < s) { return 0; } - const char* name = settings->screens[i]->name; - int len = strlen(name); + const char* tab = settings->screens[i]->heading; + int len = strlen(tab); if (x <= s + len + 1) { settings->ssIndex = i; - settings->ss = settings->screens[i]; + setActiveScreen(settings, st, i); return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR; } s += len + 3; @@ -389,7 +415,7 @@ static Htop_Reaction actionQuit(ATTR_UNUSED State* st) { } static Htop_Reaction actionSetAffinity(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; Machine* host = st->host; @@ -426,7 +452,7 @@ static Htop_Reaction actionSetAffinity(State* st) { #ifdef SCHEDULER_SUPPORT static Htop_Reaction actionSetSchedPolicy(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_KEEP_FOLLOWING; static int preSelectedPolicy = SCHEDULINGPANEL_INITSELECTEDPOLICY; @@ -470,7 +496,7 @@ static Htop_Reaction actionSetSchedPolicy(State* st) { #endif /* SCHEDULER_SUPPORT */ static Htop_Reaction actionKill(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; static int preSelectedSignal = SIGNALSPANEL_INITSELECTEDSIGNAL; @@ -521,7 +547,7 @@ static Htop_Reaction actionSetup(State* st) { } static Htop_Reaction actionLsof(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); @@ -539,6 +565,9 @@ static Htop_Reaction actionLsof(State* st) { } static Htop_Reaction actionShowLocks(State* st) { + if (!Action_readableProcess(st)) + return HTOP_OK; + const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; @@ -554,7 +583,7 @@ static Htop_Reaction actionShowLocks(State* st) { } static Htop_Reaction actionStrace(State* st) { - if (Settings_isReadonly()) + if (!Action_writeableProcess(st)) return HTOP_OK; const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); @@ -805,6 +834,9 @@ static Htop_Reaction actionTagAllChildren(State* st) { } static Htop_Reaction actionShowEnvScreen(State* st) { + if (!Action_readableProcess(st)) + return HTOP_OK; + Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; @@ -820,6 +852,9 @@ static Htop_Reaction actionShowEnvScreen(State* st) { } static Htop_Reaction actionShowCommandScreen(State* st) { + if (!Action_readableProcess(st)) + return HTOP_OK; + Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel); if (!p) return HTOP_OK; diff --git a/Action.h b/Action.h index 3540e93e3..d285e1b88 100644 --- a/Action.h +++ b/Action.h @@ -57,7 +57,7 @@ bool Action_setUserOnly(const char* userName, uid_t* userId); Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey); -Htop_Reaction Action_setScreenTab(Settings* settings, int x); +Htop_Reaction Action_setScreenTab(State* st, int x); Htop_Reaction Action_follow(State* st); diff --git a/AvailableColumnsPanel.c b/AvailableColumnsPanel.c index f3c408430..a590bac29 100644 --- a/AvailableColumnsPanel.c +++ b/AvailableColumnsPanel.c @@ -18,6 +18,7 @@ in the source distribution for its full text. #include "Hashtable.h" #include "ListItem.h" #include "Object.h" +#include "Platform.h" #include "Process.h" #include "ProvideCurses.h" #include "XUtils.h" @@ -35,7 +36,7 @@ static void AvailableColumnsPanel_delete(Object* object) { static void AvailableColumnsPanel_insert(AvailableColumnsPanel* this, int at, int key) { const char* name; if (key >= ROW_DYNAMIC_FIELDS) - name = DynamicColumn_init(key); + name = DynamicColumn_name(key); else name = Process_fields[key].name; Panel_insert(this->columns, at, (Object*) ListItem_new(name, key)); @@ -81,42 +82,61 @@ const PanelClass AvailableColumnsPanel_class = { static void AvailableColumnsPanel_addDynamicColumn(ht_key_t key, void* value, void* data) { const DynamicColumn* column = (const DynamicColumn*) value; - Panel* super = (Panel*) data; - const char* title = column->caption ? column->caption : column->heading; - if (!title) - title = column->name; // fallback to the only mandatory field + if (column->table) /* DynamicScreen, handled differently */ + return; + AvailableColumnsPanel* this = (AvailableColumnsPanel*) data; + const char* title = column->heading ? column->heading : column->name; + const char* text = column->description ? column->description : column->caption; char description[256]; - xSnprintf(description, sizeof(description), "%s - %s", title, column->description); - Panel_add(super, (Object*) ListItem_new(description, key)); + if (text) + xSnprintf(description, sizeof(description), "%s - %s", title, text); + else + xSnprintf(description, sizeof(description), "%s", title); + Panel_add(&this->super, (Object*) ListItem_new(description, key)); } // Handle DynamicColumns entries in the AvailableColumnsPanel -static void AvailableColumnsPanel_addDynamicColumns(Panel* super, Hashtable* dynamicColumns) { +static void AvailableColumnsPanel_addDynamicColumns(AvailableColumnsPanel* this, Hashtable* dynamicColumns) { assert(dynamicColumns); - Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, super); + Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, this); } // Handle remaining Platform Meter entries in the AvailableColumnsPanel -static void AvailableColumnsPanel_addPlatformColumn(Panel* super) { +static void AvailableColumnsPanel_addPlatformColumns(AvailableColumnsPanel* this) { for (int i = 1; i < LAST_PROCESSFIELD; i++) { if (i != COMM && Process_fields[i].description) { char description[256]; xSnprintf(description, sizeof(description), "%s - %s", Process_fields[i].name, Process_fields[i].description); - Panel_add(super, (Object*) ListItem_new(description, i)); + Panel_add(&this->super, (Object*) ListItem_new(description, i)); } } } +// Handle DynamicColumns entries associated with DynamicScreens +static void AvailableColumnsPanel_addDynamicScreens(AvailableColumnsPanel* this, const char* screen) { + Platform_addDynamicScreenAvailableColumns(&this->super, screen); +} + +void AvailableColumnsPanel_fill(AvailableColumnsPanel* this, const char* dynamicScreen, Hashtable* dynamicColumns) { + Panel* super = (Panel*) this; + Panel_prune(super); + if (dynamicScreen) { + AvailableColumnsPanel_addDynamicScreens(this, dynamicScreen); + } else { + AvailableColumnsPanel_addPlatformColumns(this); + AvailableColumnsPanel_addDynamicColumns(this, dynamicColumns); + } +} + AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns) { AvailableColumnsPanel* this = AllocThis(AvailableColumnsPanel); Panel* super = (Panel*) this; FunctionBar* fuBar = FunctionBar_new(AvailableColumnsFunctions, NULL, NULL); Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); - Panel_setHeader(super, "Available Columns"); - AvailableColumnsPanel_addPlatformColumn(super); - AvailableColumnsPanel_addDynamicColumns(super, dynamicColumns); this->columns = columns; + AvailableColumnsPanel_fill(this, NULL, dynamicColumns); + return this; } diff --git a/AvailableColumnsPanel.h b/AvailableColumnsPanel.h index aca59060d..3d233be09 100644 --- a/AvailableColumnsPanel.h +++ b/AvailableColumnsPanel.h @@ -9,6 +9,7 @@ in the source distribution for its full text. #include "Hashtable.h" #include "Panel.h" +#include "Settings.h" typedef struct AvailableColumnsPanel_ { @@ -20,4 +21,6 @@ extern const PanelClass AvailableColumnsPanel_class; AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns); +void AvailableColumnsPanel_fill(AvailableColumnsPanel* this, const char* dynamicScreen, Hashtable* dynamicColumns); + #endif diff --git a/CategoriesPanel.c b/CategoriesPanel.c index ba7ee5030..31c7da054 100644 --- a/CategoriesPanel.c +++ b/CategoriesPanel.c @@ -25,6 +25,7 @@ in the source distribution for its full text. #include "Object.h" #include "ProvideCurses.h" #include "ScreensPanel.h" +#include "ScreenTabsPanel.h" #include "Vector.h" #include "XUtils.h" @@ -72,11 +73,21 @@ static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) { ScreenManager_add(this->scr, colors, -1); } +#if defined(HTOP_PCP) /* all platforms supporting dynamic screens */ +static void CategoriesPanel_makeScreenTabsPage(CategoriesPanel* this) { + Settings* settings = this->host->settings; + Panel* screenTabs = (Panel*) ScreenTabsPanel_new(settings); + Panel* screenNames = (Panel*) ((ScreenTabsPanel*)screenTabs)->names; + ScreenManager_add(this->scr, screenTabs, 20); + ScreenManager_add(this->scr, screenNames, -1); +} +#endif + static void CategoriesPanel_makeScreensPage(CategoriesPanel* this) { Settings* settings = this->host->settings; Panel* screens = (Panel*) ScreensPanel_new(settings); Panel* columns = (Panel*) ((ScreensPanel*)screens)->columns; - Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, settings->dynamicColumns); + Panel* availableColumns = (Panel*) ((ScreensPanel*)screens)->availableColumns; ScreenManager_add(this->scr, screens, 20); ScreenManager_add(this->scr, columns, 20); ScreenManager_add(this->scr, availableColumns, -1); @@ -94,10 +105,13 @@ typedef struct CategoriesPanelPage_ { CategoriesPanel_makePageFunc ctor; } CategoriesPanelPage; -static const CategoriesPanelPage categoriesPanelPages[] = { +static CategoriesPanelPage categoriesPanelPages[] = { { .name = "Display options", .ctor = CategoriesPanel_makeDisplayOptionsPage }, { .name = "Header layout", .ctor = CategoriesPanel_makeHeaderOptionsPage }, { .name = "Meters", .ctor = CategoriesPanel_makeMetersPage }, +#if defined(HTOP_PCP) /* all platforms supporting dynamic screens */ + { .name = "Screen tabs", .ctor = CategoriesPanel_makeScreenTabsPage }, +#endif { .name = "Screens", .ctor = CategoriesPanel_makeScreensPage }, { .name = "Colors", .ctor = CategoriesPanel_makeColorsPage }, }; diff --git a/ColumnsPanel.c b/ColumnsPanel.c index ecae36f7a..e3445a000 100644 --- a/ColumnsPanel.c +++ b/ColumnsPanel.c @@ -128,9 +128,8 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) if (!column) { name = NULL; } else { - name = column->caption ? column->caption : column->heading; - if (!name) - name = column->name; /* name is a mandatory field */ + /* heading preferred here but name is always available */ + name = column->heading ? column->heading : column->name; } } if (name == NULL) diff --git a/CommandLine.c b/CommandLine.c index 848c7ae74..2cd905161 100644 --- a/CommandLine.c +++ b/CommandLine.c @@ -25,6 +25,7 @@ in the source distribution for its full text. #include "CRT.h" #include "DynamicColumn.h" #include "DynamicMeter.h" +#include "DynamicScreen.h" #include "Hashtable.h" #include "Header.h" #include "IncSet.h" @@ -337,15 +338,12 @@ int CommandLine_run(int argc, char** argv) { UsersTable* ut = UsersTable_new(); Hashtable* dm = DynamicMeters_new(); Hashtable* dc = DynamicColumns_new(); - if (!dc) - dc = Hashtable_new(0, true); + Hashtable* ds = DynamicScreens_new(); Machine* host = Machine_new(ut, flags.userId); ProcessList* pl = ProcessList_new(host, flags.pidMatchList); - Settings* settings = Settings_new(host->activeCPUs, dm, dc); - - host->settings = settings; - Machine_addTable(host, &pl->super, true); + Settings* settings = Settings_new(host->activeCPUs, dm, dc, ds); + Machine_populateTablesFromSettings(host, settings, &pl->super); Header* header = Header_new(host, 2); Header_populateFromSettings(header); @@ -377,7 +375,7 @@ int CommandLine_run(int argc, char** argv) { CRT_init(settings, flags.allowUnicode, flags.iterationsRemaining != -1); MainPanel* panel = MainPanel_new(); - Table_setPanel(&pl->super, (Panel*) panel); + Machine_setTablesPanel(host, (Panel*) panel); MainPanel_updateLabels(panel, settings->ss->treeView, flags.commFilter); @@ -435,6 +433,7 @@ int CommandLine_run(int argc, char** argv) { Settings_delete(settings); DynamicColumns_delete(dc); DynamicMeters_delete(dm); + DynamicScreens_delete(ds); return 0; } diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index 326d33b14..e74409fc2 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -104,12 +104,12 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* #define TABMSG "For current screen tab: \0" char tabheader[sizeof(TABMSG) + SCREEN_NAME_LEN + 1] = TABMSG; - strncat(tabheader, settings->ss->name, SCREEN_NAME_LEN); + strncat(tabheader, settings->ss->heading, SCREEN_NAME_LEN); Panel_add(super, (Object*) TextItem_new(tabheader)); #undef TABMSG Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->ss->treeView))); - Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by ID (htop 2 behavior)", &(settings->ss->treeViewAlwaysByID))); + 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*) TextItem_new("Global options:")); Panel_add(super, (Object*) CheckItem_newByRef("Show tabs for screens", &(settings->screenTabs))); diff --git a/DynamicColumn.c b/DynamicColumn.c index bd038df4e..7c0ed3ad1 100644 --- a/DynamicColumn.c +++ b/DynamicColumn.c @@ -19,7 +19,10 @@ in the source distribution for its full text. Hashtable* DynamicColumns_new(void) { - return Platform_dynamicColumns(); + Hashtable* dynamics = Platform_dynamicColumns(); + if (!dynamics) + dynamics = Hashtable_new(0, true); + return dynamics; } void DynamicColumns_delete(Hashtable* dynamics) { @@ -29,8 +32,14 @@ void DynamicColumns_delete(Hashtable* dynamics) { } } -const char* DynamicColumn_init(unsigned int key) { - return Platform_dynamicColumnInit(key); +const char* DynamicColumn_name(unsigned int key) { + return Platform_dynamicColumnName(key); +} + +void DynamicColumn_done(DynamicColumn* this) { + free(this->heading); + free(this->caption); + free(this->description); } typedef struct { diff --git a/DynamicColumn.h b/DynamicColumn.h index 4760e6ea5..3b0336a9b 100644 --- a/DynamicColumn.h +++ b/DynamicColumn.h @@ -1,29 +1,40 @@ #ifndef HEADER_DynamicColumn #define HEADER_DynamicColumn +/* +htop - DynamicColumn.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include #include "Hashtable.h" #include "Process.h" #include "RichString.h" +#include "Table.h" -#define DYNAMIC_MAX_COLUMN_WIDTH 28 +#define DYNAMIC_MAX_COLUMN_WIDTH 64 #define DYNAMIC_DEFAULT_COLUMN_WIDTH -5 typedef struct DynamicColumn_ { - char name[32]; /* unique, internal-only name */ - char* heading; /* displayed in main screen */ - char* caption; /* displayed in setup menu (short name) */ - char* description; /* displayed in setup menu (detail) */ - int width; /* display width +/- for value alignment */ + char name[32]; /* unique, internal-only name */ + char* heading; /* displayed in main screen */ + char* caption; /* displayed in setup menu (short name) */ + char* description; /* displayed in setup menu (detail) */ + int width; /* display width +/- for value alignment */ + bool enabled; /* false == ignore this column (until enabled) */ + Table* table; /* pointer to DynamicScreen or ProcessList */ } DynamicColumn; Hashtable* DynamicColumns_new(void); void DynamicColumns_delete(Hashtable* dynamics); -const char* DynamicColumn_init(unsigned int key); +const char* DynamicColumn_name(unsigned int key); + +void DynamicColumn_done(DynamicColumn* this); const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key); diff --git a/DynamicScreen.c b/DynamicScreen.c new file mode 100644 index 000000000..35eb4ee69 --- /dev/null +++ b/DynamicScreen.c @@ -0,0 +1,65 @@ +/* +htop - DynamicScreen.c +(C) 2022 Sohaib Mohammed +(C) 2022-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "DynamicScreen.h" + +#include +#include + +#include "Hashtable.h" +#include "Platform.h" +#include "XUtils.h" + + +Hashtable* DynamicScreens_new(void) { + return Platform_dynamicScreens(); +} + +void DynamicScreens_delete(Hashtable* screens) { + if (screens) { + Platform_dynamicScreensDone(screens); + Hashtable_delete(screens); + } +} + +void DynamicScreen_done(DynamicScreen* this) { + free(this->caption); + free(this->fields); + free(this->heading); + free(this->sortKey); + free(this->columnKeys); +} + +typedef struct { + ht_key_t key; + const char* name; + bool found; +} DynamicIterator; + +static void DynamicScreen_compare(ht_key_t key, void* value, void* data) { + const DynamicScreen* screen = (const DynamicScreen*)value; + DynamicIterator* iter = (DynamicIterator*)data; + if (String_eq(iter->name, screen->name)) { + iter->found = true; + iter->key = key; + } +} + +bool DynamicScreen_search(Hashtable* screens, const char* name, ht_key_t* key) { + DynamicIterator iter = { .key = 0, .name = name, .found = false }; + if (screens) + Hashtable_foreach(screens, DynamicScreen_compare, &iter); + if (key) + *key = iter.key; + return iter.found; +} + +const char* DynamicScreen_lookup(Hashtable* screens, ht_key_t key) { + const DynamicScreen* screen = Hashtable_get(screens, key); + return screen ? screen->name : NULL; +} diff --git a/DynamicScreen.h b/DynamicScreen.h new file mode 100644 index 000000000..76b3d428b --- /dev/null +++ b/DynamicScreen.h @@ -0,0 +1,39 @@ +#ifndef HEADER_DynamicScreen +#define HEADER_DynamicScreen +/* +htop - DynamicColumn.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include + +#include "Hashtable.h" +#include "Panel.h" +#include "Settings.h" + + +typedef struct DynamicScreen_ { + char name[32]; /* unique name cannot contain any spaces */ + char* heading; /* user-settable more readable name */ + char* caption; /* explanatory text for screen */ + char* fields; + char* sortKey; + char* columnKeys; + int direction; +} DynamicScreen; + +Hashtable* DynamicScreens_new(void); + +void DynamicScreens_delete(Hashtable* dynamics); + +void DynamicScreen_done(DynamicScreen* this); + +void DynamicScreens_addAvailableColumns(Panel* availableColumns, char* screen); + +const char* DynamicScreen_lookup(Hashtable* screens, unsigned int key); + +bool DynamicScreen_search(Hashtable* screens, const char* name, unsigned int* key); + +#endif diff --git a/Machine.c b/Machine.c index 8846aed76..c7b98c9fa 100644 --- a/Machine.c +++ b/Machine.c @@ -56,15 +56,15 @@ void Machine_done(Machine* this) { hwloc_topology_destroy(this->topology); } #endif - for (size_t i = 0; i < this->tableCount; i++) { - Object_delete(&this->tables[i]->super); - } + Object_delete(this->processTable); + free(this->tables); } -void Machine_addTable(Machine* this, Table* table, bool processes) { - if (processes) - this->processTable = table; - this->activeTable = table; +static void Machine_addTable(Machine* this, Table* table) { + /* check that this table has not been seen previously */ + for (size_t i = 0; i < this->tableCount; i++) + if (this->tables[i] == table) + return; size_t nmemb = this->tableCount + 1; Table** tables = xReallocArray(this->tables, nmemb, sizeof(Table*)); @@ -73,6 +73,28 @@ void Machine_addTable(Machine* this, Table* table, bool processes) { this->tableCount++; } +void Machine_populateTablesFromSettings(Machine* this, Settings* settings, Table* processTable) { + this->settings = settings; + this->processTable = processTable; + + for (size_t i = 0; i < settings->nScreens; i++) { + ScreenSettings* ss = settings->screens[i]; + Table* table = ss->table; + if (!table) + table = ss->table = processTable; + if (i == 0) + this->activeTable = table; + + Machine_addTable(this, table); + } +} + +void Machine_setTablesPanel(Machine* this, Panel* panel) { + for (size_t i = 0; i < this->tableCount; i++) { + Table_setPanel(this->tables[i], panel); + } +} + void Machine_scanTables(Machine* this) { // set scan timestamp static bool firstScanDone = false; diff --git a/Machine.h b/Machine.h index a1c49e246..6c60050db 100644 --- a/Machine.h +++ b/Machine.h @@ -17,6 +17,7 @@ in the source distribution for its full text. #include #include "Hashtable.h" +#include "Panel.h" #include "Settings.h" #include "Table.h" #include "UsersTable.h" @@ -38,8 +39,6 @@ in the source distribution for its full text. typedef unsigned long long int memory_t; #define MEMORY_MAX ULLONG_MAX -struct Settings_; - typedef struct Machine_ { struct Settings_* settings; @@ -90,7 +89,9 @@ void Machine_done(Machine* this); bool Machine_isCPUonline(const Machine* this, unsigned int id); -void Machine_addTable(Machine* this, Table *table, bool processes); +void Machine_populateTablesFromSettings(Machine* this, Settings* settings, Table* processTable); + +void Machine_setTablesPanel(Machine* host, Panel* panel); void Machine_scan(Machine* this); diff --git a/MainPanel.c b/MainPanel.c index 54127fe9c..7ca7b2015 100644 --- a/MainPanel.c +++ b/MainPanel.c @@ -47,7 +47,7 @@ static void MainPanel_idSearch(MainPanel* this, int ch) { } static const char* MainPanel_getValue(Panel* this, int i) { - const Row* row = (const Row*) Panel_get(this, i); + Row* row = (Row*) Panel_get(this, i); return Row_sortKeyString(row); } @@ -78,7 +78,7 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { int x = EVENT_HEADER_CLICK_GET_X(ch); int hx = super->scrollH + x + 1; RowField field = RowField_keyAt(settings, hx); - if (ss->treeView && ss->treeViewAlwaysByID) { + if (ss->treeView && ss->treeViewAlwaysByPID) { ss->treeView = false; ss->direction = 1; reaction |= Action_setSortKey(settings, field); @@ -91,7 +91,7 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) { result = HANDLED; } else if (EVENT_IS_SCREEN_TAB_CLICK(ch)) { int x = EVENT_SCREEN_TAB_GET_X(ch); - reaction |= Action_setScreenTab(settings, x); + reaction |= Action_setScreenTab(this->state, x); result = HANDLED; } else if (ch != ERR && this->inc->active) { bool filterChanged = IncSet_handleKey(this->inc, ch, super, MainPanel_getValue, NULL); @@ -208,9 +208,12 @@ const PanelClass MainPanel_class = { MainPanel* MainPanel_new(void) { MainPanel* this = AllocThis(MainPanel); - Panel_init((Panel*) this, 1, 1, 1, 1, Class(Row), false, FunctionBar_new(Settings_isReadonly() ? MainFunctions_ro : MainFunctions, NULL, NULL)); + this->processBar = FunctionBar_new(MainFunctions, NULL, NULL); + this->readonlyBar = FunctionBar_new(MainFunctions_ro, NULL, NULL); + FunctionBar* activeBar = Settings_isReadonly() ? this->readonlyBar : this->processBar; + Panel_init((Panel*) this, 1, 1, 1, 1, Class(Row), false, activeBar); this->keys = xCalloc(KEY_MAX, sizeof(Htop_Action)); - this->inc = IncSet_new(MainPanel_getFunctionBar(this)); + this->inc = IncSet_new(activeBar); Action_setBindings(this->keys); Platform_setBindings(this->keys); @@ -222,9 +225,16 @@ void MainPanel_setState(MainPanel* this, State* state) { this->state = state; } +void MainPanel_setFunctionBar(MainPanel* this, bool readonly) { + this->super.defaultBar = readonly ? this->readonlyBar : this->processBar; + this->inc->defaultBar = this->super.defaultBar; +} + void MainPanel_delete(Object* object) { Panel* super = (Panel*) object; MainPanel* this = (MainPanel*) object; + MainPanel_setFunctionBar(this, false); + FunctionBar_delete(this->readonlyBar); Panel_done(super); IncSet_delete(this->inc); free(this->keys); diff --git a/MainPanel.h b/MainPanel.h index d062616d2..19229d59f 100644 --- a/MainPanel.h +++ b/MainPanel.h @@ -25,6 +25,8 @@ typedef struct MainPanel_ { State* state; IncSet* inc; Htop_Action* keys; + FunctionBar* processBar; /* function bar with process-specific actions */ + FunctionBar* readonlyBar; /* function bar without process actions (ro) */ unsigned int idSearch; } MainPanel; @@ -45,6 +47,8 @@ MainPanel* MainPanel_new(void); void MainPanel_setState(MainPanel* this, State* state); +void MainPanel_setFunctionBar(MainPanel* this, bool readonly); + void MainPanel_delete(Object* object); #endif diff --git a/Makefile.am b/Makefile.am index a05c7ea9e..b7d3cca39 100644 --- a/Makefile.am +++ b/Makefile.am @@ -49,6 +49,7 @@ myhtopsources = \ DisplayOptionsPanel.c \ DynamicColumn.c \ DynamicMeter.c \ + DynamicScreen.c \ EnvScreen.c \ FileDescriptorMeter.c \ FunctionBar.c \ @@ -79,6 +80,7 @@ myhtopsources = \ Scheduling.c \ ScreenManager.c \ ScreensPanel.c \ + ScreenTabsPanel.c \ Settings.c \ SignalsPanel.c \ SwapMeter.c \ @@ -113,6 +115,7 @@ myhtopheaders = \ DisplayOptionsPanel.h \ DynamicColumn.h \ DynamicMeter.h \ + DynamicScreen.h \ EnvScreen.h \ FileDescriptorMeter.h \ FunctionBar.h \ @@ -148,6 +151,7 @@ myhtopheaders = \ Scheduling.h \ ScreenManager.h \ ScreensPanel.h \ + ScreenTabsPanel.h \ Settings.h \ SignalsPanel.h \ SwapMeter.h \ @@ -408,11 +412,15 @@ endif # -------------------------- pcp_platform_headers = \ + linux/CGroupUtils.h \ linux/PressureStallMeter.h \ linux/ZramMeter.h \ linux/ZramStats.h \ pcp/PCPDynamicColumn.h \ pcp/PCPDynamicMeter.h \ + pcp/PCPDynamicScreen.h \ + pcp/Instance.h \ + pcp/InDomTable.h \ pcp/PCPMachine.h \ pcp/PCPMetric.h \ pcp/PCPProcess.h \ @@ -424,10 +432,14 @@ pcp_platform_headers = \ zfs/ZfsCompressedArcMeter.h pcp_platform_sources = \ + linux/CGroupUtils.c \ linux/PressureStallMeter.c \ linux/ZramMeter.c \ pcp/PCPDynamicColumn.c \ pcp/PCPDynamicMeter.c \ + pcp/PCPDynamicScreen.c \ + pcp/Instance.c \ + pcp/InDomTable.c \ pcp/PCPMachine.c \ pcp/PCPMetric.c \ pcp/PCPProcess.c \ diff --git a/Process.c b/Process.c index 6f74e014e..6c4fb7adc 100644 --- a/Process.c +++ b/Process.c @@ -758,7 +758,7 @@ void Process_writeField(const Process* this, RichString* str, RowField field) { } void Process_done(Process* this) { - assert (this != NULL); + assert(this != NULL); free(this->cmdline); free(this->procComm); free(this->procExe); @@ -784,7 +784,7 @@ static const char* Process_getSortKey(const Process* this) { return Process_getCommand(this); } -const char* Process_rowGetSortKey(const Row* super) { +const char* Process_rowGetSortKey(Row* super) { const Process* this = (const Process*) super; assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class)); return Process_getSortKey(this); diff --git a/Process.h b/Process.h index c6cdb995e..92908155a 100644 --- a/Process.h +++ b/Process.h @@ -290,7 +290,7 @@ extern const ProcessClass Process_class; void Process_init(Process* this, const struct Machine_* host); -const char* Process_rowGetSortKey(const Row* super); +const char* Process_rowGetSortKey(Row* super); bool Process_rowSetPriority(Row* super, int priority); diff --git a/Row.c b/Row.c index 09a320690..67893d39e 100644 --- a/Row.c +++ b/Row.c @@ -40,6 +40,11 @@ void Row_init(Row* this, const Machine* host) { this->updated = false; } +void Row_done(Row* this) { + assert(this != NULL); + (void) this; +} + static inline bool Row_isNew(const Row* this) { const Machine* host = this->host; if (host->monotonicMs < this->seenStampMs) @@ -56,7 +61,7 @@ static inline bool Row_isTomb(const Row* this) { void Row_display(const Object* cast, RichString* out) { const Row* this = (const Row*) cast; const Settings* settings = this->host->settings; - const ProcessField* fields = settings->ss->fields; + const RowField* fields = settings->ss->fields; for (int i = 0; fields[i]; i++) As_Row(this)->writeField(this, out, fields[i]); @@ -127,7 +132,7 @@ static const char* alignedTitleDynamicColumn(const Settings* settings, int key, if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) width = DYNAMIC_DEFAULT_COLUMN_WIDTH; - xSnprintf(titleBuffer, titleBufferSize, "%*s", width, column->heading); + xSnprintf(titleBuffer, titleBufferSize, "%*s ", width, column->heading); return titleBuffer; } @@ -430,7 +435,7 @@ void Row_printLeftAlignedField(RichString* str, int attr, const char* content, u RichString_appendChr(str, attr, ' ', width + 1 - columns); } -void Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) { +int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) { if (isNonnegative(val)) { if (val < 0.05F) *attr = CRT_colors[PROCESS_SHADOW]; @@ -445,11 +450,11 @@ void Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* val = 100.0F; } - xSnprintf(buffer, n, "%*.*f ", width, precision, val); - } else { - *attr = CRT_colors[PROCESS_SHADOW]; - xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); + return xSnprintf(buffer, n, "%*.*f ", width, precision, val); } + + *attr = CRT_colors[PROCESS_SHADOW]; + return xSnprintf(buffer, n, "%*.*s ", width, width, "N/A"); } void Row_toggleTag(Row* this) { diff --git a/Row.h b/Row.h index 5cdb3fe75..c3ae56f03 100644 --- a/Row.h +++ b/Row.h @@ -81,7 +81,7 @@ typedef void (*Row_WriteField)(const Row*, RichString*, RowField); typedef bool (*Row_IsHighlighted)(const Row*); typedef bool (*Row_IsVisible)(const Row*, const struct Table_*); typedef bool (*Row_MatchesFilter)(const Row*, const struct Table_*); -typedef const char* (*Row_SortKeyString)(const Row*); +typedef const char* (*Row_SortKeyString)(Row*); typedef int (*Row_CompareByParent)(const Row*, const Row*); int Row_compare(const void* v1, const void* v2); @@ -120,6 +120,8 @@ extern const RowClass Row_class; void Row_init(Row* this, const struct Machine_* host); +void Row_done(Row* this); + void Row_display(const Object* cast, RichString* out); void Row_toggleTag(Row* this); @@ -155,7 +157,7 @@ void Row_printTime(RichString* str, unsigned long long totalHundredths, bool col /* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */ void Row_printRate(RichString* str, double rate, bool coloring); -void Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr); +int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr); void Row_display(const Object* cast, RichString* out); diff --git a/ScreenManager.c b/ScreenManager.c index e39c4f069..6c1dd12ef 100644 --- a/ScreenManager.c +++ b/ScreenManager.c @@ -193,7 +193,7 @@ static void ScreenManager_drawScreenTabs(ScreenManager* this) { } for (int s = 0; screens[s]; s++) { - bool ok = drawTab(&y, &x, l, screens[s]->name, s == cur); + bool ok = drawTab(&y, &x, l, screens[s]->heading, s == cur); if (!ok) { break; } diff --git a/ScreenTabsPanel.c b/ScreenTabsPanel.c new file mode 100644 index 000000000..f61745e09 --- /dev/null +++ b/ScreenTabsPanel.c @@ -0,0 +1,372 @@ +/* +htop - ScreenTabsPanel.c +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "ScreenTabsPanel.h" + +#include +#include +#include + +#include "CRT.h" +#include "FunctionBar.h" +#include "Hashtable.h" +#include "ProvideCurses.h" +#include "Settings.h" +#include "XUtils.h" + + +static HandlerResult ScreenNamesPanel_eventHandlerNormal(Panel* super, int ch); + +ObjectClass ScreenTabListItem_class = { + .extends = Class(ListItem), + .display = ListItem_display, + .delete = ListItem_delete, + .compare = ListItem_compare +}; + +static void ScreenNamesPanel_fill(ScreenNamesPanel* this, DynamicScreen* ds) { + const Settings* settings = this->settings; + Panel* super = (Panel*) this; + Panel_prune(super); + + for (unsigned int i = 0; i < settings->nScreens; i++) { + const ScreenSettings* ss = settings->screens[i]; + + if (ds == NULL) { + if (ss->dynamic != NULL) + continue; + /* built-in (processes, not dynamic) - e.g. Main or I/O */ + } else { + if (ss->dynamic == NULL) + continue; + if (!String_eq(ds->name, ss->dynamic)) + continue; + /* matching dynamic screen found, add it into the Panel */ + } + Panel_add(super, (Object*) ListItem_new(ss->heading, i)); + } + + this->ds = ds; +} + +static void ScreenTabsPanel_delete(Object* object) { + Panel* super = (Panel*) object; + ScreenTabsPanel* this = (ScreenTabsPanel*) object; + + Panel_done(super); + free(this); +} + +static HandlerResult ScreenTabsPanel_eventHandler(Panel* super, int ch) { + ScreenTabsPanel* const this = (ScreenTabsPanel* const) super; + + HandlerResult result = IGNORED; + + int selected = Panel_getSelectedIndex(super); + switch (ch) { + case EVENT_SET_SELECTED: + result = HANDLED; + break; + case KEY_F(5): + case KEY_CTRL('N'): + /* pass onto the Names panel for creating new screen */ + return ScreenNamesPanel_eventHandlerNormal(&this->names->super, ch); + case KEY_UP: + case KEY_DOWN: + case KEY_NPAGE: + case KEY_PPAGE: + case KEY_HOME: + case KEY_END: { + int previous = selected; + Panel_onKey(super, ch); + selected = Panel_getSelectedIndex(super); + if (previous != selected) + result = HANDLED; + break; + } + default: + { + if (ch < 255 && isalpha(ch)) + result = Panel_selectByTyping(super, ch); + if (result == BREAK_LOOP) + result = IGNORED; + break; + } + } + if (result == HANDLED) { + ScreenTabListItem* focus = (ScreenTabListItem*) Panel_getSelected(super); + ScreenNamesPanel_fill(this->names, focus->ds); + } + return result; +} + +PanelClass ScreenTabsPanel_class = { + .super = { + .extends = Class(Panel), + .delete = ScreenTabsPanel_delete, + }, + .eventHandler = ScreenTabsPanel_eventHandler +}; + +static ScreenTabListItem* ScreenTabListItem_new(const char* value, DynamicScreen* ds) { + ScreenTabListItem* this = AllocThis(ScreenTabListItem); + ListItem_init((ListItem*)this, value, 0); + this->ds = ds; + return this; +} + +static void addDynamicScreen(ATTR_UNUSED ht_key_t key, void* value, void* userdata) { + DynamicScreen* screen = (DynamicScreen*) value; + Panel* super = (Panel*) userdata; + const char* name = screen->heading ? screen->heading : screen->name; + + Panel_add(super, (Object*) ScreenTabListItem_new(name, screen)); +} + +static const char* const ScreenTabsFunctions[] = {" ", " ", " ", " ", "New ", " ", " ", " ", " ", "Done ", NULL}; + +ScreenTabsPanel* ScreenTabsPanel_new(Settings* settings) { + ScreenTabsPanel* this = AllocThis(ScreenTabsPanel); + Panel* super = (Panel*) this; + FunctionBar* fuBar = FunctionBar_new(ScreenTabsFunctions, NULL, NULL); + Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); + + this->settings = settings; + this->names = ScreenNamesPanel_new(settings); + super->cursorOn = false; + this->cursor = 0; + Panel_setHeader(super, "Screen tabs"); + + assert(settings->dynamicScreens != NULL); + Panel_add(super, (Object*) ScreenTabListItem_new("Processes", NULL)); + Hashtable_foreach(settings->dynamicScreens, addDynamicScreen, super); + + return this; +} + +// ------------- + +ObjectClass ScreenNameListItem_class = { + .extends = Class(ListItem), + .display = ListItem_display, + .delete = ListItem_delete, + .compare = ListItem_compare +}; + +ScreenNameListItem* ScreenNameListItem_new(const char* value, ScreenSettings* ss) { + ScreenNameListItem* this = AllocThis(ScreenNameListItem); + ListItem_init((ListItem*)this, value, 0); + this->ss = ss; + return this; +} + +static const char* const ScreenNamesFunctions[] = {" ", " ", " ", " ", "New ", " ", " ", " ", " ", "Done ", NULL}; + +static void ScreenNamesPanel_delete(Object* object) { + Panel* super = (Panel*) object; + ScreenNamesPanel* this = (ScreenNamesPanel*) object; + + /* do not delete screen settings still in use */ + int n = Panel_size(super); + for (int i = 0; i < n; i++) { + ScreenNameListItem* item = (ScreenNameListItem*) Panel_get(super, i); + item->ss = NULL; + } + + /* during renaming the ListItem's value points to our static buffer */ + if (this->renamingItem) + this->renamingItem->value = this->saved; + + Panel_done(super); + free(this); +} + +static void renameScreenSettings(ScreenNamesPanel* this, const ListItem* item) { + const ScreenNameListItem* nameItem = (const ScreenNameListItem*) item; + + ScreenSettings* ss = nameItem->ss; + free_and_xStrdup(&ss->heading, item->value); + + Settings* settings = this->settings; + settings->changed = true; + settings->lastUpdate++; +} + +static HandlerResult ScreenNamesPanel_eventHandlerRenaming(Panel* super, int ch) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + + if (ch >= 32 && ch < 127 && ch != '=') { + if (this->cursor < SCREEN_NAME_LEN - 1) { + this->buffer[this->cursor] = (char)ch; + this->cursor++; + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); + } + } else { + switch (ch) { + case 127: + case KEY_BACKSPACE: + { + if (this->cursor > 0) { + this->cursor--; + this->buffer[this->cursor] = '\0'; + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); + } + break; + } + case '\n': + case '\r': + case KEY_ENTER: + { + ListItem* item = (ListItem*) Panel_getSelected(super); + if (!item) + break; + assert(item == this->renamingItem); + free(this->saved); + item->value = xStrdup(this->buffer); + this->renamingItem = NULL; + super->cursorOn = false; + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + renameScreenSettings(this, item); + break; + } + case 27: // Esc + { + ListItem* item = (ListItem*) Panel_getSelected(super); + if (!item) + break; + assert(item == this->renamingItem); + item->value = this->saved; + this->renamingItem = NULL; + super->cursorOn = false; + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + break; + } + } + } + return HANDLED; +} + +static void startRenaming(Panel* super) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + + ListItem* item = (ListItem*) Panel_getSelected(super); + if (item == NULL) + return; + this->renamingItem = item; + super->cursorOn = true; + char* name = item->value; + this->saved = name; + strncpy(this->buffer, name, SCREEN_NAME_LEN); + this->buffer[SCREEN_NAME_LEN] = '\0'; + this->cursor = strlen(this->buffer); + item->value = this->buffer; + Panel_setSelectionColor(super, PANEL_EDIT); + super->selectedLen = strlen(this->buffer); + Panel_setCursorToSelection(super); +} + +static void addNewScreen(Panel* super, DynamicScreen* ds) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + const char* name = "New"; + ScreenSettings* ss = (ds != NULL) ? Settings_newDynamicScreen(this->settings, name, ds, NULL) : Settings_newScreen(this->settings, &(const ScreenDefaults) { .name = name, .columns = "PID Command", .sortKey = "PID" }); + ScreenNameListItem* item = ScreenNameListItem_new(name, ss); + int idx = Panel_getSelectedIndex(super); + Panel_insert(super, idx + 1, (Object*) item); + Panel_setSelected(super, idx + 1); +} + +static HandlerResult ScreenNamesPanel_eventHandlerNormal(Panel* super, int ch) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + ScreenNameListItem* oldFocus = (ScreenNameListItem*) Panel_getSelected(super); + HandlerResult result = IGNORED; + switch (ch) { + case '\n': + case '\r': + case KEY_ENTER: + case KEY_MOUSE: + case KEY_RECLICK: + { + Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); + result = HANDLED; + break; + } + case EVENT_SET_SELECTED: + result = HANDLED; + break; + case KEY_NPAGE: + case KEY_PPAGE: + case KEY_HOME: + case KEY_END: + { + Panel_onKey(super, ch); + break; + } + case KEY_F(5): + case KEY_CTRL('N'): + { + addNewScreen(super, this->ds); + startRenaming(super); + result = HANDLED; + break; + } + default: + { + if (ch < 255 && isalpha(ch)) + result = Panel_selectByTyping(super, ch); + if (result == BREAK_LOOP) + result = IGNORED; + break; + } + } + ScreenNameListItem* newFocus = (ScreenNameListItem*) Panel_getSelected(super); + if (newFocus && oldFocus != newFocus) + result = HANDLED; + return result; +} + +static HandlerResult ScreenNamesPanel_eventHandler(Panel* super, int ch) { + ScreenNamesPanel* const this = (ScreenNamesPanel*) super; + + if (!this->renamingItem) + return ScreenNamesPanel_eventHandlerNormal(super, ch); + return ScreenNamesPanel_eventHandlerRenaming(super, ch); +} + +PanelClass ScreenNamesPanel_class = { + .super = { + .extends = Class(Panel), + .delete = ScreenNamesPanel_delete + }, + .eventHandler = ScreenNamesPanel_eventHandler +}; + +ScreenNamesPanel* ScreenNamesPanel_new(Settings* settings) { + ScreenNamesPanel* this = AllocThis(ScreenNamesPanel); + Panel* super = (Panel*) this; + FunctionBar* fuBar = FunctionBar_new(ScreenNamesFunctions, NULL, NULL); + Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); + + this->settings = settings; + this->renamingItem = NULL; + memset(this->buffer, 0, sizeof(this->buffer)); + this->ds = NULL; + this->saved = NULL; + this->cursor = 0; + super->cursorOn = false; + Panel_setHeader(super, "Screens"); + + for (unsigned int i = 0; i < settings->nScreens; i++) { + ScreenSettings* ss = settings->screens[i]; + /* initially show only for Processes tabs (selected) */ + if (ss->dynamic) + continue; + Panel_add(super, (Object*) ScreenNameListItem_new(ss->heading, ss)); + } + return this; +} diff --git a/ScreenTabsPanel.h b/ScreenTabsPanel.h new file mode 100644 index 000000000..3b89ffc2d --- /dev/null +++ b/ScreenTabsPanel.h @@ -0,0 +1,60 @@ +#ifndef HEADER_ScreenTabsPanel +#define HEADER_ScreenTabsPanel +/* +htop - ScreenTabsPanel.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "DynamicScreen.h" +#include "ListItem.h" +#include "Panel.h" +#include "ScreensPanel.h" +#include "ScreenManager.h" +#include "Settings.h" + + +typedef struct ScreenNamesPanel_ { + Panel super; + + ScreenManager* scr; + Settings* settings; + char buffer[SCREEN_NAME_LEN + 1]; + DynamicScreen* ds; + char* saved; + int cursor; + ListItem* renamingItem; +} ScreenNamesPanel; + +typedef struct ScreenNameListItem_ { + ListItem super; + ScreenSettings* ss; +} ScreenNameListItem; + +typedef struct ScreenTabsPanel_ { + Panel super; + + ScreenManager* scr; + Settings* settings; + ScreenNamesPanel* names; + int cursor; +} ScreenTabsPanel; + +typedef struct ScreenTabListItem_ { + ListItem super; + DynamicScreen* ds; +} ScreenTabListItem; + + +ScreenTabsPanel* ScreenTabsPanel_new(Settings* settings); + +extern ObjectClass ScreenNameListItem_class; + +ScreenNameListItem* ScreenNameListItem_new(const char* value, ScreenSettings* ss); + +extern PanelClass ScreenNamesPanel_class; + +ScreenNamesPanel* ScreenNamesPanel_new(Settings* settings); + +#endif diff --git a/ScreensPanel.c b/ScreensPanel.c index cb664ac45..d00388098 100644 --- a/ScreensPanel.c +++ b/ScreensPanel.c @@ -1,7 +1,7 @@ /* htop - ScreensPanel.c (C) 2004-2011 Hisham H. Muhammad -(C) 2020-2022 htop dev team +(C) 2020-2023 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include +#include "AvailableColumnsPanel.h" #include "CRT.h" #include "FunctionBar.h" #include "Hashtable.h" @@ -43,6 +44,7 @@ ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) { } static const char* const ScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL}; +static const char* const DynamicFunctions[] = {" ", "Rename", " ", " ", " ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL}; static void ScreensPanel_delete(Object* object) { Panel* super = (Panel*) object; @@ -55,11 +57,6 @@ static void ScreensPanel_delete(Object* object) { item->ss = NULL; } - /* during renaming the ListItem's value points to our static buffer */ - if (this->renamingItem) - this->renamingItem->value = this->saved; - - Panel_done(super); free(this); } @@ -212,6 +209,8 @@ static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) { case KEY_F(5): case KEY_CTRL('N'): { + if (this->settings->dynamicScreens) + break; addNewScreen(super); startRenaming(super); shouldRebuildArray = true; @@ -272,7 +271,9 @@ static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) { } ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super); if (newFocus && oldFocus != newFocus) { - ColumnsPanel_fill(this->columns, newFocus->ss, this->settings->dynamicColumns); + Hashtable* dynamicColumns = this->settings->dynamicColumns; + ColumnsPanel_fill(this->columns, newFocus->ss, dynamicColumns); + AvailableColumnsPanel_fill(this->availableColumns, newFocus->ss->dynamic, dynamicColumns); result = HANDLED; } if (shouldRebuildArray) @@ -304,11 +305,12 @@ ScreensPanel* ScreensPanel_new(Settings* settings) { ScreensPanel* this = AllocThis(ScreensPanel); Panel* super = (Panel*) this; Hashtable* columns = settings->dynamicColumns; - FunctionBar* fuBar = FunctionBar_new(ScreensFunctions, NULL, NULL); + FunctionBar* fuBar = FunctionBar_new(settings->dynamicScreens ? DynamicFunctions : ScreensFunctions, NULL, NULL); Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); this->settings = settings; this->columns = ColumnsPanel_new(settings->screens[0], columns, &(settings->changed)); + this->availableColumns = AvailableColumnsPanel_new((Panel*) this->columns, columns); this->moving = false; this->renamingItem = NULL; super->cursorOn = false; @@ -317,7 +319,7 @@ ScreensPanel* ScreensPanel_new(Settings* settings) { for (unsigned int i = 0; i < settings->nScreens; i++) { ScreenSettings* ss = settings->screens[i]; - char* name = ss->name; + char* name = ss->heading; Panel_add(super, (Object*) ScreenListItem_new(name, ss)); } return this; @@ -332,9 +334,8 @@ void ScreensPanel_update(Panel* super) { for (int i = 0; i < size; i++) { ScreenListItem* item = (ScreenListItem*) Panel_get(super, i); ScreenSettings* ss = item->ss; - free(ss->name); + free_and_xStrdup(&ss->heading, ((ListItem*) item)->value); this->settings->screens[i] = ss; - ss->name = xStrdup(((ListItem*) item)->value); } this->settings->screens[size] = NULL; } diff --git a/ScreensPanel.h b/ScreensPanel.h index 60aaf2a2d..0be0b8244 100644 --- a/ScreensPanel.h +++ b/ScreensPanel.h @@ -10,7 +10,9 @@ in the source distribution for its full text. #include +#include "AvailableColumnsPanel.h" #include "ColumnsPanel.h" +#include "DynamicScreen.h" #include "ListItem.h" #include "Object.h" #include "Panel.h" @@ -27,6 +29,7 @@ typedef struct ScreensPanel_ { ScreenManager* scr; Settings* settings; ColumnsPanel* columns; + AvailableColumnsPanel* availableColumns; char buffer[SCREEN_NAME_LEN + 1]; char* saved; int cursor; @@ -36,6 +39,7 @@ typedef struct ScreensPanel_ { typedef struct ScreenListItem_ { ListItem super; + DynamicScreen* ds; ScreenSettings* ss; } ScreenListItem; @@ -44,8 +48,6 @@ extern ObjectClass ScreenListItem_class; ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss); -extern PanelClass ScreensPanel_class; - ScreensPanel* ScreensPanel_new(Settings* settings); void ScreensPanel_update(Panel* super); diff --git a/Settings.c b/Settings.c index b772f0916..5265ce4d6 100644 --- a/Settings.c +++ b/Settings.c @@ -19,6 +19,7 @@ in the source distribution for its full text. #include "CRT.h" #include "DynamicColumn.h" +#include "DynamicScreen.h" #include "Macros.h" #include "Meter.h" #include "Platform.h" @@ -203,13 +204,25 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) this->hColumns[1].modes[r++] = TEXT_METERMODE; } -static const char* toFieldName(Hashtable* columns, int id) { - if (id < 0) +static const char* toFieldName(Hashtable* columns, int id, bool* enabled) { + if (id < 0) { + if (enabled) + *enabled = false; return NULL; + } if (id >= ROW_DYNAMIC_FIELDS) { const DynamicColumn* column = DynamicColumn_lookup(columns, id); + if (!column) { + if (enabled) + *enabled = false; + return NULL; + } + if (enabled) + *enabled = column->enabled; return column->name; } + if (enabled) + *enabled = true; return Process_fields[id].name; } @@ -217,7 +230,7 @@ static int toFieldIndex(Hashtable* columns, const char* str) { if (isdigit(str[0])) { // This "+1" is for compatibility with the older enum format. int id = atoi(str) + 1; - if (toFieldName(columns, id)) { + if (toFieldName(columns, id, NULL)) { return id; } } else { @@ -237,7 +250,7 @@ static int toFieldIndex(Hashtable* columns, const char* str) { } // Fallback to iterative scan of table of fields by-name. for (int p = 1; p < LAST_PROCESSFIELD; p++) { - const char* pName = toFieldName(columns, p); + const char* pName = toFieldName(columns, p, NULL); if (pName && strcmp(pName, str) == 0) return p; } @@ -269,34 +282,57 @@ static void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, co String_freeArray(ids); } +static ScreenSettings* Settings_initScreenSettings(ScreenSettings* ss, Settings* this, const char *columns) { + ScreenSettings_readFields(ss, this->dynamicColumns, columns); + this->screens[this->nScreens] = ss; + this->nScreens++; + this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1)); + this->screens[this->nScreens] = NULL; + return ss; +} + ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults) { int sortKey = defaults->sortKey ? toFieldIndex(this->dynamicColumns, defaults->sortKey) : PID; + int treeSortKey = defaults->treeSortKey ? toFieldIndex(this->dynamicColumns, defaults->treeSortKey) : PID; int sortDesc = (sortKey >= 0 && sortKey < LAST_PROCESSFIELD) ? Process_fields[sortKey].defaultSortDesc : 1; ScreenSettings* ss = xMalloc(sizeof(ScreenSettings)); *ss = (ScreenSettings) { - .name = xStrdup(defaults->name), + .heading = xStrdup(defaults->name), + .dynamic = NULL, + .table = NULL, .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)), .flags = 0, .direction = sortDesc ? -1 : 1, .treeDirection = 1, .sortKey = sortKey, - .treeSortKey = PID, + .treeSortKey = treeSortKey, .treeView = false, - .treeViewAlwaysByID = false, + .treeViewAlwaysByPID = false, .allBranchesCollapsed = false, }; + return Settings_initScreenSettings(ss, this, defaults->columns); +} - ScreenSettings_readFields(ss, this->dynamicColumns, defaults->columns); - this->screens[this->nScreens] = ss; - this->nScreens++; - this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1)); - this->screens[this->nScreens] = NULL; - return ss; +ScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const DynamicScreen* screen, Table* table) { + int sortKey = toFieldIndex(this->dynamicColumns, screen->columnKeys); + + ScreenSettings* ss = xMalloc(sizeof(ScreenSettings)); + *ss = (ScreenSettings) { + .heading = xStrdup(tab), + .dynamic = xStrdup(screen->name), + .table = table, + .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)), + .direction = screen->direction, + .treeDirection = 1, + .sortKey = sortKey, + }; + return Settings_initScreenSettings(ss, this, screen->columnKeys); } void ScreenSettings_delete(ScreenSettings* this) { - free(this->name); + free(this->heading); + free(this->dynamic); free(this->fields); free(this); } @@ -308,6 +344,7 @@ static ScreenSettings* Settings_defaultScreens(Settings* this) { const ScreenDefaults* defaults = &Platform_defaultScreens[i]; Settings_newScreen(this, defaults); } + Platform_defaultDynamicScreens(this); return this->screens[0]; } @@ -372,7 +409,7 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini } else if (String_eq(option[0], "tree_view_always_by_pid") && this->config_version <= 2) { // old (no screen) naming also supported for backwards compatibility screen = Settings_defaultScreens(this); - screen->treeViewAlwaysByID = atoi(option[1]); + screen->treeViewAlwaysByPID = atoi(option[1]); } else if (String_eq(option[0], "all_branches_collapsed") && this->config_version <= 2) { // old (no screen) naming also supported for backwards compatibility screen = Settings_defaultScreens(this); @@ -501,10 +538,14 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini screen->treeView = atoi(option[1]); } else if (String_eq(option[0], ".tree_view_always_by_pid")) { if (screen) - screen->treeViewAlwaysByID = atoi(option[1]); + screen->treeViewAlwaysByPID = atoi(option[1]); } else if (String_eq(option[0], ".all_branches_collapsed")) { if (screen) screen->allBranchesCollapsed = atoi(option[1]); + } else if (String_eq(option[0], ".dynamic")) { + if (screen) + free_and_xStrdup(&screen->dynamic, option[1]); + Platform_addDynamicScreen(screen); } String_freeArray(option); } @@ -520,11 +561,13 @@ static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns const char* sep = ""; for (unsigned int i = 0; fields[i]; i++) { if (fields[i] < LAST_PROCESSFIELD && byName) { - const char* pName = toFieldName(columns, fields[i]); + const char* pName = toFieldName(columns, fields[i], NULL); fprintf(fd, "%s%s", sep, pName); } else if (fields[i] >= LAST_PROCESSFIELD && byName) { - const char* pName = toFieldName(columns, fields[i]); - fprintf(fd, " Dynamic(%s)", pName); + bool enabled; + const char* pName = toFieldName(columns, fields[i], &enabled); + if (enabled) + fprintf(fd, "%sDynamic(%s)", sep, pName); } else { // This "-1" is for compatibility with the older enum format. fprintf(fd, "%s%d", sep, (int) fields[i] - 1); @@ -634,17 +677,28 @@ int Settings_write(const Settings* this, bool onCrash) { printSettingInteger("tree_sort_key", this->screens[0]->treeSortKey - 1); printSettingInteger("sort_direction", this->screens[0]->direction); printSettingInteger("tree_sort_direction", this->screens[0]->treeDirection); - printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByID); + printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByPID); printSettingInteger("all_branches_collapsed", this->screens[0]->allBranchesCollapsed); for (unsigned int i = 0; i < this->nScreens; i++) { ScreenSettings* ss = this->screens[i]; - fprintf(fd, "screen:%s=", ss->name); + const char* sortKey = toFieldName(this->dynamicColumns, ss->sortKey, NULL); + const char* treeSortKey = toFieldName(this->dynamicColumns, ss->treeSortKey, NULL); + + fprintf(fd, "screen:%s=", ss->heading); writeFields(fd, ss->fields, this->dynamicColumns, true, separator); - printSettingString(".sort_key", toFieldName(this->dynamicColumns, ss->sortKey)); - printSettingString(".tree_sort_key", toFieldName(this->dynamicColumns, ss->treeSortKey)); + if (ss->dynamic) { + printSettingString(".dynamic", ss->dynamic); + if (ss->sortKey && ss->sortKey != PID) + fprintf(fd, "%s=Dynamic(%s)%c", ".sort_key", sortKey, separator); + if (ss->treeSortKey && ss->treeSortKey != PID) + fprintf(fd, "%s=Dynamic(%s)%c", ".tree_sort_key", treeSortKey, separator); + } else { + printSettingString(".sort_key", sortKey); + printSettingString(".tree_sort_key", treeSortKey); + printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID); + } printSettingInteger(".tree_view", ss->treeView); - printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByID); printSettingInteger(".sort_direction", ss->direction); printSettingInteger(".tree_sort_direction", ss->treeDirection); printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed); @@ -667,9 +721,10 @@ int Settings_write(const Settings* this, bool onCrash) { return r; } -Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns) { +Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens) { Settings* this = xCalloc(1, sizeof(Settings)); + this->dynamicScreens = dynamicScreens; this->dynamicColumns = dynamicColumns; this->dynamicMeters = dynamicMeters; this->hLayout = HF_TWO_50_50; @@ -787,7 +842,7 @@ void ScreenSettings_invertSortOrder(ScreenSettings* this) { } void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) { - if (this->treeViewAlwaysByID || !this->treeView) { + if (this->treeViewAlwaysByPID || !this->treeView) { this->sortKey = sortKey; this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1; this->treeView = false; diff --git a/Settings.h b/Settings.h index 3caaab282..0c202f8a4 100644 --- a/Settings.h +++ b/Settings.h @@ -21,10 +21,14 @@ in the source distribution for its full text. #define CONFIG_READER_MIN_VERSION 3 +struct DynamicScreen_; +struct Table_; + typedef struct { const char* name; const char* columns; const char* sortKey; + const char* treeSortKey; } ScreenDefaults; typedef struct { @@ -34,7 +38,9 @@ typedef struct { } MeterColumnSetting; typedef struct ScreenSettings_ { - char* name; + char* heading; /* user-editable screen name (pretty) */ + char* dynamic; /* from DynamicScreen config (fixed) */ + struct Table_* table; RowField* fields; uint32_t flags; int direction; @@ -42,7 +48,7 @@ typedef struct ScreenSettings_ { RowField sortKey; RowField treeSortKey; bool treeView; - bool treeViewAlwaysByID; + bool treeViewAlwaysByPID; bool allBranchesCollapsed; } ScreenSettings; @@ -53,6 +59,7 @@ typedef struct Settings_ { MeterColumnSetting* hColumns; Hashtable* dynamicColumns; /* runtime-discovered columns */ Hashtable* dynamicMeters; /* runtime-discovered meters */ + Hashtable* dynamicScreens; /* runtime-discovered screens */ ScreenSettings** screens; unsigned int nScreens; @@ -106,7 +113,7 @@ typedef struct Settings_ { static inline RowField ScreenSettings_getActiveSortKey(const ScreenSettings* this) { return (this->treeView) - ? (this->treeViewAlwaysByID ? 1 : this->treeSortKey) + ? (this->treeViewAlwaysByPID ? 1 : this->treeSortKey) : this->sortKey; } @@ -118,10 +125,12 @@ void Settings_delete(Settings* this); int Settings_write(const Settings* this, bool onCrash); -Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns); +Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens); ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults); +ScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const struct DynamicScreen_* screen, struct Table_* table); + void ScreenSettings_delete(ScreenSettings* this); void ScreenSettings_invertSortOrder(ScreenSettings* this); diff --git a/Table.c b/Table.c index dcb486777..aecc7e6d8 100644 --- a/Table.c +++ b/Table.c @@ -192,6 +192,7 @@ static void Table_buildTree(Table* this) { void Table_updateDisplayList(Table* this) { const Settings* settings = this->host->settings; + if (settings->ss->treeView) { if (this->needsSort) Table_buildTree(this); @@ -294,7 +295,7 @@ void Table_printHeader(const Settings* settings, RichString* header) { for (int i = 0; fields[i]; i++) { int color; - if (ss->treeView && ss->treeViewAlwaysByID) { + if (ss->treeView && ss->treeViewAlwaysByPID) { color = CRT_colors[PANEL_HEADER_FOCUS]; } else if (key == fields[i]) { color = CRT_colors[PANEL_SELECTION_FOCUS]; diff --git a/darwin/Platform.h b/darwin/Platform.h index 5cd672979..cf00919fb 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -118,7 +118,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -126,4 +126,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h index b37cea2a1..91616d4fe 100644 --- a/dragonflybsd/Platform.h +++ b/dragonflybsd/Platform.h @@ -111,7 +111,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -119,4 +119,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/freebsd/Platform.h b/freebsd/Platform.h index 849f7ddf3..cc8645692 100644 --- a/freebsd/Platform.h +++ b/freebsd/Platform.h @@ -109,7 +109,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -119,4 +119,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/linux/Platform.h b/linux/Platform.h index 1621d5628..a64e54a78 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -132,7 +132,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -140,4 +140,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/netbsd/Platform.h b/netbsd/Platform.h index a75c766cf..ae34198cc 100644 --- a/netbsd/Platform.h +++ b/netbsd/Platform.h @@ -115,7 +115,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -123,4 +123,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/openbsd/Platform.h b/openbsd/Platform.h index f357006cc..790dc4735 100644 --- a/openbsd/Platform.h +++ b/openbsd/Platform.h @@ -109,7 +109,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -117,4 +117,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/pcp/InDomTable.c b/pcp/InDomTable.c new file mode 100644 index 000000000..120599222 --- /dev/null +++ b/pcp/InDomTable.c @@ -0,0 +1,97 @@ +/* +htop - InDomTable.c +(C) 2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "InDomTable.h" + +#include +#include +#include + +#include "CRT.h" +#include "DynamicColumn.h" +#include "Hashtable.h" +#include "Macros.h" +#include "Platform.h" +#include "Table.h" +#include "Vector.h" +#include "XUtils.h" + +#include "pcp/Instance.h" +#include "pcp/PCPDynamicColumn.h" +#include "pcp/PCPMetric.h" + + +InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey) { + InDomTable* this = xCalloc(1, sizeof(InDomTable)); + Object_setClass(this, Class(InDomTable)); + this->metricKey = metricKey; + this->id = indom; + + Table* super = &this->super; + Table_init(super, Class(Row), host); + + return this; +} + +void InDomTable_done(InDomTable* this) { + Table_done(&this->super); +} + +static void InDomTable_delete(Object* cast) { + InDomTable* this = (InDomTable*) cast; + InDomTable_done(this); + free(this); +} + +static Instance* InDomTable_getInstance(InDomTable* this, int id, bool* preExisting) { + const Table* super = &this->super; + Instance* inst = (Instance*) Hashtable_get(super->table, id); + *preExisting = inst != NULL; + if (inst) { + assert(Vector_indexOf(super->rows, inst, Row_idEqualCompare) != -1); + assert(Instance_getId(inst) == id); + } else { + inst = Instance_new(super->host, this); + assert(inst->name == NULL); + Instance_setId(inst, id); + } + return inst; +} + +static void InDomTable_goThroughEntries(InDomTable* this) { + Table* super = &this->super; + + /* for every instance ... */ + int instid = -1, offset = -1; + while (PCPMetric_iterate(this->metricKey, &instid, &offset)) { + bool preExisting; + Instance* inst = InDomTable_getInstance(this, instid, &preExisting); + inst->offset = offset >= 0 ? offset : 0; + + Row* row = &inst->super; + if (!preExisting) + Table_add(super, row); + row->updated = true; + row->show = true; + } +} + +static void InDomTable_iterateEntries(Table* super) { + InDomTable* this = (InDomTable*) super; + InDomTable_goThroughEntries(this); +} + +const TableClass InDomTable_class = { + .super = { + .extends = Class(Table), + .delete = InDomTable_delete, + }, + .prepare = Table_prepareEntries, + .iterate = InDomTable_iterateEntries, + .cleanup = Table_cleanupEntries, +}; diff --git a/pcp/InDomTable.h b/pcp/InDomTable.h new file mode 100644 index 000000000..7d848d572 --- /dev/null +++ b/pcp/InDomTable.h @@ -0,0 +1,36 @@ +#ifndef HEADER_InDomTable +#define HEADER_InDomTable +/* +htop - InDomTable.h +(C) 2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include +#include + +#include "Platform.h" +#include "Table.h" + + +typedef struct InDomTable_ { + Table super; + pmInDom id; /* shared by metrics in the table */ + unsigned int metricKey; /* representative metric using this indom */ +} InDomTable; + +extern const TableClass InDomTable_class; + +InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey); + +void InDomTable_done(InDomTable* this); + +RowField RowField_keyAt(const Settings* settings, int at); + +void InDomTable_scan(Table* super); + +#endif diff --git a/pcp/Instance.c b/pcp/Instance.c new file mode 100644 index 000000000..1d56c108b --- /dev/null +++ b/pcp/Instance.c @@ -0,0 +1,160 @@ +/* +htop - Instance.c +(C) 2022-2023 Sohaib Mohammed +(C) 2022-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/Instance.h" + +#include +#include + +#include "CRT.h" +#include "DynamicColumn.h" +#include "DynamicScreen.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Macros.h" +#include "PCPDynamicColumn.h" +#include "PCPDynamicScreen.h" +#include "PCPMetric.h" +#include "Platform.h" +#include "Row.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/InDomTable.h" +#include "pcp/PCPMetric.h" + + +Instance* Instance_new(const Machine* host, const InDomTable* indom) { + Instance* this = xCalloc(1, sizeof(Instance)); + Object_setClass(this, Class(Instance)); + + Row* super = &this->super; + Row_init(super, host); + + this->indom = indom; + + return this; +} + +void Instance_done(Instance* this) { + if (this->name) + free(this->name); + Row_done(&this->super); +} + +static void Instance_delete(Object* cast) { + Instance* this = (Instance*) cast; + Instance_done(this); + free(this); +} + +static void Instance_writeField(const Row* super, RichString* str, RowField field) { + const Instance* this = (const Instance*) super; + int instid = Instance_getId(this); + + const Settings* settings = super->host->settings; + DynamicColumn* column = Hashtable_get(settings->dynamicColumns, field); + PCPDynamicColumn* cp = (PCPDynamicColumn*) column; + const pmDesc* descp = PCPMetric_desc(cp->id); + + pmAtomValue atom; + pmAtomValue *ap = &atom; + if (!PCPMetric_instance(cp->id, instid, this->offset, ap, descp->type)) + ap = NULL; + + PCPDynamicColumn_writeAtomValue(cp, str, settings, cp->id, instid, descp, ap); + + if (ap && descp->type == PM_TYPE_STRING) + free(ap->cp); +} + +static const char* Instance_externalName(Row* super) { + Instance* this = (Instance*) super; + + if (!this->name) + pmNameInDom(InDom_getId(this), Instance_getId(this), &this->name); + return this->name; +} + +static int Instance_compareByKey(const Row* v1, const Row* v2, int key) { + const Instance* i1 = (const Instance*)v1; + const Instance* i2 = (const Instance*)v2; + + if (key < 0) + return 0; + + Hashtable* dc = Platform_dynamicColumns(); + const PCPDynamicColumn* column = Hashtable_get(dc, key); + if (!column) + return -1; + + size_t metric = column->id; + unsigned int type = PCPMetric_type(metric); + + pmAtomValue atom1 = {0}, atom2 = {0}; + if (!PCPMetric_instance(metric, i1->offset, i1->offset, &atom1, type) || + !PCPMetric_instance(metric, i2->offset, i2->offset, &atom2, type)) { + if (type == PM_TYPE_STRING) { + free(atom1.cp); + free(atom2.cp); + } + return -1; + } + + switch (type) { + case PM_TYPE_STRING: { + int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp); + free(atom2.cp); + free(atom1.cp); + return cmp; + } + case PM_TYPE_32: + return SPACESHIP_NUMBER(atom2.l, atom1.l); + case PM_TYPE_U32: + return SPACESHIP_NUMBER(atom2.ul, atom1.ul); + case PM_TYPE_64: + return SPACESHIP_NUMBER(atom2.ll, atom1.ll); + case PM_TYPE_U64: + return SPACESHIP_NUMBER(atom2.ull, atom1.ull); + case PM_TYPE_FLOAT: + return SPACESHIP_NUMBER(atom2.f, atom1.f); + case PM_TYPE_DOUBLE: + return SPACESHIP_NUMBER(atom2.d, atom1.d); + default: + break; + } + + return 0; +} + +static int Instance_compare(const void* v1, const void* v2) { + const Instance* i1 = (const Instance*)v1; + const Instance* i2 = (const Instance*)v2; + const ScreenSettings* ss = i1->super.host->settings->ss; + RowField key = ScreenSettings_getActiveSortKey(ss); + int result = Instance_compareByKey(v1, v2, key); + + // Implement tie-breaker (needed to make tree mode more stable) + if (!result) + return SPACESHIP_NUMBER(Instance_getId(i1), Instance_getId(i2)); + + return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result; +} + +const RowClass Instance_class = { + .super = { + .extends = Class(Row), + .display = Row_display, + .delete = Instance_delete, + .compare = Instance_compare, + }, + .sortKeyString = Instance_externalName, + .writeField = Instance_writeField, +}; diff --git a/pcp/Instance.h b/pcp/Instance.h new file mode 100644 index 000000000..c7d688fcb --- /dev/null +++ b/pcp/Instance.h @@ -0,0 +1,37 @@ +#ifndef HEADER_Instance +#define HEADER_Instance +/* +htop - Instance.h +(C) 2022-2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Hashtable.h" +#include "Object.h" +#include "Platform.h" +#include "Row.h" + + +typedef struct Instance_ { + Row super; + + char *name; /* external instance name */ + const struct InDomTable_* indom; /* instance domain */ + + /* default result offset to use for searching metrics with instances */ + unsigned int offset; +} Instance; + +#define InDom_getId(i_) ((i_)->indom->id) +#define Instance_getId(i_) ((i_)->super.id) +#define Instance_setId(i_, id_) ((i_)->super.id = (id_)) + +extern const RowClass Instance_class; + +Instance* Instance_new(const Machine* host, const struct InDomTable_* indom); + +void Instance_done(Instance* this); + +#endif diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c index 88b4dbd55..99e0d8f1f 100644 --- a/pcp/PCPDynamicColumn.c +++ b/pcp/PCPDynamicColumn.c @@ -1,8 +1,7 @@ /* htop - PCPDynamicColumn.c -(C) 2021 Sohaib Mohammed -(C) 2021 htop dev team -(C) 2021 Red Hat, Inc. +(C) 2021-2023 Sohaib Mohammed +(C) 2021-2023 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ @@ -28,6 +27,7 @@ in the source distribution for its full text. #include "RichString.h" #include "XUtils.h" +#include "linux/CGroupUtils.h" #include "pcp/PCPProcess.h" #include "pcp/PCPMetric.h" @@ -49,6 +49,11 @@ static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicCol } static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) { + /* pmLookupText */ + if (!column->super.description) { + PCPMetric_lookupText(value, &column->super.description); + } + /* lookup a dynamic metric with this name, else create */ if (PCPDynamicColumn_addMetric(columns, column) == false) return; @@ -108,6 +113,10 @@ static bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) { static PCPDynamicColumn* PCPDynamicColumn_new(PCPDynamicColumns* columns, const char* name) { PCPDynamicColumn* column = xCalloc(1, sizeof(*column)); String_safeStrncpy(column->super.name, name, sizeof(column->super.name)); + column->super.enabled = false; + column->percent = false; + column->instances = false; + column->defaultEnabled = true; size_t id = columns->count + LAST_PROCESSFIELD; Hashtable_put(columns->table, id, column); @@ -160,6 +169,14 @@ static void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* p free_and_xStrdup(&column->super.description, value); } else if (value && column && String_eq(key, "width")) { column->super.width = strtoul(value, NULL, 10); + } else if (value && column && String_eq(key, "format")) { + free_and_xStrdup(&column->format, value); + } else if (value && column && String_eq(key, "instances")) { + if (String_eq(value, "True") || String_eq(value, "true")) + column->instances = true; + } else if (value && column && (String_eq(key, "default") || String_eq(key, "enabled"))) { + if (String_eq(value, "False") || String_eq(value, "false")) + column->defaultEnabled = false; } else if (value && column && String_eq(key, "metric")) { PCPDynamicColumn_parseMetric(columns, column, path, lineno, value); } @@ -233,74 +250,222 @@ void PCPDynamicColumns_init(PCPDynamicColumns* columns) { free(path); } +void PCPDynamicColumn_done(PCPDynamicColumn* this) { + DynamicColumn_done(&this->super); + free(this->metricName); + free(this->format); +} + static void PCPDynamicColumns_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { PCPDynamicColumn* column = (PCPDynamicColumn*) value; - free(column->metricName); - free(column->super.heading); - free(column->super.caption); - free(column->super.description); + PCPDynamicColumn_done(column); } void PCPDynamicColumns_done(Hashtable* table) { Hashtable_foreach(table, PCPDynamicColumns_free, NULL); } -void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) { - const PCPProcess* pp = (const PCPProcess*) proc; - unsigned int type = PCPMetric_type(this->id); +static void PCPDynamicColumn_setupWidth(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicColumn* column = (PCPDynamicColumn*) value; - pmAtomValue atom; - if (!PCPMetric_instance(this->id, Process_getPid(proc), pp->offset, &atom, type)) { - RichString_appendAscii(str, CRT_colors[METER_VALUE_ERROR], "no data"); + /* calculate column size based on config file and metric units */ + const pmDesc* desc = PCPMetric_desc(column->id); + + if (column->instances || desc->type == PM_TYPE_STRING) { + column->super.width = column->width; + if (column->super.width == 0) + column->super.width = -16; return; } - int width = this->super.width; - if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) - width = DYNAMIC_DEFAULT_COLUMN_WIDTH; - int abswidth = abs(width); - if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) { - abswidth = DYNAMIC_MAX_COLUMN_WIDTH; - width = -abswidth; + if (column->format) { + if (strcmp(column->format, "percent") == 0) { + column->super.width = 5; + return; + } + if (strcmp(column->format, "process") == 0) { + column->super.width = Process_pidDigits; + return; + } } - char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1]; - int attr = CRT_colors[DEFAULT_COLOR]; + if (column->width) { + column->super.width = column->width; + return; + } + + pmUnits units = desc->units; + if (units.dimSpace && units.dimTime) + column->super.width = 11; // Row_printRate + else if (units.dimSpace) + column->super.width = 5; // Row_printBytes + else if (units.dimCount && units.dimTime) + column->super.width = 11; // Row_printCount + else if (units.dimTime) + column->super.width = 8; // Row_printTime + else + column->super.width = 11; // Row_printCount +} + +void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns) { + Hashtable_foreach(columns->table, PCPDynamicColumn_setupWidth, NULL); +} + +/* normalize output units to bytes and seconds */ +static int PCPDynamicColumn_normalize(const pmDesc* desc, const pmAtomValue* ap, double* value) { + /* form normalized units based on the original metric units */ + pmUnits units = desc->units; + if (units.dimTime) + units.scaleTime = PM_TIME_SEC; + if (units.dimSpace) + units.scaleSpace = PM_SPACE_BYTE; + if (units.dimCount) + units.scaleCount = PM_COUNT_ONE; + + pmAtomValue atom; + int sts, type = desc->type; + if ((sts = pmConvScale(type, ap, &desc->units, &atom, &units)) < 0) + return sts; + switch (type) { - case PM_TYPE_STRING: - attr = CRT_colors[PROCESS_SHADOW]; - Row_printLeftAlignedField(str, attr, atom.cp, abswidth); - free(atom.cp); - break; case PM_TYPE_32: - xSnprintf(buffer, sizeof(buffer), "%*d ", width, atom.l); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.l; break; case PM_TYPE_U32: - xSnprintf(buffer, sizeof(buffer), "%*u ", width, atom.ul); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.ul; break; case PM_TYPE_64: - xSnprintf(buffer, sizeof(buffer), "%*lld ", width, (long long) atom.ll); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.ll; break; case PM_TYPE_U64: - xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long) atom.ull); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.ull; break; case PM_TYPE_FLOAT: - xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, (double) atom.f); - RichString_appendAscii(str, attr, buffer); + *value = (double) atom.f; break; case PM_TYPE_DOUBLE: - xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, atom.d); - RichString_appendAscii(str, attr, buffer); + *value = atom.d; break; default: - attr = CRT_colors[METER_VALUE_ERROR]; - RichString_appendAscii(str, attr, "no type"); - break; + return PM_ERR_CONV; + } + return 0; +} + +void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const pmDesc* desc, const void* atom) { + const pmAtomValue* atomvalue = (const pmAtomValue*) atom; + char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /*space*/ 1 + /*null*/ 1]; + int attr = CRT_colors[DEFAULT_COLOR]; + int width = column->super.width; + int n; + + if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) + width = DYNAMIC_DEFAULT_COLUMN_WIDTH; + int abswidth = abs(width); + if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) { + abswidth = DYNAMIC_MAX_COLUMN_WIDTH; + width = -abswidth; + } + + if (atomvalue == NULL) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A"); + RichString_appendnAscii(str, CRT_colors[PROCESS_SHADOW], buffer, n); + return; + } + + /* deal with instance names and metrics with string values first */ + if (column->instances || desc->type == PM_TYPE_STRING) { + char* value = NULL; + char* dupd1 = NULL; + if (column->instances) { + attr = CRT_colors[DYNAMIC_GRAY]; + PCPMetric_externalName(metric, instance, &dupd1); + value = dupd1; + } else { + attr = CRT_colors[DYNAMIC_GREEN]; + value = atomvalue->cp; + } + if (column->format && value) { + char* dupd2 = NULL; + if (strcmp(column->format, "command") == 0) + attr = CRT_colors[PROCESS_COMM]; + else if (strcmp(column->format, "process") == 0) + attr = CRT_colors[PROCESS_SHADOW]; + else if (strcmp(column->format, "device") == 0 && strncmp(value, "/dev/", 5) == 0) + value += 5; + else if (strcmp(column->format, "cgroup") == 0 && (dupd2 = CGroup_filterName(value))) + value = dupd2; + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value); + if (dupd2) + free(dupd2); + } else if (value) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value); + } else { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A"); + } + if (dupd1) + free(dupd1); + RichString_appendnAscii(str, attr, buffer, n); + return; + } + + /* deal with any numeric value - first, normalize units to bytes/seconds */ + double value; + if (PCPDynamicColumn_normalize(desc, atomvalue, &value) < 0) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "no conv"); + RichString_appendnAscii(str, CRT_colors[METER_VALUE_ERROR], buffer, n); + return; + } + + if (column->format) { + if (strcmp(column->format, "percent") == 0) { + n = Row_printPercentage(value, buffer, sizeof(buffer), width, &attr); + RichString_appendnAscii(str, attr, buffer, n); + return; + } + if (strcmp(column->format, "process") == 0) { + n = xSnprintf(buffer, sizeof(buffer), "%*d ", Row_pidDigits, (int)value); + RichString_appendnAscii(str, attr, buffer, n); + return; + } } + + /* width overrides unit suffix and coloring; too complex for a corner case */ + if (column->width) { + if (value - (unsigned long long)value > 0) /* display floating point */ + n = xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, value); + else /* display as integer */ + n = xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long)value); + RichString_appendnAscii(str, CRT_colors[PROCESS], buffer, n); + return; + } + + bool coloring = settings->highlightMegabytes; + pmUnits units = desc->units; + if (units.dimSpace && units.dimTime) + Row_printRate(str, value, coloring); + else if (units.dimSpace) + Row_printBytes(str, value, coloring); + else if (units.dimCount) + Row_printCount(str, value, coloring); + else if (units.dimTime) + Row_printTime(str, value / 10 /* hundreds of a second */, coloring); + else + Row_printCount(str, value, 0); /* e.g. PID */ +} + +void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) { + const Settings* settings = proc->super.host->settings; + const PCPProcess* pp = (const PCPProcess*) proc; + const pmDesc* desc = PCPMetric_desc(this->id); + pid_t pid = Process_getPid(proc); + + pmAtomValue atom; + pmAtomValue *ap = &atom; + if (!PCPMetric_instance(this->id, pid, pp->offset, ap, desc->type)) + ap = NULL; + + PCPDynamicColumn_writeAtomValue(this, str, settings, this->id, pid, desc, ap); } int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) { diff --git a/pcp/PCPDynamicColumn.h b/pcp/PCPDynamicColumn.h index d0ffe7192..ade782b7f 100644 --- a/pcp/PCPDynamicColumn.h +++ b/pcp/PCPDynamicColumn.h @@ -1,5 +1,11 @@ #ifndef HEADER_PCPDynamicColumn #define HEADER_PCPDynamicColumn +/* +htop - PCPDynamicColumn.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include @@ -11,15 +17,22 @@ #include "pcp/PCPProcess.h" +struct pmDesc; + typedef struct PCPDynamicColumn_ { DynamicColumn super; char* metricName; + char* format; size_t id; /* identifier for metric array lookups */ + int width; /* optional width from configuration file */ + bool defaultEnabled; /* default enabled in dynamic screen */ + bool percent; + bool instances; /* an instance *names* column, not values */ } PCPDynamicColumn; typedef struct PCPDynamicColumns_ { Hashtable* table; - size_t count; /* count of dynamic meters discovered by scan */ + size_t count; /* count of dynamic columns discovered by scan */ size_t offset; /* start offset into the Platform metric array */ size_t cursor; /* identifier allocator for each new metric used */ } PCPDynamicColumns; @@ -28,8 +41,14 @@ void PCPDynamicColumns_init(PCPDynamicColumns* columns); void PCPDynamicColumns_done(Hashtable* table); +void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns); + void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str); +void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const struct pmDesc* desc, const void* atomvalue); + int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key); +void PCPDynamicColumn_done(PCPDynamicColumn* this); + #endif diff --git a/pcp/PCPDynamicMeter.h b/pcp/PCPDynamicMeter.h index 0e5ddd2b5..3a72d13c3 100644 --- a/pcp/PCPDynamicMeter.h +++ b/pcp/PCPDynamicMeter.h @@ -1,5 +1,11 @@ #ifndef HEADER_PCPDynamicMeter #define HEADER_PCPDynamicMeter +/* +htop - PCPDynamicMeter.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ #include diff --git a/pcp/PCPDynamicScreen.c b/pcp/PCPDynamicScreen.c new file mode 100644 index 000000000..573bc4276 --- /dev/null +++ b/pcp/PCPDynamicScreen.c @@ -0,0 +1,405 @@ +/* +htop - PCPDynamicScreen.c +(C) 2022 Sohaib Mohammed +(C) 2022-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "pcp/PCPDynamicScreen.h" + +#include +#include +#include +#include + +#include "AvailableColumnsPanel.h" +#include "Macros.h" +#include "Platform.h" +#include "Settings.h" +#include "XUtils.h" + +#include "pcp/InDomTable.h" +#include "pcp/PCPDynamicColumn.h" + + +static char* formatFields(PCPDynamicScreen* screen) { + char* columns = strdup(""); + + for (size_t j = 0; j < screen->totalColumns; j++) { + const PCPDynamicColumn* column = screen->columns[j]; + if (column->super.enabled == false) + continue; + char* prefix = columns; + xAsprintf(&columns, "%s Dynamic(%s)", prefix, column->super.name); + free(prefix); + } + + return columns; +} + +static void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { + for (size_t i = 0; i < screens->count; i++) { + PCPDynamicScreen *screen = Hashtable_get(screens->table, i); + if (!screen) + return; + + /* setup default fields (columns) based on configuration */ + for (size_t j = 0; j < screen->totalColumns; j++) { + PCPDynamicColumn* column = screen->columns[j]; + + column->id = columns->offset + columns->cursor; + columns->cursor++; + Platform_addMetric(column->id, column->metricName); + + size_t id = columns->count + LAST_PROCESSFIELD; + Hashtable_put(columns->table, id, column); + columns->count++; + + if (j == 0) { + const pmDesc* desc = PCPMetric_desc(column->id); + assert(desc->indom != PM_INDOM_NULL); + screen->indom = desc->indom; + screen->key = column->id; + } + } + screen->super.columnKeys = formatFields(screen); + } +} + +static PCPDynamicColumn* PCPDynamicScreen_lookupMetric(PCPDynamicScreen* screen, const char* name) { + PCPDynamicColumn* column = NULL; + size_t bytes = strlen(name) + strlen(screen->super.name) + 1; /* colon */ + if (bytes >= sizeof(column->super.name)) + return NULL; + + bytes += 16; /* prefix, dots and terminator */ + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.screen.%s.%s", screen->super.name, name); + + for (size_t i = 0; i < screen->totalColumns; i++) { + column = screen->columns[i]; + if (String_eq(column->metricName, metricName)) { + free(metricName); + return column; + } + } + + /* not an existing column in this screen - create it and add to the list */ + column = xCalloc(1, sizeof(PCPDynamicColumn)); + xSnprintf(column->super.name, sizeof(column->super.name), "%s:%s", screen->super.name, name); + column->super.table = &screen->table->super; + column->metricName = metricName; + column->super.enabled = true; + + size_t n = screen->totalColumns + 1; + screen->columns = xReallocArray(screen->columns, n, sizeof(PCPDynamicColumn*)); + screen->columns[n - 1] = column; + screen->totalColumns = n; + + return column; +} + +static void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* path, unsigned int line, char* key, char* value) { + PCPDynamicColumn* column; + char* p; + + if ((p = strchr(key, '.')) == NULL) + return; + *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */ + + /* lookup a dynamic column with this name, else create */ + column = PCPDynamicScreen_lookupMetric(screen, key); + + if (String_eq(p, "metric")) { + char* error; + if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) { + char* note; + xAsprintf(¬e, + "%s: failed to parse expression in %s at line %u\n%s\n", + pmGetProgname(), path, line, error); + free(error); + errno = EINVAL; + CRT_fatalError(note); + free(note); + } + + /* pmLookupText - add optional metric help text */ + if (!column->super.description && !column->instances) + PCPMetric_lookupText(value, &column->super.description); + + } else { + /* this is a property of a dynamic column - the column expression */ + /* may not have been observed yet; i.e. we allow for any ordering */ + + if (String_eq(p, "caption")) { + free_and_xStrdup(&column->super.caption, value); + } else if (String_eq(p, "heading")) { + free_and_xStrdup(&column->super.heading, value); + } else if (String_eq(p, "description")) { + free_and_xStrdup(&column->super.description, value); + } else if (String_eq(p, "width")) { + column->width = strtoul(value, NULL, 10); + } else if (String_eq(p, "format")) { + free_and_xStrdup(&column->format, value); + } else if (String_eq(p, "instances")) { + if (String_eq(value, "True") || String_eq(value, "true")) + column->instances = true; + free_and_xStrdup(&column->super.description, screen->super.caption); + } else if (String_eq(p, "default")) { /* displayed by default */ + if (String_eq(value, "False") || String_eq(value, "false")) + column->defaultEnabled = column->super.enabled = false; + } + } +} + +static bool PCPDynamicScreen_validateScreenName(char* key, const char* path, unsigned int line) { + char* p = key; + char* end = strrchr(key, ']'); + + if (end) { + *end = '\0'; + } else { + fprintf(stderr, + "%s: no closing brace on screen name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + + while (*p) { + if (p == key) { + if (!isalpha(*p) && *p != '_') + break; + } else { + if (!isalnum(*p) && *p != '_') + break; + } + p++; + } + if (*p != '\0') { /* badness */ + fprintf(stderr, + "%s: invalid screen name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +/* Ensure a screen name has not been defined previously */ +static bool PCPDynamicScreen_uniqueName(char* key, PCPDynamicScreens* screens) { + return !DynamicScreen_search(screens->table, key, NULL); +} + +static PCPDynamicScreen* PCPDynamicScreen_new(PCPDynamicScreens* screens, const char* name) { + PCPDynamicScreen* screen = xCalloc(1, sizeof(*screen)); + String_safeStrncpy(screen->super.name, name, sizeof(screen->super.name)); + screen->defaultEnabled = true; + + size_t id = screens->count; + Hashtable_put(screens->table, id, screen); + screens->count++; + + return screen; +} + +static void PCPDynamicScreen_parseFile(PCPDynamicScreens* screens, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicScreen* screen = NULL; + unsigned int lineno = 0; + bool ok = true; + for (;;) { + char* line = String_readLine(file); + if (!line) + break; + lineno++; + + /* cleanup whitespace, skip comment lines */ + char* trimmed = String_trim(line); + free(line); + if (!trimmed || !trimmed[0] || trimmed[0] == '#') { + free(trimmed); + continue; + } + + size_t n; + char** config = String_split(trimmed, '=', &n); + free(trimmed); + if (config == NULL) + continue; + + char* key = String_trim(config[0]); + char* value = n > 1 ? String_trim(config[1]) : NULL; + if (key[0] == '[') { /* new section name - i.e. new screen */ + ok = PCPDynamicScreen_validateScreenName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicScreen_uniqueName(key + 1, screens); + if (ok) + screen = PCPDynamicScreen_new(screens, key + 1); + if (pmDebugOptions.appl0) + fprintf(stderr, "[%s] screen: %s\n", path, key+1); + } else if (!ok) { + ; /* skip this one, we're looking for a new header */ + } else if (!value || !screen) { + ; /* skip this one as we always need value strings */ + } else if (String_eq(key, "heading")) { + free_and_xStrdup(&screen->super.heading, value); + } else if (String_eq(key, "caption")) { + free_and_xStrdup(&screen->super.caption, value); + } else if (String_eq(key, "sortKey")) { + free_and_xStrdup(&screen->super.sortKey, value); + } else if (String_eq(key, "sortDirection")) { + screen->super.direction = strtoul(value, NULL, 10); + } else if (String_eq(key, "default") || String_eq(key, "enabled")) { + if (String_eq(value, "False") || String_eq(value, "false")) + screen->defaultEnabled = false; + else if (String_eq(value, "True") || String_eq(value, "true")) + screen->defaultEnabled = true; /* also default */ + } else { + PCPDynamicScreen_parseColumn(screen, path, lineno, key, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicScreen_scanDir(PCPDynamicScreens* screens, char* path) { + DIR* dir = opendir(path); + if (!dir) + return; + + struct dirent* dirent; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + + char* file = String_cat(path, dirent->d_name); + PCPDynamicScreen_parseFile(screens, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicScreens_init(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { + const char* share = pmGetConfig("PCP_SHARE_DIR"); + const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR"); + const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + const char* override = getenv("PCP_HTOP_DIR"); + const char* home = getenv("HOME"); + char* path; + + screens->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/screens/"); + else if (home) + path = String_cat(home, "/.config/htop/screens/"); + else + path = NULL; + if (path) { + PCPDynamicScreen_scanDir(screens, path); + free(path); + } + + /* next, search in the system screens directory */ + path = String_cat(sysconf, "/htop/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + + /* next, try the readonly system screens directory */ + path = String_cat(share, "/htop/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + + /* establish internal metric identifier mappings */ + PCPDynamicScreens_appendDynamicColumns(screens, columns); +} + +static void PCPDynamicScreen_done(PCPDynamicScreen* ds) { + DynamicScreen_done(&ds->super); + Object_delete(ds->table); + free(ds->columns); +} + +static void PCPDynamicScreens_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicScreen* ds = (PCPDynamicScreen*) value; + PCPDynamicScreen_done(ds); +} + +void PCPDynamicScreens_done(Hashtable* table) { + Hashtable_foreach(table, PCPDynamicScreens_free, NULL); +} + +void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + ds->table = InDomTable_new(host, ds->indom, ds->key); + } +} + +void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (ds->defaultEnabled == false) + continue; + const char* tab = ds->super.heading; + Settings_newDynamicScreen(settings, tab, &ds->super, &ds->table->super); + } +} + +/* called when htoprc .dynamic line is parsed for a dynamic screen */ +void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (String_eq(ss->dynamic, ds->super.name) == false) + continue; + ss->table = &ds->table->super; + } +} + +void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen) { + Vector_prune(availableColumns->items); + + bool success; + unsigned int key; + success = DynamicScreen_search(screens, screen, &key); + if (!success) + return; + + PCPDynamicScreen* dynamicScreen = Hashtable_get(screens, key); + if (!screen) + return; + + for (unsigned int j = 0; j < dynamicScreen->totalColumns; j++) { + PCPDynamicColumn* column = dynamicScreen->columns[j]; + const char* title = column->super.heading ? column->super.heading : column->super.name; + const char* text = column->super.description ? column->super.description : column->super.caption; + char description[256]; + if (text) + xSnprintf(description, sizeof(description), "%s - %s", title, text); + else + xSnprintf(description, sizeof(description), "%s", title); + Panel_add(availableColumns, (Object*) ListItem_new(description, j)); + } +} diff --git a/pcp/PCPDynamicScreen.h b/pcp/PCPDynamicScreen.h new file mode 100644 index 000000000..73535925a --- /dev/null +++ b/pcp/PCPDynamicScreen.h @@ -0,0 +1,56 @@ +#ifndef HEADER_PCPDynamicScreen +#define HEADER_PCPDynamicScreen +/* +htop - PCPDynamicScreen.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + +#include "CRT.h" +#include "DynamicScreen.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Panel.h" +#include "Settings.h" + + +struct InDomTable_; +struct PCPDynamicColumn_; +struct PCPDynamicColumns_; + +typedef struct PCPDynamicScreen_ { + DynamicScreen super; + + struct InDomTable_ *table; + struct PCPDynamicColumn_** columns; + size_t totalColumns; + + unsigned int indom; /* instance domain number */ + unsigned int key; /* PCPMetric identifier */ + + bool defaultEnabled; /* enabled setting from configuration file */ + /* at runtime enabled screens have entries in settings->screens */ +} PCPDynamicScreen; + +typedef struct PCPDynamicScreens_ { + Hashtable* table; + size_t count; /* count of dynamic screens discovered from scan */ +} PCPDynamicScreens; + +void PCPDynamicScreens_init(PCPDynamicScreens* screens, struct PCPDynamicColumns_* columns); + +void PCPDynamicScreens_done(Hashtable* table); + +void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host); + +void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings); + +void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss); + +void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen); + +#endif diff --git a/pcp/PCPMachine.c b/pcp/PCPMachine.c index 59e056247..726218d99 100644 --- a/pcp/PCPMachine.c +++ b/pcp/PCPMachine.c @@ -307,6 +307,8 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue)); PCPMachine_updateCPUcount(this); + Platform_updateTables(super); + return super; } diff --git a/pcp/PCPMetric.c b/pcp/PCPMetric.c index 606a5df06..ebb453d3a 100644 --- a/pcp/PCPMetric.c +++ b/pcp/PCPMetric.c @@ -10,6 +10,7 @@ in the source distribution for its full text. #include "pcp/PCPMetric.h" +#include #include #include #include @@ -178,3 +179,21 @@ bool PCPMetric_fetch(struct timeval* timestamp) { *timestamp = pcp->result->timestamp; return true; } + +void PCPMetric_externalName(PCPMetric metric, int inst, char** externalName) { + const pmDesc* desc = &pcp->descs[metric]; + pmNameInDom(desc->indom, inst, externalName); +} + +int PCPMetric_lookupText(const char* metric, char** desc) { + pmID pmid; + int sts; + + sts = pmLookupName(1, &metric, &pmid); + if (sts < 0) + return sts; + + if (pmLookupText(pmid, PM_TEXT_ONELINE, desc) >= 0) + (*desc)[0] = toupper((*desc)[0]); /* UI consistency */ + return 0; +} diff --git a/pcp/PCPMetric.h b/pcp/PCPMetric.h index e89a0a4c7..b53b82de9 100644 --- a/pcp/PCPMetric.h +++ b/pcp/PCPMetric.h @@ -180,4 +180,8 @@ int PCPMetric_instanceOffset(PCPMetric metric, int inst); pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type); +void PCPMetric_externalName(PCPMetric metric, int inst, char** externalName); + +int PCPMetric_lookupText(const char* metric, char** desc); + #endif diff --git a/pcp/Platform.c b/pcp/Platform.c index 13746fc25..470a5f2d4 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -25,6 +25,7 @@ in the source distribution for its full text. #include "DiskIOMeter.h" #include "DynamicColumn.h" #include "DynamicMeter.h" +#include "DynamicScreen.h" #include "FileDescriptorMeter.h" #include "HostnameMeter.h" #include "LoadAverageMeter.h" @@ -46,6 +47,7 @@ in the source distribution for its full text. #include "linux/ZramStats.h" #include "pcp/PCPDynamicColumn.h" #include "pcp/PCPDynamicMeter.h" +#include "pcp/PCPDynamicScreen.h" #include "pcp/PCPMachine.h" #include "pcp/PCPMetric.h" #include "pcp/PCPProcessList.h" @@ -355,6 +357,7 @@ bool Platform_init(void) { pcp->columns.offset = PCP_METRIC_COUNT + pcp->meters.cursor; PCPDynamicColumns_init(&pcp->columns); + PCPDynamicScreens_init(&pcp->screens, &pcp->columns); sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids); if (sts < 0) { @@ -386,6 +389,7 @@ bool Platform_init(void) { PCPMetric_enable(PCP_UNAME_MACHINE, true); PCPMetric_enable(PCP_UNAME_DISTRO, true); + /* enable metrics for all dynamic columns (including those from dynamic screens) */ for (size_t i = pcp->columns.offset; i < pcp->columns.offset + pcp->columns.count; i++) PCPMetric_enable(i, true); @@ -417,6 +421,10 @@ void Platform_dynamicMetersDone(Hashtable* meters) { PCPDynamicMeters_done(meters); } +void Platform_dynamicScreensDone(Hashtable* screens) { + PCPDynamicScreens_done(screens); +} + void Platform_done(void) { pmDestroyContext(pcp->context); if (pcp->result) @@ -846,7 +854,7 @@ Hashtable* Platform_dynamicColumns(void) { return pcp->columns.table; } -const char* Platform_dynamicColumnInit(unsigned int key) { +const char* Platform_dynamicColumnName(unsigned int key) { PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); if (this) { PCPMetric_enable(this->id, true); @@ -867,3 +875,25 @@ bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsi } return false; } + +Hashtable* Platform_dynamicScreens(void) { + return pcp->screens.table; +} + +void Platform_defaultDynamicScreens(Settings* settings) { + PCPDynamicScreen_appendScreens(&pcp->screens, settings); +} + +void Platform_addDynamicScreen(ScreenSettings* ss) { + PCPDynamicScreen_addDynamicScreen(&pcp->screens, ss); +} + +void Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen) { + Hashtable* screens = pcp->screens.table; + PCPDynamicScreens_addAvailableColumns(availableColumns, screens, screen); +} + +void Platform_updateTables(Machine* host) { + PCPDynamicScreen_appendTables(&pcp->screens, host); + PCPDynamicColumns_setupWidths(&pcp->columns); +} diff --git a/pcp/Platform.h b/pcp/Platform.h index f90e28135..f2e8a49d4 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -38,6 +38,7 @@ in the source distribution for its full text. #include "pcp/PCPDynamicColumn.h" #include "pcp/PCPDynamicMeter.h" +#include "pcp/PCPDynamicScreen.h" #include "pcp/PCPMetric.h" @@ -51,6 +52,7 @@ typedef struct Platform_ { pmResult* result; /* sample values result indexed by Metric */ PCPDynamicMeters meters; /* dynamic meters via configuration files */ PCPDynamicColumns columns; /* dynamic columns via configuration files */ + PCPDynamicScreens screens; /* dynamic screens via configuration files */ struct timeval offset; /* time offset used in archive mode only */ long long btime; /* boottime in seconds since the epoch */ char* release; /* uname and distro from this context */ @@ -151,8 +153,20 @@ Hashtable* Platform_dynamicColumns(void); void Platform_dynamicColumnsDone(Hashtable* columns); -const char* Platform_dynamicColumnInit(unsigned int key); +const char* Platform_dynamicColumnName(unsigned int key); bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key); +Hashtable* Platform_dynamicScreens(void); + +void Platform_defaultDynamicScreens(Settings* settings); + +void Platform_addDynamicScreen(ScreenSettings* ss); + +void Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen); + +void Platform_dynamicScreensDone(Hashtable* screens); + +void Platform_updateTables(Machine* host); + #endif diff --git a/pcp/screens/biosnoop b/pcp/screens/biosnoop new file mode 100644 index 000000000..e6cdf8945 --- /dev/null +++ b/pcp/screens/biosnoop @@ -0,0 +1,41 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[biosnoop] +heading = BioSnoop +caption = BPF block I/O snoop +default = false + +pid.heading = PID +pid.caption = Process identifier +pid.metric = bpf.biosnoop.pid +pid.format = process + +disk.heading = DISK +disk.caption = Device name +disk.width = -7 +disk.metric = bpf.biosnoop.disk + +rwbs.heading = TYPE +rwbs.caption = I/O type string +rwbs.width = -4 +rwbs.metric = bpf.biosnoop.rwbs + +bytes.heading = BYTES +bytes.caption = I/O size in bytes +bytes.metric = bpf.biosnoop.bytes + +lat.heading = LAT +lat.caption = I/O latency +lat.metric = bpf.biosnoop.lat + +sector.heading = SECTOR +sector.caption = Device sector +sector.metric = bpf.biosnoop.sector + +comm.heading = Command +comm.caption = Process command name +comm.width = -16 +comm.metric = bpf.biosnoop.comm +comm.format = process diff --git a/pcp/screens/cgroups b/pcp/screens/cgroups new file mode 100644 index 000000000..0ddc65c4a --- /dev/null +++ b/pcp/screens/cgroups @@ -0,0 +1,45 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroups] +heading = CGroups +caption = Control Groups +default = true + +user_cpu.heading = UTIME +user_cpu.caption = User CPU Time +user_cpu.metric = 1000 * rate(cgroup.cpu.stat.user) +user_cpu.width = 6 + +system_cpu.heading = STIME +system_cpu.caption = Kernel CPU Time +system_cpu.metric = 1000 * rate(cgroup.cpu.stat.system) +system_cpu.width = 6 + +cpu_usage.heading = CPU% +cpu_usage.caption = CPU Utilization +cpu_usage.metric = 100 * (rate(cgroup.cpu.stat.usage) / hinv.ncpu) +cpu_usage.format = percent + +cpu_psi.heading = CPU-PSI +cpu_psi.caption = CPU Pressure Stall Information +cpu_psi.metric = 1000 * rate(cgroup.pressure.cpu.some.total) +cpu_psi.width = 7 + +mem_psi.heading = MEM-PSI +mem_psi.caption = Memory Pressure Stall Information +mem_psi.metric = 1000 * rate(cgroup.pressure.memory.some.total) +mem_psi.width = 7 + +io_psi.heading = I/O-PSI +io_psi.caption = I/O Pressure Stall Information +io_psi.metric = 1000 * rate(cgroup.pressure.io.some.total) +io_psi.width = 7 + +name.heading = Control group +name.caption = Control group name +name.width = -64 +name.metric = cgroup.cpu.stat.system +name.instances = true +name.format = cgroup diff --git a/pcp/screens/cgroupsio b/pcp/screens/cgroupsio new file mode 100644 index 000000000..3a431db5a --- /dev/null +++ b/pcp/screens/cgroupsio @@ -0,0 +1,49 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroupsio] +heading = CGroupsIO +caption = Control Groups I/O +default = false + +iops.heading = IOPS +iops.caption = I/O operations +iops.metric = rate(cgroup.io.stat.rios) + rate(cgroup.io.stat.wios) + rate(cgroup.io.stat.dios) + +readops.heading = RDIO +readops.caption = Read operations +readops.metric = rate(cgroup.io.stat.rios) +readops.default = false + +writeops.heading = WRIO +writeops.caption = Write operations +writeops.metric = rate(cgroup.io.stat.wios) +writeops.default = false + +directops.heading = DIO +directops.caption = Direct I/O operations +directops.metric = rate(cgroup.io.stat.dios) +directops.default = false + +totalbytes.heading = R/W/D +totalbytes.caption = Disk throughput +totalbytes.metric = rate(cgroup.io.stat.rbytes) + rate(cgroup.io.stat.wbytes) + rate(cgroup.io.stat.dbytes) + +readbytes.heading = RBYTE +readbytes.caption = Disk read throughput +readbytes.metric = rate(cgroup.io.stat.rbytes) + +writebytes.heading = WBYTE +writebytes.caption = Disk throughput +writebytes.metric = rate(cgroup.io.stat.wbytes) + +directio.heading = DBYTE +directio.caption = Direct I/O throughput +directio.metric = rate(cgroup.io.stat.dbytes) + +name.heading = Control group device +name.caption = Control group device +name.width = -64 +name.metric = cgroup.io.stat.rbytes +name.instances = true diff --git a/pcp/screens/cgroupsmem b/pcp/screens/cgroupsmem new file mode 100644 index 000000000..17bc1e38f --- /dev/null +++ b/pcp/screens/cgroupsmem @@ -0,0 +1,48 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroupsmem] +heading = CGroupsMem +caption = Control Groups Memory +default = false + +current.heading = MEM +current.caption = Current memory +current.metric = cgroup.memory.current + +usage.heading = USAGE +usage.caption = Memory usage +usage.metric = cgroup.memory.usage + +container.heading = CONTAINER +container.caption = Container Name +container.metric = cgroup.memory.id.container + +resident.heading = RSS +resident.metric = cgroup.memory.stat.rss + +cresident.heading = CRSS +cresident.metric = cgroup.memory.stat.total.rss + +anonmem.heading = ANON +anonmem.metric = cgroup.memory.stat.anon + +filemem.heading = FILE +filemem.metric = cgroup.memory.stat.file + +shared.heading = SHMEM +shared.metric = cgroup.memory.stat.shmem + +swap.heading = SWAP +swap.metric = cgroup.memory.stat.swap + +pgfault.heading = FAULTS +pgfault.metric = cgroup.memory.stat.pgfaults + +name.heading = Control group +name.caption = Control group name +name.width = -64 +name.metric = cgroup.memory.current +name.instances = true +name.format = cgroup diff --git a/pcp/screens/devices b/pcp/screens/devices new file mode 100644 index 000000000..f9f6bc0c1 --- /dev/null +++ b/pcp/screens/devices @@ -0,0 +1,114 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[disks] +heading = Disks +caption = Disk devices + +diskdev.heading = Device +diskdev.metric = disk.dev.read +diskdev.instances = true +diskdev.format = device +diskdev.width = -8 + +total.heading = TPS +total.metric = rate(disk.dev.read) + rate(disk.dev.write) + rate(disk.dev.discard) +total.caption = Rate of read requests + +read.heading = RR/S +read.metric = rate(disk.dev.read) +read.caption = Rate of read requests + +read_bytes.heading = RRB/S +read_bytes.metric = rate(disk.dev.read_bytes) +read_bytes.caption = Read throughput from the device + +read_merge.heading = RRQM/S +read_merge.metric = rate(disk.dev.read_merge) +read_merge.caption = Rate reads merged before queued +read_merge.default = false + +read_merge_pct.heading = RRQM% +read_merge_pct.metric = 100 * rate(disk.dev.read_merge) / rate(disk.dev.read) +read_merge_pct.caption = Percentage reads merged before queued +read_merge_pct.format = percent + +read_await.heading = RAWAIT +read_await.metric = disk.dev.r_await +read_await.default = false + +read_avqsz.heading = RARQSZ +read_avqsz.metric = disk.dev.r_avg_rqsz +read_avqsz.default = false + +write.heading = WR/S +write.metric = rate(disk.dev.write) +write.caption = Rate of write requests + +write_bytes.heading = WRB/S +write_bytes.metric = rate(disk.dev.write_bytes) +write_bytes.caption = Write throughput to the device + +write_merge.heading = WRQM/S +write_merge.metric = rate(disk.dev.write_merge) +write_merge.caption = Rate writes merged before queued +write_merge.default = false + +write_merge_pct.heading = WRQM% +write_merge_pct.metric = 100 * rate(disk.dev.write_merge) / rate(disk.dev.write) +write_merge_pct.caption = Percentage writes merged before queued +write_merge_pct.format = percent + +write_await.heading = WAWAIT +write_await.metric = disk.dev.w_await +write_await.default = false + +write_avqsz.heading = WARQSZ +write_avqsz.metric = disk.dev.w_avg_rqsz +write_avqsz.default = false + +discard.heading = DR/S +discard.metric = rate(disk.dev.discard) +discard.caption = Rate of discard requests + +discard_bytes.heading = DRB/S +discard_bytes.metric = rate(disk.dev.discard_bytes) +discard_bytes.caption = Discard request throughput +discard_bytes.default = false + +discard_merge.heading = DRQM/S +discard_merge.metric = rate(disk.dev.discard_merge) +discard_merge.caption = Rate discards merged before queued +discard_merge.default = false + +discard_merge_pct.heading = DRQM% +discard_merge_pct.metric = 100 * rate(disk.dev.discard_merge) / rate(disk.dev.discard) +discard_merge_pct.caption = Percentage discards merged before queued +discard_merge_pct.format = percent +discard_merge_pct.default = false + +discard_await.heading = DAWAIT +discard_await.metric = disk.dev.d_await +discard_await.default = false + +discard_avqsz.heading = DARQSZ +discard_avqsz.metric = disk.dev.d_avg_rqsz +discard_avqsz.default = false + +flush.heading = F/S +flush.metric = rate(disk.dev.flush) +flush.default = false +flush.caption = Flushes per second + +flush_await.heading = FAWAIT +flush_await.metric = disk.dev.f_await +flush_await.default = false + +qlen.heading = AQU-SZ +qlen.metric = disk.dev.avg_qlen + +util.heading = UTIL% +util.metric = 100 * disk.dev.util +util.caption = Perentage device utilitization +util.format = percent diff --git a/pcp/screens/execsnoop b/pcp/screens/execsnoop new file mode 100644 index 000000000..d706e7640 --- /dev/null +++ b/pcp/screens/execsnoop @@ -0,0 +1,37 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[execsnoop] +heading = ExecSnoop +caption = BPF exec(2) syscall snoop +default = false + +pid.heading = PID +pid.caption = Process Identifier +pid.metric = bpf.execsnoop.pid +pid.format = process + +ppid.heading = PPID +ppid.caption = Parent Process +ppid.metric = bpf.execsnoop.ppid +ppid.format = process + +uid.heading = UID +uid.caption = User Identifier +uid.metric = bpf.execsnoop.uid + +comm.heading = COMM +comm.caption = Command +comm.width = -16 +comm.metric = bpf.execsnoop.comm +comm.format = command + +ret.heading = RET +ret.caption = Return Code +ret.metric = bpf.execsnoop.ret + +path.heading = Arguments +path.caption = Arguments +path.width = -12 +path.metric = bpf.execsnoop.args diff --git a/pcp/screens/exitsnoop b/pcp/screens/exitsnoop new file mode 100644 index 000000000..6c6b867c0 --- /dev/null +++ b/pcp/screens/exitsnoop @@ -0,0 +1,48 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[exitsnoop] +heading = ExitSnoop +caption = BPF process exit(2) snoop +default = false + +pid.heading = PID +pid.caption = Process Identifier +pid.metric = bpf.exitsnoop.pid +pid.format = process + +ppid.heading = PPID +ppid.caption = Parent Process +ppid.metric = bpf.exitsnoop.ppid +ppid.format = process + +tid.heading = TID +tid.caption = Task Identifier +tid.metric = bpf.exitsnoop.tid +tid.format = process +tid.default = false + +signal.heading = SIG +signal.caption = Signal number +signal.metric = bpf.exitsnoop.sig + +exit.heading = EXIT +exit.caption = Exit Code +exit.metric = bpf.exitsnoop.exit_code + +core.heading = CORE +core.caption = Dumped core +core.metric = bpf.exitsnoop.coredump +core.default = false + +age.heading = AGE +age.caption = Process age +age.metric = bpf.exitsnoop.age +age.default = false + +comm.heading = Command +comm.caption = COMM +comm.width = -16 +comm.metric = bpf.exitsnoop.comm +comm.format = command diff --git a/pcp/screens/filesystems b/pcp/screens/filesystems new file mode 100644 index 000000000..06f3bf236 --- /dev/null +++ b/pcp/screens/filesystems @@ -0,0 +1,50 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[filesystems] +heading = Filesystems +caption = Mounted block device filesystems + +blockdev.heading = Device +blockdev.metric = filesys.mountdir +blockdev.instances = true +blockdev.width = -14 + +blocksize.heading = BSIZE +blocksize.metric = filesys.blocksize +blocksize.default = false + +capacity.heading = SIZE +capacity.metric = filesys.capacity + +used.heading = USED +used.metric = filesys.used + +free.heading = FREE +free.metric = filesys.free +free.default = false + +avail.heading = AVAIL +avail.metric = filesys.avail + +full.heading = USE% +full.metric = filesys.full +full.format = percent + +usedfiles.heading = USEDF +usedfiles.metric = filesys.usedfiles +usedfiles.default = false + +freefiles.heading = FREEF +freefiles.metric = filesys.freefiles +freefiles.default = false + +maxfiles.heading = MAXF +maxfiles.metric = filesys.maxfiles +maxfiles.default = false + +mountdir.heading = Mount point +mountdir.metric = filesys.mountdir +mountdir.format = path +mountdir.width = -33 diff --git a/pcp/screens/opensnoop b/pcp/screens/opensnoop new file mode 100644 index 000000000..ec209b03f --- /dev/null +++ b/pcp/screens/opensnoop @@ -0,0 +1,27 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[opensnoop] +heading = OpenSnoop +caption = BPF open(2) syscall snoop +default = false + +pid.heading = PID +pid.metric = bpf.opensnoop.pid +pid.format = process + +comm.heading = COMM +comm.metric = bpf.opensnoop.comm +comm.format = command + +fd.heading = FD +fd.metric = bpf.opensnoop.fd + +err.heading = ERR +err.metric = bpf.opensnoop.err + +file.heading = File name +file.width = -32 +file.metric = bpf.opensnoop.fname +file.format = path diff --git a/solaris/Platform.h b/solaris/Platform.h index 3dc6e3b59..00bc5832c 100644 --- a/solaris/Platform.h +++ b/solaris/Platform.h @@ -150,7 +150,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -158,4 +158,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif diff --git a/unsupported/Platform.h b/unsupported/Platform.h index a718ca09e..8e08dc238 100644 --- a/unsupported/Platform.h +++ b/unsupported/Platform.h @@ -98,7 +98,7 @@ static inline Hashtable* Platform_dynamicColumns(void) { static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } -static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { return NULL; } @@ -106,4 +106,16 @@ static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* p return false; } +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + #endif From 526abd8f53eff7ff0bc37be77f7d117f6db7dde2 Mon Sep 17 00:00:00 2001 From: Nathan Scott Date: Tue, 29 Aug 2023 18:09:19 +1000 Subject: [PATCH 3/4] Rename PCPMetric files, structures and related internal functions Simplify names to just Metric in this case as it is not mirroring core htop naming (which is the norm for that naming convention). --- Makefile.am | 18 +++---- pcp/InDomTable.c | 4 +- pcp/Instance.c | 16 +++--- pcp/{PCPMetric.c => Metric.c} | 36 +++++++------- pcp/{PCPMetric.h => Metric.h} | 36 +++++++------- pcp/PCPDynamicColumn.c | 21 ++++---- pcp/PCPDynamicMeter.c | 12 ++--- pcp/PCPDynamicScreen.c | 6 +-- pcp/PCPMachine.c | 88 ++++++++++++++++---------------- pcp/PCPProcessList.c | 40 +++++++-------- pcp/Platform.c | 94 +++++++++++++++++------------------ pcp/Platform.h | 4 +- 12 files changed, 187 insertions(+), 188 deletions(-) rename pcp/{PCPMetric.c => Metric.c} (80%) rename pcp/{PCPMetric.h => Metric.h} (89%) diff --git a/Makefile.am b/Makefile.am index b7d3cca39..90bd30ef1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -416,17 +416,17 @@ pcp_platform_headers = \ linux/PressureStallMeter.h \ linux/ZramMeter.h \ linux/ZramStats.h \ + pcp/Instance.h \ + pcp/InDomTable.h \ + pcp/Metric.h \ + pcp/Platform.h \ + pcp/ProcessField.h \ pcp/PCPDynamicColumn.h \ pcp/PCPDynamicMeter.h \ pcp/PCPDynamicScreen.h \ - pcp/Instance.h \ - pcp/InDomTable.h \ pcp/PCPMachine.h \ - pcp/PCPMetric.h \ pcp/PCPProcess.h \ pcp/PCPProcessList.h \ - pcp/Platform.h \ - pcp/ProcessField.h \ zfs/ZfsArcMeter.h \ zfs/ZfsArcStats.h \ zfs/ZfsCompressedArcMeter.h @@ -435,16 +435,16 @@ pcp_platform_sources = \ linux/CGroupUtils.c \ linux/PressureStallMeter.c \ linux/ZramMeter.c \ + pcp/Instance.c \ + pcp/InDomTable.c \ + pcp/Metric.c \ + pcp/Platform.c \ pcp/PCPDynamicColumn.c \ pcp/PCPDynamicMeter.c \ pcp/PCPDynamicScreen.c \ - pcp/Instance.c \ - pcp/InDomTable.c \ pcp/PCPMachine.c \ - pcp/PCPMetric.c \ pcp/PCPProcess.c \ pcp/PCPProcessList.c \ - pcp/Platform.c \ zfs/ZfsArcMeter.c \ zfs/ZfsCompressedArcMeter.c diff --git a/pcp/InDomTable.c b/pcp/InDomTable.c index 120599222..b96f8a81e 100644 --- a/pcp/InDomTable.c +++ b/pcp/InDomTable.c @@ -22,8 +22,8 @@ in the source distribution for its full text. #include "XUtils.h" #include "pcp/Instance.h" +#include "pcp/Metric.h" #include "pcp/PCPDynamicColumn.h" -#include "pcp/PCPMetric.h" InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey) { @@ -68,7 +68,7 @@ static void InDomTable_goThroughEntries(InDomTable* this) { /* for every instance ... */ int instid = -1, offset = -1; - while (PCPMetric_iterate(this->metricKey, &instid, &offset)) { + while (Metric_iterate(this->metricKey, &instid, &offset)) { bool preExisting; Instance* inst = InDomTable_getInstance(this, instid, &preExisting); inst->offset = offset >= 0 ? offset : 0; diff --git a/pcp/Instance.c b/pcp/Instance.c index 1d56c108b..e70e7b703 100644 --- a/pcp/Instance.c +++ b/pcp/Instance.c @@ -19,16 +19,16 @@ in the source distribution for its full text. #include "Hashtable.h" #include "Machine.h" #include "Macros.h" +#include "Metric.h" +#include "Platform.h" #include "PCPDynamicColumn.h" #include "PCPDynamicScreen.h" -#include "PCPMetric.h" -#include "Platform.h" #include "Row.h" #include "RichString.h" #include "XUtils.h" #include "pcp/InDomTable.h" -#include "pcp/PCPMetric.h" +#include "pcp/Metric.h" Instance* Instance_new(const Machine* host, const InDomTable* indom) { @@ -62,11 +62,11 @@ static void Instance_writeField(const Row* super, RichString* str, RowField fiel const Settings* settings = super->host->settings; DynamicColumn* column = Hashtable_get(settings->dynamicColumns, field); PCPDynamicColumn* cp = (PCPDynamicColumn*) column; - const pmDesc* descp = PCPMetric_desc(cp->id); + const pmDesc* descp = Metric_desc(cp->id); pmAtomValue atom; pmAtomValue *ap = &atom; - if (!PCPMetric_instance(cp->id, instid, this->offset, ap, descp->type)) + if (!Metric_instance(cp->id, instid, this->offset, ap, descp->type)) ap = NULL; PCPDynamicColumn_writeAtomValue(cp, str, settings, cp->id, instid, descp, ap); @@ -96,11 +96,11 @@ static int Instance_compareByKey(const Row* v1, const Row* v2, int key) { return -1; size_t metric = column->id; - unsigned int type = PCPMetric_type(metric); + unsigned int type = Metric_type(metric); pmAtomValue atom1 = {0}, atom2 = {0}; - if (!PCPMetric_instance(metric, i1->offset, i1->offset, &atom1, type) || - !PCPMetric_instance(metric, i2->offset, i2->offset, &atom2, type)) { + if (!Metric_instance(metric, i1->offset, i1->offset, &atom1, type) || + !Metric_instance(metric, i2->offset, i2->offset, &atom2, type)) { if (type == PM_TYPE_STRING) { free(atom1.cp); free(atom2.cp); diff --git a/pcp/PCPMetric.c b/pcp/Metric.c similarity index 80% rename from pcp/PCPMetric.c rename to pcp/Metric.c index ebb453d3a..ce6d90d00 100644 --- a/pcp/PCPMetric.c +++ b/pcp/Metric.c @@ -1,5 +1,5 @@ /* -htop - PCPMetric.c +htop - Metric.c (C) 2020-2021 htop dev team (C) 2020-2021 Red Hat, Inc. Released under the GNU GPLv2+, see the COPYING file @@ -8,7 +8,7 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep -#include "pcp/PCPMetric.h" +#include "pcp/Metric.h" #include #include @@ -22,15 +22,15 @@ in the source distribution for its full text. extern Platform* pcp; -const pmDesc* PCPMetric_desc(PCPMetric metric) { +const pmDesc* Metric_desc(Metric metric) { return &pcp->descs[metric]; } -int PCPMetric_type(PCPMetric metric) { +int Metric_type(Metric metric) { return pcp->descs[metric].type; } -pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type) { +pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type) { if (pcp->result == NULL) return NULL; @@ -55,14 +55,14 @@ pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, in return atom; } -int PCPMetric_instanceCount(PCPMetric metric) { +int Metric_instanceCount(Metric metric) { pmValueSet* vset = pcp->result->vset[metric]; if (vset) return vset->numval; return 0; } -int PCPMetric_instanceOffset(PCPMetric metric, int inst) { +int Metric_instanceOffset(Metric metric, int inst) { pmValueSet* vset = pcp->result->vset[metric]; if (!vset || vset->numval <= 0) return 0; @@ -75,7 +75,7 @@ int PCPMetric_instanceOffset(PCPMetric metric, int inst) { return 0; } -static pmAtomValue* PCPMetric_extract(PCPMetric metric, int inst, int offset, pmValueSet* vset, pmAtomValue* atom, int type) { +static pmAtomValue* Metric_extract(Metric metric, int inst, int offset, pmValueSet* vset, pmAtomValue* atom, int type) { /* extract value (using requested type) of given metric instance */ const pmDesc* desc = &pcp->descs[metric]; @@ -90,7 +90,7 @@ static pmAtomValue* PCPMetric_extract(PCPMetric metric, int inst, int offset, pm return atom; } -pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type) { +pmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type) { pmValueSet* vset = pcp->result->vset[metric]; if (!vset || vset->numval <= 0) @@ -98,12 +98,12 @@ pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomVa /* fast-path using heuristic offset based on expected location */ if (offset >= 0 && offset < vset->numval && inst == vset->vlist[offset].inst) - return PCPMetric_extract(metric, inst, offset, vset, atom, type); + return Metric_extract(metric, inst, offset, vset, atom, type); /* slow-path using a linear search for the requested instance */ for (int i = 0; i < vset->numval; i++) { if (inst == vset->vlist[i].inst) - return PCPMetric_extract(metric, inst, i, vset, atom, type); + return Metric_extract(metric, inst, i, vset, atom, type); } return NULL; } @@ -114,7 +114,7 @@ pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomVa * * Start it off by passing offset -1 into the routine. */ -bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp) { +bool Metric_iterate(Metric metric, int* instp, int* offsetp) { if (!pcp->result) return false; @@ -133,15 +133,15 @@ bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp) { } /* Switch on/off a metric for value fetching (sampling) */ -void PCPMetric_enable(PCPMetric metric, bool enable) { +void Metric_enable(Metric metric, bool enable) { pcp->fetch[metric] = enable ? pcp->pmids[metric] : PM_ID_NULL; } -bool PCPMetric_enabled(PCPMetric metric) { +bool Metric_enabled(Metric metric) { return pcp->fetch[metric] != PM_ID_NULL; } -void PCPMetric_enableThreads(void) { +void Metric_enableThreads(void) { pmValueSet* vset = xCalloc(1, sizeof(pmValueSet)); vset->vlist[0].inst = PM_IN_NULL; vset->vlist[0].value.lval = 1; @@ -160,7 +160,7 @@ void PCPMetric_enableThreads(void) { pmFreeResult(result); } -bool PCPMetric_fetch(struct timeval* timestamp) { +bool Metric_fetch(struct timeval* timestamp) { if (pcp->result) { pmFreeResult(pcp->result); pcp->result = NULL; @@ -180,12 +180,12 @@ bool PCPMetric_fetch(struct timeval* timestamp) { return true; } -void PCPMetric_externalName(PCPMetric metric, int inst, char** externalName) { +void Metric_externalName(Metric metric, int inst, char** externalName) { const pmDesc* desc = &pcp->descs[metric]; pmNameInDom(desc->indom, inst, externalName); } -int PCPMetric_lookupText(const char* metric, char** desc) { +int Metric_lookupText(const char* metric, char** desc) { pmID pmid; int sts; diff --git a/pcp/PCPMetric.h b/pcp/Metric.h similarity index 89% rename from pcp/PCPMetric.h rename to pcp/Metric.h index b53b82de9..789ef4b24 100644 --- a/pcp/PCPMetric.h +++ b/pcp/Metric.h @@ -1,7 +1,7 @@ -#ifndef HEADER_PCPMetric -#define HEADER_PCPMetric +#ifndef HEADER_Metric +#define HEADER_Metric /* -htop - PCPMetric.h +htop - Metric.h (C) 2020-2021 htop dev team (C) 2020-2021 Red Hat, Inc. Released under the GNU GPLv2+, see the COPYING file @@ -22,7 +22,7 @@ in the source distribution for its full text. #undef PACKAGE_BUGREPORT -typedef enum PCPMetric_ { +typedef enum Metric_ { PCP_CONTROL_THREADS, /* proc.control.perclient.threads */ PCP_HINV_NCPU, /* hinv.ncpu */ @@ -156,32 +156,32 @@ typedef enum PCPMetric_ { PCP_PROC_SMAPS_SWAPPSS, /* proc.smaps.swappss */ PCP_METRIC_COUNT /* total metric count */ -} PCPMetric; +} Metric; -void PCPMetric_enable(PCPMetric metric, bool enable); +void Metric_enable(Metric metric, bool enable); -bool PCPMetric_enabled(PCPMetric metric); +bool Metric_enabled(Metric metric); -void PCPMetric_enableThreads(void); +void Metric_enableThreads(void); -bool PCPMetric_fetch(struct timeval* timestamp); +bool Metric_fetch(struct timeval* timestamp); -bool PCPMetric_iterate(PCPMetric metric, int* instp, int* offsetp); +bool Metric_iterate(Metric metric, int* instp, int* offsetp); -pmAtomValue* PCPMetric_values(PCPMetric metric, pmAtomValue* atom, int count, int type); +pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type); -const pmDesc* PCPMetric_desc(PCPMetric metric); +const pmDesc* Metric_desc(Metric metric); -int PCPMetric_type(PCPMetric metric); +int Metric_type(Metric metric); -int PCPMetric_instanceCount(PCPMetric metric); +int Metric_instanceCount(Metric metric); -int PCPMetric_instanceOffset(PCPMetric metric, int inst); +int Metric_instanceOffset(Metric metric, int inst); -pmAtomValue* PCPMetric_instance(PCPMetric metric, int inst, int offset, pmAtomValue* atom, int type); +pmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type); -void PCPMetric_externalName(PCPMetric metric, int inst, char** externalName); +void Metric_externalName(Metric metric, int inst, char** externalName); -int PCPMetric_lookupText(const char* metric, char** desc); +int Metric_lookupText(const char* metric, char** desc); #endif diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c index 99e0d8f1f..02d3ab9c5 100644 --- a/pcp/PCPDynamicColumn.c +++ b/pcp/PCPDynamicColumn.c @@ -28,8 +28,8 @@ in the source distribution for its full text. #include "XUtils.h" #include "linux/CGroupUtils.h" +#include "pcp/Metric.h" #include "pcp/PCPProcess.h" -#include "pcp/PCPMetric.h" static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) { @@ -50,9 +50,8 @@ static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicCol static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) { /* pmLookupText */ - if (!column->super.description) { - PCPMetric_lookupText(value, &column->super.description); - } + if (!column->super.description) + Metric_lookupText(value, &column->super.description); /* lookup a dynamic metric with this name, else create */ if (PCPDynamicColumn_addMetric(columns, column) == false) @@ -269,7 +268,7 @@ static void PCPDynamicColumn_setupWidth(ATTR_UNUSED ht_key_t key, void* value, A PCPDynamicColumn* column = (PCPDynamicColumn*) value; /* calculate column size based on config file and metric units */ - const pmDesc* desc = PCPMetric_desc(column->id); + const pmDesc* desc = Metric_desc(column->id); if (column->instances || desc->type == PM_TYPE_STRING) { column->super.width = column->width; @@ -379,7 +378,7 @@ void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, char* dupd1 = NULL; if (column->instances) { attr = CRT_colors[DYNAMIC_GRAY]; - PCPMetric_externalName(metric, instance, &dupd1); + Metric_externalName(metric, instance, &dupd1); value = dupd1; } else { attr = CRT_colors[DYNAMIC_GREEN]; @@ -457,12 +456,12 @@ void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) { const Settings* settings = proc->super.host->settings; const PCPProcess* pp = (const PCPProcess*) proc; - const pmDesc* desc = PCPMetric_desc(this->id); + const pmDesc* desc = Metric_desc(this->id); pid_t pid = Process_getPid(proc); pmAtomValue atom; pmAtomValue *ap = &atom; - if (!PCPMetric_instance(this->id, pid, pp->offset, ap, desc->type)) + if (!Metric_instance(this->id, pid, pp->offset, ap, desc->type)) ap = NULL; PCPDynamicColumn_writeAtomValue(this, str, settings, this->id, pid, desc, ap); @@ -477,11 +476,11 @@ int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, Pr return -1; size_t metric = column->id; - unsigned int type = PCPMetric_type(metric); + unsigned int type = Metric_type(metric); pmAtomValue atom1 = {0}, atom2 = {0}; - if (!PCPMetric_instance(metric, Process_getPid(&p1->super), p1->offset, &atom1, type) || - !PCPMetric_instance(metric, Process_getPid(&p2->super), p2->offset, &atom2, type)) { + if (!Metric_instance(metric, Process_getPid(&p1->super), p1->offset, &atom1, type) || + !Metric_instance(metric, Process_getPid(&p2->super), p2->offset, &atom2, type)) { if (type == PM_TYPE_STRING) { free(atom1.cp); free(atom2.cp); diff --git a/pcp/PCPDynamicMeter.c b/pcp/PCPDynamicMeter.c index e89998813..87e51700c 100644 --- a/pcp/PCPDynamicMeter.c +++ b/pcp/PCPDynamicMeter.c @@ -25,7 +25,7 @@ in the source distribution for its full text. #include "RichString.h" #include "XUtils.h" -#include "pcp/PCPMetric.h" +#include "pcp/Metric.h" static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) { @@ -309,7 +309,7 @@ void PCPDynamicMeters_done(Hashtable* table) { void PCPDynamicMeter_enable(PCPDynamicMeter* this) { for (size_t i = 0; i < this->totalMetrics; i++) - PCPMetric_enable(this->metrics[i].id, true); + Metric_enable(this->metrics[i].id, true); } void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { @@ -322,10 +322,10 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { buffer[bytes++] = '/'; /* separator */ PCPDynamicMetric* metric = &this->metrics[i]; - const pmDesc* desc = PCPMetric_desc(metric->id); + const pmDesc* desc = Metric_desc(metric->id); pmAtomValue atom, raw; - if (!PCPMetric_values(metric->id, &raw, 1, desc->type)) { + if (!Metric_values(metric->id, &raw, 1, desc->type)) { bytes--; /* clear the separator */ continue; } @@ -393,11 +393,11 @@ void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* met for (size_t i = 0; i < this->totalMetrics; i++) { PCPDynamicMetric* metric = &this->metrics[i]; - const pmDesc* desc = PCPMetric_desc(metric->id); + const pmDesc* desc = Metric_desc(metric->id); pmAtomValue atom, raw; char buffer[64]; - if (!PCPMetric_values(metric->id, &raw, 1, desc->type)) + if (!Metric_values(metric->id, &raw, 1, desc->type)) continue; pmUnits conv = desc->units; /* convert to canonical units */ diff --git a/pcp/PCPDynamicScreen.c b/pcp/PCPDynamicScreen.c index 573bc4276..a4622e342 100644 --- a/pcp/PCPDynamicScreen.c +++ b/pcp/PCPDynamicScreen.c @@ -57,8 +57,8 @@ static void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, P columns->count++; if (j == 0) { - const pmDesc* desc = PCPMetric_desc(column->id); - assert(desc->indom != PM_INDOM_NULL); + const pmDesc* desc = Metric_desc(column->id); + assert(desc->indom != PM_INDOM_NULL); screen->indom = desc->indom; screen->key = column->id; } @@ -126,7 +126,7 @@ static void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* p /* pmLookupText - add optional metric help text */ if (!column->super.description && !column->instances) - PCPMetric_lookupText(value, &column->super.description); + Metric_lookupText(value, &column->super.description); } else { /* this is a property of a dynamic column - the column expression */ diff --git a/pcp/PCPMachine.c b/pcp/PCPMachine.c index 726218d99..801f39784 100644 --- a/pcp/PCPMachine.c +++ b/pcp/PCPMachine.c @@ -25,13 +25,13 @@ in the source distribution for its full text. #include "Settings.h" #include "XUtils.h" -#include "pcp/PCPMetric.h" +#include "pcp/Metric.h" #include "pcp/PCPProcess.h" static void PCPMachine_updateCPUcount(PCPMachine* this) { Machine* super = &this->super; - super->activeCPUs = PCPMetric_instanceCount(PCP_PERCPU_SYSTEM); + super->activeCPUs = Metric_instanceCount(PCP_PERCPU_SYSTEM); unsigned int cpus = Platform_getMaxCPU(); if (cpus == super->existingCPUs) return; @@ -58,30 +58,30 @@ static void PCPMachine_updateMemoryInfo(Machine* host) { host->usedSwap = host->totalSwap = host->sharedMem = 0; pmAtomValue value; - if (PCPMetric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL) host->totalMem = value.ull; - if (PCPMetric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL) freeMem = value.ull; - if (PCPMetric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL) host->buffersMem = value.ull; - if (PCPMetric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL) sreclaimableMem = value.ull; - if (PCPMetric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL) host->sharedMem = value.ull; - if (PCPMetric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL) host->cachedMem = value.ull + sreclaimableMem - host->sharedMem; const memory_t usedDiff = freeMem + host->cachedMem + sreclaimableMem + host->buffersMem; host->usedMem = (host->totalMem >= usedDiff) ? host->totalMem - usedDiff : host->totalMem - freeMem; - if (PCPMetric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL) host->availableMem = MINIMUM(value.ull, host->totalMem); else host->availableMem = freeMem; - if (PCPMetric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL) swapFreeMem = value.ull; - if (PCPMetric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL) host->totalSwap = value.ull; - if (PCPMetric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL) host->cachedSwap = value.ull; host->usedSwap = host->totalSwap - swapFreeMem - host->cachedSwap; } @@ -153,26 +153,26 @@ static void PCPMachine_deriveCPUTime(pmAtomValue* values) { PCPMachine_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime); } -static void PCPMachine_updateAllCPUTime(PCPMachine* this, PCPMetric metric, CPUMetric cpumetric) +static void PCPMachine_updateAllCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric) { pmAtomValue* value = &this->cpu[cpumetric]; - if (PCPMetric_values(metric, value, 1, PM_TYPE_U64) == NULL) + if (Metric_values(metric, value, 1, PM_TYPE_U64) == NULL) memset(value, 0, sizeof(pmAtomValue)); } -static void PCPMachine_updatePerCPUTime(PCPMachine* this, PCPMetric metric, CPUMetric cpumetric) +static void PCPMachine_updatePerCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric) { int cpus = this->super.existingCPUs; - if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL) + if (Metric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL) memset(this->values, 0, cpus * sizeof(pmAtomValue)); for (int i = 0; i < cpus; i++) this->percpu[i][cpumetric].ull = this->values[i].ull; } -static void PCPMachine_updatePerCPUReal(PCPMachine* this, PCPMetric metric, CPUMetric cpumetric) +static void PCPMachine_updatePerCPUReal(PCPMachine* this, Metric metric, CPUMetric cpumetric) { int cpus = this->super.existingCPUs; - if (PCPMetric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL) + if (Metric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL) memset(this->values, 0, cpus * sizeof(pmAtomValue)); for (int i = 0; i < cpus; i++) this->percpu[i][cpumetric].d = this->values[i].d; @@ -185,29 +185,29 @@ static inline void PCPMachine_scanZfsArcstats(PCPMachine* this) { pmAtomValue value; memset(&this->zfs, 0, sizeof(ZfsArcStats)); - if (PCPMetric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.anon = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64)) this->zfs.min = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64)) this->zfs.max = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64)) bonusSize = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_DBUF_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_DBUF_SIZE, &value, 1, PM_TYPE_U64)) dbufSize = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_DNODE_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_DNODE_SIZE, &value, 1, PM_TYPE_U64)) dnodeSize = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_COMPRESSED_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_COMPRESSED_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.compressed = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_UNCOMPRESSED_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_UNCOMPRESSED_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.uncompressed = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_HDR_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_HDR_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.header = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_MFU_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_MFU_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.MFU = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_MRU_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_MRU_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.MRU = value.ull / ONE_K; - if (PCPMetric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64)) + if (Metric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64)) this->zfs.size = value.ull / ONE_K; this->zfs.other = (dbufSize + dnodeSize + bonusSize) / ONE_K; @@ -260,31 +260,31 @@ void Machine_scan(Machine* super) { bool flagged; for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++) - PCPMetric_enable(metric, true); + Metric_enable(metric, true); flagged = settings->showCPUFrequency; - PCPMetric_enable(PCP_HINV_CPUCLOCK, flagged); + Metric_enable(PCP_HINV_CPUCLOCK, flagged); flagged = flags & PROCESS_FLAG_LINUX_CGROUP; - PCPMetric_enable(PCP_PROC_CGROUPS, flagged); + Metric_enable(PCP_PROC_CGROUPS, flagged); flagged = flags & PROCESS_FLAG_LINUX_OOM; - PCPMetric_enable(PCP_PROC_OOMSCORE, flagged); + Metric_enable(PCP_PROC_OOMSCORE, flagged); flagged = flags & PROCESS_FLAG_LINUX_CTXT; - PCPMetric_enable(PCP_PROC_VCTXSW, flagged); - PCPMetric_enable(PCP_PROC_NVCTXSW, flagged); + Metric_enable(PCP_PROC_VCTXSW, flagged); + Metric_enable(PCP_PROC_NVCTXSW, flagged); flagged = flags & PROCESS_FLAG_LINUX_SECATTR; - PCPMetric_enable(PCP_PROC_LABELS, flagged); + Metric_enable(PCP_PROC_LABELS, flagged); flagged = flags & PROCESS_FLAG_LINUX_AUTOGROUP; - PCPMetric_enable(PCP_PROC_AUTOGROUP_ID, flagged); - PCPMetric_enable(PCP_PROC_AUTOGROUP_NICE, flagged); + Metric_enable(PCP_PROC_AUTOGROUP_ID, flagged); + Metric_enable(PCP_PROC_AUTOGROUP_NICE, flagged); /* Sample smaps metrics on every second pass to improve performance */ host->smaps_flag = !!host->smaps_flag; - PCPMetric_enable(PCP_PROC_SMAPS_PSS, host->smaps_flag); - PCPMetric_enable(PCP_PROC_SMAPS_SWAP, host->smaps_flag); - PCPMetric_enable(PCP_PROC_SMAPS_SWAPPSS, host->smaps_flag); + Metric_enable(PCP_PROC_SMAPS_PSS, host->smaps_flag); + Metric_enable(PCP_PROC_SMAPS_SWAP, host->smaps_flag); + Metric_enable(PCP_PROC_SMAPS_SWAPPSS, host->smaps_flag); struct timeval timestamp; - if (PCPMetric_fetch(×tamp) != true) + if (Metric_fetch(×tamp) != true) return; double sample = host->timestamp; @@ -328,7 +328,7 @@ bool Machine_isCPUonline(const Machine* host, unsigned int id) { (void) host; pmAtomValue value; - if (PCPMetric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32)) + if (Metric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32)) return true; return false; } diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c index f320ba61d..7492b2c1c 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessList.c @@ -26,8 +26,8 @@ in the source distribution for its full text. #include "Settings.h" #include "XUtils.h" +#include "pcp/Metric.h" #include "pcp/PCPMachine.h" -#include "pcp/PCPMetric.h" #include "pcp/PCPProcess.h" @@ -49,49 +49,49 @@ void ProcessList_delete(Object* cast) { static inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_32)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_32)) return value.l; return fallback; } static inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_64)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_64)) return value.l; return fallback; } static inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U32)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U32)) return value.ul; return fallback; } static inline unsigned long long Metric_instance_u64(int metric, int pid, int offset, unsigned long long fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) return value.ull; return fallback; } static inline unsigned long long Metric_instance_time(int metric, int pid, int offset) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) return value.ull / 10; return 0; } static inline unsigned long long Metric_instance_ONE_K(int metric, int pid, int offset) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) return value.ull / ONE_K; return ULLONG_MAX; } static inline char Metric_instance_char(int metric, int pid, int offset, char fallback) { pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) { + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) { char uchar = value.cp[0]; free(value.cp); return uchar; @@ -105,7 +105,7 @@ static char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) { return name; pmAtomValue value; - if (PCPMetric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) { + if (Metric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) { Hashtable_put(this->users, uid, value.cp); name = value.cp; } @@ -139,7 +139,7 @@ static void PCPProcessList_updateInfo(PCPProcess* pp, int pid, int offset, char* Process* process = &pp->super; pmAtomValue value; - if (!PCPMetric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING)) + if (!Metric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING)) value.cp = xStrdup(""); String_safeStrncpy(command, value.cp, commLen); free(value.cp); @@ -174,7 +174,7 @@ static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigne pp->io_syscw = Metric_instance_u64(PCP_PROC_IO_SYSCW, pid, offset, ULLONG_MAX); pp->io_cancelled_write_bytes = Metric_instance_ONE_K(PCP_PROC_IO_CANCELLED, pid, offset); - if (PCPMetric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) { + if (Metric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) { unsigned long long last_read = pp->io_read_bytes; pp->io_read_bytes = value.ull / ONE_K; pp->io_rate_read_bps = ONE_K * (pp->io_read_bytes - last_read) / @@ -184,7 +184,7 @@ static void PCPProcessList_updateIO(PCPProcess* pp, int pid, int offset, unsigne pp->io_rate_read_bps = NAN; } - if (PCPMetric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) { + if (Metric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) { unsigned long long last_write = pp->io_write_bytes; pp->io_write_bytes = value.ull; pp->io_rate_write_bps = ONE_K * (pp->io_write_bytes - last_write) / @@ -226,20 +226,20 @@ static void PCPProcessList_readCtxtData(PCPProcess* pp, int pid, int offset) { pmAtomValue value; unsigned long ctxt = 0; - if (PCPMetric_instance(PCP_PROC_VCTXSW, pid, offset, &value, PM_TYPE_U32)) + if (Metric_instance(PCP_PROC_VCTXSW, pid, offset, &value, PM_TYPE_U32)) ctxt += value.ul; - if (PCPMetric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32)) + if (Metric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32)) ctxt += value.ul; pp->ctxt_diff = ctxt > pp->ctxt_total ? ctxt - pp->ctxt_total : 0; pp->ctxt_total = ctxt; } -static char* setString(PCPMetric metric, int pid, int offset, char* string) { +static char* setString(Metric metric, int pid, int offset, char* string) { if (string) free(string); pmAtomValue value; - if (PCPMetric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) string = value.cp; else string = NULL; @@ -269,7 +269,7 @@ static void PCPProcessList_updateUsername(Process* process, int pid, int offset, static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, const char* comm) { pmAtomValue value; - if (!PCPMetric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) { + if (!Metric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) { if (process->state != ZOMBIE) process->isKernelThread = true; Process_updateCmdline(process, NULL, 0, 0); @@ -303,7 +303,7 @@ static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, Process_updateComm(process, comm); - if (PCPMetric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) { + if (Metric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) { Process_updateExe(process, value.cp[0] ? value.cp : NULL); free(value.cp); } @@ -323,7 +323,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this) { int pid = -1, offset = -1; /* for every process ... */ - while (PCPMetric_iterate(PCP_PROC_PID, &pid, &offset)) { + while (Metric_iterate(PCP_PROC_PID, &pid, &offset)) { bool preExisting; Process* proc = ProcessList_getProcess(pl, pid, &preExisting, PCPProcess_new); @@ -364,7 +364,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this) { if ((flags & PROCESS_FLAG_LINUX_SMAPS) && (Process_isKernelThread(proc) == false)) { - if (PCPMetric_enabled(PCP_PROC_SMAPS_PSS)) + if (Metric_enabled(PCP_PROC_SMAPS_PSS)) PCPProcessList_updateSmaps(pp, pid, offset); } diff --git a/pcp/Platform.c b/pcp/Platform.c index 470a5f2d4..fb0447f78 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -45,11 +45,11 @@ in the source distribution for its full text. #include "linux/PressureStallMeter.h" #include "linux/ZramMeter.h" #include "linux/ZramStats.h" +#include "pcp/Metric.h" #include "pcp/PCPDynamicColumn.h" #include "pcp/PCPDynamicMeter.h" #include "pcp/PCPDynamicScreen.h" #include "pcp/PCPMachine.h" -#include "pcp/PCPMetric.h" #include "pcp/PCPProcessList.h" #include "zfs/ZfsArcMeter.h" #include "zfs/ZfsArcStats.h" @@ -288,7 +288,7 @@ int pmLookupDescs(int numpmid, pmID* pmids, pmDesc* descs) { } #endif -size_t Platform_addMetric(PCPMetric id, const char* name) { +size_t Platform_addMetric(Metric id, const char* name) { unsigned int i = (unsigned int)id; if (i >= PCP_METRIC_COUNT && i >= pcp->totalMetrics) { @@ -377,32 +377,32 @@ bool Platform_init(void) { } /* set proc.control.perclient.threads to 1 for live contexts */ - PCPMetric_enableThreads(); + Metric_enableThreads(); /* extract values needed for setup - e.g. cpu count, pid_max */ - PCPMetric_enable(PCP_PID_MAX, true); - PCPMetric_enable(PCP_BOOTTIME, true); - PCPMetric_enable(PCP_HINV_NCPU, true); - PCPMetric_enable(PCP_PERCPU_SYSTEM, true); - PCPMetric_enable(PCP_UNAME_SYSNAME, true); - PCPMetric_enable(PCP_UNAME_RELEASE, true); - PCPMetric_enable(PCP_UNAME_MACHINE, true); - PCPMetric_enable(PCP_UNAME_DISTRO, true); + Metric_enable(PCP_PID_MAX, true); + Metric_enable(PCP_BOOTTIME, true); + Metric_enable(PCP_HINV_NCPU, true); + Metric_enable(PCP_PERCPU_SYSTEM, true); + Metric_enable(PCP_UNAME_SYSNAME, true); + Metric_enable(PCP_UNAME_RELEASE, true); + Metric_enable(PCP_UNAME_MACHINE, true); + Metric_enable(PCP_UNAME_DISTRO, true); /* enable metrics for all dynamic columns (including those from dynamic screens) */ for (size_t i = pcp->columns.offset; i < pcp->columns.offset + pcp->columns.count; i++) - PCPMetric_enable(i, true); + Metric_enable(i, true); - PCPMetric_fetch(NULL); + Metric_fetch(NULL); - for (PCPMetric metric = 0; metric < PCP_PROC_PID; metric++) - PCPMetric_enable(metric, true); - PCPMetric_enable(PCP_PID_MAX, false); /* needed one time only */ - PCPMetric_enable(PCP_BOOTTIME, false); - PCPMetric_enable(PCP_UNAME_SYSNAME, false); - PCPMetric_enable(PCP_UNAME_RELEASE, false); - PCPMetric_enable(PCP_UNAME_MACHINE, false); - PCPMetric_enable(PCP_UNAME_DISTRO, false); + for (Metric metric = 0; metric < PCP_PROC_PID; metric++) + Metric_enable(metric, true); + Metric_enable(PCP_PID_MAX, false); /* needed one time only */ + Metric_enable(PCP_BOOTTIME, false); + Metric_enable(PCP_UNAME_SYSNAME, false); + Metric_enable(PCP_UNAME_RELEASE, false); + Metric_enable(PCP_UNAME_MACHINE, false); + Metric_enable(PCP_UNAME_DISTRO, false); /* first sample (fetch) performed above, save constants */ Platform_getBootTime(); @@ -444,7 +444,7 @@ void Platform_setBindings(Htop_Action* keys) { int Platform_getUptime(void) { pmAtomValue value; - if (PCPMetric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL) + if (Metric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL) return 0; return value.l; } @@ -453,7 +453,7 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) { *one = *five = *fifteen = 0.0; pmAtomValue values[3] = {0}; - if (PCPMetric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) { + if (Metric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) { *one = values[0].d; *five = values[1].d; *fifteen = values[2].d; @@ -465,7 +465,7 @@ unsigned int Platform_getMaxCPU(void) { return pcp->ncpu; pmAtomValue value; - if (PCPMetric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL) + if (Metric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL) pcp->ncpu = value.ul; else pcp->ncpu = 1; @@ -477,7 +477,7 @@ int Platform_getMaxPid(void) { return pcp->pidmax; pmAtomValue value; - if (PCPMetric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL) + if (Metric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL) return -1; pcp->pidmax = value.l; return pcp->pidmax; @@ -488,7 +488,7 @@ long long Platform_getBootTime(void) { return pcp->btime; pmAtomValue value; - if (PCPMetric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL) + if (Metric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL) pcp->btime = value.ll; return pcp->btime; } @@ -575,7 +575,7 @@ void Platform_setSwapValues(Meter* this) { } void Platform_setZramValues(Meter* this) { - int i, count = PCPMetric_instanceCount(PCP_ZRAM_CAPACITY); + int i, count = Metric_instanceCount(PCP_ZRAM_CAPACITY); if (!count) { this->total = 0; this->values[0] = 0; @@ -586,15 +586,15 @@ void Platform_setZramValues(Meter* this) { pmAtomValue* values = xCalloc(count, sizeof(pmAtomValue)); ZramStats stats = {0}; - if (PCPMetric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) { + if (Metric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) { for (i = 0; i < count; i++) stats.totalZram += values[i].ull; } - if (PCPMetric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) { + if (Metric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) { for (i = 0; i < count; i++) stats.usedZramOrig += values[i].ull; } - if (PCPMetric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) { + if (Metric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) { for (i = 0; i < count; i++) stats.usedZramComp += values[i].ull; } @@ -632,13 +632,13 @@ void Platform_getRelease(char** string) { /* first call, extract just-sampled values */ pmAtomValue sysname, release, machine, distro; - if (!PCPMetric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING)) + if (!Metric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING)) sysname.cp = NULL; - if (!PCPMetric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING)) + if (!Metric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING)) release.cp = NULL; - if (!PCPMetric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING)) + if (!Metric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING)) machine.cp = NULL; - if (!PCPMetric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING)) + if (!Metric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING)) distro.cp = NULL; size_t length = 16; /* padded for formatting characters */ @@ -686,7 +686,7 @@ void Platform_getRelease(char** string) { char* Platform_getProcessEnv(pid_t pid) { pmAtomValue value; - if (!PCPMetric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING)) + if (!Metric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING)) return NULL; return value.cp; } @@ -699,7 +699,7 @@ FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) { void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) { *ten = *sixty = *threehundred = 0; - PCPMetric metric; + Metric metric; if (String_eq(file, "cpu")) metric = PCP_PSI_CPUSOME; else if (String_eq(file, "io")) @@ -712,7 +712,7 @@ void Platform_getPressureStall(const char* file, bool some, double* ten, double* return; pmAtomValue values[3] = {0}; - if (PCPMetric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) { + if (Metric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) { *ten = values[0].d; *sixty = values[1].d; *threehundred = values[2].d; @@ -723,11 +723,11 @@ bool Platform_getDiskIO(DiskIOData* data) { memset(data, 0, sizeof(*data)); pmAtomValue value; - if (PCPMetric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL) data->totalBytesRead = value.ull; - if (PCPMetric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL) data->totalBytesWritten = value.ull; - if (PCPMetric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL) data->totalMsTimeSpend = value.ull; return true; } @@ -736,13 +736,13 @@ bool Platform_getNetworkIO(NetworkIOData* data) { memset(data, 0, sizeof(*data)); pmAtomValue value; - if (PCPMetric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL) data->bytesReceived = value.ull; - if (PCPMetric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL) data->bytesTransmitted = value.ull; - if (PCPMetric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL) data->packetsReceived = value.ull; - if (PCPMetric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL) + if (Metric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL) data->packetsTransmitted = value.ull; return true; } @@ -752,9 +752,9 @@ void Platform_getFileDescriptors(double* used, double* max) { *max = 65536; pmAtomValue value; - if (PCPMetric_values(PCP_VFS_FILES_COUNT, &value, 1, PM_TYPE_32) != NULL) + if (Metric_values(PCP_VFS_FILES_COUNT, &value, 1, PM_TYPE_32) != NULL) *used = value.l; - if (PCPMetric_values(PCP_VFS_FILES_MAX, &value, 1, PM_TYPE_32) != NULL) + if (Metric_values(PCP_VFS_FILES_MAX, &value, 1, PM_TYPE_32) != NULL) *max = value.l; } @@ -857,7 +857,7 @@ Hashtable* Platform_dynamicColumns(void) { const char* Platform_dynamicColumnName(unsigned int key) { PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); if (this) { - PCPMetric_enable(this->id, true); + Metric_enable(this->id, true); if (this->super.caption) return this->super.caption; if (this->super.heading) diff --git a/pcp/Platform.h b/pcp/Platform.h index f2e8a49d4..ce21f845b 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -36,10 +36,10 @@ in the source distribution for its full text. #include "SignalsPanel.h" #include "CommandLine.h" +#include "pcp/Metric.h" #include "pcp/PCPDynamicColumn.h" #include "pcp/PCPDynamicMeter.h" #include "pcp/PCPDynamicScreen.h" -#include "pcp/PCPMetric.h" typedef struct Platform_ { @@ -131,7 +131,7 @@ CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv); extern pmOptions opts; -size_t Platform_addMetric(PCPMetric id, const char* name); +size_t Platform_addMetric(Metric id, const char* name); void Platform_getFileDescriptors(double* used, double* max); From e557236eda3c82028bc34c00426ff9829b4a829b Mon Sep 17 00:00:00 2001 From: Nathan Scott Date: Wed, 23 Aug 2023 14:50:48 +1000 Subject: [PATCH 4/4] Fix handling of PCP process arguments with space then slash Sync up with similar code from Linux platform, so that such processes are correctly shaded and do not trip an assert in debug builds. --- pcp/PCPProcessList.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c index 7492b2c1c..69c04e221 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessList.c @@ -288,15 +288,23 @@ static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, process->isKernelThread = true; } + int tokenEnd = 0; int tokenStart = 0; + bool argSepSpace = false; + for (int i = 0; i < length; i++) { /* htop considers the next character after the last / that is before * basenameOffset, as the start of the basename in cmdline - see * Process_writeCommand */ if (command[i] == '/') tokenStart = i + 1; + /* special-case arguments for problematic situations like "find /" */ + if (command[i] <= ' ') + argSepSpace = true; } - int tokenEnd = length; + tokenEnd = length; + if (argSepSpace) + tokenStart = 0; Process_updateCmdline(process, command, tokenStart, tokenEnd); free(value.cp);