diff --git a/web/service/server.go b/web/service/server.go index 45a76f86..eb261c88 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -110,6 +110,7 @@ type ServerService struct { mu sync.Mutex lastCPUTimes cpu.TimesStat hasLastCPUSample bool + hasNativeCPUSample bool emaCPU float64 cpuHistory []CPUSample cachedCpuSpeedMhz float64 @@ -432,23 +433,27 @@ func (s *ServerService) AppendCpuSample(t time.Time, v float64) { } func (s *ServerService) sampleCPUUtilization() (float64, error) { - // Prefer native Windows API to avoid external deps for CPU percent - if runtime.GOOS == "windows" { - if pct, err := sys.CPUPercentRaw(); err == nil { - s.mu.Lock() - // Smooth with EMA - const alpha = 0.3 - if s.emaCPU == 0 { - s.emaCPU = pct - } else { - s.emaCPU = alpha*pct + (1-alpha)*s.emaCPU - } - val := s.emaCPU + // Try native platform-specific CPU implementation first (Windows, Linux, macOS) + if pct, err := sys.CPUPercentRaw(); err == nil { + s.mu.Lock() + // First call to native method returns 0 (initializes baseline) + if !s.hasNativeCPUSample { + s.hasNativeCPUSample = true s.mu.Unlock() - return val, nil + return 0, nil } - // If native call fails, fall back to gopsutil times + // Smooth with EMA + const alpha = 0.3 + if s.emaCPU == 0 { + s.emaCPU = pct + } else { + s.emaCPU = alpha*pct + (1-alpha)*s.emaCPU + } + val := s.emaCPU + s.mu.Unlock() + return val, nil } + // If native call fails, fall back to gopsutil times // Read aggregate CPU times (all CPUs combined) times, err := cpu.Times(false) if err != nil { @@ -471,17 +476,16 @@ func (s *ServerService) sampleCPUUtilization() (float64, error) { } // Compute busy and total deltas + // Note: Guest and GuestNice times are already included in User and Nice respectively, + // so we exclude them to avoid double-counting (Linux kernel accounting) idleDelta := cur.Idle - s.lastCPUTimes.Idle - // Sum of busy deltas (exclude Idle) busyDelta := (cur.User - s.lastCPUTimes.User) + (cur.System - s.lastCPUTimes.System) + (cur.Nice - s.lastCPUTimes.Nice) + (cur.Iowait - s.lastCPUTimes.Iowait) + (cur.Irq - s.lastCPUTimes.Irq) + (cur.Softirq - s.lastCPUTimes.Softirq) + - (cur.Steal - s.lastCPUTimes.Steal) + - (cur.Guest - s.lastCPUTimes.Guest) + - (cur.GuestNice - s.lastCPUTimes.GuestNice) + (cur.Steal - s.lastCPUTimes.Steal) totalDelta := busyDelta + idleDelta