package dashboard import ( "fmt" "sort" "sync" "time" ) // MetricSnapshot represents a point-in-time metric value type MetricSnapshot struct { Timestamp time.Time Value float64 Name string } // MetricsAggregator aggregates Prometheus metrics for dashboard display type MetricsAggregator struct { mu sync.RWMutex metrics map[string][]MetricSnapshot ttl time.Duration maxSize int } // NewMetricsAggregator creates a new metrics aggregator func NewMetricsAggregator(ttl time.Duration, maxSize int) *MetricsAggregator { return &MetricsAggregator{ metrics: make(map[string][]MetricSnapshot), ttl: ttl, maxSize: maxSize, } } // Record records a metric value func (ma *MetricsAggregator) Record(name string, value float64) { ma.mu.Lock() defer ma.mu.Unlock() snapshot := MetricSnapshot{ Timestamp: time.Now(), Value: value, Name: name, } ma.metrics[name] = append(ma.metrics[name], snapshot) // Trim old entries if len(ma.metrics[name]) > ma.maxSize { ma.metrics[name] = ma.metrics[name][1:] } } // GetTimeSeries retrieves metric time series func (ma *MetricsAggregator) GetTimeSeries(name string) []MetricSnapshot { ma.mu.RLock() defer ma.mu.RUnlock() snapshots, exists := ma.metrics[name] if !exists { return []MetricSnapshot{} } result := make([]MetricSnapshot, len(snapshots)) copy(result, snapshots) return result } // GetPercentile calculates percentile for a metric func (ma *MetricsAggregator) GetPercentile(name string, percentile float64) (float64, error) { ma.mu.RLock() defer ma.mu.RUnlock() snapshots, exists := ma.metrics[name] if !exists || len(snapshots) == 0 { return 0, fmt.Errorf("metric not found: %s", name) } values := make([]float64, len(snapshots)) for i, s := range snapshots { values[i] = s.Value } sort.Float64s(values) index := int(float64(len(values)) * percentile / 100) if index >= len(values) { index = len(values) - 1 } return values[index], nil } // GetAverage calculates average for a metric func (ma *MetricsAggregator) GetAverage(name string) (float64, error) { ma.mu.RLock() defer ma.mu.RUnlock() snapshots, exists := ma.metrics[name] if !exists || len(snapshots) == 0 { return 0, fmt.Errorf("metric not found: %s", name) } sum := 0.0 for _, s := range snapshots { sum += s.Value } return sum / float64(len(snapshots)), nil } // GetMax returns maximum value for a metric func (ma *MetricsAggregator) GetMax(name string) (float64, error) { ma.mu.RLock() defer ma.mu.RUnlock() snapshots, exists := ma.metrics[name] if !exists || len(snapshots) == 0 { return 0, fmt.Errorf("metric not found: %s", name) } max := snapshots[0].Value for _, s := range snapshots { if s.Value > max { max = s.Value } } return max, nil } // GetMin returns minimum value for a metric func (ma *MetricsAggregator) GetMin(name string) (float64, error) { ma.mu.RLock() defer ma.mu.RUnlock() snapshots, exists := ma.metrics[name] if !exists || len(snapshots) == 0 { return 0, fmt.Errorf("metric not found: %s", name) } min := snapshots[0].Value for _, s := range snapshots { if s.Value < min { min = s.Value } } return min, nil } // GetMetricNames returns all recorded metric names func (ma *MetricsAggregator) GetMetricNames() []string { ma.mu.RLock() defer ma.mu.RUnlock() names := make([]string, 0, len(ma.metrics)) for name := range ma.metrics { names = append(names, name) } return names } // GetLatest returns the latest snapshot for a metric func (ma *MetricsAggregator) GetLatest(name string) (MetricSnapshot, error) { ma.mu.RLock() defer ma.mu.RUnlock() snapshots, exists := ma.metrics[name] if !exists || len(snapshots) == 0 { return MetricSnapshot{}, fmt.Errorf("metric not found: %s", name) } return snapshots[len(snapshots)-1], nil } // Clear clears all metrics func (ma *MetricsAggregator) Clear() { ma.mu.Lock() defer ma.mu.Unlock() ma.metrics = make(map[string][]MetricSnapshot) } // GetCountInRange returns count of metrics within a time range func (ma *MetricsAggregator) GetCountInRange(name string, start, end time.Time) (int, error) { ma.mu.RLock() defer ma.mu.RUnlock() snapshots, exists := ma.metrics[name] if !exists { return 0, fmt.Errorf("metric not found: %s", name) } count := 0 for _, s := range snapshots { if s.Timestamp.After(start) && s.Timestamp.Before(end) { count++ } } return count, nil }