mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 11:30:15 +00:00
371 lines
10 KiB
Go
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
|
|
}
|