package agent import ( "bytes" "errors" "os" "strconv" "time" ) // Previous /proc/stat reading; the load is the busy share of the ticks since. var ( cpuPrevIdle, cpuPrevTotal uint64 cpuPrevAt time.Time ) // cpuStaleAfter is how old the previous reading may be: past it the delta // would average over a stretch nobody was watching, so take a fresh pair. const cpuStaleAfter = 15 * time.Second func cpuPercent() (float64, error) { if time.Since(cpuPrevAt) > cpuStaleAfter { idle, total, err := readCPUTicks() if err != nil { return 0, err } cpuPrevIdle, cpuPrevTotal = idle, total time.Sleep(200 * time.Millisecond) } idle, total, err := readCPUTicks() if err != nil { return 0, err } dIdle, dTotal := idle-cpuPrevIdle, total-cpuPrevTotal cpuPrevIdle, cpuPrevTotal, cpuPrevAt = idle, total, time.Now() if dTotal == 0 || dIdle > dTotal { return 0, nil } return clampPercent(100 * float64(dTotal-dIdle) / float64(dTotal)), nil } // readCPUTicks parses the aggregate "cpu" line of /proc/stat: // user nice system idle iowait irq softirq steal ... func readCPUTicks() (idle, total uint64, err error) { var buf [512]byte f, err := os.Open("/proc/stat") if err != nil { return 0, 0, err } n, _ := f.Read(buf[:]) f.Close() line := buf[:n] if i := bytes.IndexByte(line, '\n'); i >= 0 { line = line[:i] } fields := bytes.Fields(line) if len(fields) < 5 || string(fields[0]) != "cpu" { return 0, 0, errors.New("unexpected /proc/stat") } for i, f := range fields[1:] { if i >= 8 { // guest time is already counted in user/nice break } v, _ := strconv.ParseUint(string(f), 10, 64) total += v if i == 3 || i == 4 { // idle, iowait idle += v } } return idle, total, nil } // memUsage reads /proc/meminfo; used is everything not available to new // processes (so page cache does not count as used). func memUsage() (total, used uint64, err error) { data, err := os.ReadFile("/proc/meminfo") if err != nil { return 0, 0, err } var avail, free uint64 var haveAvail bool for len(data) > 0 { line := data if i := bytes.IndexByte(data, '\n'); i >= 0 { line, data = data[:i], data[i+1:] } else { data = nil } switch { case bytes.HasPrefix(line, []byte("MemTotal:")): total = meminfoKB(line) case bytes.HasPrefix(line, []byte("MemFree:")): free = meminfoKB(line) case bytes.HasPrefix(line, []byte("MemAvailable:")): avail, haveAvail = meminfoKB(line), true } if haveAvail && total > 0 { break } } if total == 0 { return 0, 0, errors.New("unexpected /proc/meminfo") } if !haveAvail { avail = free } if avail > total { avail = total } return total, total - avail, nil } // meminfoKB returns the value of a "Key: 123 kB" line in bytes. func meminfoKB(line []byte) uint64 { f := bytes.Fields(line) if len(f) < 2 { return 0 } v, _ := strconv.ParseUint(string(f[1]), 10, 64) return v * 1024 }