diff --git a/Makefile b/Makefile index 9e837cba1a..eb131de45d 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ $(SCOPE_EXPORT): $(APP_EXE) $(PROBE_EXE) docker/* $(APP_EXE): app/*.go render/*.go report/*.go xfer/*.go -$(PROBE_EXE): probe/*.go probe/tag/*.go probe/docker/*.go report/*.go xfer/*.go +$(PROBE_EXE): probe/*.go probe/tag/*.go probe/docker/*.go probe/process/*.go report/*.go xfer/*.go $(APP_EXE) $(PROBE_EXE): go get -tags netgo ./$(@D) diff --git a/probe/process/reporter.go b/probe/process/reporter.go index 1007efec37..f95a6b960c 100644 --- a/probe/process/reporter.go +++ b/probe/process/reporter.go @@ -8,9 +8,10 @@ import ( // We use these keys in node metadata const ( - PID = "pid" - Comm = "comm" - PPID = "ppid" + PID = "pid" + Comm = "comm" + PPID = "ppid" + Cmdline = "cmdline" ) // Reporter generate Reports containing the Process topology @@ -44,8 +45,9 @@ func (r *Reporter) processTopology() (report.Topology, error) { pidstr := strconv.Itoa(p.PID) nodeID := report.MakeProcessNodeID(r.scope, pidstr) t.NodeMetadatas[nodeID] = report.NodeMetadata{ - PID: pidstr, - Comm: p.Comm, + PID: pidstr, + Comm: p.Comm, + Cmdline: p.Cmdline, } if p.PPID > 0 { t.NodeMetadatas[nodeID][PPID] = strconv.Itoa(p.PPID) diff --git a/probe/process/reporter_test.go b/probe/process/reporter_test.go index e0300027c3..454e560f8d 100644 --- a/probe/process/reporter_test.go +++ b/probe/process/reporter_test.go @@ -18,7 +18,7 @@ func TestReporter(t *testing.T) { {PID: 1, PPID: 0, Comm: "init"}, {PID: 2, PPID: 1, Comm: "bash"}, {PID: 3, PPID: 1, Comm: "apache"}, - {PID: 4, PPID: 2, Comm: "ping"}, + {PID: 4, PPID: 2, Comm: "ping", Cmdline: "ping foo.bar.local"}, } { f(p) } @@ -32,23 +32,27 @@ func TestReporter(t *testing.T) { EdgeMetadatas: report.EdgeMetadatas{}, NodeMetadatas: report.NodeMetadatas{ report.MakeProcessNodeID("", "1"): report.NodeMetadata{ - process.PID: "1", - process.Comm: "init", + process.PID: "1", + process.Comm: "init", + process.Cmdline: "", }, report.MakeProcessNodeID("", "2"): report.NodeMetadata{ - process.PID: "2", - process.Comm: "bash", - process.PPID: "1", + process.PID: "2", + process.Comm: "bash", + process.PPID: "1", + process.Cmdline: "", }, report.MakeProcessNodeID("", "3"): report.NodeMetadata{ - process.PID: "3", - process.Comm: "apache", - process.PPID: "1", + process.PID: "3", + process.Comm: "apache", + process.PPID: "1", + process.Cmdline: "", }, report.MakeProcessNodeID("", "4"): report.NodeMetadata{ - process.PID: "4", - process.Comm: "ping", - process.PPID: "2", + process.PID: "4", + process.Comm: "ping", + process.PPID: "2", + process.Cmdline: "ping foo.bar.local", }, }, } diff --git a/probe/process/tree.go b/probe/process/tree.go index 940e21e33a..080b84b8e4 100644 --- a/probe/process/tree.go +++ b/probe/process/tree.go @@ -20,21 +20,7 @@ func NewTree(procRoot string) (Tree, error) { pt.processes[p.PID] = p }) - if err != nil { - return nil, err - } - - for _, child := range pt.processes { - parent, ok := pt.processes[child.PPID] - if !ok { - // This can happen as listing proc is not a consistent snapshot - continue - } - child.parent = parent - parent.children = append(parent.children, child) - } - - return &pt, nil + return &pt, err } // GetParent returns the pid of the parent process for a given pid diff --git a/probe/process/walker.go b/probe/process/walker.go index 40e9b5df50..d6a25fc72f 100644 --- a/probe/process/walker.go +++ b/probe/process/walker.go @@ -1,7 +1,9 @@ package process import ( + "bytes" "io/ioutil" + "log" "path" "strconv" "strings" @@ -11,8 +13,7 @@ import ( type Process struct { PID, PPID int Comm string - parent *Process - children []*Process + Cmdline string } // Hooks exposed for mocking @@ -45,15 +46,22 @@ var Walk = func(procRoot string, f func(*Process)) error { return err } + cmdline := "" + if cmdlineBuf, err := ReadFile(path.Join(procRoot, filename, "cmdline")); err == nil { + cmdlineBuf = bytes.Replace(cmdlineBuf, []byte{'\000'}, []byte{' '}, -1) + cmdline = string(cmdlineBuf) + } + comm := "(unknown)" if commBuf, err := ReadFile(path.Join(procRoot, filename, "comm")); err == nil { comm = string(commBuf) } f(&Process{ - PID: pid, - PPID: ppid, - Comm: comm, + PID: pid, + PPID: ppid, + Comm: comm, + Cmdline: cmdline, }) } diff --git a/probe/process/walker_test.go b/probe/process/walker_test.go index ffc82f068c..afb12d0587 100644 --- a/probe/process/walker_test.go +++ b/probe/process/walker_test.go @@ -13,16 +13,17 @@ import ( "github.com/weaveworks/scope/test" ) -type fileinfo struct { - name string +type mockProcess struct { + name string + cmdline string } -func (f fileinfo) Name() string { return f.name } -func (f fileinfo) Size() int64 { return 0 } -func (f fileinfo) Mode() os.FileMode { return 0 } -func (f fileinfo) ModTime() time.Time { return time.Now() } -func (f fileinfo) IsDir() bool { return true } -func (f fileinfo) Sys() interface{} { return nil } +func (p mockProcess) Name() string { return p.name } +func (p mockProcess) Size() int64 { return 0 } +func (p mockProcess) Mode() os.FileMode { return 0 } +func (p mockProcess) ModTime() time.Time { return time.Now() } +func (p mockProcess) IsDir() bool { return true } +func (p mockProcess) Sys() interface{} { return nil } func TestWalker(t *testing.T) { oldReadDir, oldReadFile := process.ReadDir, process.ReadFile @@ -31,36 +32,54 @@ func TestWalker(t *testing.T) { process.ReadFile = oldReadFile }() + processes := map[string]mockProcess{ + "3": {name: "3", cmdline: "curl"}, + "2": {name: "2"}, + "4": {name: "4"}, + "notapid": {name: "notapid"}, + "1": {name: "1"}, + } + process.ReadDir = func(path string) ([]os.FileInfo, error) { - return []os.FileInfo{ - fileinfo{"3"}, fileinfo{"2"}, fileinfo{"4"}, - fileinfo{"notapid"}, fileinfo{"1"}, - }, nil + result := []os.FileInfo{} + for _, p := range processes { + result = append(result, p) + } + return result, nil } process.ReadFile = func(path string) ([]byte, error) { splits := strings.Split(path, "/") - if splits[len(splits)-1] != "stat" { - return nil, fmt.Errorf("not stat") + + pid := splits[len(splits)-2] + process, ok := processes[pid] + if !ok { + return nil, fmt.Errorf("not found") } - pid, err := strconv.Atoi(splits[len(splits)-2]) - if err != nil { - return nil, err + + file := splits[len(splits)-1] + switch file { + case "stat": + pid, _ := strconv.Atoi(splits[len(splits)-2]) + parent := pid - 1 + return []byte(fmt.Sprintf("%d na R %d", pid, parent)), nil + case "cmdline": + return []byte(process.cmdline), nil } - parent := pid - 1 - return []byte(fmt.Sprintf("%d na R %d", pid, parent)), nil + + return nil, fmt.Errorf("not found") } - want := []*process.Process{ - {PID: 3, PPID: 2, Comm: "(unknown)"}, - {PID: 2, PPID: 1, Comm: "(unknown)"}, - {PID: 4, PPID: 3, Comm: "(unknown)"}, - {PID: 1, PPID: 0, Comm: "(unknown)"}, + want := map[int]*process.Process{ + 3: {PID: 3, PPID: 2, Comm: "(unknown)", Cmdline: "curl"}, + 2: {PID: 2, PPID: 1, Comm: "(unknown)", Cmdline: ""}, + 4: {PID: 4, PPID: 3, Comm: "(unknown)", Cmdline: ""}, + 1: {PID: 1, PPID: 0, Comm: "(unknown)", Cmdline: ""}, } - have := []*process.Process{} + have := map[int]*process.Process{} err := process.Walk("unused", func(p *process.Process) { - have = append(have, p) + have[p.PID] = p }) if err != nil || !reflect.DeepEqual(want, have) { diff --git a/render/detailed_node.go b/render/detailed_node.go index fcb362b037..20f8f86af3 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/weaveworks/scope/probe/docker" + "github.com/weaveworks/scope/probe/process" "github.com/weaveworks/scope/report" ) @@ -139,15 +140,17 @@ func addressOriginTable(nmd report.NodeMetadata) (Table, bool) { func processOriginTable(nmd report.NodeMetadata) (Table, bool) { rows := []Row{} - if val, ok := nmd["comm"]; ok { - rows = append(rows, Row{"Name (comm)", val, ""}) - } - if val, ok := nmd["pid"]; ok { - rows = append(rows, Row{"PID", val, ""}) - } - if val, ok := nmd["ppid"]; ok { - rows = append(rows, Row{"Parent PID", val, ""}) + for _, tuple := range []struct{ key, human string }{ + {process.Comm, "Name (comm)"}, + {process.PID, "PID"}, + {process.PPID, "Parent PID"}, + {process.Cmdline, "Command"}, + } { + if val, ok := nmd[tuple.key]; ok { + rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) + } } + return Table{ Title: "Origin Process", Numeric: false,