package proc import ( "bufio" "fmt" "os" "strconv" "strings" ) // ProcStatus holds memory-related information from /proc/[pid]/status type ProcStatus struct { PID int Name string // Process name VmSize uint64 // Virtual memory size in bytes VmRSS uint64 // Resident Set Size in bytes (actual RAM used) VmPeak uint64 // Peak virtual memory size in bytes VmData uint64 // Data segment size in bytes VmStk uint64 // Stack size in bytes VmExe uint64 // Text (code) size in bytes VmLib uint64 // Shared library code size in bytes RssAnon uint64 // Anonymous RSS in bytes RssFile uint64 // File-backed RSS in bytes RssShmem uint64 // Shared memory RSS in bytes Threads int // Number of threads UID int // Real user ID GID int // Real group ID } // ReadStatus reads and parses /proc/[pid]/status for the given PID func ReadStatus(procPath string, pid int) (*ProcStatus, error) { path := fmt.Sprintf("%s/%d/status", procPath, pid) file, err := os.Open(path) if err != nil { return nil, fmt.Errorf("opening status file: %w", err) } defer func() { _ = file.Close() }() return parseStatus(pid, file) } // parseStatus parses the content of /proc/[pid]/status func parseStatus(pid int, file *os.File) (*ProcStatus, error) { status := &ProcStatus{PID: pid} scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() parts := strings.SplitN(line, ":", 2) if len(parts) != 2 { continue } key := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) switch key { case "Name": status.Name = value case "VmSize": status.VmSize = parseMemoryValue(value) case "VmRSS": status.VmRSS = parseMemoryValue(value) case "VmPeak": status.VmPeak = parseMemoryValue(value) case "VmData": status.VmData = parseMemoryValue(value) case "VmStk": status.VmStk = parseMemoryValue(value) case "VmExe": status.VmExe = parseMemoryValue(value) case "VmLib": status.VmLib = parseMemoryValue(value) case "RssAnon": status.RssAnon = parseMemoryValue(value) case "RssFile": status.RssFile = parseMemoryValue(value) case "RssShmem": status.RssShmem = parseMemoryValue(value) case "Threads": if n, err := strconv.Atoi(value); err == nil { status.Threads = n } case "Uid": // Format: "Uid: real effective saved filesystem" fields := strings.Fields(value) if len(fields) > 0 { if uid, err := strconv.Atoi(fields[0]); err == nil { status.UID = uid } } case "Gid": // Format: "Gid: real effective saved filesystem" fields := strings.Fields(value) if len(fields) > 0 { if gid, err := strconv.Atoi(fields[0]); err == nil { status.GID = gid } } } } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("reading status file: %w", err) } return status, nil } // parseMemoryValue parses memory values from /proc/[pid]/status // Format is typically "1234 kB" func parseMemoryValue(value string) uint64 { fields := strings.Fields(value) if len(fields) == 0 { return 0 } num, err := strconv.ParseUint(fields[0], 10, 64) if err != nil { return 0 } // Convert to bytes if unit is specified if len(fields) > 1 { unit := strings.ToLower(fields[1]) switch unit { case "kb": num *= 1024 case "mb": num *= 1024 * 1024 case "gb": num *= 1024 * 1024 * 1024 } } return num } // TotalRSS returns the total RSS (RssAnon + RssFile + RssShmem) // Falls back to VmRSS if the detailed fields are not available func (s *ProcStatus) TotalRSS() uint64 { total := s.RssAnon + s.RssFile + s.RssShmem if total == 0 { return s.VmRSS } return total }