diff --git a/Action.c b/Action.c index f00d30b91e..8adf7fc511 100644 --- a/Action.c +++ b/Action.c @@ -165,6 +165,16 @@ Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) { // ---------------------------------------- +static bool Action_writeableProcess(State* st) { + const Settings* settings = st->host->settings; + return !(Settings_isReadonly() || settings->ss->dynamic); +} + +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 +313,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 +321,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 +355,25 @@ 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]; + 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 +384,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 +412,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; @@ -427,7 +450,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; @@ -471,7 +494,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; @@ -522,7 +545,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); @@ -540,6 +563,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; @@ -555,7 +581,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 +831,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 +849,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 3540e93e3e..d285e1b884 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 f3c408430a..f79ee71853 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,59 @@ 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; + if (column->table) /* DynamicScreen, handled differently */ + return; + AvailableColumnsPanel* this = (AvailableColumnsPanel*) 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_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 aca59060df..3d233be09e 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 ba7ee50307..23e4348b1a 100644 --- a/CategoriesPanel.c +++ b/CategoriesPanel.c @@ -75,10 +75,12 @@ static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) { 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); - ScreenManager_add(this->scr, screens, 20); - ScreenManager_add(this->scr, columns, 20); + Panel* activeScreens = (Panel*) ((ScreensPanel*)screens)->activeScreens; + Panel* activeColumns = (Panel*) ((ActiveScreensPanel*)activeScreens)->activeColumns; + Panel* availableColumns = (Panel*) ((ActiveScreensPanel*)activeScreens)->availableColumns; + ScreenManager_add(this->scr, screens, 14); + ScreenManager_add(this->scr, activeScreens, 16); + ScreenManager_add(this->scr, activeColumns, 20); ScreenManager_add(this->scr, availableColumns, -1); } diff --git a/CommandLine.c b/CommandLine.c index 848c7ae749..2cd905161d 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 326d33b145..22585cf5d2 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -104,7 +104,7 @@ 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 diff --git a/DynamicColumn.c b/DynamicColumn.c index bd038df4e5..7c0ed3ad11 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 4760e6ea5d..bdcebd1429 100644 --- a/DynamicColumn.h +++ b/DynamicColumn.h @@ -6,24 +6,29 @@ #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 0000000000..7b659fc34c --- /dev/null +++ b/DynamicScreen.c @@ -0,0 +1,64 @@ +/* +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* dynamics) { + if (dynamics) { + Platform_dynamicScreensDone(dynamics); + Hashtable_delete(dynamics); + } +} + +void DynamicScreen_done(DynamicScreen* this) { + free(this->caption); + free(this->fields); + free(this->heading); + free(this->sortKey); +} + +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* dynamics, const char* name, ht_key_t* key) { + DynamicIterator iter = { .key = 0, .name = name, .found = false }; + if (dynamics) + Hashtable_foreach(dynamics, DynamicScreen_compare, &iter); + if (key) + *key = iter.key; + return iter.found; +} + +const char* DynamicScreen_lookup(Hashtable* dynamics, ht_key_t key) { + const DynamicScreen* screen = Hashtable_get(dynamics, key); + return screen ? screen->name : NULL; +} diff --git a/DynamicScreen.h b/DynamicScreen.h new file mode 100644 index 0000000000..faa8a65b2b --- /dev/null +++ b/DynamicScreen.h @@ -0,0 +1,32 @@ +#ifndef HEADER_DynamicScreen +#define HEADER_DynamicScreen + +#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; + 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* dynamics, unsigned int key); + +bool DynamicScreen_search(Hashtable* dynamics, const char* name, unsigned int* key); + +#endif diff --git a/Machine.c b/Machine.c index 8040bb9896..b9affe496b 100644 --- a/Machine.c +++ b/Machine.c @@ -57,15 +57,16 @@ void Machine_done(Machine* this) { hwloc_topology_destroy(this->topology); } #endif - for (unsigned int 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 = (Table**) xReallocArray(this->tables, nmemb, sizeof(Table*)); @@ -76,6 +77,28 @@ void Machine_addTable(Machine* this, Table* table, bool processes) { } } +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 3e2ae57990..44376d0342 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 b761c400ea..f95520e132 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); } @@ -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); @@ -211,9 +211,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); @@ -225,9 +228,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 d062616d28..19229d59fe 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 48b0ca77e4..6dc41b7b45 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 \ @@ -113,6 +114,7 @@ myhtopheaders = \ DisplayOptionsPanel.h \ DynamicColumn.h \ DynamicMeter.h \ + DynamicScreen.h \ EnvScreen.h \ FileDescriptorMeter.h \ FunctionBar.h \ @@ -407,11 +409,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 \ @@ -423,10 +429,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 62f69e1dc5..ee19156b36 100644 --- a/Process.c +++ b/Process.c @@ -747,7 +747,7 @@ void Process_writeField(const Row* super, RichString* str, RowField field) { } void Process_done(Process* this) { - assert (this != NULL); + assert(this != NULL); free(this->cmdline); free(this->procComm); free(this->procExe); @@ -769,8 +769,8 @@ const char* Process_getCommand(const Process* this) { return this->mergedCommand.str; } -const char* Process_getSortKey(const Row* super) { - const Process* this = (const Process*) super; +const char* Process_getSortKey(Row* super) { + Process* this = (Process*) super; return Process_getCommand(this); } diff --git a/Process.h b/Process.h index 2dd55ede9e..e1e8a83d5f 100644 --- a/Process.h +++ b/Process.h @@ -305,7 +305,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField const char* Process_getCommand(const Process* this); -const char* Process_getSortKey(const Row* super); +const char* Process_getSortKey(Row* super); void Process_updateComm(Process* this, const char* comm); void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd); diff --git a/Row.c b/Row.c index 9b7908ea93..3f444d8010 100644 --- a/Row.c +++ b/Row.c @@ -41,6 +41,11 @@ void Row_init(Row* this, const Machine* host) { this->uid = (uid_t)-1; } +void Row_done(Row* this) { + assert(this != NULL); + (void) this; +} + bool Row_isNew(const Row* this) { const Machine* host = this->host; if (host->monotonicMs >= this->seenStampMs) { @@ -58,10 +63,11 @@ void Row_display(const Object* cast, RichString* out) { const Row* this = (const Row*) cast; const Machine* host = this->host; const Settings* settings = host->settings; - const ProcessField* fields = settings->ss->fields; + const RowField* fields = settings->ss->fields; - for (int i = 0; fields[i]; i++) + for (int i = 0; fields[i]; i++) { As_Row(this)->writeField(this, out, fields[i]); + } if (settings->shadowOtherUsers && this->uid != host->htopUserId) { RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]); @@ -130,7 +136,7 @@ static const char* alignedTitleDynamicColumn(const Settings* settings, int key, int width = column->width; 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; } diff --git a/Row.h b/Row.h index 50d3d99e0a..39e773f39d 100644 --- a/Row.h +++ b/Row.h @@ -123,7 +123,7 @@ typedef Row* (*Row_New)(const struct Machine_*); typedef void (*Row_WriteField)(const Row*, RichString*, RowField); 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); @@ -160,6 +160,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); bool Row_isNew(const Row* this); diff --git a/ScreenManager.c b/ScreenManager.c index e39c4f0694..6c1dd12efc 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/ScreensPanel.c b/ScreensPanel.c index cb664ac450..71886abe8e 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" @@ -20,38 +21,155 @@ in the source distribution for its full text. #include "XUtils.h" -static void ScreenListItem_delete(Object* cast) { - ScreenListItem* this = (ScreenListItem*)cast; +ObjectClass ScreenListItem_class = { + .extends = Class(ListItem), + .display = ListItem_display, + .compare = ListItem_compare +}; + +static void ActiveScreensPanel_fill(ActiveScreensPanel* this, DynamicScreen* ds) { + const Settings* settings = this->settings; + Panel* super = (Panel*) this; + Panel_prune(super); + + for (unsigned int i = 0; settings->screens[i]; 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) == false) + continue; + /* matching dynamic screen found, add it into the Panel */ + } + Panel_add(super, (Object*) ListItem_new(ss->heading, i)); + } +} + +static HandlerResult ScreensPanel_eventHandler(Panel* super, int ch) { + ScreensPanel* const this = (ScreensPanel* const) super; + ScreenListItem* oldFocus = (ScreenListItem*) 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_UP: + case KEY_DOWN: + case KEY_NPAGE: + case KEY_PPAGE: + case KEY_HOME: + case KEY_END: { + Panel_onKey(super, ch); + break; + } + default: + { + if (ch < 255 && isalpha(ch)) + result = Panel_selectByTyping(super, ch); + if (result == BREAK_LOOP) + result = IGNORED; + break; + } + } + ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super); + if (newFocus && oldFocus != newFocus) { + ActiveScreensPanel_fill(this->activeScreens, newFocus->ds); + result = HANDLED; + } + return result; +} + +PanelClass ScreensPanel_class = { + .super = { + .extends = Class(Panel), + }, + .eventHandler = ScreensPanel_eventHandler +}; + +static ScreenListItem* ScreenListItem_new(const char* value, DynamicScreen* ds) { + ScreenListItem* this = AllocThis(ScreenListItem); + 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*) ScreenListItem_new(name, screen)); +} + +static const char* const ScreensFunctions[] = {" ", " ", " ", " ", " ", " ", " ", " ", " ", "Done ", NULL}; + +ScreensPanel* ScreensPanel_new(Settings* settings) { + ScreensPanel* this = AllocThis(ScreensPanel); + Panel* super = (Panel*) this; + FunctionBar* fuBar = FunctionBar_new(ScreensFunctions, NULL, NULL); + Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar); + + this->settings = settings; + this->activeScreens = ActiveScreensPanel_new(settings); + super->cursorOn = false; + this->cursor = 0; + Panel_setHeader(super, "Available"); + + Panel_add(super, (Object*) ScreenListItem_new("Processes", NULL)); + if (settings->dynamicScreens) + Hashtable_foreach(settings->dynamicScreens, addDynamicScreen, super); + return this; +} + +// ------------- + +static void ActiveScreenListItem_delete(Object* cast) { + ActiveScreenListItem* this = (ActiveScreenListItem*)cast; if (this->ss) { ScreenSettings_delete(this->ss); } ListItem_delete(cast); } -ObjectClass ScreenListItem_class = { +ObjectClass ActiveScreenListItem_class = { .extends = Class(ListItem), .display = ListItem_display, - .delete = ScreenListItem_delete, + .delete = ActiveScreenListItem_delete, .compare = ListItem_compare }; -ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) { - ScreenListItem* this = AllocThis(ScreenListItem); +ActiveScreenListItem* ActiveScreenListItem_new(const char* value, ScreenSettings* ss) { + ActiveScreenListItem* this = AllocThis(ActiveScreenListItem); ListItem_init((ListItem*)this, value, 0); this->ss = ss; return this; } -static const char* const ScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL}; +static const char* const ActiveScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL}; -static void ScreensPanel_delete(Object* object) { +static void ActiveScreensPanel_delete(Object* object) { Panel* super = (Panel*) object; - ScreensPanel* this = (ScreensPanel*) object; + ActiveScreensPanel* this = (ActiveScreensPanel*) object; /* do not delete screen settings still in use */ int n = Panel_size(super); for (int i = 0; i < n; i++) { - ScreenListItem* item = (ScreenListItem*) Panel_get(super, i); + ActiveScreenListItem* item = (ActiveScreenListItem*) Panel_get(super, i); item->ss = NULL; } @@ -63,8 +181,24 @@ static void ScreensPanel_delete(Object* object) { free(this); } -static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) { - ScreensPanel* const this = (ScreensPanel*) super; +static void ActiveScreensPanel_update(Panel* super) { + ActiveScreensPanel* this = (ActiveScreensPanel*) super; + int size = Panel_size(super); + this->settings->changed = true; + this->settings->lastUpdate++; + this->settings->screens = xReallocArray(this->settings->screens, size + 1, sizeof(ScreenSettings*)); + for (int i = 0; i < size; i++) { + ActiveScreenListItem* item = (ActiveScreenListItem*) Panel_get(super, i); + ScreenSettings* ss = item->ss; + free(ss->heading); + this->settings->screens[i] = ss; + ss->heading = xStrdup(((ListItem*) item)->value); + } + this->settings->screens[size] = NULL; +} + +static HandlerResult ActiveScreensPanel_eventHandlerRenaming(Panel* super, int ch) { + ActiveScreensPanel* const this = (ActiveScreensPanel*) super; if (ch >= 32 && ch < 127 && ch != '=') { if (this->cursor < SCREEN_NAME_LEN - 1) { @@ -99,7 +233,7 @@ static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) { this->renamingItem = NULL; super->cursorOn = false; Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS); - ScreensPanel_update(super); + ActiveScreensPanel_update(super); break; } case 27: // Esc @@ -120,7 +254,7 @@ static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) { } static void startRenaming(Panel* super) { - ScreensPanel* const this = (ScreensPanel*) super; + ActiveScreensPanel* const this = (ActiveScreensPanel*) super; ListItem* item = (ListItem*) Panel_getSelected(super); if (item == NULL) @@ -139,14 +273,14 @@ static void startRenaming(Panel* super) { } static void rebuildSettingsArray(Panel* super, int selected) { - ScreensPanel* const this = (ScreensPanel*) super; + ActiveScreensPanel* const this = (ActiveScreensPanel*) super; int n = Panel_size(super); free(this->settings->screens); this->settings->screens = xMallocArray(n + 1, sizeof(ScreenSettings*)); this->settings->screens[n] = NULL; for (int i = 0; i < n; i++) { - ScreenListItem* item = (ScreenListItem*) Panel_get(super, i); + ActiveScreenListItem* item = (ActiveScreenListItem*) Panel_get(super, i); this->settings->screens[i] = item->ss; } this->settings->nScreens = n; @@ -160,21 +294,21 @@ static void rebuildSettingsArray(Panel* super, int selected) { } static void addNewScreen(Panel* super) { - ScreensPanel* const this = (ScreensPanel*) super; + ActiveScreensPanel* const this = (ActiveScreensPanel*) super; const char* name = "New"; ScreenSettings* ss = Settings_newScreen(this->settings, &(const ScreenDefaults) { .name = name, .columns = "PID Command", .sortKey = "PID" }); - ScreenListItem* item = ScreenListItem_new(name, ss); + ActiveScreenListItem* item = ActiveScreenListItem_new(name, ss); int idx = Panel_getSelectedIndex(super); Panel_insert(super, idx + 1, (Object*) item); Panel_setSelected(super, idx + 1); } -static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) { - ScreensPanel* const this = (ScreensPanel*) super; +static HandlerResult ActiveScreensPanel_eventHandlerNormal(Panel* super, int ch) { + ActiveScreensPanel* const this = (ActiveScreensPanel*) super; int selected = Panel_getSelectedIndex(super); - ScreenListItem* oldFocus = (ScreenListItem*) Panel_getSelected(super); + ActiveScreenListItem* oldFocus = (ActiveScreenListItem*) Panel_getSelected(super); bool shouldRebuildArray = false; HandlerResult result = IGNORED; switch (ch) { @@ -270,71 +404,59 @@ static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) { break; } } - ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super); + ActiveScreenListItem* newFocus = (ActiveScreenListItem*) Panel_getSelected(super); if (newFocus && oldFocus != newFocus) { - ColumnsPanel_fill(this->columns, newFocus->ss, this->settings->dynamicColumns); + Hashtable* dynamicColumns = this->settings->dynamicColumns; + ColumnsPanel_fill(this->activeColumns, newFocus->ss, dynamicColumns); + AvailableColumnsPanel_fill(this->availableColumns, newFocus->ss->dynamic, dynamicColumns); result = HANDLED; } if (shouldRebuildArray) rebuildSettingsArray(super, selected); if (result == HANDLED) - ScreensPanel_update(super); + ActiveScreensPanel_update(super); return result; } -static HandlerResult ScreensPanel_eventHandler(Panel* super, int ch) { - ScreensPanel* const this = (ScreensPanel*) super; +static HandlerResult ActiveScreensPanel_eventHandler(Panel* super, int ch) { + ActiveScreensPanel* const this = (ActiveScreensPanel*) super; if (this->renamingItem) { - return ScreensPanel_eventHandlerRenaming(super, ch); + return ActiveScreensPanel_eventHandlerRenaming(super, ch); } else { - return ScreensPanel_eventHandlerNormal(super, ch); + return ActiveScreensPanel_eventHandlerNormal(super, ch); } } -PanelClass ScreensPanel_class = { +PanelClass ActiveScreensPanel_class = { .super = { .extends = Class(Panel), - .delete = ScreensPanel_delete + .delete = ActiveScreensPanel_delete }, - .eventHandler = ScreensPanel_eventHandler + .eventHandler = ActiveScreensPanel_eventHandler }; -ScreensPanel* ScreensPanel_new(Settings* settings) { - ScreensPanel* this = AllocThis(ScreensPanel); +ActiveScreensPanel* ActiveScreensPanel_new(Settings* settings) { + ActiveScreensPanel* this = AllocThis(ActiveScreensPanel); Panel* super = (Panel*) this; Hashtable* columns = settings->dynamicColumns; - FunctionBar* fuBar = FunctionBar_new(ScreensFunctions, NULL, NULL); + FunctionBar* fuBar = FunctionBar_new(ActiveScreensFunctions, 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->activeColumns = ColumnsPanel_new(settings->screens[0], columns, &settings->changed); + this->availableColumns = AvailableColumnsPanel_new((Panel*) this->activeColumns, settings->dynamicColumns); + this->moving = false; this->renamingItem = NULL; super->cursorOn = false; this->cursor = 0; - Panel_setHeader(super, "Screens"); + Panel_setHeader(super, "Active"); for (unsigned int i = 0; i < settings->nScreens; i++) { ScreenSettings* ss = settings->screens[i]; - char* name = ss->name; - Panel_add(super, (Object*) ScreenListItem_new(name, ss)); + char* name = ss->heading; + Panel_add(super, (Object*) ActiveScreenListItem_new(name, ss)); } return this; } - -void ScreensPanel_update(Panel* super) { - ScreensPanel* this = (ScreensPanel*) super; - int size = Panel_size(super); - this->settings->changed = true; - this->settings->lastUpdate++; - this->settings->screens = xReallocArray(this->settings->screens, size + 1, sizeof(ScreenSettings*)); - for (int i = 0; i < size; i++) { - ScreenListItem* item = (ScreenListItem*) Panel_get(super, i); - ScreenSettings* ss = item->ss; - free(ss->name); - 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 60aaf2a2d4..77b297fabf 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" @@ -21,33 +23,48 @@ in the source distribution for its full text. #define SCREEN_NAME_LEN 20 #endif -typedef struct ScreensPanel_ { +typedef struct ActiveScreensPanel_ { Panel super; ScreenManager* scr; Settings* settings; - ColumnsPanel* columns; + ColumnsPanel* activeColumns; + AvailableColumnsPanel* availableColumns; char buffer[SCREEN_NAME_LEN + 1]; char* saved; int cursor; bool moving; ListItem* renamingItem; +} ActiveScreensPanel; + +typedef struct ActiveScreenListItem_ { + ListItem super; + ScreenSettings* ss; +} ActiveScreenListItem; + +typedef struct ScreensPanel_ { + Panel super; + + ScreenManager* scr; + Settings* settings; + ActiveScreensPanel* activeScreens; + int cursor; } ScreensPanel; typedef struct ScreenListItem_ { ListItem super; - ScreenSettings* ss; + DynamicScreen* ds; } ScreenListItem; -extern ObjectClass ScreenListItem_class; +ScreensPanel* ScreensPanel_new(Settings* settings); -ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss); +extern ObjectClass ActiveScreenListItem_class; -extern PanelClass ScreensPanel_class; +ActiveScreenListItem* ActiveScreenListItem_new(const char* value, ScreenSettings* ss); -ScreensPanel* ScreensPanel_new(Settings* settings); +extern PanelClass ActiveScreensPanel_class; -void ScreensPanel_update(Panel* super); +ActiveScreensPanel* ActiveScreensPanel_new(Settings* settings); #endif diff --git a/Settings.c b/Settings.c index b772f09161..b9b298989e 100644 --- a/Settings.c +++ b/Settings.c @@ -271,17 +271,20 @@ static void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, co 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, .allBranchesCollapsed = false, @@ -292,11 +295,13 @@ ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* default this->nScreens++; this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1)); this->screens[this->nScreens] = NULL; + return ss; } void ScreenSettings_delete(ScreenSettings* this) { - free(this->name); + free(this->heading); + free(this->dynamic); free(this->fields); free(this); } @@ -308,6 +313,7 @@ static ScreenSettings* Settings_defaultScreens(Settings* this) { const ScreenDefaults* defaults = &Platform_defaultScreens[i]; Settings_newScreen(this, defaults); } + Platform_defaultDynamicScreens(this); return this->screens[0]; } @@ -499,12 +505,16 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini } else if (String_eq(option[0], ".tree_view")) { if (screen) screen->treeView = atoi(option[1]); - } else if (String_eq(option[0], ".tree_view_always_by_pid")) { + } else if (String_eq(option[0], ".tree_view_always_by_id") || String_eq(option[0], ".tree_view_always_by_pid")) { if (screen) screen->treeViewAlwaysByID = 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); } @@ -524,7 +534,7 @@ static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns 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); + 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); @@ -639,12 +649,24 @@ int Settings_write(const Settings* this, bool onCrash) { 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); + const char* treeSortKey = toFieldName(this->dynamicColumns, ss->treeSortKey); + + 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); + printSettingInteger(".tree_view_always_by_id", ss->treeViewAlwaysByID); + } else { + printSettingString(".sort_key", sortKey); + printSettingString(".tree_sort_key", treeSortKey); + printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByID); + } 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 +689,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; diff --git a/Settings.h b/Settings.h index 8b44a24483..a8ccd7d065 100644 --- a/Settings.h +++ b/Settings.h @@ -21,10 +21,13 @@ in the source distribution for its full text. #define CONFIG_READER_MIN_VERSION 3 +struct Table_; + typedef struct { const char* name; const char* columns; const char* sortKey; + const char* treeSortKey; } ScreenDefaults; typedef struct { @@ -34,7 +37,9 @@ typedef struct { } MeterColumnSetting; typedef struct { - 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; @@ -53,6 +58,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; @@ -118,7 +124,7 @@ 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); diff --git a/Table.c b/Table.c index 3b273fd86b..c2e4e3bbb5 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); diff --git a/darwin/Platform.h b/darwin/Platform.h index 5cd672979f..cf00919fbe 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 b37cea2a13..91616d4fe0 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 849f7ddf31..cc86456928 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 1621d56280..a64e54a78c 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 a75c766cfb..ae34198cc2 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 f357006cc6..790dc47359 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 0000000000..1205992225 --- /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 0000000000..7d848d572e --- /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 0000000000..1d56c108b3 --- /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 0000000000..c7d688fcb1 --- /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 9835f22f5e..b6f9343e98 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,10 @@ 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 +112,9 @@ 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->percent = false; + column->instances = false; + column->super.enabled = true; size_t id = columns->count + LAST_PROCESSFIELD; Hashtable_put(columns->table, id, column); @@ -160,6 +167,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")) { + if (String_eq(value, "False") || String_eq(value, "false")) + column->super.enabled = false; } else if (value && column && String_eq(key, "metric")) { PCPDynamicColumn_parseMetric(columns, column, path, lineno, value); } @@ -233,74 +248,219 @@ 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.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) { + Row_printPercentage(value, buffer, sizeof(buffer), width, &attr); + 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.dimTime) + Row_printTime(str, value / 10 /* hundreds of a second */, coloring); + else if (units.dimCount) + Row_printCount(str, value, 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 d0ffe71921..10d24947f4 100644 --- a/pcp/PCPDynamicColumn.h +++ b/pcp/PCPDynamicColumn.h @@ -11,15 +11,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 enabled; /* 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 +35,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/PCPDynamicScreen.c b/pcp/PCPDynamicScreen.c new file mode 100644 index 0000000000..c1fe5cda4a --- /dev/null +++ b/pcp/PCPDynamicScreen.c @@ -0,0 +1,410 @@ +/* +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 "Macros.h" +#include "Platform.h" +#include "Settings.h" +#include "XUtils.h" +#include "AvailableColumnsPanel.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]; + 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->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; + + 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) + 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->super.width = strtoul(value, NULL, 10); + } else if (String_eq(p, "instances")) { + if (String_eq(value, "True") || String_eq(value, "true")) + column->instances = true; + } else if (String_eq(p, "default")) { /* displayed by default */ + if (String_eq(value, "False") || String_eq(value, "false")) + column->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->columnKeys); + 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; + ScreenSettings* ss; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (ds->defaultEnabled == false) + continue; + + ScreenDefaults screen = { + .name = ds->super.name, + .columns = ds->columnKeys, + .sortKey = ds->super.sortKey, + }; + + ss = Settings_newScreen(settings, &screen); + ss->direction = ds->super.direction; + ss->heading = xStrdup(ds->super.heading); + ss->dynamic = xStrdup(ds->super.name); + ss->table = &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.caption ? column->super.caption : column->super.heading; + if (!title) + title = column->super.name; // fallback to the only mandatory field + + char description[256]; + xSnprintf(description, sizeof(description), "%s - %s", title, column->super.description); + Panel_add(availableColumns, (Object*) ListItem_new(description, j)); + } +} diff --git a/pcp/PCPDynamicScreen.h b/pcp/PCPDynamicScreen.h new file mode 100644 index 0000000000..3534a66143 --- /dev/null +++ b/pcp/PCPDynamicScreen.h @@ -0,0 +1,51 @@ +#ifndef HEADER_PCPDynamicScreen +#define HEADER_PCPDynamicScreen + +#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; + char* columnKeys; + + 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 59e056247d..726218d990 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 606a5df06f..f7886bb842 100644 --- a/pcp/PCPMetric.c +++ b/pcp/PCPMetric.c @@ -178,3 +178,20 @@ 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; + + pmLookupText(pmid, PM_TEXT_ONELINE, desc); + return 0; +} diff --git a/pcp/PCPMetric.h b/pcp/PCPMetric.h index e89a0a4c77..b53b82de9a 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 12e0f4d780..059979773d 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) @@ -842,7 +850,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); @@ -863,3 +871,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 f90e28135d..f2e8a49d41 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 0000000000..e6cdf89452 --- /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 0000000000..d69df0de9b --- /dev/null +++ b/pcp/screens/cgroups @@ -0,0 +1,41 @@ +# +# 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) + +system_cpu.heading = STIME +system_cpu.caption = Kernel CPU Time +system_cpu.metric = 1000 * rate(cgroup.cpu.stat.system) + +cpu_usage.heading = CPU% +cpu_usage.caption = CPU Utilization +cpu_usage.width = 5 +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) + +mem_psi.heading = MEM-PSI +mem_psi.caption = Memory Pressure Stall Information +mem_psi.metric = 1000 * rate(cgroup.pressure.memory.some.total) + +io_psi.heading = I/O-PSI +io_psi.caption = I/O Pressure Stall Information +io_psi.metric = 1000 * rate(cgroup.pressure.io.some.total) + +name.heading = Control group +name.caption = Control group name +name.width = -22 +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 0000000000..8e23bfa5cd --- /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 = cgroup.io.stat.rios + cgroup.io.stat.wios + cgroup.io.stat.dios + +readops.heading = RDIO +readops.caption = Read operations +readops.metric = cgroup.io.stat.rios +readops.default = false + +writeops.heading = WRIO +writeops.caption = Write operations +writeops.metric = cgroup.io.stat.wios +writeops.default = false + +directops.heading = DIO +directops.caption = Direct I/O operations +directops.metric = cgroup.io.stat.dios +directops.default = false + +totalbytes.heading = R/W/D +totalbytes.caption = Disk throughput +totalbytes.metric = cgroup.io.stat.rbytes + cgroup.io.stat.wbytes + cgroup.io.stat.dbytes + +readbytes.heading = RBYTE +readbytes.caption = Disk read throughput +readbytes.metric = cgroup.io.stat.rbytes + +writebytes.heading = WBYTE +writebytes.caption = Disk throughput +writebytes.metric = cgroup.io.stat.wbytes + +directio.heading = DBYTE +directio.caption = Direct I/O throughput +directio.metric = cgroup.io.stat.dbytes + +name.heading = Control group device +name.caption = Control group device +name.width = -22 +name.metric = cgroup.io.stat.rbytes +name.instances = true diff --git a/pcp/screens/cgroupsmem b/pcp/screens/cgroupsmem new file mode 100644 index 0000000000..185ddc3da5 --- /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 = -22 +name.metric = cgroup.memory.current +name.instances = true +name.format = cgroup diff --git a/pcp/screens/execsnoop b/pcp/screens/execsnoop new file mode 100644 index 0000000000..d706e76405 --- /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 0000000000..6c6b867c0e --- /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/filesys b/pcp/screens/filesys new file mode 100644 index 0000000000..e0e0112327 --- /dev/null +++ b/pcp/screens/filesys @@ -0,0 +1,50 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[filesys] +heading = Filesystems +caption = Mounted block device filesystems + +blockdev.heading = Filesystem +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 = -22 diff --git a/pcp/screens/opensnoop b/pcp/screens/opensnoop new file mode 100644 index 0000000000..ec209b03f3 --- /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 3dc6e3b593..00bc5832c1 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 a718ca09ed..8e08dc238d 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