mirror of https://github.com/jetkvm/kvm.git
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: 20.0, // Below 20% CPU
|
|
HighCPUThreshold: 60.0, // Above 60% CPU (lowered to be more responsive)
|
|
|
|
// Memory thresholds for 256MB total RAM
|
|
LowMemoryThreshold: 35.0, // Below 35% memory usage
|
|
HighMemoryThreshold: 75.0, // Above 75% memory usage (lowered for earlier response)
|
|
|
|
// Latency targets
|
|
TargetLatency: 20 * time.Millisecond, // Target 20ms latency
|
|
MaxLatency: 50 * time.Millisecond, // Max acceptable 50ms
|
|
|
|
// Adaptation settings
|
|
AdaptationInterval: 500 * time.Millisecond, // Check every 500ms
|
|
SmoothingFactor: 0.3, // 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)*0.7 + float64(newLatency)*0.3)
|
|
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 := 0.5*cpuFactor + 0.3*memoryFactor + 0.2*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)) / 100,
|
|
"system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / 100,
|
|
"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()
|
|
}
|
|
}
|