kvm/internal/audio/monitor_adaptive_optimizer.go
Alex P 1d1658db15 refactor(audio): replace GetConfig() calls with direct Config access
This change replaces all instances of GetConfig() function calls with direct access to the Config variable throughout the audio package. The modification improves performance by eliminating function call overhead and simplifies the codebase by removing unnecessary indirection.

The commit also includes minor optimizations in validation logic and connection handling, while maintaining all existing functionality. Error handling remains robust with appropriate fallbacks when config values are not available.

Additional improvements include:
- Enhanced connection health monitoring in UnifiedAudioClient
- Optimized validation functions using cached config values
- Reduced memory allocations in hot paths
- Improved error recovery during quality changes
2025-09-08 17:30:49 +00:00

330 lines
12 KiB
Go

package audio
import (
"context"
"sync"
"sync/atomic"
"time"
"github.com/rs/zerolog"
)
// AdaptiveOptimizer automatically adjusts audio parameters based on latency metrics
type AdaptiveOptimizer struct {
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
optimizationCount int64 // Number of optimizations performed (atomic)
lastOptimization int64 // Timestamp of last optimization (atomic)
optimizationLevel int64 // Current optimization level (0-10) (atomic)
stabilityScore int64 // Current stability score (0-100) (atomic)
optimizationInterval int64 // Current optimization interval in nanoseconds (atomic)
latencyMonitor *LatencyMonitor
bufferManager *AdaptiveBufferManager
logger zerolog.Logger
// Control channels
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
// Configuration
config OptimizerConfig
// Stability tracking
stabilityHistory []StabilityMetric
stabilityMutex sync.RWMutex
}
// StabilityMetric tracks system stability over time
type StabilityMetric struct {
Timestamp time.Time
LatencyStdev float64
CPUVariance float64
MemoryStable bool
ErrorRate float64
StabilityScore int
}
// OptimizerConfig holds configuration for the adaptive optimizer
type OptimizerConfig struct {
MaxOptimizationLevel int // Maximum optimization level (0-10)
CooldownPeriod time.Duration // Minimum time between optimizations
Aggressiveness float64 // How aggressively to optimize (0.0-1.0)
RollbackThreshold time.Duration // Latency threshold to rollback optimizations
StabilityPeriod time.Duration // Time to wait for stability after optimization
// Adaptive interval configuration
MinOptimizationInterval time.Duration // Minimum optimization interval (high stability)
MaxOptimizationInterval time.Duration // Maximum optimization interval (low stability)
StabilityThreshold int // Stability score threshold for interval adjustment
StabilityHistorySize int // Number of stability metrics to track
}
// DefaultOptimizerConfig returns a sensible default configuration
func DefaultOptimizerConfig() OptimizerConfig {
return OptimizerConfig{
MaxOptimizationLevel: 8,
CooldownPeriod: Config.CooldownPeriod,
Aggressiveness: Config.OptimizerAggressiveness,
RollbackThreshold: Config.RollbackThreshold,
StabilityPeriod: Config.AdaptiveOptimizerStability,
// Adaptive interval defaults
MinOptimizationInterval: 100 * time.Millisecond, // High stability: check every 100ms
MaxOptimizationInterval: 2 * time.Second, // Low stability: check every 2s
StabilityThreshold: 70, // Stability score threshold
StabilityHistorySize: 20, // Track last 20 stability metrics
}
}
// NewAdaptiveOptimizer creates a new adaptive optimizer
func NewAdaptiveOptimizer(latencyMonitor *LatencyMonitor, bufferManager *AdaptiveBufferManager, config OptimizerConfig, logger zerolog.Logger) *AdaptiveOptimizer {
ctx, cancel := context.WithCancel(context.Background())
optimizer := &AdaptiveOptimizer{
latencyMonitor: latencyMonitor,
bufferManager: bufferManager,
config: config,
logger: logger.With().Str("component", "adaptive-optimizer").Logger(),
ctx: ctx,
cancel: cancel,
stabilityHistory: make([]StabilityMetric, 0, config.StabilityHistorySize),
}
// Initialize stability score and optimization interval
atomic.StoreInt64(&optimizer.stabilityScore, 50) // Start with medium stability
atomic.StoreInt64(&optimizer.optimizationInterval, int64(config.MaxOptimizationInterval))
// Register as latency monitor callback
latencyMonitor.AddOptimizationCallback(optimizer.handleLatencyOptimization)
return optimizer
}
// Start begins the adaptive optimization process
func (ao *AdaptiveOptimizer) Start() {
ao.wg.Add(1)
go ao.optimizationLoop()
ao.logger.Debug().Msg("adaptive optimizer started")
}
// Stop stops the adaptive optimizer
func (ao *AdaptiveOptimizer) Stop() {
ao.cancel()
ao.wg.Wait()
ao.logger.Debug().Msg("adaptive optimizer stopped")
}
// initializeStrategies sets up the available optimization strategies
// handleLatencyOptimization is called when latency optimization is needed
func (ao *AdaptiveOptimizer) handleLatencyOptimization(metrics LatencyMetrics) error {
currentLevel := atomic.LoadInt64(&ao.optimizationLevel)
lastOpt := atomic.LoadInt64(&ao.lastOptimization)
// Check cooldown period
if time.Since(time.Unix(0, lastOpt)) < ao.config.CooldownPeriod {
return nil
}
// Determine if we need to increase or decrease optimization level
targetLevel := ao.calculateTargetOptimizationLevel(metrics)
if targetLevel > currentLevel {
return ao.increaseOptimization(int(targetLevel))
} else if targetLevel < currentLevel {
return ao.decreaseOptimization(int(targetLevel))
}
return nil
}
// calculateTargetOptimizationLevel determines the appropriate optimization level
func (ao *AdaptiveOptimizer) calculateTargetOptimizationLevel(metrics LatencyMetrics) int64 {
// Base calculation on current latency vs target
latencyRatio := float64(metrics.Current) / float64(Config.AdaptiveOptimizerLatencyTarget) // 50ms target
// Adjust based on trend
switch metrics.Trend {
case LatencyTrendIncreasing:
latencyRatio *= 1.2 // Be more aggressive
case LatencyTrendDecreasing:
latencyRatio *= 0.8 // Be less aggressive
case LatencyTrendVolatile:
latencyRatio *= 1.1 // Slightly more aggressive
}
// Apply aggressiveness factor
latencyRatio *= ao.config.Aggressiveness
// Convert to optimization level
targetLevel := int64(latencyRatio * Config.LatencyScalingFactor) // Scale to 0-10 range
if targetLevel > int64(ao.config.MaxOptimizationLevel) {
targetLevel = int64(ao.config.MaxOptimizationLevel)
}
if targetLevel < 0 {
targetLevel = 0
}
return targetLevel
}
// increaseOptimization applies optimization strategies up to the target level
func (ao *AdaptiveOptimizer) increaseOptimization(targetLevel int) error {
atomic.StoreInt64(&ao.optimizationLevel, int64(targetLevel))
atomic.StoreInt64(&ao.lastOptimization, time.Now().UnixNano())
atomic.AddInt64(&ao.optimizationCount, 1)
return nil
}
// decreaseOptimization rolls back optimization strategies to the target level
func (ao *AdaptiveOptimizer) decreaseOptimization(targetLevel int) error {
atomic.StoreInt64(&ao.optimizationLevel, int64(targetLevel))
atomic.StoreInt64(&ao.lastOptimization, time.Now().UnixNano())
return nil
}
// optimizationLoop runs the main optimization monitoring loop
func (ao *AdaptiveOptimizer) optimizationLoop() {
defer ao.wg.Done()
// Start with initial interval
currentInterval := time.Duration(atomic.LoadInt64(&ao.optimizationInterval))
ticker := time.NewTicker(currentInterval)
defer ticker.Stop()
for {
select {
case <-ao.ctx.Done():
return
case <-ticker.C:
// Update stability metrics and check for optimization needs
ao.updateStabilityMetrics()
ao.checkStability()
// Adjust optimization interval based on current stability
newInterval := ao.calculateOptimizationInterval()
if newInterval != currentInterval {
currentInterval = newInterval
ticker.Reset(currentInterval)
ao.logger.Debug().Dur("new_interval", currentInterval).Int64("stability_score", atomic.LoadInt64(&ao.stabilityScore)).Msg("adjusted optimization interval")
}
}
}
}
// checkStability monitors system stability and rolls back if needed
func (ao *AdaptiveOptimizer) checkStability() {
metrics := ao.latencyMonitor.GetMetrics()
// Check if we need to rollback due to excessive latency
if metrics.Current > ao.config.RollbackThreshold {
currentLevel := int(atomic.LoadInt64(&ao.optimizationLevel))
if currentLevel > 0 {
ao.logger.Warn().Dur("current_latency", metrics.Current).Dur("threshold", ao.config.RollbackThreshold).Msg("rolling back optimizations due to excessive latency")
if err := ao.decreaseOptimization(currentLevel - 1); err != nil {
ao.logger.Error().Err(err).Msg("failed to decrease optimization level")
}
}
}
}
// updateStabilityMetrics calculates and stores current system stability metrics
func (ao *AdaptiveOptimizer) updateStabilityMetrics() {
metrics := ao.latencyMonitor.GetMetrics()
// Calculate stability score based on multiple factors
stabilityScore := ao.calculateStabilityScore(metrics)
atomic.StoreInt64(&ao.stabilityScore, int64(stabilityScore))
// Store stability metric in history
stabilityMetric := StabilityMetric{
Timestamp: time.Now(),
LatencyStdev: float64(metrics.Jitter), // Use Jitter as variance indicator
CPUVariance: 0.0, // TODO: Get from system metrics
MemoryStable: true, // TODO: Get from system metrics
ErrorRate: 0.0, // TODO: Get from error tracking
StabilityScore: stabilityScore,
}
ao.stabilityMutex.Lock()
ao.stabilityHistory = append(ao.stabilityHistory, stabilityMetric)
if len(ao.stabilityHistory) > ao.config.StabilityHistorySize {
ao.stabilityHistory = ao.stabilityHistory[1:]
}
ao.stabilityMutex.Unlock()
}
// calculateStabilityScore computes a stability score (0-100) based on system metrics
func (ao *AdaptiveOptimizer) calculateStabilityScore(metrics LatencyMetrics) int {
// Base score starts at 100 (perfect stability)
score := 100.0
// Penalize high jitter (latency variance)
if metrics.Jitter > 0 && metrics.Average > 0 {
jitterRatio := float64(metrics.Jitter) / float64(metrics.Average)
variancePenalty := jitterRatio * 50 // Scale jitter impact
score -= variancePenalty
}
// Penalize latency trend volatility
switch metrics.Trend {
case LatencyTrendVolatile:
score -= 20
case LatencyTrendIncreasing:
score -= 10
case LatencyTrendDecreasing:
score += 5 // Slight bonus for improving latency
}
// Ensure score is within bounds
if score < 0 {
score = 0
}
if score > 100 {
score = 100
}
return int(score)
}
// calculateOptimizationInterval determines the optimization interval based on stability
func (ao *AdaptiveOptimizer) calculateOptimizationInterval() time.Duration {
stabilityScore := atomic.LoadInt64(&ao.stabilityScore)
// High stability = shorter intervals (more frequent optimization)
// Low stability = longer intervals (less frequent optimization)
if stabilityScore >= int64(ao.config.StabilityThreshold) {
// High stability: use minimum interval
interval := ao.config.MinOptimizationInterval
atomic.StoreInt64(&ao.optimizationInterval, int64(interval))
return interval
} else {
// Low stability: scale interval based on stability score
// Lower stability = longer intervals
stabilityRatio := float64(stabilityScore) / float64(ao.config.StabilityThreshold)
minInterval := float64(ao.config.MinOptimizationInterval)
maxInterval := float64(ao.config.MaxOptimizationInterval)
// Linear interpolation between min and max intervals
interval := time.Duration(minInterval + (maxInterval-minInterval)*(1.0-stabilityRatio))
atomic.StoreInt64(&ao.optimizationInterval, int64(interval))
return interval
}
}
// GetOptimizationStats returns current optimization statistics
func (ao *AdaptiveOptimizer) GetOptimizationStats() map[string]interface{} {
return map[string]interface{}{
"optimization_level": atomic.LoadInt64(&ao.optimizationLevel),
"optimization_count": atomic.LoadInt64(&ao.optimizationCount),
"last_optimization": time.Unix(0, atomic.LoadInt64(&ao.lastOptimization)),
"stability_score": atomic.LoadInt64(&ao.stabilityScore),
"optimization_interval": time.Duration(atomic.LoadInt64(&ao.optimizationInterval)),
}
}
// Strategy implementation methods (stubs for now)