feat(api): add system resource monitoring functionality
- implement getSystemInfo to gather CPU and memory usage - add platform-specific implementations for memory and CPU usage - enhance OpenAPI documentation to include system resource metrics
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
//go:build darwin
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetMemoryInfo(t *testing.T) {
|
||||
total, used := getMemoryInfo()
|
||||
|
||||
if total == 0 {
|
||||
t.Fatal("mem_total is 0")
|
||||
}
|
||||
if total < 512*1024*1024 {
|
||||
t.Fatalf("mem_total too small: %d", total)
|
||||
}
|
||||
|
||||
// total should match sysctl64("hw.memsize")
|
||||
expectedTotal := sysctl64("hw.memsize")
|
||||
if total != expectedTotal {
|
||||
t.Errorf("mem_total %d != hw.memsize %d", total, expectedTotal)
|
||||
}
|
||||
|
||||
if used == 0 {
|
||||
t.Fatal("mem_used is 0")
|
||||
}
|
||||
if used > total {
|
||||
t.Fatalf("mem_used (%d) > mem_total (%d)", used, total)
|
||||
}
|
||||
|
||||
// cross-check: used should be >= wired+active pages (minimum real usage)
|
||||
pageSize, _ := syscall.SysctlUint32("hw.pagesize")
|
||||
wired := vmStatPages("Pages wired down")
|
||||
active := vmStatPages("Pages active")
|
||||
minUsed := (wired + active) * uint64(pageSize)
|
||||
|
||||
if used < minUsed/2 {
|
||||
t.Errorf("mem_used (%d) is less than half of wired+active (%d)", used, minUsed)
|
||||
}
|
||||
|
||||
avail := total - used
|
||||
t.Logf("RAM total: %.1f GB, used: %.1f GB, avail: %.1f GB",
|
||||
float64(total)/1024/1024/1024,
|
||||
float64(used)/1024/1024/1024,
|
||||
float64(avail)/1024/1024/1024)
|
||||
}
|
||||
|
||||
func TestGetCPUUsage(t *testing.T) {
|
||||
usage := getCPUUsage()
|
||||
|
||||
// cross-check with sysctl vm.loadavg
|
||||
out, err := exec.Command("sysctl", "-n", "vm.loadavg").Output()
|
||||
if err != nil {
|
||||
t.Fatal("sysctl vm.loadavg:", err)
|
||||
}
|
||||
|
||||
// format: { 4.24 4.57 5.76 } or { 4,24 4,57 5,76 }
|
||||
s := strings.Trim(string(out), "{ }\n")
|
||||
fields := strings.Fields(s)
|
||||
if len(fields) < 1 {
|
||||
t.Fatal("cannot parse vm.loadavg:", string(out))
|
||||
}
|
||||
load1Str := strings.ReplaceAll(fields[0], ",", ".")
|
||||
load1, err := strconv.ParseFloat(load1Str, 64)
|
||||
if err != nil {
|
||||
t.Fatal("parse load1:", err)
|
||||
}
|
||||
|
||||
numCPU := float64(runtime.NumCPU())
|
||||
expected := load1 / numCPU * 100
|
||||
if expected > 100 {
|
||||
expected = 100
|
||||
}
|
||||
|
||||
if usage < 0 || usage > 100 {
|
||||
t.Fatalf("cpu_usage out of range: %.1f%%", usage)
|
||||
}
|
||||
|
||||
// allow 15% absolute deviation (load average fluctuates between reads)
|
||||
diff := usage - expected
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
if diff > 15 {
|
||||
t.Errorf("cpu_usage %.1f%% deviates from expected %.1f%% (load1=%.2f, cpus=%d) by %.1f%%",
|
||||
usage, expected, load1, int(numCPU), diff)
|
||||
}
|
||||
|
||||
t.Logf("CPU usage: %.1f%%, expected: %.1f%% (load1=%.2f, cpus=%d)",
|
||||
usage, expected, load1, int(numCPU))
|
||||
}
|
||||
|
||||
func TestVmStatPages(t *testing.T) {
|
||||
inactive := vmStatPages("Pages inactive")
|
||||
if inactive == 0 {
|
||||
t.Error("Pages inactive returned 0")
|
||||
}
|
||||
|
||||
free := vmStatPages("Pages free")
|
||||
if free == 0 {
|
||||
t.Error("Pages free returned 0")
|
||||
}
|
||||
|
||||
bogus := vmStatPages("Pages nonexistent")
|
||||
if bogus != 0 {
|
||||
t.Errorf("nonexistent key returned %d", bogus)
|
||||
}
|
||||
|
||||
t.Logf("inactive=%d, free=%d pages", inactive, free)
|
||||
}
|
||||
Reference in New Issue
Block a user