- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
136 lines
3.5 KiB
Go
136 lines
3.5 KiB
Go
//go:build !windows
|
|
|
|
// 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
|
|
iowait uint64 // iowait 单独累计 jiffies(用于计算 iowait 占比)
|
|
}
|
|
|
|
// 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
|
|
iowaitDelta := cur.iowait - c.prev.iowait
|
|
if totalDelta > 0 {
|
|
// 使用率 = (总增量 - 空闲增量) / 总增量 * 100
|
|
stat.UsagePercent = float64(totalDelta-idleDelta) / float64(totalDelta) * 100.0
|
|
if stat.UsagePercent < 0 {
|
|
stat.UsagePercent = 0
|
|
}
|
|
// iowait = iowait增量 / 总增量 * 100
|
|
stat.IOWait = float64(iowaitDelta) / float64(totalDelta) * 100.0
|
|
if stat.IOWait < 0 {
|
|
stat.IOWait = 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
|
|
}
|
|
// iowait(索引4) 单独记录
|
|
if i == 4 {
|
|
sample.iowait += 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
|
|
}
|