//go:build windows package api import ( "sync" "syscall" "unsafe" "golang.org/x/sys/windows" ) // collectSystemMetrics reads CPU% and memory from kernel32 directly. // Network throughput on Windows is left at zero for now — the iphlpapi // MIB_IF_ROW2 layout is large and version-sensitive; reliable net stats // would warrant a separate, well-tested implementation. CPU + RAM are // enough for the dashboard's main signal. func collectSystemMetrics() sysMetrics { m := sysMetrics{} if cpu, ok := readWindowsCPUPercent(); ok { m.CPUPercent = cpu } if memTotalMB, memUsedMB, memPct, ok := readWindowsMemory(); ok { m.MemTotalMB = memTotalMB m.MemUsedMB = memUsedMB m.MemPercent = memPct } // Net: zero (TODO). return m } // --- CPU --------------------------------------------------------------- var ( cpuOnce sync.Once getSystemTimes *syscall.LazyProc lastWinCPUIdle uint64 lastWinCPUTotal uint64 lastWinCPUSet bool winCPUMu sync.Mutex ) func loadCPUFns() { cpuOnce.Do(func() { k := syscall.NewLazyDLL("kernel32.dll") getSystemTimes = k.NewProc("GetSystemTimes") }) } func filetimeToUint64(low, high uint32) uint64 { return uint64(high)<<32 | uint64(low) } // readWindowsCPUPercent samples GetSystemTimes twice and computes the busy // ratio as 1 - dIdle / (dKernel + dUser). The first call returns 0% and // stores the baseline; subsequent calls return the delta-based percentage. func readWindowsCPUPercent() (float64, bool) { loadCPUFns() if getSystemTimes == nil { return 0, false } var idle, kernel, user windows.Filetime r1, _, _ := getSystemTimes.Call( uintptr(unsafe.Pointer(&idle)), uintptr(unsafe.Pointer(&kernel)), uintptr(unsafe.Pointer(&user)), ) if r1 == 0 { return 0, false } idleT := filetimeToUint64(idle.LowDateTime, idle.HighDateTime) totalT := filetimeToUint64(kernel.LowDateTime, kernel.HighDateTime) + filetimeToUint64(user.LowDateTime, user.HighDateTime) winCPUMu.Lock() defer winCPUMu.Unlock() if !lastWinCPUSet { lastWinCPUIdle = idleT lastWinCPUTotal = totalT lastWinCPUSet = true return 0, true } dIdle := idleT - lastWinCPUIdle dTotal := totalT - lastWinCPUTotal lastWinCPUIdle = idleT lastWinCPUTotal = totalT if dTotal == 0 { return 0, true } pct := (1 - float64(dIdle)/float64(dTotal)) * 100 if pct < 0 { pct = 0 } else if pct > 100 { pct = 100 } return pct, true } // --- Memory ------------------------------------------------------------ type memoryStatusEx struct { Length uint32 MemoryLoad uint32 TotalPhys uint64 AvailPhys uint64 TotalPageFile uint64 AvailPageFile uint64 TotalVirtual uint64 AvailVirtual uint64 AvailExtendedVirtual uint64 } var globalMemoryStatusEx = syscall.NewLazyDLL("kernel32.dll").NewProc("GlobalMemoryStatusEx") func readWindowsMemory() (totalMB, usedMB, percent float64, ok bool) { var ms memoryStatusEx ms.Length = uint32(unsafe.Sizeof(ms)) r1, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&ms))) if r1 == 0 { return 0, 0, 0, false } const mb = 1024 * 1024 totalMB = float64(ms.TotalPhys) / mb usedMB = float64(ms.TotalPhys-ms.AvailPhys) / mb if ms.TotalPhys > 0 { percent = float64(ms.TotalPhys-ms.AvailPhys) * 100 / float64(ms.TotalPhys) } return totalMB, usedMB, percent, true }