Add a new DynamicMeter class for runtime Meter extension

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 ( https://github.com/htop-dev/htop/issues/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.
This commit is contained in:
Nathan Scott
2021-06-23 17:44:56 +10:00
parent 865b85eb2d
commit f0ed0fdafb
48 changed files with 1044 additions and 68 deletions

View File

@ -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"
@ -91,6 +92,49 @@ 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 };
assert(pl->dynamicMeters != NULL);
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;
@ -104,26 +148,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;
}