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
This commit is contained in:
1 parent
77d13b07d5
commit
ac0b8e75c0
17 files changed
+1267
No files matched your search
@@ -0,0 +1,122 @@
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user