Skip to content

Commit

Permalink
Produce the container topology by way of the process topology.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Wilkie committed Jun 16, 2015
1 parent 8051a71 commit 8b57567
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 98 deletions.
9 changes: 3 additions & 6 deletions app/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,9 @@ var topologyRegistry = map[string]topologyView{
renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessName, Pseudo: render.GenericGroupedPseudoNode},
},
"containers": {
human: "Containers",
parent: "",
renderer: render.Reduce([]render.Renderer{
render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.MapEndpoint2Container, Pseudo: render.InternetOnlyPseudoNode},
render.LeafMap{Selector: report.SelectContainer, Mapper: render.MapContainerIdentity, Pseudo: render.InternetOnlyPseudoNode},
}),
human: "Containers",
parent: "",
renderer: render.ContainerRenderer,
},
"containers-by-image": {
human: "by image",
Expand Down
6 changes: 5 additions & 1 deletion probe/spy.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ func addConnection(
if _, ok := r.Endpoint.NodeMetadatas[scopedLocal]; !ok {
// First hit establishes NodeMetadata for scoped local address + port
md := report.NodeMetadata{
"pid": fmt.Sprintf("%d", c.Proc.PID),
"addr": c.LocalAddress.String(),
"port": strconv.Itoa(int(c.LocalPort)),
"pid": fmt.Sprintf("%d", c.Proc.PID),

// TODO: These can go away once we derives process graph from process topology
"name": c.Proc.Name,
"domain": hostID,
}
Expand Down
129 changes: 85 additions & 44 deletions render/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,35 @@ import (
const humanTheInternet = "the Internet"

// NewRenderableNode makes a new RenderableNode
func NewRenderableNode(id, major, minor, rank string) RenderableNode {
func NewRenderableNode(id, major, minor, rank string, nmd report.NodeMetadata) RenderableNode {
return RenderableNode{
ID: id,
LabelMajor: major,
LabelMinor: minor,
Rank: rank,
Pseudo: false,
Metadata: report.AggregateMetadata{},
ID: id,
LabelMajor: major,
LabelMinor: minor,
Rank: rank,
Pseudo: false,
Metadata: report.AggregateMetadata{},
NodeMetadata: nmd,
}
}

func newPseudoNode(id, major, minor string) RenderableNode {
return RenderableNode{
ID: id,
LabelMajor: major,
LabelMinor: minor,
Rank: "",
Pseudo: true,
Metadata: report.AggregateMetadata{},
ID: id,
LabelMajor: major,
LabelMinor: minor,
Rank: "",
Pseudo: true,
Metadata: report.AggregateMetadata{},
NodeMetadata: report.NodeMetadata{},
}
}

const (
uncontainedID = "uncontained"
uncontainedMajor = "Uncontained"
)

// LeafMapFunc is anything which can take an arbitrary NodeMetadata, which is
// always one-to-one with nodes in a topology, and return a specific
// representation of the referenced node, in the form of a node ID and a
Expand All @@ -54,6 +61,67 @@ type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string)
// return another RenderableNode.
type MapFunc func(RenderableNode) (RenderableNode, bool)

// MapEndpointIdentity maps a endpoint topology node to endpoint RenderableNode node.
// As it is only ever run on endpoint topology nodes, we can safely assume the
// presences of certain keys.
func MapEndpointIdentity(m report.NodeMetadata) (RenderableNode, bool) {
var (
id = fmt.Sprintf("endpoint:%s:%s:%s", m[report.HostNodeID], m["addr"], m["port"])
major = fmt.Sprintf("%s:%s", m["addr"], m["port"])
minor = fmt.Sprintf("%s (%s)", m[report.HostNodeID], m["pid"])
rank = m["pid"]
)
return NewRenderableNode(id, major, minor, rank, m), true
}

// MapProcessIdentity maps a process topology node to process RenderableNode node.
// As it is only ever run on process topology nodes, we can safely assume the
// presences of certain keys.
func MapProcessIdentity(m report.NodeMetadata) (RenderableNode, bool) {
var (
id = fmt.Sprintf("pid:%s:%s", m[report.HostNodeID], m["pid"])
major = m["comm"]
minor = fmt.Sprintf("%s (%s)", m[report.HostNodeID], m["pid"])
rank = m["pid"]
)

return NewRenderableNode(id, major, minor, rank, m), true
}

// MapContainerIdentity maps a container topology node to container RenderableNode node.
// As it is only ever run on container topology nodes, we can safely assume the
// presences of certain keys.
func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) {
var (
id = m["docker_container_id"]
major = m["docker_container_name"]
minor = m[report.HostNodeID]
rank = m["docker_image_id"]
)

return NewRenderableNode(id, major, minor, rank, m), true
}

// MapEndpoint2Process maps endpoint RenderableNodes to process RenderableNodes.
func MapEndpoint2Process(n RenderableNode) (RenderableNode, bool) {
var (
id = fmt.Sprintf("pid:%s:%s", n.NodeMetadata[report.HostNodeID], n.NodeMetadata["pid"])
major, minor, rank = "", "", ""
)

return NewRenderableNode(id, major, minor, rank, n.NodeMetadata), true
}

// MapProcess2Container maps process RenderableNodes to container RenderableNodes.
func MapProcess2Container(n RenderableNode) (RenderableNode, bool) {
id, ok := n.NodeMetadata["docker_container_id"]
if !ok {
return newPseudoNode(uncontainedID, uncontainedMajor, ""), true
}

return NewRenderableNode(id, "", "", "", n.NodeMetadata), true
}

// ProcessPID takes a node NodeMetadata from topology, and returns a
// representation with the ID based on the process PID and the labels based on
// the process name.
Expand All @@ -64,42 +132,15 @@ func ProcessPID(m report.NodeMetadata) (RenderableNode, bool) {
show = m["pid"] != "" && m["name"] != ""
)

return NewRenderableNode(identifier, m["name"], minor, m["pid"]), show
return NewRenderableNode(identifier, m["name"], minor, m["pid"], m), show
}

// ProcessName takes a node NodeMetadata from a topology, and returns a
// representation with the ID based on the process name (grouping all
// processes with the same name together).
func ProcessName(m report.NodeMetadata) (RenderableNode, bool) {
show := m["pid"] != "" && m["name"] != ""
return NewRenderableNode(m["name"], m["name"], "", m["name"]), show
}

// MapEndpoint2Container maps endpoint topology nodes to the containers they run
// in. We consider container and image IDs to be globally unique, and so don't
// scope them further by e.g. host. If no container metadata is found, nodes are
// grouped into the Uncontained node.
func MapEndpoint2Container(m report.NodeMetadata) (RenderableNode, bool) {
var id, major, minor, rank string
if m["docker_container_id"] == "" {
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
} else {
id, major, minor, rank = m["docker_container_id"], "", m["domain"], ""
}

return NewRenderableNode(id, major, minor, rank), true
}

// MapContainerIdentity maps container topology node to container mapped nodes.
func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) {
var id, major, minor, rank string
if m["docker_container_id"] == "" {
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
} else {
id, major, minor, rank = m["docker_container_id"], m["docker_container_name"], m["domain"], m["docker_image_id"]
}

return NewRenderableNode(id, major, minor, rank), true
return NewRenderableNode(m["name"], m["name"], "", m["name"], m), show
}

// ProcessContainerImage maps topology nodes to the container images they run
Expand All @@ -113,7 +154,7 @@ func ProcessContainerImage(m report.NodeMetadata) (RenderableNode, bool) {
id, major, minor, rank = m["docker_image_id"], m["docker_image_name"], "", m["docker_image_id"]
}

return NewRenderableNode(id, major, minor, rank), true
return NewRenderableNode(id, major, minor, rank, m), true
}

