Merge pull request #1079 from shirou/feature/fix_1056_tests_and_copy_to_v2

[process][linux] fix 1056 test and copy to v2
pull/1080/head
shirou 4 years ago committed by GitHub
commit d5e9031b2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -999,28 +999,24 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui
if err != nil { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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 { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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 { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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 { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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 { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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 // 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 // will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux
// docs). Note: I am assuming at least Linux 2.6.18 // 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 { if err != nil {
iotime = 0 // Ancient linux version, most likely 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) bootTime, _ := common.BootTimeWithContext(ctx)
t, err := strconv.ParseUint(fields[i+20], 10, 64) t, err := strconv.ParseUint(fields[22], 10, 64)
if err != nil { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err return 0, 0, nil, 0, 0, 0, nil, err
} }
ctime := (t / uint64(ClockTicks)) + uint64(bootTime) ctime := (t / uint64(ClockTicks)) + uint64(bootTime)
createTime := int64(ctime * 1000) createTime := int64(ctime * 1000)
rtpriority, err := strconv.ParseInt(fields[i+16], 10, 32) rtpriority, err := strconv.ParseInt(fields[18], 10, 32)
if err != nil { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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)) snice, _ := unix.Getpriority(PrioProcess, int(pid))
nice := int32(snice) // FIXME: is this true? 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 { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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 { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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 { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err 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 { if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err return 0, 0, nil, 0, 0, 0, nil, err
} }
@ -1141,3 +1137,16 @@ func readPidsFromDir(path string) ([]int32, error) {
return ret, nil 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
}

@ -4,13 +4,96 @@ package process
import ( import (
"context" "context"
"fmt"
"io/ioutil" "io/ioutil"
"os"
"strconv" "strconv"
"strings"
"testing" "testing"
"github.com/shirou/gopsutil/internal/common" "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) { func Test_fillFromStatusWithContext(t *testing.T) {
pids, err := ioutil.ReadDir("testdata/linux/") pids, err := ioutil.ReadDir("testdata/linux/")
if err != nil { if err != nil {
@ -19,9 +102,14 @@ func Test_fillFromStatusWithContext(t *testing.T) {
f := common.MockEnv("HOST_PROC", "testdata/linux") f := common.MockEnv("HOST_PROC", "testdata/linux")
defer f() defer f()
for _, pid := range pids { 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)) p, _ := NewProcess(int32(pid))
if err := p.fillFromStatusWithContext(context.Background()); err != nil { if err := p.fillFromStatusWithContext(context.Background()); err != nil {
t.Error(err) t.Error(err)
} }

@ -3,6 +3,7 @@
package process package process
import ( import (
"os"
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -57,22 +58,40 @@ func Test_Process_splitProcStat(t *testing.T) {
} }
func Test_Process_splitProcStat_fromFile(t *testing.T) { func Test_Process_splitProcStat_fromFile(t *testing.T) {
pid := "68927" pids, err := ioutil.ReadDir("testdata/linux/")
ppid := "68044" if err != nil {
statFile := "testdata/linux/proc/" + pid + "/stat" 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) contents, err := ioutil.ReadFile(statFile)
assert.NoError(t, err) assert.NoError(t, err)
pidStr := strconv.Itoa(int(pid))
ppid := "68044" // TODO: how to pass ppid to test?
fields := splitProcStat(contents) fields := splitProcStat(contents)
assert.Equal(t, fields[1], pid) assert.Equal(t, fields[1], pidStr)
assert.Equal(t, fields[2], "test(cmd).sh") assert.Equal(t, fields[2], "test(cmd).sh")
assert.Equal(t, fields[3], "S") assert.Equal(t, fields[3], "S")
assert.Equal(t, fields[4], ppid) assert.Equal(t, fields[4], ppid)
assert.Equal(t, fields[5], pid) // pgrp assert.Equal(t, fields[5], pidStr) // pgrp
assert.Equal(t, fields[6], ppid) // session assert.Equal(t, fields[6], ppid) // session
assert.Equal(t, fields[8], pid) // tpgrp assert.Equal(t, fields[8], pidStr) // tpgrp
assert.Equal(t, fields[18], "20") // priority assert.Equal(t, fields[18], "20") // priority
assert.Equal(t, fields[20], "1") // num threads assert.Equal(t, fields[20], "1") // num threads
assert.Equal(t, fields[52], "0") // exit code assert.Equal(t, fields[52], "0") // exit code
}
} }
func Test_fillFromStatusWithContext(t *testing.T) { func Test_fillFromStatusWithContext(t *testing.T) {
@ -83,9 +102,14 @@ func Test_fillFromStatusWithContext(t *testing.T) {
f := common.MockEnv("HOST_PROC", "testdata/linux") f := common.MockEnv("HOST_PROC", "testdata/linux")
defer f() defer f()
for _, pid := range pids { 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)) p, _ := NewProcess(int32(pid))
if err := p.fillFromStatusWithContext(context.Background()); err != nil { if err := p.fillFromStatusWithContext(context.Background()); err != nil {
t.Error(err) t.Error(err)
} }

@ -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
Loading…
Cancel
Save