Use guest meminfo available for VM memory usage
This commit is contained in:
parent
7ba89da12c
commit
2163d6f5a8
4 changed files with 171 additions and 51 deletions
|
|
@ -39,18 +39,22 @@ type NodeMemorySnapshot struct {
|
||||||
|
|
||||||
// VMMemoryRaw captures both the listing and detailed status memory fields for a VM/CT.
|
// VMMemoryRaw captures both the listing and detailed status memory fields for a VM/CT.
|
||||||
type VMMemoryRaw struct {
|
type VMMemoryRaw struct {
|
||||||
ListingMem uint64 `json:"listingMem"`
|
ListingMem uint64 `json:"listingMem"`
|
||||||
ListingMaxMem uint64 `json:"listingMaxmem"`
|
ListingMaxMem uint64 `json:"listingMaxmem"`
|
||||||
StatusMem uint64 `json:"statusMem,omitempty"`
|
StatusMem uint64 `json:"statusMem,omitempty"`
|
||||||
StatusFreeMem uint64 `json:"statusFreemem,omitempty"`
|
StatusFreeMem uint64 `json:"statusFreemem,omitempty"`
|
||||||
StatusMaxMem uint64 `json:"statusMaxmem,omitempty"`
|
StatusMaxMem uint64 `json:"statusMaxmem,omitempty"`
|
||||||
Balloon uint64 `json:"balloon,omitempty"`
|
Balloon uint64 `json:"balloon,omitempty"`
|
||||||
BalloonMin uint64 `json:"balloonMin,omitempty"`
|
BalloonMin uint64 `json:"balloonMin,omitempty"`
|
||||||
MemInfoUsed uint64 `json:"meminfoUsed,omitempty"`
|
MemInfoUsed uint64 `json:"meminfoUsed,omitempty"`
|
||||||
MemInfoFree uint64 `json:"meminfoFree,omitempty"`
|
MemInfoFree uint64 `json:"meminfoFree,omitempty"`
|
||||||
MemInfoTotal uint64 `json:"meminfoTotal,omitempty"`
|
MemInfoTotal uint64 `json:"meminfoTotal,omitempty"`
|
||||||
Agent int `json:"agent,omitempty"`
|
MemInfoAvailable uint64 `json:"meminfoAvailable,omitempty"`
|
||||||
DerivedFromBall bool `json:"derivedFromBalloon,omitempty"`
|
MemInfoBuffers uint64 `json:"meminfoBuffers,omitempty"`
|
||||||
|
MemInfoCached uint64 `json:"meminfoCached,omitempty"`
|
||||||
|
MemInfoShared uint64 `json:"meminfoShared,omitempty"`
|
||||||
|
Agent int `json:"agent,omitempty"`
|
||||||
|
DerivedFromBall bool `json:"derivedFromBalloon,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GuestMemorySnapshot records the memory calculation for a guest (VM/LXC).
|
// GuestMemorySnapshot records the memory calculation for a guest (VM/LXC).
|
||||||
|
|
|
||||||
|
|
@ -2527,10 +2527,28 @@ func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceNam
|
||||||
guestRaw.Balloon = detailedStatus.Balloon
|
guestRaw.Balloon = detailedStatus.Balloon
|
||||||
guestRaw.BalloonMin = detailedStatus.BalloonMin
|
guestRaw.BalloonMin = detailedStatus.BalloonMin
|
||||||
guestRaw.Agent = detailedStatus.Agent
|
guestRaw.Agent = detailedStatus.Agent
|
||||||
|
memAvailable := uint64(0)
|
||||||
if detailedStatus.MemInfo != nil {
|
if detailedStatus.MemInfo != nil {
|
||||||
guestRaw.MemInfoUsed = detailedStatus.MemInfo.Used
|
guestRaw.MemInfoUsed = detailedStatus.MemInfo.Used
|
||||||
guestRaw.MemInfoFree = detailedStatus.MemInfo.Free
|
guestRaw.MemInfoFree = detailedStatus.MemInfo.Free
|
||||||
guestRaw.MemInfoTotal = detailedStatus.MemInfo.Total
|
guestRaw.MemInfoTotal = detailedStatus.MemInfo.Total
|
||||||
|
guestRaw.MemInfoAvailable = detailedStatus.MemInfo.Available
|
||||||
|
guestRaw.MemInfoBuffers = detailedStatus.MemInfo.Buffers
|
||||||
|
guestRaw.MemInfoCached = detailedStatus.MemInfo.Cached
|
||||||
|
guestRaw.MemInfoShared = detailedStatus.MemInfo.Shared
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case detailedStatus.MemInfo.Available > 0:
|
||||||
|
memAvailable = detailedStatus.MemInfo.Available
|
||||||
|
memorySource = "meminfo-available"
|
||||||
|
case detailedStatus.MemInfo.Free > 0 ||
|
||||||
|
detailedStatus.MemInfo.Buffers > 0 ||
|
||||||
|
detailedStatus.MemInfo.Cached > 0:
|
||||||
|
memAvailable = detailedStatus.MemInfo.Free +
|
||||||
|
detailedStatus.MemInfo.Buffers +
|
||||||
|
detailedStatus.MemInfo.Cached
|
||||||
|
memorySource = "meminfo-derived"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use actual disk I/O values from detailed status
|
// Use actual disk I/O values from detailed status
|
||||||
|
|
@ -2547,10 +2565,16 @@ func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceNam
|
||||||
guestRaw.DerivedFromBall = false
|
guestRaw.DerivedFromBall = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if detailedStatus.FreeMem > 0 && memTotal >= detailedStatus.FreeMem {
|
switch {
|
||||||
|
case memAvailable > 0:
|
||||||
|
if memAvailable > memTotal {
|
||||||
|
memAvailable = memTotal
|
||||||
|
}
|
||||||
|
memUsed = memTotal - memAvailable
|
||||||
|
case detailedStatus.FreeMem > 0 && memTotal >= detailedStatus.FreeMem:
|
||||||
memUsed = memTotal - detailedStatus.FreeMem
|
memUsed = memTotal - detailedStatus.FreeMem
|
||||||
memorySource = "status-freemem"
|
memorySource = "status-freemem"
|
||||||
} else if detailedStatus.Mem > 0 {
|
case detailedStatus.Mem > 0:
|
||||||
memUsed = detailedStatus.Mem
|
memUsed = detailedStatus.Mem
|
||||||
memorySource = "status-mem"
|
memorySource = "status-mem"
|
||||||
}
|
}
|
||||||
|
|
@ -3180,10 +3204,28 @@ func (m *Monitor) pollVMsWithNodes(ctx context.Context, instanceName string, cli
|
||||||
guestRaw.Balloon = status.Balloon
|
guestRaw.Balloon = status.Balloon
|
||||||
guestRaw.BalloonMin = status.BalloonMin
|
guestRaw.BalloonMin = status.BalloonMin
|
||||||
guestRaw.Agent = status.Agent
|
guestRaw.Agent = status.Agent
|
||||||
|
memAvailable := uint64(0)
|
||||||
if status.MemInfo != nil {
|
if status.MemInfo != nil {
|
||||||
guestRaw.MemInfoUsed = status.MemInfo.Used
|
guestRaw.MemInfoUsed = status.MemInfo.Used
|
||||||
guestRaw.MemInfoFree = status.MemInfo.Free
|
guestRaw.MemInfoFree = status.MemInfo.Free
|
||||||
guestRaw.MemInfoTotal = status.MemInfo.Total
|
guestRaw.MemInfoTotal = status.MemInfo.Total
|
||||||
|
guestRaw.MemInfoAvailable = status.MemInfo.Available
|
||||||
|
guestRaw.MemInfoBuffers = status.MemInfo.Buffers
|
||||||
|
guestRaw.MemInfoCached = status.MemInfo.Cached
|
||||||
|
guestRaw.MemInfoShared = status.MemInfo.Shared
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case status.MemInfo.Available > 0:
|
||||||
|
memAvailable = status.MemInfo.Available
|
||||||
|
memorySource = "meminfo-available"
|
||||||
|
case status.MemInfo.Free > 0 ||
|
||||||
|
status.MemInfo.Buffers > 0 ||
|
||||||
|
status.MemInfo.Cached > 0:
|
||||||
|
memAvailable = status.MemInfo.Free +
|
||||||
|
status.MemInfo.Buffers +
|
||||||
|
status.MemInfo.Cached
|
||||||
|
memorySource = "meminfo-derived"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use actual disk I/O values from detailed status
|
// Use actual disk I/O values from detailed status
|
||||||
|
|
@ -3201,18 +3243,28 @@ func (m *Monitor) pollVMsWithNodes(ctx context.Context, instanceName string, cli
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have free memory from guest agent, calculate actual usage
|
// If we have free memory from guest agent, calculate actual usage
|
||||||
if status.FreeMem > 0 {
|
switch {
|
||||||
|
case memAvailable > 0:
|
||||||
|
if memAvailable > memTotal {
|
||||||
|
memAvailable = memTotal
|
||||||
|
}
|
||||||
|
memUsed = memTotal - memAvailable
|
||||||
|
case status.FreeMem > 0:
|
||||||
// Guest agent reports free memory, so calculate used
|
// Guest agent reports free memory, so calculate used
|
||||||
memUsed = memTotal - status.FreeMem
|
memUsed = memTotal - status.FreeMem
|
||||||
memorySource = "status-freemem"
|
memorySource = "status-freemem"
|
||||||
} else if status.Mem > 0 {
|
case status.Mem > 0:
|
||||||
// No guest agent free memory data, but we have actual memory usage
|
// No guest agent free memory data, but we have actual memory usage
|
||||||
// Use the reported memory usage from Proxmox
|
// Use the reported memory usage from Proxmox
|
||||||
memUsed = status.Mem
|
memUsed = status.Mem
|
||||||
memorySource = "status-mem"
|
memorySource = "status-mem"
|
||||||
} else {
|
default:
|
||||||
// No memory data available at all - show 0% usage
|
// No memory data available at all - show 0% usage
|
||||||
memUsed = 0
|
memUsed = 0
|
||||||
|
memorySource = "status-unavailable"
|
||||||
|
}
|
||||||
|
if memUsed > memTotal {
|
||||||
|
memUsed = memTotal
|
||||||
}
|
}
|
||||||
|
|
||||||
guestIPs, guestIfaces, guestOSName, guestOSVersion := fetchGuestAgentMetadata(ctx, client, instanceName, node.Node, vm.Name, vm.VMID, status)
|
guestIPs, guestIfaces, guestOSName, guestOSVersion := fetchGuestAgentMetadata(ctx, client, instanceName, node.Node, vm.Name, vm.VMID, status)
|
||||||
|
|
@ -3456,6 +3508,25 @@ func (m *Monitor) pollVMsWithNodes(ctx context.Context, instanceName string, cli
|
||||||
}
|
}
|
||||||
diskReadRate, diskWriteRate, netInRate, netOutRate := m.rateTracker.CalculateRates(guestID, currentMetrics)
|
diskReadRate, diskWriteRate, netInRate, netOutRate := m.rateTracker.CalculateRates(guestID, currentMetrics)
|
||||||
|
|
||||||
|
memTotalBytes := clampToInt64(memTotal)
|
||||||
|
memUsedBytes := clampToInt64(memUsed)
|
||||||
|
if memTotalBytes > 0 && memUsedBytes > memTotalBytes {
|
||||||
|
memUsedBytes = memTotalBytes
|
||||||
|
}
|
||||||
|
memFreeBytes := memTotalBytes - memUsedBytes
|
||||||
|
if memFreeBytes < 0 {
|
||||||
|
memFreeBytes = 0
|
||||||
|
}
|
||||||
|
memory := models.Memory{
|
||||||
|
Total: memTotalBytes,
|
||||||
|
Used: memUsedBytes,
|
||||||
|
Free: memFreeBytes,
|
||||||
|
Usage: safePercentage(float64(memUsed), float64(memTotal)),
|
||||||
|
}
|
||||||
|
if guestRaw.Balloon > 0 {
|
||||||
|
memory.Balloon = clampToInt64(guestRaw.Balloon)
|
||||||
|
}
|
||||||
|
|
||||||
modelVM := models.VM{
|
modelVM := models.VM{
|
||||||
ID: guestID,
|
ID: guestID,
|
||||||
VMID: vm.VMID,
|
VMID: vm.VMID,
|
||||||
|
|
@ -3466,12 +3537,7 @@ func (m *Monitor) pollVMsWithNodes(ctx context.Context, instanceName string, cli
|
||||||
Type: "qemu",
|
Type: "qemu",
|
||||||
CPU: cpuUsage, // Already in percentage
|
CPU: cpuUsage, // Already in percentage
|
||||||
CPUs: vm.CPUs,
|
CPUs: vm.CPUs,
|
||||||
Memory: models.Memory{
|
Memory: memory,
|
||||||
Total: int64(memTotal),
|
|
||||||
Used: int64(memUsed),
|
|
||||||
Free: int64(memTotal - memUsed),
|
|
||||||
Usage: safePercentage(float64(memUsed), float64(memTotal)),
|
|
||||||
},
|
|
||||||
Disk: models.Disk{
|
Disk: models.Disk{
|
||||||
Total: int64(diskTotal),
|
Total: int64(diskTotal),
|
||||||
Used: int64(diskUsed),
|
Used: int64(diskUsed),
|
||||||
|
|
|
||||||
|
|
@ -163,21 +163,51 @@ func (m *Monitor) pollVMsWithNodesOptimized(ctx context.Context, instanceName st
|
||||||
guestRaw.Balloon = status.Balloon
|
guestRaw.Balloon = status.Balloon
|
||||||
guestRaw.BalloonMin = status.BalloonMin
|
guestRaw.BalloonMin = status.BalloonMin
|
||||||
guestRaw.Agent = status.Agent
|
guestRaw.Agent = status.Agent
|
||||||
|
memAvailable := uint64(0)
|
||||||
if status.MemInfo != nil {
|
if status.MemInfo != nil {
|
||||||
guestRaw.MemInfoUsed = status.MemInfo.Used
|
guestRaw.MemInfoUsed = status.MemInfo.Used
|
||||||
guestRaw.MemInfoFree = status.MemInfo.Free
|
guestRaw.MemInfoFree = status.MemInfo.Free
|
||||||
guestRaw.MemInfoTotal = status.MemInfo.Total
|
guestRaw.MemInfoTotal = status.MemInfo.Total
|
||||||
|
guestRaw.MemInfoAvailable = status.MemInfo.Available
|
||||||
|
guestRaw.MemInfoBuffers = status.MemInfo.Buffers
|
||||||
|
guestRaw.MemInfoCached = status.MemInfo.Cached
|
||||||
|
guestRaw.MemInfoShared = status.MemInfo.Shared
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case status.MemInfo.Available > 0:
|
||||||
|
memAvailable = status.MemInfo.Available
|
||||||
|
memorySource = "meminfo-available"
|
||||||
|
case status.MemInfo.Free > 0 ||
|
||||||
|
status.MemInfo.Buffers > 0 ||
|
||||||
|
status.MemInfo.Cached > 0:
|
||||||
|
memAvailable = status.MemInfo.Free +
|
||||||
|
status.MemInfo.Buffers +
|
||||||
|
status.MemInfo.Cached
|
||||||
|
memorySource = "meminfo-derived"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if vmStatus.Balloon > 0 && vmStatus.Balloon < vmStatus.MaxMem {
|
if vmStatus.Balloon > 0 && vmStatus.Balloon < vmStatus.MaxMem {
|
||||||
memTotal = vmStatus.Balloon
|
memTotal = vmStatus.Balloon
|
||||||
guestRaw.DerivedFromBall = true
|
guestRaw.DerivedFromBall = true
|
||||||
}
|
}
|
||||||
if vmStatus.FreeMem > 0 {
|
switch {
|
||||||
|
case memAvailable > 0:
|
||||||
|
if memAvailable > memTotal {
|
||||||
|
memAvailable = memTotal
|
||||||
|
}
|
||||||
|
memUsed = memTotal - memAvailable
|
||||||
|
case vmStatus.FreeMem > 0:
|
||||||
memUsed = memTotal - vmStatus.FreeMem
|
memUsed = memTotal - vmStatus.FreeMem
|
||||||
memorySource = "status-freemem"
|
memorySource = "status-freemem"
|
||||||
} else if vmStatus.Mem > 0 {
|
case vmStatus.Mem > 0:
|
||||||
memUsed = vmStatus.Mem
|
memUsed = vmStatus.Mem
|
||||||
memorySource = "status-mem"
|
memorySource = "status-mem"
|
||||||
|
default:
|
||||||
|
memUsed = 0
|
||||||
|
memorySource = "status-unavailable"
|
||||||
|
}
|
||||||
|
if memUsed > memTotal {
|
||||||
|
memUsed = memTotal
|
||||||
}
|
}
|
||||||
// Use actual disk I/O values from detailed status
|
// Use actual disk I/O values from detailed status
|
||||||
diskReadBytes = int64(vmStatus.DiskRead)
|
diskReadBytes = int64(vmStatus.DiskRead)
|
||||||
|
|
@ -461,14 +491,23 @@ func (m *Monitor) pollVMsWithNodesOptimized(ctx context.Context, instanceName st
|
||||||
diskStatusReason = "no-status"
|
diskStatusReason = "no-status"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memTotalBytes := clampToInt64(memTotal)
|
||||||
|
memUsedBytes := clampToInt64(memUsed)
|
||||||
|
if memTotalBytes > 0 && memUsedBytes > memTotalBytes {
|
||||||
|
memUsedBytes = memTotalBytes
|
||||||
|
}
|
||||||
|
memFreeBytes := memTotalBytes - memUsedBytes
|
||||||
|
if memFreeBytes < 0 {
|
||||||
|
memFreeBytes = 0
|
||||||
|
}
|
||||||
memory := models.Memory{
|
memory := models.Memory{
|
||||||
Total: int64(memTotal),
|
Total: memTotalBytes,
|
||||||
Used: int64(memUsed),
|
Used: memUsedBytes,
|
||||||
Free: int64(memTotal - memUsed),
|
Free: memFreeBytes,
|
||||||
Usage: safePercentage(float64(memUsed), float64(memTotal)),
|
Usage: safePercentage(float64(memUsed), float64(memTotal)),
|
||||||
}
|
}
|
||||||
if vmStatus != nil && vmStatus.Balloon > 0 {
|
if guestRaw.Balloon > 0 {
|
||||||
memory.Balloon = int64(vmStatus.Balloon)
|
memory.Balloon = clampToInt64(guestRaw.Balloon)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create VM model
|
// Create VM model
|
||||||
|
|
|
||||||
|
|
@ -1243,28 +1243,39 @@ type ZFSPoolDevice struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VMStatus represents detailed VM status
|
// VMStatus represents detailed VM status
|
||||||
|
// VMMemInfo describes memory statistics reported by the guest agent.
|
||||||
|
// Proxmox surfaces guest /proc/meminfo values (in bytes). The available
|
||||||
|
// field is only present on newer agent versions, so we keep the raw
|
||||||
|
// components to reconstruct it when missing.
|
||||||
|
type VMMemInfo struct {
|
||||||
|
Total uint64 `json:"total,omitempty"`
|
||||||
|
Used uint64 `json:"used,omitempty"`
|
||||||
|
Free uint64 `json:"free,omitempty"`
|
||||||
|
Available uint64 `json:"available,omitempty"`
|
||||||
|
Buffers uint64 `json:"buffers,omitempty"`
|
||||||
|
Cached uint64 `json:"cached,omitempty"`
|
||||||
|
Shared uint64 `json:"shared,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VMStatus represents detailed VM status returned by Proxmox.
|
||||||
type VMStatus struct {
|
type VMStatus struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
CPU float64 `json:"cpu"`
|
CPU float64 `json:"cpu"`
|
||||||
CPUs int `json:"cpus"`
|
CPUs int `json:"cpus"`
|
||||||
Mem uint64 `json:"mem"`
|
Mem uint64 `json:"mem"`
|
||||||
MaxMem uint64 `json:"maxmem"`
|
MaxMem uint64 `json:"maxmem"`
|
||||||
Balloon uint64 `json:"balloon"`
|
Balloon uint64 `json:"balloon"`
|
||||||
BalloonMin uint64 `json:"balloon_min"`
|
BalloonMin uint64 `json:"balloon_min"`
|
||||||
FreeMem uint64 `json:"freemem"`
|
FreeMem uint64 `json:"freemem"`
|
||||||
MemInfo *struct {
|
MemInfo *VMMemInfo `json:"meminfo,omitempty"`
|
||||||
Used uint64 `json:"used"`
|
Disk uint64 `json:"disk"`
|
||||||
Free uint64 `json:"free"`
|
MaxDisk uint64 `json:"maxdisk"`
|
||||||
Total uint64 `json:"total"`
|
DiskRead uint64 `json:"diskread"`
|
||||||
} `json:"meminfo,omitempty"`
|
DiskWrite uint64 `json:"diskwrite"`
|
||||||
Disk uint64 `json:"disk"`
|
NetIn uint64 `json:"netin"`
|
||||||
MaxDisk uint64 `json:"maxdisk"`
|
NetOut uint64 `json:"netout"`
|
||||||
DiskRead uint64 `json:"diskread"`
|
Uptime uint64 `json:"uptime"`
|
||||||
DiskWrite uint64 `json:"diskwrite"`
|
Agent int `json:"agent"`
|
||||||
NetIn uint64 `json:"netin"`
|
|
||||||
NetOut uint64 `json:"netout"`
|
|
||||||
Uptime uint64 `json:"uptime"`
|
|
||||||
Agent int `json:"agent"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetZFSPoolStatus gets the status of ZFS pools on a node
|
// GetZFSPoolStatus gets the status of ZFS pools on a node
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue