Files
pulse/internal/monitoring/monitor_previous_state.go
T
pulse-triage[bot] 8b56fc72e4 fix(monitoring): prefer linked agent memory for Proxmox LXC
Extend the #1962 linked-agent memory fallback to Proxmox LXC containers (#2148). A correlated online Pulse agent running inside a container now supplies the container's memory metric instead of the cache-inclusive cluster/resources fallback, which can badly under-report shared-memory workloads. The agent sample is only trusted when its total matches the container's configured limit, so an agent without lxcfs cannot leak host memory into the guest row. Threads the previous-guest agent map through the efficient and node-by-node container builders and reads agent-owned memory from merged ContainerView resources.

Change-source: pulse-maintainer
2026-09-21 23:24:33 +01:00

223 lines
6.5 KiB
Go

package monitoring
import (
"strings"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
)
type previousGuestContext struct {
vms []models.VM
vmsByID map[string]models.VM
containers []models.Container
containersByID map[string]models.Container
containerOCIByVMID map[int]bool
hostAgentsByVMID map[string]models.Host
}
func (m *Monitor) previousGuestContextForInstance(instanceName string) previousGuestContext {
ctx := previousGuestContext{
vms: make([]models.VM, 0),
vmsByID: make(map[string]models.VM),
containers: make([]models.Container, 0),
containersByID: make(map[string]models.Container),
containerOCIByVMID: make(map[int]bool),
hostAgentsByVMID: make(map[string]models.Host),
}
readState := m.GetUnifiedReadStateOrSnapshot()
if readState == nil {
return ctx
}
for _, vm := range readState.VMs() {
if vm == nil || vm.Instance() != instanceName {
continue
}
modelVM := previousVMFromView(vm)
ctx.vms = append(ctx.vms, modelVM)
if modelVM.ID != "" {
ctx.vmsByID[modelVM.ID] = modelVM
}
guestID := makeGuestID(modelVM.Instance, modelVM.Node, modelVM.VMID)
if guestID != "" {
ctx.vmsByID[guestID] = modelVM
if memory, ok := vm.LinkedAgentMemory(); ok {
ctx.hostAgentsByVMID[guestID] = models.Host{LinkedVMID: guestID, Status: "online", Memory: memory}
}
}
}
for _, ct := range readState.Containers() {
if ct == nil || ct.Instance() != instanceName {
continue
}
container := previousContainerFromView(ct)
ctx.containers = append(ctx.containers, container)
if container.ID != "" {
ctx.containersByID[container.ID] = container
}
guestID := makeGuestID(container.Instance, container.Node, container.VMID)
if guestID != "" {
ctx.containersByID[guestID] = container
if memory, ok := ct.LinkedAgentMemory(); ok {
ctx.hostAgentsByVMID[guestID] = models.Host{LinkedVMID: guestID, Status: "online", Memory: memory}
}
}
if container.VMID > 0 && (strings.EqualFold(strings.TrimSpace(container.Type), "oci") || container.IsOCI) {
ctx.containerOCIByVMID[container.VMID] = true
}
}
for _, host := range readState.Hosts() {
if host == nil {
continue
}
modelHost := previousHostFromView(host)
if modelHost.Status != "online" {
continue
}
if modelHost.LinkedVMID != "" {
ctx.hostAgentsByVMID[modelHost.LinkedVMID] = modelHost
}
if modelHost.LinkedContainerID != "" {
ctx.hostAgentsByVMID[modelHost.LinkedContainerID] = modelHost
}
}
return ctx
}
func (m *Monitor) previousNodesForInstance(instanceName string) (map[string]models.Memory, []models.Node) {
prevNodeMemory := make(map[string]models.Memory)
prevInstanceNodes := make([]models.Node, 0)
readState := m.GetUnifiedReadStateOrSnapshot()
if readState == nil {
return prevNodeMemory, prevInstanceNodes
}
for _, existingNode := range readState.Nodes() {
if existingNode == nil || existingNode.Instance() != instanceName {
continue
}
modelNode := previousNodeFromView(existingNode)
prevNodeMemory[modelNode.ID] = modelNode.Memory
prevInstanceNodes = append(prevInstanceNodes, modelNode)
}
return prevNodeMemory, prevInstanceNodes
}
func previousVMFromView(vm *unifiedresources.VMView) models.VM {
if vm == nil {
return models.VM{}
}
instance := vm.Instance()
node := vm.Node()
vmid := vm.VMID()
return models.VM{
ID: makeGuestID(instance, node, vmid),
Instance: instance,
Node: node,
VMID: vmid,
Name: vm.Name(),
Type: "qemu",
Status: vm.RuntimeStatus(),
IPAddresses: vm.IPAddresses(),
OSName: vm.OSName(),
OSVersion: vm.OSVersion(),
AgentVersion: vm.AgentVersion(),
Lock: vm.Lock(),
Disk: models.Disk{
Used: vm.DiskUsed(),
Total: vm.DiskTotal(),
Free: max(0, vm.DiskTotal()-vm.DiskUsed()),
Usage: vm.DiskPercent(),
},
NetworkInterfaces: guestNetworkInterfacesFromReadStateView(vm.NetworkInterfaces()),
Disks: guestDisksFromReadStateView(vm.Disks()),
DiskStatusReason: vm.DiskStatusReason(),
LastSeen: vm.LastSeen(),
}
}
func previousContainerFromView(ct *unifiedresources.ContainerView) models.Container {
if ct == nil {
return models.Container{}
}
instance := ct.Instance()
node := ct.Node()
vmid := ct.VMID()
return models.Container{
ID: makeGuestID(instance, node, vmid),
Instance: instance,
Node: node,
VMID: vmid,
Name: ct.Name(),
Status: ct.RuntimeStatus(),
Type: ct.ContainerType(),
IsOCI: ct.IsOCI(),
LastSeen: ct.LastSeen(),
IPAddresses: ct.IPAddresses(),
NetworkInterfaces: guestNetworkInterfacesFromReadStateView(ct.NetworkInterfaces()),
OSName: ct.OSName(),
OSTemplate: ct.OSTemplate(),
HasDocker: ct.HasDocker(),
DockerCheckedAt: ct.DockerCheckedAt(),
Disks: guestDisksFromReadStateView(ct.Disks()),
Lock: ct.Lock(),
Memory: models.Memory{
Used: ct.MemoryUsed(),
Total: ct.MemoryTotal(),
Usage: ct.MemoryPercent() / 100,
},
}
}
func previousHostFromView(host *unifiedresources.HostView) models.Host {
if host == nil {
return models.Host{}
}
return models.Host{
ID: host.ID(),
Hostname: host.Hostname(),
Status: string(host.Status()),
LinkedVMID: host.LinkedVMID(),
LinkedContainerID: host.LinkedContainerID(),
LastSeen: host.LastSeen(),
Disks: guestDisksFromReadStateView(host.Disks()),
Memory: models.Memory{
Used: host.MemoryUsed(),
Total: host.MemoryTotal(),
Usage: host.MemoryPercent() / 100,
},
}
}
func previousNodeFromView(node *unifiedresources.NodeView) models.Node {
if node == nil {
return models.Node{}
}
return models.Node{
ID: node.ID(),
Name: node.NodeName(),
DisplayName: node.Name(),
Instance: node.Instance(),
Host: node.HostURL(),
Status: string(node.Status()),
Uptime: node.Uptime(),
IsClusterMember: node.IsClusterMember(),
LastSeen: node.LastSeen(),
LoadAverage: node.LoadAverage(),
PVEVersion: node.PVEVersion(),
KernelVersion: node.KernelVersion(),
NetworkInterfaces: hostNetworkInterfacesFromReadStateView(node.NetworkInterfaces()),
Memory: models.Memory{
Used: node.MemoryUsed(),
Total: node.MemoryTotal(),
Usage: node.MemoryPercent() / 100,
},
}
}