diff --git a/agent/pve.go b/agent/pve.go index 1a4d069c..a7d6660d 100644 --- a/agent/pve.go +++ b/agent/pve.go @@ -20,88 +20,6 @@ type pveManager struct { nodeStatsMap map[string]*container.PveNodeStats // Keeps track of pve node stats } -// Returns stats for all running VMs/LXCs -func (pm *pveManager) getPVEStats() ([]*container.PveNodeStats, error) { - if pm.client == nil { - return nil, errors.New("PVE client not configured") - } - cluster, err := pm.client.Cluster(context.Background()) - if err != nil { - slog.Error("Error getting cluster", "err", err) - return nil, err - } - resources, err := cluster.Resources(context.Background(), "vm") - if err != nil { - slog.Error("Error getting resources", "err", err, "resources", resources) - return nil, err - } - containersLength := len(resources) - containerIds := make(map[string]struct{}, containersLength) - - // only include running vms and lxcs on selected node - for _, resource := range resources { - if resource.Node == pm.nodeName && resource.Status == "running" { - containerIds[resource.ID] = struct{}{} - } - } - // remove invalid container stats - for id := range pm.nodeStatsMap { - if _, exists := containerIds[id]; !exists { - delete(pm.nodeStatsMap, id) - } - } - - // populate stats - stats := make([]*container.PveNodeStats, 0, len(containerIds)) - for _, resource := range resources { - if _, exists := containerIds[resource.ID]; !exists { - continue - } - resourceStats, initialized := pm.nodeStatsMap[resource.ID] - if !initialized { - resourceStats = &container.PveNodeStats{} - pm.nodeStatsMap[resource.ID] = resourceStats - } - // reset current stats - resourceStats.Cpu = 0 - resourceStats.Mem = 0 - resourceStats.Bandwidth = [2]uint64{0, 0} - // Store clean name (no type suffix) - resourceStats.Name = resource.Name - // Store resource ID (e.g. "qemu/100") in .Id (cbor key 7, json:"-") - resourceStats.Id = resource.ID - // Store type (e.g. "qemu" or "lxc") in .Image (cbor key 8, json:"-") - resourceStats.Type = resource.Type - // PVE limits (cbor-only, for pve_vms table) - resourceStats.MaxCPU = resource.MaxCPU - resourceStats.MaxMem = resource.MaxMem - resourceStats.Uptime = resource.Uptime - // prevent first run from sending all prev sent/recv bytes - total_sent := uint64(resource.NetOut) - total_recv := uint64(resource.NetIn) - var sent_delta, recv_delta float64 - if initialized { - secondsElapsed := time.Since(resourceStats.PrevReadTime).Seconds() - if secondsElapsed > 0 { - sent_delta = float64(total_sent-resourceStats.PrevNet.Sent) / secondsElapsed - recv_delta = float64(total_recv-resourceStats.PrevNet.Recv) / secondsElapsed - } - } - resourceStats.PrevNet.Sent = total_sent - resourceStats.PrevNet.Recv = total_recv - resourceStats.PrevReadTime = time.Now() - - // Update final stats values - resourceStats.Cpu = twoDecimals(100.0 * resource.CPU * float64(resource.MaxCPU) / float64(pm.cpuCount)) - resourceStats.Mem = bytesToMegabytes(float64(resource.Mem)) - resourceStats.Bandwidth = [2]uint64{uint64(sent_delta), uint64(recv_delta)} - - stats = append(stats, resourceStats) - } - - return stats, nil -} - // Creates a new PVE manager - may return nil if required environment variables are not set or if there is an error connecting to the API func newPVEManager() *pveManager { url, exists := GetEnv("PROXMOX_URL") @@ -156,3 +74,78 @@ func newPVEManager() *pveManager { return &pveManager } + +// Returns stats for all running VMs/LXCs +func (pm *pveManager) getPVEStats() ([]*container.PveNodeStats, error) { + if pm.client == nil { + return nil, errors.New("PVE client not configured") + } + cluster, err := pm.client.Cluster(context.Background()) + if err != nil { + slog.Error("Error getting cluster", "err", err) + return nil, err + } + resources, err := cluster.Resources(context.Background(), "vm") + if err != nil { + slog.Error("Error getting resources", "err", err, "resources", resources) + return nil, err + } + containersLength := len(resources) + resourceIds := make(map[string]struct{}, containersLength) + + // only include running vms and lxcs on selected node + for _, resource := range resources { + if resource.Node == pm.nodeName && resource.Status == "running" { + resourceIds[resource.ID] = struct{}{} + } + } + // remove invalid container stats + for id := range pm.nodeStatsMap { + if _, exists := resourceIds[id]; !exists { + delete(pm.nodeStatsMap, id) + } + } + + // populate stats + stats := make([]*container.PveNodeStats, 0, len(resourceIds)) + for _, resource := range resources { + if _, exists := resourceIds[resource.ID]; !exists { + continue + } + resourceStats, initialized := pm.nodeStatsMap[resource.ID] + if !initialized { + resourceStats = &container.PveNodeStats{} + pm.nodeStatsMap[resource.ID] = resourceStats + } + resourceStats.Name = resource.Name + resourceStats.Id = resource.ID + resourceStats.Type = resource.Type + resourceStats.MaxCPU = resource.MaxCPU + resourceStats.MaxMem = resource.MaxMem + resourceStats.Uptime = resource.Uptime + + // prevent first run from sending all prev sent/recv bytes + total_sent := resource.NetOut + total_recv := resource.NetIn + var sent_delta, recv_delta float64 + if initialized { + secondsElapsed := time.Since(resourceStats.PrevReadTime).Seconds() + if secondsElapsed > 0 { + sent_delta = float64(total_sent-resourceStats.PrevNet.Sent) / secondsElapsed + recv_delta = float64(total_recv-resourceStats.PrevNet.Recv) / secondsElapsed + } + } + resourceStats.PrevNet.Sent = total_sent + resourceStats.PrevNet.Recv = total_recv + resourceStats.PrevReadTime = time.Now() + + // Update final stats values + resourceStats.Cpu = twoDecimals(100.0 * resource.CPU * float64(resource.MaxCPU) / float64(pm.cpuCount)) + resourceStats.Mem = bytesToMegabytes(float64(resource.Mem)) + resourceStats.Bandwidth = [2]uint64{uint64(sent_delta), uint64(recv_delta)} + + stats = append(stats, resourceStats) + } + + return stats, nil +}