//go:build !windows package api import ( "fmt" "os" "strings" "time" ) // collectSystemMetrics reads /proc on Linux. On macOS / BSD this returns // zeroes for files that don't exist — the dashboard panel renders blanks // rather than crashing. macOS-specific metrics could be added later via // `vm_stat` / `iostat` parsing. func collectSystemMetrics() sysMetrics { m := sysMetrics{} // CPU from /proc/stat if data, err := os.ReadFile("/proc/stat"); err == nil { line := strings.Split(string(data), "\n")[0] fields := strings.Fields(line) if len(fields) >= 5 { var idle, total float64 for i := 1; i < len(fields) && i <= 4; i++ { var v float64 fmt.Sscanf(fields[i], "%f", &v) total += v if i == 4 { idle = v } } if lastCPUSet { dIdle := idle - lastCPU[0] dTotal := total - lastCPU[1] if dTotal > 0 { m.CPUPercent = (1 - dIdle/dTotal) * 100 } } lastCPU = [2]float64{idle, total} lastCPUSet = true } } // Memory from /proc/meminfo if data, err := os.ReadFile("/proc/meminfo"); err == nil { var memTotal, memAvailable float64 for _, line := range strings.Split(string(data), "\n") { fields := strings.Fields(line) if len(fields) < 2 { continue } var v float64 fmt.Sscanf(fields[1], "%f", &v) switch fields[0] { case "MemTotal:": memTotal = v case "MemAvailable:": memAvailable = v } } if memTotal > 0 { m.MemTotalMB = memTotal / 1024 m.MemUsedMB = (memTotal - memAvailable) / 1024 m.MemPercent = (memTotal - memAvailable) / memTotal * 100 } } // Network from /proc/net/dev if data, err := os.ReadFile("/proc/net/dev"); err == nil { var rxBytes, txBytes float64 for _, line := range strings.Split(string(data), "\n")[2:] { fields := strings.Fields(line) if len(fields) < 10 { continue } iface := strings.TrimSuffix(fields[0], ":") if iface == "lo" { continue } var rx, tx float64 fmt.Sscanf(fields[1], "%f", &rx) fmt.Sscanf(fields[9], "%f", &tx) rxBytes += rx txBytes += tx } now := time.Now() if !lastNetTs.IsZero() { elapsed := now.Sub(lastNetTs).Seconds() if elapsed > 0 { m.NetRxKBs = (rxBytes - lastNet[0]) / 1024 / elapsed m.NetTxKBs = (txBytes - lastNet[1]) / 1024 / elapsed if m.NetRxKBs < 0 { m.NetRxKBs = 0 } if m.NetTxKBs < 0 { m.NetTxKBs = 0 } } } lastNet = [2]float64{rxBytes, txBytes} lastNetTs = now } return m }