From 7a9ecebf22e9f2d09c1391471ca1a7b5b783e8f8 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Thu, 27 Mar 2025 05:37:33 +0800 Subject: [PATCH 1/6] Introduce isPercentChart member in MeterClass This property distinguishes meters that have a (relatively) fixed "total" value and meters that do not have a definite maximum value. The former meters would be drawn as a "100% stacked bar" or "graph", and the latter could have their "total" values updated automatically in bar meter mode. This commit is a prerequisite for the feature "Graph meter dynamic scaling and percent graph drawing". Signed-off-by: Kang-Che Sung --- BatteryMeter.c | 1 + CPUMeter.c | 1 + DiskIOMeter.c | 1 + FileDescriptorMeter.c | 1 + GPUMeter.c | 1 + LoadAverageMeter.c | 2 ++ MemoryMeter.c | 1 + Meter.h | 7 +++++++ NetworkIOMeter.c | 1 + SwapMeter.c | 1 + TasksMeter.c | 1 + linux/HugePageMeter.c | 1 + linux/PressureStallMeter.c | 6 ++++++ linux/ZramMeter.c | 1 + zfs/ZfsArcMeter.c | 1 + zfs/ZfsCompressedArcMeter.c | 1 + 16 files changed, 28 insertions(+) diff --git a/BatteryMeter.c b/BatteryMeter.c index 001249d4c..7ab1bac97 100644 --- a/BatteryMeter.c +++ b/BatteryMeter.c @@ -64,6 +64,7 @@ const MeterClass BatteryMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 1, + .isPercentChart = true, .total = 100.0, .attributes = BatteryMeter_attributes, .name = "Battery", diff --git a/CPUMeter.c b/CPUMeter.c index 69da88db0..ea03bb227 100644 --- a/CPUMeter.c +++ b/CPUMeter.c @@ -352,6 +352,7 @@ const MeterClass CPUMeter_class = { .defaultMode = BAR_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = CPU_METER_ITEMCOUNT, + .isPercentChart = true, .total = 100.0, .attributes = CPUMeter_attributes, .name = "CPU", diff --git a/DiskIOMeter.c b/DiskIOMeter.c index 4bb689fac..4df5c21b8 100644 --- a/DiskIOMeter.c +++ b/DiskIOMeter.c @@ -158,6 +158,7 @@ const MeterClass DiskIOMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 1, + .isPercentChart = true, .total = 1.0, .attributes = DiskIOMeter_attributes, .name = "DiskIO", diff --git a/FileDescriptorMeter.c b/FileDescriptorMeter.c index cd3baf58c..bd7585cfd 100644 --- a/FileDescriptorMeter.c +++ b/FileDescriptorMeter.c @@ -110,6 +110,7 @@ const MeterClass FileDescriptorMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 2, + .isPercentChart = false, .total = 65536.0, .attributes = FileDescriptorMeter_attributes, .name = "FileDescriptors", diff --git a/GPUMeter.c b/GPUMeter.c index 9e1685b21..0a536960e 100644 --- a/GPUMeter.c +++ b/GPUMeter.c @@ -156,6 +156,7 @@ const MeterClass GPUMeter_class = { .defaultMode = BAR_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = ARRAYSIZE(GPUMeter_attributes), + .isPercentChart = true, .total = 100.0, .attributes = GPUMeter_attributes, .name = "GPU", diff --git a/LoadAverageMeter.c b/LoadAverageMeter.c index 6bf13a03e..f70f884b3 100644 --- a/LoadAverageMeter.c +++ b/LoadAverageMeter.c @@ -111,6 +111,7 @@ const MeterClass LoadAverageMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 3, + .isPercentChart = false, .total = 100.0, .attributes = LoadAverageMeter_attributes, .name = "LoadAverage", @@ -129,6 +130,7 @@ const MeterClass LoadMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 1, + .isPercentChart = false, .total = 100.0, .attributes = LoadMeter_attributes, .name = "Load", diff --git a/MemoryMeter.c b/MemoryMeter.c index 6769e3bec..b308b7a9b 100644 --- a/MemoryMeter.c +++ b/MemoryMeter.c @@ -117,6 +117,7 @@ const MeterClass MemoryMeter_class = { .defaultMode = BAR_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = MEMORY_METER_ITEMCOUNT, + .isPercentChart = true, .total = 100.0, .attributes = MemoryMeter_attributes, .name = "Memory", diff --git a/Meter.h b/Meter.h index b53a82324..d81e8d8bc 100644 --- a/Meter.h +++ b/Meter.h @@ -75,6 +75,12 @@ typedef struct MeterClass_ { const char* const description; /* optional meter description in header setup menu */ const uint8_t maxItems; const bool isMultiColumn; /* whether the meter draws multiple sub-columns (defaults to false) */ + + /* Specifies how the meter is rendered in bar or graph mode: + true: a percent bar or graph with 'total' representing 100% or maximum. + false: the meter has no definite maximum; 'total' repesents initial + maximum value while actual maximum is updated automatically. */ + const bool isPercentChart; } MeterClass; #define As_Meter(this_) ((const MeterClass*)((this_)->super.klass)) @@ -95,6 +101,7 @@ typedef struct MeterClass_ { #define Meter_name(this_) As_Meter(this_)->name #define Meter_uiName(this_) As_Meter(this_)->uiName #define Meter_isMultiColumn(this_) As_Meter(this_)->isMultiColumn +#define Meter_isPercentChart(this_) As_Meter(this_)->isPercentChart typedef struct GraphData_ { struct timeval time; diff --git a/NetworkIOMeter.c b/NetworkIOMeter.c index 784c90ac3..79e193848 100644 --- a/NetworkIOMeter.c +++ b/NetworkIOMeter.c @@ -171,6 +171,7 @@ const MeterClass NetworkIOMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 2, + .isPercentChart = false, .total = 100.0, .attributes = NetworkIOMeter_attributes, .name = "NetworkIO", diff --git a/SwapMeter.c b/SwapMeter.c index 29c295d32..faa194556 100644 --- a/SwapMeter.c +++ b/SwapMeter.c @@ -75,6 +75,7 @@ const MeterClass SwapMeter_class = { .defaultMode = BAR_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = SWAP_METER_ITEMCOUNT, + .isPercentChart = true, .total = 100.0, .attributes = SwapMeter_attributes, .name = "Swap", diff --git a/TasksMeter.c b/TasksMeter.c index fc1e4b0ed..e87813353 100644 --- a/TasksMeter.c +++ b/TasksMeter.c @@ -74,6 +74,7 @@ const MeterClass TasksMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 4, + .isPercentChart = false, .total = 100.0, .attributes = TasksMeter_attributes, .name = "Tasks", diff --git a/linux/HugePageMeter.c b/linux/HugePageMeter.c index bb975f942..f9a2241a3 100644 --- a/linux/HugePageMeter.c +++ b/linux/HugePageMeter.c @@ -101,6 +101,7 @@ const MeterClass HugePageMeter_class = { .defaultMode = BAR_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = ARRAYSIZE(HugePageMeter_active_labels), + .isPercentChart = true, .total = 100.0, .attributes = HugePageMeter_attributes, .name = "HugePages", diff --git a/linux/PressureStallMeter.c b/linux/PressureStallMeter.c index 5010c11d2..942213ea5 100644 --- a/linux/PressureStallMeter.c +++ b/linux/PressureStallMeter.c @@ -77,6 +77,7 @@ const MeterClass PressureStallCPUSomeMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 3, + .isPercentChart = true, .total = 100.0, .attributes = PressureStallMeter_attributes, .name = "PressureStallCPUSome", @@ -95,6 +96,7 @@ const MeterClass PressureStallIOSomeMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 3, + .isPercentChart = true, .total = 100.0, .attributes = PressureStallMeter_attributes, .name = "PressureStallIOSome", @@ -113,6 +115,7 @@ const MeterClass PressureStallIOFullMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 3, + .isPercentChart = true, .total = 100.0, .attributes = PressureStallMeter_attributes, .name = "PressureStallIOFull", @@ -131,6 +134,7 @@ const MeterClass PressureStallIRQFullMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 3, + .isPercentChart = true, .total = 100.0, .attributes = PressureStallMeter_attributes, .name = "PressureStallIRQFull", @@ -149,6 +153,7 @@ const MeterClass PressureStallMemorySomeMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 3, + .isPercentChart = true, .total = 100.0, .attributes = PressureStallMeter_attributes, .name = "PressureStallMemorySome", @@ -167,6 +172,7 @@ const MeterClass PressureStallMemoryFullMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 3, + .isPercentChart = true, .total = 100.0, .attributes = PressureStallMeter_attributes, .name = "PressureStallMemoryFull", diff --git a/linux/ZramMeter.c b/linux/ZramMeter.c index 2a1c7715c..fe10c3baa 100644 --- a/linux/ZramMeter.c +++ b/linux/ZramMeter.c @@ -77,6 +77,7 @@ const MeterClass ZramMeter_class = { .defaultMode = BAR_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = ZRAM_METER_ITEMCOUNT, + .isPercentChart = true, .total = 100.0, .attributes = ZramMeter_attributes, .name = "Zram", diff --git a/zfs/ZfsArcMeter.c b/zfs/ZfsArcMeter.c index 87b7e19ce..8177ff2a5 100644 --- a/zfs/ZfsArcMeter.c +++ b/zfs/ZfsArcMeter.c @@ -93,6 +93,7 @@ const MeterClass ZfsArcMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 6, + .isPercentChart = true, .total = 100.0, .attributes = ZfsArcMeter_attributes, .name = "ZFSARC", diff --git a/zfs/ZfsCompressedArcMeter.c b/zfs/ZfsCompressedArcMeter.c index 35ab8b379..cd3bf43f6 100644 --- a/zfs/ZfsCompressedArcMeter.c +++ b/zfs/ZfsCompressedArcMeter.c @@ -81,6 +81,7 @@ const MeterClass ZfsCompressedArcMeter_class = { .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 1, + .isPercentChart = true, .total = 100.0, .attributes = ZfsCompressedArcMeter_attributes, .name = "ZFSCARC", From 1dbae33d157ebeae099b0725650a5ded79663af0 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Thu, 27 Mar 2025 05:37:36 +0800 Subject: [PATCH 2/6] Update "total" value for non-percent bar meters If "isPercentChart" of a meter is false, update its "total" value automatically in the bar meter mode. The "total" value is capped to DBL_MAX in order to ensure the division never produces NaN. The newly introduced Meter_computeSum() function will be reused by the feature "Graph meter dynamic scaling and percent graph drawing". Signed-off-by: Kang-Che Sung --- Meter.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Meter.c b/Meter.c index 7a9e5ff46..f1bf6f0d8 100644 --- a/Meter.c +++ b/Meter.c @@ -10,6 +10,7 @@ in the source distribution for its full text. #include "Meter.h" #include +#include #include // IWYU pragma: keep #include #include @@ -47,6 +48,14 @@ static inline void Meter_displayBuffer(const Meter* this, RichString* out) { } } +static double Meter_computeSum(const Meter* this) { + assert(this->curItems > 0); + assert(this->values); + double sum = sumPositiveValues(this->values, this->curItems); + // Prevent rounding to infinity in IEEE 754 + return MINIMUM(DBL_MAX, sum); +} + /* ---------- TextMeterMode ---------- */ static void TextMeterMode_draw(Meter* this, int x, int y, int w) { @@ -100,6 +109,12 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) { w--; } + // Update the "total" if necessary + if (!Meter_isPercentChart(this) && this->curItems > 0) { + double sum = Meter_computeSum(this); + this->total = MAXIMUM(sum, this->total); + } + if (w < 1) { goto end; } From 314a57ac34ba7f3fb3b9d66c08c9dca63b4b0c83 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Thu, 7 Aug 2025 20:12:50 +0800 Subject: [PATCH 3/6] Graph meter dynamic scaling and percent graph drawing Implement dynamic scaling for Graph meter mode, and separate it from "100% graph" drawing. This is controlled by the "isPercentChart" property of a MeterClass. If "isPercentChart" is true, the meter would be drawn as a "100% graph". Graph meters now expect the "total" value may change, and the newly changed "total" value no longer affects the percent graph drawings of earlier meter values. If "isPercentChart" is false, the meter would be drawn with a dynamic scale. Signed-off-by: Kang-Che Sung --- Meter.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Meter.c b/Meter.c index f1bf6f0d8..4f8f76673 100644 --- a/Meter.c +++ b/Meter.c @@ -230,9 +230,12 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) { } w -= captionLen; + // Prepare parameters for drawing assert(this->h >= 1); int h = this->h; + bool isPercentChart = Meter_isPercentChart(this); + GraphData* data = &this->drawData; // Expand the graph data buffer if necessary @@ -261,8 +264,10 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) { data->values[nValues - 1] = 0.0; if (this->curItems > 0) { - assert(this->values); - data->values[nValues - 1] = sumPositiveValues(this->values, this->curItems); + data->values[nValues - 1] = Meter_computeSum(this); + if (isPercentChart && this->total > 0.0) { + data->values[nValues - 1] /= this->total; + } } } @@ -292,10 +297,19 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) { } size_t i = nValues - (size_t)w * 2; + // Determine the graph scale + double total = 1.0; + if (!isPercentChart) { + for (size_t j = i; j < nValues; j++) { + total = MAXIMUM(data->values[j], total); + } + assert(total <= DBL_MAX); + } + assert(total >= 1.0); + // Draw the actual graph for (int col = 0; i < nValues - 1; i += 2, col++) { int pix = GraphMeterMode_pixPerRow * h; - double total = MAXIMUM(this->total, 1); int v1 = (int) lround(CLAMP(data->values[i] / total * pix, 1.0, pix)); int v2 = (int) lround(CLAMP(data->values[i + 1] / total * pix, 1.0, pix)); From 5f9af8dd2494de16698aafbf1194093bd766b5df Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Thu, 7 Aug 2025 20:12:54 +0800 Subject: [PATCH 4/6] Adjust LoadAverageMeter "total" assignment The meter now has a dynamic scale, and so the "total" value can be updated automatically by the bar meter drawing code. The initial "total" value for LoadAverageMeter is now always the number of active CPUs (processor threads). Signed-off-by: Kang-Che Sung --- LoadAverageMeter.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/LoadAverageMeter.c b/LoadAverageMeter.c index f70f884b3..a0b05f2be 100644 --- a/LoadAverageMeter.c +++ b/LoadAverageMeter.c @@ -46,15 +46,15 @@ static void LoadAverageMeter_updateValues(Meter* this) { this->curItems = 1; // change bar color and total based on value + if (this->total < this->host->activeCPUs) { + this->total = this->host->activeCPUs; + } if (this->values[0] < 1.0) { this->curAttributes = OK_attributes; - this->total = 1.0; } else if (this->values[0] < this->host->activeCPUs) { this->curAttributes = Medium_attributes; - this->total = this->host->activeCPUs; } else { this->curAttributes = High_attributes; - this->total = 2 * this->host->activeCPUs; } xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.2f/%.2f/%.2f", this->values[0], this->values[1], this->values[2]); @@ -78,15 +78,15 @@ static void LoadMeter_updateValues(Meter* this) { Platform_getLoadAverage(&this->values[0], &five, &fifteen); // change bar color and total based on value + if (this->total < this->host->activeCPUs) { + this->total = this->host->activeCPUs; + } if (this->values[0] < 1.0) { this->curAttributes = OK_attributes; - this->total = 1.0; } else if (this->values[0] < this->host->activeCPUs) { this->curAttributes = Medium_attributes; - this->total = this->host->activeCPUs; } else { this->curAttributes = High_attributes; - this->total = 2 * this->host->activeCPUs; } xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.2f", this->values[0]); @@ -112,7 +112,7 @@ const MeterClass LoadAverageMeter_class = { .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 3, .isPercentChart = false, - .total = 100.0, + .total = 1.0, .attributes = LoadAverageMeter_attributes, .name = "LoadAverage", .uiName = "Load average", @@ -131,7 +131,7 @@ const MeterClass LoadMeter_class = { .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 1, .isPercentChart = false, - .total = 100.0, + .total = 1.0, .attributes = LoadMeter_attributes, .name = "Load", .uiName = "Load", From 8b4b19552d3b5edcb1ccea5f50379feeb86d48c7 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Thu, 7 Aug 2025 20:12:57 +0800 Subject: [PATCH 5/6] TasksMeter: remove code for auto-updating "total" The bar meter drawing code can now update the "total" value automatically. Signed-off-by: Kang-Che Sung --- TasksMeter.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TasksMeter.c b/TasksMeter.c index e87813353..f522b753b 100644 --- a/TasksMeter.c +++ b/TasksMeter.c @@ -34,7 +34,6 @@ static void TasksMeter_updateValues(Meter* this) { this->values[1] = pt->userlandThreads; this->values[2] = pt->totalTasks - pt->kernelThreads - pt->userlandThreads; this->values[3] = MINIMUM(pt->runningTasks, host->activeCPUs); - this->total = pt->totalTasks; xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%u/%u", MINIMUM(pt->runningTasks, host->activeCPUs), pt->totalTasks); } @@ -75,7 +74,7 @@ const MeterClass TasksMeter_class = { .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 4, .isPercentChart = false, - .total = 100.0, + .total = 1.0, .attributes = TasksMeter_attributes, .name = "Tasks", .uiName = "Task counter", From 496b3fb7fd655ba5a74480b7e60ade6185f68e8c Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Thu, 7 Aug 2025 20:12:59 +0800 Subject: [PATCH 6/6] NetworkIOMeter: remove code for auto-updating "total" The bar meter drawing code can now update the "total" value automatically. Signed-off-by: Kang-Che Sung --- NetworkIOMeter.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/NetworkIOMeter.c b/NetworkIOMeter.c index 79e193848..66045ffe2 100644 --- a/NetworkIOMeter.c +++ b/NetworkIOMeter.c @@ -111,9 +111,6 @@ static void NetworkIOMeter_updateValues(Meter* this) { this->values[0] = cached_rxb_diff; this->values[1] = cached_txb_diff; - if (cached_rxb_diff + cached_txb_diff > this->total) { - this->total = cached_rxb_diff + cached_txb_diff; - } if (status == RATESTATUS_NODATA) { xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); @@ -172,7 +169,7 @@ const MeterClass NetworkIOMeter_class = { .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 2, .isPercentChart = false, - .total = 100.0, + .total = 1.0, .attributes = NetworkIOMeter_attributes, .name = "NetworkIO", .uiName = "Network IO",