From fdf6309c2d33508aaf9baf80f1c7897c6beb3d2e Mon Sep 17 00:00:00 2001 From: Abhiram Shibu Date: Sun, 19 Jul 2026 13:43:32 +0530 Subject: [PATCH] Add zswap pool usage meters Add bar and statistics meters for zswap pool usage, including disabled and unavailable states. Assisted-by: OpenAI Codex --- Makefile.am | 2 + linux/LinuxMachine.c | 44 +++++++++- linux/Platform.c | 3 + linux/ZswapMeter.c | 200 +++++++++++++++++++++++++++++++++++++++++++ linux/ZswapMeter.h | 28 ++++++ linux/ZswapStats.h | 10 +++ 6 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 linux/ZswapMeter.c create mode 100644 linux/ZswapMeter.h diff --git a/Makefile.am b/Makefile.am index 4d83bbcb9..115a07e89 100644 --- a/Makefile.am +++ b/Makefile.am @@ -206,6 +206,7 @@ linux_platform_headers = \ linux/SystemdMeter.h \ linux/ZramMeter.h \ linux/ZramStats.h \ + linux/ZswapMeter.h \ linux/ZswapStats.h \ zfs/ZfsArcMeter.h \ zfs/ZfsArcStats.h \ @@ -231,6 +232,7 @@ linux_platform_sources = \ linux/SELinuxMeter.c \ linux/SystemdMeter.c \ linux/ZramMeter.c \ + linux/ZswapMeter.c \ zfs/ZfsArcMeter.c \ zfs/ZfsCompressedArcMeter.c diff --git a/linux/LinuxMachine.c b/linux/LinuxMachine.c index b9fa4491c..495caca97 100644 --- a/linux/LinuxMachine.c +++ b/linux/LinuxMachine.c @@ -141,6 +141,8 @@ static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) { memory_t sreclaimableMem = 0; memory_t zswapCompMem = 0; memory_t zswapOrigMem = 0; + bool zswapCompAvailable = false; + bool zswapOrigAvailable = false; FILE* file = fopen(PROCMEMINFOFILE, "r"); if (!file) @@ -157,6 +159,15 @@ static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) { } \ break; \ } else (void) 0 /* Require a ";" after the macro use. */ + #define tryReadFlag(label, variable, flag) \ + if (String_startsWith(buffer, label)) { \ + memory_t parsed_; \ + (flag) = sscanf(buffer + strlen(label), "%llu kB", &parsed_) == 1; \ + if (flag) { \ + (variable) = parsed_; \ + } \ + break; \ + } else (void) 0 /* Require a ";" after the macro use. */ switch (buffer[0]) { case 'M': @@ -186,11 +197,12 @@ static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) { } break; case 'Z': - tryRead("Zswap:", zswapCompMem); - tryRead("Zswapped:", zswapOrigMem); + tryReadFlag("Zswap:", zswapCompMem, zswapCompAvailable); + tryReadFlag("Zswapped:", zswapOrigMem, zswapOrigAvailable); break; } + #undef tryReadFlag #undef tryRead } @@ -214,10 +226,37 @@ static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) { host->totalSwap = swapTotalMem; host->usedSwap = swapTotalMem - swapFreeMem - swapCacheMem; host->cachedSwap = swapCacheMem; + this->zswap.available = zswapCompAvailable && zswapOrigAvailable; this->zswap.usedZswapComp = zswapCompMem; this->zswap.usedZswapOrig = zswapOrigMem; } +static void LinuxMachine_scanZswapInfo(LinuxMachine* this) { + ZswapStats* zswap = &this->zswap; + + zswap->enabled = false; + zswap->hasPoolLimit = false; + zswap->totalZswapPool = 0; + + if (!zswap->available) + return; + + /* If the parameter is unavailable, counters from /proc/meminfo imply enabled. */ + char buffer[16]; + ssize_t enabledRead = Compat_readfile("/sys/module/zswap/parameters/enabled", buffer, sizeof(buffer)); + zswap->enabled = enabledRead <= 0 || (buffer[0] != 'N' && buffer[0] != 'n'); + + if (!zswap->enabled) + return; + + ssize_t limitRead = Compat_readfile("/sys/module/zswap/parameters/max_pool_percent", buffer, sizeof(buffer)); + unsigned int maxPoolPercent; + if (limitRead > 0 && sscanf(buffer, "%u", &maxPoolPercent) == 1 && maxPoolPercent <= 100) { + zswap->hasPoolLimit = true; + zswap->totalZswapPool = this->super.totalMem * maxPoolPercent / 100; + } +} + static void LinuxMachine_scanHugePages(LinuxMachine* this) { this->totalHugePageMem = 0; for (size_t i = 0; i < HTOP_HUGEPAGE_COUNT; i++) { @@ -801,6 +840,7 @@ void Machine_scan(Machine* super) { LinuxMachine* this = (LinuxMachine*) super; LinuxMachine_scanMemoryInfo(this); + LinuxMachine_scanZswapInfo(this); LinuxMachine_scanHugePages(this); LinuxMachine_scanZfsArcstats(this); LinuxMachine_scanZramInfo(this); diff --git a/linux/Platform.c b/linux/Platform.c index cecaf3b5d..bed9ecee2 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -58,6 +58,7 @@ in the source distribution for its full text. #include "linux/SystemdMeter.h" #include "linux/ZramMeter.h" #include "linux/ZramStats.h" +#include "linux/ZswapMeter.h" #include "linux/ZswapStats.h" #include "zfs/ZfsArcMeter.h" #include "zfs/ZfsArcStats.h" @@ -266,6 +267,8 @@ const MeterClass* const Platform_meterTypes[] = { &ZfsArcMeter_class, &ZfsCompressedArcMeter_class, &ZramMeter_class, + &ZswapMeter_class, + &ZswapStatsMeter_class, &DiskIORateMeter_class, &DiskIOTimeMeter_class, &DiskIOMeter_class, diff --git a/linux/ZswapMeter.c b/linux/ZswapMeter.c new file mode 100644 index 000000000..7e86e80bf --- /dev/null +++ b/linux/ZswapMeter.c @@ -0,0 +1,200 @@ +/* +htop - linux/ZswapMeter.c +(C) 2026 Abhiram Shibu +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "linux/ZswapMeter.h" + +#include +#include + +#include "CRT.h" +#include "Meter.h" +#include "Object.h" +#include "RichString.h" +#include "XUtils.h" +#include "linux/LinuxMachine.h" + + +static const int ZswapMeter_attributes[ZSWAP_METER_ITEMCOUNT] = { + [ZSWAP_METER_COMPRESSED] = ZRAM_COMPRESSED, +}; + +static const int ZswapStatsMeter_attributes[ZSWAP_STATS_METER_ITEMCOUNT] = { + [ZSWAP_STATS_METER_COMPRESSED] = ZRAM_COMPRESSED, + [ZSWAP_STATS_METER_ORIGINAL] = ZRAM_UNCOMPRESSED, +}; + +static int ZswapStatsMeter_printRatio(const Meter* this, char* buffer, size_t size) { + if (this->values[ZSWAP_STATS_METER_COMPRESSED] > 0) { + return xSnprintf(buffer, size, "%.2f:1", + this->values[ZSWAP_STATS_METER_ORIGINAL] / this->values[ZSWAP_STATS_METER_COMPRESSED]); + } + + return xSnprintf(buffer, size, "N/A"); +} + +static void ZswapMeter_updateValues(Meter* this) { + const LinuxMachine* host = (const LinuxMachine*) this->host; + const ZswapStats* zswap = &host->zswap; + + if (!zswap->available) { + this->values[ZSWAP_METER_COMPRESSED] = NAN; + this->total = this->host->totalMem; + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "unavailable"); + return; + } + + this->total = zswap->hasPoolLimit ? zswap->totalZswapPool : this->host->totalMem; + + if (!zswap->enabled) { + this->values[ZSWAP_METER_COMPRESSED] = NAN; + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "disabled"); + return; + } + + this->values[ZSWAP_METER_COMPRESSED] = zswap->usedZswapComp; + + char* buffer = this->txtBuffer; + size_t size = sizeof(this->txtBuffer); + int written = Meter_humanUnit(buffer, this->values[ZSWAP_METER_COMPRESSED], size); + METER_BUFFER_CHECK(buffer, size, written); + + METER_BUFFER_APPEND_CHR(buffer, size, '/'); + + if (zswap->hasPoolLimit) + Meter_humanUnit(buffer, this->total, size); + else + xSnprintf(buffer, size, "?"); +} + +static void ZswapMeter_display(const Object* cast, RichString* out) { + const Meter* this = (const Meter*)cast; + const LinuxMachine* host = (const LinuxMachine*) this->host; + const ZswapStats* zswap = &host->zswap; + + if (!zswap->available) { + RichString_writeAscii(out, CRT_colors[FAILED_READ], "unavailable"); + return; + } + + if (!zswap->enabled) { + RichString_writeAscii(out, CRT_colors[METER_SHADOW], "disabled"); + return; + } + + char buffer[16]; + RichString_writeAscii(out, CRT_colors[METER_TEXT], ":"); + + if (zswap->hasPoolLimit) { + Meter_humanUnit(buffer, this->total, sizeof(buffer)); + RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); + } else { + RichString_appendAscii(out, CRT_colors[METER_SHADOW], "unknown"); + } + + Meter_humanUnit(buffer, this->values[ZSWAP_METER_COMPRESSED], sizeof(buffer)); + RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:"); + RichString_appendAscii(out, CRT_colors[ZRAM_COMPRESSED], buffer); +} + +static void ZswapStatsMeter_updateValues(Meter* this) { + const LinuxMachine* host = (const LinuxMachine*) this->host; + const ZswapStats* zswap = &host->zswap; + + if (!zswap->available) { + this->values[ZSWAP_STATS_METER_COMPRESSED] = NAN; + this->values[ZSWAP_STATS_METER_ORIGINAL] = NAN; + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "unavailable"); + return; + } + + if (!zswap->enabled) { + this->values[ZSWAP_STATS_METER_COMPRESSED] = NAN; + this->values[ZSWAP_STATS_METER_ORIGINAL] = NAN; + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "disabled"); + return; + } + + this->values[ZSWAP_STATS_METER_COMPRESSED] = zswap->usedZswapComp; + this->values[ZSWAP_STATS_METER_ORIGINAL] = zswap->usedZswapOrig; + + char compressedBuffer[16]; + char originalBuffer[16]; + char ratioBuffer[16]; + Meter_humanUnit(compressedBuffer, this->values[ZSWAP_STATS_METER_COMPRESSED], sizeof(compressedBuffer)); + Meter_humanUnit(originalBuffer, this->values[ZSWAP_STATS_METER_ORIGINAL], sizeof(originalBuffer)); + ZswapStatsMeter_printRatio(this, ratioBuffer, sizeof(ratioBuffer)); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s/%s (%s)", compressedBuffer, originalBuffer, ratioBuffer); +} + +static void ZswapStatsMeter_display(const Object* cast, RichString* out) { + const Meter* this = (const Meter*)cast; + const LinuxMachine* host = (const LinuxMachine*) this->host; + const ZswapStats* zswap = &host->zswap; + + if (!zswap->available) { + RichString_writeAscii(out, CRT_colors[FAILED_READ], "unavailable"); + return; + } + + if (!zswap->enabled) { + RichString_writeAscii(out, CRT_colors[METER_SHADOW], "disabled"); + return; + } + + char buffer[16]; + Meter_humanUnit(buffer, this->values[ZSWAP_STATS_METER_COMPRESSED], sizeof(buffer)); + RichString_writeAscii(out, CRT_colors[METER_TEXT], "pool:"); + RichString_appendAscii(out, CRT_colors[ZRAM_COMPRESSED], buffer); + + Meter_humanUnit(buffer, this->values[ZSWAP_STATS_METER_ORIGINAL], sizeof(buffer)); + RichString_appendAscii(out, CRT_colors[METER_TEXT], " stored:"); + RichString_appendAscii(out, CRT_colors[ZRAM_UNCOMPRESSED], buffer); + + ZswapStatsMeter_printRatio(this, buffer, sizeof(buffer)); + RichString_appendAscii(out, CRT_colors[METER_TEXT], " ratio:"); + RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); +} + +const MeterClass ZswapMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = ZswapMeter_display, + }, + .updateValues = ZswapMeter_updateValues, + .defaultMode = BAR_METERMODE, + .supportedModes = METERMODE_DEFAULT_SUPPORTED, + .maxItems = ZSWAP_METER_ITEMCOUNT, + .isPercentChart = true, + .total = 100.0, + .attributes = ZswapMeter_attributes, + .name = "Zswap", + .uiName = "Zswap", + .caption = "Zsw", + .description = "Zswap compressed pool usage" +}; + +const MeterClass ZswapStatsMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = ZswapStatsMeter_display, + }, + .updateValues = ZswapStatsMeter_updateValues, + .defaultMode = TEXT_METERMODE, + .supportedModes = (1 << TEXT_METERMODE), + .maxItems = ZSWAP_STATS_METER_ITEMCOUNT, + .isPercentChart = false, + .total = 1.0, + .attributes = ZswapStatsMeter_attributes, + .name = "ZswapStats", + .uiName = "Zswap Stats", + .caption = "Zsw: ", + .description = "Zswap compression statistics" +}; diff --git a/linux/ZswapMeter.h b/linux/ZswapMeter.h new file mode 100644 index 000000000..99a7a797c --- /dev/null +++ b/linux/ZswapMeter.h @@ -0,0 +1,28 @@ +#ifndef HEADER_ZswapMeter +#define HEADER_ZswapMeter +/* +htop - ZswapMeter.h +(C) 2026 Abhiram Shibu +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Meter.h" + + +typedef enum { + ZSWAP_METER_COMPRESSED = 0, + ZSWAP_METER_ITEMCOUNT = 1, // number of entries in this enum +} ZswapMeterValues; + +typedef enum { + ZSWAP_STATS_METER_COMPRESSED = 0, + ZSWAP_STATS_METER_ORIGINAL = 1, + ZSWAP_STATS_METER_ITEMCOUNT = 2, // number of entries in this enum +} ZswapStatsMeterValues; + +extern const MeterClass ZswapMeter_class; + +extern const MeterClass ZswapStatsMeter_class; + +#endif diff --git a/linux/ZswapStats.h b/linux/ZswapStats.h index 29e516f48..b466fa6bf 100644 --- a/linux/ZswapStats.h +++ b/linux/ZswapStats.h @@ -7,13 +7,23 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include + #include "ProcessTable.h" typedef struct ZswapStats_ { + /* maximum configured size of the zswap pool */ + memory_t totalZswapPool; /* amount of RAM used by the zswap pool */ memory_t usedZswapComp; /* amount of data stored inside the zswap pool */ memory_t usedZswapOrig; + /* whether both zswap counters are available from /proc/meminfo */ + bool available; + /* whether zswap is enabled */ + bool enabled; + /* whether totalZswapPool was obtained from the zswap module parameter */ + bool hasPoolLimit; } ZswapStats; #endif