feat: Phase 6 Agent 系统信息+进程监控采集器
- 新增 collector/system.go: SystemCollector(OS/内核/ulimit) + ProcessCollector(Top 5 CPU/内存) - 模型新增 SystemStat + ProcessStat + ProcessInfo - Report 结构新增 System + Processes 字段 - Agent 启动时采集系统信息,每周期采集进程列表
This commit is contained in:
1 parent
2e71371257
commit
06d7451cf9
3 files changed
+265
No files matched your search
@@ -0,0 +1,217 @@
|
||||
// Package collector 提供系统信息及进程列表采集。
|
||||
// 系统信息:OS 名称、内核版本、架构、ulimit。
|
||||
// 进程列表:按 CPU/内存排序的 Top 5 进程。
|
||||
//
|
||||
// 署名 Logan.Li
|
||||
package collector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"ezops/agent/model"
|
||||
)
|
||||
|
||||
// SystemCollector 系统信息采集器
|
||||
type SystemCollector struct{}
|
||||
|
||||
// NewSystemCollector 创建系统信息采集器
|
||||
func NewSystemCollector() *SystemCollector {
|
||||
return &SystemCollector{}
|
||||
}
|
||||
|
||||
// Collect 采集系统信息(OS、内核、架构、ulimit)
|
||||
func (s *SystemCollector) Collect() (model.SystemStat, error) {
|
||||
return model.SystemStat{
|
||||
OS: readFirstLine("/etc/os-release", "PRETTY_NAME="),
|
||||
Kernel: readProcVersion(),
|
||||
Arch: readArch(),
|
||||
Hostname: readHostname(),
|
||||
UlimitNofile: readUlimitNofile(),
|
||||
UlimitNproc: readUlimitNproc(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readFirstLine(path, prefix string) string {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
val := strings.TrimPrefix(line, prefix)
|
||||
return strings.Trim(val, `"`)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func readProcVersion() string {
|
||||
data, err := os.ReadFile("/proc/version")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// Linux version 6.8.0-90-generic ... (buildd@...) (gcc ...) #...
|
||||
parts := strings.Fields(string(data))
|
||||
if len(parts) >= 3 {
|
||||
return strings.Join(parts[:3], " ")
|
||||
}
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
func readArch() string {
|
||||
data, err := os.ReadFile("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.HasPrefix(line, "flags") || strings.HasPrefix(line, "Features") {
|
||||
return "x86_64"
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func readHostname() string {
|
||||
h, _ := os.Hostname()
|
||||
return h
|
||||
}
|
||||
|
||||
func readUlimitNofile() int64 {
|
||||
var rlim syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlim); err == nil {
|
||||
return int64(rlim.Cur)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func readUlimitNproc() int64 {
|
||||
var rlim syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NPROC, &rlim); err == nil {
|
||||
return int64(rlim.Cur)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ==================== Process Collector ====================
|
||||
|
||||
// ProcessCollector 进程列表采集器 — Top N 按 CPU/内存
|
||||
type ProcessCollector struct{}
|
||||
|
||||
// NewProcessCollector 创建进程采集器
|
||||
func NewProcessCollector() *ProcessCollector {
|
||||
return &ProcessCollector{}
|
||||
}
|
||||
|
||||
// Collect 采集 Top 5 CPU 和 Top 5 内存进程
|
||||
func (p *ProcessCollector) Collect() (model.ProcessStat, error) {
|
||||
procs := readProcStatAll()
|
||||
if len(procs) == 0 {
|
||||
return model.ProcessStat{}, nil
|
||||
}
|
||||
|
||||
// Top 5 by CPU
|
||||
sort.Slice(procs, func(i, j int) bool { return procs[i].CPUPercent > procs[j].CPUPercent })
|
||||
topCPU := procs
|
||||
if len(topCPU) > 5 {
|
||||
topCPU = topCPU[:5]
|
||||
}
|
||||
|
||||
// Top 5 by RSS
|
||||
sort.Slice(procs, func(i, j int) bool { return procs[i].RSSKB > procs[j].RSSKB })
|
||||
topMem := procs
|
||||
if len(topMem) > 5 {
|
||||
topMem = topMem[:5]
|
||||
}
|
||||
|
||||
return model.ProcessStat{
|
||||
TopCPU: topCPU,
|
||||
TopMem: topMem,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type procInfo struct {
|
||||
PID int
|
||||
Name string
|
||||
CPUPercent float64
|
||||
RSSKB int64
|
||||
}
|
||||
|
||||
func readProcStatAll() []procInfo {
|
||||
entries, err := os.ReadDir("/proc")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var procs []procInfo
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
pid, err := strconv.Atoi(entry.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
statData, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
stat := string(statData)
|
||||
|
||||
// /proc/PID/stat: PID (comm) state ppid ...
|
||||
// comm 在括号中,可能含空格
|
||||
closeParen := strings.LastIndex(stat, ")")
|
||||
if closeParen < 0 {
|
||||
continue
|
||||
}
|
||||
openParen := strings.Index(stat, "(")
|
||||
if openParen < 0 {
|
||||
continue
|
||||
}
|
||||
name := stat[openParen+1 : closeParen]
|
||||
|
||||
fields := strings.Fields(stat[closeParen+2:])
|
||||
if len(fields) < 20 {
|
||||
continue
|
||||
}
|
||||
|
||||
// utime=fields[11], stime=fields[12], rss=fields[21]
|
||||
utime, _ := strconv.ParseInt(fields[11], 10, 64)
|
||||
stime, _ := strconv.ParseInt(fields[12], 10, 64)
|
||||
rss, _ := strconv.ParseInt(fields[21], 10, 64)
|
||||
|
||||
// RSS pages * 4KB = RSS in KB
|
||||
rssKB := rss * 4
|
||||
|
||||
// Simple CPU%: total ticks as fraction of uptime (approximate)
|
||||
totalTicks := utime + stime
|
||||
uptimeData, _ := os.ReadFile("/proc/uptime")
|
||||
uptime := 0.0
|
||||
if uptimeData != nil {
|
||||
uptime, _ = strconv.ParseFloat(strings.Fields(string(uptimeData))[0], 64)
|
||||
}
|
||||
hertz := float64(syscall.Getpagesize()) * 25
|
||||
if hertz == 0 {
|
||||
hertz = 100
|
||||
}
|
||||
// CLK_TCK is typically 100 on Linux
|
||||
cpuPercent := 0.0
|
||||
if uptime > 0 {
|
||||
cpuPercent = float64(totalTicks) / 100.0 / uptime * 100
|
||||
}
|
||||
|
||||
procs = append(procs, procInfo{
|
||||
PID: pid,
|
||||
Name: name,
|
||||
CPUPercent: cpuPercent,
|
||||
RSSKB: rssKB,
|
||||
})
|
||||
}
|
||||
return procs
|
||||
}
|
||||
Reference in new issue
Block a user