Pulse/internal/monitoring/metrics_history.go
2025-10-11 23:29:47 +00:00

371 lines
10 KiB
Go

package monitoring
import (
"sync"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/types"
)
// Use MetricPoint from types package
type MetricPoint = types.MetricPoint
// GuestMetrics holds historical metrics for a single guest
type GuestMetrics struct {
CPU []MetricPoint `json:"cpu"`
Memory []MetricPoint `json:"memory"`
Disk []MetricPoint `json:"disk"`
DiskRead []MetricPoint `json:"diskread"`
DiskWrite []MetricPoint `json:"diskwrite"`
NetworkIn []MetricPoint `json:"netin"`
NetworkOut []MetricPoint `json:"netout"`
}
// StorageMetrics holds historical metrics for a single storage
type StorageMetrics struct {
Usage []MetricPoint `json:"usage"`
Used []MetricPoint `json:"used"`
Total []MetricPoint `json:"total"`
Avail []MetricPoint `json:"avail"`
}
// MetricsHistory maintains historical metrics for all guests and nodes
type MetricsHistory struct {
mu sync.RWMutex
guestMetrics map[string]*GuestMetrics // key: guestID
nodeMetrics map[string]*GuestMetrics // key: nodeID
storageMetrics map[string]*StorageMetrics // key: storageID
maxDataPoints int
retentionTime time.Duration
}
// NewMetricsHistory creates a new metrics history tracker
func NewMetricsHistory(maxDataPoints int, retentionTime time.Duration) *MetricsHistory {
return &MetricsHistory{
guestMetrics: make(map[string]*GuestMetrics),
nodeMetrics: make(map[string]*GuestMetrics),
storageMetrics: make(map[string]*StorageMetrics),
maxDataPoints: maxDataPoints,
retentionTime: retentionTime,
}
}
// AddGuestMetric adds a metric value for a guest
func (mh *MetricsHistory) AddGuestMetric(guestID string, metricType string, value float64, timestamp time.Time) {
mh.mu.Lock()
defer mh.mu.Unlock()
// Initialize guest metrics if not exists
if _, exists := mh.guestMetrics[guestID]; !exists {
mh.guestMetrics[guestID] = &GuestMetrics{
CPU: make([]MetricPoint, 0, mh.maxDataPoints),
Memory: make([]MetricPoint, 0, mh.maxDataPoints),
Disk: make([]MetricPoint, 0, mh.maxDataPoints),
DiskRead: make([]MetricPoint, 0, mh.maxDataPoints),
DiskWrite: make([]MetricPoint, 0, mh.maxDataPoints),
NetworkIn: make([]MetricPoint, 0, mh.maxDataPoints),
NetworkOut: make([]MetricPoint, 0, mh.maxDataPoints),
}
}
metrics := mh.guestMetrics[guestID]
point := MetricPoint{Value: value, Timestamp: timestamp}
// Add metric based on type
switch metricType {
case "cpu":
metrics.CPU = mh.appendMetric(metrics.CPU, point)
case "memory":
metrics.Memory = mh.appendMetric(metrics.Memory, point)
case "disk":
metrics.Disk = mh.appendMetric(metrics.Disk, point)
case "diskread":
metrics.DiskRead = mh.appendMetric(metrics.DiskRead, point)
case "diskwrite":
metrics.DiskWrite = mh.appendMetric(metrics.DiskWrite, point)
case "netin":
metrics.NetworkIn = mh.appendMetric(metrics.NetworkIn, point)
case "netout":
metrics.NetworkOut = mh.appendMetric(metrics.NetworkOut, point)
}
}
// AddNodeMetric adds a metric value for a node
func (mh *MetricsHistory) AddNodeMetric(nodeID string, metricType string, value float64, timestamp time.Time) {
mh.mu.Lock()
defer mh.mu.Unlock()
// Initialize node metrics if not exists
if _, exists := mh.nodeMetrics[nodeID]; !exists {
mh.nodeMetrics[nodeID] = &GuestMetrics{
CPU: make([]MetricPoint, 0, mh.maxDataPoints),
Memory: make([]MetricPoint, 0, mh.maxDataPoints),
Disk: make([]MetricPoint, 0, mh.maxDataPoints),
}
}
metrics := mh.nodeMetrics[nodeID]
point := MetricPoint{Value: value, Timestamp: timestamp}
// Add metric based on type
switch metricType {
case "cpu":
metrics.CPU = mh.appendMetric(metrics.CPU, point)
case "memory":
metrics.Memory = mh.appendMetric(metrics.Memory, point)
case "disk":
metrics.Disk = mh.appendMetric(metrics.Disk, point)
}
}
// appendMetric appends a metric point and maintains max data points and retention
func (mh *MetricsHistory) appendMetric(metrics []MetricPoint, point MetricPoint) []MetricPoint {
// Append new point
metrics = append(metrics, point)
// Remove old points beyond retention time
cutoffTime := time.Now().Add(-mh.retentionTime)
startIdx := 0
for i, p := range metrics {
if p.Timestamp.After(cutoffTime) {
startIdx = i
break
}
}
if startIdx > 0 {
metrics = metrics[startIdx:]
}
// Ensure we don't exceed max data points
if len(metrics) > mh.maxDataPoints {
// Keep the most recent points
metrics = metrics[len(metrics)-mh.maxDataPoints:]
}
return metrics
}
// GetGuestMetrics returns historical metrics for a guest
func (mh *MetricsHistory) GetGuestMetrics(guestID string, metricType string, duration time.Duration) []MetricPoint {
mh.mu.RLock()
defer mh.mu.RUnlock()
metrics, exists := mh.guestMetrics[guestID]
if !exists {
return []MetricPoint{}
}
cutoffTime := time.Now().Add(-duration)
var data []MetricPoint
switch metricType {
case "cpu":
data = metrics.CPU
case "memory":
data = metrics.Memory
case "disk":
data = metrics.Disk
case "diskread":
data = metrics.DiskRead
case "diskwrite":
data = metrics.DiskWrite
case "netin":
data = metrics.NetworkIn
case "netout":
data = metrics.NetworkOut
default:
return []MetricPoint{}
}
// Filter by duration
result := make([]MetricPoint, 0)
for _, point := range data {
if point.Timestamp.After(cutoffTime) {
result = append(result, point)
}
}
return result
}
// GetNodeMetrics returns historical metrics for a node
func (mh *MetricsHistory) GetNodeMetrics(nodeID string, metricType string, duration time.Duration) []MetricPoint {
mh.mu.RLock()
defer mh.mu.RUnlock()
metrics, exists := mh.nodeMetrics[nodeID]
if !exists {
return []MetricPoint{}
}
cutoffTime := time.Now().Add(-duration)
var data []MetricPoint
switch metricType {
case "cpu":
data = metrics.CPU
case "memory":
data = metrics.Memory
case "disk":
data = metrics.Disk
default:
return []MetricPoint{}
}
// Filter by duration
result := make([]MetricPoint, 0)
for _, point := range data {
if point.Timestamp.After(cutoffTime) {
result = append(result, point)
}
}
return result
}
// GetAllGuestMetrics returns all metrics for a guest within a duration
func (mh *MetricsHistory) GetAllGuestMetrics(guestID string, duration time.Duration) map[string][]MetricPoint {
mh.mu.RLock()
defer mh.mu.RUnlock()
result := make(map[string][]MetricPoint)
cutoffTime := time.Now().Add(-duration)
metrics, exists := mh.guestMetrics[guestID]
if !exists {
return result
}
// Helper function to filter by time
filterByTime := func(data []MetricPoint) []MetricPoint {
filtered := make([]MetricPoint, 0)
for _, point := range data {
if point.Timestamp.After(cutoffTime) {
filtered = append(filtered, point)
}
}
return filtered
}
result["cpu"] = filterByTime(metrics.CPU)
result["memory"] = filterByTime(metrics.Memory)
result["disk"] = filterByTime(metrics.Disk)
result["diskread"] = filterByTime(metrics.DiskRead)
result["diskwrite"] = filterByTime(metrics.DiskWrite)
result["netin"] = filterByTime(metrics.NetworkIn)
result["netout"] = filterByTime(metrics.NetworkOut)
return result
}
// AddStorageMetric adds a metric value for storage
func (mh *MetricsHistory) AddStorageMetric(storageID string, metricType string, value float64, timestamp time.Time) {
mh.mu.Lock()
defer mh.mu.Unlock()
// Initialize storage metrics if not exists
if _, exists := mh.storageMetrics[storageID]; !exists {
mh.storageMetrics[storageID] = &StorageMetrics{
Usage: make([]MetricPoint, 0, mh.maxDataPoints),
Used: make([]MetricPoint, 0, mh.maxDataPoints),
Total: make([]MetricPoint, 0, mh.maxDataPoints),
Avail: make([]MetricPoint, 0, mh.maxDataPoints),
}
}
metrics := mh.storageMetrics[storageID]
point := MetricPoint{Value: value, Timestamp: timestamp}
// Add metric based on type
switch metricType {
case "usage":
metrics.Usage = mh.appendMetric(metrics.Usage, point)
case "used":
metrics.Used = mh.appendMetric(metrics.Used, point)
case "total":
metrics.Total = mh.appendMetric(metrics.Total, point)
case "avail":
metrics.Avail = mh.appendMetric(metrics.Avail, point)
}
}
// GetAllStorageMetrics returns all metrics for storage within a duration
func (mh *MetricsHistory) GetAllStorageMetrics(storageID string, duration time.Duration) map[string][]MetricPoint {
mh.mu.RLock()
defer mh.mu.RUnlock()
result := make(map[string][]MetricPoint)
cutoffTime := time.Now().Add(-duration)
metrics, exists := mh.storageMetrics[storageID]
if !exists {
return result
}
// Helper function to filter by time
filterByTime := func(data []MetricPoint) []MetricPoint {
filtered := make([]MetricPoint, 0)
for _, point := range data {
if point.Timestamp.After(cutoffTime) {
filtered = append(filtered, point)
}
}
return filtered
}
result["usage"] = filterByTime(metrics.Usage)
result["used"] = filterByTime(metrics.Used)
result["total"] = filterByTime(metrics.Total)
result["avail"] = filterByTime(metrics.Avail)
return result
}
// Cleanup removes old data points beyond retention time
func (mh *MetricsHistory) Cleanup() {
mh.mu.Lock()
defer mh.mu.Unlock()
cutoffTime := time.Now().Add(-mh.retentionTime)
// Cleanup guest metrics
for _, metrics := range mh.guestMetrics {
metrics.CPU = mh.cleanupMetrics(metrics.CPU, cutoffTime)
metrics.Memory = mh.cleanupMetrics(metrics.Memory, cutoffTime)
metrics.Disk = mh.cleanupMetrics(metrics.Disk, cutoffTime)
metrics.DiskRead = mh.cleanupMetrics(metrics.DiskRead, cutoffTime)
metrics.DiskWrite = mh.cleanupMetrics(metrics.DiskWrite, cutoffTime)
metrics.NetworkIn = mh.cleanupMetrics(metrics.NetworkIn, cutoffTime)
metrics.NetworkOut = mh.cleanupMetrics(metrics.NetworkOut, cutoffTime)
}
// Cleanup node metrics
for _, metrics := range mh.nodeMetrics {
metrics.CPU = mh.cleanupMetrics(metrics.CPU, cutoffTime)
metrics.Memory = mh.cleanupMetrics(metrics.Memory, cutoffTime)
metrics.Disk = mh.cleanupMetrics(metrics.Disk, cutoffTime)
}
// Cleanup storage metrics
for _, metrics := range mh.storageMetrics {
metrics.Usage = mh.cleanupMetrics(metrics.Usage, cutoffTime)
metrics.Used = mh.cleanupMetrics(metrics.Used, cutoffTime)
metrics.Total = mh.cleanupMetrics(metrics.Total, cutoffTime)
metrics.Avail = mh.cleanupMetrics(metrics.Avail, cutoffTime)
}
}
// cleanupMetrics removes points older than cutoff time
func (mh *MetricsHistory) cleanupMetrics(metrics []MetricPoint, cutoffTime time.Time) []MetricPoint {
startIdx := 0
for i, p := range metrics {
if p.Timestamp.After(cutoffTime) {
startIdx = i
break
}
}
if startIdx > 0 {
return metrics[startIdx:]
}
return metrics
}