Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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

Expand Down
44 changes: 42 additions & 2 deletions linux/LinuxMachine.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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':
Expand Down Expand Up @@ -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
}

Expand All @@ -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++) {
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions linux/Platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
200 changes: 200 additions & 0 deletions linux/ZswapMeter.c
Original file line number Diff line number Diff line change
@@ -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 <math.h>
#include <stddef.h>

#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"
};
28 changes: 28 additions & 0 deletions linux/ZswapMeter.h
Original file line number Diff line number Diff line change
@@ -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"

Comment thread
BenBE marked this conversation as resolved.

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
10 changes: 10 additions & 0 deletions linux/ZswapStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@ Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

#include <stdbool.h>

#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
Loading