kvm/internal/audio/monitor_adaptive_optimizer.go

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)