github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/system/proc.go (about) 1 package system 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strconv" 8 "strings" 9 ) 10 11 // State is the status of a process. 12 type State rune 13 14 const ( // Only values for Linux 3.14 and later are listed here 15 Dead State = 'X' 16 DiskSleep State = 'D' 17 Running State = 'R' 18 Sleeping State = 'S' 19 Stopped State = 'T' 20 TracingStop State = 't' 21 Zombie State = 'Z' 22 Parked State = 'P' 23 Idle State = 'I' 24 ) 25 26 // String forms of the state from proc(5)'s documentation for 27 // /proc/[pid]/status' "State" field. 28 func (s State) String() string { 29 switch s { 30 case Dead: 31 return "dead" 32 case DiskSleep: 33 return "disk sleep" 34 case Running: 35 return "running" 36 case Sleeping: 37 return "sleeping" 38 case Stopped: 39 return "stopped" 40 case TracingStop: 41 return "tracing stop" 42 case Zombie: 43 return "zombie" 44 case Parked: 45 return "parked" 46 case Idle: 47 return "idle" // kernel thread 48 default: 49 return fmt.Sprintf("unknown (%c)", s) 50 } 51 } 52 53 // Stat_t represents the information from /proc/[pid]/stat, as 54 // described in proc(5) with names based on the /proc/[pid]/status 55 // fields. 56 type Stat_t struct { 57 // Name is the command run by the process. 58 Name string 59 60 // State is the state of the process. 61 State State 62 63 // StartTime is the number of clock ticks after system boot (since 64 // Linux 2.6). 65 StartTime uint64 66 } 67 68 // Stat returns a Stat_t instance for the specified process. 69 func Stat(pid int) (stat Stat_t, err error) { 70 bytes, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat")) 71 if err != nil { 72 return stat, err 73 } 74 return parseStat(string(bytes)) 75 } 76 77 func parseStat(data string) (stat Stat_t, err error) { 78 // Example: 79 // 89653 (gunicorn: maste) S 89630 89653 89653 0 -1 4194560 29689 28896 0 3 146 32 76 19 20 0 1 0 2971844 52965376 3920 18446744073709551615 1 1 0 0 0 0 0 16781312 137447943 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0 80 // The fields are space-separated, see full description in proc(5). 81 // 82 // We are only interested in: 83 // * field 2: process name. It is the only field enclosed into 84 // parenthesis, as it can contain spaces (and parenthesis) inside. 85 // * field 3: process state, a single character (%c) 86 // * field 22: process start time, a long unsigned integer (%llu). 87 88 // 1. Look for the first '(' and the last ')' first, what's in between is Name. 89 // We expect at least 20 fields and a space after the last one. 90 91 const minAfterName = 20*2 + 1 // the min field is '0 '. 92 93 first := strings.IndexByte(data, '(') 94 if first < 0 || first+minAfterName >= len(data) { 95 return stat, fmt.Errorf("invalid stat data (no comm or too short): %q", data) 96 } 97 98 last := strings.LastIndexByte(data, ')') 99 if last <= first || last+minAfterName >= len(data) { 100 return stat, fmt.Errorf("invalid stat data (no comm or too short): %q", data) 101 } 102 103 stat.Name = data[first+1 : last] 104 105 // 2. Remove fields 1 and 2 and a space after. State is right after. 106 data = data[last+2:] 107 stat.State = State(data[0]) 108 109 // 3. StartTime is field 22, data is at field 3 now, so we need to skip 19 spaces. 110 skipSpaces := 22 - 3 111 for first = 0; skipSpaces > 0 && first < len(data); first++ { 112 if data[first] == ' ' { 113 skipSpaces-- 114 } 115 } 116 // Now first points to StartTime; look for space right after. 117 i := strings.IndexByte(data[first:], ' ') 118 if i < 0 { 119 return stat, fmt.Errorf("invalid stat data (too short): %q", data) 120 } 121 stat.StartTime, err = strconv.ParseUint(data[first:first+i], 10, 64) 122 if err != nil { 123 return stat, fmt.Errorf("invalid stat data (bad start time): %w", err) 124 } 125 126 return stat, nil 127 }