package main import ( "context" "encoding/json" "os/exec" "sort" "sync" "time" ) type GuestMetrics struct { VMID int `json:"vmid"` Name string `json:"name"` Type string `json:"type"` Node string `json:"node"` Status string `json:"status"` CPU float64 `json:"cpu"` MaxCPU float64 `json:"maxcpu"` MemoryBytes uint64 `json:"mem"` MaxMemory uint64 `json:"maxmem"` Uptime uint64 `json:"uptime"` Template int `json:"template"` } type GuestsMetrics struct { Available bool `json:"available"` Guests []GuestMetrics `json:"guests"` } type guestCollector struct { mu sync.Mutex cached GuestsMetrics updatedAt time.Time } func (c *guestCollector) collect() GuestsMetrics { c.mu.Lock() defer c.mu.Unlock() if time.Since(c.updatedAt) < 3*time.Second { return c.cached } c.cached = readGuests() c.updatedAt = time.Now() return c.cached } func readGuests() GuestsMetrics { if _, err := exec.LookPath("pvesh"); err != nil { return GuestsMetrics{} } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() output, err := exec.CommandContext(ctx, "pvesh", "get", "/cluster/resources", "--type", "vm", "--output-format", "json").Output() result := GuestsMetrics{Available: true} if err != nil { return result } if json.Unmarshal(output, &result.Guests) != nil { return result } sort.Slice(result.Guests, func(i, j int) bool { return result.Guests[i].VMID < result.Guests[j].VMID }) return result }