//go:build windows // Package metrics reads host CPU / RAM / disk usage on Windows using only the // stdlib (kernel32 via syscall) — no external dependency (GOPROXY is unavailable // in this environment, and gopsutil isn't needed for these three numbers). package metrics import ( "runtime" "syscall" "time" "unsafe" ) var ( kernel32 = syscall.NewLazyDLL("kernel32.dll") procGlobalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx") procGetSystemTimes = kernel32.NewProc("GetSystemTimes") procGetDiskFreeSpaceExW = kernel32.NewProc("GetDiskFreeSpaceExW") ) type memoryStatusEx struct { cbSize uint32 dwMemoryLoad uint32 ullTotalPhys uint64 ullAvailPhys uint64 ullTotalPageFile uint64 ullAvailPageFile uint64 ullTotalVirtual uint64 ullAvailVirtual uint64 ullAvailExtendedVirtual uint64 } type filetime struct{ low, high uint32 } func (f filetime) u64() uint64 { return uint64(f.high)<<32 | uint64(f.low) } type cpuSample struct{ idle, kernel, user uint64 } func readCPU() (cpuSample, bool) { var idle, kernel, user filetime r, _, _ := procGetSystemTimes.Call( uintptr(unsafe.Pointer(&idle)), uintptr(unsafe.Pointer(&kernel)), uintptr(unsafe.Pointer(&user)), ) if r == 0 { return cpuSample{}, false } return cpuSample{idle.u64(), kernel.u64(), user.u64()}, true } // CPUPercent samples system CPU times over `interval` and returns busy % (0-100). // On Windows the "kernel" time INCLUDES idle, so total = kernel+user, busy = total-idle. func CPUPercent(interval time.Duration) int { a, ok := readCPU() if !ok { return 0 } time.Sleep(interval) b, ok := readCPU() if !ok { return 0 } idleD := float64(b.idle - a.idle) totalD := float64((b.kernel - a.kernel) + (b.user - a.user)) if totalD <= 0 { return 0 } busy := (totalD - idleD) / totalD * 100 if busy < 0 { busy = 0 } if busy > 100 { busy = 100 } return int(busy + 0.5) } // Memory returns (totalMB, availableMB). func Memory() (totalMB, availMB int) { var m memoryStatusEx m.cbSize = uint32(unsafe.Sizeof(m)) if r, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&m))); r == 0 { return 0, 0 } return int(m.ullTotalPhys / 1024 / 1024), int(m.ullAvailPhys / 1024 / 1024) } // Disk returns (usedPct, totalGB) for the volume containing path (default C:\). func Disk(path string) (usedPct, totalGB int) { if path == "" { path = "C:\\" } p, err := syscall.UTF16PtrFromString(path) if err != nil { return 0, 0 } var freeAvail, total, totalFree uint64 r, _, _ := procGetDiskFreeSpaceExW.Call( uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(&freeAvail)), uintptr(unsafe.Pointer(&total)), uintptr(unsafe.Pointer(&totalFree)), ) if r == 0 || total == 0 { return 0, 0 } used := total - totalFree return int(float64(used)/float64(total)*100 + 0.5), int(total / 1024 / 1024 / 1024) } // Cores returns the logical CPU count. func Cores() int { return runtime.NumCPU() }