diff --git a/Action.c b/Action.c index 62cb4525..8a1a47dc 100644 --- a/Action.c +++ b/Action.c @@ -13,9 +13,10 @@ in the source distribution for its full text. #include #include +#include "CRT.h" #include "CategoriesPanel.h" #include "CommandScreen.h" -#include "CRT.h" +#include "DynamicColumn.h" #include "EnvScreen.h" #include "FunctionBar.h" #include "Hashtable.h" @@ -168,8 +169,16 @@ static Htop_Reaction actionSetSortColumn(State* st) { Panel* sortPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Sort ", "Cancel ")); Panel_setHeader(sortPanel, "Sort by"); const ProcessField* fields = st->settings->fields; + Hashtable* dynamicColumns = st->settings->dynamicColumns; for (int i = 0; fields[i]; i++) { - char* name = String_trim(Process_fields[fields[i]].name); + char* name = NULL; + if (fields[i] >= LAST_PROCESSFIELD) { + DynamicColumn* column = Hashtable_get(dynamicColumns, fields[i]); + if (column) + name = xStrdup(column->caption ? column->caption : column->name); + } else { + name = String_trim(Process_fields[fields[i]].name); + } Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i])); if (fields[i] == Settings_getActiveSortKey(st->settings)) Panel_setSelected(sortPanel, i); diff --git a/AvailableColumnsPanel.c b/AvailableColumnsPanel.c index 04e4fa58..f6c1868e 100644 --- a/AvailableColumnsPanel.c +++ b/AvailableColumnsPanel.c @@ -7,15 +7,20 @@ in the source distribution for its full text. #include "AvailableColumnsPanel.h" +#include #include #include #include #include "ColumnsPanel.h" +#include "DynamicColumn.h" #include "FunctionBar.h" +#include "Hashtable.h" #include "ListItem.h" +#include "Macros.h" #include "Object.h" #include "Process.h" +#include "ProcessList.h" #include "ProvideCurses.h" #include "XUtils.h" @@ -29,6 +34,15 @@ static void AvailableColumnsPanel_delete(Object* object) { free(this); } +static void AvailableColumnsPanel_insert(AvailableColumnsPanel* this, int at, int key) { + const char* name; + if (key >= LAST_PROCESSFIELD) + name = DynamicColumn_init(key); + else + name = Process_fields[key].name; + Panel_insert(this->columns, at, (Object*) ListItem_new(name, key)); +} + static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) { AvailableColumnsPanel* this = (AvailableColumnsPanel*) super; HandlerResult result = IGNORED; @@ -42,10 +56,9 @@ static HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) { if (!selected) break; - int key = selected->key; int at = Panel_getSelectedIndex(this->columns); - Panel_insert(this->columns, at, (Object*) ListItem_new(Process_fields[key].name, key)); - Panel_setSelected(this->columns, at+1); + AvailableColumnsPanel_insert(this, at, selected->key); + Panel_setSelected(this->columns, at + 1); ColumnsPanel_update(this->columns); result = HANDLED; break; @@ -68,14 +81,25 @@ const PanelClass AvailableColumnsPanel_class = { .eventHandler = AvailableColumnsPanel_eventHandler }; -AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns) { - 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); +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 + char description[256]; + xSnprintf(description, sizeof(description), "%s - %s", title, column->description); + Panel_add(super, (Object*) ListItem_new(description, key)); +} - Panel_setHeader(super, "Available Columns"); +// Handle DynamicColumns entries in the AvailableColumnsPanel +static void AvailableColumnsPanel_addDynamicColumns(Panel* super, Hashtable* dynamicColumns) { + assert(dynamicColumns); + Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, super); +} +// Handle remaining Platform Meter entries in the AvailableColumnsPanel +static void AvailableColumnsPanel_addPlatformColumn(Panel* super) { for (int i = 1; i < LAST_PROCESSFIELD; i++) { if (i != COMM && Process_fields[i].description) { char description[256]; @@ -83,6 +107,18 @@ AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns) { Panel_add(super, (Object*) ListItem_new(description, i)); } } +} + +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; return this; } diff --git a/AvailableColumnsPanel.h b/AvailableColumnsPanel.h index bf87dcfb..99477b6e 100644 --- a/AvailableColumnsPanel.h +++ b/AvailableColumnsPanel.h @@ -7,7 +7,9 @@ Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ +#include "Hashtable.h" #include "Panel.h" +#include "ProcessList.h" typedef struct AvailableColumnsPanel_ { @@ -17,6 +19,6 @@ typedef struct AvailableColumnsPanel_ { extern const PanelClass AvailableColumnsPanel_class; -AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns); +AvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns); #endif diff --git a/AvailableMetersPanel.c b/AvailableMetersPanel.c index 384c8864..55a45d15 100644 --- a/AvailableMetersPanel.c +++ b/AvailableMetersPanel.c @@ -14,8 +14,10 @@ in the source distribution for its full text. #include "CPUMeter.h" #include "DynamicMeter.h" #include "FunctionBar.h" +#include "Hashtable.h" #include "Header.h" #include "ListItem.h" +#include "Macros.h" #include "Meter.h" #include "MetersPanel.h" #include "Object.h" diff --git a/CategoriesPanel.c b/CategoriesPanel.c index 4ee1ad46..9adc7be5 100644 --- a/CategoriesPanel.c +++ b/CategoriesPanel.c @@ -56,7 +56,7 @@ static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) { static void CategoriesPanel_makeColumnsPage(CategoriesPanel* this) { Panel* columns = (Panel*) ColumnsPanel_new(this->settings); - Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns); + Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, this->settings->dynamicColumns); ScreenManager_add(this->scr, columns, 20); ScreenManager_add(this->scr, availableColumns, -1); } diff --git a/ColumnsPanel.c b/ColumnsPanel.c index 5382db0b..c49e8f78 100644 --- a/ColumnsPanel.c +++ b/ColumnsPanel.c @@ -11,7 +11,9 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "DynamicColumn.h" #include "FunctionBar.h" +#include "Hashtable.h" #include "ListItem.h" #include "Object.h" #include "Process.h" @@ -115,6 +117,32 @@ const PanelClass ColumnsPanel_class = { .eventHandler = ColumnsPanel_eventHandler }; +typedef struct { + Panel* super; + unsigned int id; + unsigned int offset; +} DynamicIterator; + +static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) { + const char* name; + if (key < LAST_PROCESSFIELD) { + name = Process_fields[key].name; + } else { + const DynamicColumn* column = Hashtable_get(columns, key); + assert(column); + if (!column) { + name = NULL; + } else { + name = column->caption ? column->caption : column->heading; + if (!name) + name = column->name; /* name is a mandatory field */ + } + } + if (name == NULL) + name = "- "; + Panel_add(super, (Object*) ListItem_new(name, key)); +} + ColumnsPanel* ColumnsPanel_new(Settings* settings) { ColumnsPanel* this = AllocThis(ColumnsPanel); Panel* super = (Panel*) this; @@ -125,12 +153,11 @@ ColumnsPanel* ColumnsPanel_new(Settings* settings) { this->moving = false; Panel_setHeader(super, "Active Columns"); - const ProcessField* fields = this->settings->fields; - for (; *fields; fields++) { - if (Process_fields[*fields].name) { - Panel_add(super, (Object*) ListItem_new(Process_fields[*fields].name, *fields)); - } - } + Hashtable* dynamicColumns = settings->dynamicColumns; + const ProcessField* fields = settings->fields; + for (; *fields; fields++) + ColumnsPanel_add(super, *fields, dynamicColumns); + return this; } @@ -143,7 +170,8 @@ void ColumnsPanel_update(Panel* super) { for (int i = 0; i < size; i++) { int key = ((ListItem*) Panel_get(super, i))->key; this->settings->fields[i] = key; - this->settings->flags |= Process_fields[key].flags; + if (key < LAST_PROCESSFIELD) + this->settings->flags |= Process_fields[key].flags; } this->settings->fields[size] = 0; } diff --git a/ColumnsPanel.h b/ColumnsPanel.h index 8bc806eb..39503a50 100644 --- a/ColumnsPanel.h +++ b/ColumnsPanel.h @@ -10,6 +10,7 @@ in the source distribution for its full text. #include #include "Panel.h" +#include "ProcessList.h" #include "Settings.h" diff --git a/CommandLine.c b/CommandLine.c index d932bb04..b4144d8d 100644 --- a/CommandLine.c +++ b/CommandLine.c @@ -22,6 +22,7 @@ in the source distribution for its full text. #include "Action.h" #include "CRT.h" +#include "DynamicColumn.h" #include "DynamicMeter.h" #include "Hashtable.h" #include "Header.h" @@ -292,10 +293,14 @@ int CommandLine_run(const char* name, int argc, char** argv) { Process_setupColumnWidths(); UsersTable* ut = UsersTable_new(); + Hashtable* dc = DynamicColumns_new(); Hashtable* dm = DynamicMeters_new(); - ProcessList* pl = ProcessList_new(ut, dm, flags.pidMatchList, flags.userId); + if (!dc) + dc = Hashtable_new(0, true); - Settings* settings = Settings_new(pl->activeCPUs); + ProcessList* pl = ProcessList_new(ut, dm, dc, flags.pidMatchList, flags.userId); + + Settings* settings = Settings_new(pl->activeCPUs, dc); pl->settings = settings; Header* header = Header_new(pl, settings, 2); @@ -384,8 +389,12 @@ int CommandLine_run(const char* name, int argc, char** argv) { if (flags.pidMatchList) Hashtable_delete(flags.pidMatchList); - /* Delete Settings last, since it can get accessed in the crash handler */ + /* Delete these last, since they can get accessed in the crash handler */ Settings_delete(settings); + if (dc) + Hashtable_delete(dc); + if (dm) + Hashtable_delete(dm); return 0; } diff --git a/DynamicColumn.c b/DynamicColumn.c new file mode 100644 index 00000000..62b38238 --- /dev/null +++ b/DynamicColumn.c @@ -0,0 +1,60 @@ +/* +htop - DynamicColumn.c +(C) 2021 Sohaib Mohammed +(C) 2021 htop dev team +(C) 2021 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 "DynamicColumn.h" + +#include + +#include "Platform.h" +#include "ProcessList.h" +#include "RichString.h" +#include "XUtils.h" + + +Hashtable* DynamicColumns_new(void) { + return Platform_dynamicColumns(); +} + +const char* DynamicColumn_init(unsigned int key) { + return Platform_dynamicColumnInit(key); +} + +typedef struct { + const char* name; + const DynamicColumn* data; + unsigned int key; +} DynamicIterator; + +static void DynamicColumn_compare(ht_key_t key, void* value, void* data) { + const DynamicColumn* column = (const DynamicColumn*)value; + DynamicIterator* iter = (DynamicIterator*)data; + if (String_eq(iter->name, column->name)) { + iter->data = column; + iter->key = key; + } +} + +const DynamicColumn* DynamicColumn_search(Hashtable* dynamics, const char* name, unsigned int* key) { + DynamicIterator iter = { .key = 0, .data = NULL, .name = name }; + if (dynamics) + Hashtable_foreach(dynamics, DynamicColumn_compare, &iter); + if (key) + *key = iter.key; + return iter.data; +} + +const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key) { + return (const DynamicColumn*) Hashtable_get(dynamics, key); +} + +bool DynamicColumn_writeField(const Process* proc, RichString* str, unsigned int key) { + return Platform_dynamicColumnWriteField(proc, str, key); +} diff --git a/DynamicColumn.h b/DynamicColumn.h new file mode 100644 index 00000000..464ca668 --- /dev/null +++ b/DynamicColumn.h @@ -0,0 +1,31 @@ +#ifndef HEADER_DynamicColumn +#define HEADER_DynamicColumn + +#include "Hashtable.h" +#include "Process.h" +#include "ProcessList.h" +#include "RichString.h" + + +#define DYNAMIC_MAX_COLUMN_WIDTH 28 +#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 */ +} DynamicColumn; + +Hashtable* DynamicColumns_new(void); + +const char* DynamicColumn_init(unsigned int key); + +const DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key); + +const DynamicColumn* DynamicColumn_search(Hashtable* dynamics, const char* name, unsigned int* field); + +bool DynamicColumn_writeField(const Process* proc, RichString* str, unsigned int key); + +#endif diff --git a/DynamicMeter.h b/DynamicMeter.h index 582bc277..f787cbb7 100644 --- a/DynamicMeter.h +++ b/DynamicMeter.h @@ -1,6 +1,8 @@ #ifndef HEADER_DynamicMeter #define HEADER_DynamicMeter +#include + #include "Hashtable.h" #include "Meter.h" @@ -11,8 +13,6 @@ typedef struct DynamicMeter_ { char* description; unsigned int type; double maximum; - - void* dynamicData; /* platform-specific meter data */ } DynamicMeter; Hashtable* DynamicMeters_new(void); diff --git a/Makefile.am b/Makefile.am index 1e027c6a..fc19e7c5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,6 +39,7 @@ myhtopsources = \ DateTimeMeter.c \ DiskIOMeter.c \ DisplayOptionsPanel.c \ + DynamicColumn.c \ DynamicMeter.c \ EnvScreen.c \ FunctionBar.c \ @@ -94,6 +95,7 @@ myhtopheaders = \ DateTimeMeter.h \ DiskIOMeter.h \ DisplayOptionsPanel.h \ + DynamicColumn.h \ DynamicMeter.h \ EnvScreen.h \ FunctionBar.h \ @@ -359,25 +361,27 @@ endif # -------------------------- pcp_platform_headers = \ + linux/PressureStallMeter.h \ + linux/ZramMeter.h \ + linux/ZramStats.h \ + pcp/PCPDynamicColumn.h \ pcp/PCPDynamicMeter.h \ pcp/PCPProcess.h \ pcp/PCPProcessList.h \ pcp/Platform.h \ pcp/ProcessField.h \ - linux/PressureStallMeter.h \ - linux/ZramMeter.h \ - linux/ZramStats.h \ zfs/ZfsArcMeter.h \ zfs/ZfsArcStats.h \ zfs/ZfsCompressedArcMeter.h pcp_platform_sources = \ + linux/PressureStallMeter.c \ + linux/ZramMeter.c \ + pcp/PCPDynamicColumn.c \ pcp/PCPDynamicMeter.c \ pcp/PCPProcess.c \ pcp/PCPProcessList.c \ pcp/Platform.c \ - linux/PressureStallMeter.c \ - linux/ZramMeter.c \ zfs/ZfsArcMeter.c \ zfs/ZfsCompressedArcMeter.c diff --git a/Process.c b/Process.c index dfb3f7ba..fbc9f732 100644 --- a/Process.c +++ b/Process.c @@ -26,6 +26,7 @@ in the source distribution for its full text. #include "Macros.h" #include "Platform.h" #include "ProcessList.h" +#include "DynamicColumn.h" #include "RichString.h" #include "Settings.h" #include "XUtils.h" @@ -905,8 +906,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field xSnprintf(buffer, n, "%-9d ", this->st_uid); break; default: + if (DynamicColumn_writeField(this, str, field)) + return; assert(0 && "Process_writeField: default key reached"); /* should never be reached */ xSnprintf(buffer, n, "- "); + break; } RichString_appendAscii(str, attr, buffer); } diff --git a/Process.h b/Process.h index 6e69f9c3..c3166927 100644 --- a/Process.h +++ b/Process.h @@ -56,6 +56,7 @@ typedef enum ProcessField_ { /* 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; @@ -267,7 +268,7 @@ typedef struct ProcessFieldData_ { /* Scan flag to enable scan-method otherwise not run */ uint32_t flags; - /* Whether the values are process identifies; adjusts the width of title and values if true */ + /* Whether the values are process identifiers; adjusts the width of title and values if true */ bool pidColumn; /* Whether the column should be sorted in descending order by default */ diff --git a/ProcessList.c b/ProcessList.c index 8ab4d8f6..daf871a7 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include "CRT.h" +#include "DynamicColumn.h" #include "Hashtable.h" #include "Macros.h" #include "Platform.h" @@ -19,7 +20,7 @@ in the source distribution for its full text. #include "XUtils.h" -ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { this->processes = Vector_new(klass, true, DEFAULT_SIZE); this->processes2 = Vector_new(klass, true, DEFAULT_SIZE); // tree-view auxiliary buffer @@ -30,6 +31,7 @@ ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, Users this->usersTable = usersTable; this->pidMatchList = pidMatchList; this->dynamicMeters = dynamicMeters; + this->dynamicColumns = dynamicColumns; this->userId = userId; @@ -83,7 +85,22 @@ void ProcessList_setPanel(ProcessList* this, Panel* panel) { this->panel = panel; } -static const char* alignedProcessFieldTitle(ProcessField field) { +static const char* alignedDynamicColumnTitle(const ProcessList* this, int key) { + const DynamicColumn* column = Hashtable_get(this->dynamicColumns, key); + if (column == NULL) + return "- "; + static char titleBuffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1]; + int width = column->width; + if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) + width = DYNAMIC_DEFAULT_COLUMN_WIDTH; + xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s", width, column->heading); + return titleBuffer; +} + +static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessField field) { + if (field >= LAST_PROCESSFIELD) + return alignedDynamicColumnTitle(this, field); + const char* title = Process_fields[field].title; if (!title) return "- "; @@ -115,7 +132,7 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header) { color = CRT_colors[PANEL_HEADER_FOCUS]; } - RichString_appendWide(header, color, alignedProcessFieldTitle(fields[i])); + RichString_appendWide(header, color, alignedProcessFieldTitle(this, fields[i])); if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') { RichString_rewind(header, 1); // rewind to override space RichString_appendnWide(header, @@ -478,7 +495,7 @@ ProcessField ProcessList_keyAt(const ProcessList* this, int at) { const ProcessField* fields = this->settings->fields; ProcessField field; for (int i = 0; (field = fields[i]); i++) { - int len = strlen(alignedProcessFieldTitle(field)); + int len = strlen(alignedProcessFieldTitle(this, field)); if (at >= x && at <= x + len) { return field; } diff --git a/ProcessList.h b/ProcessList.h index 93958cbb..92e8f1b5 100644 --- a/ProcessList.h +++ b/ProcessList.h @@ -51,6 +51,7 @@ typedef struct ProcessList_ { Hashtable* draftingTreeSet; Hashtable* dynamicMeters; /* runtime-discovered meters */ + Hashtable* dynamicColumns; /* runtime-discovered Columns */ struct timeval realtime; /* time of the current sample */ uint64_t realtimeMs; /* current time in milliseconds */ @@ -88,13 +89,13 @@ typedef struct ProcessList_ { } ProcessList; /* Implemented by platforms */ -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* pl); void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate); bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id); -ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_done(ProcessList* this); diff --git a/Settings.c b/Settings.c index 1cf67395..569d5450 100644 --- a/Settings.c +++ b/Settings.c @@ -8,12 +8,14 @@ in the source distribution for its full text. #include "Settings.h" #include +#include #include #include #include #include #include "CRT.h" +#include "DynamicColumn.h" #include "Macros.h" #include "Meter.h" #include "Platform.h" @@ -106,22 +108,42 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) this->columns[1].modes[r++] = TEXT_METERMODE; } -static void readFields(ProcessField* fields, uint32_t* flags, const char* line) { +static void Settings_readFields(Settings* settings, const char* line) { char* trim = String_trim(line); char** ids = String_split(trim, ' ', NULL); free(trim); - int i, j; - *flags = 0; - for (j = 0, i = 0; i < LAST_PROCESSFIELD && ids[i]; i++) { + + settings->flags = 0; + + unsigned int i, j; + for (j = 0, i = 0; ids[i]; i++) { + if (j >= UINT_MAX / sizeof(ProcessField)) + continue; + if (j >= LAST_PROCESSFIELD) { + settings->fields = xRealloc(settings->fields, j * sizeof(ProcessField)); + memset(&settings->fields[j], 0, sizeof(ProcessField)); + } + + // Dynamically-defined columns are always stored by-name. + char* end, dynamic[32] = {0}; + if (sscanf(ids[i], "Dynamic(%30s)", dynamic)) { + if ((end = strrchr(dynamic, ')')) == NULL) + continue; + *end = '\0'; + unsigned int key; + if (!DynamicColumn_search(settings->dynamicColumns, dynamic, &key)) + continue; + settings->fields[j++] = key; + continue; + } // This "+1" is for compatibility with the older enum format. int id = atoi(ids[i]) + 1; if (id > 0 && id < LAST_PROCESSFIELD && Process_fields[id].name) { - fields[j] = id; - *flags |= Process_fields[id].flags; - j++; + settings->flags |= Process_fields[id].flags; + settings->fields[j++] = id; } } - fields[j] = NULL_PROCESSFIELD; + settings->fields[j] = NULL_PROCESSFIELD; String_freeArray(ids); } @@ -145,7 +167,7 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini continue; } if (String_eq(option[0], "fields")) { - readFields(this->fields, &(this->flags), option[1]); + Settings_readFields(this, option[1]); didReadFields = true; } else if (String_eq(option[0], "sort_key")) { // This "+1" is for compatibility with the older enum format. @@ -256,12 +278,17 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini return didReadFields; } -static void writeFields(FILE* fd, const ProcessField* fields, const char* name) { +static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, const char* name) { fprintf(fd, "%s=", name); const char* sep = ""; - for (int i = 0; fields[i]; i++) { - // This "-1" is for compatibility with the older enum format. - fprintf(fd, "%s%d", sep, (int) fields[i] - 1); + for (unsigned int i = 0; fields[i]; i++) { + if (fields[i] >= LAST_PROCESSFIELD) { + const DynamicColumn* column = DynamicColumn_lookup(columns, fields[i]); + fprintf(fd, "%sDynamic(%s)", sep, column->name); + } else { + // This "-1" is for compatibility with the older enum format. + fprintf(fd, "%s%d", sep, (int) fields[i] - 1); + } sep = " "; } fprintf(fd, "\n"); @@ -299,7 +326,7 @@ int Settings_write(const Settings* this, bool onCrash) { fprintf(fd, "# Beware! This file is rewritten by htop when settings are changed in the interface.\n"); fprintf(fd, "# The parser is also very primitive, and not human-friendly.\n"); } - writeFields(fd, this->fields, "fields"); + writeFields(fd, this->fields, this->dynamicColumns, "fields"); // This "-1" is for compatibility with the older enum format. fprintf(fd, "sort_key=%d\n", (int) this->sortKey - 1); fprintf(fd, "sort_direction=%d\n", (int) this->direction); @@ -361,9 +388,10 @@ int Settings_write(const Settings* this, bool onCrash) { return r; } -Settings* Settings_new(unsigned int initialCpuCount) { +Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns) { Settings* this = xCalloc(1, sizeof(Settings)); + this->dynamicColumns = dynamicColumns; this->sortKey = PERCENT_CPU; this->treeSortKey = PID; this->direction = -1; diff --git a/Settings.h b/Settings.h index 6737f3bb..97ef58a0 100644 --- a/Settings.h +++ b/Settings.h @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include +#include "Hashtable.h" #include "Process.h" @@ -26,6 +27,7 @@ typedef struct { typedef struct Settings_ { char* filename; MeterColumnSettings columns[2]; + Hashtable* dynamicColumns; ProcessField* fields; uint32_t flags; @@ -92,7 +94,7 @@ void Settings_delete(Settings* this); int Settings_write(const Settings* this, bool onCrash); -Settings* Settings_new(unsigned int initialCpuCount); +Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns); void Settings_invertSortOrder(Settings* this); diff --git a/darwin/DarwinProcessList.c b/darwin/DarwinProcessList.c index 8d14efe1..60f8a7ca 100644 --- a/darwin/DarwinProcessList.c +++ b/darwin/DarwinProcessList.c @@ -128,10 +128,10 @@ static struct kinfo_proc* ProcessList_getKInfoProcs(size_t* count) { CRT_fatalError("Unable to get kinfo_procs"); } -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { DarwinProcessList* this = xCalloc(1, sizeof(DarwinProcessList)); - ProcessList_init(&this->super, Class(DarwinProcess), usersTable, dynamicMeters, pidMatchList, userId); + ProcessList_init(&this->super, Class(DarwinProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); /* Initialize the CPU information */ this->super.activeCPUs = ProcessList_allocateCPULoadInfo(&this->prev_load); diff --git a/darwin/DarwinProcessList.h b/darwin/DarwinProcessList.h index 24259d3e..af1140b3 100644 --- a/darwin/DarwinProcessList.h +++ b/darwin/DarwinProcessList.h @@ -28,7 +28,7 @@ typedef struct DarwinProcessList_ { ZfsArcStats zfs; } DarwinProcessList; -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* this); diff --git a/darwin/Platform.h b/darwin/Platform.h index 43a868f9..b1733a56 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -15,6 +15,7 @@ in the source distribution for its full text. #include "BatteryMeter.h" #include "CPUMeter.h" #include "DiskIOMeter.h" +#include "Hashtable.h" #include "NetworkIOMeter.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" @@ -92,9 +93,7 @@ static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) void Platform_gettime_monotonic(uint64_t* msec); -static inline Hashtable* Platform_dynamicMeters(void) { - return NULL; -} +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } @@ -102,4 +101,10 @@ static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } + #endif diff --git a/dragonflybsd/DragonFlyBSDProcessList.c b/dragonflybsd/DragonFlyBSDProcessList.c index 9b1bb41f..08e3d7b6 100644 --- a/dragonflybsd/DragonFlyBSDProcessList.c +++ b/dragonflybsd/DragonFlyBSDProcessList.c @@ -42,12 +42,12 @@ static int MIB_kern_cp_time[2]; static int MIB_kern_cp_times[2]; static int kernelFScale; -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { size_t len; char errbuf[_POSIX2_LINE_MAX]; DragonFlyBSDProcessList* dfpl = xCalloc(1, sizeof(DragonFlyBSDProcessList)); ProcessList* pl = (ProcessList*) dfpl; - ProcessList_init(pl, Class(DragonFlyBSDProcess), usersTable, dynamicMeters, pidMatchList, userId); + ProcessList_init(pl, Class(DragonFlyBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); // physical memory in system: hw.physmem // physical page size: hw.pagesize diff --git a/dragonflybsd/DragonFlyBSDProcessList.h b/dragonflybsd/DragonFlyBSDProcessList.h index 626d2b24..8a5b31c6 100644 --- a/dragonflybsd/DragonFlyBSDProcessList.h +++ b/dragonflybsd/DragonFlyBSDProcessList.h @@ -53,7 +53,7 @@ typedef struct DragonFlyBSDProcessList_ { Hashtable* jails; } DragonFlyBSDProcessList; -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* this); diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h index 346e299b..85b70ab2 100644 --- a/dragonflybsd/Platform.h +++ b/dragonflybsd/Platform.h @@ -16,6 +16,7 @@ in the source distribution for its full text. #include "Action.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" +#include "Hashtable.h" #include "Macros.h" #include "Meter.h" #include "NetworkIOMeter.h" @@ -89,9 +90,7 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) { Generic_gettime_monotonic(msec); } -static inline Hashtable* Platform_dynamicMeters(void) { - return NULL; -} +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } @@ -99,4 +98,10 @@ static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } + #endif diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessList.c index 8f8560a2..48c0648d 100644 --- a/freebsd/FreeBSDProcessList.c +++ b/freebsd/FreeBSDProcessList.c @@ -56,12 +56,12 @@ static int MIB_kern_cp_time[2]; static int MIB_kern_cp_times[2]; static int kernelFScale; -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* DynamicColumns, Hashtable* pidMatchList, uid_t userId) { size_t len; char errbuf[_POSIX2_LINE_MAX]; FreeBSDProcessList* fpl = xCalloc(1, sizeof(FreeBSDProcessList)); ProcessList* pl = (ProcessList*) fpl; - ProcessList_init(pl, Class(FreeBSDProcess), usersTable, dynamicMeters, pidMatchList, userId); + ProcessList_init(pl, Class(FreeBSDProcess), usersTable, dynamicMeters, DynamicColumns, pidMatchList, userId); // physical memory in system: hw.physmem // physical page size: hw.pagesize diff --git a/freebsd/FreeBSDProcessList.h b/freebsd/FreeBSDProcessList.h index 7efcda92..ae822055 100644 --- a/freebsd/FreeBSDProcessList.h +++ b/freebsd/FreeBSDProcessList.h @@ -47,7 +47,7 @@ typedef struct FreeBSDProcessList_ { } FreeBSDProcessList; -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* this); diff --git a/freebsd/Platform.h b/freebsd/Platform.h index 8c962929..9bc72c7b 100644 --- a/freebsd/Platform.h +++ b/freebsd/Platform.h @@ -13,6 +13,7 @@ in the source distribution for its full text. #include "Action.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" +#include "Hashtable.h" #include "Meter.h" #include "NetworkIOMeter.h" #include "Process.h" @@ -89,9 +90,7 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) { Generic_gettime_monotonic(msec); } -static inline Hashtable* Platform_dynamicMeters(void) { - return NULL; -} +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } @@ -99,4 +98,10 @@ static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } + #endif diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index 3556669d..3ad4e2f0 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -240,11 +240,11 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) { super->existingCPUs = currExisting; } -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { LinuxProcessList* this = xCalloc(1, sizeof(LinuxProcessList)); ProcessList* pl = &(this->super); - ProcessList_init(pl, Class(LinuxProcess), usersTable, dynamicMeters, pidMatchList, userId); + ProcessList_init(pl, Class(LinuxProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); LinuxProcessList_initTtyDrivers(this); // Initialize page size diff --git a/linux/LinuxProcessList.h b/linux/LinuxProcessList.h index 0d1ad48c..a5640e2b 100644 --- a/linux/LinuxProcessList.h +++ b/linux/LinuxProcessList.h @@ -115,7 +115,7 @@ typedef struct LinuxProcessList_ { #define PROC_LINE_LENGTH 4096 #endif -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* pl); diff --git a/linux/Platform.h b/linux/Platform.h index 9729efca..47a30af5 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -17,6 +17,7 @@ in the source distribution for its full text. #include "Action.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" +#include "Hashtable.h" #include "Meter.h" #include "NetworkIOMeter.h" #include "Process.h" @@ -105,9 +106,7 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) { Generic_gettime_monotonic(msec); } -static inline Hashtable* Platform_dynamicMeters(void) { - return NULL; -} +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } @@ -115,4 +114,10 @@ static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } + #endif diff --git a/netbsd/Platform.h b/netbsd/Platform.h index e9ec2f08..4ba4c84b 100644 --- a/netbsd/Platform.h +++ b/netbsd/Platform.h @@ -94,9 +94,7 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) { Generic_gettime_monotonic(msec); } -static inline Hashtable* Platform_dynamicMeters(void) { - return NULL; -} +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } @@ -104,4 +102,10 @@ static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } + #endif diff --git a/openbsd/OpenBSDProcessList.c b/openbsd/OpenBSDProcessList.c index 089ca21d..cb473956 100644 --- a/openbsd/OpenBSDProcessList.c +++ b/openbsd/OpenBSDProcessList.c @@ -94,14 +94,14 @@ static void OpenBSDProcessList_updateCPUcount(ProcessList* super) { } -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { const int fmib[] = { CTL_KERN, KERN_FSCALE }; size_t size; char errbuf[_POSIX2_LINE_MAX]; OpenBSDProcessList* opl = xCalloc(1, sizeof(OpenBSDProcessList)); ProcessList* pl = (ProcessList*) opl; - ProcessList_init(pl, Class(OpenBSDProcess), usersTable, dynamicMeters, pidMatchList, userId); + ProcessList_init(pl, Class(OpenBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); OpenBSDProcessList_updateCPUcount(pl); diff --git a/openbsd/OpenBSDProcessList.h b/openbsd/OpenBSDProcessList.h index 0a47773f..52457051 100644 --- a/openbsd/OpenBSDProcessList.h +++ b/openbsd/OpenBSDProcessList.h @@ -49,7 +49,7 @@ typedef struct OpenBSDProcessList_ { } OpenBSDProcessList; -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* this); diff --git a/openbsd/Platform.h b/openbsd/Platform.h index fb3b909b..3e2ec48a 100644 --- a/openbsd/Platform.h +++ b/openbsd/Platform.h @@ -14,6 +14,7 @@ in the source distribution for its full text. #include "Action.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" +#include "Hashtable.h" #include "Meter.h" #include "NetworkIOMeter.h" #include "Process.h" @@ -87,9 +88,7 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) { Generic_gettime_monotonic(msec); } -static inline Hashtable* Platform_dynamicMeters(void) { - return NULL; -} +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } @@ -97,4 +96,10 @@ static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } + #endif diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c new file mode 100644 index 00000000..2848490e --- /dev/null +++ b/pcp/PCPDynamicColumn.c @@ -0,0 +1,326 @@ +/* +htop - PCPDynamicColumn.c +(C) 2021 Sohaib Mohammed +(C) 2021 htop dev team +(C) 2021 Red Hat, Inc. +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/PCPDynamicColumn.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "Macros.h" +#include "Platform.h" +#include "Process.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/PCPProcess.h" + + +static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) { + if (!column->super.name[0]) + return false; + + size_t bytes = 16 + strlen(column->super.name); + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.column.%s", column->super.name); + + column->metricName = metricName; + column->id = columns->offset + columns->cursor; + columns->cursor++; + + Platform_addMetric(column->id, metricName); + return true; +} + +static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) { + /* lookup a dynamic metric with this name, else create */ + if (PCPDynamicColumn_addMetric(columns, column) == false) + return; + + /* derived metrics in all dynamic columns for simplicity */ + 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); + } +} + +// Ensure a valid name for use in a PCP metric name and in htoprc +static bool PCPDynamicColumn_validateColumnName(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 column 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 column name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +// Ensure a column name has not been defined previously +static bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) { + return DynamicColumn_search(columns->table, key, NULL) == NULL; +} + +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)); + + size_t id = columns->count + LAST_PROCESSFIELD; + Hashtable_put(columns->table, id, column); + columns->count++; + + return column; +} + +static void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicColumn* column = 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 heading - i.e. new column */ + ok = PCPDynamicColumn_validateColumnName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicColumn_uniqueName(key + 1, columns); + if (ok) + column = PCPDynamicColumn_new(columns, key + 1); + } else if (value && column && String_eq(key, "caption")) { + free_and_xStrdup(&column->super.caption, value); + } else if (value && column && String_eq(key, "heading")) { + free_and_xStrdup(&column->super.heading, value); + } else if (value && column && String_eq(key, "description")) { + 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, "metric")) { + PCPDynamicColumn_parseMetric(columns, column, path, lineno, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicColumn_scanDir(PCPDynamicColumns* columns, 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); + PCPDynamicColumn_parseFile(columns, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicColumns_init(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; + + columns->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/columns/"); + else if (home) + path = String_cat(home, "/.config/htop/columns/"); + else + path = NULL; + if (path) { + PCPDynamicColumn_scanDir(columns, path); + free(path); + } + + /* next, search in the system columns directory */ + path = String_cat(sysconf, "/htop/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); + + /* next, try the readonly system columns directory */ + path = String_cat(share, "/htop/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); +} + +void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) { + const PCPProcess* pp = (const PCPProcess*) proc; + unsigned int type = Metric_type(this->id); + + pmAtomValue atom; + if (!Metric_instance(this->id, proc->pid, pp->offset, &atom, type)) { + RichString_appendAscii(str, CRT_colors[METER_VALUE_ERROR], "no data"); + 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; + } + + char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /* space */ 1 + /* null terminator */ + 1]; + int attr = CRT_colors[DEFAULT_COLOR]; + switch (type) { + case PM_TYPE_STRING: + attr = CRT_colors[PROCESS_SHADOW]; + Process_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); + break; + case PM_TYPE_U32: + xSnprintf(buffer, sizeof(buffer), "%*u ", width, atom.ul); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_64: + xSnprintf(buffer, sizeof(buffer), "%*lld ", width, (long long) atom.ll); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_U64: + xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long) atom.ull); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_FLOAT: + xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, (double) atom.f); + RichString_appendAscii(str, attr, buffer); + break; + case PM_TYPE_DOUBLE: + xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, atom.d); + RichString_appendAscii(str, attr, buffer); + break; + default: + attr = CRT_colors[METER_VALUE_ERROR]; + RichString_appendAscii(str, attr, "no type"); + break; + } +} + +int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) { + const PCPDynamicColumn* column = Hashtable_get(p1->super.processList->dynamicColumns, key); + + size_t metric = column->id; + unsigned int type = Metric_type(metric); + + pmAtomValue atom1 = {0}, atom2 = {0}; + if (!Metric_instance(metric, p1->super.pid, p1->offset, &atom1, type) || + !Metric_instance(metric, p2->super.pid, p2->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 -1; +} diff --git a/pcp/PCPDynamicColumn.h b/pcp/PCPDynamicColumn.h new file mode 100644 index 00000000..39f79358 --- /dev/null +++ b/pcp/PCPDynamicColumn.h @@ -0,0 +1,32 @@ +#ifndef HEADER_PCPDynamicColumn +#define HEADER_PCPDynamicColumn + +#include "CRT.h" +#include "DynamicColumn.h" +#include "Hashtable.h" +#include "Process.h" +#include "RichString.h" + +#include "pcp/PCPProcess.h" + + +typedef struct PCPDynamicColumn_ { + DynamicColumn super; + char* metricName; + size_t id; /* identifier for metric array lookups */ +} PCPDynamicColumn; + +typedef struct PCPDynamicColumns_ { + Hashtable* table; + size_t count; /* count of dynamic meters discovered by scan */ + size_t offset; /* start offset into the Platform metric array */ + size_t cursor; /* identifier allocator for each new metric used */ +} PCPDynamicColumns; + +void PCPDynamicColumns_init(PCPDynamicColumns* columns); + +void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str); + +int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key); + +#endif diff --git a/pcp/PCPDynamicMeter.c b/pcp/PCPDynamicMeter.c index ffde2b4f..b90511ec 100644 --- a/pcp/PCPDynamicMeter.c +++ b/pcp/PCPDynamicMeter.c @@ -1,7 +1,7 @@ /* htop - PCPDynamicMeter.c (C) 2021 htop dev team -(C) 2021 Red Hat, Inc. All Rights Reserved. +(C) 2021 Red Hat, Inc. Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ @@ -18,13 +18,14 @@ in the source distribution for its full text. #include "Settings.h" #include "XUtils.h" + static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) { - size_t bytes = 8 + strlen(meter->super.name) + strlen(name); + size_t bytes = 16 + strlen(meter->super.name) + strlen(name); char* metricName = xMalloc(bytes); - xSnprintf(metricName, bytes, "htop.%s.%s", meter->super.name, name); + xSnprintf(metricName, bytes, "htop.meter.%s.%s", meter->super.name, name); PCPDynamicMetric* metric; - for (unsigned int i = 0; i < meter->totalMetrics; i++) { + for (size_t i = 0; i < meter->totalMetrics; i++) { metric = &meter->metrics[i]; if (String_eq(metric->name, metricName)) { free(metricName); @@ -33,7 +34,7 @@ static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, } /* not an existing metric in this meter - add it */ - unsigned int n = meter->totalMetrics + 1; + size_t n = meter->totalMetrics + 1; meter->metrics = xReallocArray(meter->metrics, n, sizeof(PCPDynamicMetric)); meter->totalMetrics = n; metric = &meter->metrics[n - 1]; @@ -139,13 +140,8 @@ static bool PCPDynamicMeter_validateMeterName(char* key, const char* path, unsig } // Ensure a meter name has not been defined previously -static bool PCPDynamicMeter_uniqueName(char* key, const char* path, unsigned int line, PCPDynamicMeters* meters) { - if (DynamicMeter_search(meters->table, key, NULL) == false) - return true; - - fprintf(stderr, "%s: duplicate name at %s line %u: \"%s\", ignored\n", - pmGetProgname(), path, line, key); - return false; +static bool PCPDynamicMeter_uniqueName(char* key, PCPDynamicMeters* meters) { + return !DynamicMeter_search(meters->table, key, NULL); } static PCPDynamicMeter* PCPDynamicMeter_new(PCPDynamicMeters* meters, const char* name) { @@ -188,7 +184,7 @@ static void PCPDynamicMeter_parseFile(PCPDynamicMeters* meters, const char* path if (key[0] == '[') { /* new section heading - i.e. new meter */ ok = PCPDynamicMeter_validateMeterName(key + 1, path, lineno); if (ok) - ok = PCPDynamicMeter_uniqueName(key + 1, path, lineno, meters); + ok = PCPDynamicMeter_uniqueName(key + 1, meters); if (ok) meter = PCPDynamicMeter_new(meters, key + 1); } else if (!ok) { @@ -241,40 +237,47 @@ static void PCPDynamicMeter_scanDir(PCPDynamicMeters* meters, char* path) { } void PCPDynamicMeters_init(PCPDynamicMeters* meters) { + 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; meters->table = Hashtable_new(0, true); - /* search in the users home directory first of all */ - if (xdgConfigHome) { - path = String_cat(xdgConfigHome, "/htop/meters/"); - } else { - if (!home) - home = ""; - path = String_cat(home, "/.config/htop/meters/"); + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/meters/"); + PCPDynamicMeter_scanDir(meters, path); + free(path); } - PCPDynamicMeter_scanDir(meters, path); - free(path); - /* secondly search in the system meters directory */ + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/meters/"); + else if (home) + path = String_cat(home, "/.config/htop/meters/"); + else + path = NULL; + if (path) { + PCPDynamicMeter_scanDir(meters, path); + free(path); + } + + /* next, search in the system meters directory */ path = String_cat(sysconf, "/htop/meters/"); PCPDynamicMeter_scanDir(meters, path); free(path); - /* check the working directory, as a final option */ - char cwd[PATH_MAX]; - if (getcwd(cwd, sizeof(cwd)) != NULL) { - path = String_cat(cwd, "/pcp/meters/"); - PCPDynamicMeter_scanDir(meters, path); - free(path); - } + /* next, try the readonly system meters directory */ + path = String_cat(share, "/htop/meters/"); + PCPDynamicMeter_scanDir(meters, path); + free(path); } void PCPDynamicMeter_enable(PCPDynamicMeter* this) { - for (unsigned int i = 0; i < this->totalMetrics; i++) + for (size_t i = 0; i < this->totalMetrics; i++) Metric_enable(this->metrics[i].id, true); } @@ -283,7 +286,7 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { size_t size = sizeof(meter->txtBuffer); size_t bytes = 0; - for (unsigned int i = 0; i < this->totalMetrics; i++) { + for (size_t i = 0; i < this->totalMetrics; i++) { if (i > 0 && bytes < size - 1) buffer[bytes++] = '/'; /* separator */ @@ -357,7 +360,7 @@ void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* meter, RichString* out) { int nodata = 1; - for (unsigned int i = 0; i < this->totalMetrics; i++) { + for (size_t i = 0; i < this->totalMetrics; i++) { PCPDynamicMetric* metric = &this->metrics[i]; const pmDesc* desc = Metric_desc(metric->id); pmAtomValue atom, raw; diff --git a/pcp/PCPDynamicMeter.h b/pcp/PCPDynamicMeter.h index a7390e61..cfe488e5 100644 --- a/pcp/PCPDynamicMeter.h +++ b/pcp/PCPDynamicMeter.h @@ -4,25 +4,26 @@ #include "CRT.h" #include "DynamicMeter.h" -typedef struct { - unsigned int id; /* index into metric array */ + +typedef struct PCPDynamicMetric_ { + size_t id; /* index into metric array */ ColorElements color; char* name; /* derived metric name */ char* label; char* suffix; } PCPDynamicMetric; -typedef struct { +typedef struct PCPDynamicMeter_ { DynamicMeter super; PCPDynamicMetric* metrics; - unsigned int totalMetrics; + size_t totalMetrics; } PCPDynamicMeter; -typedef struct { +typedef struct PCPDynamicMeters_ { Hashtable* table; - unsigned int count; /* count of dynamic meters discovered by scan */ - unsigned int offset; /* start offset into the Platform metric array */ - unsigned int cursor; /* identifier allocator for each new metric used */ + size_t count; /* count of dynamic meters discovered by scan */ + size_t offset; /* start offset into the Platform metric array */ + size_t cursor; /* identifier allocator for each new metric used */ } PCPDynamicMeters; void PCPDynamicMeters_init(PCPDynamicMeters* meters); diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c index de709110..5407a021 100644 --- a/pcp/PCPProcess.c +++ b/pcp/PCPProcess.c @@ -1,8 +1,8 @@ /* htop - PCPProcess.c (C) 2014 Hisham H. Muhammad -(C) 2020 htop dev team -(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ @@ -12,15 +12,18 @@ in the source distribution for its full text. #include #include #include -#include -#include -#include #include "CRT.h" +#include "Macros.h" +#include "Platform.h" #include "Process.h" #include "ProvideCurses.h" +#include "RichString.h" #include "XUtils.h" +#include "pcp/PCPDynamicColumn.h" + + const ProcessFieldData Process_fields[] = { [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, }, [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, }, @@ -271,7 +274,9 @@ static int PCPProcess_compareByKey(const Process* v1, const Process* v2, Process case AUTOGROUP_NICE: return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice); default: - return Process_compareByKey_Base(v1, v2, key); + if (key < LAST_PROCESSFIELD) + return Process_compareByKey_Base(v1, v2, key); + return PCPDynamicColumn_compareByKey(p1, p2, key); } } diff --git a/pcp/PCPProcess.h b/pcp/PCPProcess.h index 2d1a8b6c..3593255b 100644 --- a/pcp/PCPProcess.h +++ b/pcp/PCPProcess.h @@ -12,13 +12,13 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep #include -#include #include "Object.h" #include "Process.h" -#include "RichString.h" #include "Settings.h" +#include "pcp/Platform.h" + #define PROCESS_FLAG_LINUX_CGROUP 0x00000800 #define PROCESS_FLAG_LINUX_OOM 0x00001000 @@ -29,6 +29,10 @@ in the source distribution for its full text. typedef struct PCPProcess_ { Process super; + + /* default result offset to use for searching proc metrics */ + unsigned int offset; + unsigned long int cminflt; unsigned long int cmajflt; unsigned long long int utime; diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c index 638ece21..8e644b07 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessList.c @@ -2,7 +2,7 @@ htop - PCPProcessList.c (C) 2014 Hisham H. Muhammad (C) 2020-2021 htop dev team -(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +(C) 2020-2021 Red Hat, Inc. Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ @@ -11,11 +11,15 @@ in the source distribution for its full text. #include "pcp/PCPProcessList.h" +#include #include +#include +#include +#include -#include "CRT.h" #include "Macros.h" #include "Object.h" +#include "Platform.h" #include "Process.h" #include "Settings.h" #include "XUtils.h" @@ -57,11 +61,11 @@ static char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) { return name; } -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { PCPProcessList* this = xCalloc(1, sizeof(PCPProcessList)); ProcessList* super = &(this->super); - ProcessList_init(super, Class(PCPProcess), usersTable, dynamicMeters, pidMatchList, userId); + ProcessList_init(super, Class(PCPProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); struct timeval timestamp; gettimeofday(×tamp, NULL); @@ -334,6 +338,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, PCPProcess* pp = (PCPProcess*) proc; PCPProcessList_updateID(proc, pid, offset); proc->isUserlandThread = proc->pid != proc->tgid; + pp->offset = offset >= 0 ? offset : 0; /* * These conditions will not trigger on first occurrence, cause we need to diff --git a/pcp/PCPProcessList.h b/pcp/PCPProcessList.h index 7f0f6fe4..07f6c399 100644 --- a/pcp/PCPProcessList.h +++ b/pcp/PCPProcessList.h @@ -56,14 +56,14 @@ typedef enum CPUMetric_ { typedef struct PCPProcessList_ { ProcessList super; - double timestamp; /* previous sample timestamp */ - pmAtomValue* cpu; /* aggregate values for each metric */ - pmAtomValue** percpu; /* per-processor values for each metric */ - pmAtomValue* values; /* per-processor buffer for just one metric */ + double timestamp; /* previous sample timestamp */ + pmAtomValue* cpu; /* aggregate values for each metric */ + pmAtomValue** percpu; /* per-processor values for each metric */ + pmAtomValue* values; /* per-processor buffer for just one metric */ ZfsArcStats zfs; } PCPProcessList; -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* pl); diff --git a/pcp/Platform.c b/pcp/Platform.c index 63ff50a7..5c7e6c34 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -2,7 +2,7 @@ htop - linux/Platform.c (C) 2014 Hisham H. Muhammad (C) 2020-2021 htop dev team -(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +(C) 2020-2021 Red Hat, Inc. Released under the GNU GPLv2, see the COPYING file in the source distribution for its full text. */ @@ -42,6 +42,7 @@ in the source distribution for its full text. #include "linux/PressureStallMeter.h" #include "linux/ZramMeter.h" #include "linux/ZramStats.h" +#include "pcp/PCPDynamicColumn.h" #include "pcp/PCPDynamicMeter.h" #include "pcp/PCPProcess.h" #include "pcp/PCPProcessList.h" @@ -51,19 +52,20 @@ in the source distribution for its full text. typedef struct Platform_ { - int context; /* PMAPI(3) context identifier */ - unsigned int totalMetrics; /* total number of all metrics */ - const char** names; /* name array indexed by Metric */ - pmID* pmids; /* all known metric identifiers */ - pmID* fetch; /* enabled identifiers for sampling */ - pmDesc* descs; /* metric desc array indexed by Metric */ - pmResult* result; /* sample values result indexed by Metric */ - PCPDynamicMeters meters; /* dynamic meters 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 */ - int pidmax; /* maximum platform process identifier */ - int ncpu; /* maximum processor count configured */ + int context; /* PMAPI(3) context identifier */ + size_t totalMetrics; /* total number of all metrics */ + const char** names; /* name array indexed by Metric */ + pmID* pmids; /* all known metric identifiers */ + pmID* fetch; /* enabled identifiers for sampling */ + pmDesc* descs; /* metric desc array indexed by Metric */ + pmResult* result; /* sample values result indexed by Metric */ + PCPDynamicMeters meters; /* dynamic meters via configuration files */ + PCPDynamicColumns columns; /* dynamic columns 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 */ + int pidmax; /* maximum platform process identifier */ + int ncpu; /* maximum processor count configured */ } Platform; Platform* pcp; @@ -253,6 +255,10 @@ const pmDesc* Metric_desc(Metric metric) { return &pcp->descs[metric]; } +int Metric_type(Metric metric) { + return pcp->descs[metric].type; +} + pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type) { if (pcp->result == NULL) return NULL; @@ -400,12 +406,12 @@ bool Metric_fetch(struct timeval* timestamp) { return true; } -int Platform_addMetric(Metric 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) { /* added via configuration files */ - unsigned int j = pcp->totalMetrics + 1; + size_t j = pcp->totalMetrics + 1; pcp->fetch = xRealloc(pcp->fetch, j * sizeof(pmID)); pcp->pmids = xRealloc(pcp->pmids, j * sizeof(pmID)); pcp->names = xRealloc(pcp->names, j * sizeof(char*)); @@ -467,14 +473,17 @@ void Platform_init(void) { PCPDynamicMeters_init(&pcp->meters); + pcp->columns.offset = PCP_METRIC_COUNT + pcp->meters.cursor; + PCPDynamicColumns_init(&pcp->columns); + sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids); if (sts < 0) { fprintf(stderr, "Error: cannot lookup metric names: %s\n", pmErrStr(sts)); exit(1); } - for (unsigned int i = 0; i < pcp->totalMetrics; i++) { - pcp->fetch[i] = PM_ID_NULL; /* default is to not sample */ + for (size_t i = 0; i < pcp->totalMetrics; i++) { + pcp->fetch[i] = PM_ID_NULL; /* default is to not sample */ /* expect some metrics to be missing - e.g. PMDA not available */ if (pcp->pmids[i] == PM_ID_NULL) @@ -503,11 +512,14 @@ void Platform_init(void) { Metric_enable(PCP_UNAME_MACHINE, true); Metric_enable(PCP_UNAME_DISTRO, true); + for (size_t i = pcp->columns.offset; i < pcp->columns.offset + pcp->columns.count; i++) + Metric_enable(i, true); + Metric_fetch(NULL); 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_PID_MAX, false); /* needed one time only */ Metric_enable(PCP_BOOTTIME, false); Metric_enable(PCP_UNAME_SYSNAME, false); Metric_enable(PCP_UNAME_RELEASE, false); @@ -629,7 +641,7 @@ static double Platform_setOneCPUValues(Meter* this, pmAtomValue* values) { double Platform_setCPUValues(Meter* this, int cpu) { const PCPProcessList* pl = (const PCPProcessList*) this->pl; - if (cpu <= 0) /* use aggregate values */ + if (cpu <= 0) /* use aggregate values */ return Platform_setOneCPUValues(this, pl->cpu); return Platform_setOneCPUValues(this, pl->percpu[cpu - 1]); } @@ -926,3 +938,29 @@ void Platform_dynamicMeterDisplay(const Meter* meter, RichString* out) { if (this) PCPDynamicMeter_display(this, meter, out); } + +Hashtable* Platform_dynamicColumns(void) { + return pcp->columns.table; +} + +const char* Platform_dynamicColumnInit(unsigned int key) { + PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); + if (this) { + Metric_enable(this->id, true); + if (this->super.caption) + return this->super.caption; + if (this->super.heading) + return this->super.heading; + return this->super.name; + } + return NULL; +} + +bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key) { + PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); + if (this) { + PCPDynamicColumn_writeField(this, proc, str); + return true; + } + return false; +} diff --git a/pcp/Platform.h b/pcp/Platform.h index 527bef29..3f98a733 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -11,6 +11,8 @@ in the source distribution for its full text. #include #include +#include +#include #include /* use htop config.h values for these macros, not pcp values */ @@ -29,6 +31,7 @@ in the source distribution for its full text. #include "NetworkIOMeter.h" #include "Process.h" #include "ProcessLocksScreen.h" +#include "RichString.h" #include "SignalsPanel.h" #include "SysArchMeter.h" @@ -253,13 +256,15 @@ pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type const pmDesc* Metric_desc(Metric metric); +int Metric_type(Metric metric); + int Metric_instanceCount(Metric metric); int Metric_instanceOffset(Metric metric, int inst); pmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type); -int Platform_addMetric(Metric id, const char* name); +size_t Platform_addMetric(Metric id, const char* name); void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec); @@ -273,4 +278,10 @@ void Platform_dynamicMeterUpdateValues(Meter* meter); void Platform_dynamicMeterDisplay(const Meter* meter, RichString* out); +Hashtable* Platform_dynamicColumns(void); + +const char* Platform_dynamicColumnInit(unsigned int key); + +bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key); + #endif diff --git a/pcp/columns/container b/pcp/columns/container new file mode 100644 index 00000000..519288f4 --- /dev/null +++ b/pcp/columns/container @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[container] +heading = Container +caption = CONTAINER +width = -12 +metric = proc.id.container +description = Name of processes container via cgroup heuristics diff --git a/pcp/columns/delayacct b/pcp/columns/delayacct new file mode 100644 index 00000000..016904c6 --- /dev/null +++ b/pcp/columns/delayacct @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[blkio] +heading = BLKIOD +caption = BLKIO_TIME +width = 6 +metric = proc.psinfo.delayacct_blkio_time +description = Aggregated block I/O delays diff --git a/pcp/columns/fdcount b/pcp/columns/fdcount new file mode 100644 index 00000000..e6794803 --- /dev/null +++ b/pcp/columns/fdcount @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[fds] +heading = FDS +caption = FDCOUNT +width = 4 +metric = proc.fd.count +description = Open file descriptors diff --git a/pcp/columns/guest b/pcp/columns/guest new file mode 100644 index 00000000..89bb926b --- /dev/null +++ b/pcp/columns/guest @@ -0,0 +1,17 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[guest] +heading = GUEST +caption = GUEST_TIME +width = 6 +metric = proc.psinfo.guest_time +description = Guest time for the process + +[cguest] +heading = CGUEST +caption = CGUEST_TIME +width = 6 +metric = proc.psinfo.guest_time + proc.psinfo.cguest_time +description = Cumulative guest time for the process and its children diff --git a/pcp/columns/memory b/pcp/columns/memory new file mode 100644 index 00000000..305a654a --- /dev/null +++ b/pcp/columns/memory @@ -0,0 +1,39 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[vmdata] +heading = VDATA +width = 6 +metric = proc.memory.vmdata +description = Virtual memory used for data + +[vmstack] +heading = VSTACK +width = -6 +metric = proc.memory.vmstack +description = Virtual memory used for stack + +[vmexe] +heading = VEXEC +width = 6 +metric = proc.memory.vmexe +description = Virtual memory used for non-library executable code + +[vmlib] +heading = VLIBS +width = 6 +metric = proc.memory.vmlib +description = Virtual memory used for libraries + +[vmswap] +heading = VSWAP +width = 6 +metric = proc.memory.vmswap +description = Virtual memory size currently swapped out + +[vmlock] +heading = VLOCK +width = 6 +metric = proc.memory.vmlock +description = Locked virtual memory diff --git a/pcp/columns/sched b/pcp/columns/sched new file mode 100644 index 00000000..36b8b551 --- /dev/null +++ b/pcp/columns/sched @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[rundelay] +heading = RUNQ +caption = RUN_DELAY +width = 4 +metric = proc.schedstat.run_delay +description = Run queue time diff --git a/pcp/columns/swap b/pcp/columns/swap new file mode 100644 index 00000000..234b3db3 --- /dev/null +++ b/pcp/columns/swap @@ -0,0 +1,15 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[swap] +heading = SWAP +width = 5 +metric = proc.psinfo.nswap +description = Count of swap operations for the process + +[cswap] +heading = CSWAP +width = 5 +metric = proc.psinfo.nswap + proc.psinfo.cnswap +description = Cumulative swap operations for the process and its children diff --git a/pcp/columns/tcp b/pcp/columns/tcp new file mode 100644 index 00000000..f9a18196 --- /dev/null +++ b/pcp/columns/tcp @@ -0,0 +1,31 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[tcp_send_calls] +heading = TCPS +caption = TCP_SEND +width = 6 +metric = bcc.proc.net.tcp.send.calls +description = Count of TCP send calls + +[tcp_send_bytes] +heading = TCPSB +caption = TCP_SEND_BYTES +width = 6 +metric = bcc.proc.net.tcp.send.bytes +description = Cumulative bytes sent via TCP + +[tcp_recv_calls] +heading = TCPR +caption = TCP_RECV +width = 6 +metric = bcc.proc.net.tcp.recv.calls +description = Count of TCP recv calls + +[tcp_recv_bytes] +heading = TCPRB +caption = TCP_RECV_BYTES +width = 6 +metric = bcc.proc.net.tcp.recv.bytes +description = Cumulative bytes received via TCP diff --git a/pcp/columns/udp b/pcp/columns/udp new file mode 100644 index 00000000..060f0486 --- /dev/null +++ b/pcp/columns/udp @@ -0,0 +1,31 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[udp_send_calls] +heading = UDPS +caption = UDP_SEND +width = 6 +metric = bcc.proc.net.udp.send.calls +description = Count of UDP send calls + +[udp_send_bytes] +heading = UDPSB +caption = UDP_SEND_BYTES +width = 6 +metric = bcc.proc.net.udp.send.bytes +description = Cumulative bytes sent via UDP + +[udp_recv_calls] +heading = UDPR +caption = UDP_RECV +width = 6 +metric = bcc.proc.net.udp.recv.calls +description = Count of UDP recv calls + +[udp_recv_bytes] +heading = UDPRB +caption = UDP_RECV_BYTES +width = 6 +metric = bcc.proc.net.udp.recv.bytes +description = Cumulative bytes received via UDP diff --git a/pcp/columns/wchan b/pcp/columns/wchan new file mode 100644 index 00000000..893de587 --- /dev/null +++ b/pcp/columns/wchan @@ -0,0 +1,17 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[wchan] +heading = WCHAN +caption = WCHAN_ADDRESS +width = 8 +metric = proc.psinfo.wchan +description = Wait channel, kernel address process is blocked or sleeping on + +[wchans] +heading = WCHANS +caption = WCHAN_SYMBOL +width = -12 +metric = proc.psinfo.wchan_s +description = Wait channel, kernel symbol process is blocked or sleeping on diff --git a/solaris/Platform.h b/solaris/Platform.h index 90108776..caf296b8 100644 --- a/solaris/Platform.h +++ b/solaris/Platform.h @@ -31,6 +31,7 @@ in the source distribution for its full text. #include "Action.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" +#include "Hashtable.h" #include "NetworkIOMeter.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" @@ -128,9 +129,7 @@ IGNORE_WCASTQUAL_BEGIN IGNORE_WCASTQUAL_END } -static inline Hashtable* Platform_dynamicMeters(void) { - return NULL; -} +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } @@ -138,4 +137,10 @@ static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } + #endif diff --git a/solaris/SolarisProcessList.c b/solaris/SolarisProcessList.c index 62a1f42c..45bc5ba3 100644 --- a/solaris/SolarisProcessList.c +++ b/solaris/SolarisProcessList.c @@ -89,10 +89,10 @@ static void SolarisProcessList_updateCPUcount(ProcessList* super) { } } -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { SolarisProcessList* spl = xCalloc(1, sizeof(SolarisProcessList)); ProcessList* pl = (ProcessList*) spl; - ProcessList_init(pl, Class(SolarisProcess), usersTable, dynamicMeters, pidMatchList, userId); + ProcessList_init(pl, Class(SolarisProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); spl->kd = kstat_open(); if (!spl->kd) diff --git a/solaris/SolarisProcessList.h b/solaris/SolarisProcessList.h index bee35cce..f653b7cf 100644 --- a/solaris/SolarisProcessList.h +++ b/solaris/SolarisProcessList.h @@ -54,7 +54,7 @@ typedef struct SolarisProcessList_ { ZfsArcStats zfs; } SolarisProcessList; -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId); +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); void ProcessList_delete(ProcessList* pl); diff --git a/unsupported/Platform.h b/unsupported/Platform.h index 3f704f0d..c1591de1 100644 --- a/unsupported/Platform.h +++ b/unsupported/Platform.h @@ -11,6 +11,7 @@ in the source distribution for its full text. #include "Action.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" +#include "Hashtable.h" #include "NetworkIOMeter.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" @@ -78,9 +79,7 @@ static inline void Platform_gettime_monotonic(uint64_t* msec) { Generic_gettime_monotonic(msec); } -static inline Hashtable* Platform_dynamicMeters(void) { - return NULL; -} +static inline Hashtable* Platform_dynamicMeters(void) { return NULL; } static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } @@ -88,4 +87,10 @@ static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } +static inline Hashtable* Platform_dynamicColumns(void) { return NULL; } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { return NULL; } + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { return false; } + #endif diff --git a/unsupported/UnsupportedProcessList.c b/unsupported/UnsupportedProcessList.c index 16c01558..bed9ca96 100644 --- a/unsupported/UnsupportedProcessList.c +++ b/unsupported/UnsupportedProcessList.c @@ -14,9 +14,9 @@ in the source distribution for its full text. #include "UnsupportedProcess.h" -ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* pidMatchList, uid_t userId) { +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { ProcessList* this = xCalloc(1, sizeof(ProcessList)); - ProcessList_init(this, Class(Process), usersTable, dynamicMeters, pidMatchList, userId); + ProcessList_init(this, Class(Process), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); this->existingCPUs = 1; this->activeCPUs = 1;