019 Project 19: Linux ps-lite

019 Build a Linux ps-Lite Tool

Read process info from /proc and print a compact process table.

/proc -> parse /proc/<pid>/stat + comm -> rows -> sort by pid -> table

Full main.go

package main

import (
    "flag"
    "fmt"
    "os"
    "path/filepath"
    "sort"
    "strconv"
    "strings"
)

type proc struct {
    PID  int
    Comm string
    RSS  int64
    State string
}

func parseProc(pid int) (proc, error) {
    statPath := fmt.Sprintf("/proc/%d/stat", pid)
    b, err := os.ReadFile(statPath)
    if err != nil {
        return proc{}, err
    }
    parts := strings.Fields(string(b))
    if len(parts) < 24 {
        return proc{}, fmt.Errorf("short stat")
    }
    comm := strings.Trim(parts[1], "()")
    state := parts[2]
    rssPages, _ := strconv.ParseInt(parts[23], 10, 64)
    return proc{PID: pid, Comm: comm, State: state, RSS: rssPages * 4096}, nil
}

func main() {
    limit := flag.Int("n", 50, "max rows")
    flag.Parse()

    entries, err := os.ReadDir("/proc")
    if err != nil {
        fmt.Println("/proc unavailable (Linux only)")
        os.Exit(1)
    }

    var rows []proc
    for _, e := range entries {
        if !e.IsDir() {
            continue
        }
        pid, err := strconv.Atoi(e.Name())
        if err != nil {
            continue
        }
        p, err := parseProc(pid)
        if err == nil {
            rows = append(rows, p)
        }
    }

    sort.Slice(rows, func(i, j int) bool { return rows[i].PID < rows[j].PID })
    if *limit > len(rows) {
        *limit = len(rows)
    }

    fmt.Printf("%-8s %-4s %-10s %s\n", "PID", "S", "RSS(MB)", "COMM")
    for i := 0; i < *limit; i++ {
        r := rows[i]
        fmt.Printf("%-8d %-4s %-10.1f %s\n", r.PID, r.State, float64(r.RSS)/1024.0/1024.0, filepath.Base(r.Comm))
    }
}

Run

go run . -n 40

Step-by-Step Explanation

  1. Parse command-line flags and validate inputs early.
  2. Keep the core operation in a small, testable function.
  3. Process data as a stream when possible to reduce memory use.
  4. Print stable output and meaningful exit codes.
  5. Add one extension feature and test edge cases.

Code Anatomy

  • main handles flags, orchestration, and errors.
  • Worker/helper functions hold business logic.
  • Output section should be deterministic for scripting and CI usage.

Learning Goals

  • Write composable Unix-style Go tools.
  • Improve error messages and operator experience.
  • Practice iterative improvement over one clear baseline.