// NetworkHostname takes a node NodeMetadata and returns a representation
Expand All @@ -130,7 +171,7 @@ func NetworkHostname(m report.NodeMetadata) (RenderableNode, bool) {
domain = parts[1]
}

return NewRenderableNode(fmt.Sprintf("host:%s", name), parts[0], domain, parts[0]), name != ""
return NewRenderableNode(fmt.Sprintf("host:%s", name), parts[0], domain, parts[0], m), name != ""
}

// GenericPseudoNode contains heuristics for building sensible pseudo nodes.
Expand Down
32 changes: 0 additions & 32 deletions render/mapping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,38 +54,6 @@ func TestUngroupedMapping(t *testing.T) {
wantMinor: "hosta (42)",
wantRank: "42",
},
{
f: render.MapEndpoint2Container,
id: "foo-id",
meta: report.NodeMetadata{
"pid": "42",
"name": "curl",
"domain": "hosta",
},
wantOK: true,
wantID: "uncontained",
wantMajor: "Uncontained",
wantMinor: "",
wantRank: "uncontained",
},
{
f: render.MapEndpoint2Container,
id: "bar-id",
meta: report.NodeMetadata{
"pid": "42",
"name": "curl",
"domain": "hosta",
"docker_container_id": "d321fe0",
"docker_container_name": "walking_sparrow",
"docker_image_id": "1101fff",
"docker_image_name": "org/app:latest",
},
wantOK: true,
wantID: "d321fe0",
wantMajor: "",
wantMinor: "hosta",
wantRank: "",
},
} {
identity := fmt.Sprintf("(%d %s %v)", i, c.id, c.meta)

Expand Down
5 changes: 5 additions & 0 deletions render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ type LeafMap struct {
Pseudo PseudoFunc
}

// MakeReduce is the only sane way to produce a Reduce Renderer
func MakeReduce(renderers ...Renderer) Renderer {
return Reduce(renderers)
}

// Render produces a set of RenderableNodes given a Report
func (r Reduce) Render(rpt report.Report) RenderableNodes {
result := RenderableNodes{}
Expand Down
26 changes: 19 additions & 7 deletions render/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func TestMapEdge(t *testing.T) {
}

identity := func(nmd report.NodeMetadata) (render.RenderableNode, bool) {
return render.NewRenderableNode(nmd["id"], "", "", ""), true
return render.NewRenderableNode(nmd["id"], "", "", "", nmd), true
}

mapper := render.Map{
Expand Down Expand Up @@ -331,6 +331,15 @@ var (
}
)

func trimNodeMetadata(rns render.RenderableNodes) render.RenderableNodes {
result := render.RenderableNodes{}
for id, rn := range rns {
rn.NodeMetadata = nil
result[id] = rn
}
return result
}

func TestRenderByEndpointPID(t *testing.T) {
want := render.RenderableNodes{
"pid:client-54001-domain:10001": {
Expand Down Expand Up @@ -392,9 +401,10 @@ func TestRenderByEndpointPID(t *testing.T) {
}
have := render.LeafMap{
Selector: report.SelectEndpoint,
Mapper: render.ProcessPID,
Pseudo: render.GenericPseudoNode,
Mapper: render.ProcessPID,
Pseudo: render.GenericPseudoNode,
}.Render(rpt)
have = trimNodeMetadata(have)
if !reflect.DeepEqual(want, have) {
t.Error("\n" + diff(want, have))
}
Expand Down Expand Up @@ -450,9 +460,10 @@ func TestRenderByEndpointPIDGrouped(t *testing.T) {
}
have := render.LeafMap{
Selector: report.SelectEndpoint,
Mapper: render.ProcessName,
Pseudo: render.GenericGroupedPseudoNode,
Mapper: render.ProcessName,
Pseudo: render.GenericGroupedPseudoNode,
}.Render(rpt)
have = trimNodeMetadata(have)
if !reflect.DeepEqual(want, have) {
t.Error("\n" + diff(want, have))
}
Expand Down Expand Up @@ -509,9 +520,10 @@ func TestRenderByNetworkHostname(t *testing.T) {
}
have := render.LeafMap{
Selector: report.SelectAddress,
Mapper: render.NetworkHostname,
Pseudo: render.GenericPseudoNode,
Mapper: render.NetworkHostname,
Pseudo: render.GenericPseudoNode,
}.Render(rpt)
have = trimNodeMetadata(have)
if !reflect.DeepEqual(want, have) {
t.Error("\n" + diff(want, have))
}
Expand Down
18 changes: 10 additions & 8 deletions render/renderable_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (
// an element of a topology. It should contain information that's relevant
// to rendering a node when there are many nodes visible at once.
type RenderableNode struct {
ID string `json:"id"` //
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
Rank string `json:"rank"` // to help the layout engine
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
Adjacency report.IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain)
Origins report.IDList `json:"origins,omitempty"` // Core node IDs that contributed information
Metadata report.AggregateMetadata `json:"metadata"` // Numeric sums
ID string `json:"id"` //
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
Rank string `json:"rank"` // to help the layout engine
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
Adjacency report.IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain)
Origins report.IDList `json:"origins,omitempty"` // Core node IDs that contributed information
Metadata report.AggregateMetadata `json:"metadata"` // Numeric sums
NodeMetadata report.NodeMetadata `json:"-"` // merged NodeMetadata of the nodes used to build this
}

// RenderableNodes is a set of RenderableNodes
Expand Down Expand Up @@ -55,4 +56,5 @@ func (rn *RenderableNode) Merge(other RenderableNode) {
rn.Origins = rn.Origins.Add(other.Origins...)

rn.Metadata.Merge(other.Metadata)
rn.NodeMetadata.Merge(other.NodeMetadata)
}
40 changes: 40 additions & 0 deletions render/topologies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package render

import (
"github.com/weaveworks/scope/report"
)

// EndpointRenderer is a Renderer which produces a renderable endpoint graph.
var EndpointRenderer = LeafMap{
Selector: report.SelectEndpoint,
Mapper: MapEndpointIdentity,
Pseudo: GenericPseudoNode,
}

// ProcessRenderer is a Renderer which produces a renderable process
// graph by merging the endpoint graph and the process topology.
var ProcessRenderer = MakeReduce(
Map{
MapFunc: MapEndpoint2Process,
Renderer: EndpointRenderer,
},
LeafMap{
Selector: report.SelectProcess,
Mapper: MapProcessIdentity,
Pseudo: GenericPseudoNode,
},
)

// ContainerRenderer is a Renderer which produces a renderable container
// graph by merging the process graph and the container topology.
var ContainerRenderer = MakeReduce(
Map{
MapFunc: MapProcess2Container,
Renderer: ProcessRenderer,
},
LeafMap{
Selector: report.SelectContainer,
Mapper: MapContainerIdentity,
Pseudo: GenericPseudoNode,
},
)
Loading

0 comments on commit 8b57567

Please sign in to comment.