package cmd import ( "fmt" "os" "os/exec" "runtime" "strconv" "strings" "syscall" "time" "github.com/zemenawi/zutils/pkg/colors" "github.com/zemenawi/zutils/pkg/formatter" ) type SystemStats struct { Hostname string OS string Platform string CPUModel string CPUCount int Uptime string LoadAvg string MemTotal int64 MemUsed int64 MemFree int64 DiskTotal int64 DiskUsed int64 DiskFree int64 TopProcs []ProcInfo } type ProcInfo struct { PID int Name string CPU float64 Mem float64 Command string } func SystemCommand(args []string) error { stats, err := getSystemStats() if err != nil { return fmt.Errorf("error getting system stats: %v", err) } PrintBoxHeader("SYSTEM OVERVIEW", colors.Cyan) fmt.Printf("%s%sšŸ  Hostname:%s %s\n", colors.Blue, colors.Bold, colors.Reset, stats.Hostname) fmt.Printf("%s%sšŸ’» OS:%s %s\n", colors.Blue, colors.Bold, colors.Reset, stats.OS) fmt.Printf("%s%sšŸ”§ Platform:%s %s\n", colors.Blue, colors.Bold, colors.Reset, stats.Platform) fmt.Println() PrintSectionHeader("CPU") fmt.Printf("%s%sšŸ–„ļø Model:%s %s\n", colors.Green, colors.Bold, colors.Reset, stats.CPUModel) fmt.Printf("%s%sšŸ”¢ Cores:%s %d\n", colors.Green, colors.Bold, colors.Reset, stats.CPUCount) fmt.Printf("%s%sšŸ“Š Load Avg:%s %s\n", colors.Green, colors.Bold, colors.Reset, stats.LoadAvg) fmt.Printf("%s%sā±ļø Uptime:%s %s\n", colors.Green, colors.Bold, colors.Reset, stats.Uptime) fmt.Println() PrintSectionHeader("MEMORY") memTotal := formatter.FormatSize(stats.MemTotal) memUsed := formatter.FormatSize(stats.MemUsed) memFree := formatter.FormatSize(stats.MemFree) memPct := float64(stats.MemUsed) / float64(stats.MemTotal) * 100 fmt.Printf("%s%sšŸ’¾ Total:%s %s\n", colors.Purple, colors.Bold, colors.Reset, memTotal) fmt.Printf("%s%sšŸ“ˆ Used:%s %s (%.1f%%)\n", colors.Purple, colors.Bold, colors.Reset, memUsed, memPct) fmt.Printf("%s%sšŸ“‰ Free:%s %s\n", colors.Purple, colors.Bold, colors.Reset, memFree) printMemoryBar(stats.MemUsed, stats.MemTotal) fmt.Println() PrintSectionHeader("DISK") diskTotal := formatter.FormatSize(stats.DiskTotal) diskUsed := formatter.FormatSize(stats.DiskUsed) diskFree := formatter.FormatSize(stats.DiskFree) diskPct := float64(stats.DiskUsed) / float64(stats.DiskTotal) * 100 fmt.Printf("%s%sšŸ’½ Total:%s %s\n", colors.Yellow, colors.Bold, colors.Reset, diskTotal) fmt.Printf("%s%sšŸ“ˆ Used:%s %s (%.1f%%)\n", colors.Yellow, colors.Bold, colors.Reset, diskUsed, diskPct) fmt.Printf("%s%sšŸ“‰ Free:%s %s\n", colors.Yellow, colors.Bold, colors.Reset, diskFree) printDiskBar(stats.DiskUsed, stats.DiskTotal) fmt.Println() if len(stats.TopProcs) > 0 { PrintSectionHeader("TOP PROCESSES") headers := []string{"PID", "Name", "CPU%", "MEM%"} data := make([][]string, 0, len(stats.TopProcs)) for _, proc := range stats.TopProcs { row := []string{ fmt.Sprintf("%d", proc.PID), truncateString(proc.Name, 20), fmt.Sprintf("%.1f", proc.CPU), fmt.Sprintf("%.1f", proc.Mem), } data = append(data, row) } formatter.PrintTable(headers, data, colors.Cyan) fmt.Println() } return nil } func printMemoryBar(used, total int64) { width := 30 if total == 0 { return } fillLen := int(float64(used) / float64(total) * float64(width)) bar := "[" for i := 0; i < width; i++ { if i < fillLen { if i < width/3 { bar += colors.Green + "ā–ˆ" + colors.Reset } else if i < 2*width/3 { bar += colors.Yellow + "ā–ˆ" + colors.Reset } else { bar += colors.Red + "ā–ˆ" + colors.Reset } } else { bar += colors.Gray + "ā–‘" + colors.Reset } } bar += "]" fmt.Printf("%s%sMemory Bar:%s %s\n", colors.Gray, colors.Bold, colors.Reset, bar) } func printDiskBar(used, total int64) { width := 30 if total == 0 { return } fillLen := int(float64(used) / float64(total) * float64(width)) bar := "[" for i := 0; i < width; i++ { if i < fillLen { if i < width/3 { bar += colors.Green + "ā–ˆ" + colors.Reset } else if i < 2*width/3 { bar += colors.Yellow + "ā–ˆ" + colors.Reset } else { bar += colors.Red + "ā–ˆ" + colors.Reset } } else { bar += colors.Gray + "ā–‘" + colors.Reset } } bar += "]" fmt.Printf("%s%sDisk Bar:%s %s\n", colors.Gray, colors.Bold, colors.Reset, bar) } func getSystemStats() (*SystemStats, error) { stats := &SystemStats{} stats.Hostname, _ = os.Hostname() stats.OS = runtime.GOOS stats.Platform = runtime.GOARCH stats.CPUModel = getCPUModel() stats.CPUCount = runtime.NumCPU() stats.Uptime = getUptime() stats.LoadAvg = getLoadAvg() getMemoryStats(stats) getDiskStats(stats) stats.TopProcs = getTopProcs(5) return stats, nil } func getCPUModel() string { if runtime.GOOS == "linux" { if data, err := os.ReadFile("/proc/cpuinfo"); err == nil { lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "model name") || strings.HasPrefix(line, "Processor") || strings.HasPrefix(line, "cpu model") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { return strings.TrimSpace(parts[1]) } } } } } return "Unknown" } func getUptime() string { if runtime.GOOS == "linux" { if data, err := os.ReadFile("/proc/uptime"); err == nil { parts := strings.Split(string(data), " ") if len(parts) >= 1 { if secs, err := strconv.ParseFloat(strings.TrimSpace(parts[0]), 64); err == nil { duration := time.Duration(int64(secs)) * time.Second return formatDuration(duration) } } } } return "Unknown" } func formatDuration(d time.Duration) string { days := int(d.Hours() / 24) hours := int(d.Hours()) % 24 minutes := int(d.Minutes()) % 60 if days > 0 { return fmt.Sprintf("%dd %dh %dm", days, hours, minutes) } if hours > 0 { return fmt.Sprintf("%dh %dm", hours, minutes) } return fmt.Sprintf("%dm", minutes) } func getLoadAvg() string { if runtime.GOOS == "linux" { if data, err := os.ReadFile("/proc/loadavg"); err == nil { parts := strings.Split(strings.TrimSpace(string(data)), " ") if len(parts) >= 3 { return fmt.Sprintf("%.2f %.2f %.2f", parseFloatSafe(parts[0]), parseFloatSafe(parts[1]), parseFloatSafe(parts[2])) } } } return "N/A" } func parseFloatSafe(s string) float64 { if f, err := strconv.ParseFloat(s, 64); err == nil { return f } return 0 } func getMemoryStats(stats *SystemStats) { if runtime.GOOS == "linux" { if data, err := os.ReadFile("/proc/meminfo"); err == nil { lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "MemTotal") { stats.MemTotal = parseMeminfoLine(line) } else if strings.HasPrefix(line, "MemAvailable") || strings.HasPrefix(line, "MemFree") { if stats.MemFree == 0 { stats.MemFree = parseMeminfoLine(line) } } } if stats.MemFree > 0 && stats.MemTotal > 0 { stats.MemUsed = stats.MemTotal - stats.MemFree } } } } func parseMeminfoLine(line string) int64 { parts := strings.Fields(line) if len(parts) >= 2 { if val, err := strconv.ParseInt(parts[1], 10, 64); err == nil { return val * 1024 } } return 0 } func getDiskStats(stats *SystemStats) { if runtime.GOOS == "linux" { output, err := exec.Command("df", "-k", "/").Output() if err == nil { lines := strings.Split(string(output), "\n") if len(lines) >= 2 { parts := strings.Fields(lines[1]) if len(parts) >= 4 { stats.DiskTotal, _ = strconv.ParseInt(parts[1], 10, 64) stats.DiskUsed, _ = strconv.ParseInt(parts[2], 10, 64) stats.DiskFree, _ = strconv.ParseInt(parts[3], 10, 64) stats.DiskTotal *= 1024 stats.DiskUsed *= 1024 stats.DiskFree *= 1024 } } } } } func getTopProcs(count int) []ProcInfo { var procs []ProcInfo if runtime.GOOS == "linux" { dir, err := os.Open("/proc") if err != nil { return procs } defer dir.Close() entries, err := dir.Readdirnames(count * 3) if err != nil { return procs } for _, entry := range entries { pid, err := strconv.Atoi(entry) if err != nil { continue } stat, err := readProcStat(pid) if err != nil { continue } procs = append(procs, stat) if len(procs) >= count { break } } } return procs } func readProcStat(pid int) (ProcInfo, error) { data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) if err != nil { return ProcInfo{}, err } parts := strings.SplitN(string(data), " ", 4) if len(parts) < 4 { return ProcInfo{}, fmt.Errorf("invalid format") } name := parts[1] name = strings.Trim(name, "(") name = strings.TrimRight(name, ")") comm, _ := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid)) commName := strings.TrimSpace(string(comm)) var rusage syscall.Rusage syscall.Getrusage(syscall.RUSAGE_SELF, &rusage) return ProcInfo{ PID: pid, Name: commName, CPU: 0, Mem: 0, Command: name, }, nil } func truncateString(s string, maxLen int) string { if len(s) <= maxLen { return s } return s[:maxLen-3] + "..." }