diff --git a/config.go b/config.go index 1bebda9..a851929 100644 --- a/config.go +++ b/config.go @@ -49,8 +49,10 @@ type SystemMetrics struct { CollectMemory bool `mapstructure:"collect_memory"` CollectDisk bool `mapstructure:"collect_disk"` CollectNetwork bool `mapstructure:"collect_network"` + CollectProcesses bool `mapstructure:"collect_processes"` DiskPaths []string `mapstructure:"disk_paths"` NetworkInterfaces []string `mapstructure:"network_interfaces"` + TopProcessesLimit int `mapstructure:"top_processes_limit"` } type Config struct { diff --git a/models.go b/models.go index 914c871..f1adf9f 100644 --- a/models.go +++ b/models.go @@ -5,17 +5,19 @@ import ( ) type SystemResources struct { - Timestamp time.Time `json:"@timestamp"` - Type string `json:"type"` - Host string `json:"host"` - CPUPercent float64 `json:"cpu_percent,omitempty"` - MemoryUsed uint64 `json:"memory_used,omitempty"` - MemoryTotal uint64 `json:"memory_total,omitempty"` - MemoryPercent float64 `json:"memory_percent,omitempty"` - DiskUsage map[string]DiskUsage `json:"disk_usage,omitempty"` - NetworkStats map[string]NetworkStat `json:"network_stats,omitempty"` - LoadAverage []float64 `json:"load_average,omitempty"` - Uptime uint64 `json:"uptime,omitempty"` + Timestamp time.Time `json:"@timestamp"` + Type string `json:"type"` + Host string `json:"host"` + CPUPercent float64 `json:"cpu_percent,omitempty"` + MemoryUsed uint64 `json:"memory_used,omitempty"` + MemoryTotal uint64 `json:"memory_total,omitempty"` + MemoryPercent float64 `json:"memory_percent,omitempty"` + DiskUsage map[string]DiskUsage `json:"disk_usage,omitempty"` + NetworkStats map[string]NetworkStat `json:"network_stats,omitempty"` + LoadAverage []float64 `json:"load_average,omitempty"` + Uptime uint64 `json:"uptime,omitempty"` + TopProcesses []ProcessInfo `json:"top_processes"` + OpenFileDescriptors int32 `json:"open_file_descriptors"` } type DiskUsage struct { @@ -25,6 +27,15 @@ type DiskUsage struct { Free uint64 `json:"free"` } +type ProcessInfo struct { + PID int32 `json:"pid"` + Name string `json:"name"` + CPUPercent float64 `json:"cpu_percent"` + MemoryMB float32 `json:"memory_mb"` + Status string `json:"status"` + CreateTime int64 `json:"create_time"` +} + type NetworkStat struct { BytesSent uint64 `json:"bytes_sent"` BytesRecv uint64 `json:"bytes_recv"` diff --git a/system_metrics.go b/system_metrics.go index 54cbec0..f73d4e4 100644 --- a/system_metrics.go +++ b/system_metrics.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" "slices" + "sort" "time" "github.com/elastic/go-elasticsearch/v7" @@ -13,6 +14,7 @@ import ( "github.com/shirou/gopsutil/host" "github.com/shirou/gopsutil/mem" "github.com/shirou/gopsutil/net" + "github.com/shirou/gopsutil/process" ) type SystemMetricsCollector struct { @@ -81,9 +83,79 @@ func (smc *SystemMetricsCollector) collectMetrics() (SystemResources, error) { } } + if smc.config.CollectProcesses { + if err := smc.collectProcessMetrics(&result); err != nil { + slog.Warn("failed to collect process metrics", "error", err) + } + } + return result, nil } +func (smc *SystemMetricsCollector) collectProcessMetrics(result *SystemResources) error { + processes, err := process.Processes() + if err != nil { + return err + } + + var processInfos []ProcessInfo + var totalOpenFiles int32 + + for _, p := range processes { + name, err := p.Name() + if err != nil { + continue + } + + cpuPercent, err := p.CPUPercent() + if err != nil { + continue + } + + memInfo, err := p.MemoryInfo() + if err != nil { + continue + } + + status, err := p.Status() + if err != nil { + status = "" + } + + createTime, err := p.CreateTime() + if err != nil { + createTime = 0 + } + + if openFiles, err := p.NumFDs(); err == nil { + totalOpenFiles += openFiles + } + + processInfos = append(processInfos, ProcessInfo{ + PID: p.Pid, + Name: name, + CPUPercent: cpuPercent, + MemoryMB: float32(memInfo.RSS) / 1024 / 1024, + Status: status, + CreateTime: createTime, + }) + } + + sort.Slice(processInfos, func(i, j int) bool { + return processInfos[i].CPUPercent > processInfos[j].CPUPercent + }) + + limit := smc.config.TopProcessesLimit + if len(processInfos) > limit { + processInfos = processInfos[:limit] + } + + result.TopProcesses = processInfos + result.OpenFileDescriptors = totalOpenFiles + + return nil +} + func (smc *SystemMetricsCollector) collectCPUMetrics(result *SystemResources) error { cpuPercents, err := cpu.Percent(time.Second, false) if err != nil {