mirror of
https://github.com/jetkvm/kvm.git
synced 2025-09-16 08:38:14 +00:00
Replace hardcoded values with centralized config constants for better maintainability and flexibility. This includes sleep durations, buffer sizes, thresholds, and various audio processing parameters. The changes affect multiple components including buffer pools, latency monitoring, IPC, and audio processing. This refactoring makes it easier to adjust parameters without modifying individual files. Key changes: - Replace hardcoded sleep durations with config values - Centralize buffer sizes and pool configurations - Move thresholds and limits to config - Update audio quality presets to use config values
339 lines
12 KiB
Go
339 lines
12 KiB
Go
package audio
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/jetkvm/kvm/internal/logging"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
// AdaptiveBufferConfig holds configuration for adaptive buffer sizing
|
|
type AdaptiveBufferConfig struct {
|
|
// Buffer size limits (in frames)
|
|
MinBufferSize int
|
|
MaxBufferSize int
|
|
DefaultBufferSize int
|
|
|
|
// System load thresholds
|
|
LowCPUThreshold float64 // Below this, increase buffer size
|
|
HighCPUThreshold float64 // Above this, decrease buffer size
|
|
LowMemoryThreshold float64 // Below this, increase buffer size
|
|
HighMemoryThreshold float64 // Above this, decrease buffer size
|
|
|
|
// Latency thresholds (in milliseconds)
|
|
TargetLatency time.Duration
|
|
MaxLatency time.Duration
|
|
|
|
// Adaptation parameters
|
|
AdaptationInterval time.Duration
|
|
SmoothingFactor float64 // 0.0-1.0, higher = more responsive
|
|
}
|
|
|
|
// DefaultAdaptiveBufferConfig returns optimized config for JetKVM hardware
|
|
func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig {
|
|
return AdaptiveBufferConfig{
|
|
// Conservative buffer sizes for 256MB RAM constraint
|
|
MinBufferSize: 3, // Minimum 3 frames (slightly higher for stability)
|
|
MaxBufferSize: 20, // Maximum 20 frames (increased for high load scenarios)
|
|
DefaultBufferSize: 6, // Default 6 frames (increased for better stability)
|
|
|
|
// CPU thresholds optimized for single-core ARM Cortex A7 under load
|
|
LowCPUThreshold: GetConfig().LowCPUThreshold * 100, // Below 20% CPU
|
|
HighCPUThreshold: GetConfig().HighCPUThreshold * 100, // Above 60% CPU (lowered to be more responsive)
|
|
|
|
// Memory thresholds for 256MB total RAM
|
|
LowMemoryThreshold: GetConfig().LowMemoryThreshold * 100, // Below 35% memory usage
|
|
HighMemoryThreshold: GetConfig().HighMemoryThreshold * 100, // Above 75% memory usage (lowered for earlier response)
|
|
|
|
// Latency targets
|
|
TargetLatency: GetConfig().TargetLatency, // Target 20ms latency
|
|
MaxLatency: GetConfig().MaxLatencyTarget, // Max acceptable latency
|
|
|
|
// Adaptation settings
|
|
AdaptationInterval: GetConfig().BufferUpdateInterval, // Check every 500ms
|
|
SmoothingFactor: GetConfig().SmoothingFactor, // Moderate responsiveness
|
|
}
|
|
}
|
|
|
|
// AdaptiveBufferManager manages dynamic buffer sizing based on system conditions
|
|
type AdaptiveBufferManager struct {
|
|
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
|
|
currentInputBufferSize int64 // Current input buffer size (atomic)
|
|
currentOutputBufferSize int64 // Current output buffer size (atomic)
|
|
averageLatency int64 // Average latency in nanoseconds (atomic)
|
|
systemCPUPercent int64 // System CPU percentage * 100 (atomic)
|
|
systemMemoryPercent int64 // System memory percentage * 100 (atomic)
|
|
adaptationCount int64 // Metrics tracking (atomic)
|
|
|
|
config AdaptiveBufferConfig
|
|
logger zerolog.Logger
|
|
processMonitor *ProcessMonitor
|
|
|
|
// Control channels
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
|
|
// Metrics tracking
|
|
lastAdaptation time.Time
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewAdaptiveBufferManager creates a new adaptive buffer manager
|
|
func NewAdaptiveBufferManager(config AdaptiveBufferConfig) *AdaptiveBufferManager {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &AdaptiveBufferManager{
|
|
currentInputBufferSize: int64(config.DefaultBufferSize),
|
|
currentOutputBufferSize: int64(config.DefaultBufferSize),
|
|
config: config,
|
|
logger: logging.GetDefaultLogger().With().Str("component", "adaptive-buffer").Logger(),
|
|
processMonitor: GetProcessMonitor(),
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
lastAdaptation: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Start begins the adaptive buffer management
|
|
func (abm *AdaptiveBufferManager) Start() {
|
|
abm.wg.Add(1)
|
|
go abm.adaptationLoop()
|
|
abm.logger.Info().Msg("Adaptive buffer manager started")
|
|
}
|
|
|
|
// Stop stops the adaptive buffer management
|
|
func (abm *AdaptiveBufferManager) Stop() {
|
|
abm.cancel()
|
|
abm.wg.Wait()
|
|
abm.logger.Info().Msg("Adaptive buffer manager stopped")
|
|
}
|
|
|
|
// GetInputBufferSize returns the current recommended input buffer size
|
|
func (abm *AdaptiveBufferManager) GetInputBufferSize() int {
|
|
return int(atomic.LoadInt64(&abm.currentInputBufferSize))
|
|
}
|
|
|
|
// GetOutputBufferSize returns the current recommended output buffer size
|
|
func (abm *AdaptiveBufferManager) GetOutputBufferSize() int {
|
|
return int(atomic.LoadInt64(&abm.currentOutputBufferSize))
|
|
}
|
|
|
|
// UpdateLatency updates the current latency measurement
|
|
func (abm *AdaptiveBufferManager) UpdateLatency(latency time.Duration) {
|
|
// Use exponential moving average for latency
|
|
currentAvg := atomic.LoadInt64(&abm.averageLatency)
|
|
newLatency := latency.Nanoseconds()
|
|
|
|
if currentAvg == 0 {
|
|
atomic.StoreInt64(&abm.averageLatency, newLatency)
|
|
} else {
|
|
// Exponential moving average: 70% historical, 30% current
|
|
newAvg := int64(float64(currentAvg)*GetConfig().HistoricalWeight + float64(newLatency)*GetConfig().CurrentWeight)
|
|
atomic.StoreInt64(&abm.averageLatency, newAvg)
|
|
}
|
|
}
|
|
|
|
// adaptationLoop is the main loop that adjusts buffer sizes
|
|
func (abm *AdaptiveBufferManager) adaptationLoop() {
|
|
defer abm.wg.Done()
|
|
|
|
ticker := time.NewTicker(abm.config.AdaptationInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-abm.ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
abm.adaptBufferSizes()
|
|
}
|
|
}
|
|
}
|
|
|
|
// adaptBufferSizes analyzes system conditions and adjusts buffer sizes
|
|
func (abm *AdaptiveBufferManager) adaptBufferSizes() {
|
|
// Collect current system metrics
|
|
metrics := abm.processMonitor.GetCurrentMetrics()
|
|
if len(metrics) == 0 {
|
|
return // No metrics available
|
|
}
|
|
|
|
// Calculate system-wide CPU and memory usage
|
|
totalCPU := 0.0
|
|
totalMemory := 0.0
|
|
processCount := 0
|
|
|
|
for _, metric := range metrics {
|
|
totalCPU += metric.CPUPercent
|
|
totalMemory += metric.MemoryPercent
|
|
processCount++
|
|
}
|
|
|
|
if processCount == 0 {
|
|
return
|
|
}
|
|
|
|
// Store system metrics atomically
|
|
systemCPU := totalCPU // Total CPU across all monitored processes
|
|
systemMemory := totalMemory / float64(processCount) // Average memory usage
|
|
|
|
atomic.StoreInt64(&abm.systemCPUPercent, int64(systemCPU*100))
|
|
atomic.StoreInt64(&abm.systemMemoryPercent, int64(systemMemory*100))
|
|
|
|
// Get current latency
|
|
currentLatencyNs := atomic.LoadInt64(&abm.averageLatency)
|
|
currentLatency := time.Duration(currentLatencyNs)
|
|
|
|
// Calculate adaptation factors
|
|
cpuFactor := abm.calculateCPUFactor(systemCPU)
|
|
memoryFactor := abm.calculateMemoryFactor(systemMemory)
|
|
latencyFactor := abm.calculateLatencyFactor(currentLatency)
|
|
|
|
// Combine factors with weights (CPU has highest priority for KVM coexistence)
|
|
combinedFactor := GetConfig().CPUMemoryWeight*cpuFactor + GetConfig().MemoryWeight*memoryFactor + GetConfig().LatencyWeight*latencyFactor
|
|
|
|
// Apply adaptation with smoothing
|
|
currentInput := float64(atomic.LoadInt64(&abm.currentInputBufferSize))
|
|
currentOutput := float64(atomic.LoadInt64(&abm.currentOutputBufferSize))
|
|
|
|
// Calculate new buffer sizes
|
|
newInputSize := abm.applyAdaptation(currentInput, combinedFactor)
|
|
newOutputSize := abm.applyAdaptation(currentOutput, combinedFactor)
|
|
|
|
// Update buffer sizes if they changed significantly
|
|
adjustmentMade := false
|
|
if math.Abs(newInputSize-currentInput) >= 0.5 || math.Abs(newOutputSize-currentOutput) >= 0.5 {
|
|
atomic.StoreInt64(&abm.currentInputBufferSize, int64(math.Round(newInputSize)))
|
|
atomic.StoreInt64(&abm.currentOutputBufferSize, int64(math.Round(newOutputSize)))
|
|
|
|
atomic.AddInt64(&abm.adaptationCount, 1)
|
|
abm.mutex.Lock()
|
|
abm.lastAdaptation = time.Now()
|
|
abm.mutex.Unlock()
|
|
adjustmentMade = true
|
|
|
|
abm.logger.Debug().
|
|
Float64("cpu_percent", systemCPU).
|
|
Float64("memory_percent", systemMemory).
|
|
Dur("latency", currentLatency).
|
|
Float64("combined_factor", combinedFactor).
|
|
Int("new_input_size", int(newInputSize)).
|
|
Int("new_output_size", int(newOutputSize)).
|
|
Msg("Adapted buffer sizes")
|
|
}
|
|
|
|
// Update metrics with current state
|
|
currentInputSize := int(atomic.LoadInt64(&abm.currentInputBufferSize))
|
|
currentOutputSize := int(atomic.LoadInt64(&abm.currentOutputBufferSize))
|
|
UpdateAdaptiveBufferMetrics(currentInputSize, currentOutputSize, systemCPU, systemMemory, adjustmentMade)
|
|
}
|
|
|
|
// calculateCPUFactor returns adaptation factor based on CPU usage
|
|
// Returns: -1.0 (decrease buffers) to +1.0 (increase buffers)
|
|
func (abm *AdaptiveBufferManager) calculateCPUFactor(cpuPercent float64) float64 {
|
|
if cpuPercent > abm.config.HighCPUThreshold {
|
|
// High CPU: decrease buffers to reduce latency and give CPU to KVM
|
|
return -1.0
|
|
} else if cpuPercent < abm.config.LowCPUThreshold {
|
|
// Low CPU: increase buffers for better quality
|
|
return 1.0
|
|
}
|
|
// Medium CPU: linear interpolation
|
|
midpoint := (abm.config.HighCPUThreshold + abm.config.LowCPUThreshold) / 2
|
|
return (midpoint - cpuPercent) / (midpoint - abm.config.LowCPUThreshold)
|
|
}
|
|
|
|
// calculateMemoryFactor returns adaptation factor based on memory usage
|
|
func (abm *AdaptiveBufferManager) calculateMemoryFactor(memoryPercent float64) float64 {
|
|
if memoryPercent > abm.config.HighMemoryThreshold {
|
|
// High memory: decrease buffers to free memory
|
|
return -1.0
|
|
} else if memoryPercent < abm.config.LowMemoryThreshold {
|
|
// Low memory: increase buffers for better performance
|
|
return 1.0
|
|
}
|
|
// Medium memory: linear interpolation
|
|
midpoint := (abm.config.HighMemoryThreshold + abm.config.LowMemoryThreshold) / 2
|
|
return (midpoint - memoryPercent) / (midpoint - abm.config.LowMemoryThreshold)
|
|
}
|
|
|
|
// calculateLatencyFactor returns adaptation factor based on latency
|
|
func (abm *AdaptiveBufferManager) calculateLatencyFactor(latency time.Duration) float64 {
|
|
if latency > abm.config.MaxLatency {
|
|
// High latency: decrease buffers
|
|
return -1.0
|
|
} else if latency < abm.config.TargetLatency {
|
|
// Low latency: can increase buffers
|
|
return 1.0
|
|
}
|
|
// Medium latency: linear interpolation
|
|
midLatency := (abm.config.MaxLatency + abm.config.TargetLatency) / 2
|
|
return float64(midLatency-latency) / float64(midLatency-abm.config.TargetLatency)
|
|
}
|
|
|
|
// applyAdaptation applies the adaptation factor to current buffer size
|
|
func (abm *AdaptiveBufferManager) applyAdaptation(currentSize, factor float64) float64 {
|
|
// Calculate target size based on factor
|
|
var targetSize float64
|
|
if factor > 0 {
|
|
// Increase towards max
|
|
targetSize = currentSize + factor*(float64(abm.config.MaxBufferSize)-currentSize)
|
|
} else {
|
|
// Decrease towards min
|
|
targetSize = currentSize + factor*(currentSize-float64(abm.config.MinBufferSize))
|
|
}
|
|
|
|
// Apply smoothing
|
|
newSize := currentSize + abm.config.SmoothingFactor*(targetSize-currentSize)
|
|
|
|
// Clamp to valid range
|
|
return math.Max(float64(abm.config.MinBufferSize),
|
|
math.Min(float64(abm.config.MaxBufferSize), newSize))
|
|
}
|
|
|
|
// GetStats returns current adaptation statistics
|
|
func (abm *AdaptiveBufferManager) GetStats() map[string]interface{} {
|
|
abm.mutex.RLock()
|
|
lastAdaptation := abm.lastAdaptation
|
|
abm.mutex.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"input_buffer_size": abm.GetInputBufferSize(),
|
|
"output_buffer_size": abm.GetOutputBufferSize(),
|
|
"average_latency_ms": float64(atomic.LoadInt64(&abm.averageLatency)) / 1e6,
|
|
"system_cpu_percent": float64(atomic.LoadInt64(&abm.systemCPUPercent)) / GetConfig().PercentageMultiplier,
|
|
"system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / GetConfig().PercentageMultiplier,
|
|
"adaptation_count": atomic.LoadInt64(&abm.adaptationCount),
|
|
"last_adaptation": lastAdaptation,
|
|
}
|
|
}
|
|
|
|
// Global adaptive buffer manager instance
|
|
var globalAdaptiveBufferManager *AdaptiveBufferManager
|
|
var adaptiveBufferOnce sync.Once
|
|
|
|
// GetAdaptiveBufferManager returns the global adaptive buffer manager instance
|
|
func GetAdaptiveBufferManager() *AdaptiveBufferManager {
|
|
adaptiveBufferOnce.Do(func() {
|
|
globalAdaptiveBufferManager = NewAdaptiveBufferManager(DefaultAdaptiveBufferConfig())
|
|
})
|
|
return globalAdaptiveBufferManager
|
|
}
|
|
|
|
// StartAdaptiveBuffering starts the global adaptive buffer manager
|
|
func StartAdaptiveBuffering() {
|
|
GetAdaptiveBufferManager().Start()
|
|
}
|
|
|
|
// StopAdaptiveBuffering stops the global adaptive buffer manager
|
|
func StopAdaptiveBuffering() {
|
|
if globalAdaptiveBufferManager != nil {
|
|
globalAdaptiveBufferManager.Stop()
|
|
}
|
|
}
|