From 902bdd92c21edfb4628599ce2535e39567769e02 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 4 Feb 2026 12:08:33 +0000 Subject: [PATCH] fix: prefer status-mem over status-freemem for VM memory calculation Proxmox's FreeMem field reports free memory relative to the balloon's guest-visible total (total_mem), not relative to MaxMem. When ballooning is active and the VM's memory has been reduced, subtracting FreeMem from MaxMem produces wildly inflated usage (e.g. 97% when actual usage is 20%). Proxmox's Mem field is already calculated as (total_mem - free_mem), giving the correct used bytes regardless of balloon state. Swap the priority so Mem is checked before FreeMem. Related to #1185 --- internal/monitoring/monitor.go | 13 ++++++++++--- internal/monitoring/monitor_polling.go | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/internal/monitoring/monitor.go b/internal/monitoring/monitor.go index a0f65a848e..715bdf505a 100644 --- a/internal/monitoring/monitor.go +++ b/internal/monitoring/monitor.go @@ -6647,12 +6647,19 @@ func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceNam memAvailable = memTotal } memUsed = memTotal - memAvailable + case detailedStatus.Mem > 0: + // Prefer Mem over FreeMem: Proxmox calculates Mem as + // (total_mem - free_mem) using the balloon's guest-visible + // total, which is correct even when ballooning is active. + // FreeMem is relative to the balloon allocation (not MaxMem), + // so subtracting it from MaxMem produces wildly inflated + // usage when the balloon has reduced the VM's memory. + // Refs: #1185 + memUsed = detailedStatus.Mem + memorySource = "status-mem" case detailedStatus.FreeMem > 0 && memTotal >= detailedStatus.FreeMem: memUsed = memTotal - detailedStatus.FreeMem memorySource = "status-freemem" - case detailedStatus.Mem > 0: - memUsed = detailedStatus.Mem - memorySource = "status-mem" } if memUsed > memTotal { memUsed = memTotal diff --git a/internal/monitoring/monitor_polling.go b/internal/monitoring/monitor_polling.go index ff672873cf..c417c6cb1f 100644 --- a/internal/monitoring/monitor_polling.go +++ b/internal/monitoring/monitor_polling.go @@ -364,12 +364,19 @@ func (m *Monitor) pollVMsWithNodes(ctx context.Context, instanceName string, clu memAvailable = memTotal } memUsed = memTotal - memAvailable + case vmStatus.Mem > 0: + // Prefer Mem over FreeMem: Proxmox calculates Mem as + // (total_mem - free_mem) using the balloon's guest-visible + // total, which is correct even when ballooning is active. + // FreeMem is relative to the balloon allocation (not MaxMem), + // so subtracting it from MaxMem produces wildly inflated + // usage when the balloon has reduced the VM's memory. + // Refs: #1185 + memUsed = vmStatus.Mem + memorySource = "status-mem" case vmStatus.FreeMem > 0: memUsed = memTotal - vmStatus.FreeMem memorySource = "status-freemem" - case vmStatus.Mem > 0: - memUsed = vmStatus.Mem - memorySource = "status-mem" default: memUsed = 0 memorySource = "status-unavailable"