Pulse/internal/ai/metrics_history_adapter.go
rcourtman 96af101c98 feat(ai): Add enriched context with historical trends and predictions
Phase 1 of Pulse AI differentiation:

- Create internal/ai/context package with types, trends, builder, formatter
- Implement linear regression for trend computation (growing/declining/stable/volatile)
- Add storage capacity predictions (predicts days until 90% and 100%)
- Wire MetricsHistory from monitor to patrol service
- Update patrol to use buildEnrichedContext instead of basic summary
- Update patrol prompt to reference trend indicators and predictions

This gives the AI awareness of historical patterns, enabling it to:
- Identify resources with concerning growth rates
- Predict capacity exhaustion before it happens
- Distinguish between stable high usage vs growing problems
- Provide more actionable, time-aware insights

All tests passing. Falls back to basic summary if metrics history unavailable.
2025-12-12 09:45:57 +00:00

85 lines
2.6 KiB
Go

package ai
import (
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
)
// MetricsHistoryAdapter adapts monitoring.MetricsHistory to the MetricsHistoryProvider interface
// This allows the patrol service to use the monitoring package's metrics history
// without creating a direct package dependency
type MetricsHistoryAdapter struct {
history *monitoring.MetricsHistory
}
// NewMetricsHistoryAdapter creates an adapter for the monitoring.MetricsHistory
func NewMetricsHistoryAdapter(history *monitoring.MetricsHistory) *MetricsHistoryAdapter {
if history == nil {
return nil
}
return &MetricsHistoryAdapter{history: history}
}
// GetNodeMetrics returns historical metrics for a node
func (a *MetricsHistoryAdapter) GetNodeMetrics(nodeID string, metricType string, duration time.Duration) []MetricPoint {
if a.history == nil {
return nil
}
points := a.history.GetNodeMetrics(nodeID, metricType, duration)
return convertMetricPoints(points)
}
// GetGuestMetrics returns historical metrics for a guest
func (a *MetricsHistoryAdapter) GetGuestMetrics(guestID string, metricType string, duration time.Duration) []MetricPoint {
if a.history == nil {
return nil
}
points := a.history.GetGuestMetrics(guestID, metricType, duration)
return convertMetricPoints(points)
}
// GetAllGuestMetrics returns all metrics for a guest
func (a *MetricsHistoryAdapter) GetAllGuestMetrics(guestID string, duration time.Duration) map[string][]MetricPoint {
if a.history == nil {
return nil
}
metricsMap := a.history.GetAllGuestMetrics(guestID, duration)
return convertMetricsMap(metricsMap)
}
// GetAllStorageMetrics returns all metrics for storage
func (a *MetricsHistoryAdapter) GetAllStorageMetrics(storageID string, duration time.Duration) map[string][]MetricPoint {
if a.history == nil {
return nil
}
metricsMap := a.history.GetAllStorageMetrics(storageID, duration)
return convertMetricsMap(metricsMap)
}
// convertMetricPoints converts from monitoring.MetricPoint to ai.MetricPoint
func convertMetricPoints(points []monitoring.MetricPoint) []MetricPoint {
if points == nil {
return nil
}
result := make([]MetricPoint, len(points))
for i, p := range points {
result[i] = MetricPoint{
Value: p.Value,
Timestamp: p.Timestamp,
}
}
return result
}
// convertMetricsMap converts a map of metric types to their points
func convertMetricsMap(metricsMap map[string][]monitoring.MetricPoint) map[string][]MetricPoint {
if metricsMap == nil {
return nil
}
result := make(map[string][]MetricPoint, len(metricsMap))
for key, points := range metricsMap {
result[key] = convertMetricPoints(points)
}
return result
}