-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathprocreader.go
556 lines (489 loc) · 17 KB
/
procreader.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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
package procreader
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"reflect"
"runtime"
"strconv"
"strings"
"unicode"
)
type procConfig struct {
basepath string
contents map[string]string
}
// All errors returned should be type ProcErr and include a stack
type ProcErr struct {
error
Message string
Stack []byte
}
func (f *ProcErr) Error() string {
return fmt.Sprintf("procreader: %s", f.Message)
}
/*
* Fields from https://www.kernel.org/doc/Documentation/filesystems/proc.txt
* See also: https://gitorious.org/procps/procps/source/3a66fba1e934cbd830df572d8d03c05b4f4a5f1e:proc/readproc.c#L550
*/
type Stat_t struct {
// fields from /proc/<pid>/stat -- order here is critical
// Remember: lower case fields are not exposed (ie. ignored)
Pid uint64 // process id
Tcomm string // filename of the executable '(executable)'
State string // state (R is running, S is sleeping, D is sleeping in an uninterruptible wait, Z is zombie, T is traced/stopped)
Ppid int64 // process id of the parent process
Pgrp int64 // pgrp of the process
Sid int64 // session id
Tty_nr int64 // tty the process uses
Tty_pgrp int64 // pgrp of the tty
Flags uint64 // task flags
Min_flt uint64 // number of minor faults
Cmin_flt uint64 // number of minor faults with child's
Maj_flt uint64 // number of major faults
Cmaj_flt uint64 // number of major faults with child's
Utime uint64 // user mode jiffies
Stime uint64 // kernel mode jiffies
Cutime uint64 // user mode jiffies with child's
Cstime uint64 // kernel mode jiffies with child's
Priority int32 // priority level
Nice int32 // nice level
Num_threads uint32 // number of threads
it_real_value uint32 // (obsolete, always 0) -- IGNORED
Start_time uint64 // time the process started after system boot (* 1000000 for no decimals)
Vsize uint64 // virtual memory size
Rss uint64 // resident set memory size
Rsslim uint64 // current limit in bytes on the rss
Start_code uint64 // address above which program text can run
End_code uint64 // address below which program text can run
Start_stack uint64 // address of the start of the main process stack
Esp uint64 // current value of ESP
Eip uint64 // current value of EIP
Pending string // bitmap of pending signals
Blocked string // bitmap of blocked signals
Sigign string // bitmap of ignored signals
Sigcatch string // bitmap of caught signals
Wchan uint64 // address where process went to sleep
placeholder1 uint64 // (place holder) -- IGNORED
placeholder2 uint64 // (place holder) -- IGNORED
Exit_signal uint64 // signal to send to parent thread on exit
Task_cpu uint64 // which CPU the task is scheduled on
Rt_priority uint64 // realtime priority
Policy uint64 // scheduling policy (man sched_setscheduler)
Blkio_ticks uint64 // time spent waiting for block IO
Gtime uint64 // guest time of the task in jiffies
Cgtime uint64 // guest time of the task children in jiffies
Start_data uint64 // address above which program data+bss is placed
End_data uint64 // address below which program data+bss is placed
Start_brk uint64 // address above which program heap can be expanded with brk()
Arg_start uint64 // address above which program command line is placed
Arg_end uint64 // address below which program command line is placed
Env_start uint64 // address above which program environment is placed
Env_end uint64 // address below which program environment is placed
Exit_code uint64 // the thread's exit_code in the form reported by the waitpid system call
}
type Statm_t struct {
// fields from /proc/<pid>/statm -- order here is critical
Size uint64 // total program size (pages) (same as VmSize in status)
Resident uint64 // size of memory portions (pages) (same as VmRSS in status)
Shared uint64 // number of pages that are shared (i.e. backed by a file)
Trs uint64 // number of pages that are 'code' (not including libs; broken, includes data segment)
Lrs uint64 // number of pages of library (always 0 on 2.6)
Drs uint64 // number of pages of data/stack (including libs; broken, includes library text)
Dt uint64 // number of dirty pages (always 0 on 2.6)
}
type Ids struct {
Real uint64
Effective uint64
Saved uint64
FS uint64
}
type SigQVal struct {
Num uint64
Max uint64
}
type Status_t struct {
// fields from /proc/<pid>/status
Name string // filename of the executable
State string // state (R=running, S=sleeping, D=sleeping in an uninterruptible wait, Z=zombie, T=(traced or stopped))
Tgid uint64 // thread group ID
Ngid uint64 // numa group ID
Pid uint64 // process id
PPid uint64 // process id of the parent process
TracerPid uint64 // PID of process tracing this process (0 if not)
Uid Ids // Real, effective, saved set, and file system UIDs
Gid Ids // Real, effective, saved set, and file system GIDs
FDSize uint64 // number of file descriptor slots currently allocated
Groups []uint64 // supplementary group list
VmPeak uint64 // peak virtual memory size
VmSize uint64 // total program size
VmLck uint64 // locked memory size
VmPin uint64 // locked memory size
VmHWM uint64 // peak resident set size ("high water mark")
VmRSS uint64 // size of memory portions
VmData uint64 // size of data, stack, and text segments
VmStk uint64 // size of data, stack, and text segments
VmExe uint64 // size of text segment
VmLib uint64 // size of shared library code
VmPTE uint64 // size of page table entries
VmSwap uint64 // size of swap usage (the number of referred swapents)
Threads uint64 // number of threads
SigQ SigQVal // number of signals queued (Num) / limit (Max)
SigPnd string // bitmap of pending signals for the thread
ShdPnd string // bitmap of shared pending signals for the process
SigBlk string // bitmap of blocked signals
SigIgn string // bitmap of ignored signals
SigCgt string // bitmap of caught signals
CapInh string // bitmap of inheritable capabilities
CapPrm string // bitmap of permitted capabilities
CapEff string // bitmap of effective capabilities
CapBnd string // bitmap of capabilities bounding set
Seccomp uint64 // seccomp mode, like prctl(PR_GET_SECCOMP, ...)
Cpus_allowed string // mask of CPUs on which this process may run "mask format"
Cpus_allowed_list string // Same as previous, but in "list format"
Mems_allowed string // mask of memory nodes allowed to this process "mask format"
Mems_allowed_list string // Same as previous, but in "list format"
Voluntary_ctxt_switches uint64 // number of voluntary context switches
Nonvoluntary_ctxt_switches uint64 // number of non voluntary context switches
}
type Proc struct {
Stat Stat_t
Statm Statm_t
Status Status_t
// Environ and Cmdline are from /proc/<pid>/{environ,cmdline}
Cmdline []string
Environ []string
}
func wrapError(err error) error {
if err == nil {
return nil
}
if e, ok := err.(*ProcErr); ok {
return e
}
trace := make([]byte, 4096)
runtime.Stack(trace, true)
return &ProcErr{
error: err,
Message: err.Error(),
Stack: trace,
}
}
func newError(format string, args ...interface{}) error {
return wrapError(errors.New(fmt.Sprintf(format, args...)))
}
// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(cfg *procConfig, pid uint64, filename string) ([]string, error) {
var lines []string
var scanner *bufio.Scanner
if contents, ok := cfg.contents[filename]; ok {
scanner = bufio.NewScanner(strings.NewReader(contents))
} else {
fn := fmt.Sprintf("%s/%d/%s", cfg.basepath, pid, filename)
data, err := ioutil.ReadFile(fn)
if err != nil {
return nil, wrapError(err)
}
// for generating test cases, having the input is required
cfg.contents[filename] = string(data)
scanner = bufio.NewScanner(strings.NewReader(cfg.contents[filename]))
}
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines, wrapError(scanner.Err())
}
func readStat(cfg *procConfig, pid uint64, proc *Proc) error {
var stat Stat_t
lines, err := readLines(cfg, pid, "stat")
if err != nil {
return wrapError(err)
}
if len(lines) != 1 {
return newError("readStat(): expected 1 line, got %d", len(lines))
}
s := reflect.ValueOf(&stat).Elem()
typeOfS := s.Type()
// field[1] is special in that it's '(<command>)' and we need to
// watch out for things like ')' in the <command>. We do what
// procps does and take everything between the first '(' and
// last ')'
cmd_end := strings.LastIndex(lines[0], ")")
cmd_start := strings.Index(lines[0], "(") + 1
// This mess:
//
// * splits all the fields *after* the command
// * prepends the first field (Pid) and command (w/ '()' removed)
//
fields := strings.Split(lines[0][cmd_end+2:], " ")
fields = append([]string{lines[0][0 : cmd_start-1],
lines[0][cmd_start:cmd_end]}, fields...)
// loop through the struct's fields and add them to 'stat'
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
if !f.CanSet() {
continue
}
// Older kernels are missing newer fields
if i >= len(fields) {
continue
}
//fmt.Printf("%d: %s %s = '%s'\n", i,
//typeOfS.Field(i).Name, f.Type(), fields[i])
switch f.Type().String() {
case "int32":
u, err := strconv.ParseInt(strings.TrimSpace(fields[i]), 10, 32)
if err != nil {
return wrapError(err)
}
s.Field(i).SetInt(u)
case "int64":
u, err := strconv.ParseInt(strings.TrimSpace(fields[i]), 10, 64)
if err != nil {
return wrapError(err)
}
s.Field(i).SetInt(u)
case "uint32":
u, err := strconv.ParseUint(strings.TrimSpace(fields[i]), 10, 32)
if err != nil {
return wrapError(err)
}
s.Field(i).SetUint(u)
case "uint64":
u, err := strconv.ParseUint(strings.TrimSpace(fields[i]), 10, 64)
if err != nil {
return wrapError(err)
}
s.Field(i).SetUint(u)
case "string":
s.Field(i).SetString(fields[i])
default:
return newError("readStat(): unhandled type '%s' for '%s'",
f.Type().String(), typeOfS.Field(i).Name)
}
}
proc.Stat = stat
return nil
}
func readStatm(cfg *procConfig, pid uint64, proc *Proc) error {
var statm Statm_t
lines, err := readLines(cfg, pid, "statm")
if err != nil {
return wrapError(err)
}
if len(lines) != 1 {
return newError("readStatm(): expected 1 line, got %d", len(lines))
}
s := reflect.ValueOf(&statm).Elem()
typeOfS := s.Type()
fields := strings.Split(lines[0], " ")
// loop through the struct's fields and add them to 'statm'
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
if !f.CanSet() {
continue
}
// fmt.Printf("%d: %s %s = '%s'\n", i,
// typeOfS.Field(i).Name, f.Type(), fields[i])
switch f.Type().String() {
case "uint64":
u, err := strconv.ParseUint(strings.TrimSpace(fields[i]), 10, 64)
if err != nil {
return wrapError(err)
}
s.Field(i).SetUint(u)
default:
return newError("readStatm(): unhandled type '%s' for '%s'",
f.Type().String(), typeOfS.Field(i).Name)
}
}
proc.Statm = statm
return nil
}
func readStatus(cfg *procConfig, pid uint64, proc *Proc) error {
var err error
var statusMap = make(map[string]reflect.Value)
var status Status_t
lines, err := readLines(cfg, pid, "status")
if err != nil {
return wrapError(err)
}
s := reflect.ValueOf(&status).Elem()
typeOfS := s.Type()
for i := 0; i < s.NumField(); i++ {
statusMap[typeOfS.Field(i).Name] = s.Field(i)
}
for line := range lines {
fields := strings.SplitN(lines[line], ":\t", 2)
name := fields[0]
value := strings.TrimSpace(fields[1])
f := statusMap[name]
if !f.IsValid() {
// not valid, see if capitalizing first character fixes
a := []rune(name)
a[0] = unicode.ToUpper(a[0])
name = string(a)
f = statusMap[name]
if !f.IsValid() {
// still not valid, can't move forward, though we'll skip fields we know
// exist on older kernels that we don't care about any more.
switch fields[0] {
case "SleepAVG":
continue
}
return newError("readStatus(): '%s' is unhandled", fields[0])
}
}
switch f.Type().String() {
case "procreader.Ids":
if name == "Uid" {
cnt, err := fmt.Sscanf(value, "%d\t%d\t%d\t%d",
&status.Uid.Real, &status.Uid.Effective,
&status.Uid.Saved, &status.Uid.FS,
)
if err != nil {
return wrapError(err)
}
if cnt != 4 {
return newError("readStatus(Uid): expected 4 fields, got %d: '%s'", cnt, value)
}
} else if name == "Gid" {
cnt, err := fmt.Sscanf(value, "%d\t%d\t%d\t%d",
&status.Gid.Real, &status.Gid.Effective,
&status.Gid.Saved, &status.Gid.FS,
)
if err != nil {
return wrapError(err)
}
if cnt != 4 {
return newError("readStatus(Gid): expected 4 fields, got %d: '%s'", cnt, value)
}
} else {
return newError("readStatus: Internal Error: %s not supported for type Ids", name)
}
case "procreader.SigQVal":
cnt, err := fmt.Sscanf(value, "%d/%d", &status.SigQ.Num, &status.SigQ.Max)
if err != nil {
return wrapError(err)
}
if cnt != 2 {
return newError("readStatus[%s]: expected 2 fields, got %d: '%s'", name, cnt, value)
}
case "[]uint64":
if name != "Groups" {
return newError("readStatus: Internal Error: %s not supported for type []uint64", name)
}
groups := strings.Split(value, " ")
for g := range groups {
if len(groups[g]) == 0 {
continue
}
val, err := strconv.ParseUint(groups[g], 10, 64)
if err != nil {
return wrapError(err)
}
status.Groups = append(status.Groups, val)
}
case "string":
f.SetString(value)
case "uint64":
if strings.HasSuffix(value, " kB") {
value = value[:len(value)-3]
}
u, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return wrapError(err)
}
f.SetUint(u)
default:
return newError("readStatus(): unhandled type '%s' for '%s'", f.Type().String(), name)
}
}
proc.Status = status
return nil
}
func readNullSeparated(cfg *procConfig, pid uint64, filename string) ([]string, error) {
var r *bufio.Reader
var strs []string
if contents, ok := cfg.contents[filename]; ok {
r = bufio.NewReader(strings.NewReader(contents))
} else {
fn := fmt.Sprintf("%s/%d/%s", cfg.basepath, pid, filename)
data, err := ioutil.ReadFile(fn)
if err != nil {
return nil, wrapError(err)
}
// for generating test cases, having the input is required
cfg.contents[filename] = string(data)
r = bufio.NewReader(strings.NewReader(cfg.contents[filename]))
}
for {
str, err := r.ReadString(0)
if err == io.EOF {
// do something here
break
} else if err != nil {
return nil, wrapError(err)
}
strs = append(strs, strings.TrimRight(str, "\000"))
}
return strs, nil
}
func readCmdline(cfg *procConfig, pid uint64, proc *Proc) error {
var err error
proc.Cmdline, err = readNullSeparated(cfg, pid, "cmdline")
return wrapError(err)
}
func readEnviron(cfg *procConfig, pid uint64, proc *Proc) error {
var err error
proc.Environ, err = readNullSeparated(cfg, pid, "environ")
return wrapError(err)
}
//
// This function dispatches the reading of the various /proc files but allows
// (via cfg) the replacement of the "readers" that actually read the files. This
// is mostly done to make this module more testable.
//
func readProc(cfg *procConfig, pid uint64) (Proc, error) {
var err error
var proc Proc
err = readStat(cfg, pid, &proc)
if err != nil {
return proc, wrapError(err)
}
err = readStatm(cfg, pid, &proc)
if err != nil {
return proc, wrapError(err)
}
err = readStatus(cfg, pid, &proc)
if err != nil {
return proc, wrapError(err)
}
err = readCmdline(cfg, pid, &proc)
if err != nil {
return proc, wrapError(err)
}
err = readEnviron(cfg, pid, &proc)
if err != nil {
return proc, wrapError(err)
}
return proc, nil
}
//
// This function reads /proc/<pid>/* files and returns a Proc object
// which contains the information for the specified process.
//
func ReadProc(pid uint64) (Proc, error) {
proc, _, err := ReadProcData(pid)
return proc, err
}
func ReadProcData(pid uint64) (Proc, map[string]string, error) {
var cfg procConfig
cfg.basepath = "/proc"
cfg.contents = make(map[string]string)
proc, err := readProc(&cfg, pid)
return proc, cfg.contents, err
}