package cmd import ( "fmt" "os" "path/filepath" "sort" "strconv" "strings" "github.com/zemenawi/zutils/pkg/colors" "github.com/zemenawi/zutils/pkg/formatter" ) type ProcessInfo struct { PID int Name string Command string CPUPercent float64 MemBytes int64 MemPercent float64 Status string User string } func UsageCommand(args []string) error { if len(args) < 1 { return fmt.Errorf("missing process name\n\nUsage: z usage \n\nExamples:\n z usage cron # Show cron process usage\n z usage mongod # Show MongoDB usage\n z usage chrome # Show all Chrome processes") } processName := args[0] processes := findProcessesByName(processName) if len(processes) == 0 { fmt.Printf("\n %sNo processes found matching '%s'%s\n\n", colors.Yellow, processName, colors.Reset) fmt.Printf(" Try a different process name, or use %sz sys%s to see all processes\n\n", colors.Gray, colors.Reset) return nil } PrintBoxHeader(fmt.Sprintf("USAGE: %s (%d processes)", processName, len(processes)), colors.Cyan) totalCPU := 0.0 totalMem := int64(0) for _, p := range processes { totalCPU += p.CPUPercent totalMem += p.MemBytes } fmt.Printf("%s%s📊 Total CPU:%s %.1f%%\n", colors.Green, colors.Bold, colors.Reset, totalCPU) fmt.Printf("%s%s💾 Total Memory:%s %s (%.1f MB)\n", colors.Purple, colors.Bold, colors.Reset, formatter.FormatSize(totalMem), float64(totalMem)/1024/1024) fmt.Println() headers := []string{"PID", "Process", "CPU%", "Memory", "Status"} data := make([][]string, 0, len(processes)) sort.Slice(processes, func(i, j int) bool { return processes[i].CPUPercent > processes[j].CPUPercent }) for _, p := range processes { cmd := p.Command if len(cmd) > 30 { parts := strings.Fields(cmd) if len(parts) > 0 { cmd = filepath.Base(parts[0]) + " " + strings.Join(parts[1:], " ")[:25] } } status := p.Status if status == "R" { status = colors.Green + "running" + colors.Reset } else if status == "S" || status == "I" { status = colors.Gray + "sleeping" + colors.Reset } else { status = colors.Yellow + status + colors.Reset } row := []string{ fmt.Sprintf("%d", p.PID), cmd, fmt.Sprintf("%.1f", p.CPUPercent), formatter.FormatSize(p.MemBytes), status, } data = append(data, row) } formatter.PrintTable(headers, data, colors.Cyan) fmt.Println() return nil } func findProcessesByName(name string) []ProcessInfo { var processes []ProcessInfo dir, err := os.Open("/proc") if err != nil { return processes } defer dir.Close() entries, err := dir.Readdirnames(500) if err != nil { return processes } for _, entry := range entries { pid, err := strconv.Atoi(entry) if err != nil { continue } proc, err := getProcessInfo(pid, name) if err == nil && proc != nil { processes = append(processes, *proc) } } return processes } func getProcessInfo(pid int, targetName string) (*ProcessInfo, error) { comm, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid)) if err != nil { return nil, err } procName := strings.TrimSpace(string(comm)) if !strings.Contains(strings.ToLower(procName), strings.ToLower(targetName)) { if !strings.Contains(strings.ToLower(procName), strings.ToLower(targetName)) { cmdline, _ := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) if cmdline != nil && !strings.Contains(strings.ToLower(string(cmdline)), strings.ToLower(targetName)) { return nil, fmt.Errorf("name mismatch") } } } stat, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) if err != nil { return nil, err } parts := strings.Split(string(stat), " ") if len(parts) < 24 { return nil, fmt.Errorf("invalid stat format") } status := "?" if len(parts) > 2 { status = strings.Trim(parts[2], "()") } utime := parseIntSafe(parts[13]) stime := parseIntSafe(parts[14]) memBytes := int64(0) if data, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid)); err == nil { lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "VmRSS:") { fields := strings.Fields(line) if len(fields) >= 2 { memKB := parseIntSafe(fields[1]) memBytes = memKB * 1024 } break } } } totalMem := int64(0) if data, err := os.ReadFile("/proc/meminfo"); err == nil { lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "MemTotal:") { fields := strings.Fields(line) if len(fields) >= 2 { totalMem = parseIntSafe(fields[1]) * 1024 } break } } } cpuPercent := 0.0 if totalMem > 0 { cpuPercent = float64(utime+stime) / float64(totalMem) * 100 } cmdline, _ := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) cmdStr := strings.Join(strings.Fields(string(cmdline)), " ") return &ProcessInfo{ PID: pid, Name: procName, Command: cmdStr, CPUPercent: cpuPercent, MemBytes: memBytes, MemPercent: float64(memBytes) / float64(totalMem) * 100, Status: status, User: getProcessUser(pid), }, nil } func getProcessUser(pid int) string { if data, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid)); err == nil { lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "Uid:") { fields := strings.Fields(line) if len(fields) >= 2 { uid := parseIntSafe(fields[1]) return fmt.Sprintf("uid:%d", uid) } } } } return "?" } func parseIntSafe(s string) int64 { if i, err := strconv.ParseInt(strings.TrimSpace(s), 10, 64); err == nil { return i } return 0 }