package monitoring import ( "sync" "github.com/rcourtman/pulse-go-rewrite/internal/types" ) // Use IOMetrics from types package type IOMetrics = types.IOMetrics // RateTracker tracks I/O metrics to calculate rates type RateTracker struct { mu sync.RWMutex previous map[string]IOMetrics lastRates map[string]RateCache } // RateCache stores the last calculated rates for a guest type RateCache struct { DiskReadRate float64 DiskWriteRate float64 NetInRate float64 NetOutRate float64 } // NewRateTracker creates a new rate tracker func NewRateTracker() *RateTracker { return &RateTracker{ previous: make(map[string]IOMetrics), lastRates: make(map[string]RateCache), } } // CalculateRates calculates I/O rates for a guest // Returns -1 for rates that don't have enough data yet (will be converted to null in JSON) func (rt *RateTracker) CalculateRates(guestID string, current IOMetrics) (diskReadRate, diskWriteRate, netInRate, netOutRate float64) { rt.mu.Lock() defer rt.mu.Unlock() prev, exists := rt.previous[guestID] if !exists { // No previous data, store it and return -1 to indicate no data available rt.previous[guestID] = current return -1, -1, -1, -1 } // Check if the values have actually changed (detect stale data) // If all cumulative values are the same, we're getting cached data from Proxmox if current.DiskRead == prev.DiskRead && current.DiskWrite == prev.DiskWrite && current.NetworkIn == prev.NetworkIn && current.NetworkOut == prev.NetworkOut { // Data hasn't changed - return last known good rates if lastRate, hasRate := rt.lastRates[guestID]; hasRate { return lastRate.DiskReadRate, lastRate.DiskWriteRate, lastRate.NetInRate, lastRate.NetOutRate } // No last rates available, return 0 return 0, 0, 0, 0 } // Data has changed, update our cache rt.previous[guestID] = current // Calculate time difference in seconds timeDiff := current.Timestamp.Sub(prev.Timestamp).Seconds() if timeDiff <= 0 { // Return last known rates if time hasn't advanced if lastRate, hasRate := rt.lastRates[guestID]; hasRate { return lastRate.DiskReadRate, lastRate.DiskWriteRate, lastRate.NetInRate, lastRate.NetOutRate } return 0, 0, 0, 0 } // Calculate rates (bytes per second) if current.DiskRead >= prev.DiskRead { diskReadRate = float64(current.DiskRead-prev.DiskRead) / timeDiff } if current.DiskWrite >= prev.DiskWrite { diskWriteRate = float64(current.DiskWrite-prev.DiskWrite) / timeDiff } if current.NetworkIn >= prev.NetworkIn { netInRate = float64(current.NetworkIn-prev.NetworkIn) / timeDiff } if current.NetworkOut >= prev.NetworkOut { netOutRate = float64(current.NetworkOut-prev.NetworkOut) / timeDiff } // Cache the calculated rates rt.lastRates[guestID] = RateCache{ DiskReadRate: diskReadRate, DiskWriteRate: diskWriteRate, NetInRate: netInRate, NetOutRate: netOutRate, } return } // Clear removes all tracked data func (rt *RateTracker) Clear() { rt.mu.Lock() defer rt.mu.Unlock() rt.previous = make(map[string]IOMetrics) rt.lastRates = make(map[string]RateCache) }