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
5 changes: 5 additions & 0 deletions src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/perf/memorybar.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 10 additions & 10 deletions src/perf/memorydetailwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,23 +162,23 @@ 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<quint64>(qMax<qint64>(0, used)), 1));
this->ui->statAvailValue->setText(Misc::FormatKiB(static_cast<quint64>(qMax<qint64>(0, avail)), 1));
this->ui->statCachedValue->setText(Misc::FormatKiB(static_cast<quint64>(qMax<qint64>(0, cached)), 1));
this->ui->statBuffersValue->setText(Misc::FormatKiB(static_cast<quint64>(qMax<qint64>(0, buffers)), 1));
this->ui->statFreeValue->setText(Misc::FormatKiB(static_cast<quint64>(qMax<qint64>(0, free)), 1));
this->ui->statDirtyValue->setText(Misc::FormatKiB(static_cast<quint64>(qMax<qint64>(0, dirty)), 1));
if (hasZram)
if (hasCompressedMemory)
{
this->ui->statCompressedValue->setText(tr("%1 (using %2 RAM)")
.arg(Misc::FormatKiB(static_cast<quint64>(qMax<qint64>(0, compressedData)), 1),
Expand All @@ -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();
Expand Down
30 changes: 27 additions & 3 deletions src/system/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,16 @@ Memory::Memory()

bool Memory::Sample()
{
this->updateZswapEnabled();

QFile f("/proc/meminfo");
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return false;

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 (;;)
{
Expand Down Expand Up @@ -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();

Expand All @@ -131,19 +136,38 @@ 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<double>(this->m_memUsedKb) / static_cast<double>(memTotal) : 0.0;
this->m_memHistory.Push(frac * 100.0);
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.
Expand Down
22 changes: 18 additions & 4 deletions src/system/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "../globals.h"
#include "../historybuffer.h"
#include <QElapsedTimer>

class Memory
{
Expand All @@ -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; }
Expand All @@ -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; }
Expand All @@ -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 };
Expand All @@ -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 };
Expand Down
Loading