package main import ( "fmt" "log" "time" ) func runHistoryCollector(collect func() (dashboardMetrics, error), store *Store) { record := func() { metrics, err := collect() if err != nil { log.Printf("Не удалось собрать исторические метрики: %v", err) return } if err := store.AddHistory(historyPointFromMetrics(metrics)); err != nil { log.Printf("Не удалось сохранить исторические метрики: %v", err) } store.AddEntityMetrics(metrics) } record() ticker := time.NewTicker(time.Minute) defer ticker.Stop() for range ticker.C { record() } } func (s *Store) AddEntityMetrics(metrics dashboardMetrics) { ts := time.Now().Unix() for _, p := range metrics.ZFS.Pools { v := p.CapacityPercent s.AddEntityHistory("zfs", p.Name, ts, &v, nil) } for _, g := range metrics.Guests.Guests { if g.Template != 0 { continue } cpu := g.CPU * 100 var mem float64 if g.MaxMemory > 0 { mem = float64(g.MemoryBytes) / float64(g.MaxMemory) * 100 } s.AddEntityHistory("guest", fmt.Sprintf("%d · %s", g.VMID, g.Name), ts, &cpu, &mem) } for _, d := range metrics.Disks { iops := d.ReadIOPS + d.WriteIOPS util := d.Utilization s.AddEntityHistory("disk-io", d.Name+" · "+d.Model, ts, &iops, &util) latency := d.LatencyMs s.AddEntityHistory("disk-latency", d.Name+" · "+d.Model, ts, &latency, nil) if d.Temperature == nil { continue } var wear *float64 if d.WearUsedPercent != nil { wear = d.WearUsedPercent } s.AddEntityHistory("disk", d.Name+" · "+d.Model, ts, d.Temperature, wear) } for _, task := range metrics.Activity.Tasks { if task.Type == "vzdump" && task.Duration > 0 && task.EndTime >= time.Now().Add(-15*time.Minute).Unix() { duration := float64(task.Duration) s.AddEntityHistory("backup-duration", "VMID "+task.ID, ts, &duration, nil) } } s.PruneEntityHistory() } func historyPointFromMetrics(metrics dashboardMetrics) HistoryPoint { point := HistoryPoint{ Timestamp: time.Now().Unix(), CPUUsage: metrics.CPU.UsagePercent, MemoryUsage: metrics.Memory.UsagePercent, SwapUsage: metrics.Memory.SwapPercent, RootUsage: metrics.Storage.UsagePercent, NetworkReceive: metrics.Network.ReceivePerSec, NetworkTransmit: metrics.Network.TransmitPerSec, UPSCharge: metrics.UPS.ChargePercent, UPSLoad: metrics.UPS.LoadPercent, } if len(metrics.CPU.Temperatures) > 0 { for _, temperature := range metrics.CPU.Temperatures { point.CPUTemperature += temperature.Celsius } point.CPUTemperature /= float64(len(metrics.CPU.Temperatures)) } var zfsSize, zfsUsed uint64 for _, pool := range metrics.ZFS.Pools { zfsSize += pool.SizeBytes zfsUsed += pool.AllocatedBytes } if zfsSize > 0 { point.ZFSUsage = float64(zfsUsed) / float64(zfsSize) * 100 } var guestCores, guestUsedCPU float64 var guestMemory, guestMaxMemory uint64 for _, guest := range metrics.Guests.Guests { if guest.Template != 0 || guest.Status != "running" { continue } guestCores += guest.MaxCPU guestUsedCPU += guest.CPU * guest.MaxCPU guestMemory += guest.MemoryBytes guestMaxMemory += guest.MaxMemory } if guestCores > 0 { point.GuestsCPU = guestUsedCPU / guestCores * 100 } if guestMaxMemory > 0 { point.GuestsMemory = float64(guestMemory) / float64(guestMaxMemory) * 100 } return point }