From 9bd4bc70aee029833e050446b2c67553e93739fd Mon Sep 17 00:00:00 2001 From: shirou Date: Sat, 29 May 2021 21:31:50 +0900 Subject: [PATCH] [process][linux] fix 1056 test and copy to v2 --- process/process_linux.go | 43 +++++---- process/process_linux_test.go | 92 ++++++++++++++++++- .../testdata/linux}/68927/stat | 0 v3/process/process_linux_test.go | 60 ++++++++---- v3/process/testdata/linux/68927/stat | 1 + 5 files changed, 159 insertions(+), 37 deletions(-) rename {v3/process/testdata/linux/proc => process/testdata/linux}/68927/stat (100%) create mode 100644 v3/process/testdata/linux/68927/stat diff --git a/process/process_linux.go b/process/process_linux.go index 5550dd4b..c9edf808 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -999,28 +999,24 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } - fields := strings.Fields(string(contents)) + // Indexing from one, as described in `man proc` about the file /proc/[pid]/stat + fields := splitProcStat(contents) - i := 1 - for !strings.HasSuffix(fields[i], ")") { - i++ - } - - terminal, err := strconv.ParseUint(fields[i+5], 10, 64) + terminal, err := strconv.ParseUint(fields[7], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } - ppid, err := strconv.ParseInt(fields[i+2], 10, 32) + ppid, err := strconv.ParseInt(fields[4], 10, 32) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } - utime, err := strconv.ParseFloat(fields[i+12], 64) + utime, err := strconv.ParseFloat(fields[14], 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } - stime, err := strconv.ParseFloat(fields[i+13], 64) + stime, err := strconv.ParseFloat(fields[15], 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } @@ -1028,7 +1024,7 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui // There is no such thing as iotime in stat file. As an approximation, we // will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux // docs). Note: I am assuming at least Linux 2.6.18 - iotime, err := strconv.ParseFloat(fields[i+40], 64) + iotime, err := strconv.ParseFloat(fields[42], 64) if err != nil { iotime = 0 // Ancient linux version, most likely } @@ -1041,14 +1037,14 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui } bootTime, _ := common.BootTimeWithContext(ctx) - t, err := strconv.ParseUint(fields[i+20], 10, 64) + t, err := strconv.ParseUint(fields[22], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } ctime := (t / uint64(ClockTicks)) + uint64(bootTime) createTime := int64(ctime * 1000) - rtpriority, err := strconv.ParseInt(fields[i+16], 10, 32) + rtpriority, err := strconv.ParseInt(fields[18], 10, 32) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } @@ -1063,19 +1059,19 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui snice, _ := unix.Getpriority(PrioProcess, int(pid)) nice := int32(snice) // FIXME: is this true? - minFault, err := strconv.ParseUint(fields[i+8], 10, 64) + minFault, err := strconv.ParseUint(fields[10], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } - cMinFault, err := strconv.ParseUint(fields[i+9], 10, 64) + cMinFault, err := strconv.ParseUint(fields[11], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } - majFault, err := strconv.ParseUint(fields[i+10], 10, 64) + majFault, err := strconv.ParseUint(fields[12], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } - cMajFault, err := strconv.ParseUint(fields[i+11], 10, 64) + cMajFault, err := strconv.ParseUint(fields[13], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } @@ -1141,3 +1137,16 @@ func readPidsFromDir(path string) ([]int32, error) { return ret, nil } + +func splitProcStat(content []byte) []string { + nameStart := bytes.IndexByte(content, '(') + nameEnd := bytes.LastIndexByte(content, ')') + restFields := strings.Fields(string(content[nameEnd+2:])) // +2 skip ') ' + name := content[nameStart+1 : nameEnd] + pid := strings.TrimSpace(string(content[:nameStart])) + fields := make([]string, 3, len(restFields)+3) + fields[1] = string(pid) + fields[2] = string(name) + fields = append(fields, restFields...) + return fields +} diff --git a/process/process_linux_test.go b/process/process_linux_test.go index bfbe96db..e0ff628c 100644 --- a/process/process_linux_test.go +++ b/process/process_linux_test.go @@ -4,13 +4,96 @@ package process import ( "context" + "fmt" "io/ioutil" + "os" "strconv" + "strings" "testing" "github.com/shirou/gopsutil/internal/common" + "github.com/stretchr/testify/assert" ) +func Test_Process_splitProcStat(t *testing.T) { + expectedFieldsNum := 53 + statLineContent := make([]string, expectedFieldsNum-1) + for i := 0; i < expectedFieldsNum-1; i++ { + statLineContent[i] = strconv.Itoa(i + 1) + } + + cases := []string{ + "ok", + "ok)", + "(ok", + "ok )", + "ok )(", + "ok )()", + "() ok )()", + "() ok (()", + " ) ok )", + "(ok) (ok)", + } + + consideredFields := []int{4, 7, 10, 11, 12, 13, 14, 15, 18, 22, 42} + + commandNameIndex := 2 + for _, expectedName := range cases { + statLineContent[commandNameIndex-1] = "(" + expectedName + ")" + statLine := strings.Join(statLineContent, " ") + t.Run(fmt.Sprintf("name: %s", expectedName), func(t *testing.T) { + parsedStatLine := splitProcStat([]byte(statLine)) + assert.Equal(t, expectedName, parsedStatLine[commandNameIndex]) + for _, idx := range consideredFields { + expected := strconv.Itoa(idx) + parsed := parsedStatLine[idx] + assert.Equal( + t, expected, parsed, + "field %d (index from 1 as in man proc) must be %q but %q is received", + idx, expected, parsed, + ) + } + }) + } +} + +func Test_Process_splitProcStat_fromFile(t *testing.T) { + pids, err := ioutil.ReadDir("testdata/linux/") + if err != nil { + t.Error(err) + } + f := common.MockEnv("HOST_PROC", "testdata/linux") + defer f() + for _, pid := range pids { + pid, err := strconv.ParseInt(pid.Name(), 0, 32) + if err != nil { + continue + } + statFile := fmt.Sprintf("testdata/linux/%d/stat", pid) + if _, err := os.Stat(statFile); err != nil { + continue + } + contents, err := ioutil.ReadFile(statFile) + assert.NoError(t, err) + + pidStr := strconv.Itoa(int(pid)) + + ppid := "68044" // TODO: how to pass ppid to test? + + fields := splitProcStat(contents) + assert.Equal(t, fields[1], pidStr) + assert.Equal(t, fields[2], "test(cmd).sh") + assert.Equal(t, fields[3], "S") + assert.Equal(t, fields[4], ppid) + assert.Equal(t, fields[5], pidStr) // pgrp + assert.Equal(t, fields[6], ppid) // session + assert.Equal(t, fields[8], pidStr) // tpgrp + assert.Equal(t, fields[18], "20") // priority + assert.Equal(t, fields[20], "1") // num threads + assert.Equal(t, fields[52], "0") // exit code + } +} + func Test_fillFromStatusWithContext(t *testing.T) { pids, err := ioutil.ReadDir("testdata/linux/") if err != nil { @@ -19,9 +102,14 @@ func Test_fillFromStatusWithContext(t *testing.T) { f := common.MockEnv("HOST_PROC", "testdata/linux") defer f() for _, pid := range pids { - pid, _ := strconv.ParseInt(pid.Name(), 0, 32) + pid, err := strconv.ParseInt(pid.Name(), 0, 32) + if err != nil { + continue + } + if _, err := os.Stat(fmt.Sprintf("testdata/linux/%d/status", pid)); err != nil { + continue + } p, _ := NewProcess(int32(pid)) - if err := p.fillFromStatusWithContext(context.Background()); err != nil { t.Error(err) } diff --git a/v3/process/testdata/linux/proc/68927/stat b/process/testdata/linux/68927/stat similarity index 100% rename from v3/process/testdata/linux/proc/68927/stat rename to process/testdata/linux/68927/stat diff --git a/v3/process/process_linux_test.go b/v3/process/process_linux_test.go index c7a206e0..2bc65d6d 100644 --- a/v3/process/process_linux_test.go +++ b/v3/process/process_linux_test.go @@ -3,6 +3,7 @@ package process import ( + "os" "context" "fmt" "io/ioutil" @@ -57,22 +58,40 @@ func Test_Process_splitProcStat(t *testing.T) { } func Test_Process_splitProcStat_fromFile(t *testing.T) { - pid := "68927" - ppid := "68044" - statFile := "testdata/linux/proc/" + pid + "/stat" - contents, err := ioutil.ReadFile(statFile) - assert.NoError(t, err) - fields := splitProcStat(contents) - assert.Equal(t, fields[1], pid) - assert.Equal(t, fields[2], "test(cmd).sh") - assert.Equal(t, fields[3], "S") - assert.Equal(t, fields[4], ppid) - assert.Equal(t, fields[5], pid) // pgrp - assert.Equal(t, fields[6], ppid) // session - assert.Equal(t, fields[8], pid) // tpgrp - assert.Equal(t, fields[18], "20") // priority - assert.Equal(t, fields[20], "1") // num threads - assert.Equal(t, fields[52], "0") // exit code + pids, err := ioutil.ReadDir("testdata/linux/") + if err != nil { + t.Error(err) + } + f := common.MockEnv("HOST_PROC", "testdata/linux") + defer f() + for _, pid := range pids { + pid, err := strconv.ParseInt(pid.Name(), 0, 32) + if err != nil{ + continue + } + statFile := fmt.Sprintf("testdata/linux/%d/stat", pid) + if _, err := os.Stat(statFile); err != nil{ + continue + } + contents, err := ioutil.ReadFile(statFile) + assert.NoError(t, err) + + pidStr := strconv.Itoa(int(pid)) + + ppid := "68044" // TODO: how to pass ppid to test? + + fields := splitProcStat(contents) + assert.Equal(t, fields[1], pidStr) + assert.Equal(t, fields[2], "test(cmd).sh") + assert.Equal(t, fields[3], "S") + assert.Equal(t, fields[4], ppid) + assert.Equal(t, fields[5], pidStr) // pgrp + assert.Equal(t, fields[6], ppid) // session + assert.Equal(t, fields[8], pidStr) // tpgrp + assert.Equal(t, fields[18], "20") // priority + assert.Equal(t, fields[20], "1") // num threads + assert.Equal(t, fields[52], "0") // exit code + } } func Test_fillFromStatusWithContext(t *testing.T) { @@ -83,9 +102,14 @@ func Test_fillFromStatusWithContext(t *testing.T) { f := common.MockEnv("HOST_PROC", "testdata/linux") defer f() for _, pid := range pids { - pid, _ := strconv.ParseInt(pid.Name(), 0, 32) + pid, err := strconv.ParseInt(pid.Name(), 0, 32) + if err != nil{ + continue + } + if _, err := os.Stat(fmt.Sprintf("testdata/linux/%d/status", pid)); err != nil{ + continue + } p, _ := NewProcess(int32(pid)) - if err := p.fillFromStatusWithContext(context.Background()); err != nil { t.Error(err) } diff --git a/v3/process/testdata/linux/68927/stat b/v3/process/testdata/linux/68927/stat new file mode 100644 index 00000000..6f3a7d0d --- /dev/null +++ b/v3/process/testdata/linux/68927/stat @@ -0,0 +1 @@ +68927 (test(cmd).sh) S 68044 68927 68044 34818 68927 4194304 165 0 0 0 0 0 0 0 20 0 1 0 114413973 9961472 868 18446744073709551615 94388826710016 94388827626021 140725039102800 0 0 0 2 4 65536 1 0 0 17 1 0 0 0 0 0 94388827875984 94388827924080 94388835627008 140725039105503 140725039105528 140725039105528 140725039108073 0