Skip to content

Commit

Permalink
Add a new DynamicMeter class for runtime Meter extension
Browse files Browse the repository at this point in the history
This commit is based on exploratory work by Sohaib Mohamed.
The end goal is two-fold - to support addition of Meters we
build via configuration files for both the PCP platform and
for scripts ( htop-dev#526 )

Here, we focus on generic code and the PCP support.  A new
class DynamicMeter is introduced - it uses the special case
'param' field handling that previously was used only by the
CPUMeter, such that every runtime-configured Meter is given
a unique identifier.  Unlike with the CPUMeter this is used
internally only.  When reading/writing to htoprc instead of
CPU(N) - where N is an integer param (CPU number) - we use
the string name for each meter.  For example, if we have a
configuration for a DynamicMeter for some Redis metrics, we
might read and write "Dynamic(redis)".  This identifier is
subsequently matched (back) up to the configuration file so
we're able to re-create arbitrary user configurations.

The PCP platform configuration file format is fairly simple.
We expand configs from several directories, including the
users homedir alongside htoprc (below htop/meters/) and also
/etc/pcp/htop/meters.  The format will be described via a
new pcp-htop(5) man page, but its basically ini-style and
each Meter has one or more metric expressions associated, as
well as specifications for labels, color and so on via a dot
separated notation for individual metrics within the Meter.

A few initial sample configuration files are provided below
./pcp/meters that give the general idea.  The PCP "derived"
metric specification - see pmRegisterDerived(3) - is used
as the syntax for specifying metrics in PCP DynamicMeters.
  • Loading branch information
natoscott committed Jun 24, 2021
1 parent d578476 commit 33ad090
Show file tree
Hide file tree
Showing 42 changed files with 888 additions and 68 deletions.
72 changes: 54 additions & 18 deletions AvailableMetersPanel.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ in the source distribution for its full text.
#include <stdlib.h>

#include "CPUMeter.h"
#include "DynamicMeter.h"
#include "FunctionBar.h"
#include "Header.h"
#include "ListItem.h"
Expand Down Expand Up @@ -91,6 +92,48 @@ const PanelClass AvailableMetersPanel_class = {
.eventHandler = AvailableMetersPanel_eventHandler
};

// Handle (&CPUMeter_class) entries in the AvailableMetersPanel
static void AvailableMetersPanel_addCPUMeters(Panel* super, const MeterClass* type, const ProcessList* pl) {
if (pl->cpuCount > 1) {
Panel_add(super, (Object*) ListItem_new("CPU average", 0));
for (unsigned int i = 1; i <= pl->cpuCount; i++) {
char buffer[50];
xSnprintf(buffer, sizeof(buffer), "%s %d", type->uiName, Settings_cpuId(pl->settings, i - 1));
Panel_add(super, (Object*) ListItem_new(buffer, i));
}
} else {
Panel_add(super, (Object*) ListItem_new(type->uiName, 1));
}
}

typedef struct {
Panel* super;
unsigned int id;
unsigned int offset;
} DynamicIterator;

static void AvailableMetersPanel_addDynamicMeter(ATTR_UNUSED ht_key_t key, void* value, void* data) {
const DynamicMeter* meter = (const DynamicMeter*)value;
DynamicIterator* iter = (DynamicIterator*)data;
unsigned int identifier = (iter->offset << 16) | iter->id;
const char* label = meter->description ? meter->description : meter->caption;
if (!label) label = meter->name; /* last fallback to name, guaranteed set */
Panel_add(iter->super, (Object*) ListItem_new(label, identifier));
iter->id++;
}

// Handle (&DynamicMeter_class) entries in the AvailableMetersPanel
static void AvailableMetersPanel_addDynamicMeters(Panel* super, const ProcessList *pl, unsigned int offset) {
DynamicIterator iter = { .super = super, .id = 1, .offset = offset };
Hashtable_foreach(pl->dynamicMeters, AvailableMetersPanel_addDynamicMeter, &iter);
}

// Handle remaining Platform Meter entries in the AvailableMetersPanel
static void AvailableMetersPanel_addPlatformMeter(Panel* super, const MeterClass* type, unsigned int offset) {
const char* label = type->description ? type->description : type->uiName;
Panel_add(super, (Object*) ListItem_new(label, offset << 16));
}

AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* header, Panel* leftMeters, Panel* rightMeters, ScreenManager* scr, const ProcessList* pl) {
AvailableMetersPanel* this = AllocThis(AvailableMetersPanel);
Panel* super = (Panel*) this;
Expand All @@ -104,26 +147,19 @@ AvailableMetersPanel* AvailableMetersPanel_new(Settings* settings, Header* heade
this->scr = scr;

Panel_setHeader(super, "Available meters");
// Platform_meterTypes[0] should be always (&CPUMeter_class), which we will
// handle separately in the code below.
for (int i = 1; Platform_meterTypes[i]; i++) {
// Platform_meterTypes[0] should be always (&CPUMeter_class) which we will
// handle separately in the code below. Likewise, identifiers for Dynamic
// Meters are handled separately - similar to CPUs, this allows generation
// of multiple different Meters (also using 'param' to distinguish them).
for (unsigned int i = 1; Platform_meterTypes[i]; i++) {
const MeterClass* type = Platform_meterTypes[i];
assert(type != &CPUMeter_class);
const char* label = type->description ? type->description : type->uiName;
Panel_add(super, (Object*) ListItem_new(label, i << 16));
}
// Handle (&CPUMeter_class)
const MeterClass* type = &CPUMeter_class;
unsigned int cpus = pl->cpuCount;
if (cpus > 1) {
Panel_add(super, (Object*) ListItem_new("CPU average", 0));
for (unsigned int i = 1; i <= cpus; i++) {
char buffer[50];
xSnprintf(buffer, sizeof(buffer), "%s %d", type->uiName, Settings_cpuId(this->settings, i - 1));
Panel_add(super, (Object*) ListItem_new(buffer, i));
}
} else {
Panel_add(super, (Object*) ListItem_new("CPU", 1));
if (type == &DynamicMeter_class)
AvailableMetersPanel_addDynamicMeters(super, pl, i);
else
AvailableMetersPanel_addPlatformMeter(super, type, i);
}
AvailableMetersPanel_addCPUMeters(super, &CPUMeter_class, pl);

return this;
}
9 changes: 9 additions & 0 deletions CPUMeter.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ static void CPUMeter_init(Meter* this) {
}
}

// Custom uiName runtime logic to include the param (processor)
static void CPUMeter_getUiName(const Meter* this, char* buffer, size_t length) {
if (this->param > 0)
xSnprintf(buffer, sizeof(length), "%s %u", Meter_uiName(this), this->param);
else
xSnprintf(buffer, sizeof(length), "%s", Meter_uiName(this));
}

static void CPUMeter_updateValues(Meter* this) {
unsigned int cpu = this->param;
if (cpu > this->pl->cpuCount) {
Expand Down Expand Up @@ -326,6 +334,7 @@ const MeterClass CPUMeter_class = {
.display = CPUMeter_display
},
.updateValues = CPUMeter_updateValues,
.getUiName = CPUMeter_getUiName,
.defaultMode = BAR_METERMODE,
.maxItems = CPU_METER_ITEMCOUNT,
.total = 100.0,
Expand Down
56 changes: 55 additions & 1 deletion CRT.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = ColorPair(Blue, Black),
[ZFS_RATIO] = ColorPair(Magenta, Black),
[ZRAM] = ColorPair(Yellow, Black),
[DYNAMIC_GRAY] = ColorPairGrayBlack,
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
[DYNAMIC_RED] = ColorPair(Red, Black),
[DYNAMIC_GREEN] = ColorPair(Green, Black),
[DYNAMIC_BLUE] = ColorPair(Blue, Black),
[DYNAMIC_CYAN] = ColorPair(Cyan, Black),
[DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
[DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
[DYNAMIC_WHITE] = ColorPair(White, Black),
},
[COLORSCHEME_MONOCHROME] = {
[RESET_COLOR] = A_NORMAL,
Expand Down Expand Up @@ -288,6 +297,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = A_BOLD,
[ZFS_RATIO] = A_BOLD,
[ZRAM] = A_NORMAL,
[DYNAMIC_GRAY] = A_DIM,
[DYNAMIC_DARKGRAY] = A_DIM,
[DYNAMIC_RED] = A_BOLD,
[DYNAMIC_GREEN] = A_NORMAL,
[DYNAMIC_BLUE] = A_NORMAL,
[DYNAMIC_CYAN] = A_BOLD,
[DYNAMIC_MAGENTA] = A_NORMAL,
[DYNAMIC_YELLOW] = A_NORMAL,
[DYNAMIC_WHITE] = A_BOLD,
},
[COLORSCHEME_BLACKONWHITE] = {
[RESET_COLOR] = ColorPair(Black, White),
Expand Down Expand Up @@ -380,7 +398,16 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_OTHER] = ColorPair(Magenta, White),
[ZFS_COMPRESSED] = ColorPair(Cyan, White),
[ZFS_RATIO] = ColorPair(Magenta, White),
[ZRAM] = ColorPair(Yellow, White)
[ZRAM] = ColorPair(Yellow, White),
[DYNAMIC_GRAY] = ColorPair(Black, White),
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPair(Black, White),
[DYNAMIC_RED] = ColorPair(Red, White),
[DYNAMIC_GREEN] = ColorPair(Green, White),
[DYNAMIC_BLUE] = ColorPair(Blue, White),
[DYNAMIC_CYAN] = ColorPair(Yellow, White),
[DYNAMIC_MAGENTA] = ColorPair(Magenta, White),
[DYNAMIC_YELLOW] = ColorPair(Yellow, White),
[DYNAMIC_WHITE] = A_BOLD | ColorPair(Black, White),
},
[COLORSCHEME_LIGHTTERMINAL] = {
[RESET_COLOR] = ColorPair(Black, Black),
Expand Down Expand Up @@ -474,6 +501,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = ColorPair(Cyan, Black),
[ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Black),
[ZRAM] = ColorPair(Yellow, Black),
[DYNAMIC_GRAY] = ColorPairGrayBlack,
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
[DYNAMIC_RED] = ColorPair(Red, Black),
[DYNAMIC_GREEN] = ColorPair(Green, Black),
[DYNAMIC_BLUE] = ColorPair(Blue, Black),
[DYNAMIC_CYAN] = ColorPair(Cyan, Black),
[DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
[DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
[DYNAMIC_WHITE] = ColorPairWhiteDefault,
},
[COLORSCHEME_MIDNIGHT] = {
[RESET_COLOR] = ColorPair(White, Blue),
Expand Down Expand Up @@ -567,6 +603,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = A_BOLD | ColorPair(White, Blue),
[ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Blue),
[ZRAM] = A_BOLD | ColorPair(Yellow, Blue),
[DYNAMIC_GRAY] = ColorPairGrayBlack,
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
[DYNAMIC_RED] = ColorPair(Red, Blue),
[DYNAMIC_GREEN] = ColorPair(Green, Blue),
[DYNAMIC_BLUE] = ColorPair(Black, Blue),
[DYNAMIC_CYAN] = ColorPair(Cyan, Blue),
[DYNAMIC_MAGENTA] = ColorPair(Magenta, Blue),
[DYNAMIC_YELLOW] = ColorPair(Yellow, Blue),
[DYNAMIC_WHITE] = ColorPair(White, Blue),
},
[COLORSCHEME_BLACKNIGHT] = {
[RESET_COLOR] = ColorPair(Cyan, Black),
Expand Down Expand Up @@ -658,6 +703,15 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
[ZFS_COMPRESSED] = ColorPair(Blue, Black),
[ZFS_RATIO] = ColorPair(Magenta, Black),
[ZRAM] = ColorPair(Yellow, Black),
[DYNAMIC_GRAY] = ColorPairGrayBlack,
[DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,
[DYNAMIC_RED] = ColorPair(Red, Black),
[DYNAMIC_GREEN] = ColorPair(Green, Black),
[DYNAMIC_BLUE] = ColorPair(Blue, Black),
[DYNAMIC_CYAN] = ColorPair(Cyan, Black),
[DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),
[DYNAMIC_YELLOW] = ColorPair(Yellow, Black),
[DYNAMIC_WHITE] = ColorPair(White, Black),
},
[COLORSCHEME_BROKENGRAY] = { 0 } // dynamically generated.
};
Expand Down
9 changes: 9 additions & 0 deletions CRT.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ typedef enum ColorElements_ {
ZFS_COMPRESSED,
ZFS_RATIO,
ZRAM,
DYNAMIC_GRAY,
DYNAMIC_DARKGRAY,
DYNAMIC_RED,
DYNAMIC_GREEN,
DYNAMIC_BLUE,
DYNAMIC_CYAN,
DYNAMIC_MAGENTA,
DYNAMIC_YELLOW,
DYNAMIC_WHITE,
LAST_COLORELEMENT
} ColorElements;

Expand Down
4 changes: 3 additions & 1 deletion CommandLine.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ in the source distribution for its full text.

#include "Action.h"
#include "CRT.h"
#include "DynamicMeter.h"
#include "Hashtable.h"
#include "Header.h"
#include "IncSet.h"
Expand Down Expand Up @@ -288,7 +289,8 @@ int CommandLine_run(const char* name, int argc, char** argv) {
Process_setupColumnWidths();

UsersTable* ut = UsersTable_new();
ProcessList* pl = ProcessList_new(ut, flags.pidMatchList, flags.userId);
Hashtable* dm = DynamicMeters_new();
ProcessList* pl = ProcessList_new(ut, dm, flags.pidMatchList, flags.userId);

Settings* settings = Settings_new(pl->cpuCount);
pl->settings = settings;
Expand Down
98 changes: 98 additions & 0 deletions DynamicMeter.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
htop - DynamicMeter.c
(C) 2021 htop dev team
(C) 2021 Red Hat, Inc. All Rights Reserved.
Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
#include "config.h" // IWYU pragma: keep

#include "DynamicMeter.h"

#include "CRT.h"
#include "Object.h"
#include "Platform.h"
#include "ProcessList.h"
#include "RichString.h"
#include "XUtils.h"


static const int DynamicMeter_attributes[] = {
DYNAMIC_GRAY,
DYNAMIC_DARKGRAY,
DYNAMIC_RED,
DYNAMIC_GREEN,
DYNAMIC_BLUE,
DYNAMIC_CYAN,
DYNAMIC_MAGENTA,
DYNAMIC_YELLOW,
DYNAMIC_WHITE
};

Hashtable* DynamicMeters_new(void) {
return Platform_dynamicMeters();
}

typedef struct {
unsigned int key;
const char* name;
} DynamicIterator;

static void DynamicMeter_compare(ht_key_t key, void* value, void* data) {
const DynamicMeter* meter = (const DynamicMeter*)value;
DynamicIterator* iter = (DynamicIterator*)data;
if (String_eq(iter->name, meter->name))
iter->key = key;
}

unsigned int DynamicMeter_search(const ProcessList* pl, const char* name) {
DynamicIterator iter = { .key = 0, .name = name };
if (pl->dynamicMeters)
Hashtable_foreach(pl->dynamicMeters, DynamicMeter_compare, &iter);
return iter.key;
}

const char* DynamicMeter_lookup(const ProcessList* pl, unsigned int key) {
const DynamicMeter* meter = Hashtable_get(pl->dynamicMeters, key);
return meter ? meter->name : NULL;
}

static void DynamicMeter_init(Meter* meter) {
Platform_dynamicMeterInit(meter);
}

static void DynamicMeter_updateValues(Meter* meter) {
Platform_dynamicMeterUpdateValues(meter);
}

static void DynamicMeter_display(const Object* cast, RichString* out) {
const Meter* meter = (const Meter*)cast;
Platform_dynamicMeterDisplay(meter, out);
}

static void DynamicMeter_getUiName(const Meter* this, char* name, size_t length) {
const ProcessList* pl = this->pl;
const DynamicMeter* meter = Hashtable_get(pl->dynamicMeters, this->param);
if (meter) {
const char* uiName = meter->caption ? meter->caption : meter->name;
xSnprintf(name, length, "%s", uiName);
}
}

const MeterClass DynamicMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
.display = DynamicMeter_display
},
.init = DynamicMeter_init,
.updateValues = DynamicMeter_updateValues,
.getUiName = DynamicMeter_getUiName,
.defaultMode = TEXT_METERMODE,
.maxItems = 0,
.total = 100.0,
.attributes = DynamicMeter_attributes,
.name = "Dynamic",
.uiName = "Dynamic",
.caption = "",
};
25 changes: 25 additions & 0 deletions DynamicMeter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef HEADER_DynamicMeter
#define HEADER_DynamicMeter

#include "Meter.h"


typedef struct DynamicMeter_ {
char name[32]; /* unique name, cannot contain spaces */
char* caption;
char* description;
unsigned int type;
double maximum;

void* dynamicData; /* platform-specific meter data */
} DynamicMeter;

Hashtable* DynamicMeters_new(void);

const char* DynamicMeter_lookup(const ProcessList* pl, unsigned int param);

unsigned int DynamicMeter_search(const ProcessList* pl, const char* name);

extern const MeterClass DynamicMeter_class;

#endif
Loading

0 comments on commit 33ad090

Please sign in to comment.