Files
aiops/agent/collector/cpu.go
T
attacker ac0b8e75c0 feat: add monitoring agent (Go) + agent report API + cloud billing UI
- Go agent: CPU/mem/disk/port collectors, HTTP report, static binary
- Cross-compile: linux/amd64 + linux/arm64 (statically linked)
- One-line install script for all Linux distros
- Backend: POST /api/v1/agent/report (no JWT) + GET /agent/latest,history (JWT)
- CloudBilling.tsx: fixed syntax errors, stats/trends/records/alerts
- K8s menu renamed to Kubernetes管理

Signed-off-by: Logan.Li
2026-06-14 21:27:14 +08:00

123 lines
3.1 KiB
Go

// Package collector 提供服务器各项系统指标的采集能力。
// 本文件实现 CPU 使用率与负载采集,主要通过解析 Linux /proc/stat 与 /proc/loadavg 实现。
//
// 署名 Logan.Li
package collector
import (
"bufio"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"sync"
"ezops/agent/model"
)
// cpuSample 保存某一时刻 CPU 的累计 jiffies,用于两次采样计算差值
type cpuSample struct {
total uint64 // 自系统启动以来 CPU 所有状态的累计 jiffies
idle uint64 // idle + iowait 的累计 jiffies
}
// CPUCollector CPU 指标采集器。
// 通过在两次采集之间保存上一次的采样数据来计算真实使用率。
type CPUCollector struct {
mu sync.Mutex
prev *cpuSample
}
// NewCPUCollector 创建 CPU 采集器实例
func NewCPUCollector() *CPUCollector {
return &CPUCollector{}
}
// Collect 采集当前 CPU 指标。
// 注意:首次调用因缺少上次采样数据,返回的使用率为 0;后续调用返回两次采集之间的平均使用率。
func (c *CPUCollector) Collect() (model.CPUStat, error) {
stat := model.CPUStat{CoreCount: runtime.NumCPU()}
c.mu.Lock()
defer c.mu.Unlock()
cur, err := readCPUSample()
if err != nil {
return stat, fmt.Errorf("读取 CPU 采样失败: %w", err)
}
if c.prev != nil {
totalDelta := cur.total - c.prev.total
idleDelta := cur.idle - c.prev.idle
if totalDelta > 0 {
// 使用率 = (总增量 - 空闲增量) / 总增量 * 100
stat.UsagePercent = float64(totalDelta-idleDelta) / float64(totalDelta) * 100.0
if stat.UsagePercent < 0 {
stat.UsagePercent = 0
}
}
}
c.prev = &cur
// 负载平均值采集失败不影响 CPU 使用率上报
if l1, l5, l15, err := readLoadAvg(); err == nil {
stat.Load1, stat.Load5, stat.Load15 = l1, l5, l15
}
return stat, nil
}
// readCPUSample 读取 /proc/stat 第一行(聚合所有 CPU)并计算 total 与 idle
func readCPUSample() (cpuSample, error) {
var sample cpuSample
f, err := os.Open("/proc/stat")
if err != nil {
return sample, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "cpu ") {
continue
}
// 形如: cpu user nice system idle iowait irq softirq steal guest guest_nice
fields := strings.Fields(line)
if len(fields) < 5 {
return sample, fmt.Errorf("/proc/stat 数据格式异常")
}
// fields[0] == "cpu",之后依次为各类 jiffies
for i, v := range fields[1:] {
x, perr := strconv.ParseUint(v, 10, 64)
if perr != nil {
continue
}
sample.total += x
// idle(索引3) 与 iowait(索引4) 计入空闲
if i == 3 || i == 4 {
sample.idle += x
}
}
return sample, nil
}
if err := scanner.Err(); err != nil {
return sample, err
}
return sample, fmt.Errorf("/proc/stat 中未找到聚合 CPU 行")
}
// readLoadAvg 读取 /proc/loadavg,返回 1/5/15 分钟平均负载
func readLoadAvg() (load1, load5, load15 float64, err error) {
f, err := os.Open("/proc/loadavg")
if err != nil {
return 0, 0, 0, err
}
defer f.Close()
_, err = fmt.Fscanf(f, "%f %f %f", &load1, &load5, &load15)
return load1, load5, load15, err
}