package system import ( "bufio" "fmt" "math" "os" "runtime" "strconv" "strings" "sync" "time" "github.com/vishvananda/netlink" ) type SystemStatus struct { Hostname string `json:"hostname"` OS string `json:"os"` Kernel string `json:"kernel"` UptimeSeconds float64 `json:"uptime_seconds"` UptimeHuman string `json:"uptime_human"` CPUPct float64 `json:"cpu_pct"` // Actual container CPU utilization % (0-100) CPULoad []string `json:"cpu_load"` // 1m, 5m, 15m host load average CPUCores int `json:"cpu_cores"` MemTotalMB uint64 `json:"mem_total_mb"` MemUsedMB uint64 `json:"mem_used_mb"` MemUsagePct float64 `json:"mem_usage_pct"` TotalIfaces int `json:"total_ifaces"` UpIfaces int `json:"up_ifaces"` DownIfaces int `json:"down_ifaces"` VlanIfaces int `json:"vlan_ifaces"` DefaultGateway string `json:"default_gateway"` } type RouteInfo struct { Destination string `json:"destination"` Gateway string `json:"gateway"` Interface string `json:"interface"` Protocol int `json:"protocol"` Scope int `json:"scope"` } // GetStatus returns system-level metrics and router telemetry. func GetStatus() (*SystemStatus, error) { hostname, _ := os.Hostname() osName := "Debian Linux" if f, err := os.Open("/etc/os-release"); err == nil { scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "PRETTY_NAME=") { osName = strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"") break } } _ = f.Close() } kernel := "Linux" if data, err := os.ReadFile("/proc/sys/kernel/osrelease"); err == nil { kernel = strings.TrimSpace(string(data)) } uptimeSec, uptimeHuman := getUptime() cpuLoads := getCPULoad() cpuPct := getContainerCPUUsage() memTotal, memUsed, memPct := getMemory() defGateway := getDefaultGateway() return &SystemStatus{ Hostname: hostname, OS: osName, Kernel: kernel, UptimeSeconds: uptimeSec, UptimeHuman: uptimeHuman, CPUPct: cpuPct, CPULoad: cpuLoads, CPUCores: runtime.NumCPU(), MemTotalMB: memTotal, MemUsedMB: memUsed, MemUsagePct: memPct, DefaultGateway: defGateway, }, nil } var ( lastUsageUsec uint64 lastUsageTime time.Time lastUsageMu sync.Mutex containerCPUPct float64 ) func getContainerCPUUsage() float64 { lastUsageMu.Lock() defer lastUsageMu.Unlock() // Measure container CPU utilization via cgroup v2 if f, err := os.Open("/sys/fs/cgroup/cpu.stat"); err == nil { scanner := bufio.NewScanner(f) var usageUsec uint64 for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "usage_usec ") { parts := strings.Fields(line) if len(parts) >= 2 { usageUsec, _ = strconv.ParseUint(parts[1], 10, 64) } break } } _ = f.Close() now := time.Now() if lastUsageTime.IsZero() { lastUsageUsec = usageUsec lastUsageTime = now return 0.5 } elapsedSec := now.Sub(lastUsageTime).Seconds() if elapsedSec >= 0.5 && usageUsec >= lastUsageUsec { deltaUsec := float64(usageUsec - lastUsageUsec) pct := (deltaUsec / (elapsedSec * 1000000.0)) * 100.0 if pct < 0 { pct = 0 } if pct > 100 { pct = 100 } containerCPUPct = math.Round(pct*10) / 10 lastUsageUsec = usageUsec lastUsageTime = now } return containerCPUPct } return 0.5 } func getUptime() (float64, string) { data, err := os.ReadFile("/proc/uptime") if err != nil { return 0, "Unknown" } parts := strings.Fields(string(data)) if len(parts) == 0 { return 0, "Unknown" } sec, _ := strconv.ParseFloat(parts[0], 64) days := int(sec) / 86400 hours := (int(sec) % 86400) / 3600 minutes := (int(sec) % 3600) / 60 if days > 0 { return sec, fmt.Sprintf("%dd %dh %dm", days, hours, minutes) } return sec, fmt.Sprintf("%dh %dm", hours, minutes) } func getCPULoad() []string { data, err := os.ReadFile("/proc/loadavg") if err != nil { return []string{"0.00", "0.00", "0.00"} } parts := strings.Fields(string(data)) if len(parts) >= 3 { return parts[:3] } return []string{"0.00", "0.00", "0.00"} } func getMemory() (totalMB uint64, usedMB uint64, pct float64) { f, err := os.Open("/proc/meminfo") if err != nil { return 0, 0, 0 } defer f.Close() var total, available uint64 scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "MemTotal:") { parts := strings.Fields(line) if len(parts) >= 2 { total, _ = strconv.ParseUint(parts[1], 10, 64) } } else if strings.HasPrefix(line, "MemAvailable:") { parts := strings.Fields(line) if len(parts) >= 2 { available, _ = strconv.ParseUint(parts[1], 10, 64) } } } totalMB = total / 1024 usedMB = (total - available) / 1024 if total > 0 { pct = (float64(total-available) / float64(total)) * 100.0 } return } func getDefaultGateway() string { routes, err := netlink.RouteList(nil, netlink.FAMILY_V4) if err != nil { return "" } for _, r := range routes { if r.Dst == nil && r.Gw != nil { return r.Gw.String() } } return "" } // GetRoutingTable returns the list of active routes. func GetRoutingTable() ([]RouteInfo, error) { routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) if err != nil { return nil, err } links, _ := netlink.LinkList() linkMap := make(map[int]string) for _, l := range links { linkMap[l.Attrs().Index] = l.Attrs().Name } result := make([]RouteInfo, 0) for _, r := range routes { dst := "default" if r.Dst != nil { dst = r.Dst.String() } gw := "" if r.Gw != nil { gw = r.Gw.String() } result = append(result, RouteInfo{ Destination: dst, Gateway: gw, Interface: linkMap[r.LinkIndex], Protocol: int(r.Protocol), Scope: int(r.Scope), }) } return result, nil }