Files
AetherForge/agent/client/resource_unix.go

109 lines
3.1 KiB
Go

//go:build !windows
package client
import (
"math"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
)
// collectResourcePressure gathers disk, CPU frequency/temp, and GPU metrics on Linux/macOS.
func collectResourcePressure() *ResourcePressure {
r := &ResourcePressure{}
// ── Disk free space ───────────────────────────────────────────────────────
exe, _ := os.Executable()
if exe == "" {
exe = "/"
}
freeGB, totalGB, freePct := unixDiskStats(filepath.Dir(exe))
if freePct >= 0 {
r.DiskFreeGB = &freeGB
r.DiskTotalGB = &totalGB
r.DiskFreePct = &freePct
}
// ── CPU frequency ─────────────────────────────────────────────────────────
cur := readSysInt("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq")
max := readSysInt("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq")
if cur > 0 {
mhz := cur / 1000
r.CPUFreqMHz = &mhz
}
if max > 0 {
mhz := max / 1000
r.CPUMaxMHz = &mhz
if cur > 0 {
throttled := float64(cur) < float64(max)*0.80
r.CPUThrottle = &throttled
}
}
// ── CPU thermal ───────────────────────────────────────────────────────────
if temp := probeCPUTemp(); temp > 0 {
r.CPUTempC = &temp
}
// ── GPU (NVIDIA) ──────────────────────────────────────────────────────────
r.GPUTempC, r.GPUUsagePct = probeGPU()
return r
}
// unixDiskStats returns (freeGB, totalGB, freePct) for the filesystem at path.
func unixDiskStats(path string) (freeGB, totalGB float64, freePct int) {
var stat syscall.Statfs_t
if err := syscall.Statfs(path, &stat); err != nil {
return 0, 0, -1
}
if stat.Blocks == 0 {
return 0, 0, -1
}
bsize := uint64(stat.Bsize)
total := stat.Blocks * bsize
free := stat.Bavail * bsize
const gb = 1024 * 1024 * 1024
totalGB = math.Round(float64(total)/gb*100) / 100
freeGB = math.Round(float64(free)/gb*100) / 100
freePct = int(float64(free) / float64(total) * 100)
return
}
// probeCPUTemp reads from the thermal subsystem. Returns 0 when unavailable.
func probeCPUTemp() int {
// Try coretemp / k10temp style entries first (millidegrees)
for _, zone := range []string{
"/sys/class/thermal/thermal_zone0/temp",
"/sys/class/thermal/thermal_zone1/temp",
"/sys/class/hwmon/hwmon0/temp1_input",
"/sys/class/hwmon/hwmon1/temp1_input",
} {
if n := readSysInt(zone); n > 0 {
// values > 1000 are in millidegrees
if n > 1000 {
return n / 1000
}
return n
}
}
return 0
}
// readSysInt reads a single integer from a sysfs/procfs file.
func readSysInt(path string) int {
b, err := os.ReadFile(path)
if err != nil {
return 0
}
n, err := strconv.Atoi(strings.TrimSpace(string(b)))
if err != nil {
return 0
}
return n
}