htop/Meter.c

488 lines
13 KiB
C
Raw Permalink Normal View History

2006-03-04 18:16:49 +00:00
/*
htop - Meter.c
2011-05-26 16:35:07 +00:00
(C) 2004-2011 Hisham H. Muhammad
Released under the GNU GPLv2+, see the COPYING file
2006-03-04 18:16:49 +00:00
in the source distribution for its full text.
*/
#include "config.h" // IWYU pragma: keep
2006-03-04 18:16:49 +00:00
#include "Meter.h"
2011-12-26 21:35:57 +00:00
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
2006-03-04 18:16:49 +00:00
#include "CRT.h"
#include "Macros.h"
#include "Object.h"
#include "ProvideCurses.h"
#include "RichString.h"
2020-12-06 14:22:41 +00:00
#include "Settings.h"
2020-10-14 18:21:09 +00:00
#include "XUtils.h"
2009-06-02 04:51:23 +00:00
2006-03-04 18:16:49 +00:00
#define GRAPH_HEIGHT 4 /* Unit: rows (lines) */
2020-10-05 11:19:50 +00:00
const MeterClass Meter_class = {
.super = {
.extends = Class(Object)
}
};
2006-03-04 18:16:49 +00:00
Meter* Meter_new(const struct ProcessList_* pl, unsigned int param, const MeterClass* type) {
2016-02-02 14:53:02 +00:00
Meter* this = xCalloc(1, sizeof(Meter));
Object_setClass(this, type);
this->h = 1;
this->param = param;
this->pl = pl;
this->curItems = type->maxItems;
this->curAttributes = NULL;
this->values = type->maxItems ? xCalloc(type->maxItems, sizeof(double)) : NULL;
this->total = type->total;
2016-02-02 14:53:02 +00:00
this->caption = xStrdup(type->caption);
2020-11-01 00:09:51 +00:00
if (Meter_initFn(this)) {
Meter_init(this);
2020-11-01 00:09:51 +00:00
}
Meter_setMode(this, type->defaultMode);
return this;
2006-03-04 18:16:49 +00:00
}
int Meter_humanUnit(char* buffer, unsigned long int value, size_t size) {
2020-10-31 22:28:02 +00:00
const char* prefix = "KMGTPEZY";
2015-08-28 00:37:06 +00:00
unsigned long int powi = 1;
unsigned int powj = 1, precision = 2;
2020-10-31 19:55:36 +00:00
for (;;) {
2015-08-28 00:37:06 +00:00
if (value / 1024 < powi)
break;
if (prefix[1] == '\0')
2015-08-28 00:37:06 +00:00
break;
2015-08-28 00:37:06 +00:00
powi *= 1024;
++prefix;
}
if (*prefix == 'K')
precision = 0;
2015-08-28 00:37:06 +00:00
for (; precision > 0; precision--) {
powj *= 10;
if (value / powi < powj)
break;
}
return snprintf(buffer, size, "%.*f%c", precision, (double) value / powi, *prefix);
}
2006-03-04 18:16:49 +00:00
void Meter_delete(Object* cast) {
if (!cast)
return;
2020-11-01 00:09:51 +00:00
2006-03-04 18:16:49 +00:00
Meter* this = (Meter*) cast;
if (Meter_doneFn(this)) {
Meter_done(this);
}
free(this->drawData);
free(this->caption);
free(this->values);
2006-03-04 18:16:49 +00:00
free(this);
}
2010-02-25 01:43:18 +00:00
void Meter_setCaption(Meter* this, const char* caption) {
free_and_xStrdup(&this->caption, caption);
}
2020-10-06 11:13:16 +00:00
static inline void Meter_displayBuffer(const Meter* this, RichString* out) {
if (Object_displayFn(this)) {
Object_display(this, out);
} else {
2020-10-06 11:13:16 +00:00
RichString_writeWide(out, CRT_colors[Meter_attributes(this)[0]], this->txtBuffer);
2006-03-04 18:16:49 +00:00
}
}
void Meter_setMode(Meter* this, int modeIndex) {
2020-11-01 00:09:51 +00:00
if (modeIndex > 0 && modeIndex == this->mode) {
return;
2020-11-01 00:09:51 +00:00
}
if (!modeIndex) {
modeIndex = 1;
2020-11-01 00:09:51 +00:00
}
assert(modeIndex < LAST_METERMODE);
if (Meter_defaultMode(this) == CUSTOM_METERMODE) {
this->draw = Meter_drawFn(this);
2020-11-01 00:09:51 +00:00
if (Meter_updateModeFn(this)) {
Meter_updateMode(this, modeIndex);
2020-11-01 00:09:51 +00:00
}
} else {
2006-05-09 18:18:08 +00:00
assert(modeIndex >= 1);
free(this->drawData);
this->drawData = NULL;
2006-05-09 18:18:08 +00:00
2020-10-05 11:19:50 +00:00
const MeterMode* mode = Meter_modes[modeIndex];
2006-05-09 18:18:08 +00:00
this->draw = mode->draw;
this->h = mode->h;
2006-03-04 18:16:49 +00:00
}
this->mode = modeIndex;
2006-03-04 18:16:49 +00:00
}
ListItem* Meter_toListItem(const Meter* this, bool moving) {
char mode[20];
2020-11-01 00:09:51 +00:00
if (this->mode) {
xSnprintf(mode, sizeof(mode), " [%s]", Meter_modes[this->mode]->uiName);
2020-11-01 00:09:51 +00:00
} else {
mode[0] = '\0';
2020-11-01 00:09:51 +00:00
}
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.
2021-06-23 07:44:56 +00:00
char name[32];
if (Meter_getUiNameFn(this))
Meter_getUiName(this, name, sizeof(name));
else
xSnprintf(name, sizeof(name), "%s", Meter_uiName(this));
char buffer[50];
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.
2021-06-23 07:44:56 +00:00
xSnprintf(buffer, sizeof(buffer), "%s%s", name, mode);
ListItem* li = ListItem_new(buffer, 0);
li->moving = moving;
return li;
2006-03-04 18:16:49 +00:00
}
/* ---------- TextMeterMode ---------- */
static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
mvaddnstr(y, x, caption, w - 1);
attrset(CRT_colors[RESET_COLOR]);
int captionLen = strlen(caption);
x += captionLen;
w -= captionLen;
if (w <= 0)
return;
RichString_begin(out);
2020-10-06 11:13:16 +00:00
Meter_displayBuffer(this, &out);
RichString_printoffnVal(out, y, x, 0, w - 1);
RichString_delete(&out);
}
/* ---------- BarMeterMode ---------- */
static const char BarMeterMode_characters[] = "|#*@$%&.";
static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
const char* caption = Meter_getCaption(this);
2006-03-04 18:16:49 +00:00
w -= 2;
attrset(CRT_colors[METER_TEXT]);
int captionLen = 3;
mvaddnstr(y, x, caption, captionLen);
2006-03-04 18:16:49 +00:00
x += captionLen;
w -= captionLen;
attrset(CRT_colors[BAR_BORDER]);
mvaddch(y, x, '[');
mvaddch(y, x + MAXIMUM(w, 0), ']');
attrset(CRT_colors[RESET_COLOR]);
2019-10-31 16:39:12 +00:00
2006-03-04 18:16:49 +00:00
w--;
x++;
if (w < 1)
return;
2019-10-31 16:39:12 +00:00
// The text in the bar is right aligned;
2020-12-20 16:15:51 +00:00
// Pad with maximal spaces and then calculate needed starting position offset
RichString_begin(bar);
RichString_appendChr(&bar, 0, ' ', w);
2020-10-06 11:13:16 +00:00
RichString_appendWide(&bar, 0, this->txtBuffer);
int startPos = RichString_sizeVal(bar) - w;
if (startPos > w) {
// Text is too large for bar
2020-12-20 16:15:51 +00:00
// Truncate meter text at a space character
for (int pos = 2 * w; pos > w; pos--) {
if (RichString_getCharVal(bar, pos) == ' ') {
while (pos > w && RichString_getCharVal(bar, pos - 1) == ' ')
pos--;
startPos = pos - w;
break;
}
}
// If still too large, print the start not the end
startPos = MINIMUM(startPos, w);
}
assert(startPos >= 0);
assert(startPos <= w);
assert(startPos + w <= RichString_sizeVal(bar));
2006-03-04 18:16:49 +00:00
int blockSizes[10];
2006-03-04 18:16:49 +00:00
// First draw in the bar[] buffer...
int offset = 0;
for (uint8_t i = 0; i < this->curItems; i++) {
2006-03-04 18:16:49 +00:00
double value = this->values[i];
value = CLAMP(value, 0.0, this->total);
2006-03-04 18:16:49 +00:00
if (value > 0) {
2020-10-31 22:28:02 +00:00
blockSizes[i] = ceil((value / this->total) * w);
2006-03-04 18:16:49 +00:00
} else {
blockSizes[i] = 0;
}
int nextOffset = offset + blockSizes[i];
// (Control against invalid values)
nextOffset = CLAMP(nextOffset, 0, w);
2006-03-04 18:16:49 +00:00
for (int j = offset; j < nextOffset; j++)
if (RichString_getCharVal(bar, startPos + j) == ' ') {
2006-03-04 18:16:49 +00:00
if (CRT_colorScheme == COLORSCHEME_MONOCHROME) {
RichString_setChar(&bar, startPos + j, BarMeterMode_characters[i]);
2006-03-04 18:16:49 +00:00
} else {
RichString_setChar(&bar, startPos + j, '|');
2006-03-04 18:16:49 +00:00
}
}
offset = nextOffset;
}
// ...then print the buffer.
offset = 0;
for (uint8_t i = 0; i < this->curItems; i++) {
int attr = this->curAttributes ? this->curAttributes[i] : Meter_attributes(this)[i];
RichString_setAttrn(&bar, CRT_colors[attr], startPos + offset, blockSizes[i]);
RichString_printoffnVal(bar, y, x + offset, startPos + offset, MINIMUM(blockSizes[i], w - offset));
2006-03-04 18:16:49 +00:00
offset += blockSizes[i];
offset = CLAMP(offset, 0, w);
2006-03-04 18:16:49 +00:00
}
if (offset < w) {
RichString_setAttrn(&bar, CRT_colors[BAR_SHADOW], startPos + offset, w - offset);
RichString_printoffnVal(bar, y, x + offset, startPos + offset, w - offset);
2006-03-04 18:16:49 +00:00
}
RichString_delete(&bar);
2006-03-04 18:16:49 +00:00
move(y, x + w + 1);
attrset(CRT_colors[RESET_COLOR]);
}
/* ---------- GraphMeterMode ---------- */
#ifdef HAVE_LIBNCURSESW
#define PIXPERROW_UTF8 4
static const char* const GraphMeterMode_dotsUtf8[] = {
/*00*/" ", /*01*/"", /*02*/"", /*03*/"", /*04*/ "",
/*10*/"", /*11*/"", /*12*/"", /*13*/"", /*14*/ "",
/*20*/"", /*21*/"", /*22*/"", /*23*/"", /*24*/ "",
/*30*/"", /*31*/"", /*32*/"", /*33*/"", /*34*/ "",
/*40*/"", /*41*/"", /*42*/"", /*43*/"", /*44*/ ""
};
#endif
#define PIXPERROW_ASCII 2
static const char* const GraphMeterMode_dotsAscii[] = {
/*00*/" ", /*01*/".", /*02*/":",
/*10*/".", /*11*/".", /*12*/":",
/*20*/":", /*21*/":", /*22*/":"
};
static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
const ProcessList* pl = this->pl;
2020-11-01 00:09:51 +00:00
if (!this->drawData) {
this->drawData = xCalloc(1, sizeof(GraphData));
}
2020-10-02 14:27:57 +00:00
GraphData* data = this->drawData;
2020-10-06 11:13:16 +00:00
const int nValues = METER_GRAPHDATA_SIZE;
const char* const* GraphMeterMode_dots;
int GraphMeterMode_pixPerRow;
#ifdef HAVE_LIBNCURSESW
if (CRT_utf8) {
GraphMeterMode_dots = GraphMeterMode_dotsUtf8;
2015-08-20 04:27:07 +00:00
GraphMeterMode_pixPerRow = PIXPERROW_UTF8;
} else
#endif
{
GraphMeterMode_dots = GraphMeterMode_dotsAscii;
2015-08-20 04:27:07 +00:00
GraphMeterMode_pixPerRow = PIXPERROW_ASCII;
}
const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
int captionLen = 3;
mvaddnstr(y, x, caption, captionLen);
x += captionLen;
w -= captionLen;
2019-10-31 16:39:12 +00:00
if (!timercmp(&pl->realtime, &(data->time), <)) {
int globalDelay = this->pl->settings->delay;
struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay % 10) * 100000L };
timeradd(&pl->realtime, &delay, &(data->time));
for (int i = 0; i < nValues - 1; i++)
2020-10-31 22:28:02 +00:00
data->values[i] = data->values[i + 1];
2019-10-31 16:39:12 +00:00
double value = 0.0;
for (uint8_t i = 0; i < this->curItems; i++)
value += this->values[i];
data->values[nValues - 1] = value;
}
2019-10-31 16:39:12 +00:00
2020-10-31 22:28:02 +00:00
int i = nValues - (w * 2) + 2, k = 0;
if (i < 0) {
2020-10-31 22:28:02 +00:00
k = -i / 2;
i = 0;
}
2020-10-31 22:28:02 +00:00
for (; i < nValues - 1; i += 2, k++) {
int pix = GraphMeterMode_pixPerRow * GRAPH_HEIGHT;
if (this->total < 1)
this->total = 1;
int v1 = CLAMP((int) lround(data->values[i] / this->total * pix), 1, pix);
int v2 = CLAMP((int) lround(data->values[i + 1] / this->total * pix), 1, pix);
int colorIdx = GRAPH_1;
for (int line = 0; line < GRAPH_HEIGHT; line++) {
int line1 = CLAMP(v1 - (GraphMeterMode_pixPerRow * (GRAPH_HEIGHT - 1 - line)), 0, GraphMeterMode_pixPerRow);
int line2 = CLAMP(v2 - (GraphMeterMode_pixPerRow * (GRAPH_HEIGHT - 1 - line)), 0, GraphMeterMode_pixPerRow);
attrset(CRT_colors[colorIdx]);
2020-10-31 22:28:02 +00:00
mvaddstr(y + line, x + k, GraphMeterMode_dots[line1 * (GraphMeterMode_pixPerRow + 1) + line2]);
colorIdx = GRAPH_2;
}
}
2006-03-04 18:16:49 +00:00
attrset(CRT_colors[RESET_COLOR]);
}
/* ---------- LEDMeterMode ---------- */
static const char* const LEDMeterMode_digitsAscii[] = {
2020-10-31 22:28:02 +00:00
" __ ", " ", " __ ", " __ ", " ", " __ ", " __ ", " __ ", " __ ", " __ ",
"| |", " |", " __|", " __|", "|__|", "|__ ", "|__ ", " |", "|__|", "|__|",
"|__|", " |", "|__ ", " __|", " |", " __|", "|__|", " |", "|__|", " __|"
};
2006-03-04 18:16:49 +00:00
#ifdef HAVE_LIBNCURSESW
static const char* const LEDMeterMode_digitsUtf8[] = {
2020-10-31 22:28:02 +00:00
"┌──┐", "", "╶──┐", "╶──┐", "╷ ╷", "┌──╴", "┌──╴", "╶──┐", "┌──┐", "┌──┐",
"│ │", "", "┌──┘", " ──┤", "└──┤", "└──┐", "├──┐", "", "├──┤", "└──┤",
"└──┘", "", "└──╴", "╶──┘", "", "╶──┘", "└──┘", "", "└──┘", " ──┘"
};
#endif
static const char* const* LEDMeterMode_digits;
static void LEDMeterMode_drawDigit(int x, int y, int n) {
for (int i = 0; i < 3; i++)
2021-07-14 17:18:27 +00:00
mvaddstr(y + i, x, LEDMeterMode_digits[i * 10 + n]);
}
static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
#ifdef HAVE_LIBNCURSESW
if (CRT_utf8)
LEDMeterMode_digits = LEDMeterMode_digitsUtf8;
else
#endif
LEDMeterMode_digits = LEDMeterMode_digitsAscii;
RichString_begin(out);
2020-10-06 11:13:16 +00:00
Meter_displayBuffer(this, &out);
int yText =
#ifdef HAVE_LIBNCURSESW
2020-10-31 22:28:02 +00:00
CRT_utf8 ? y + 1 :
#endif
2020-10-31 22:28:02 +00:00
y + 2;
2006-03-04 18:16:49 +00:00
attrset(CRT_colors[LED_COLOR]);
const char* caption = Meter_getCaption(this);
mvaddstr(yText, x, caption);
int xx = x + strlen(caption);
int len = RichString_sizeVal(out);
for (int i = 0; i < len; i++) {
2020-11-24 16:52:05 +00:00
int c = RichString_getCharVal(out, i);
2006-03-04 18:16:49 +00:00
if (c >= '0' && c <= '9') {
if (xx - x + 4 > w)
break;
LEDMeterMode_drawDigit(xx, y, c - '0');
xx += 4;
2006-03-04 18:16:49 +00:00
} else {
if (xx - x + 1 > w)
break;
#ifdef HAVE_LIBNCURSESW
const cchar_t wc = { .chars = { c, '\0' }, .attr = 0 }; /* use LED_COLOR from attrset() */
mvadd_wch(yText, xx, &wc);
#else
mvaddch(yText, xx, c);
#endif
2006-03-04 18:16:49 +00:00
xx += 1;
}
}
attrset(CRT_colors[RESET_COLOR]);
RichString_delete(&out);
2006-03-04 18:16:49 +00:00
}
static MeterMode BarMeterMode = {
.uiName = "Bar",
.h = 1,
.draw = BarMeterMode_draw,
};
static MeterMode TextMeterMode = {
.uiName = "Text",
.h = 1,
.draw = TextMeterMode_draw,
};
static MeterMode GraphMeterMode = {
.uiName = "Graph",
.h = GRAPH_HEIGHT,
.draw = GraphMeterMode_draw,
};
static MeterMode LEDMeterMode = {
.uiName = "LED",
.h = 3,
.draw = LEDMeterMode_draw,
};
2020-10-05 11:19:50 +00:00
const MeterMode* const Meter_modes[] = {
NULL,
&BarMeterMode,
&TextMeterMode,
&GraphMeterMode,
&LEDMeterMode,
NULL
};
2014-02-27 19:35:22 +00:00
/* Blank meter */
2020-10-06 11:13:16 +00:00
static void BlankMeter_updateValues(Meter* this) {
this->txtBuffer[0] = '\0';
2014-02-27 19:35:22 +00:00
}
static void BlankMeter_display(ATTR_UNUSED const Object* cast, ATTR_UNUSED RichString* out) {
2014-02-27 19:35:22 +00:00
}
static const int BlankMeter_attributes[] = {
2014-02-27 19:35:22 +00:00
DEFAULT_COLOR
};
2020-10-05 11:19:50 +00:00
const MeterClass BlankMeter_class = {
2014-02-27 19:35:22 +00:00
.super = {
.extends = Class(Meter),
.delete = Meter_delete,
.display = BlankMeter_display,
},
.updateValues = BlankMeter_updateValues,
2014-02-27 19:35:22 +00:00
.defaultMode = TEXT_METERMODE,
.maxItems = 0,
2014-02-27 19:35:22 +00:00
.total = 100.0,
.attributes = BlankMeter_attributes,
.name = "Blank",
.uiName = "Blank",
.caption = ""
};