From b36a5372259f946783171cb8acec4a597345948e Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Tue, 31 Mar 2020 20:13:21 +0200 Subject: [PATCH 1/2] internal/proc: parse /proc/$PID/stat correctly Parsing the command name properly requires some effort: we need to determine the slice between the first ( and the last(!) ) in order to support command names which contain white space or closing parentheses. Fixes #66. Signed-off-by: Leah Neukirchen --- internal/proc/stat.go | 21 ++++++++++++++++----- internal/proc/stat_test.go | 34 +++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/internal/proc/stat.go b/internal/proc/stat.go index 866a5cd..e328670 100644 --- a/internal/proc/stat.go +++ b/internal/proc/stat.go @@ -15,6 +15,7 @@ package proc import ( + "errors" "fmt" "io/ioutil" "strings" @@ -112,21 +113,31 @@ type Stat struct { } // readStat is used for mocking in unit tests. -var readStat = func(path string) ([]string, error) { - data, err := ioutil.ReadFile(path) +var readStat = func(path string) (string, error) { + rawData, err := ioutil.ReadFile(path) if err != nil { - return nil, err + return "", err } - return strings.Fields(string(data)), nil + return string(rawData), nil } // ParseStat parses the /proc/$pid/stat file and returns a Stat. func ParseStat(pid string) (*Stat, error) { - fields, err := readStat(fmt.Sprintf("/proc/%s/stat", pid)) + data, err := readStat(fmt.Sprintf("/proc/%s/stat", pid)) if err != nil { return nil, err } + firstParen := strings.IndexByte(data, '(') + lastParen := strings.LastIndexByte(data, ')') + if firstParen == -1 || lastParen == -1 { + return nil, errors.New("invalid format in stat") + } + pidstr := data[0 : firstParen-1] + comm := data[firstParen+1 : lastParen] + rest := strings.Fields(data[lastParen+1:]) + fields := append([]string{pidstr, comm}, rest...) + fieldAt := func(i int) string { return fields[i-1] } diff --git a/internal/proc/stat_test.go b/internal/proc/stat_test.go index 1297864..708aa37 100644 --- a/internal/proc/stat_test.go +++ b/internal/proc/stat_test.go @@ -15,28 +15,38 @@ package proc import ( - "strings" + "errors" "testing" "github.com/stretchr/testify/assert" ) var statFile = "31404 (gedit) R 2109 2128 2128 0 -1 4194304 13153 328 0 0 590 55 0 0 20 0 6 0 1331588 419667968 19515 18446744073709551615 94120519110656 94120519115256 140737253236304 0 0 0 0 4096 0 0 0 0 17 2 0 0 62588346 0 0 94120521215368 94120521216168 94120544436224 140737253242331 140737253242369 140737253242369 140737253244905 0" +var statFileSpace = "31405 (ge d it) R 2109 2128 2128 0 -1 4194304 13153 328 0 0 590 55 0 0 20 0 6 0 1331588 419667968 19515 18446744073709551615 94120519110656 94120519115256 140737253236304 0 0 0 0 4096 0 0 0 0 17 2 0 0 62588346 0 0 94120521215368 94120521216168 94120544436224 140737253242331 140737253242369 140737253242369 140737253244905 0" +var statFileParen = "31406 (ged)it) R 2109 2128 2128 0 -1 4194304 13153 328 0 0 590 55 0 0 20 0 6 0 1331588 419667968 19515 18446744073709551615 94120519110656 94120519115256 140737253236304 0 0 0 0 4096 0 0 0 0 17 2 0 0 62588346 0 0 94120521215368 94120521216168 94120544436224 140737253242331 140737253242369 140737253242369 140737253244905 0" -func testReadStat(_ string) ([]string, error) { - return strings.Fields(statFile), nil +func testReadStat(file string) (string, error) { + switch file { + case "/proc/31404/stat": + return statFile, nil + case "/proc/31405/stat": + return statFileSpace, nil + case "/proc/31406/stat": + return statFileParen, nil + } + return "", errors.New("unimplemented test case") } func TestParseStat(t *testing.T) { readStat = testReadStat - s, err := ParseStat("") + s, err := ParseStat("31404") assert.Nil(t, err) assert.NotNil(t, s) assert.Equal(t, "31404", s.Pid) - assert.Equal(t, "(gedit)", s.Comm) + assert.Equal(t, "gedit", s.Comm) assert.Equal(t, "R", s.State) assert.Equal(t, "2109", s.Ppid) assert.Equal(t, "2128", s.Pgrp) @@ -58,4 +68,18 @@ func TestParseStat(t *testing.T) { assert.Equal(t, "0", s.Itrealvalue) assert.Equal(t, "1331588", s.Starttime) assert.Equal(t, "419667968", s.Vsize) + + s2, err := ParseStat("31405") + + assert.Nil(t, err) + assert.NotNil(t, s2) + + assert.Equal(t, "ge d it", s2.Comm) + + s3, err := ParseStat("31406") + + assert.Nil(t, err) + assert.NotNil(t, s3) + + assert.Equal(t, "ged)it", s3.Comm) } From d8c41f5bf4f83e75288d3613900db25c76a69648 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Fri, 3 Apr 2020 11:45:09 +0200 Subject: [PATCH 2/2] process: ElapsedTime: fix goclang-ci warning Signed-off-by: Leah Neukirchen --- internal/process/process.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/process/process.go b/internal/process/process.go index a936cc4..b46a39f 100644 --- a/internal/process/process.go +++ b/internal/process/process.go @@ -192,7 +192,7 @@ func (p *Process) ElapsedTime() (time.Duration, error) { if err != nil { return 0, err } - return (time.Now()).Sub(startTime), nil + return time.Since(startTime), nil } // StarTime returns the time.Time when process p was started.