Files
AetherForge/agent/client/resource_windows.go

94 lines
2.9 KiB
Go

//go:build windows
package client
import (
"encoding/json"
"math"
"os"
"os/exec"
"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 := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", cpuScript).Output(); 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
}