From cfb729d17c6e03c66c4e0d249a307f22b510e459 Mon Sep 17 00:00:00 2001 From: Petr Bena Date: Fri, 22 May 2026 22:40:00 +0200 Subject: [PATCH] implemented zswap --- src/globals.h | 5 +++++ src/perf/memorybar.h | 2 +- src/perf/memorydetailwidget.cpp | 20 ++++++++++---------- src/system/memory.cpp | 30 +++++++++++++++++++++++++++--- src/system/memory.h | 22 ++++++++++++++++++---- 5 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/globals.h b/src/globals.h index 2d30b17..faabdd1 100644 --- a/src/globals.h +++ b/src/globals.h @@ -26,6 +26,11 @@ #define TUX_MANAGER_HISTORY_SIZE 900 #define TUX_MANAGER_TASK_HISTORY 20 +/// Minimum dynamic rate graph scale, in bytes per second, so idle I/O graphs do not collapse to 0 #define TUX_MANAGER_MIN_RATE 1024.0 +// zswap is a runtime option, so we need to check it periodically instead of just once at startup +// there is no point checking too often +#define TUX_MANAGER_ZSWAP_ENABLED_CHECK_INTERVAL_MS 5000 + #endif // GLOBALS_H diff --git a/src/perf/memorybar.h b/src/perf/memorybar.h index 18239b9..a4349d0 100644 --- a/src/perf/memorybar.h +++ b/src/perf/memorybar.h @@ -28,7 +28,7 @@ namespace Perf /// /// Five segments are drawn left→right to fill the full widget width: /// 1. Used — processes' non-reclaimable footprint (bright purple) - /// 2. Compressed — zram's physical RAM footprint (teal) + /// 2. Compressed — compressed memory pools' physical RAM footprint /// 3. Dirty — Dirty + Writeback pages (amber) /// 4. Cached — reclaimable page cache (clean part) (muted purple) /// 5. Free — MemFree (near-background) diff --git a/src/perf/memorydetailwidget.cpp b/src/perf/memorydetailwidget.cpp index 2acd4e6..5fdd211 100644 --- a/src/perf/memorydetailwidget.cpp +++ b/src/perf/memorydetailwidget.cpp @@ -162,15 +162,15 @@ void MemoryDetailWidget::Init() void MemoryDetailWidget::onUpdated() { const qint64 total = Metrics::GetMemory()->MemTotalKb(); - const qint64 used = Metrics::GetMemory()->MemUsedNonZramKb(); + const qint64 used = Metrics::GetMemory()->MemUsedNonCompressedKb(); const qint64 avail = Metrics::GetMemory()->MemAvailKb(); const qint64 free = Metrics::GetMemory()->MemFreeKb(); const qint64 cached = Metrics::GetMemory()->MemCachedKb(); // includes buffers const qint64 buffers = Metrics::GetMemory()->MemBuffersKb(); const qint64 dirty = Metrics::GetMemory()->MemDirtyKb(); - const qint64 compressedData = Metrics::GetMemory()->ZramCompressedKb(); - const qint64 compressedRam = Metrics::GetMemory()->ZramMemUsedKb(); - const bool hasZram = Metrics::GetMemory()->HasZram(); + const qint64 compressedData = Metrics::GetMemory()->CompressedPayloadKb(); + const qint64 compressedRam = Metrics::GetMemory()->CompressedRamUsedKb(); + const bool hasCompressedMemory = Metrics::GetMemory()->HasCompressedMemory(); this->ui->statInUseValue->setText(Misc::FormatKiB(static_cast(qMax(0, used)), 1)); this->ui->statAvailValue->setText(Misc::FormatKiB(static_cast(qMax(0, avail)), 1)); @@ -178,7 +178,7 @@ void MemoryDetailWidget::onUpdated() this->ui->statBuffersValue->setText(Misc::FormatKiB(static_cast(qMax(0, buffers)), 1)); this->ui->statFreeValue->setText(Misc::FormatKiB(static_cast(qMax(0, free)), 1)); this->ui->statDirtyValue->setText(Misc::FormatKiB(static_cast(qMax(0, dirty)), 1)); - if (hasZram) + if (hasCompressedMemory) { this->ui->statCompressedValue->setText(tr("%1 (using %2 RAM)") .arg(Misc::FormatKiB(static_cast(qMax(0, compressedData)), 1), @@ -187,15 +187,15 @@ void MemoryDetailWidget::onUpdated() { this->ui->statCompressedValue->setText(tr("—")); } - this->updateCompressedVisibility(hasZram); + this->updateCompressedVisibility(hasCompressedMemory); // Composition bar — 5 segments must sum to total // free = MemFree // cached = Buffers + PageCache (includes dirty subset) - // used = Total - Free - Cached - zram_mem_used - // zram = RAM physically used by zram pools - // Verify: used + zram + cached + free == total ✓ - this->ui->compositionBar->SetSegments(used, hasZram ? compressedRam : 0, dirty, cached, free, total); + // used = Total - Free - Cached - compressed memory pools + // compressed = RAM physically used by zram/zswap pools + // Verify: used + compressed + cached + free == total ✓ + this->ui->compositionBar->SetSegments(used, hasCompressedMemory ? compressedRam : 0, dirty, cached, free, total); if (this->m_memHistory) this->ui->graphWidget->Tick(); diff --git a/src/system/memory.cpp b/src/system/memory.cpp index 783c4cf..bca4f13 100644 --- a/src/system/memory.cpp +++ b/src/system/memory.cpp @@ -77,6 +77,8 @@ Memory::Memory() bool Memory::Sample() { + this->updateZswapEnabled(); + QFile f("/proc/meminfo"); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return false; @@ -84,6 +86,7 @@ bool Memory::Sample() qint64 memTotal = 0, memAvail = 0, memFree = 0; qint64 buffers = 0, cached = 0, sReclaimable = 0, shmem = 0; qint64 dirty = 0, writeback = 0; + qint64 zswapCompressed = 0, zswapPayload = 0; for (;;) { @@ -114,6 +117,8 @@ bool Memory::Sample() else if (key == "Shmem") shmem = val; else if (key == "Dirty") dirty = val; else if (key == "Writeback") writeback = val; + else if (this->m_zswapEnabled && key == "Zswap") zswapCompressed = val; + else if (this->m_zswapEnabled && key == "Zswapped") zswapPayload = val; } f.close(); @@ -131,12 +136,17 @@ bool Memory::Sample() this->m_zramCompressedKb = zramStats.CompressedKb; this->m_zramMemUsedKb = zramStats.MemUsedKb; this->m_hasZram = zramStats.HasZram; + this->m_zswapPayloadKb = this->m_zswapEnabled ? zswapPayload : 0; + this->m_zswapMemUsedKb = this->m_zswapEnabled ? zswapCompressed : 0; + this->m_compressedPayloadKb = this->m_zramCompressedKb + this->m_zswapPayloadKb; + this->m_compressedRamUsedKb = this->m_zramMemUsedKb + this->m_zswapMemUsedKb; + this->m_hasCompressedMemory = this->m_hasZram || this->m_compressedPayloadKb > 0 || this->m_compressedRamUsedKb > 0; // Full page cache including buffers (what we show in stats and composition bar) this->m_memCachedKb = buffers + pageCache; - // RAM used, including zram pools. + // RAM used, including compressed memory pools. this->m_memUsedKb = memUsed; - // Non-zram in-use RAM for composition views that split compressed memory out separately. - this->m_memUsedNonZramKb = qMax(0LL, memUsed - this->m_zramMemUsedKb); + // Non-compressed in-use RAM for composition views that split compressed memory out separately. + this->m_memUsedNonCompressedKb = qMax(0LL, memUsed - this->m_compressedRamUsedKb); // Graph tracks used / total (htop formula matches the green bar) const double frac = (memTotal > 0) ? static_cast(this->m_memUsedKb) / static_cast(memTotal) : 0.0; @@ -144,6 +154,20 @@ bool Memory::Sample() return true; } +void Memory::updateZswapEnabled() +{ + if (this->m_zswapEnabledCheckTimer.isValid() && this->m_zswapEnabledCheckTimer.elapsed() < TUX_MANAGER_ZSWAP_ENABLED_CHECK_INTERVAL_MS) + return; + + const QString enabledText = Misc::ReadFile("/sys/module/zswap/parameters/enabled").trimmed().toLower(); + this->m_zswapEnabled = enabledText == "y"; + + if (!this->m_zswapEnabledCheckTimer.isValid()) + this->m_zswapEnabledCheckTimer.start(); + else + this->m_zswapEnabledCheckTimer.restart(); +} + void Memory::readHardwareMetadata() { // Best-effort DIMM population and memory speed from SMBIOS type 17. diff --git a/src/system/memory.h b/src/system/memory.h index 7db4fbf..1c32c00 100644 --- a/src/system/memory.h +++ b/src/system/memory.h @@ -21,6 +21,7 @@ #include "../globals.h" #include "../historybuffer.h" +#include class Memory { @@ -31,10 +32,10 @@ class Memory bool Sample(); qint64 MemTotalKb() const { return this->m_memTotalKb; } - /// In-use RAM including compressed zram pools. + /// In-use RAM including compressed memory pools. qint64 MemUsedKb() const { return this->m_memUsedKb; } - /// In-use RAM excluding zram's physical RAM footprint. - qint64 MemUsedNonZramKb() const { return this->m_memUsedNonZramKb; } + /// In-use RAM excluding compressed memory pools' physical RAM footprint. + qint64 MemUsedNonCompressedKb() const { return this->m_memUsedNonCompressedKb; } qint64 MemAvailKb() const { return this->m_memAvailKb; } /// Truly free (MemFree from /proc/meminfo) qint64 MemFreeKb() const { return this->m_memFreeKb; } @@ -48,6 +49,11 @@ class Memory /// Physical RAM currently consumed by zram devices. qint64 ZramMemUsedKb() const { return this->m_zramMemUsedKb; } bool HasZram() const { return this->m_hasZram; } + /// Total uncompressed payload currently stored in compressed memory pools. + qint64 CompressedPayloadKb() const { return this->m_compressedPayloadKb; } + /// Physical RAM currently consumed by compressed memory pools. + qint64 CompressedRamUsedKb() const { return this->m_compressedRamUsedKb; } + bool HasCompressedMemory() const { return this->m_hasCompressedMemory; } int MemDimmSlotsTotal() const { return this->m_memDimmSlotsTotal; } int MemDimmSlotsUsed() const { return this->m_memDimmSlotsUsed; } int MemSpeedMtps() const { return this->m_memSpeedMtps; } @@ -56,10 +62,11 @@ class Memory private: void readHardwareMetadata(); + void updateZswapEnabled(); qint64 m_memTotalKb { 0 }; qint64 m_memUsedKb { 0 }; - qint64 m_memUsedNonZramKb { 0 }; + qint64 m_memUsedNonCompressedKb { 0 }; qint64 m_memAvailKb { 0 }; qint64 m_memFreeKb { 0 }; qint64 m_memCachedKb { 0 }; @@ -68,6 +75,13 @@ class Memory qint64 m_zramCompressedKb { 0 }; qint64 m_zramMemUsedKb { 0 }; bool m_hasZram { false }; + qint64 m_zswapPayloadKb { 0 }; + qint64 m_zswapMemUsedKb { 0 }; + bool m_zswapEnabled { false }; + QElapsedTimer m_zswapEnabledCheckTimer; + qint64 m_compressedPayloadKb { 0 }; + qint64 m_compressedRamUsedKb { 0 }; + bool m_hasCompressedMemory { false }; int m_memDimmSlotsTotal { 0 }; int m_memDimmSlotsUsed { 0 }; int m_memSpeedMtps { 0 };