From d435e368347f547623d7aed7e161b6bb2650aa6b Mon Sep 17 00:00:00 2001 From: Peter Bourgon Date: Wed, 10 Jun 2015 17:09:05 +0200 Subject: [PATCH] Add and populate Process topology Also, add comm value (name) to process node metadata. --- probe/main.go | 16 ++++++++----- probe/tag/docker_tagger.go | 5 +++++ probe/tag/pidtree.go | 36 ++++++++++++++++++++++++++---- report/detailed_node.go | 21 ++++++++++++++++++ report/detailed_node_test.go | 27 +++++++--------------- report/merge.go | 1 + report/report.go | 5 +++++ report/report_fixture_test.go | 42 +++++++++++++++++------------------ 8 files changed, 103 insertions(+), 50 deletions(-) diff --git a/probe/main.go b/probe/main.go index 89bd245b3..34272fe3a 100644 --- a/probe/main.go +++ b/probe/main.go @@ -29,7 +29,7 @@ func main() { listen = flag.String("listen", ":"+strconv.Itoa(xfer.ProbePort), "listen address") prometheusEndpoint = flag.String("prometheus.endpoint", "/metrics", "Prometheus metrics exposition endpoint (requires -http.listen)") spyProcs = flag.Bool("processes", true, "report processes (needs root)") - dockerTagger = flag.Bool("docker", true, "collect Docker-related attributes for processes") + dockerEnabled = flag.Bool("docker", true, "collect Docker-related attributes for processes") dockerInterval = flag.Duration("docker.interval", 10*time.Second, "how often to update Docker attributes") procRoot = flag.String("proc.root", "/proc", "location of the proc filesystem") ) @@ -65,13 +65,15 @@ func main() { defer publisher.Close() taggers := []tag.Tagger{tag.NewTopologyTagger()} - if *dockerTagger && runtime.GOOS == "linux" { - t, err := tag.NewDockerTagger(*procRoot, *dockerInterval) + var dockerTagger *tag.DockerTagger + if *dockerEnabled && runtime.GOOS == "linux" { + var err error + dockerTagger, err = tag.NewDockerTagger(*procRoot, *dockerInterval) if err != nil { log.Fatalf("failed to start docker tagger: %v", err) } - defer t.Stop() - taggers = append(taggers, t) + defer dockerTagger.Stop() + taggers = append(taggers, dockerTagger) } log.Printf("listening on %s", *listen) @@ -97,8 +99,10 @@ func main() { case <-spyTick: r.Merge(spy(hostID, hostName, *spyProcs)) + if dockerTagger != nil { + r.Process.Merge(dockerTagger.ProcessTopology(hostID)) + } r = tag.Apply(r, taggers) - // log.Printf("merged report:\n%#v\n", r) case <-quit: return diff --git a/probe/tag/docker_tagger.go b/probe/tag/docker_tagger.go index a9072eb3c..7980e0276 100644 --- a/probe/tag/docker_tagger.go +++ b/probe/tag/docker_tagger.go @@ -64,6 +64,11 @@ func (t *DockerTagger) Stop() { close(t.quit) } +// ProcessTopology returns a process topology from the pidtree. +func (t *DockerTagger) ProcessTopology(hostID string) report.Topology { + return t.pidTree.processTopology(hostID) +} + func (t *DockerTagger) loop() { if !t.update() { return diff --git a/probe/tag/pidtree.go b/probe/tag/pidtree.go index fe665be9a..88b010ce5 100644 --- a/probe/tag/pidtree.go +++ b/probe/tag/pidtree.go @@ -6,6 +6,8 @@ import ( "path" "strconv" "strings" + + "github.com/weaveworks/scope/report" ) type pidTree struct { @@ -16,6 +18,7 @@ type process struct { pid, ppid int parent *process children []*process + comm string } // Hooks for mocking @@ -32,23 +35,32 @@ func newPIDTree(procRoot string) (*pidTree, error) { pt := pidTree{processes: map[int]*process{}} for _, dirEntry := range dirEntries { - pid, err := strconv.Atoi(dirEntry.Name()) + filename := dirEntry.Name() + pid, err := strconv.Atoi(filename) if err != nil { continue } - stat, err := readFile(path.Join(procRoot, dirEntry.Name(), "stat")) + stat, err := readFile(path.Join(procRoot, filename, "stat")) if err != nil { continue } - splits := strings.Split(string(stat), " ") ppid, err := strconv.Atoi(splits[3]) if err != nil { return nil, err } - pt.processes[pid] = &process{pid: pid, ppid: ppid} + comm := "(unknown)" + if commBuf, err := readFile(path.Join(procRoot, filename, "comm")); err == nil { + comm = string(commBuf) + } + + pt.processes[pid] = &process{ + pid: pid, + ppid: ppid, + comm: comm, + } } for _, child := range pt.processes { @@ -93,3 +105,19 @@ func (pt *pidTree) allChildren(pid int) ([]int, error) { f(proc) return result, nil } + +func (pt *pidTree) processTopology(hostID string) report.Topology { + t := report.NewTopology() + for pid, proc := range pt.processes { + pidstr := strconv.Itoa(pid) + nodeID := report.MakeProcessNodeID(hostID, pidstr) + t.NodeMetadatas[nodeID] = report.NodeMetadata{ + "pid": pidstr, + "comm": proc.comm, + } + if proc.ppid > 0 { + t.NodeMetadatas[nodeID]["ppid"] = strconv.Itoa(proc.ppid) + } + } + return t +} diff --git a/report/detailed_node.go b/report/detailed_node.go index 193c55b79..1490176ac 100644 --- a/report/detailed_node.go +++ b/report/detailed_node.go @@ -52,6 +52,9 @@ func OriginTable(r Report, originID string) (Table, bool) { if nmd, ok := r.Address.NodeMetadatas[originID]; ok { return addressOriginTable(nmd) } + if nmd, ok := r.Process.NodeMetadatas[originID]; ok { + return processOriginTable(nmd) + } if nmd, ok := r.Host.NodeMetadatas[originID]; ok { return hostOriginTable(nmd) } @@ -97,6 +100,24 @@ func addressOriginTable(nmd NodeMetadata) (Table, bool) { }, len(rows) > 0 } +func processOriginTable(nmd 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, ""}) + } + return Table{ + Title: "Origin Process", + Numeric: false, + Rows: rows, + }, len(rows) > 0 +} + func hostOriginTable(nmd NodeMetadata) (Table, bool) { rows := []Row{} if val, ok := nmd["host_name"]; ok { diff --git a/report/detailed_node_test.go b/report/detailed_node_test.go index deaa03ce2..a69980c63 100644 --- a/report/detailed_node_test.go +++ b/report/detailed_node_test.go @@ -21,13 +21,6 @@ func TestOriginTable(t *testing.T) { Numeric: false, Rows: []report.Row{{"Host name", clientHostName, ""}}, }, - //report.MakeProcessNodeID(clientHostID, "4242"): { - // Title: "Origin Process", - // Numeric: false, - // Rows: []report.Row{ - // {"Host name", "client.host.com", ""}, - // }, - //}, clientAddressNodeID: { Title: "Origin Address", Numeric: false, @@ -35,18 +28,14 @@ func TestOriginTable(t *testing.T) { {"Host name", clientHostName, ""}, }, }, - //report.MakeProcessNodeID(clientHostID, "4242"): { - // Title: "Origin Process", - // Numeric: false, - // Rows: []report.Row{ - // {"Process name", "curl", ""}, - // {"PID", "4242", ""}, - // {"Docker container ID", "a1b2c3d4e5", ""}, - // {"Docker container name", "fixture-container", ""}, - // {"Docker image ID", "0000000000", ""}, - // {"Docker image name", "fixture/container:latest", ""}, - // }, - //}, + report.MakeProcessNodeID(clientHostID, "4242"): { + Title: "Origin Process", + Numeric: false, + Rows: []report.Row{ + {"Name (comm)", "curl", ""}, + {"PID", "4242", ""}, + }, + }, serverHostNodeID: { Title: "Origin Host", Numeric: false, diff --git a/report/merge.go b/report/merge.go index 9bce1a863..8117af21d 100644 --- a/report/merge.go +++ b/report/merge.go @@ -8,6 +8,7 @@ package report func (r *Report) Merge(other Report) { r.Endpoint.Merge(other.Endpoint) r.Address.Merge(other.Address) + r.Process.Merge(other.Process) r.Host.Merge(other.Host) } diff --git a/report/report.go b/report/report.go index c3b0827eb..a20b8e5de 100644 --- a/report/report.go +++ b/report/report.go @@ -19,6 +19,9 @@ type Report struct { // endpoints (e.g. ICMP). Edges are present. Address Topology + // Process nodes are processes on each host. Edges are not present. + Process Topology + // Host nodes are physical hosts that run probes. Metadata includes things // like operating system, load, etc. The information is scraped by the // probes with each published report. Edges are not present. @@ -69,6 +72,7 @@ func MakeReport() Report { return Report{ Endpoint: NewTopology(), Address: NewTopology(), + Process: NewTopology(), Host: NewTopology(), } } @@ -79,6 +83,7 @@ func (r Report) Squash() Report { localNetworks := r.LocalNetworks() r.Endpoint = r.Endpoint.Squash(EndpointIDAddresser, localNetworks) r.Address = r.Address.Squash(AddressIDAddresser, localNetworks) + r.Process = r.Process.Squash(PanicIDAddresser, localNetworks) r.Host = r.Host.Squash(PanicIDAddresser, localNetworks) return r } diff --git a/report/report_fixture_test.go b/report/report_fixture_test.go index 479d8d5a1..72bd86bed 100644 --- a/report/report_fixture_test.go +++ b/report/report_fixture_test.go @@ -101,27 +101,27 @@ var reportFixture = report.Report{ }, }, }, - //Process: report.Topology{ - // Adjacency: report.Adjacency{}, - // NodeMetadatas: report.NodeMetadatas{ - // report.MakeProcessNodeID(clientHostID, "4242"): report.NodeMetadata{ - // "host_name": "client.host.com", - // "pid": "4242", - // "process_name": "curl", - // "docker_container_id": "a1b2c3d4e5", - // "docker_container_name": "fixture-container", - // "docker_image_id": "0000000000", - // "docker_image_name": "fixture/container:latest", - // }, - // report.MakeProcessNodeID(serverHostID, "215"): report.NodeMetadata{ - // "pid": "215", - // "process_name": "apache", - // }, - // - // "no-container": report.NodeMetadata{}, - // }, - // EdgeMetadatas: report.EdgeMetadatas{}, - //}, + Process: report.Topology{ + Adjacency: report.Adjacency{}, + NodeMetadatas: report.NodeMetadatas{ + report.MakeProcessNodeID(clientHostID, "4242"): report.NodeMetadata{ + "host_name": "client.host.com", + "pid": "4242", + "comm": "curl", + "docker_container_id": "a1b2c3d4e5", + "docker_container_name": "fixture-container", + "docker_image_id": "0000000000", + "docker_image_name": "fixture/container:latest", + }, + report.MakeProcessNodeID(serverHostID, "215"): report.NodeMetadata{ + "pid": "215", + "process_name": "apache", + }, + + "no-container": report.NodeMetadata{}, + }, + EdgeMetadatas: report.EdgeMetadatas{}, + }, Host: report.Topology{ Adjacency: report.Adjacency{}, NodeMetadatas: report.NodeMetadatas{