// 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 }