package monitor import ( "runtime" "sync" "sync/atomic" "time" ) var startedAt = time.Now() var totalRequests uint64 var totalErrors uint64 type Snapshot struct { UptimeSeconds int64 `json:"uptime_seconds"` Goroutines int `json:"goroutines"` MemoryAllocMB float64 `json:"memory_alloc_mb"` MemorySysMB float64 `json:"memory_sys_mb"` CPUCores int `json:"cpu_cores"` DiskTotalGB float64 `json:"disk_total_gb"` DiskFreeGB float64 `json:"disk_free_gb"` RequestsTotal uint64 `json:"requests_total"` ErrorsTotal uint64 `json:"errors_total"` } var ( cachedSnapshot Snapshot snapshotMutex sync.RWMutex ) func init() { // Initialize the snapshot once on startup updateSnapshot() // Update the snapshot in the background every 2 seconds to avoid STW runtime.ReadMemStats in request threads go func() { ticker := time.NewTicker(2 * time.Second) for range ticker.C { updateSnapshot() } }() } func updateSnapshot() { var mem runtime.MemStats runtime.ReadMemStats(&mem) diskTotal, diskFree := getDiskSpaceGB() snapshotMutex.Lock() cachedSnapshot = Snapshot{ UptimeSeconds: int64(time.Since(startedAt).Seconds()), Goroutines: runtime.NumGoroutine(), MemoryAllocMB: bytesToMB(mem.Alloc), MemorySysMB: bytesToMB(mem.Sys), CPUCores: runtime.NumCPU(), DiskTotalGB: diskTotal, DiskFreeGB: diskFree, RequestsTotal: atomic.LoadUint64(&totalRequests), ErrorsTotal: atomic.LoadUint64(&totalErrors), } snapshotMutex.Unlock() } func IncrementRequestCount() { atomic.AddUint64(&totalRequests, 1) } func IncrementErrorCount() { atomic.AddUint64(&totalErrors, 1) } func GetSnapshot() Snapshot { snapshotMutex.RLock() defer snapshotMutex.RUnlock() // Return the cached snapshot, overlaying volatile/cheap fields in real-time s := cachedSnapshot s.UptimeSeconds = int64(time.Since(startedAt).Seconds()) s.Goroutines = runtime.NumGoroutine() s.RequestsTotal = atomic.LoadUint64(&totalRequests) s.ErrorsTotal = atomic.LoadUint64(&totalErrors) return s } func bytesToMB(v uint64) float64 { return float64(v) / 1024.0 / 1024.0 }