Add and populate Process topology

Also, add comm value (name) to process node metadata.
This commit is contained in:
Peter Bourgon
2015-06-10 17:09:05 +02:00
parent d9fbaf348c
commit d435e36834
8 changed files with 103 additions and 50 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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{