-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathps.go
74 lines (58 loc) · 1.71 KB
/
ps.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package gpublame
import (
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"
"time"
)
// PGID returns the process group ID for a specified process.
func PGID(pid int) (int, error) {
var stdout bytes.Buffer
//nolint:gosec // The PID will always be an int, so this is safe.
ps := exec.Command("ps", "-heo", "pgid", "-q", strconv.Itoa(pid))
ps.Stdout = &stdout
if err := ps.Run(); err != nil {
return 0, fmt.Errorf("ps: %w", err)
}
return strconv.Atoi(strings.TrimSpace(stdout.String()))
}
// ProcessInfo is the information returned by ps for a single process.
type ProcessInfo struct {
Start time.Time
User string
Command string
PID int
}
// ProcessGroupInfo returns information provided by ps for the process group of the specified
// process.
func ProcessGroupInfo(pid int) (ProcessInfo, error) {
pgid, err := PGID(pid)
if err != nil {
return ProcessInfo{}, fmt.Errorf("get PGID: %w", err)
}
var stdout bytes.Buffer
//nolint:gosec // The PGID will always be an int, so this is safe.
ps := exec.Command("ps", "-heo", "user,lstart,cmd", "-q", strconv.Itoa(pgid))
ps.Stdout = &stdout
err = ps.Run()
if err != nil {
return ProcessInfo{}, fmt.Errorf("find user for PID %d: %w", pid, err)
}
return processInfoFromFields(stdout.String(), pgid)
}
func processInfoFromFields(s string, pid int) (p ProcessInfo, err error) {
fields := strings.Fields(s)
if len(fields) < 7 {
return p, &ParseError{fmt.Sprintf("expected 7 fields; got %d", len(fields)), s}
}
p.Start, err = time.Parse("Mon Jan _2 15:04:05 2006", strings.Join(fields[1:6], " "))
if err != nil {
return p, fmt.Errorf("invalid time field: %w", err)
}
p.User = fields[0]
p.PID = pid
p.Command = strings.Join(fields[6:], " ")
return p, nil
}