148 lines
3.6 KiB
Go
148 lines
3.6 KiB
Go
|
|
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
|
||
|
|
}
|