Fix: linting errors

This commit is contained in:
Alex P 2025-08-24 23:36:29 +00:00
parent a9a1082bcc
commit 3a28105f56
15 changed files with 320 additions and 317 deletions

View File

@ -14,20 +14,20 @@ import (
// AdaptiveBufferConfig holds configuration for adaptive buffer sizing // AdaptiveBufferConfig holds configuration for adaptive buffer sizing
type AdaptiveBufferConfig struct { type AdaptiveBufferConfig struct {
// Buffer size limits (in frames) // Buffer size limits (in frames)
MinBufferSize int MinBufferSize int
MaxBufferSize int MaxBufferSize int
DefaultBufferSize int DefaultBufferSize int
// System load thresholds // System load thresholds
LowCPUThreshold float64 // Below this, increase buffer size LowCPUThreshold float64 // Below this, increase buffer size
HighCPUThreshold float64 // Above this, decrease buffer size HighCPUThreshold float64 // Above this, decrease buffer size
LowMemoryThreshold float64 // Below this, increase buffer size LowMemoryThreshold float64 // Below this, increase buffer size
HighMemoryThreshold float64 // Above this, decrease buffer size HighMemoryThreshold float64 // Above this, decrease buffer size
// Latency thresholds (in milliseconds) // Latency thresholds (in milliseconds)
TargetLatency time.Duration TargetLatency time.Duration
MaxLatency time.Duration MaxLatency time.Duration
// Adaptation parameters // Adaptation parameters
AdaptationInterval time.Duration AdaptationInterval time.Duration
SmoothingFactor float64 // 0.0-1.0, higher = more responsive SmoothingFactor float64 // 0.0-1.0, higher = more responsive
@ -37,25 +37,25 @@ type AdaptiveBufferConfig struct {
func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig { func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig {
return AdaptiveBufferConfig{ return AdaptiveBufferConfig{
// Conservative buffer sizes for 256MB RAM constraint // Conservative buffer sizes for 256MB RAM constraint
MinBufferSize: 3, // Minimum 3 frames (slightly higher for stability) MinBufferSize: 3, // Minimum 3 frames (slightly higher for stability)
MaxBufferSize: 20, // Maximum 20 frames (increased for high load scenarios) MaxBufferSize: 20, // Maximum 20 frames (increased for high load scenarios)
DefaultBufferSize: 6, // Default 6 frames (increased for better stability) DefaultBufferSize: 6, // Default 6 frames (increased for better stability)
// CPU thresholds optimized for single-core ARM Cortex A7 under load // CPU thresholds optimized for single-core ARM Cortex A7 under load
LowCPUThreshold: 20.0, // Below 20% CPU LowCPUThreshold: 20.0, // Below 20% CPU
HighCPUThreshold: 60.0, // Above 60% CPU (lowered to be more responsive) HighCPUThreshold: 60.0, // Above 60% CPU (lowered to be more responsive)
// Memory thresholds for 256MB total RAM // Memory thresholds for 256MB total RAM
LowMemoryThreshold: 35.0, // Below 35% memory usage LowMemoryThreshold: 35.0, // Below 35% memory usage
HighMemoryThreshold: 75.0, // Above 75% memory usage (lowered for earlier response) HighMemoryThreshold: 75.0, // Above 75% memory usage (lowered for earlier response)
// Latency targets // Latency targets
TargetLatency: 20 * time.Millisecond, // Target 20ms latency TargetLatency: 20 * time.Millisecond, // Target 20ms latency
MaxLatency: 50 * time.Millisecond, // Max acceptable 50ms MaxLatency: 50 * time.Millisecond, // Max acceptable 50ms
// Adaptation settings // Adaptation settings
AdaptationInterval: 500 * time.Millisecond, // Check every 500ms AdaptationInterval: 500 * time.Millisecond, // Check every 500ms
SmoothingFactor: 0.3, // Moderate responsiveness SmoothingFactor: 0.3, // Moderate responsiveness
} }
} }
@ -64,38 +64,38 @@ type AdaptiveBufferManager struct {
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
currentInputBufferSize int64 // Current input buffer size (atomic) currentInputBufferSize int64 // Current input buffer size (atomic)
currentOutputBufferSize int64 // Current output buffer size (atomic) currentOutputBufferSize int64 // Current output buffer size (atomic)
averageLatency int64 // Average latency in nanoseconds (atomic) averageLatency int64 // Average latency in nanoseconds (atomic)
systemCPUPercent int64 // System CPU percentage * 100 (atomic) systemCPUPercent int64 // System CPU percentage * 100 (atomic)
systemMemoryPercent int64 // System memory percentage * 100 (atomic) systemMemoryPercent int64 // System memory percentage * 100 (atomic)
adaptationCount int64 // Metrics tracking (atomic) adaptationCount int64 // Metrics tracking (atomic)
config AdaptiveBufferConfig config AdaptiveBufferConfig
logger zerolog.Logger logger zerolog.Logger
processMonitor *ProcessMonitor processMonitor *ProcessMonitor
// Control channels // Control channels
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
wg sync.WaitGroup wg sync.WaitGroup
// Metrics tracking // Metrics tracking
lastAdaptation time.Time lastAdaptation time.Time
mutex sync.RWMutex mutex sync.RWMutex
} }
// NewAdaptiveBufferManager creates a new adaptive buffer manager // NewAdaptiveBufferManager creates a new adaptive buffer manager
func NewAdaptiveBufferManager(config AdaptiveBufferConfig) *AdaptiveBufferManager { func NewAdaptiveBufferManager(config AdaptiveBufferConfig) *AdaptiveBufferManager {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &AdaptiveBufferManager{ return &AdaptiveBufferManager{
currentInputBufferSize: int64(config.DefaultBufferSize), currentInputBufferSize: int64(config.DefaultBufferSize),
currentOutputBufferSize: int64(config.DefaultBufferSize), currentOutputBufferSize: int64(config.DefaultBufferSize),
config: config, config: config,
logger: logging.GetDefaultLogger().With().Str("component", "adaptive-buffer").Logger(), logger: logging.GetDefaultLogger().With().Str("component", "adaptive-buffer").Logger(),
processMonitor: GetProcessMonitor(), processMonitor: GetProcessMonitor(),
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
lastAdaptation: time.Now(), lastAdaptation: time.Now(),
} }
} }
@ -128,7 +128,7 @@ func (abm *AdaptiveBufferManager) UpdateLatency(latency time.Duration) {
// Use exponential moving average for latency // Use exponential moving average for latency
currentAvg := atomic.LoadInt64(&abm.averageLatency) currentAvg := atomic.LoadInt64(&abm.averageLatency)
newLatency := latency.Nanoseconds() newLatency := latency.Nanoseconds()
if currentAvg == 0 { if currentAvg == 0 {
atomic.StoreInt64(&abm.averageLatency, newLatency) atomic.StoreInt64(&abm.averageLatency, newLatency)
} else { } else {
@ -141,10 +141,10 @@ func (abm *AdaptiveBufferManager) UpdateLatency(latency time.Duration) {
// adaptationLoop is the main loop that adjusts buffer sizes // adaptationLoop is the main loop that adjusts buffer sizes
func (abm *AdaptiveBufferManager) adaptationLoop() { func (abm *AdaptiveBufferManager) adaptationLoop() {
defer abm.wg.Done() defer abm.wg.Done()
ticker := time.NewTicker(abm.config.AdaptationInterval) ticker := time.NewTicker(abm.config.AdaptationInterval)
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
case <-abm.ctx.Done(): case <-abm.ctx.Done():
@ -162,61 +162,61 @@ func (abm *AdaptiveBufferManager) adaptBufferSizes() {
if len(metrics) == 0 { if len(metrics) == 0 {
return // No metrics available return // No metrics available
} }
// Calculate system-wide CPU and memory usage // Calculate system-wide CPU and memory usage
totalCPU := 0.0 totalCPU := 0.0
totalMemory := 0.0 totalMemory := 0.0
processCount := 0 processCount := 0
for _, metric := range metrics { for _, metric := range metrics {
totalCPU += metric.CPUPercent totalCPU += metric.CPUPercent
totalMemory += metric.MemoryPercent totalMemory += metric.MemoryPercent
processCount++ processCount++
} }
if processCount == 0 { if processCount == 0 {
return return
} }
// Store system metrics atomically // Store system metrics atomically
systemCPU := totalCPU // Total CPU across all monitored processes systemCPU := totalCPU // Total CPU across all monitored processes
systemMemory := totalMemory / float64(processCount) // Average memory usage systemMemory := totalMemory / float64(processCount) // Average memory usage
atomic.StoreInt64(&abm.systemCPUPercent, int64(systemCPU*100)) atomic.StoreInt64(&abm.systemCPUPercent, int64(systemCPU*100))
atomic.StoreInt64(&abm.systemMemoryPercent, int64(systemMemory*100)) atomic.StoreInt64(&abm.systemMemoryPercent, int64(systemMemory*100))
// Get current latency // Get current latency
currentLatencyNs := atomic.LoadInt64(&abm.averageLatency) currentLatencyNs := atomic.LoadInt64(&abm.averageLatency)
currentLatency := time.Duration(currentLatencyNs) currentLatency := time.Duration(currentLatencyNs)
// Calculate adaptation factors // Calculate adaptation factors
cpuFactor := abm.calculateCPUFactor(systemCPU) cpuFactor := abm.calculateCPUFactor(systemCPU)
memoryFactor := abm.calculateMemoryFactor(systemMemory) memoryFactor := abm.calculateMemoryFactor(systemMemory)
latencyFactor := abm.calculateLatencyFactor(currentLatency) latencyFactor := abm.calculateLatencyFactor(currentLatency)
// Combine factors with weights (CPU has highest priority for KVM coexistence) // Combine factors with weights (CPU has highest priority for KVM coexistence)
combinedFactor := 0.5*cpuFactor + 0.3*memoryFactor + 0.2*latencyFactor combinedFactor := 0.5*cpuFactor + 0.3*memoryFactor + 0.2*latencyFactor
// Apply adaptation with smoothing // Apply adaptation with smoothing
currentInput := float64(atomic.LoadInt64(&abm.currentInputBufferSize)) currentInput := float64(atomic.LoadInt64(&abm.currentInputBufferSize))
currentOutput := float64(atomic.LoadInt64(&abm.currentOutputBufferSize)) currentOutput := float64(atomic.LoadInt64(&abm.currentOutputBufferSize))
// Calculate new buffer sizes // Calculate new buffer sizes
newInputSize := abm.applyAdaptation(currentInput, combinedFactor) newInputSize := abm.applyAdaptation(currentInput, combinedFactor)
newOutputSize := abm.applyAdaptation(currentOutput, combinedFactor) newOutputSize := abm.applyAdaptation(currentOutput, combinedFactor)
// Update buffer sizes if they changed significantly // Update buffer sizes if they changed significantly
adjustmentMade := false adjustmentMade := false
if math.Abs(newInputSize-currentInput) >= 0.5 || math.Abs(newOutputSize-currentOutput) >= 0.5 { if math.Abs(newInputSize-currentInput) >= 0.5 || math.Abs(newOutputSize-currentOutput) >= 0.5 {
atomic.StoreInt64(&abm.currentInputBufferSize, int64(math.Round(newInputSize))) atomic.StoreInt64(&abm.currentInputBufferSize, int64(math.Round(newInputSize)))
atomic.StoreInt64(&abm.currentOutputBufferSize, int64(math.Round(newOutputSize))) atomic.StoreInt64(&abm.currentOutputBufferSize, int64(math.Round(newOutputSize)))
atomic.AddInt64(&abm.adaptationCount, 1) atomic.AddInt64(&abm.adaptationCount, 1)
abm.mutex.Lock() abm.mutex.Lock()
abm.lastAdaptation = time.Now() abm.lastAdaptation = time.Now()
abm.mutex.Unlock() abm.mutex.Unlock()
adjustmentMade = true adjustmentMade = true
abm.logger.Debug(). abm.logger.Debug().
Float64("cpu_percent", systemCPU). Float64("cpu_percent", systemCPU).
Float64("memory_percent", systemMemory). Float64("memory_percent", systemMemory).
@ -226,7 +226,7 @@ func (abm *AdaptiveBufferManager) adaptBufferSizes() {
Int("new_output_size", int(newOutputSize)). Int("new_output_size", int(newOutputSize)).
Msg("Adapted buffer sizes") Msg("Adapted buffer sizes")
} }
// Update metrics with current state // Update metrics with current state
currentInputSize := int(atomic.LoadInt64(&abm.currentInputBufferSize)) currentInputSize := int(atomic.LoadInt64(&abm.currentInputBufferSize))
currentOutputSize := int(atomic.LoadInt64(&abm.currentOutputBufferSize)) currentOutputSize := int(atomic.LoadInt64(&abm.currentOutputBufferSize))
@ -287,12 +287,12 @@ func (abm *AdaptiveBufferManager) applyAdaptation(currentSize, factor float64) f
// Decrease towards min // Decrease towards min
targetSize = currentSize + factor*(currentSize-float64(abm.config.MinBufferSize)) targetSize = currentSize + factor*(currentSize-float64(abm.config.MinBufferSize))
} }
// Apply smoothing // Apply smoothing
newSize := currentSize + abm.config.SmoothingFactor*(targetSize-currentSize) newSize := currentSize + abm.config.SmoothingFactor*(targetSize-currentSize)
// Clamp to valid range // Clamp to valid range
return math.Max(float64(abm.config.MinBufferSize), return math.Max(float64(abm.config.MinBufferSize),
math.Min(float64(abm.config.MaxBufferSize), newSize)) math.Min(float64(abm.config.MaxBufferSize), newSize))
} }
@ -301,15 +301,15 @@ func (abm *AdaptiveBufferManager) GetStats() map[string]interface{} {
abm.mutex.RLock() abm.mutex.RLock()
lastAdaptation := abm.lastAdaptation lastAdaptation := abm.lastAdaptation
abm.mutex.RUnlock() abm.mutex.RUnlock()
return map[string]interface{}{ return map[string]interface{}{
"input_buffer_size": abm.GetInputBufferSize(), "input_buffer_size": abm.GetInputBufferSize(),
"output_buffer_size": abm.GetOutputBufferSize(), "output_buffer_size": abm.GetOutputBufferSize(),
"average_latency_ms": float64(atomic.LoadInt64(&abm.averageLatency)) / 1e6, "average_latency_ms": float64(atomic.LoadInt64(&abm.averageLatency)) / 1e6,
"system_cpu_percent": float64(atomic.LoadInt64(&abm.systemCPUPercent)) / 100, "system_cpu_percent": float64(atomic.LoadInt64(&abm.systemCPUPercent)) / 100,
"system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / 100, "system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / 100,
"adaptation_count": atomic.LoadInt64(&abm.adaptationCount), "adaptation_count": atomic.LoadInt64(&abm.adaptationCount),
"last_adaptation": lastAdaptation, "last_adaptation": lastAdaptation,
} }
} }
@ -335,4 +335,4 @@ func StopAdaptiveBuffering() {
if globalAdaptiveBufferManager != nil { if globalAdaptiveBufferManager != nil {
globalAdaptiveBufferManager.Stop() globalAdaptiveBufferManager.Stop()
} }
} }

View File

@ -15,47 +15,44 @@ type AdaptiveOptimizer struct {
optimizationCount int64 // Number of optimizations performed (atomic) optimizationCount int64 // Number of optimizations performed (atomic)
lastOptimization int64 // Timestamp of last optimization (atomic) lastOptimization int64 // Timestamp of last optimization (atomic)
optimizationLevel int64 // Current optimization level (0-10) (atomic) optimizationLevel int64 // Current optimization level (0-10) (atomic)
latencyMonitor *LatencyMonitor latencyMonitor *LatencyMonitor
bufferManager *AdaptiveBufferManager bufferManager *AdaptiveBufferManager
logger zerolog.Logger logger zerolog.Logger
// Control channels // Control channels
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
wg sync.WaitGroup wg sync.WaitGroup
// Configuration // Configuration
config OptimizerConfig config OptimizerConfig
mutex sync.RWMutex
} }
// OptimizerConfig holds configuration for the adaptive optimizer // OptimizerConfig holds configuration for the adaptive optimizer
type OptimizerConfig struct { type OptimizerConfig struct {
MaxOptimizationLevel int // Maximum optimization level (0-10) MaxOptimizationLevel int // Maximum optimization level (0-10)
CooldownPeriod time.Duration // Minimum time between optimizations CooldownPeriod time.Duration // Minimum time between optimizations
Aggressiveness float64 // How aggressively to optimize (0.0-1.0) Aggressiveness float64 // How aggressively to optimize (0.0-1.0)
RollbackThreshold time.Duration // Latency threshold to rollback optimizations RollbackThreshold time.Duration // Latency threshold to rollback optimizations
StabilityPeriod time.Duration // Time to wait for stability after optimization StabilityPeriod time.Duration // Time to wait for stability after optimization
} }
// DefaultOptimizerConfig returns a sensible default configuration // DefaultOptimizerConfig returns a sensible default configuration
func DefaultOptimizerConfig() OptimizerConfig { func DefaultOptimizerConfig() OptimizerConfig {
return OptimizerConfig{ return OptimizerConfig{
MaxOptimizationLevel: 8, MaxOptimizationLevel: 8,
CooldownPeriod: 30 * time.Second, CooldownPeriod: 30 * time.Second,
Aggressiveness: 0.7, Aggressiveness: 0.7,
RollbackThreshold: 300 * time.Millisecond, RollbackThreshold: 300 * time.Millisecond,
StabilityPeriod: 10 * time.Second, StabilityPeriod: 10 * time.Second,
} }
} }
// NewAdaptiveOptimizer creates a new adaptive optimizer // NewAdaptiveOptimizer creates a new adaptive optimizer
func NewAdaptiveOptimizer(latencyMonitor *LatencyMonitor, bufferManager *AdaptiveBufferManager, config OptimizerConfig, logger zerolog.Logger) *AdaptiveOptimizer { func NewAdaptiveOptimizer(latencyMonitor *LatencyMonitor, bufferManager *AdaptiveBufferManager, config OptimizerConfig, logger zerolog.Logger) *AdaptiveOptimizer {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
optimizer := &AdaptiveOptimizer{ optimizer := &AdaptiveOptimizer{
latencyMonitor: latencyMonitor, latencyMonitor: latencyMonitor,
bufferManager: bufferManager, bufferManager: bufferManager,
@ -64,12 +61,10 @@ func NewAdaptiveOptimizer(latencyMonitor *LatencyMonitor, bufferManager *Adaptiv
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
} }
// Register as latency monitor callback // Register as latency monitor callback
latencyMonitor.AddOptimizationCallback(optimizer.handleLatencyOptimization) latencyMonitor.AddOptimizationCallback(optimizer.handleLatencyOptimization)
return optimizer return optimizer
} }
@ -89,26 +84,25 @@ func (ao *AdaptiveOptimizer) Stop() {
// initializeStrategies sets up the available optimization strategies // initializeStrategies sets up the available optimization strategies
// handleLatencyOptimization is called when latency optimization is needed // handleLatencyOptimization is called when latency optimization is needed
func (ao *AdaptiveOptimizer) handleLatencyOptimization(metrics LatencyMetrics) error { func (ao *AdaptiveOptimizer) handleLatencyOptimization(metrics LatencyMetrics) error {
currentLevel := atomic.LoadInt64(&ao.optimizationLevel) currentLevel := atomic.LoadInt64(&ao.optimizationLevel)
lastOpt := atomic.LoadInt64(&ao.lastOptimization) lastOpt := atomic.LoadInt64(&ao.lastOptimization)
// Check cooldown period // Check cooldown period
if time.Since(time.Unix(0, lastOpt)) < ao.config.CooldownPeriod { if time.Since(time.Unix(0, lastOpt)) < ao.config.CooldownPeriod {
return nil return nil
} }
// Determine if we need to increase or decrease optimization level // Determine if we need to increase or decrease optimization level
targetLevel := ao.calculateTargetOptimizationLevel(metrics) targetLevel := ao.calculateTargetOptimizationLevel(metrics)
if targetLevel > currentLevel { if targetLevel > currentLevel {
return ao.increaseOptimization(int(targetLevel)) return ao.increaseOptimization(int(targetLevel))
} else if targetLevel < currentLevel { } else if targetLevel < currentLevel {
return ao.decreaseOptimization(int(targetLevel)) return ao.decreaseOptimization(int(targetLevel))
} }
return nil return nil
} }
@ -116,7 +110,7 @@ func (ao *AdaptiveOptimizer) handleLatencyOptimization(metrics LatencyMetrics) e
func (ao *AdaptiveOptimizer) calculateTargetOptimizationLevel(metrics LatencyMetrics) int64 { func (ao *AdaptiveOptimizer) calculateTargetOptimizationLevel(metrics LatencyMetrics) int64 {
// Base calculation on current latency vs target // Base calculation on current latency vs target
latencyRatio := float64(metrics.Current) / float64(50*time.Millisecond) // 50ms target latencyRatio := float64(metrics.Current) / float64(50*time.Millisecond) // 50ms target
// Adjust based on trend // Adjust based on trend
switch metrics.Trend { switch metrics.Trend {
case LatencyTrendIncreasing: case LatencyTrendIncreasing:
@ -126,10 +120,10 @@ func (ao *AdaptiveOptimizer) calculateTargetOptimizationLevel(metrics LatencyMet
case LatencyTrendVolatile: case LatencyTrendVolatile:
latencyRatio *= 1.1 // Slightly more aggressive latencyRatio *= 1.1 // Slightly more aggressive
} }
// Apply aggressiveness factor // Apply aggressiveness factor
latencyRatio *= ao.config.Aggressiveness latencyRatio *= ao.config.Aggressiveness
// Convert to optimization level // Convert to optimization level
targetLevel := int64(latencyRatio * 2) // Scale to 0-10 range targetLevel := int64(latencyRatio * 2) // Scale to 0-10 range
if targetLevel > int64(ao.config.MaxOptimizationLevel) { if targetLevel > int64(ao.config.MaxOptimizationLevel) {
@ -138,7 +132,7 @@ func (ao *AdaptiveOptimizer) calculateTargetOptimizationLevel(metrics LatencyMet
if targetLevel < 0 { if targetLevel < 0 {
targetLevel = 0 targetLevel = 0
} }
return targetLevel return targetLevel
} }
@ -147,7 +141,7 @@ func (ao *AdaptiveOptimizer) increaseOptimization(targetLevel int) error {
atomic.StoreInt64(&ao.optimizationLevel, int64(targetLevel)) atomic.StoreInt64(&ao.optimizationLevel, int64(targetLevel))
atomic.StoreInt64(&ao.lastOptimization, time.Now().UnixNano()) atomic.StoreInt64(&ao.lastOptimization, time.Now().UnixNano())
atomic.AddInt64(&ao.optimizationCount, 1) atomic.AddInt64(&ao.optimizationCount, 1)
return nil return nil
} }
@ -155,17 +149,17 @@ func (ao *AdaptiveOptimizer) increaseOptimization(targetLevel int) error {
func (ao *AdaptiveOptimizer) decreaseOptimization(targetLevel int) error { func (ao *AdaptiveOptimizer) decreaseOptimization(targetLevel int) error {
atomic.StoreInt64(&ao.optimizationLevel, int64(targetLevel)) atomic.StoreInt64(&ao.optimizationLevel, int64(targetLevel))
atomic.StoreInt64(&ao.lastOptimization, time.Now().UnixNano()) atomic.StoreInt64(&ao.lastOptimization, time.Now().UnixNano())
return nil return nil
} }
// optimizationLoop runs the main optimization monitoring loop // optimizationLoop runs the main optimization monitoring loop
func (ao *AdaptiveOptimizer) optimizationLoop() { func (ao *AdaptiveOptimizer) optimizationLoop() {
defer ao.wg.Done() defer ao.wg.Done()
ticker := time.NewTicker(ao.config.StabilityPeriod) ticker := time.NewTicker(ao.config.StabilityPeriod)
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
case <-ao.ctx.Done(): case <-ao.ctx.Done():
@ -179,13 +173,15 @@ func (ao *AdaptiveOptimizer) optimizationLoop() {
// checkStability monitors system stability and rolls back if needed // checkStability monitors system stability and rolls back if needed
func (ao *AdaptiveOptimizer) checkStability() { func (ao *AdaptiveOptimizer) checkStability() {
metrics := ao.latencyMonitor.GetMetrics() metrics := ao.latencyMonitor.GetMetrics()
// Check if we need to rollback due to excessive latency // Check if we need to rollback due to excessive latency
if metrics.Current > ao.config.RollbackThreshold { if metrics.Current > ao.config.RollbackThreshold {
currentLevel := int(atomic.LoadInt64(&ao.optimizationLevel)) currentLevel := int(atomic.LoadInt64(&ao.optimizationLevel))
if currentLevel > 0 { if currentLevel > 0 {
ao.logger.Warn().Dur("current_latency", metrics.Current).Dur("threshold", ao.config.RollbackThreshold).Msg("Rolling back optimizations due to excessive latency") ao.logger.Warn().Dur("current_latency", metrics.Current).Dur("threshold", ao.config.RollbackThreshold).Msg("Rolling back optimizations due to excessive latency")
ao.decreaseOptimization(currentLevel - 1) if err := ao.decreaseOptimization(currentLevel - 1); err != nil {
ao.logger.Error().Err(err).Msg("Failed to decrease optimization level")
}
} }
} }
} }
@ -199,4 +195,4 @@ func (ao *AdaptiveOptimizer) GetOptimizationStats() map[string]interface{} {
} }
} }
// Strategy implementation methods (stubs for now) // Strategy implementation methods (stubs for now)

View File

@ -199,12 +199,12 @@ func (bap *BatchAudioProcessor) processBatchRead(batch []batchReadRequest) {
start := time.Now() start := time.Now()
if atomic.CompareAndSwapInt32(&bap.threadPinned, 0, 1) { if atomic.CompareAndSwapInt32(&bap.threadPinned, 0, 1) {
runtime.LockOSThread() runtime.LockOSThread()
// Set high priority for batch audio processing // Set high priority for batch audio processing
if err := SetAudioThreadPriority(); err != nil { if err := SetAudioThreadPriority(); err != nil {
bap.logger.Warn().Err(err).Msg("Failed to set batch audio processing priority") bap.logger.Warn().Err(err).Msg("Failed to set batch audio processing priority")
} }
defer func() { defer func() {
if err := ResetThreadPriority(); err != nil { if err := ResetThreadPriority(); err != nil {
bap.logger.Warn().Err(err).Msg("Failed to reset thread priority") bap.logger.Warn().Err(err).Msg("Failed to reset thread priority")

View File

@ -7,15 +7,15 @@ import (
type AudioBufferPool struct { type AudioBufferPool struct {
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
currentSize int64 // Current pool size (atomic) currentSize int64 // Current pool size (atomic)
hitCount int64 // Pool hit counter (atomic) hitCount int64 // Pool hit counter (atomic)
missCount int64 // Pool miss counter (atomic) missCount int64 // Pool miss counter (atomic)
// Other fields // Other fields
pool sync.Pool pool sync.Pool
bufferSize int bufferSize int
maxPoolSize int maxPoolSize int
mutex sync.RWMutex mutex sync.RWMutex
// Memory optimization fields // Memory optimization fields
preallocated []*[]byte // Pre-allocated buffers for immediate use preallocated []*[]byte // Pre-allocated buffers for immediate use
preallocSize int // Number of pre-allocated buffers preallocSize int // Number of pre-allocated buffers
@ -25,16 +25,16 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
// Pre-allocate 20% of max pool size for immediate availability // Pre-allocate 20% of max pool size for immediate availability
preallocSize := 20 preallocSize := 20
preallocated := make([]*[]byte, 0, preallocSize) preallocated := make([]*[]byte, 0, preallocSize)
// Pre-allocate buffers to reduce initial allocation overhead // Pre-allocate buffers to reduce initial allocation overhead
for i := 0; i < preallocSize; i++ { for i := 0; i < preallocSize; i++ {
buf := make([]byte, 0, bufferSize) buf := make([]byte, 0, bufferSize)
preallocated = append(preallocated, &buf) preallocated = append(preallocated, &buf)
} }
return &AudioBufferPool{ return &AudioBufferPool{
bufferSize: bufferSize, bufferSize: bufferSize,
maxPoolSize: 100, // Limit pool size to prevent excessive memory usage maxPoolSize: 100, // Limit pool size to prevent excessive memory usage
preallocated: preallocated, preallocated: preallocated,
preallocSize: preallocSize, preallocSize: preallocSize,
pool: sync.Pool{ pool: sync.Pool{
@ -56,10 +56,10 @@ func (p *AudioBufferPool) Get() []byte {
return (*buf)[:0] // Reset length but keep capacity return (*buf)[:0] // Reset length but keep capacity
} }
p.mutex.Unlock() p.mutex.Unlock()
// Try sync.Pool next // Try sync.Pool next
if buf := p.pool.Get(); buf != nil { if buf := p.pool.Get(); buf != nil {
bufSlice := buf.([]byte) bufPtr := buf.(*[]byte)
// Update pool size counter when retrieving from pool // Update pool size counter when retrieving from pool
p.mutex.Lock() p.mutex.Lock()
if p.currentSize > 0 { if p.currentSize > 0 {
@ -67,9 +67,9 @@ func (p *AudioBufferPool) Get() []byte {
} }
p.mutex.Unlock() p.mutex.Unlock()
atomic.AddInt64(&p.hitCount, 1) atomic.AddInt64(&p.hitCount, 1)
return bufSlice[:0] // Reset length but keep capacity return (*bufPtr)[:0] // Reset length but keep capacity
} }
// Last resort: allocate new buffer // Last resort: allocate new buffer
atomic.AddInt64(&p.missCount, 1) atomic.AddInt64(&p.missCount, 1)
return make([]byte, 0, p.bufferSize) return make([]byte, 0, p.bufferSize)
@ -82,7 +82,7 @@ func (p *AudioBufferPool) Put(buf []byte) {
// Reset buffer for reuse // Reset buffer for reuse
resetBuf := buf[:0] resetBuf := buf[:0]
// First try to return to pre-allocated pool for fastest reuse // First try to return to pre-allocated pool for fastest reuse
p.mutex.Lock() p.mutex.Lock()
if len(p.preallocated) < p.preallocSize { if len(p.preallocated) < p.preallocSize {
@ -102,7 +102,7 @@ func (p *AudioBufferPool) Put(buf []byte) {
} }
// Return to sync.Pool // Return to sync.Pool
p.pool.Put(resetBuf) p.pool.Put(&resetBuf)
// Update pool size counter // Update pool size counter
p.mutex.Lock() p.mutex.Lock()
@ -137,16 +137,16 @@ func (p *AudioBufferPool) GetPoolStats() AudioBufferPoolDetailedStats {
preallocatedCount := len(p.preallocated) preallocatedCount := len(p.preallocated)
currentSize := p.currentSize currentSize := p.currentSize
p.mutex.RUnlock() p.mutex.RUnlock()
hitCount := atomic.LoadInt64(&p.hitCount) hitCount := atomic.LoadInt64(&p.hitCount)
missCount := atomic.LoadInt64(&p.missCount) missCount := atomic.LoadInt64(&p.missCount)
totalRequests := hitCount + missCount totalRequests := hitCount + missCount
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * 100 hitRate = float64(hitCount) / float64(totalRequests) * 100
} }
return AudioBufferPoolDetailedStats{ return AudioBufferPoolDetailedStats{
BufferSize: p.bufferSize, BufferSize: p.bufferSize,
MaxPoolSize: p.maxPoolSize, MaxPoolSize: p.maxPoolSize,
@ -173,15 +173,15 @@ type AudioBufferPoolDetailedStats struct {
// GetAudioBufferPoolStats returns statistics about the audio buffer pools // GetAudioBufferPoolStats returns statistics about the audio buffer pools
type AudioBufferPoolStats struct { type AudioBufferPoolStats struct {
FramePoolSize int64 FramePoolSize int64
FramePoolMax int FramePoolMax int
ControlPoolSize int64 ControlPoolSize int64
ControlPoolMax int ControlPoolMax int
// Enhanced statistics // Enhanced statistics
FramePoolHitRate float64 FramePoolHitRate float64
ControlPoolHitRate float64 ControlPoolHitRate float64
FramePoolDetails AudioBufferPoolDetailedStats FramePoolDetails AudioBufferPoolDetailedStats
ControlPoolDetails AudioBufferPoolDetailedStats ControlPoolDetails AudioBufferPoolDetailedStats
} }
func GetAudioBufferPoolStats() AudioBufferPoolStats { func GetAudioBufferPoolStats() AudioBufferPoolStats {
@ -194,19 +194,19 @@ func GetAudioBufferPoolStats() AudioBufferPoolStats {
controlSize := audioControlPool.currentSize controlSize := audioControlPool.currentSize
controlMax := audioControlPool.maxPoolSize controlMax := audioControlPool.maxPoolSize
audioControlPool.mutex.RUnlock() audioControlPool.mutex.RUnlock()
// Get detailed statistics // Get detailed statistics
frameDetails := audioFramePool.GetPoolStats() frameDetails := audioFramePool.GetPoolStats()
controlDetails := audioControlPool.GetPoolStats() controlDetails := audioControlPool.GetPoolStats()
return AudioBufferPoolStats{ return AudioBufferPoolStats{
FramePoolSize: frameSize, FramePoolSize: frameSize,
FramePoolMax: frameMax, FramePoolMax: frameMax,
ControlPoolSize: controlSize, ControlPoolSize: controlSize,
ControlPoolMax: controlMax, ControlPoolMax: controlMax,
FramePoolHitRate: frameDetails.HitRate, FramePoolHitRate: frameDetails.HitRate,
ControlPoolHitRate: controlDetails.HitRate, ControlPoolHitRate: controlDetails.HitRate,
FramePoolDetails: frameDetails, FramePoolDetails: frameDetails,
ControlPoolDetails: controlDetails, ControlPoolDetails: controlDetails,
} }
} }

View File

@ -49,18 +49,18 @@ type InputIPCMessage struct {
// OptimizedIPCMessage represents an optimized message with pre-allocated buffers // OptimizedIPCMessage represents an optimized message with pre-allocated buffers
type OptimizedIPCMessage struct { type OptimizedIPCMessage struct {
header [headerSize]byte // Pre-allocated header buffer header [headerSize]byte // Pre-allocated header buffer
data []byte // Reusable data buffer data []byte // Reusable data buffer
msg InputIPCMessage // Embedded message msg InputIPCMessage // Embedded message
} }
// MessagePool manages a pool of reusable messages to reduce allocations // MessagePool manages a pool of reusable messages to reduce allocations
type MessagePool struct { type MessagePool struct {
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
hitCount int64 // Pool hit counter (atomic) hitCount int64 // Pool hit counter (atomic)
missCount int64 // Pool miss counter (atomic) missCount int64 // Pool miss counter (atomic)
// Other fields // Other fields
pool chan *OptimizedIPCMessage pool chan *OptimizedIPCMessage
// Memory optimization fields // Memory optimization fields
preallocated []*OptimizedIPCMessage // Pre-allocated messages for immediate use preallocated []*OptimizedIPCMessage // Pre-allocated messages for immediate use
preallocSize int // Number of pre-allocated messages preallocSize int // Number of pre-allocated messages
@ -73,32 +73,37 @@ var globalMessagePool = &MessagePool{
pool: make(chan *OptimizedIPCMessage, messagePoolSize), pool: make(chan *OptimizedIPCMessage, messagePoolSize),
} }
// Initialize the message pool with pre-allocated messages var messagePoolInitOnce sync.Once
func init() {
// Pre-allocate 30% of pool size for immediate availability // initializeMessagePool initializes the message pool with pre-allocated messages
preallocSize := messagePoolSize * 30 / 100 func initializeMessagePool() {
globalMessagePool.preallocSize = preallocSize messagePoolInitOnce.Do(func() {
globalMessagePool.maxPoolSize = messagePoolSize * 2 // Allow growth up to 2x // Pre-allocate 30% of pool size for immediate availability
globalMessagePool.preallocated = make([]*OptimizedIPCMessage, 0, preallocSize) preallocSize := messagePoolSize * 30 / 100
globalMessagePool.preallocSize = preallocSize
// Pre-allocate messages to reduce initial allocation overhead globalMessagePool.maxPoolSize = messagePoolSize * 2 // Allow growth up to 2x
for i := 0; i < preallocSize; i++ { globalMessagePool.preallocated = make([]*OptimizedIPCMessage, 0, preallocSize)
msg := &OptimizedIPCMessage{
data: make([]byte, 0, maxFrameSize), // Pre-allocate messages to reduce initial allocation overhead
for i := 0; i < preallocSize; i++ {
msg := &OptimizedIPCMessage{
data: make([]byte, 0, maxFrameSize),
}
globalMessagePool.preallocated = append(globalMessagePool.preallocated, msg)
} }
globalMessagePool.preallocated = append(globalMessagePool.preallocated, msg)
} // Fill the channel pool with remaining messages
for i := preallocSize; i < messagePoolSize; i++ {
// Fill the channel pool with remaining messages globalMessagePool.pool <- &OptimizedIPCMessage{
for i := preallocSize; i < messagePoolSize; i++ { data: make([]byte, 0, maxFrameSize),
globalMessagePool.pool <- &OptimizedIPCMessage{ }
data: make([]byte, 0, maxFrameSize),
} }
} })
} }
// Get retrieves a message from the pool // Get retrieves a message from the pool
func (mp *MessagePool) Get() *OptimizedIPCMessage { func (mp *MessagePool) Get() *OptimizedIPCMessage {
initializeMessagePool()
// First try pre-allocated messages for fastest access // First try pre-allocated messages for fastest access
mp.mutex.Lock() mp.mutex.Lock()
if len(mp.preallocated) > 0 { if len(mp.preallocated) > 0 {
@ -109,7 +114,7 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
return msg return msg
} }
mp.mutex.Unlock() mp.mutex.Unlock()
// Try channel pool next // Try channel pool next
select { select {
case msg := <-mp.pool: case msg := <-mp.pool:
@ -129,7 +134,7 @@ func (mp *MessagePool) Put(msg *OptimizedIPCMessage) {
// Reset the message for reuse // Reset the message for reuse
msg.data = msg.data[:0] msg.data = msg.data[:0]
msg.msg = InputIPCMessage{} msg.msg = InputIPCMessage{}
// First try to return to pre-allocated pool for fastest reuse // First try to return to pre-allocated pool for fastest reuse
mp.mutex.Lock() mp.mutex.Lock()
if len(mp.preallocated) < mp.preallocSize { if len(mp.preallocated) < mp.preallocSize {
@ -138,7 +143,7 @@ func (mp *MessagePool) Put(msg *OptimizedIPCMessage) {
return return
} }
mp.mutex.Unlock() mp.mutex.Unlock()
// Try channel pool next // Try channel pool next
select { select {
case mp.pool <- msg: case mp.pool <- msg:
@ -335,7 +340,7 @@ func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error
} else { } else {
optMsg.data = optMsg.data[:msg.Length] optMsg.data = optMsg.data[:msg.Length]
} }
_, err = io.ReadFull(conn, optMsg.data) _, err = io.ReadFull(conn, optMsg.data)
if err != nil { if err != nil {
return nil, err return nil, err
@ -350,7 +355,7 @@ func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error
Length: msg.Length, Length: msg.Length,
Timestamp: msg.Timestamp, Timestamp: msg.Timestamp,
} }
if msg.Length > 0 { if msg.Length > 0 {
// Copy data to ensure it's not affected by buffer reuse // Copy data to ensure it's not affected by buffer reuse
result.Data = make([]byte, msg.Length) result.Data = make([]byte, msg.Length)
@ -733,7 +738,7 @@ func (ais *AudioInputServer) startProcessorGoroutine() {
go func() { go func() {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
// Set high priority for audio processing // Set high priority for audio processing
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-processor").Logger() logger := logging.GetDefaultLogger().With().Str("component", "audio-input-processor").Logger()
if err := SetAudioThreadPriority(); err != nil { if err := SetAudioThreadPriority(); err != nil {
@ -744,7 +749,7 @@ func (ais *AudioInputServer) startProcessorGoroutine() {
logger.Warn().Err(err).Msg("Failed to reset thread priority") logger.Warn().Err(err).Msg("Failed to reset thread priority")
} }
}() }()
defer ais.wg.Done() defer ais.wg.Done()
for { for {
select { select {
@ -785,7 +790,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
go func() { go func() {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
// Set I/O priority for monitoring // Set I/O priority for monitoring
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-monitor").Logger() logger := logging.GetDefaultLogger().With().Str("component", "audio-input-monitor").Logger()
if err := SetAudioIOThreadPriority(); err != nil { if err := SetAudioIOThreadPriority(); err != nil {
@ -796,11 +801,11 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
logger.Warn().Err(err).Msg("Failed to reset thread priority") logger.Warn().Err(err).Msg("Failed to reset thread priority")
} }
}() }()
defer ais.wg.Done() defer ais.wg.Done()
ticker := time.NewTicker(100 * time.Millisecond) ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop() defer ticker.Stop()
// Buffer size update ticker (less frequent) // Buffer size update ticker (less frequent)
bufferUpdateTicker := time.NewTicker(500 * time.Millisecond) bufferUpdateTicker := time.NewTicker(500 * time.Millisecond)
defer bufferUpdateTicker.Stop() defer bufferUpdateTicker.Stop()
@ -835,7 +840,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
newAvg := (currentAvg + processingTime.Nanoseconds()) / 2 newAvg := (currentAvg + processingTime.Nanoseconds()) / 2
atomic.StoreInt64(&ais.processingTime, newAvg) atomic.StoreInt64(&ais.processingTime, newAvg)
} }
// Report latency to adaptive buffer manager // Report latency to adaptive buffer manager
ais.ReportLatency(latency) ais.ReportLatency(latency)
@ -847,7 +852,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
goto checkBufferUpdate goto checkBufferUpdate
} }
} }
checkBufferUpdate: checkBufferUpdate:
// Check if we need to update buffer size // Check if we need to update buffer size
select { select {
@ -888,19 +893,19 @@ func (mp *MessagePool) GetMessagePoolStats() MessagePoolStats {
mp.mutex.RLock() mp.mutex.RLock()
preallocatedCount := len(mp.preallocated) preallocatedCount := len(mp.preallocated)
mp.mutex.RUnlock() mp.mutex.RUnlock()
hitCount := atomic.LoadInt64(&mp.hitCount) hitCount := atomic.LoadInt64(&mp.hitCount)
missCount := atomic.LoadInt64(&mp.missCount) missCount := atomic.LoadInt64(&mp.missCount)
totalRequests := hitCount + missCount totalRequests := hitCount + missCount
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * 100 hitRate = float64(hitCount) / float64(totalRequests) * 100
} }
// Calculate channel pool size // Calculate channel pool size
channelPoolSize := len(mp.pool) channelPoolSize := len(mp.pool)
return MessagePoolStats{ return MessagePoolStats{
MaxPoolSize: mp.maxPoolSize, MaxPoolSize: mp.maxPoolSize,
ChannelPoolSize: channelPoolSize, ChannelPoolSize: channelPoolSize,

View File

@ -11,18 +11,18 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
const ( const (
outputMagicNumber uint32 = 0x4A4B4F55 // "JKOU" (JetKVM Output) outputMagicNumber uint32 = 0x4A4B4F55 // "JKOU" (JetKVM Output)
outputSocketName = "audio_output.sock" outputSocketName = "audio_output.sock"
outputMaxFrameSize = 4096 // Maximum Opus frame size outputMaxFrameSize = 4096 // Maximum Opus frame size
outputWriteTimeout = 10 * time.Millisecond // Non-blocking write timeout (increased for high load) outputWriteTimeout = 10 * time.Millisecond // Non-blocking write timeout (increased for high load)
outputMaxDroppedFrames = 50 // Maximum consecutive dropped frames outputMaxDroppedFrames = 50 // Maximum consecutive dropped frames
outputHeaderSize = 17 // Fixed header size: 4+1+4+8 bytes outputHeaderSize = 17 // Fixed header size: 4+1+4+8 bytes
outputMessagePoolSize = 128 // Pre-allocated message pool size outputMessagePoolSize = 128 // Pre-allocated message pool size
) )
// OutputMessageType represents the type of IPC message // OutputMessageType represents the type of IPC message
@ -61,7 +61,7 @@ func NewOutputMessagePool(size int) *OutputMessagePool {
pool := &OutputMessagePool{ pool := &OutputMessagePool{
pool: make(chan *OutputOptimizedMessage, size), pool: make(chan *OutputOptimizedMessage, size),
} }
// Pre-allocate messages // Pre-allocate messages
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
msg := &OutputOptimizedMessage{ msg := &OutputOptimizedMessage{
@ -69,7 +69,7 @@ func NewOutputMessagePool(size int) *OutputMessagePool {
} }
pool.pool <- msg pool.pool <- msg
} }
return pool return pool
} }
@ -101,10 +101,9 @@ var globalOutputMessagePool = NewOutputMessagePool(outputMessagePoolSize)
type AudioServer struct { type AudioServer struct {
// Atomic fields must be first for proper alignment on ARM // Atomic fields must be first for proper alignment on ARM
bufferSize int64 // Current buffer size (atomic) bufferSize int64 // Current buffer size (atomic)
processingTime int64 // Average processing time in nanoseconds (atomic) droppedFrames int64 // Dropped frames counter (atomic)
droppedFrames int64 // Dropped frames counter (atomic) totalFrames int64 // Total frames counter (atomic)
totalFrames int64 // Total frames counter (atomic)
listener net.Listener listener net.Listener
conn net.Conn conn net.Conn
@ -115,9 +114,9 @@ type AudioServer struct {
messageChan chan *OutputIPCMessage // Buffered channel for incoming messages messageChan chan *OutputIPCMessage // Buffered channel for incoming messages
stopChan chan struct{} // Stop signal stopChan chan struct{} // Stop signal
wg sync.WaitGroup // Wait group for goroutine coordination wg sync.WaitGroup // Wait group for goroutine coordination
// Latency monitoring // Latency monitoring
latencyMonitor *LatencyMonitor latencyMonitor *LatencyMonitor
adaptiveOptimizer *AdaptiveOptimizer adaptiveOptimizer *AdaptiveOptimizer
} }
@ -138,11 +137,11 @@ func NewAudioServer() (*AudioServer, error) {
latencyConfig := DefaultLatencyConfig() latencyConfig := DefaultLatencyConfig()
logger := zerolog.New(os.Stderr).With().Timestamp().Str("component", "audio-server").Logger() logger := zerolog.New(os.Stderr).With().Timestamp().Str("component", "audio-server").Logger()
latencyMonitor := NewLatencyMonitor(latencyConfig, logger) latencyMonitor := NewLatencyMonitor(latencyConfig, logger)
// Initialize adaptive buffer manager with default config // Initialize adaptive buffer manager with default config
bufferConfig := DefaultAdaptiveBufferConfig() bufferConfig := DefaultAdaptiveBufferConfig()
bufferManager := NewAdaptiveBufferManager(bufferConfig) bufferManager := NewAdaptiveBufferManager(bufferConfig)
// Initialize adaptive optimizer // Initialize adaptive optimizer
optimizerConfig := DefaultOptimizerConfig() optimizerConfig := DefaultOptimizerConfig()
adaptiveOptimizer := NewAdaptiveOptimizer(latencyMonitor, bufferManager, optimizerConfig, logger) adaptiveOptimizer := NewAdaptiveOptimizer(latencyMonitor, bufferManager, optimizerConfig, logger)
@ -216,7 +215,10 @@ func (s *AudioServer) startProcessorGoroutine() {
case msg := <-s.messageChan: case msg := <-s.messageChan:
// Process message (currently just frame sending) // Process message (currently just frame sending)
if msg.Type == OutputMessageTypeOpusFrame { if msg.Type == OutputMessageTypeOpusFrame {
s.sendFrameToClient(msg.Data) if err := s.sendFrameToClient(msg.Data); err != nil {
// Log error but continue processing
atomic.AddInt64(&s.droppedFrames, 1)
}
} }
case <-s.stopChan: case <-s.stopChan:
return return
@ -283,13 +285,13 @@ func (s *AudioServer) SendFrame(frame []byte) error {
select { select {
case s.messageChan <- msg: case s.messageChan <- msg:
atomic.AddInt64(&s.totalFrames, 1) atomic.AddInt64(&s.totalFrames, 1)
// Record latency for monitoring // Record latency for monitoring
if s.latencyMonitor != nil { if s.latencyMonitor != nil {
processingTime := time.Since(start) processingTime := time.Since(start)
s.latencyMonitor.RecordLatency(processingTime, "ipc_send") s.latencyMonitor.RecordLatency(processingTime, "ipc_send")
} }
return nil return nil
default: default:
// Channel full, drop frame to prevent blocking // Channel full, drop frame to prevent blocking

View File

@ -19,19 +19,19 @@ type LatencyMonitor struct {
latencySamples int64 // Number of latency samples collected (atomic) latencySamples int64 // Number of latency samples collected (atomic)
jitterAccumulator int64 // Accumulated jitter for variance calculation (atomic) jitterAccumulator int64 // Accumulated jitter for variance calculation (atomic)
lastOptimization int64 // Timestamp of last optimization in nanoseconds (atomic) lastOptimization int64 // Timestamp of last optimization in nanoseconds (atomic)
config LatencyConfig config LatencyConfig
logger zerolog.Logger logger zerolog.Logger
// Control channels // Control channels
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
wg sync.WaitGroup wg sync.WaitGroup
// Optimization callbacks // Optimization callbacks
optimizationCallbacks []OptimizationCallback optimizationCallbacks []OptimizationCallback
mutex sync.RWMutex mutex sync.RWMutex
// Performance tracking // Performance tracking
latencyHistory []LatencyMeasurement latencyHistory []LatencyMeasurement
historyMutex sync.RWMutex historyMutex sync.RWMutex
@ -39,12 +39,12 @@ type LatencyMonitor struct {
// LatencyConfig holds configuration for latency monitoring // LatencyConfig holds configuration for latency monitoring
type LatencyConfig struct { type LatencyConfig struct {
TargetLatency time.Duration // Target latency to maintain TargetLatency time.Duration // Target latency to maintain
MaxLatency time.Duration // Maximum acceptable latency MaxLatency time.Duration // Maximum acceptable latency
OptimizationInterval time.Duration // How often to run optimization OptimizationInterval time.Duration // How often to run optimization
HistorySize int // Number of latency measurements to keep HistorySize int // Number of latency measurements to keep
JitterThreshold time.Duration // Jitter threshold for optimization JitterThreshold time.Duration // Jitter threshold for optimization
AdaptiveThreshold float64 // Threshold for adaptive adjustments (0.0-1.0) AdaptiveThreshold float64 // Threshold for adaptive adjustments (0.0-1.0)
} }
// LatencyMeasurement represents a single latency measurement // LatencyMeasurement represents a single latency measurement
@ -83,18 +83,18 @@ const (
func DefaultLatencyConfig() LatencyConfig { func DefaultLatencyConfig() LatencyConfig {
return LatencyConfig{ return LatencyConfig{
TargetLatency: 50 * time.Millisecond, TargetLatency: 50 * time.Millisecond,
MaxLatency: 200 * time.Millisecond, MaxLatency: 200 * time.Millisecond,
OptimizationInterval: 5 * time.Second, OptimizationInterval: 5 * time.Second,
HistorySize: 100, HistorySize: 100,
JitterThreshold: 20 * time.Millisecond, JitterThreshold: 20 * time.Millisecond,
AdaptiveThreshold: 0.8, // Trigger optimization when 80% above target AdaptiveThreshold: 0.8, // Trigger optimization when 80% above target
} }
} }
// NewLatencyMonitor creates a new latency monitoring system // NewLatencyMonitor creates a new latency monitoring system
func NewLatencyMonitor(config LatencyConfig, logger zerolog.Logger) *LatencyMonitor { func NewLatencyMonitor(config LatencyConfig, logger zerolog.Logger) *LatencyMonitor {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &LatencyMonitor{ return &LatencyMonitor{
config: config, config: config,
logger: logger.With().Str("component", "latency-monitor").Logger(), logger: logger.With().Str("component", "latency-monitor").Logger(),
@ -123,11 +123,11 @@ func (lm *LatencyMonitor) Stop() {
func (lm *LatencyMonitor) RecordLatency(latency time.Duration, source string) { func (lm *LatencyMonitor) RecordLatency(latency time.Duration, source string) {
now := time.Now() now := time.Now()
latencyNanos := latency.Nanoseconds() latencyNanos := latency.Nanoseconds()
// Update atomic counters // Update atomic counters
atomic.StoreInt64(&lm.currentLatency, latencyNanos) atomic.StoreInt64(&lm.currentLatency, latencyNanos)
atomic.AddInt64(&lm.latencySamples, 1) atomic.AddInt64(&lm.latencySamples, 1)
// Update min/max // Update min/max
for { for {
oldMin := atomic.LoadInt64(&lm.minLatency) oldMin := atomic.LoadInt64(&lm.minLatency)
@ -135,26 +135,26 @@ func (lm *LatencyMonitor) RecordLatency(latency time.Duration, source string) {
break break
} }
} }
for { for {
oldMax := atomic.LoadInt64(&lm.maxLatency) oldMax := atomic.LoadInt64(&lm.maxLatency)
if latencyNanos <= oldMax || atomic.CompareAndSwapInt64(&lm.maxLatency, oldMax, latencyNanos) { if latencyNanos <= oldMax || atomic.CompareAndSwapInt64(&lm.maxLatency, oldMax, latencyNanos) {
break break
} }
} }
// Update rolling average using exponential moving average // Update rolling average using exponential moving average
oldAvg := atomic.LoadInt64(&lm.averageLatency) oldAvg := atomic.LoadInt64(&lm.averageLatency)
newAvg := oldAvg + (latencyNanos-oldAvg)/10 // Alpha = 0.1 newAvg := oldAvg + (latencyNanos-oldAvg)/10 // Alpha = 0.1
atomic.StoreInt64(&lm.averageLatency, newAvg) atomic.StoreInt64(&lm.averageLatency, newAvg)
// Calculate jitter (difference from average) // Calculate jitter (difference from average)
jitter := latencyNanos - newAvg jitter := latencyNanos - newAvg
if jitter < 0 { if jitter < 0 {
jitter = -jitter jitter = -jitter
} }
atomic.AddInt64(&lm.jitterAccumulator, jitter) atomic.AddInt64(&lm.jitterAccumulator, jitter)
// Store in history // Store in history
lm.historyMutex.Lock() lm.historyMutex.Lock()
measurement := LatencyMeasurement{ measurement := LatencyMeasurement{
@ -163,7 +163,7 @@ func (lm *LatencyMonitor) RecordLatency(latency time.Duration, source string) {
Jitter: time.Duration(jitter), Jitter: time.Duration(jitter),
Source: source, Source: source,
} }
if len(lm.latencyHistory) >= lm.config.HistorySize { if len(lm.latencyHistory) >= lm.config.HistorySize {
// Remove oldest measurement // Remove oldest measurement
copy(lm.latencyHistory, lm.latencyHistory[1:]) copy(lm.latencyHistory, lm.latencyHistory[1:])
@ -182,12 +182,12 @@ func (lm *LatencyMonitor) GetMetrics() LatencyMetrics {
max := atomic.LoadInt64(&lm.maxLatency) max := atomic.LoadInt64(&lm.maxLatency)
samples := atomic.LoadInt64(&lm.latencySamples) samples := atomic.LoadInt64(&lm.latencySamples)
jitterSum := atomic.LoadInt64(&lm.jitterAccumulator) jitterSum := atomic.LoadInt64(&lm.jitterAccumulator)
var jitter time.Duration var jitter time.Duration
if samples > 0 { if samples > 0 {
jitter = time.Duration(jitterSum / samples) jitter = time.Duration(jitterSum / samples)
} }
return LatencyMetrics{ return LatencyMetrics{
Current: time.Duration(current), Current: time.Duration(current),
Average: time.Duration(average), Average: time.Duration(average),
@ -209,10 +209,10 @@ func (lm *LatencyMonitor) AddOptimizationCallback(callback OptimizationCallback)
// monitoringLoop runs the main monitoring and optimization loop // monitoringLoop runs the main monitoring and optimization loop
func (lm *LatencyMonitor) monitoringLoop() { func (lm *LatencyMonitor) monitoringLoop() {
defer lm.wg.Done() defer lm.wg.Done()
ticker := time.NewTicker(lm.config.OptimizationInterval) ticker := time.NewTicker(lm.config.OptimizationInterval)
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
case <-lm.ctx.Done(): case <-lm.ctx.Done():
@ -226,44 +226,44 @@ func (lm *LatencyMonitor) monitoringLoop() {
// runOptimization checks if optimization is needed and triggers callbacks // runOptimization checks if optimization is needed and triggers callbacks
func (lm *LatencyMonitor) runOptimization() { func (lm *LatencyMonitor) runOptimization() {
metrics := lm.GetMetrics() metrics := lm.GetMetrics()
// Check if optimization is needed // Check if optimization is needed
needsOptimization := false needsOptimization := false
// Check if current latency exceeds threshold // Check if current latency exceeds threshold
if metrics.Current > lm.config.MaxLatency { if metrics.Current > lm.config.MaxLatency {
needsOptimization = true needsOptimization = true
lm.logger.Warn().Dur("current_latency", metrics.Current).Dur("max_latency", lm.config.MaxLatency).Msg("Latency exceeds maximum threshold") lm.logger.Warn().Dur("current_latency", metrics.Current).Dur("max_latency", lm.config.MaxLatency).Msg("Latency exceeds maximum threshold")
} }
// Check if average latency is above adaptive threshold // Check if average latency is above adaptive threshold
adaptiveThreshold := time.Duration(float64(lm.config.TargetLatency.Nanoseconds()) * (1.0 + lm.config.AdaptiveThreshold)) adaptiveThreshold := time.Duration(float64(lm.config.TargetLatency.Nanoseconds()) * (1.0 + lm.config.AdaptiveThreshold))
if metrics.Average > adaptiveThreshold { if metrics.Average > adaptiveThreshold {
needsOptimization = true needsOptimization = true
lm.logger.Info().Dur("average_latency", metrics.Average).Dur("threshold", adaptiveThreshold).Msg("Average latency above adaptive threshold") lm.logger.Info().Dur("average_latency", metrics.Average).Dur("threshold", adaptiveThreshold).Msg("Average latency above adaptive threshold")
} }
// Check if jitter is too high // Check if jitter is too high
if metrics.Jitter > lm.config.JitterThreshold { if metrics.Jitter > lm.config.JitterThreshold {
needsOptimization = true needsOptimization = true
lm.logger.Info().Dur("jitter", metrics.Jitter).Dur("threshold", lm.config.JitterThreshold).Msg("Jitter above threshold") lm.logger.Info().Dur("jitter", metrics.Jitter).Dur("threshold", lm.config.JitterThreshold).Msg("Jitter above threshold")
} }
if needsOptimization { if needsOptimization {
atomic.StoreInt64(&lm.lastOptimization, time.Now().UnixNano()) atomic.StoreInt64(&lm.lastOptimization, time.Now().UnixNano())
// Run optimization callbacks // Run optimization callbacks
lm.mutex.RLock() lm.mutex.RLock()
callbacks := make([]OptimizationCallback, len(lm.optimizationCallbacks)) callbacks := make([]OptimizationCallback, len(lm.optimizationCallbacks))
copy(callbacks, lm.optimizationCallbacks) copy(callbacks, lm.optimizationCallbacks)
lm.mutex.RUnlock() lm.mutex.RUnlock()
for _, callback := range callbacks { for _, callback := range callbacks {
if err := callback(metrics); err != nil { if err := callback(metrics); err != nil {
lm.logger.Error().Err(err).Msg("Optimization callback failed") lm.logger.Error().Err(err).Msg("Optimization callback failed")
} }
} }
lm.logger.Info().Interface("metrics", metrics).Msg("Latency optimization triggered") lm.logger.Info().Interface("metrics", metrics).Msg("Latency optimization triggered")
} }
} }
@ -272,14 +272,14 @@ func (lm *LatencyMonitor) runOptimization() {
func (lm *LatencyMonitor) calculateTrend() LatencyTrend { func (lm *LatencyMonitor) calculateTrend() LatencyTrend {
lm.historyMutex.RLock() lm.historyMutex.RLock()
defer lm.historyMutex.RUnlock() defer lm.historyMutex.RUnlock()
if len(lm.latencyHistory) < 10 { if len(lm.latencyHistory) < 10 {
return LatencyTrendStable return LatencyTrendStable
} }
// Analyze last 10 measurements // Analyze last 10 measurements
recentMeasurements := lm.latencyHistory[len(lm.latencyHistory)-10:] recentMeasurements := lm.latencyHistory[len(lm.latencyHistory)-10:]
var increasing, decreasing int var increasing, decreasing int
for i := 1; i < len(recentMeasurements); i++ { for i := 1; i < len(recentMeasurements); i++ {
if recentMeasurements[i].Latency > recentMeasurements[i-1].Latency { if recentMeasurements[i].Latency > recentMeasurements[i-1].Latency {
@ -288,7 +288,7 @@ func (lm *LatencyMonitor) calculateTrend() LatencyTrend {
decreasing++ decreasing++
} }
} }
// Determine trend based on direction changes // Determine trend based on direction changes
if increasing > 6 { if increasing > 6 {
return LatencyTrendIncreasing return LatencyTrendIncreasing
@ -297,7 +297,7 @@ func (lm *LatencyMonitor) calculateTrend() LatencyTrend {
} else if increasing+decreasing > 7 { } else if increasing+decreasing > 7 {
return LatencyTrendVolatile return LatencyTrendVolatile
} }
return LatencyTrendStable return LatencyTrendStable
} }
@ -305,8 +305,8 @@ func (lm *LatencyMonitor) calculateTrend() LatencyTrend {
func (lm *LatencyMonitor) GetLatencyHistory() []LatencyMeasurement { func (lm *LatencyMonitor) GetLatencyHistory() []LatencyMeasurement {
lm.historyMutex.RLock() lm.historyMutex.RLock()
defer lm.historyMutex.RUnlock() defer lm.historyMutex.RUnlock()
history := make([]LatencyMeasurement, len(lm.latencyHistory)) history := make([]LatencyMeasurement, len(lm.latencyHistory))
copy(history, lm.latencyHistory) copy(history, lm.latencyHistory)
return history return history
} }

View File

@ -13,17 +13,17 @@ import (
// MemoryMetrics provides comprehensive memory allocation statistics // MemoryMetrics provides comprehensive memory allocation statistics
type MemoryMetrics struct { type MemoryMetrics struct {
// Runtime memory statistics // Runtime memory statistics
RuntimeStats RuntimeMemoryStats `json:"runtime_stats"` RuntimeStats RuntimeMemoryStats `json:"runtime_stats"`
// Audio buffer pool statistics // Audio buffer pool statistics
BufferPools AudioBufferPoolStats `json:"buffer_pools"` BufferPools AudioBufferPoolStats `json:"buffer_pools"`
// Zero-copy frame pool statistics // Zero-copy frame pool statistics
ZeroCopyPool ZeroCopyFramePoolStats `json:"zero_copy_pool"` ZeroCopyPool ZeroCopyFramePoolStats `json:"zero_copy_pool"`
// Message pool statistics // Message pool statistics
MessagePool MessagePoolStats `json:"message_pool"` MessagePool MessagePoolStats `json:"message_pool"`
// Batch processor statistics // Batch processor statistics
BatchProcessor BatchProcessorMemoryStats `json:"batch_processor,omitempty"` BatchProcessor BatchProcessorMemoryStats `json:"batch_processor,omitempty"`
// Collection timestamp // Collection timestamp
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
} }
// RuntimeMemoryStats provides Go runtime memory statistics // RuntimeMemoryStats provides Go runtime memory statistics
@ -59,10 +59,10 @@ type RuntimeMemoryStats struct {
// BatchProcessorMemoryStats provides batch processor memory statistics // BatchProcessorMemoryStats provides batch processor memory statistics
type BatchProcessorMemoryStats struct { type BatchProcessorMemoryStats struct {
Initialized bool `json:"initialized"` Initialized bool `json:"initialized"`
Running bool `json:"running"` Running bool `json:"running"`
Stats BatchAudioStats `json:"stats"` Stats BatchAudioStats `json:"stats"`
BufferPool AudioBufferPoolDetailedStats `json:"buffer_pool,omitempty"` BufferPool AudioBufferPoolDetailedStats `json:"buffer_pool,omitempty"`
} }
// GetBatchAudioProcessor is defined in batch_audio.go // GetBatchAudioProcessor is defined in batch_audio.go
@ -83,7 +83,7 @@ func CollectMemoryMetrics() MemoryMetrics {
// Collect runtime memory statistics // Collect runtime memory statistics
var m runtime.MemStats var m runtime.MemStats
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)
runtimeStats := RuntimeMemoryStats{ runtimeStats := RuntimeMemoryStats{
Alloc: m.Alloc, Alloc: m.Alloc,
TotalAlloc: m.TotalAlloc, TotalAlloc: m.TotalAlloc,
@ -113,16 +113,16 @@ func CollectMemoryMetrics() MemoryMetrics {
NumForcedGC: m.NumForcedGC, NumForcedGC: m.NumForcedGC,
GCCPUFraction: m.GCCPUFraction, GCCPUFraction: m.GCCPUFraction,
} }
// Collect audio buffer pool statistics // Collect audio buffer pool statistics
bufferPoolStats := GetAudioBufferPoolStats() bufferPoolStats := GetAudioBufferPoolStats()
// Collect zero-copy frame pool statistics // Collect zero-copy frame pool statistics
zeroCopyStats := GetGlobalZeroCopyPoolStats() zeroCopyStats := GetGlobalZeroCopyPoolStats()
// Collect message pool statistics // Collect message pool statistics
messagePoolStats := GetGlobalMessagePoolStats() messagePoolStats := GetGlobalMessagePoolStats()
// Collect batch processor statistics if available // Collect batch processor statistics if available
var batchStats BatchProcessorMemoryStats var batchStats BatchProcessorMemoryStats
if processor := GetBatchAudioProcessor(); processor != nil { if processor := GetBatchAudioProcessor(); processor != nil {
@ -131,7 +131,7 @@ func CollectMemoryMetrics() MemoryMetrics {
batchStats.Stats = processor.GetStats() batchStats.Stats = processor.GetStats()
// Note: BatchAudioProcessor uses sync.Pool, detailed stats not available // Note: BatchAudioProcessor uses sync.Pool, detailed stats not available
} }
return MemoryMetrics{ return MemoryMetrics{
RuntimeStats: runtimeStats, RuntimeStats: runtimeStats,
BufferPools: bufferPoolStats, BufferPools: bufferPoolStats,
@ -145,23 +145,23 @@ func CollectMemoryMetrics() MemoryMetrics {
// HandleMemoryMetrics provides an HTTP handler for memory metrics // HandleMemoryMetrics provides an HTTP handler for memory metrics
func HandleMemoryMetrics(w http.ResponseWriter, r *http.Request) { func HandleMemoryMetrics(w http.ResponseWriter, r *http.Request) {
logger := getMemoryMetricsLogger() logger := getMemoryMetricsLogger()
if r.Method != http.MethodGet { if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
metrics := CollectMemoryMetrics() metrics := CollectMemoryMetrics()
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Cache-Control", "no-cache")
if err := json.NewEncoder(w).Encode(metrics); err != nil { if err := json.NewEncoder(w).Encode(metrics); err != nil {
logger.Error().Err(err).Msg("failed to encode memory metrics") logger.Error().Err(err).Msg("failed to encode memory metrics")
http.Error(w, "Internal server error", http.StatusInternalServerError) http.Error(w, "Internal server error", http.StatusInternalServerError)
return return
} }
logger.Debug().Msg("memory metrics served") logger.Debug().Msg("memory metrics served")
} }
@ -169,7 +169,7 @@ func HandleMemoryMetrics(w http.ResponseWriter, r *http.Request) {
func LogMemoryMetrics() { func LogMemoryMetrics() {
logger := getMemoryMetricsLogger() logger := getMemoryMetricsLogger()
metrics := CollectMemoryMetrics() metrics := CollectMemoryMetrics()
logger.Info(). logger.Info().
Uint64("heap_alloc_mb", metrics.RuntimeStats.HeapAlloc/1024/1024). Uint64("heap_alloc_mb", metrics.RuntimeStats.HeapAlloc/1024/1024).
Uint64("heap_sys_mb", metrics.RuntimeStats.HeapSys/1024/1024). Uint64("heap_sys_mb", metrics.RuntimeStats.HeapSys/1024/1024).
@ -186,13 +186,13 @@ func LogMemoryMetrics() {
func StartMemoryMetricsLogging(interval time.Duration) { func StartMemoryMetricsLogging(interval time.Duration) {
logger := getMemoryMetricsLogger() logger := getMemoryMetricsLogger()
logger.Info().Dur("interval", interval).Msg("starting memory metrics logging") logger.Info().Dur("interval", interval).Msg("starting memory metrics logging")
go func() { go func() {
ticker := time.NewTicker(interval) ticker := time.NewTicker(interval)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
LogMemoryMetrics() LogMemoryMetrics()
} }
}() }()
} }

View File

@ -12,8 +12,8 @@ type MicrophoneContentionManager struct {
lastOpNano int64 lastOpNano int64
cooldownNanos int64 cooldownNanos int64
operationID int64 operationID int64
lockPtr unsafe.Pointer lockPtr unsafe.Pointer
} }
func NewMicrophoneContentionManager(cooldown time.Duration) *MicrophoneContentionManager { func NewMicrophoneContentionManager(cooldown time.Duration) *MicrophoneContentionManager {

View File

@ -61,9 +61,9 @@ func NewOutputStreamer() (*OutputStreamer, error) {
bufferPool: NewAudioBufferPool(MaxAudioFrameSize), // Use existing buffer pool bufferPool: NewAudioBufferPool(MaxAudioFrameSize), // Use existing buffer pool
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
batchSize: initialBatchSize, // Use adaptive batch size batchSize: initialBatchSize, // Use adaptive batch size
processingChan: make(chan []byte, 500), // Large buffer for smooth processing processingChan: make(chan []byte, 500), // Large buffer for smooth processing
statsInterval: 5 * time.Second, // Statistics every 5 seconds statsInterval: 5 * time.Second, // Statistics every 5 seconds
lastStatsTime: time.Now().UnixNano(), lastStatsTime: time.Now().UnixNano(),
}, nil }, nil
} }
@ -85,9 +85,9 @@ func (s *OutputStreamer) Start() error {
// Start multiple goroutines for optimal performance // Start multiple goroutines for optimal performance
s.wg.Add(3) s.wg.Add(3)
go s.streamLoop() // Main streaming loop go s.streamLoop() // Main streaming loop
go s.processingLoop() // Frame processing loop go s.processingLoop() // Frame processing loop
go s.statisticsLoop() // Performance monitoring loop go s.statisticsLoop() // Performance monitoring loop
return nil return nil
} }
@ -125,7 +125,7 @@ func (s *OutputStreamer) streamLoop() {
frameInterval := time.Duration(20) * time.Millisecond // 50 FPS base rate frameInterval := time.Duration(20) * time.Millisecond // 50 FPS base rate
ticker := time.NewTicker(frameInterval) ticker := time.NewTicker(frameInterval)
defer ticker.Stop() defer ticker.Stop()
// Batch size update ticker // Batch size update ticker
batchUpdateTicker := time.NewTicker(500 * time.Millisecond) batchUpdateTicker := time.NewTicker(500 * time.Millisecond)
defer batchUpdateTicker.Stop() defer batchUpdateTicker.Stop()
@ -181,7 +181,7 @@ func (s *OutputStreamer) processingLoop() {
// Pin goroutine to OS thread for consistent performance // Pin goroutine to OS thread for consistent performance
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
// Set high priority for audio output processing // Set high priority for audio output processing
if err := SetAudioThreadPriority(); err != nil { if err := SetAudioThreadPriority(); err != nil {
getOutputStreamingLogger().Warn().Err(err).Msg("Failed to set audio output processing priority") getOutputStreamingLogger().Warn().Err(err).Msg("Failed to set audio output processing priority")
@ -192,7 +192,7 @@ func (s *OutputStreamer) processingLoop() {
} }
}() }()
for _ = range s.processingChan { for range s.processingChan {
// Process frame (currently just receiving, but can be extended) // Process frame (currently just receiving, but can be extended)
if _, err := s.client.ReceiveFrame(); err != nil { if _, err := s.client.ReceiveFrame(); err != nil {
if s.client.IsConnected() { if s.client.IsConnected() {
@ -260,13 +260,13 @@ func (s *OutputStreamer) GetDetailedStats() map[string]interface{} {
processingTime := atomic.LoadInt64(&s.processingTime) processingTime := atomic.LoadInt64(&s.processingTime)
stats := map[string]interface{}{ stats := map[string]interface{}{
"processed_frames": processed, "processed_frames": processed,
"dropped_frames": dropped, "dropped_frames": dropped,
"avg_processing_time_ns": processingTime, "avg_processing_time_ns": processingTime,
"batch_size": s.batchSize, "batch_size": s.batchSize,
"channel_buffer_size": cap(s.processingChan), "channel_buffer_size": cap(s.processingChan),
"channel_current_size": len(s.processingChan), "channel_current_size": len(s.processingChan),
"connected": s.client.IsConnected(), "connected": s.client.IsConnected(),
} }
if processed+dropped > 0 { if processed+dropped > 0 {

View File

@ -22,7 +22,7 @@ const (
AudioHighPriority = 80 // High priority for critical audio processing AudioHighPriority = 80 // High priority for critical audio processing
AudioMediumPriority = 60 // Medium priority for regular audio processing AudioMediumPriority = 60 // Medium priority for regular audio processing
AudioLowPriority = 40 // Low priority for background audio tasks AudioLowPriority = 40 // Low priority for background audio tasks
// SCHED_NORMAL is the default (priority 0) // SCHED_NORMAL is the default (priority 0)
NormalPriority = 0 NormalPriority = 0
) )
@ -36,14 +36,14 @@ const (
// PriorityScheduler manages thread priorities for audio processing // PriorityScheduler manages thread priorities for audio processing
type PriorityScheduler struct { type PriorityScheduler struct {
logger zerolog.Logger logger zerolog.Logger
enabled bool enabled bool
} }
// NewPriorityScheduler creates a new priority scheduler // NewPriorityScheduler creates a new priority scheduler
func NewPriorityScheduler() *PriorityScheduler { func NewPriorityScheduler() *PriorityScheduler {
return &PriorityScheduler{ return &PriorityScheduler{
logger: logging.GetDefaultLogger().With().Str("component", "priority-scheduler").Logger(), logger: logging.GetDefaultLogger().With().Str("component", "priority-scheduler").Logger(),
enabled: true, enabled: true,
} }
} }
@ -162,4 +162,4 @@ func SetAudioIOThreadPriority() error {
// ResetThreadPriority is a convenience function to reset thread priority // ResetThreadPriority is a convenience function to reset thread priority
func ResetThreadPriority() error { func ResetThreadPriority() error {
return GetPriorityScheduler().ResetPriority() return GetPriorityScheduler().ResetPriority()
} }

View File

@ -17,7 +17,7 @@ type AudioRelay struct {
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
framesRelayed int64 framesRelayed int64
framesDropped int64 framesDropped int64
client *AudioClient client *AudioClient
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc

View File

@ -20,14 +20,14 @@ type ZeroCopyAudioFrame struct {
// ZeroCopyFramePool manages reusable zero-copy audio frames // ZeroCopyFramePool manages reusable zero-copy audio frames
type ZeroCopyFramePool struct { type ZeroCopyFramePool struct {
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
counter int64 // Frame counter (atomic) counter int64 // Frame counter (atomic)
hitCount int64 // Pool hit counter (atomic) hitCount int64 // Pool hit counter (atomic)
missCount int64 // Pool miss counter (atomic) missCount int64 // Pool miss counter (atomic)
// Other fields // Other fields
pool sync.Pool pool sync.Pool
maxSize int maxSize int
mutex sync.RWMutex mutex sync.RWMutex
// Memory optimization fields // Memory optimization fields
preallocated []*ZeroCopyAudioFrame // Pre-allocated frames for immediate use preallocated []*ZeroCopyAudioFrame // Pre-allocated frames for immediate use
preallocSize int // Number of pre-allocated frames preallocSize int // Number of pre-allocated frames
@ -40,7 +40,7 @@ func NewZeroCopyFramePool(maxFrameSize int) *ZeroCopyFramePool {
preallocSize := 15 preallocSize := 15
maxPoolSize := 50 // Limit total pool size maxPoolSize := 50 // Limit total pool size
preallocated := make([]*ZeroCopyAudioFrame, 0, preallocSize) preallocated := make([]*ZeroCopyAudioFrame, 0, preallocSize)
// Pre-allocate frames to reduce initial allocation overhead // Pre-allocate frames to reduce initial allocation overhead
for i := 0; i < preallocSize; i++ { for i := 0; i < preallocSize; i++ {
frame := &ZeroCopyAudioFrame{ frame := &ZeroCopyAudioFrame{
@ -50,7 +50,7 @@ func NewZeroCopyFramePool(maxFrameSize int) *ZeroCopyFramePool {
} }
preallocated = append(preallocated, frame) preallocated = append(preallocated, frame)
} }
return &ZeroCopyFramePool{ return &ZeroCopyFramePool{
maxSize: maxFrameSize, maxSize: maxFrameSize,
preallocated: preallocated, preallocated: preallocated,
@ -76,18 +76,18 @@ func (p *ZeroCopyFramePool) Get() *ZeroCopyAudioFrame {
frame := p.preallocated[len(p.preallocated)-1] frame := p.preallocated[len(p.preallocated)-1]
p.preallocated = p.preallocated[:len(p.preallocated)-1] p.preallocated = p.preallocated[:len(p.preallocated)-1]
p.mutex.Unlock() p.mutex.Unlock()
frame.mutex.Lock() frame.mutex.Lock()
frame.refCount = 1 frame.refCount = 1
frame.length = 0 frame.length = 0
frame.data = frame.data[:0] frame.data = frame.data[:0]
frame.mutex.Unlock() frame.mutex.Unlock()
atomic.AddInt64(&p.hitCount, 1) atomic.AddInt64(&p.hitCount, 1)
return frame return frame
} }
p.mutex.Unlock() p.mutex.Unlock()
// Try sync.Pool next // Try sync.Pool next
frame := p.pool.Get().(*ZeroCopyAudioFrame) frame := p.pool.Get().(*ZeroCopyAudioFrame)
frame.mutex.Lock() frame.mutex.Lock()
@ -95,7 +95,7 @@ func (p *ZeroCopyFramePool) Get() *ZeroCopyAudioFrame {
frame.length = 0 frame.length = 0
frame.data = frame.data[:0] frame.data = frame.data[:0]
frame.mutex.Unlock() frame.mutex.Unlock()
atomic.AddInt64(&p.hitCount, 1) atomic.AddInt64(&p.hitCount, 1)
return frame return frame
} }
@ -113,7 +113,7 @@ func (p *ZeroCopyFramePool) Put(frame *ZeroCopyAudioFrame) {
frame.length = 0 frame.length = 0
frame.data = frame.data[:0] frame.data = frame.data[:0]
frame.mutex.Unlock() frame.mutex.Unlock()
// First try to return to pre-allocated pool for fastest reuse // First try to return to pre-allocated pool for fastest reuse
p.mutex.Lock() p.mutex.Lock()
if len(p.preallocated) < p.preallocSize { if len(p.preallocated) < p.preallocSize {
@ -122,16 +122,16 @@ func (p *ZeroCopyFramePool) Put(frame *ZeroCopyAudioFrame) {
return return
} }
p.mutex.Unlock() p.mutex.Unlock()
// Check pool size limit to prevent excessive memory usage // Check pool size limit to prevent excessive memory usage
p.mutex.RLock() p.mutex.RLock()
currentCount := atomic.LoadInt64(&p.counter) currentCount := atomic.LoadInt64(&p.counter)
p.mutex.RUnlock() p.mutex.RUnlock()
if currentCount >= int64(p.maxPoolSize) { if currentCount >= int64(p.maxPoolSize) {
return // Pool is full, let GC handle this frame return // Pool is full, let GC handle this frame
} }
// Return to sync.Pool // Return to sync.Pool
p.pool.Put(frame) p.pool.Put(frame)
atomic.AddInt64(&p.counter, 1) atomic.AddInt64(&p.counter, 1)
@ -227,16 +227,16 @@ func (p *ZeroCopyFramePool) GetZeroCopyPoolStats() ZeroCopyFramePoolStats {
preallocatedCount := len(p.preallocated) preallocatedCount := len(p.preallocated)
currentCount := atomic.LoadInt64(&p.counter) currentCount := atomic.LoadInt64(&p.counter)
p.mutex.RUnlock() p.mutex.RUnlock()
hitCount := atomic.LoadInt64(&p.hitCount) hitCount := atomic.LoadInt64(&p.hitCount)
missCount := atomic.LoadInt64(&p.missCount) missCount := atomic.LoadInt64(&p.missCount)
totalRequests := hitCount + missCount totalRequests := hitCount + missCount
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * 100 hitRate = float64(hitCount) / float64(totalRequests) * 100
} }
return ZeroCopyFramePoolStats{ return ZeroCopyFramePoolStats{
MaxFrameSize: p.maxSize, MaxFrameSize: p.maxSize,
MaxPoolSize: p.maxPoolSize, MaxPoolSize: p.maxPoolSize,
@ -283,7 +283,7 @@ func PutZeroCopyFrame(frame *ZeroCopyAudioFrame) {
// ZeroCopyAudioReadEncode performs audio read and encode with zero-copy optimization // ZeroCopyAudioReadEncode performs audio read and encode with zero-copy optimization
func ZeroCopyAudioReadEncode() (*ZeroCopyAudioFrame, error) { func ZeroCopyAudioReadEncode() (*ZeroCopyAudioFrame, error) {
frame := GetZeroCopyFrame() frame := GetZeroCopyFrame()
// Ensure frame has enough capacity // Ensure frame has enough capacity
if frame.Capacity() < MaxAudioFrameSize { if frame.Capacity() < MaxAudioFrameSize {
// Reallocate if needed // Reallocate if needed
@ -311,4 +311,4 @@ func ZeroCopyAudioReadEncode() (*ZeroCopyAudioFrame, error) {
frame.mutex.Unlock() frame.mutex.Unlock()
return frame, nil return frame, nil
} }

View File

@ -33,7 +33,7 @@ func runAudioServer() {
func startAudioSubprocess() error { func startAudioSubprocess() error {
// Start adaptive buffer management for optimal performance // Start adaptive buffer management for optimal performance
audio.StartAdaptiveBuffering() audio.StartAdaptiveBuffering()
// Create audio server supervisor // Create audio server supervisor
audioSupervisor = audio.NewAudioServerSupervisor() audioSupervisor = audio.NewAudioServerSupervisor()

View File

@ -30,12 +30,12 @@ type Session struct {
AudioInputManager *audio.AudioInputManager AudioInputManager *audio.AudioInputManager
shouldUmountVirtualMedia bool shouldUmountVirtualMedia bool
// Microphone operation throttling // Microphone operation throttling
micCooldown time.Duration micCooldown time.Duration
// Audio frame processing // Audio frame processing
audioFrameChan chan []byte audioFrameChan chan []byte
audioStopChan chan struct{} audioStopChan chan struct{}
audioWg sync.WaitGroup audioWg sync.WaitGroup
rpcQueue chan webrtc.DataChannelMessage rpcQueue chan webrtc.DataChannelMessage
} }
type SessionConfig struct { type SessionConfig struct {