//go:build windows package client import ( "encoding/json" "math" "os" "path/filepath" "strings" "syscall" "unsafe" ) // collectResourcePressure gathers disk, CPU throttle, and GPU metrics on Windows. func collectResourcePressure() *ResourcePressure { r := &ResourcePressure{} // ── Disk free space ─────────────────────────────────────────────────────── // Check the drive that hosts the running binary. exe, _ := os.Executable() if exe == "" { exe = `C:\` } freeGB, totalGB, freePct := winDiskStats(filepath.VolumeName(exe)) if freePct >= 0 { r.DiskFreeGB = &freeGB r.DiskTotalGB = &totalGB r.DiskFreePct = &freePct } // ── CPU frequency via short PowerShell call ─────────────────────────────── const cpuScript = `$c=Get-CimInstance Win32_Processor|Select-Object -First 1 CurrentClockSpeed,MaxClockSpeed;@{freq=[int]$c.CurrentClockSpeed;max=[int]$c.MaxClockSpeed}|ConvertTo-Json -Compress` if out, err := silentOutput("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", cpuScript); err == nil { var m map[string]interface{} if json.Unmarshal([]byte(strings.TrimSpace(string(out))), &m) == nil { if freq, ok := m["freq"].(float64); ok && freq > 0 { n := int(freq) r.CPUFreqMHz = &n } if max, ok := m["max"].(float64); ok && max > 0 { n := int(max) r.CPUMaxMHz = &n if r.CPUFreqMHz != nil { throttled := float64(*r.CPUFreqMHz) < float64(n)*0.80 r.CPUThrottle = &throttled } } } } // ── GPU (NVIDIA) ────────────────────────────────────────────────────────── r.GPUTempC, r.GPUUsagePct = probeGPU() return r } // winDiskStats returns (freeGB, totalGB, freePct) for the given drive letter root. // e.g. pass "C:" — the function appends the backslash. // Returns freePct = -1 on failure. func winDiskStats(volumeName string) (freeGB, totalGB float64, freePct int) { root := volumeName if root == "" { root = "C:" } root += `\` kernel32 := syscall.NewLazyDLL("kernel32.dll") getDiskFreeSpaceEx := kernel32.NewProc("GetDiskFreeSpaceExW") rootPtr, err := syscall.UTF16PtrFromString(root) if err != nil { return 0, 0, -1 } var freeAvail, total, totalFree uint64 ret, _, _ := getDiskFreeSpaceEx.Call( uintptr(unsafe.Pointer(rootPtr)), uintptr(unsafe.Pointer(&freeAvail)), uintptr(unsafe.Pointer(&total)), uintptr(unsafe.Pointer(&totalFree)), ) if ret == 0 || total == 0 { return 0, 0, -1 } const gb = 1024 * 1024 * 1024 freeGB = math.Round(float64(freeAvail)/gb*100) / 100 totalGB = math.Round(float64(total)/gb*100) / 100 freePct = int(float64(freeAvail) / float64(total) * 100) return }