package audio import ( "sort" "sync" "sync/atomic" "time" "github.com/jetkvm/kvm/internal/logging" "github.com/rs/zerolog" ) // LatencyHistogram tracks latency distribution with percentile calculations type LatencyHistogram struct { // Atomic fields MUST be first for ARM32 alignment sampleCount int64 // Total number of samples (atomic) totalLatency int64 // Sum of all latencies in nanoseconds (atomic) // Latency buckets for histogram (in nanoseconds) buckets []int64 // Bucket boundaries counts []int64 // Count for each bucket (atomic) // Recent samples for percentile calculation recentSamples []time.Duration samplesMutex sync.RWMutex maxSamples int logger zerolog.Logger } // LatencyPercentiles holds calculated percentile values type LatencyPercentiles struct { P50 time.Duration `json:"p50"` P95 time.Duration `json:"p95"` P99 time.Duration `json:"p99"` Min time.Duration `json:"min"` Max time.Duration `json:"max"` Avg time.Duration `json:"avg"` } // BufferPoolEfficiencyMetrics tracks detailed buffer pool performance type BufferPoolEfficiencyMetrics struct { // Pool utilization metrics HitRate float64 `json:"hit_rate"` MissRate float64 `json:"miss_rate"` UtilizationRate float64 `json:"utilization_rate"` FragmentationRate float64 `json:"fragmentation_rate"` // Memory efficiency metrics MemoryEfficiency float64 `json:"memory_efficiency"` AllocationOverhead float64 `json:"allocation_overhead"` ReuseEffectiveness float64 `json:"reuse_effectiveness"` // Performance metrics AverageGetLatency time.Duration `json:"average_get_latency"` AveragePutLatency time.Duration `json:"average_put_latency"` Throughput float64 `json:"throughput"` // Operations per second } // GranularMetricsCollector aggregates all granular metrics type GranularMetricsCollector struct { // Latency histograms by source inputLatencyHist *LatencyHistogram outputLatencyHist *LatencyHistogram processingLatencyHist *LatencyHistogram // Buffer pool efficiency tracking framePoolMetrics *BufferPoolEfficiencyTracker controlPoolMetrics *BufferPoolEfficiencyTracker zeroCopyMetrics *BufferPoolEfficiencyTracker mutex sync.RWMutex logger zerolog.Logger } // BufferPoolEfficiencyTracker tracks detailed efficiency metrics for a buffer pool type BufferPoolEfficiencyTracker struct { // Atomic counters getOperations int64 // Total get operations (atomic) putOperations int64 // Total put operations (atomic) getLatencySum int64 // Sum of get latencies in nanoseconds (atomic) putLatencySum int64 // Sum of put latencies in nanoseconds (atomic) allocationBytes int64 // Total bytes allocated (atomic) reuseCount int64 // Number of successful reuses (atomic) // Recent operation times for throughput calculation recentOps []time.Time opsMutex sync.RWMutex poolName string logger zerolog.Logger } // NewLatencyHistogram creates a new latency histogram with predefined buckets func NewLatencyHistogram(maxSamples int, logger zerolog.Logger) *LatencyHistogram { // Define latency buckets using configuration constants buckets := []int64{ int64(1 * time.Millisecond), int64(5 * time.Millisecond), int64(GetConfig().LatencyBucket10ms), int64(GetConfig().LatencyBucket25ms), int64(GetConfig().LatencyBucket50ms), int64(GetConfig().LatencyBucket100ms), int64(GetConfig().LatencyBucket250ms), int64(GetConfig().LatencyBucket500ms), int64(GetConfig().LatencyBucket1s), int64(GetConfig().LatencyBucket2s), } return &LatencyHistogram{ buckets: buckets, counts: make([]int64, len(buckets)+1), // +1 for overflow bucket recentSamples: make([]time.Duration, 0, maxSamples), maxSamples: maxSamples, logger: logger, } } // RecordLatency adds a latency measurement to the histogram func (lh *LatencyHistogram) RecordLatency(latency time.Duration) { latencyNs := latency.Nanoseconds() atomic.AddInt64(&lh.sampleCount, 1) atomic.AddInt64(&lh.totalLatency, latencyNs) // Find appropriate bucket bucketIndex := len(lh.buckets) // Default to overflow bucket for i, boundary := range lh.buckets { if latencyNs <= boundary { bucketIndex = i break } } atomic.AddInt64(&lh.counts[bucketIndex], 1) // Store recent sample for percentile calculation lh.samplesMutex.Lock() if len(lh.recentSamples) >= lh.maxSamples { // Remove oldest sample lh.recentSamples = lh.recentSamples[1:] } lh.recentSamples = append(lh.recentSamples, latency) lh.samplesMutex.Unlock() } // GetPercentiles calculates latency percentiles from recent samples func (lh *LatencyHistogram) GetPercentiles() LatencyPercentiles { lh.samplesMutex.RLock() samples := make([]time.Duration, len(lh.recentSamples)) copy(samples, lh.recentSamples) lh.samplesMutex.RUnlock() if len(samples) == 0 { return LatencyPercentiles{} } // Sort samples for percentile calculation sort.Slice(samples, func(i, j int) bool { return samples[i] < samples[j] }) n := len(samples) totalLatency := atomic.LoadInt64(&lh.totalLatency) sampleCount := atomic.LoadInt64(&lh.sampleCount) var avg time.Duration if sampleCount > 0 { avg = time.Duration(totalLatency / sampleCount) } return LatencyPercentiles{ P50: samples[n*50/100], P95: samples[n*95/100], P99: samples[n*99/100], Min: samples[0], Max: samples[n-1], Avg: avg, } } // NewBufferPoolEfficiencyTracker creates a new efficiency tracker func NewBufferPoolEfficiencyTracker(poolName string, logger zerolog.Logger) *BufferPoolEfficiencyTracker { return &BufferPoolEfficiencyTracker{ recentOps: make([]time.Time, 0, 1000), // Track last 1000 operations poolName: poolName, logger: logger, } } // RecordGetOperation records a buffer get operation with its latency func (bpet *BufferPoolEfficiencyTracker) RecordGetOperation(latency time.Duration, wasHit bool) { atomic.AddInt64(&bpet.getOperations, 1) atomic.AddInt64(&bpet.getLatencySum, latency.Nanoseconds()) if wasHit { atomic.AddInt64(&bpet.reuseCount, 1) } // Record operation time for throughput calculation bpet.opsMutex.Lock() now := time.Now() if len(bpet.recentOps) >= 1000 { bpet.recentOps = bpet.recentOps[1:] } bpet.recentOps = append(bpet.recentOps, now) bpet.opsMutex.Unlock() } // RecordPutOperation records a buffer put operation with its latency func (bpet *BufferPoolEfficiencyTracker) RecordPutOperation(latency time.Duration, bufferSize int) { atomic.AddInt64(&bpet.putOperations, 1) atomic.AddInt64(&bpet.putLatencySum, latency.Nanoseconds()) atomic.AddInt64(&bpet.allocationBytes, int64(bufferSize)) } // GetEfficiencyMetrics calculates current efficiency metrics func (bpet *BufferPoolEfficiencyTracker) GetEfficiencyMetrics() BufferPoolEfficiencyMetrics { getOps := atomic.LoadInt64(&bpet.getOperations) putOps := atomic.LoadInt64(&bpet.putOperations) reuseCount := atomic.LoadInt64(&bpet.reuseCount) getLatencySum := atomic.LoadInt64(&bpet.getLatencySum) putLatencySum := atomic.LoadInt64(&bpet.putLatencySum) allocationBytes := atomic.LoadInt64(&bpet.allocationBytes) var hitRate, missRate, avgGetLatency, avgPutLatency float64 var throughput float64 if getOps > 0 { hitRate = float64(reuseCount) / float64(getOps) * 100 missRate = 100 - hitRate avgGetLatency = float64(getLatencySum) / float64(getOps) } if putOps > 0 { avgPutLatency = float64(putLatencySum) / float64(putOps) } // Calculate throughput from recent operations bpet.opsMutex.RLock() if len(bpet.recentOps) > 1 { timeSpan := bpet.recentOps[len(bpet.recentOps)-1].Sub(bpet.recentOps[0]) if timeSpan > 0 { throughput = float64(len(bpet.recentOps)) / timeSpan.Seconds() } } bpet.opsMutex.RUnlock() // Calculate efficiency metrics utilizationRate := hitRate // Simplified: hit rate as utilization memoryEfficiency := hitRate // Simplified: reuse rate as memory efficiency reuseEffectiveness := hitRate // Calculate fragmentation (simplified as inverse of hit rate) fragmentationRate := missRate // Calculate allocation overhead (simplified) allocationOverhead := float64(0) if getOps > 0 && allocationBytes > 0 { allocationOverhead = float64(allocationBytes) / float64(getOps) } return BufferPoolEfficiencyMetrics{ HitRate: hitRate, MissRate: missRate, UtilizationRate: utilizationRate, FragmentationRate: fragmentationRate, MemoryEfficiency: memoryEfficiency, AllocationOverhead: allocationOverhead, ReuseEffectiveness: reuseEffectiveness, AverageGetLatency: time.Duration(avgGetLatency), AveragePutLatency: time.Duration(avgPutLatency), Throughput: throughput, } } // NewGranularMetricsCollector creates a new granular metrics collector func NewGranularMetricsCollector(logger zerolog.Logger) *GranularMetricsCollector { maxSamples := GetConfig().LatencyHistorySize return &GranularMetricsCollector{ inputLatencyHist: NewLatencyHistogram(maxSamples, logger.With().Str("histogram", "input").Logger()), outputLatencyHist: NewLatencyHistogram(maxSamples, logger.With().Str("histogram", "output").Logger()), processingLatencyHist: NewLatencyHistogram(maxSamples, logger.With().Str("histogram", "processing").Logger()), framePoolMetrics: NewBufferPoolEfficiencyTracker("frame_pool", logger.With().Str("pool", "frame").Logger()), controlPoolMetrics: NewBufferPoolEfficiencyTracker("control_pool", logger.With().Str("pool", "control").Logger()), zeroCopyMetrics: NewBufferPoolEfficiencyTracker("zero_copy_pool", logger.With().Str("pool", "zero_copy").Logger()), logger: logger, } } // RecordInputLatency records latency for input operations func (gmc *GranularMetricsCollector) RecordInputLatency(latency time.Duration) { gmc.inputLatencyHist.RecordLatency(latency) } // RecordOutputLatency records latency for output operations func (gmc *GranularMetricsCollector) RecordOutputLatency(latency time.Duration) { gmc.outputLatencyHist.RecordLatency(latency) } // RecordProcessingLatency records latency for processing operations func (gmc *GranularMetricsCollector) RecordProcessingLatency(latency time.Duration) { gmc.processingLatencyHist.RecordLatency(latency) } // RecordFramePoolOperation records frame pool operations func (gmc *GranularMetricsCollector) RecordFramePoolGet(latency time.Duration, wasHit bool) { gmc.framePoolMetrics.RecordGetOperation(latency, wasHit) } func (gmc *GranularMetricsCollector) RecordFramePoolPut(latency time.Duration, bufferSize int) { gmc.framePoolMetrics.RecordPutOperation(latency, bufferSize) } // RecordControlPoolOperation records control pool operations func (gmc *GranularMetricsCollector) RecordControlPoolGet(latency time.Duration, wasHit bool) { gmc.controlPoolMetrics.RecordGetOperation(latency, wasHit) } func (gmc *GranularMetricsCollector) RecordControlPoolPut(latency time.Duration, bufferSize int) { gmc.controlPoolMetrics.RecordPutOperation(latency, bufferSize) } // RecordZeroCopyOperation records zero-copy pool operations func (gmc *GranularMetricsCollector) RecordZeroCopyGet(latency time.Duration, wasHit bool) { gmc.zeroCopyMetrics.RecordGetOperation(latency, wasHit) } func (gmc *GranularMetricsCollector) RecordZeroCopyPut(latency time.Duration, bufferSize int) { gmc.zeroCopyMetrics.RecordPutOperation(latency, bufferSize) } // GetLatencyPercentiles returns percentiles for all latency types func (gmc *GranularMetricsCollector) GetLatencyPercentiles() map[string]LatencyPercentiles { gmc.mutex.RLock() defer gmc.mutex.RUnlock() return map[string]LatencyPercentiles{ "input": gmc.inputLatencyHist.GetPercentiles(), "output": gmc.outputLatencyHist.GetPercentiles(), "processing": gmc.processingLatencyHist.GetPercentiles(), } } // GetBufferPoolEfficiency returns efficiency metrics for all buffer pools func (gmc *GranularMetricsCollector) GetBufferPoolEfficiency() map[string]BufferPoolEfficiencyMetrics { gmc.mutex.RLock() defer gmc.mutex.RUnlock() return map[string]BufferPoolEfficiencyMetrics{ "frame_pool": gmc.framePoolMetrics.GetEfficiencyMetrics(), "control_pool": gmc.controlPoolMetrics.GetEfficiencyMetrics(), "zero_copy_pool": gmc.zeroCopyMetrics.GetEfficiencyMetrics(), } } // LogGranularMetrics logs comprehensive granular metrics func (gmc *GranularMetricsCollector) LogGranularMetrics() { latencyPercentiles := gmc.GetLatencyPercentiles() bufferEfficiency := gmc.GetBufferPoolEfficiency() // Log latency percentiles for source, percentiles := range latencyPercentiles { gmc.logger.Info(). Str("source", source). Dur("p50", percentiles.P50). Dur("p95", percentiles.P95). Dur("p99", percentiles.P99). Dur("min", percentiles.Min). Dur("max", percentiles.Max). Dur("avg", percentiles.Avg). Msg("Latency percentiles") } // Log buffer pool efficiency for poolName, efficiency := range bufferEfficiency { gmc.logger.Info(). Str("pool", poolName). Float64("hit_rate", efficiency.HitRate). Float64("miss_rate", efficiency.MissRate). Float64("utilization_rate", efficiency.UtilizationRate). Float64("memory_efficiency", efficiency.MemoryEfficiency). Dur("avg_get_latency", efficiency.AverageGetLatency). Dur("avg_put_latency", efficiency.AveragePutLatency). Float64("throughput", efficiency.Throughput). Msg("Buffer pool efficiency metrics") } } // Global granular metrics collector instance var ( granularMetricsCollector *GranularMetricsCollector granularMetricsOnce sync.Once ) // GetGranularMetricsCollector returns the global granular metrics collector func GetGranularMetricsCollector() *GranularMetricsCollector { granularMetricsOnce.Do(func() { logger := logging.GetDefaultLogger().With().Str("component", "granular-metrics").Logger() granularMetricsCollector = NewGranularMetricsCollector(logger) }) return granularMetricsCollector } // StartGranularMetricsLogging starts periodic granular metrics logging func StartGranularMetricsLogging(interval time.Duration) { collector := GetGranularMetricsCollector() logger := collector.logger logger.Info().Dur("interval", interval).Msg("Starting granular metrics logging") go func() { ticker := time.NewTicker(interval) defer ticker.Stop() for range ticker.C { collector.LogGranularMetrics() } }() }