package statcollector import ( "log/slog" "os" "strings" "time" "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/disk" "github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/mem" psnet "github.com/shirou/gopsutil/v3/net" "github.com/shirou/gopsutil/v3/process" ) // SystemInfo represents overall system information type SystemInfo struct { Hostname string Uptime time.Duration MemoryTotal uint64 MemoryUsed uint64 MemoryFree uint64 CPUPercent []float64 Temperature map[string]float64 DiskUsage []DiskInfo Network []NetworkInfo Processes []ProcessInfo CollectedAt time.Time } // DiskInfo represents disk usage information type DiskInfo struct { Path string Total uint64 Used uint64 Free uint64 UsedPercent float64 } // NetworkInfo represents network interface information type NetworkInfo struct { Name string IPAddresses []string LinkSpeed uint64 BytesSent uint64 BytesRecv uint64 PacketsSent uint64 PacketsRecv uint64 } // ProcessInfo represents process information type ProcessInfo struct { PID int32 Name string CPUPercent float64 MemoryRSS uint64 MemoryVMS uint64 Username string } // Collector interface for collecting system information type Collector interface { Collect() (*SystemInfo, error) } // SystemCollector implements Collector type SystemCollector struct { logger *slog.Logger lastNetStats map[string]psnet.IOCountersStat lastCollectTime time.Time } // NewSystemCollector creates a new system collector func NewSystemCollector(logger *slog.Logger) *SystemCollector { return &SystemCollector{ logger: logger, lastNetStats: make(map[string]psnet.IOCountersStat), } } // Collect gathers system information func (c *SystemCollector) Collect() (*SystemInfo, error) { info := &SystemInfo{ CollectedAt: time.Now(), Temperature: make(map[string]float64), } // Hostname hostname, err := os.Hostname() if err != nil { c.logger.Warn("getting hostname", "error", err) info.Hostname = "unknown" } else { info.Hostname = hostname } // Uptime uptimeSecs, err := host.Uptime() if err != nil { c.logger.Warn("getting uptime", "error", err) } else { info.Uptime = time.Duration(uptimeSecs) * time.Second } // Memory vmStat, err := mem.VirtualMemory() if err != nil { c.logger.Warn("getting memory stats", "error", err) } else { info.MemoryTotal = vmStat.Total info.MemoryUsed = vmStat.Used info.MemoryFree = vmStat.Available } // CPU cpuPercent, err := cpu.Percent(time.Second, true) if err != nil { c.logger.Warn("getting cpu percent", "error", err) } else { info.CPUPercent = cpuPercent } // Temperature temps, err := host.SensorsTemperatures() if err != nil { c.logger.Warn("getting temperatures", "error", err) } else { for _, temp := range temps { if temp.Temperature > 0 { info.Temperature[temp.SensorKey] = temp.Temperature } } } // Disk usage partitions, err := disk.Partitions(false) if err != nil { c.logger.Warn("getting partitions", "error", err) } else { for _, partition := range partitions { if strings.HasPrefix(partition.Mountpoint, "/dev") || strings.HasPrefix(partition.Mountpoint, "/sys") || strings.HasPrefix(partition.Mountpoint, "/proc") { continue } usage, err := disk.Usage(partition.Mountpoint) if err != nil { continue } info.DiskUsage = append(info.DiskUsage, DiskInfo{ Path: partition.Mountpoint, Total: usage.Total, Used: usage.Used, Free: usage.Free, UsedPercent: usage.UsedPercent, }) } } // Network interfaces, err := psnet.Interfaces() if err != nil { c.logger.Warn("getting network interfaces", "error", err) } else { ioCounters, _ := psnet.IOCounters(true) ioMap := make(map[string]psnet.IOCountersStat) for _, counter := range ioCounters { ioMap[counter.Name] = counter } for _, iface := range interfaces { if iface.Name == "lo" || strings.HasPrefix(iface.Name, "docker") { continue } netInfo := NetworkInfo{ Name: iface.Name, } // Get IP addresses for _, addr := range iface.Addrs { netInfo.IPAddresses = append(netInfo.IPAddresses, addr.Addr) } // Get stats if stats, ok := ioMap[iface.Name]; ok { netInfo.BytesSent = stats.BytesSent netInfo.BytesRecv = stats.BytesRecv netInfo.PacketsSent = stats.PacketsSent netInfo.PacketsRecv = stats.PacketsRecv } info.Network = append(info.Network, netInfo) } } // Processes processes, err := process.Processes() if err != nil { c.logger.Warn("getting processes", "error", err) } else { for _, p := range processes { name, _ := p.Name() cpuPercent, _ := p.CPUPercent() memInfo, _ := p.MemoryInfo() username, _ := p.Username() info.Processes = append(info.Processes, ProcessInfo{ PID: p.Pid, Name: name, CPUPercent: cpuPercent, MemoryRSS: memInfo.RSS, MemoryVMS: memInfo.VMS, Username: username, }) } } c.lastCollectTime = time.Now() return info, nil }