proc.go 3.5 KB

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