mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
fix(monitoring): use MemAvailable instead of MemTotal-MemFree for Linux guest/node memory
When the Proxmox API's meminfo/status payload omits Available, Buffers, and Cached (common for QEMU guest-agent and node status responses), the code derived 'available' from Free alone — producing used = Total-Free which counts reclaimable page cache as used memory (e.g. 94% instead of the correct 76%). Guest path (deriveGuestMemInfoAvailable): return 0 when cache metrics are completely missing and only Free is available, so resolveGuestStatusMemory tries better sources: guest-agent /proc/meminfo file-read (returns MemAvailable), RRD memavailable, or the linked host agent. Node path (resolveNodeMemory): always try RRD memavailable when cache metrics are missing, not only when effectiveAvailable == 0 — previously a non-zero Free value blocked the RRD fallback. Refs #1501
This commit is contained in:
parent
22ed97f562
commit
b5ddbd5606
4 changed files with 114 additions and 1 deletions
|
|
@ -73,6 +73,9 @@ func deriveGuestMemInfoAvailable(memInfo *proxmox.VMMemInfo, guestRaw *VMMemoryR
|
|||
}
|
||||
}
|
||||
}
|
||||
if missingCacheMetrics {
|
||||
return 0, ""
|
||||
}
|
||||
return componentAvailable, "derived-free-buffers-cached"
|
||||
default:
|
||||
if availableFromUsed > 0 && missingCacheMetrics {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,100 @@ import (
|
|||
"github.com/rcourtman/pulse-go-rewrite/pkg/proxmox"
|
||||
)
|
||||
|
||||
func TestDeriveGuestMemInfoAvailable(t *testing.T) {
|
||||
kb := uint64(1024)
|
||||
miB := uint64(1024 * 1024)
|
||||
giB := uint64(1024 * 1024 * 1024)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
memInfo *proxmox.VMMemInfo
|
||||
wantAvailable uint64
|
||||
wantSource string
|
||||
}{
|
||||
{
|
||||
name: "uses available field when present",
|
||||
memInfo: &proxmox.VMMemInfo{
|
||||
Total: 8 * giB,
|
||||
Used: 6 * giB,
|
||||
Free: 2 * giB,
|
||||
Available: 4 * giB,
|
||||
Buffers: 500 * miB,
|
||||
Cached: 1 * giB,
|
||||
},
|
||||
wantAvailable: 4 * giB,
|
||||
wantSource: "available-field",
|
||||
},
|
||||
{
|
||||
name: "derives from free+buffers+cached when available missing",
|
||||
memInfo: &proxmox.VMMemInfo{
|
||||
Total: 8 * giB,
|
||||
Used: 5 * giB,
|
||||
Free: 2 * giB,
|
||||
Buffers: 500 * miB,
|
||||
Cached: 1 * giB,
|
||||
},
|
||||
wantAvailable: 2*giB + 500*miB + 1*giB,
|
||||
wantSource: "derived-free-buffers-cached",
|
||||
},
|
||||
{
|
||||
name: "returns zero when only free is available (no cache metrics)",
|
||||
memInfo: &proxmox.VMMemInfo{
|
||||
Total: 7796964 * kb,
|
||||
Used: 7352140 * kb,
|
||||
Free: 444824 * kb,
|
||||
},
|
||||
wantAvailable: 0,
|
||||
wantSource: "",
|
||||
},
|
||||
{
|
||||
name: "returns availableFromUsed when used excludes cache despite missing cache metrics",
|
||||
memInfo: &proxmox.VMMemInfo{
|
||||
Total: 8 * giB,
|
||||
Used: 3 * giB,
|
||||
Free: 1 * giB,
|
||||
},
|
||||
wantAvailable: 5 * giB,
|
||||
wantSource: "derived-total-minus-used",
|
||||
},
|
||||
{
|
||||
name: "returns zero when only total and used with used equal to total minus free",
|
||||
memInfo: &proxmox.VMMemInfo{
|
||||
Total: 8 * giB,
|
||||
Used: 7 * giB,
|
||||
},
|
||||
wantAvailable: 1 * giB,
|
||||
wantSource: "derived-total-minus-used",
|
||||
},
|
||||
{
|
||||
name: "returns zero for empty meminfo",
|
||||
memInfo: &proxmox.VMMemInfo{
|
||||
Total: 8 * giB,
|
||||
},
|
||||
wantAvailable: 0,
|
||||
wantSource: "",
|
||||
},
|
||||
{
|
||||
name: "nil meminfo returns zero",
|
||||
memInfo: nil,
|
||||
wantAvailable: 0,
|
||||
wantSource: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotAvailable, gotSource := deriveGuestMemInfoAvailable(tt.memInfo, nil)
|
||||
if gotAvailable != tt.wantAvailable {
|
||||
t.Fatalf("available = %d, want %d", gotAvailable, tt.wantAvailable)
|
||||
}
|
||||
if gotSource != tt.wantSource {
|
||||
t.Fatalf("source = %q, want %q", gotSource, tt.wantSource)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectGuestLowTrustUsedMemory(t *testing.T) {
|
||||
giB := uint64(1024 * 1024 * 1024)
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (m *Monitor) resolveNodeMemory(
|
|||
usedRRDAvailableFallback := false
|
||||
rrdMemUsedFallback := false
|
||||
|
||||
if effectiveAvailable == 0 && missingCacheMetrics {
|
||||
if missingCacheMetrics {
|
||||
if metrics, err := m.getNodeRRDMetrics(ctx, client, nodeName); err == nil {
|
||||
haveRRDMetrics = true
|
||||
rrdMetrics = metrics
|
||||
|
|
|
|||
|
|
@ -64,6 +64,22 @@ func TestResolveNodeMemoryCharacterization(t *testing.T) {
|
|||
wantUsed: 6 * gib,
|
||||
wantRawSource: "rrd-memused",
|
||||
},
|
||||
{
|
||||
name: "node status has only free so rrd memavailable is preferred over total-minus-free",
|
||||
memory: &proxmox.MemoryStatus{
|
||||
Total: 8 * gib,
|
||||
Used: 7 * gib,
|
||||
Free: 1 * gib,
|
||||
},
|
||||
rrdPoints: []proxmox.NodeRRDPoint{{
|
||||
MemTotal: floatPtr(float64(8 * gib)),
|
||||
MemAvailable: floatPtr(float64(4 * gib)),
|
||||
}},
|
||||
wantSource: "rrd-memavailable",
|
||||
wantFallback: "rrd-memavailable",
|
||||
wantUsed: 4 * gib,
|
||||
wantRawSource: "rrd-memavailable",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue