106 lines
2.1 KiB
Go
106 lines
2.1 KiB
Go
|
|
package cpu
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"os"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"bugs.watch/agent/internal/aggregate"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Sample struct {
|
||
|
|
Idle uint64
|
||
|
|
Total uint64
|
||
|
|
}
|
||
|
|
|
||
|
|
// ReadSample reads CPU counters from /proc/stat.
|
||
|
|
// It returns cumulative counters since boot.
|
||
|
|
func ReadSample() (Sample, error) {
|
||
|
|
f, err := os.Open("/proc/stat")
|
||
|
|
if err != nil {
|
||
|
|
return Sample{}, err
|
||
|
|
}
|
||
|
|
defer f.Close()
|
||
|
|
|
||
|
|
scanner := bufio.NewScanner(f)
|
||
|
|
if !scanner.Scan() {
|
||
|
|
if err := scanner.Err(); err != nil {
|
||
|
|
return Sample{}, err
|
||
|
|
}
|
||
|
|
return Sample{}, strconv.ErrSyntax
|
||
|
|
}
|
||
|
|
|
||
|
|
// First line:
|
||
|
|
// cpu user nice system idle iowait irq softirq steal guest guest_nice
|
||
|
|
fields := strings.Fields(scanner.Text())
|
||
|
|
if len(fields) < 5 || fields[0] != "cpu" {
|
||
|
|
return Sample{}, strconv.ErrSyntax
|
||
|
|
}
|
||
|
|
|
||
|
|
var total uint64
|
||
|
|
for i := 1; i < len(fields); i++ {
|
||
|
|
v, err := strconv.ParseUint(fields[i], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
return Sample{}, err
|
||
|
|
}
|
||
|
|
total += v
|
||
|
|
}
|
||
|
|
|
||
|
|
idle, err := strconv.ParseUint(fields[4], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
return Sample{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return Sample{Idle: idle, Total: total}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// UsagePercent computes CPU usage percentage between two samples.
|
||
|
|
func UsagePercent(prev, curr Sample) float64 {
|
||
|
|
// Guard against counter resets or weirdness
|
||
|
|
if curr.Total <= prev.Total || curr.Idle < prev.Idle {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
idleDelta := curr.Idle - prev.Idle
|
||
|
|
totalDelta := curr.Total - prev.Total
|
||
|
|
if totalDelta == 0 {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
usedDelta := totalDelta - idleDelta
|
||
|
|
return (float64(usedDelta) / float64(totalDelta)) * 100.0
|
||
|
|
}
|
||
|
|
|
||
|
|
// Collector implements a stateful CPU usage collector.
|
||
|
|
// It stores the previous sample internally so the agent does not have to.
|
||
|
|
type Collector struct {
|
||
|
|
prev Sample
|
||
|
|
hasPrev bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func New() *Collector {
|
||
|
|
return &Collector{}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Collector) Name() string {
|
||
|
|
return "cpu"
|
||
|
|
}
|
||
|
|
|
||
|
|
// Collect reads current CPU counters and, if possible, emits a usage percent.
|
||
|
|
// On first run it will not emit (no previous sample yet).
|
||
|
|
func (c *Collector) Collect(window *aggregate.Window) {
|
||
|
|
curr, err := ReadSample()
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if c.hasPrev {
|
||
|
|
usage := UsagePercent(c.prev, curr)
|
||
|
|
window.AddCPU(usage)
|
||
|
|
}
|
||
|
|
|
||
|
|
c.prev = curr
|
||
|
|
c.hasPrev = true
|
||
|
|
}
|