1
2
3 package process
4
5 import (
6 "context"
7 "fmt"
8 "os/exec"
9 "path/filepath"
10 "strconv"
11 "strings"
12 "time"
13
14 "github.com/shirou/gopsutil/cpu"
15 "github.com/shirou/gopsutil/internal/common"
16 "github.com/shirou/gopsutil/net"
17 "github.com/tklauser/go-sysconf"
18 "golang.org/x/sys/unix"
19 )
20
21
22 const (
23 CTLKern = 1
24 KernProc = 14
25 KernProcPID = 1
26 KernProcProc = 8
27 KernProcAll = 0
28 KernProcPathname = 12
29 )
30
31 var ClockTicks = 100
32
33 func init() {
34 clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
35
36 if err == nil {
37 ClockTicks = int(clkTck)
38 }
39 }
40
41 type _Ctype_struct___0 struct {
42 Pad uint64
43 }
44
45 func pidsWithContext(ctx context.Context) ([]int32, error) {
46 var ret []int32
47
48 pids, err := callPsWithContext(ctx, "pid", 0, false, false)
49 if err != nil {
50 return ret, err
51 }
52
53 for _, pid := range pids {
54 v, err := strconv.Atoi(pid[0])
55 if err != nil {
56 return ret, err
57 }
58 ret = append(ret, int32(v))
59 }
60
61 return ret, nil
62 }
63
64 func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
65 r, err := callPsWithContext(ctx, "ppid", p.Pid, false, false)
66 if err != nil {
67 return 0, err
68 }
69
70 v, err := strconv.Atoi(r[0][0])
71 if err != nil {
72 return 0, err
73 }
74
75 return int32(v), err
76 }
77
78 func (p *Process) NameWithContext(ctx context.Context) (string, error) {
79 k, err := p.getKProc()
80 if err != nil {
81 return "", err
82 }
83 name := common.IntToString(k.Proc.P_comm[:])
84
85 if len(name) >= 15 {
86 cmdName, err := p.cmdNameWithContext(ctx)
87 if err != nil {
88 return "", err
89 }
90 if len(cmdName) > 0 {
91 extendedName := filepath.Base(cmdName[0])
92 if strings.HasPrefix(extendedName, p.name) {
93 name = extendedName
94 } else {
95 name = cmdName[0]
96 }
97 }
98 }
99
100 return name, nil
101 }
102
103 func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
104 r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
105 if err != nil {
106 return "", err
107 }
108 return strings.Join(r[0], " "), err
109 }
110
111
112 func (p *Process) cmdNameWithContext(ctx context.Context) ([]string, error) {
113 r, err := callPsWithContext(ctx, "command", p.Pid, false, true)
114 if err != nil {
115 return nil, err
116 }
117 return r[0], err
118 }
119
120
121
122
123
124
125 func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
126 r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
127 if err != nil {
128 return nil, err
129 }
130 return r[0], err
131 }
132
133 func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
134 r, err := callPsWithContext(ctx, "etime", p.Pid, false, false)
135 if err != nil {
136 return 0, err
137 }
138
139 elapsedSegments := strings.Split(strings.Replace(r[0][0], "-", ":", 1), ":")
140 var elapsedDurations []time.Duration
141 for i := len(elapsedSegments) - 1; i >= 0; i-- {
142 p, err := strconv.ParseInt(elapsedSegments[i], 10, 0)
143 if err != nil {
144 return 0, err
145 }
146 elapsedDurations = append(elapsedDurations, time.Duration(p))
147 }
148
149 var elapsed = time.Duration(elapsedDurations[0]) * time.Second
150 if len(elapsedDurations) > 1 {
151 elapsed += time.Duration(elapsedDurations[1]) * time.Minute
152 }
153 if len(elapsedDurations) > 2 {
154 elapsed += time.Duration(elapsedDurations[2]) * time.Hour
155 }
156 if len(elapsedDurations) > 3 {
157 elapsed += time.Duration(elapsedDurations[3]) * time.Hour * 24
158 }
159
160 start := time.Now().Add(-elapsed)
161 return start.Unix() * 1000, nil
162 }
163
164 func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
165 out, err := common.CallLsofWithContext(ctx, invoke, p.Pid, "-FR")
166 if err != nil {
167 return nil, err
168 }
169 for _, line := range out {
170 if len(line) >= 1 && line[0] == 'R' {
171 v, err := strconv.Atoi(line[1:])
172 if err != nil {
173 return nil, err
174 }
175 return NewProcessWithContext(ctx, int32(v))
176 }
177 }
178 return nil, fmt.Errorf("could not find parent line")
179 }
180
181 func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
182 r, err := callPsWithContext(ctx, "state", p.Pid, false, false)
183 if err != nil {
184 return "", err
185 }
186
187 return r[0][0][0:1], err
188 }
189
190 func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
191
192 pid := p.Pid
193 ps, err := exec.LookPath("ps")
194 if err != nil {
195 return false, err
196 }
197 out, err := invoke.CommandWithContext(ctx, ps, "-o", "stat=", "-p", strconv.Itoa(int(pid)))
198 if err != nil {
199 return false, err
200 }
201 return strings.IndexByte(string(out), '+') != -1, nil
202 }
203
204 func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) {
205 k, err := p.getKProc()
206 if err != nil {
207 return nil, err
208 }
209
210
211 userEffectiveUID := int32(k.Eproc.Ucred.UID)
212
213 return []int32{userEffectiveUID}, nil
214 }
215
216 func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) {
217 k, err := p.getKProc()
218 if err != nil {
219 return nil, err
220 }
221
222 gids := make([]int32, 0, 3)
223 gids = append(gids, int32(k.Eproc.Pcred.P_rgid), int32(k.Eproc.Ucred.Ngroups), int32(k.Eproc.Pcred.P_svgid))
224
225 return gids, nil
226 }
227
228 func (p *Process) GroupsWithContext(ctx context.Context) ([]int32, error) {
229 return nil, common.ErrNotImplementedError
230
231
232
233
234
235
236
237
238
239
240
241 }
242
243 func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
244 return "", common.ErrNotImplementedError
245
259 }
260
261 func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
262 k, err := p.getKProc()
263 if err != nil {
264 return 0, err
265 }
266 return int32(k.Proc.P_nice), nil
267 }
268
269 func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
270 return nil, common.ErrNotImplementedError
271 }
272
273 func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
274 r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true, false)
275 if err != nil {
276 return 0, err
277 }
278 return int32(len(r)), nil
279 }
280
281 func convertCPUTimes(s string) (ret float64, err error) {
282 var t int
283 var _tmp string
284 if strings.Contains(s, ":") {
285 _t := strings.Split(s, ":")
286 switch len(_t) {
287 case 3:
288 hour, err := strconv.Atoi(_t[0])
289 if err != nil {
290 return ret, err
291 }
292 t += hour * 60 * 60 * ClockTicks
293
294 mins, err := strconv.Atoi(_t[1])
295 if err != nil {
296 return ret, err
297 }
298 t += mins * 60 * ClockTicks
299 _tmp = _t[2]
300 case 2:
301 mins, err := strconv.Atoi(_t[0])
302 if err != nil {
303 return ret, err
304 }
305 t += mins * 60 * ClockTicks
306 _tmp = _t[1]
307 case 1, 0:
308 _tmp = s
309 default:
310 return ret, fmt.Errorf("wrong cpu time string")
311 }
312 } else {
313 _tmp = s
314 }
315
316 _t := strings.Split(_tmp, ".")
317 if err != nil {
318 return ret, err
319 }
320 h, err := strconv.Atoi(_t[0])
321 t += h * ClockTicks
322 h, err = strconv.Atoi(_t[1])
323 t += h
324 return float64(t) / float64(ClockTicks), nil
325 }
326
327 func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
328 r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false, false)
329
330 if err != nil {
331 return nil, err
332 }
333
334 utime, err := convertCPUTimes(r[0][0])
335 if err != nil {
336 return nil, err
337 }
338 stime, err := convertCPUTimes(r[0][1])
339 if err != nil {
340 return nil, err
341 }
342
343 ret := &cpu.TimesStat{
344 CPU: "cpu",
345 User: utime,
346 System: stime,
347 }
348 return ret, nil
349 }
350
351 func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
352 r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false, false)
353 if err != nil {
354 return nil, err
355 }
356 rss, err := strconv.Atoi(r[0][0])
357 if err != nil {
358 return nil, err
359 }
360 vms, err := strconv.Atoi(r[0][1])
361 if err != nil {
362 return nil, err
363 }
364 pagein, err := strconv.Atoi(r[0][2])
365 if err != nil {
366 return nil, err
367 }
368
369 ret := &MemoryInfoStat{
370 RSS: uint64(rss) * 1024,
371 VMS: uint64(vms) * 1024,
372 Swap: uint64(pagein),
373 }
374
375 return ret, nil
376 }
377
378 func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
379 pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
380 if err != nil {
381 return nil, err
382 }
383 ret := make([]*Process, 0, len(pids))
384 for _, pid := range pids {
385 np, err := NewProcessWithContext(ctx, pid)
386 if err != nil {
387 return nil, err
388 }
389 ret = append(ret, np)
390 }
391 return ret, nil
392 }
393
394 func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
395 return net.ConnectionsPidWithContext(ctx, "all", p.Pid)
396 }
397
398 func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
399 return net.ConnectionsPidMaxWithContext(ctx, "all", p.Pid, max)
400 }
401
402 func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
403 out := []*Process{}
404
405 pids, err := PidsWithContext(ctx)
406 if err != nil {
407 return out, err
408 }
409
410 for _, pid := range pids {
411 p, err := NewProcessWithContext(ctx, pid)
412 if err != nil {
413 continue
414 }
415 out = append(out, p)
416 }
417
418 return out, nil
419 }
420
421
422
423 func (p *Process) getKProc() (*KinfoProc, error) {
424 buf, err := unix.SysctlRaw("kern.proc.pid", int(p.Pid))
425 if err != nil {
426 return nil, err
427 }
428 k, err := parseKinfoProc(buf)
429 if err != nil {
430 return nil, err
431 }
432
433 return &k, nil
434 }
435
436
437
438
439
440 func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool, nameOption bool) ([][]string, error) {
441 bin, err := exec.LookPath("ps")
442 if err != nil {
443 return [][]string{}, err
444 }
445
446 var cmd []string
447 if pid == 0 {
448 cmd = []string{"-ax", "-o", arg}
449 } else if threadOption {
450 cmd = []string{"-x", "-o", arg, "-M", "-p", strconv.Itoa(int(pid))}
451 } else {
452 cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))}
453 }
454
455 if nameOption {
456 cmd = append(cmd, "-c")
457 }
458 out, err := invoke.CommandWithContext(ctx, bin, cmd...)
459 if err != nil {
460 return [][]string{}, err
461 }
462 lines := strings.Split(string(out), "\n")
463
464 var ret [][]string
465 for _, l := range lines[1:] {
466
467 var lr []string
468 if nameOption {
469 lr = append(lr, l)
470 } else {
471 for _, r := range strings.Split(l, " ") {
472 if r == "" {
473 continue
474 }
475 lr = append(lr, strings.TrimSpace(r))
476 }
477 }
478
479 if len(lr) != 0 {
480 ret = append(ret, lr)
481 }
482 }
483
484 return ret, nil
485 }
486
View as plain text