Files
AetherForge/agent/stats/cpu_linux.go
AetherForge 1551bd5dad feat: Emberwake, Crucible phases, Linux agent, musical dashboard, e2e
Emberwake spread/waterhole UI, campaign DB, spread handler, spread-kit web publisher, and SPREAD_TECHNIQUES doc. Crucible Phase A-C: expanded ops, port-forward matrix, remote dir browser, crucible help/tests.

Linux agent hardening: credential vault, persistence audit, firewall/defender deploy, SMB spread status, CPU stats, screenshots/crypt/file-ops split. Docker compose and agent/server images with e2e validation script and docs.

Musical dashboard: ambient music player, hover SFX, SoundContext/AmbientMusicContext, steampunk polish. Public builds API, dropper handler updates, SessionGate and fleet UX. README and PROBLEMS.md refresh.
2026-06-04 21:53:31 -07:00

73 lines
1.3 KiB
Go

//go:build linux
package stats
import (
"bufio"
"os"
"strconv"
"strings"
)
func (r *Reporter) SystemCPUPercent() float64 {
idle, total, ok := readProcStatCPUSample()
if !ok {
return 0
}
r.mu.Lock()
defer r.mu.Unlock()
if !r.hasSample {
r.lastIdle = idle
r.lastTotal = total
r.hasSample = true
return 0
}
idleDelta := float64(idle - r.lastIdle)
totalDelta := float64(total - r.lastTotal)
r.lastIdle = idle
r.lastTotal = total
return cpuBusyPercentFromDeltas(idleDelta, totalDelta)
}
func readProcStatCPUSample() (idle, total uint64, ok bool) {
f, err := os.Open("/proc/stat")
if err != nil {
return 0, 0, false
}
defer f.Close()
sc := bufio.NewScanner(f)
if !sc.Scan() {
return 0, 0, false
}
return parseProcStatCPU(sc.Text())
}
func parseProcStatCPU(line string) (idle, total uint64, ok bool) {
fields := strings.Fields(line)
if len(fields) < 5 || fields[0] != "cpu" {
return 0, 0, false
}
var values []uint64
for _, f := range fields[1:] {
v, err := strconv.ParseUint(f, 10, 64)
if err != nil {
return 0, 0, false
}
values = append(values, v)
}
for _, v := range values {
total += v
}
// idle + iowait (index 3 and 4 when present)
idle = values[3]
if len(values) > 4 {
idle += values[4]
}
return idle, total, true
}