kvm/internal/audio/metrics_registry.go
Alex P 0d4176cf98 refactor(audio): centralize metrics collection with new registry
Introduce MetricsRegistry to serve as single source of truth for audio metrics
Remove duplicate metrics collection logic from web endpoints
Add callback mechanism for metrics updates
2025-08-28 10:01:35 +00:00

152 lines
4.5 KiB
Go

//go:build cgo
package audio
import (
"sync"
"sync/atomic"
"time"
)
// MetricsRegistry provides a centralized source of truth for all audio metrics
// This eliminates duplication between session-specific and global managers
type MetricsRegistry struct {
mu sync.RWMutex
audioMetrics AudioMetrics
audioInputMetrics AudioInputMetrics
audioConfig AudioConfig
microphoneConfig AudioConfig
lastUpdate int64 // Unix timestamp
}
var (
globalMetricsRegistry *MetricsRegistry
registryOnce sync.Once
)
// GetMetricsRegistry returns the global metrics registry instance
func GetMetricsRegistry() *MetricsRegistry {
registryOnce.Do(func() {
globalMetricsRegistry = &MetricsRegistry{
lastUpdate: time.Now().Unix(),
}
})
return globalMetricsRegistry
}
// UpdateAudioMetrics updates the centralized audio output metrics
func (mr *MetricsRegistry) UpdateAudioMetrics(metrics AudioMetrics) {
mr.mu.Lock()
mr.audioMetrics = metrics
mr.lastUpdate = time.Now().Unix()
mr.mu.Unlock()
// Update Prometheus metrics directly to avoid circular dependency
UpdateAudioMetrics(convertAudioMetricsToUnified(metrics))
}
// UpdateAudioInputMetrics updates the centralized audio input metrics
func (mr *MetricsRegistry) UpdateAudioInputMetrics(metrics AudioInputMetrics) {
mr.mu.Lock()
mr.audioInputMetrics = metrics
mr.lastUpdate = time.Now().Unix()
mr.mu.Unlock()
// Update Prometheus metrics directly to avoid circular dependency
UpdateMicrophoneMetrics(convertAudioInputMetricsToUnified(metrics))
}
// UpdateAudioConfig updates the centralized audio configuration
func (mr *MetricsRegistry) UpdateAudioConfig(config AudioConfig) {
mr.mu.Lock()
mr.audioConfig = config
mr.lastUpdate = time.Now().Unix()
mr.mu.Unlock()
// Update Prometheus metrics directly
UpdateAudioConfigMetrics(config)
}
// UpdateMicrophoneConfig updates the centralized microphone configuration
func (mr *MetricsRegistry) UpdateMicrophoneConfig(config AudioConfig) {
mr.mu.Lock()
mr.microphoneConfig = config
mr.lastUpdate = time.Now().Unix()
mr.mu.Unlock()
// Update Prometheus metrics directly
UpdateMicrophoneConfigMetrics(config)
}
// GetAudioMetrics returns the current audio output metrics
func (mr *MetricsRegistry) GetAudioMetrics() AudioMetrics {
mr.mu.RLock()
defer mr.mu.RUnlock()
return mr.audioMetrics
}
// GetAudioInputMetrics returns the current audio input metrics
func (mr *MetricsRegistry) GetAudioInputMetrics() AudioInputMetrics {
mr.mu.RLock()
defer mr.mu.RUnlock()
return mr.audioInputMetrics
}
// GetAudioConfig returns the current audio configuration
func (mr *MetricsRegistry) GetAudioConfig() AudioConfig {
mr.mu.RLock()
defer mr.mu.RUnlock()
return mr.audioConfig
}
// GetMicrophoneConfig returns the current microphone configuration
func (mr *MetricsRegistry) GetMicrophoneConfig() AudioConfig {
mr.mu.RLock()
defer mr.mu.RUnlock()
return mr.microphoneConfig
}
// GetLastUpdate returns the timestamp of the last metrics update
func (mr *MetricsRegistry) GetLastUpdate() time.Time {
timestamp := atomic.LoadInt64(&mr.lastUpdate)
return time.Unix(timestamp, 0)
}
// StartMetricsCollector starts a background goroutine to collect metrics
func (mr *MetricsRegistry) StartMetricsCollector() {
go func() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
// Collect from session-specific manager if available
if sessionProvider := GetSessionProvider(); sessionProvider != nil && sessionProvider.IsSessionActive() {
if inputManager := sessionProvider.GetAudioInputManager(); inputManager != nil {
metrics := inputManager.GetMetrics()
mr.UpdateAudioInputMetrics(metrics)
}
} else {
// Fallback to global manager if no session is active
globalManager := getAudioInputManager()
metrics := globalManager.GetMetrics()
mr.UpdateAudioInputMetrics(metrics)
}
// Collect audio output metrics directly from global metrics variable to avoid circular dependency
audioMetrics := AudioMetrics{
FramesReceived: atomic.LoadInt64(&metrics.FramesReceived),
FramesDropped: atomic.LoadInt64(&metrics.FramesDropped),
BytesProcessed: atomic.LoadInt64(&metrics.BytesProcessed),
ConnectionDrops: atomic.LoadInt64(&metrics.ConnectionDrops),
LastFrameTime: metrics.LastFrameTime,
AverageLatency: metrics.AverageLatency,
}
mr.UpdateAudioMetrics(audioMetrics)
// Collect configuration directly from global variables to avoid circular dependency
mr.UpdateAudioConfig(currentConfig)
mr.UpdateMicrophoneConfig(currentMicrophoneConfig)
}
}()
}