diff --git a/cookies.txt b/cookies.txt deleted file mode 100644 index 3fb6bea..0000000 --- a/cookies.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Netscape HTTP Cookie File -# https://curl.se/docs/http-cookies.html -# This file was generated by libcurl! Edit at your own risk. - -#HttpOnly_192.168.100.214 FALSE / FALSE 1756968962 authToken 3b0b77eb-3771-4eb2-9704-ffcdf3ba788b diff --git a/internal/audio/metrics.go b/internal/audio/metrics.go index d7d8b92..d3597f4 100644 --- a/internal/audio/metrics.go +++ b/internal/audio/metrics.go @@ -721,26 +721,17 @@ func GetLastMetricsUpdate() time.Time { // StartMetricsUpdater starts a goroutine that periodically updates Prometheus metrics func StartMetricsUpdater() { + // Start the centralized metrics collector + registry := GetMetricsRegistry() + registry.StartMetricsCollector() + + // Start a separate goroutine for periodic updates go func() { ticker := time.NewTicker(5 * time.Second) // Update every 5 seconds defer ticker.Stop() for range ticker.C { - // Update audio output metrics - audioMetrics := GetAudioMetrics() - UpdateAudioMetrics(convertAudioMetricsToUnified(audioMetrics)) - - // Update microphone input metrics - micMetrics := GetAudioInputMetrics() - UpdateMicrophoneMetrics(convertAudioInputMetricsToUnified(micMetrics)) - - // Update audio configuration metrics - audioConfig := GetAudioConfig() - UpdateAudioConfigMetrics(audioConfig) - micConfig := GetMicrophoneConfig() - UpdateMicrophoneConfigMetrics(micConfig) - - // Update memory metrics + // Update memory metrics (not part of centralized registry) UpdateMemoryMetrics() } }() diff --git a/internal/audio/metrics_registry.go b/internal/audio/metrics_registry.go new file mode 100644 index 0000000..7ecdffd --- /dev/null +++ b/internal/audio/metrics_registry.go @@ -0,0 +1,151 @@ +//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) + } + }() +} diff --git a/web.go b/web.go index 95822d9..60b1c04 100644 --- a/web.go +++ b/web.go @@ -212,7 +212,8 @@ func setupRouter() *gin.Engine { }) protected.GET("/audio/metrics", func(c *gin.Context) { - metrics := audio.GetAudioMetrics() + registry := audio.GetMetricsRegistry() + metrics := registry.GetAudioMetrics() c.JSON(200, gin.H{ "frames_received": metrics.FramesReceived, "frames_dropped": metrics.FramesDropped, @@ -399,19 +400,8 @@ func setupRouter() *gin.Engine { }) protected.GET("/microphone/metrics", func(c *gin.Context) { - if currentSession == nil || currentSession.AudioInputManager == nil { - c.JSON(200, gin.H{ - "frames_sent": 0, - "frames_dropped": 0, - "bytes_processed": 0, - "last_frame_time": "", - "connection_drops": 0, - "average_latency": "0.0ms", - }) - return - } - - metrics := currentSession.AudioInputManager.GetMetrics() + registry := audio.GetMetricsRegistry() + metrics := registry.GetAudioInputMetrics() c.JSON(200, gin.H{ "frames_sent": metrics.FramesSent, "frames_dropped": metrics.FramesDropped,