package memory import ( "runtime" "sync" "sync/atomic" "time" ) // MemoryMonitor tracks system memory usage and provides dynamic sizing recommendations type MemoryMonitor struct { targetMemoryUsage uint64 // Target total memory usage in bytes currentMemoryUsage uint64 // Current total memory usage in bytes monitoringInterval time.Duration adjustmentThreshold float64 // Threshold for cache size adjustments (e.g., 0.1 = 10%) mu sync.RWMutex ctx chan struct{} stopChan chan struct{} isMonitoring int32 } // NewMemoryMonitor creates a new memory monitor func NewMemoryMonitor(targetMemoryUsage uint64, monitoringInterval time.Duration, adjustmentThreshold float64) *MemoryMonitor { return &MemoryMonitor{ targetMemoryUsage: targetMemoryUsage, monitoringInterval: monitoringInterval, adjustmentThreshold: adjustmentThreshold, ctx: make(chan struct{}), stopChan: make(chan struct{}), } } // Start begins monitoring memory usage func (mm *MemoryMonitor) Start() { if atomic.CompareAndSwapInt32(&mm.isMonitoring, 0, 1) { go mm.monitor() } } // Stop stops monitoring memory usage func (mm *MemoryMonitor) Stop() { if atomic.CompareAndSwapInt32(&mm.isMonitoring, 1, 0) { close(mm.stopChan) } } // GetCurrentMemoryUsage returns the current total memory usage func (mm *MemoryMonitor) GetCurrentMemoryUsage() uint64 { mm.mu.RLock() defer mm.mu.RUnlock() return atomic.LoadUint64(&mm.currentMemoryUsage) } // GetTargetMemoryUsage returns the target memory usage func (mm *MemoryMonitor) GetTargetMemoryUsage() uint64 { mm.mu.RLock() defer mm.mu.RUnlock() return mm.targetMemoryUsage } // GetMemoryUtilization returns the current memory utilization as a percentage func (mm *MemoryMonitor) GetMemoryUtilization() float64 { mm.mu.RLock() defer mm.mu.RUnlock() current := atomic.LoadUint64(&mm.currentMemoryUsage) return float64(current) / float64(mm.targetMemoryUsage) } // GetRecommendedCacheSize calculates the recommended cache size based on current memory usage func (mm *MemoryMonitor) GetRecommendedCacheSize(originalCacheSize uint64) uint64 { mm.mu.RLock() defer mm.mu.RUnlock() current := atomic.LoadUint64(&mm.currentMemoryUsage) target := mm.targetMemoryUsage // If we're under target, we can use the full cache size if current <= target { return originalCacheSize } // Calculate how much we're over target overage := current - target // If overage is significant, reduce cache size if overage > uint64(float64(target)*mm.adjustmentThreshold) { // Reduce cache size by the overage amount, but don't go below 10% of original minCacheSize := uint64(float64(originalCacheSize) * 0.1) recommendedSize := originalCacheSize - overage if recommendedSize < minCacheSize { recommendedSize = minCacheSize } return recommendedSize } return originalCacheSize } // monitor runs the memory monitoring loop func (mm *MemoryMonitor) monitor() { ticker := time.NewTicker(mm.monitoringInterval) defer ticker.Stop() for { select { case <-mm.stopChan: return case <-ticker.C: mm.updateMemoryUsage() } } } // updateMemoryUsage updates the current memory usage func (mm *MemoryMonitor) updateMemoryUsage() { var m runtime.MemStats runtime.ReadMemStats(&m) // Use Alloc (currently allocated memory) as our metric atomic.StoreUint64(&mm.currentMemoryUsage, m.Alloc) } // SetTargetMemoryUsage updates the target memory usage func (mm *MemoryMonitor) SetTargetMemoryUsage(target uint64) { mm.mu.Lock() defer mm.mu.Unlock() mm.targetMemoryUsage = target } // GetMemoryStats returns detailed memory statistics func (mm *MemoryMonitor) GetMemoryStats() map[string]interface{} { var m runtime.MemStats runtime.ReadMemStats(&m) mm.mu.RLock() defer mm.mu.RUnlock() return map[string]interface{}{ "current_usage": atomic.LoadUint64(&mm.currentMemoryUsage), "target_usage": mm.targetMemoryUsage, "utilization": mm.GetMemoryUtilization(), "heap_alloc": m.HeapAlloc, "heap_sys": m.HeapSys, "heap_idle": m.HeapIdle, "heap_inuse": m.HeapInuse, "stack_inuse": m.StackInuse, "stack_sys": m.StackSys, "gc_cycles": m.NumGC, "gc_pause_total": m.PauseTotalNs, } }