From a759db89313f92eeb5338b2ba9a3ecc9409eac17 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 16 Jun 2015 13:34:52 +0000 Subject: [PATCH 01/13] Rename Map -> LeafMap, introduce Map (with tests) --- app/router.go | 12 +-- experimental/graphviz/handle.go | 6 +- render/mapping.go | 23 +++--- render/mapping_test.go | 2 +- render/render.go | 130 +++++++++++++++++++++++++------- render/render_test.go | 121 ++++++++++++++++++++++++++++- 6 files changed, 246 insertions(+), 48 deletions(-) diff --git a/app/router.go b/app/router.go index 4fa40f517..baeb97103 100644 --- a/app/router.go +++ b/app/router.go @@ -50,30 +50,30 @@ var topologyRegistry = map[string]topologyView{ "applications": { human: "Applications", parent: "", - renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessPID, Pseudo: render.GenericPseudoNode}, + renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessPID, Pseudo: render.GenericPseudoNode}, }, "applications-by-name": { human: "by name", parent: "applications", - renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessName, Pseudo: render.GenericGroupedPseudoNode}, + renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessName, Pseudo: render.GenericGroupedPseudoNode}, }, "containers": { human: "Containers", parent: "", renderer: render.Reduce([]render.Renderer{ - render.Map{Selector: report.SelectEndpoint, Mapper: render.MapEndpoint2Container, Pseudo: render.InternetOnlyPseudoNode}, - render.Map{Selector: report.SelectContainer, Mapper: render.MapContainerIdentity, Pseudo: render.InternetOnlyPseudoNode}, + render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.MapEndpoint2Container, Pseudo: render.InternetOnlyPseudoNode}, + render.LeafMap{Selector: report.SelectContainer, Mapper: render.MapContainerIdentity, Pseudo: render.InternetOnlyPseudoNode}, }), }, "containers-by-image": { human: "by image", parent: "containers", - renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessContainerImage, Pseudo: render.InternetOnlyPseudoNode}, + renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessContainerImage, Pseudo: render.InternetOnlyPseudoNode}, }, "hosts": { human: "Hosts", parent: "", - renderer: render.Map{Selector: report.SelectAddress, Mapper: render.NetworkHostname, Pseudo: render.GenericPseudoNode}, + renderer: render.LeafMap{Selector: report.SelectAddress, Mapper: render.NetworkHostname, Pseudo: render.GenericPseudoNode}, }, } diff --git a/experimental/graphviz/handle.go b/experimental/graphviz/handle.go index 5dc611a94..fbd560613 100644 --- a/experimental/graphviz/handle.go +++ b/experimental/graphviz/handle.go @@ -16,7 +16,7 @@ func handleTXT(r Reporter) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "text/plain") - renderer := render.Map{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil} + renderer := render.LeafMap{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil} dot(w, renderer.Render(r.Report())) //report.Render(r.Report(), report.SelectEndpoint, mapFunc(req), report.NoPseudoNode)) @@ -35,7 +35,7 @@ func handleSVG(r Reporter) http.HandlerFunc { cmd.Stdout = w - renderer := render.Map{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil} + renderer := render.LeafMap{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil} dot(wc, renderer.Render(r.Report())) wc.Close() @@ -102,7 +102,7 @@ func engine(r *http.Request) string { return engine } -func mapFunc(r *http.Request) render.MapFunc { +func mapFunc(r *http.Request) render.LeafMapFunc { switch strings.ToLower(r.FormValue("map_func")) { case "hosts", "networkhost", "networkhostname": return render.NetworkHostname diff --git a/render/mapping.go b/render/mapping.go index e1973a85b..856209c1b 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -9,7 +9,8 @@ import ( const humanTheInternet = "the Internet" -func newRenderableNode(id, major, minor, rank string) RenderableNode { +// NewRenderableNode makes a new RenderableNode +func NewRenderableNode(id, major, minor, rank string) RenderableNode { return RenderableNode{ ID: id, LabelMajor: major, @@ -31,7 +32,7 @@ func newPseudoNode(id, major, minor string) RenderableNode { } } -// MapFunc is anything which can take an arbitrary NodeMetadata, which is +// 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 // human-readable major and minor labels. @@ -41,7 +42,7 @@ func newPseudoNode(id, major, minor string) RenderableNode { // // If the final output parameter is false, the node shall be omitted from the // rendered topology. -type MapFunc func(report.NodeMetadata) (RenderableNode, bool) +type LeafMapFunc func(report.NodeMetadata) (RenderableNode, bool) // PseudoFunc creates RenderableNode representing pseudo nodes given the dstNodeID. // The srcNode renderable node is essentially from MapFunc, representing one of @@ -49,6 +50,10 @@ type MapFunc func(report.NodeMetadata) (RenderableNode, bool) // node IDs prior to mapping. type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string) (RenderableNode, bool) +// MapFunc is anything which can take an arbitrary RenderableNode and +// return another RenderableNode. +type MapFunc func(RenderableNode) (RenderableNode, bool) + // 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. @@ -59,7 +64,7 @@ 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"]), show } // ProcessName takes a node NodeMetadata from a topology, and returns a @@ -67,7 +72,7 @@ func ProcessPID(m report.NodeMetadata) (RenderableNode, bool) { // 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 + return NewRenderableNode(m["name"], m["name"], "", m["name"]), show } // MapEndpoint2Container maps endpoint topology nodes to the containers they run @@ -82,7 +87,7 @@ func MapEndpoint2Container(m report.NodeMetadata) (RenderableNode, bool) { id, major, minor, rank = m["docker_container_id"], "", m["domain"], "" } - return newRenderableNode(id, major, minor, rank), true + return NewRenderableNode(id, major, minor, rank), true } // MapContainerIdentity maps container topology node to container mapped nodes. @@ -94,7 +99,7 @@ func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) { 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(id, major, minor, rank), true } // ProcessContainerImage maps topology nodes to the container images they run @@ -108,7 +113,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), true } // NetworkHostname takes a node NodeMetadata and returns a representation @@ -125,7 +130,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]), name != "" } // GenericPseudoNode contains heuristics for building sensible pseudo nodes. diff --git a/render/mapping_test.go b/render/mapping_test.go index 7579e6074..5f1475fe8 100644 --- a/render/mapping_test.go +++ b/render/mapping_test.go @@ -10,7 +10,7 @@ import ( func TestUngroupedMapping(t *testing.T) { for i, c := range []struct { - f render.MapFunc + f render.LeafMapFunc id string meta report.NodeMetadata wantOK bool diff --git a/render/render.go b/render/render.go index f89843683..1fbc0637e 100644 --- a/render/render.go +++ b/render/render.go @@ -16,6 +16,21 @@ type Renderer interface { // other renderers type Reduce []Renderer +// Map is a Renderer which produces a set of RendererNodes from the set of +// RendererNodes produces by another Renderer +type Map struct { + MapFunc + Renderer +} + +// LeafMap is a Renderer which produces a set of RendererNodes from a report.Topology +// by using a map functions and topology selector. +type LeafMap struct { + Selector report.TopologySelector + Mapper LeafMapFunc + Pseudo PseudoFunc +} + // Render produces a set of RenderableNodes given a Report func (r Reduce) Render(rpt report.Report) RenderableNodes { result := RenderableNodes{} @@ -34,26 +49,93 @@ func (r Reduce) AggregateMetadata(rpt report.Report, localID, remoteID string) r return metadata } -// Map is a Renderer which produces a set of RendererNodes by using a -// Mapper functions and topology selector. -type Map struct { - Selector report.TopologySelector - Mapper MapFunc - Pseudo PseudoFunc -} - -// Render produces a set of RenderableNodes given a Report +// Render transforms a set of RendererNodes produces by another Renderer +// using a map function func (m Map) Render(rpt report.Report) RenderableNodes { - return Topology(m.Selector(rpt), m.Mapper, m.Pseudo) + output, _ := m.render(rpt) + return output } -// Topology transforms a given Topology into a set of RenderableNodes, which +func (m Map) render(rpt report.Report) (RenderableNodes, map[string]string) { + input := m.Renderer.Render(rpt) + output := RenderableNodes{} + mapped := map[string]string{} // input node ID -> output node ID + adjacencies := map[string]report.IDList{} // output node ID -> input node Adjacencies + + for _, inRenderable := range input { + outRenderable, ok := m.MapFunc(inRenderable) + if !ok { + continue + } + + existing, ok := output[outRenderable.ID] + if ok { + outRenderable.Merge(existing) + } + + output[outRenderable.ID] = outRenderable + mapped[inRenderable.ID] = outRenderable.ID + adjacencies[outRenderable.ID] = adjacencies[outRenderable.ID].Add(inRenderable.Adjacency...) + } + + // Rewrite Adjacency for new node IDs. + // NB we don't do pseudo nodes here; we assumer the input graph + // we properly-connected, and if the map func dropped a node, + // we drop links to it. + for outNodeID, inAdjacency := range adjacencies { + outAdjacency := report.MakeIDList() + for _, inAdjacent := range inAdjacency { + outAdjacent, ok := mapped[inAdjacent] + if ok { + outAdjacency = outAdjacency.Add(outAdjacent) + } + } + outNode := output[outNodeID] + outNode.Adjacency = outAdjacency + output[outNodeID] = outNode + } + + return output, mapped +} + +// AggregateMetadata gives the metadata of an edge from the perspective of the +// srcRenderableID. Since an edgeID can have multiple edges on the address +// level, it uses the supplied mapping function to translate address IDs to +// renderable node (mapped) IDs. +func (m Map) AggregateMetadata(rpt report.Report, srcRenderableID, dstRenderableID string) report.AggregateMetadata { + // First we need to map the ids in this layer into the ids in the underlying layer + _, mapped := m.render(rpt) // this maps from old -> new + inverted := map[string][]string{} // this maps from new -> old(s) + for k, v := range mapped { + existing := inverted[v] + existing = append(existing, k) + inverted[v] = existing + } + + // Now work out a slice of edges this edge is constructed from + oldEdges := []struct{ src, dst string }{} + for _, oldSrcID := range inverted[srcRenderableID] { + for _, oldDstID := range inverted[dstRenderableID] { + oldEdges = append(oldEdges, struct{ src, dst string }{oldSrcID, oldDstID}) + } + } + + // Now recurse for each old edge + output := report.AggregateMetadata{} + for _, edge := range oldEdges { + metadata := m.Renderer.AggregateMetadata(rpt, edge.src, edge.dst) + output.Merge(metadata) + } + return output +} + +// Render transforms a given Report into a set of RenderableNodes, which // the UI will render collectively as a graph. Note that a RenderableNode will // always be rendered with other nodes, and therefore contains limited detail. // -// RenderBy takes a a MapFunc, which defines how to group and label nodes. Npdes -// with the same mapped IDs will be merged. -func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) RenderableNodes { +// Nodes with the same mapped IDs will be merged. +func (m LeafMap) Render(rpt report.Report) RenderableNodes { + t := m.Selector(rpt) nodes := RenderableNodes{} // Build a set of RenderableNodes for all non-pseudo probes, and an @@ -64,7 +146,7 @@ func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) Rendera source2host = map[string]string{} // source node ID -> origin host ID ) for nodeID, metadata := range t.NodeMetadatas { - mapped, ok := mapFunc(metadata) + mapped, ok := m.Mapper(metadata) if !ok { continue } @@ -100,7 +182,7 @@ func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) Rendera for _, dstNodeID := range dsts { dstRenderableID, ok := source2mapped[dstNodeID] if !ok { - pseudoNode, ok := pseudoFunc(srcNodeID, srcRenderableNode, dstNodeID) + pseudoNode, ok := m.Pseudo(srcNodeID, srcRenderableNode, dstNodeID) if !ok { continue } @@ -124,16 +206,12 @@ func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) Rendera return nodes } -// AggregateMetadata produces an AggregateMetadata for a given edge -func (m Map) AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata { - return edgeMetadata(m.Selector(rpt), m.Mapper, localID, remoteID).Transform() -} - -// EdgeMetadata gives the metadata of an edge from the perspective of the +// AggregateMetadata gives the metadata of an edge from the perspective of the // srcRenderableID. Since an edgeID can have multiple edges on the address // level, it uses the supplied mapping function to translate address IDs to // renderable node (mapped) IDs. -func edgeMetadata(t report.Topology, mapFunc MapFunc, srcRenderableID, dstRenderableID string) report.EdgeMetadata { +func (m LeafMap) AggregateMetadata(rpt report.Report, srcRenderableID, dstRenderableID string) report.AggregateMetadata { + t := m.Selector(rpt) metadata := report.EdgeMetadata{} for edgeID, edgeMeta := range t.EdgeMetadatas { src, dst, ok := report.ParseEdgeID(edgeID) @@ -142,16 +220,16 @@ func edgeMetadata(t report.Topology, mapFunc MapFunc, srcRenderableID, dstRender continue } if src != report.TheInternet { - mapped, _ := mapFunc(t.NodeMetadatas[src]) + mapped, _ := m.Mapper(t.NodeMetadatas[src]) src = mapped.ID } if dst != report.TheInternet { - mapped, _ := mapFunc(t.NodeMetadatas[dst]) + mapped, _ := m.Mapper(t.NodeMetadatas[dst]) dst = mapped.ID } if src == srcRenderableID && dst == dstRenderableID { metadata.Flatten(edgeMeta) } } - return metadata + return metadata.Transform() } diff --git a/render/render_test.go b/render/render_test.go index a144e6e75..c6e7a82a5 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -55,6 +55,109 @@ func TestReduceEdge(t *testing.T) { } } +func TestMapRender(t *testing.T) { + // 1. Check when we return false, the node gets filtered out + { + mapper := render.Map{ + MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { + return render.RenderableNode{}, false + }, + Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ + "foo": {ID: "foo"}, + }}, + } + want := render.RenderableNodes{} + have := mapper.Render(report.MakeReport()) + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) + } + } + + // 2. Check we can remap two nodes into one + { + mapper := render.Map{ + MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { + return render.RenderableNode{ID: "bar"}, true + }, + Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ + "foo": {ID: "foo"}, + "baz": {ID: "baz"}, + }}, + } + want := render.RenderableNodes{ + "bar": render.RenderableNode{ID: "bar"}, + } + have := mapper.Render(report.MakeReport()) + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) + } + } + + // 3. Check we can remap adjacencies + { + mapper := render.Map{ + MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { + return render.RenderableNode{ID: "_" + nodes.ID}, true + }, + Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ + "foo": {ID: "foo", Adjacency: report.MakeIDList("baz")}, + "baz": {ID: "baz", Adjacency: report.MakeIDList("foo")}, + }}, + } + want := render.RenderableNodes{ + "_foo": {ID: "_foo", Adjacency: report.MakeIDList("_baz")}, + "_baz": {ID: "_baz", Adjacency: report.MakeIDList("_foo")}, + } + have := mapper.Render(report.MakeReport()) + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) + } + } +} + +func TestMapEdge(t *testing.T) { + selector := func(_ report.Report) report.Topology { + return report.Topology{ + NodeMetadatas: report.NodeMetadatas{ + "foo": report.NodeMetadata{"id": "foo"}, + "bar": report.NodeMetadata{"id": "bar"}, + }, + Adjacency: report.Adjacency{ + ">foo": report.MakeIDList("bar"), + ">bar": report.MakeIDList("foo"), + }, + EdgeMetadatas: report.EdgeMetadatas{ + "foo|bar": report.EdgeMetadata{WithBytes: true, BytesIngress: 1, BytesEgress: 2}, + "bar|foo": report.EdgeMetadata{WithBytes: true, BytesIngress: 3, BytesEgress: 4}, + }, + } + } + + identity := func(nmd report.NodeMetadata) (render.RenderableNode, bool) { + return render.NewRenderableNode(nmd["id"], "", "", ""), true + } + + mapper := render.Map{ + MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { + return render.RenderableNode{ID: "_" + nodes.ID}, true + }, + Renderer: render.LeafMap{ + Selector: selector, + Mapper: identity, + Pseudo: nil, + }, + } + + want := report.AggregateMetadata{ + report.KeyBytesIngress: 1, + report.KeyBytesEgress: 2, + } + have := mapper.AggregateMetadata(report.MakeReport(), "_foo", "_bar") + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) + } +} + var ( clientHostID = "client.hostname.com" serverHostID = "server.hostname.com" @@ -287,7 +390,11 @@ func TestRenderByEndpointPID(t *testing.T) { Metadata: report.AggregateMetadata{}, }, } - have := render.Topology(rpt.Endpoint, render.ProcessPID, render.GenericPseudoNode) + have := render.LeafMap{ + Selector: report.SelectEndpoint, + Mapper: render.ProcessPID, + Pseudo: render.GenericPseudoNode, + }.Render(rpt) if !reflect.DeepEqual(want, have) { t.Error("\n" + diff(want, have)) } @@ -341,7 +448,11 @@ func TestRenderByEndpointPIDGrouped(t *testing.T) { Metadata: report.AggregateMetadata{}, }, } - have := render.Topology(rpt.Endpoint, render.ProcessName, render.GenericGroupedPseudoNode) + have := render.LeafMap{ + Selector: report.SelectEndpoint, + Mapper: render.ProcessName, + Pseudo: render.GenericGroupedPseudoNode, + }.Render(rpt) if !reflect.DeepEqual(want, have) { t.Error("\n" + diff(want, have)) } @@ -396,7 +507,11 @@ func TestRenderByNetworkHostname(t *testing.T) { Metadata: report.AggregateMetadata{}, }, } - have := render.Topology(rpt.Address, render.NetworkHostname, render.GenericPseudoNode) + have := render.LeafMap{ + Selector: report.SelectAddress, + Mapper: render.NetworkHostname, + Pseudo: render.GenericPseudoNode, + }.Render(rpt) if !reflect.DeepEqual(want, have) { t.Error("\n" + diff(want, have)) } From 16e2ccd2bee47c6b9db767385fedb39a6f7f3600 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 16 Jun 2015 16:31:15 +0000 Subject: [PATCH 02/13] Produce the container topology by way of the process topology. --- app/router.go | 9 +-- probe/spy.go | 6 +- render/mapping.go | 141 +++++++++++++++++++++++--------------- render/mapping_test.go | 32 --------- render/render.go | 5 ++ render/render_test.go | 26 +++++-- render/renderable_node.go | 56 ++++++++++++--- render/topologies.go | 40 +++++++++++ report/report.go | 5 ++ 9 files changed, 212 insertions(+), 108 deletions(-) create mode 100644 render/topologies.go diff --git a/app/router.go b/app/router.go index baeb97103..e2db1f4ce 100644 --- a/app/router.go +++ b/app/router.go @@ -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", diff --git a/probe/spy.go b/probe/spy.go index aa7b03ef4..2e9c66d90 100644 --- a/probe/spy.go +++ b/probe/spy.go @@ -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, } diff --git a/render/mapping.go b/render/mapping.go index 856209c1b..fad92c353 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -7,30 +7,12 @@ import ( "github.com/weaveworks/scope/report" ) -const humanTheInternet = "the Internet" +const ( + uncontainedID = "uncontained" + uncontainedMajor = "Uncontained" -// NewRenderableNode makes a new RenderableNode -func NewRenderableNode(id, major, minor, rank string) RenderableNode { - return RenderableNode{ - ID: id, - LabelMajor: major, - LabelMinor: minor, - Rank: rank, - Pseudo: false, - Metadata: report.AggregateMetadata{}, - } -} - -func newPseudoNode(id, major, minor string) RenderableNode { - return RenderableNode{ - ID: id, - LabelMajor: major, - LabelMinor: minor, - Rank: "", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - } -} + humanTheInternet = "the Internet" +) // LeafMapFunc is anything which can take an arbitrary NodeMetadata, which is // always one-to-one with nodes in a topology, and return a specific @@ -54,6 +36,84 @@ 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. +// +// If this function is given a pseudo node, then it will just return it; +// Pseudo nodes will never have pids in them, and therefore will never +// be able to be turned into a Process node. +// +// Otherwise, this function will produce a node with the correct ID +// format for a process, but without any Major or Minor labels. +// It does not have enough info to do that, and the resulting graph +// must be merged with a process graph to get that info. +func MapEndpoint2Process(n RenderableNode) (RenderableNode, bool) { + if n.Pseudo { + return n, true + } + + id := fmt.Sprintf("pid:%s:%s", n.NodeMetadata[report.HostNodeID], n.NodeMetadata["pid"]) + return newDerivedNode(id, n), true +} + +// MapProcess2Container maps process RenderableNodes to container RenderableNodes. +// +// If this function is given a node without a docker_container_id (including other +// psueod nodes), it will produce an "Uncontained" pseudo node. +// +// Otherwise, this function will produce a node with the correct ID +// format for a container, but without any Major or Minor labels. +// It does not have enough info to do that, and the resulting graph +// must be merged with a container graph to get that info. +func MapProcess2Container(n RenderableNode) (RenderableNode, bool) { + id, ok := n.NodeMetadata["docker_container_id"] + if !ok { + return newPseudoNode(uncontainedID, uncontainedMajor, ""), true + } + + return newDerivedNode(id, n), 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. @@ -64,7 +124,7 @@ 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 @@ -72,34 +132,7 @@ func ProcessPID(m report.NodeMetadata) (RenderableNode, bool) { // 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 @@ -113,7 +146,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 @@ -130,7 +163,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. diff --git a/render/mapping_test.go b/render/mapping_test.go index 5f1475fe8..1b85656e5 100644 --- a/render/mapping_test.go +++ b/render/mapping_test.go @@ -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) diff --git a/render/render.go b/render/render.go index 1fbc0637e..2b4096390 100644 --- a/render/render.go +++ b/render/render.go @@ -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{} diff --git a/render/render_test.go b/render/render_test.go index c6e7a82a5..c1bcf2e73 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -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{ @@ -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": { @@ -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)) } @@ -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)) } @@ -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)) } diff --git a/render/renderable_node.go b/render/renderable_node.go index 2b6fd9a03..efb801b72 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -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 @@ -55,4 +56,43 @@ func (rn *RenderableNode) Merge(other RenderableNode) { rn.Origins = rn.Origins.Add(other.Origins...) rn.Metadata.Merge(other.Metadata) + rn.NodeMetadata.Merge(other.NodeMetadata) +} + +// NewRenderableNode makes a new 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{}, + NodeMetadata: nmd, + } +} + +func newDerivedNode(id string, node RenderableNode) RenderableNode { + return RenderableNode{ + ID: id, + LabelMajor: "", + LabelMinor: "", + Rank: "", + Pseudo: node.Pseudo, + Metadata: node.Metadata, + Origins: node.Origins, + NodeMetadata: node.NodeMetadata, + } +} + +func newPseudoNode(id, major, minor string) RenderableNode { + return RenderableNode{ + ID: id, + LabelMajor: major, + LabelMinor: minor, + Rank: "", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + NodeMetadata: report.NodeMetadata{}, + } } diff --git a/render/topologies.go b/render/topologies.go new file mode 100644 index 000000000..b93d6e9f1 --- /dev/null +++ b/render/topologies.go @@ -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, + }, +) diff --git a/report/report.go b/report/report.go index 8582469e6..fccb697e9 100644 --- a/report/report.go +++ b/report/report.go @@ -53,6 +53,11 @@ func SelectEndpoint(r Report) Topology { return r.Endpoint } +// SelectProcess selects the process topology. +func SelectProcess(r Report) Topology { + return r.Process +} + // SelectAddress selects the address topology. func SelectAddress(r Report) Topology { return r.Address From 546f336fef5e3e60ee5d514710db83602556a724 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 10:14:55 +0000 Subject: [PATCH 03/13] Add FilterUnconnected Renderer, wire up Process and ProcessName Renderers. --- app/router.go | 4 +- render/mapping.go | 63 ++++-- render/render.go | 22 +++ render/render_test.go | 393 ++------------------------------------ render/topologies.go | 7 + render/topologies_test.go | 390 +++++++++++++++++++++++++++++++++++++ 6 files changed, 482 insertions(+), 397 deletions(-) create mode 100644 render/topologies_test.go diff --git a/app/router.go b/app/router.go index e2db1f4ce..581d144b6 100644 --- a/app/router.go +++ b/app/router.go @@ -50,12 +50,12 @@ var topologyRegistry = map[string]topologyView{ "applications": { human: "Applications", parent: "", - renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessPID, Pseudo: render.GenericPseudoNode}, + renderer: render.FilterUnconnected{Renderer: render.ProcessRenderer}, }, "applications-by-name": { human: "by name", parent: "applications", - renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessName, Pseudo: render.GenericGroupedPseudoNode}, + renderer: render.FilterUnconnected{Renderer: render.ProcessNameRenderer}, }, "containers": { human: "Containers", diff --git a/render/mapping.go b/render/mapping.go index fad92c353..54742d4be 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -34,6 +34,9 @@ type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string) // MapFunc is anything which can take an arbitrary RenderableNode and // return another RenderableNode. +// +// As with LeadMapFunc, if the final output parameter is false, the node +// shall be omitted from the rendered topology. type MapFunc func(RenderableNode) (RenderableNode, bool) // MapEndpointIdentity maps a endpoint topology node to endpoint RenderableNode node. @@ -41,9 +44,9 @@ type MapFunc func(RenderableNode) (RenderableNode, bool) // 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"]) + id = fmt.Sprintf("endpoint:%s:%s:%s", getHostname(m), m["addr"], m["port"]) major = fmt.Sprintf("%s:%s", m["addr"], m["port"]) - minor = fmt.Sprintf("%s (%s)", m[report.HostNodeID], m["pid"]) + minor = fmt.Sprintf("%s (%s)", getHostname(m), m["pid"]) rank = m["pid"] ) return NewRenderableNode(id, major, minor, rank, m), true @@ -54,30 +57,31 @@ func MapEndpointIdentity(m report.NodeMetadata) (RenderableNode, bool) { // presences of certain keys. func MapProcessIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( - id = fmt.Sprintf("pid:%s:%s", m[report.HostNodeID], m["pid"]) + id = fmt.Sprintf("pid:%s:%s", getHostname(m), m["pid"]) major = m["comm"] - minor = fmt.Sprintf("%s (%s)", m[report.HostNodeID], m["pid"]) + minor = fmt.Sprintf("%s (%s)", getHostname(m), 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. +// 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] + minor = getHostname(m) rank = m["docker_image_id"] ) return NewRenderableNode(id, major, minor, rank, m), true } -// MapEndpoint2Process maps endpoint RenderableNodes to process RenderableNodes. +// MapEndpoint2Process maps endpoint RenderableNodes to process +// RenderableNodes. // // If this function is given a pseudo node, then it will just return it; // Pseudo nodes will never have pids in them, and therefore will never @@ -92,14 +96,22 @@ func MapEndpoint2Process(n RenderableNode) (RenderableNode, bool) { return n, true } - id := fmt.Sprintf("pid:%s:%s", n.NodeMetadata[report.HostNodeID], n.NodeMetadata["pid"]) + pid, ok := n.NodeMetadata["pid"] + if !ok { + // TODO: Propogate a pseudo node instead of dropping this? + return RenderableNode{}, false + } + + id := fmt.Sprintf("pid:%s:%s", getHostname(n.NodeMetadata), pid) return newDerivedNode(id, n), true } -// MapProcess2Container maps process RenderableNodes to container RenderableNodes. +// MapProcess2Container maps process RenderableNodes to container +// RenderableNodes. // -// If this function is given a node without a docker_container_id (including other -// psueod nodes), it will produce an "Uncontained" pseudo node. +// If this function is given a node without a docker_container_id +// (including other pseudo nodes), it will produce an "Uncontained" +// pseudo node. // // Otherwise, this function will produce a node with the correct ID // format for a container, but without any Major or Minor labels. @@ -114,6 +126,31 @@ func MapProcess2Container(n RenderableNode) (RenderableNode, bool) { return newDerivedNode(id, n), true } +// MapProcess2Name maps process RenderableNodes to RenderableNodes +// for each process name. +// +// This mapper is unlike the other foo2bar mappers as the intention +// is not to join the information with another topology. Therefore +// it outputs a properly-formed node with labels etc. +func MapProcess2Name(n RenderableNode) (RenderableNode, bool) { + if n.Pseudo { + return n, true + } + + name, ok := n.NodeMetadata["comm"] + if !ok { + // TODO: Propogate a pseudo node instead of dropping this? + return RenderableNode{}, false + } + + return NewRenderableNode(name, name, "", name, n.NodeMetadata), true +} + +func getHostname(m report.NodeMetadata) string { + hostname, _, _ := report.ParseNodeID(m[report.HostNodeID]) + return hostname +} + // 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. diff --git a/render/render.go b/render/render.go index 2b4096390..d6b4b01ba 100644 --- a/render/render.go +++ b/render/render.go @@ -31,6 +31,11 @@ type LeafMap struct { Pseudo PseudoFunc } +// FilterUnconnected is a Renderer which filters out unconnected nodes. +type FilterUnconnected struct { + Renderer +} + // MakeReduce is the only sane way to produce a Reduce Renderer func MakeReduce(renderers ...Renderer) Renderer { return Reduce(renderers) @@ -238,3 +243,20 @@ func (m LeafMap) AggregateMetadata(rpt report.Report, srcRenderableID, dstRender } return metadata.Transform() } + +// Render produces a set of RenderableNodes given a Report +func (f FilterUnconnected) Render(rpt report.Report) RenderableNodes { + input := f.Renderer.Render(rpt) + output := RenderableNodes{} + for id, node := range input { + if len(node.Adjacency) == 0 { + continue + } + + output[id] = node + for _, id := range node.Adjacency { + output[id] = input[id] + } + } + return output +} diff --git a/render/render_test.go b/render/render_test.go index c1bcf2e73..99752b61a 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -6,15 +6,8 @@ import ( "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" - - "github.com/davecgh/go-spew/spew" - "github.com/pmezard/go-difflib/difflib" ) -func init() { - spew.Config.SortKeys = true // :\ -} - type mockRenderer struct { render.RenderableNodes aggregateMetadata report.AggregateMetadata @@ -158,384 +151,20 @@ func TestMapEdge(t *testing.T) { } } -var ( - clientHostID = "client.hostname.com" - serverHostID = "server.hostname.com" - randomHostID = "random.hostname.com" - unknownHostID = "" - - clientHostName = clientHostID - serverHostName = serverHostID - - clientHostNodeID = report.MakeHostNodeID(clientHostID) - serverHostNodeID = report.MakeHostNodeID(serverHostID) - randomHostNodeID = report.MakeHostNodeID(randomHostID) - - client54001NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1) - client54002NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2) - unknownClient1 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected - unknownClient2 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped. - unknownClient3 = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped - server80 = report.MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache - - clientAddressNodeID = report.MakeAddressNodeID(clientHostID, "10.10.10.20") - serverAddressNodeID = report.MakeAddressNodeID(serverHostID, "192.168.1.1") - randomAddressNodeID = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology - unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, "10.10.10.10") -) - -var ( - rpt = report.Report{ - Endpoint: report.Topology{ - Adjacency: report.Adjacency{ - report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80), - report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80), - report.MakeAdjacencyID(server80): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1, unknownClient2, unknownClient3), - }, - NodeMetadatas: report.NodeMetadatas{ - // NodeMetadata is arbitrary. We're free to put only precisely what we - // care to test into the fixture. Just be sure to include the bits - // that the mapping funcs extract :) - client54001NodeID: report.NodeMetadata{ - "name": "curl", - "domain": "client-54001-domain", - "pid": "10001", - report.HostNodeID: clientHostNodeID, - "host_name": clientHostName, - }, - client54002NodeID: report.NodeMetadata{ - "name": "curl", // should be same as above! - "domain": "client-54002-domain", // may be different than above - "pid": "10001", // should be same as above! - report.HostNodeID: clientHostNodeID, - }, - server80: report.NodeMetadata{ - "name": "apache", - "domain": "server-80-domain", - "pid": "215", - report.HostNodeID: serverHostNodeID, - }, - }, - EdgeMetadatas: report.EdgeMetadatas{ - report.MakeEdgeID(client54001NodeID, server80): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 100, - BytesEgress: 10, - }, - report.MakeEdgeID(client54002NodeID, server80): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 200, - BytesEgress: 20, - }, - - report.MakeEdgeID(server80, client54001NodeID): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 10, - BytesEgress: 100, - }, - report.MakeEdgeID(server80, client54002NodeID): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 20, - BytesEgress: 200, - }, - report.MakeEdgeID(server80, unknownClient1): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 30, - BytesEgress: 300, - }, - report.MakeEdgeID(server80, unknownClient2): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 40, - BytesEgress: 400, - }, - report.MakeEdgeID(server80, unknownClient3): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 50, - BytesEgress: 500, - }, - }, - }, - 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{}, - }, - Address: report.Topology{ - Adjacency: report.Adjacency{ - report.MakeAdjacencyID(clientAddressNodeID): report.MakeIDList(serverAddressNodeID), - report.MakeAdjacencyID(randomAddressNodeID): report.MakeIDList(serverAddressNodeID), - report.MakeAdjacencyID(serverAddressNodeID): report.MakeIDList(clientAddressNodeID, unknownAddressNodeID), // no backlink to random - }, - NodeMetadatas: report.NodeMetadatas{ - clientAddressNodeID: report.NodeMetadata{ - "name": "client.hostname.com", // hostname - "host_name": "client.hostname.com", - report.HostNodeID: clientHostNodeID, - }, - randomAddressNodeID: report.NodeMetadata{ - "name": "random.hostname.com", // hostname - report.HostNodeID: randomHostNodeID, - }, - serverAddressNodeID: report.NodeMetadata{ - "name": "server.hostname.com", // hostname - report.HostNodeID: serverHostNodeID, - }, - }, - EdgeMetadatas: report.EdgeMetadatas{ - report.MakeEdgeID(clientAddressNodeID, serverAddressNodeID): report.EdgeMetadata{ - WithConnCountTCP: true, - MaxConnCountTCP: 3, - }, - report.MakeEdgeID(randomAddressNodeID, serverAddressNodeID): report.EdgeMetadata{ - WithConnCountTCP: true, - MaxConnCountTCP: 20, // dangling connections, weird but possible - }, - report.MakeEdgeID(serverAddressNodeID, clientAddressNodeID): report.EdgeMetadata{ - WithConnCountTCP: true, - MaxConnCountTCP: 3, - }, - report.MakeEdgeID(serverAddressNodeID, unknownAddressNodeID): report.EdgeMetadata{ - WithConnCountTCP: true, - MaxConnCountTCP: 7, - }, - }, - }, - Host: report.Topology{ - Adjacency: report.Adjacency{}, - NodeMetadatas: report.NodeMetadatas{ - serverHostNodeID: report.NodeMetadata{ - "host_name": serverHostName, - "local_networks": "10.10.10.0/24", - "os": "Linux", - "load": "0.01 0.01 0.01", - }, - }, - EdgeMetadatas: report.EdgeMetadatas{}, - }, +func TestFilterRender(t *testing.T) { + renderer := render.FilterUnconnected{ + Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ + "foo": {ID: "foo", Adjacency: report.MakeIDList("bar")}, + "bar": {ID: "bar", Adjacency: report.MakeIDList("foo")}, + "baz": {ID: "baz", Adjacency: report.MakeIDList()}, + }}, } -) - -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": { - ID: "pid:client-54001-domain:10001", - LabelMajor: "curl", - LabelMinor: "client-54001-domain (10001)", - Rank: "10001", - Pseudo: false, - Adjacency: report.MakeIDList("pid:server-80-domain:215"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 100, - report.KeyBytesEgress: 10, - }, - }, - "pid:client-54002-domain:10001": { - ID: "pid:client-54002-domain:10001", - LabelMajor: "curl", - LabelMinor: "client-54002-domain (10001)", - Rank: "10001", // same process - Pseudo: false, - Adjacency: report.MakeIDList("pid:server-80-domain:215"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 200, - report.KeyBytesEgress: 20, - }, - }, - "pid:server-80-domain:215": { - ID: "pid:server-80-domain:215", - LabelMajor: "apache", - LabelMinor: "server-80-domain (215)", - Rank: "215", - Pseudo: false, - Adjacency: report.MakeIDList( - "pid:client-54001-domain:10001", - "pid:client-54002-domain:10001", - "pseudo;10.10.10.10;192.168.1.1;80", - "pseudo;10.10.10.11;192.168.1.1;80", - ), - Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 150, - report.KeyBytesEgress: 1500, - }, - }, - "pseudo;10.10.10.10;192.168.1.1;80": { - ID: "pseudo;10.10.10.10;192.168.1.1;80", - LabelMajor: "10.10.10.10", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - }, - "pseudo;10.10.10.11;192.168.1.1;80": { - ID: "pseudo;10.10.10.11;192.168.1.1;80", - LabelMajor: "10.10.10.11", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - }, + "foo": {ID: "foo", Adjacency: report.MakeIDList("bar")}, + "bar": {ID: "bar", Adjacency: report.MakeIDList("foo")}, } - have := render.LeafMap{ - Selector: report.SelectEndpoint, - Mapper: render.ProcessPID, - Pseudo: render.GenericPseudoNode, - }.Render(rpt) - have = trimNodeMetadata(have) + have := renderer.Render(report.MakeReport()) if !reflect.DeepEqual(want, have) { - t.Error("\n" + diff(want, have)) + t.Errorf("want %+v, have %+v", want, have) } } - -func TestRenderByEndpointPIDGrouped(t *testing.T) { - // For grouped, I've somewhat arbitrarily chosen to squash together all - // processes with the same name by removing the PID and domain (host) - // dimensions from the ID. That could be changed. - want := render.RenderableNodes{ - "curl": { - ID: "curl", - LabelMajor: "curl", - LabelMinor: "", - Rank: "curl", - Pseudo: false, - Adjacency: report.MakeIDList("apache"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 300, - report.KeyBytesEgress: 30, - }, - }, - "apache": { - ID: "apache", - LabelMajor: "apache", - LabelMinor: "", - Rank: "apache", - Pseudo: false, - Adjacency: report.MakeIDList( - "curl", - "pseudo;10.10.10.10;apache", - "pseudo;10.10.10.11;apache", - ), - Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 150, - report.KeyBytesEgress: 1500, - }, - }, - "pseudo;10.10.10.10;apache": { - ID: "pseudo;10.10.10.10;apache", - LabelMajor: "10.10.10.10", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - }, - "pseudo;10.10.10.11;apache": { - ID: "pseudo;10.10.10.11;apache", - LabelMajor: "10.10.10.11", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - }, - } - have := render.LeafMap{ - Selector: report.SelectEndpoint, - Mapper: render.ProcessName, - Pseudo: render.GenericGroupedPseudoNode, - }.Render(rpt) - have = trimNodeMetadata(have) - if !reflect.DeepEqual(want, have) { - t.Error("\n" + diff(want, have)) - } -} - -func TestRenderByNetworkHostname(t *testing.T) { - want := render.RenderableNodes{ - "host:client.hostname.com": { - ID: "host:client.hostname.com", - LabelMajor: "client", // before first . - LabelMinor: "hostname.com", // after first . - Rank: "client", - Pseudo: false, - Adjacency: report.MakeIDList("host:server.hostname.com"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeAddressNodeID("client.hostname.com", "10.10.10.20")), - Metadata: report.AggregateMetadata{ - report.KeyMaxConnCountTCP: 3, - }, - }, - "host:random.hostname.com": { - ID: "host:random.hostname.com", - LabelMajor: "random", // before first . - LabelMinor: "hostname.com", // after first . - Rank: "random", - Pseudo: false, - Adjacency: report.MakeIDList("host:server.hostname.com"), - Origins: report.MakeIDList(report.MakeHostNodeID("random.hostname.com"), report.MakeAddressNodeID("random.hostname.com", "172.16.11.9")), - Metadata: report.AggregateMetadata{ - report.KeyMaxConnCountTCP: 20, - }, - }, - "host:server.hostname.com": { - ID: "host:server.hostname.com", - LabelMajor: "server", // before first . - LabelMinor: "hostname.com", // after first . - Rank: "server", - Pseudo: false, - Adjacency: report.MakeIDList("host:client.hostname.com", "pseudo;10.10.10.10;192.168.1.1;"), - Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeAddressNodeID("server.hostname.com", "192.168.1.1")), - Metadata: report.AggregateMetadata{ - report.KeyMaxConnCountTCP: 10, - }, - }, - "pseudo;10.10.10.10;192.168.1.1;": { - ID: "pseudo;10.10.10.10;192.168.1.1;", - LabelMajor: "10.10.10.10", - LabelMinor: "", // after first . - Rank: "", - Pseudo: true, - Adjacency: nil, - Origins: nil, - Metadata: report.AggregateMetadata{}, - }, - } - have := render.LeafMap{ - Selector: report.SelectAddress, - Mapper: render.NetworkHostname, - Pseudo: render.GenericPseudoNode, - }.Render(rpt) - have = trimNodeMetadata(have) - if !reflect.DeepEqual(want, have) { - t.Error("\n" + diff(want, have)) - } -} - -func diff(want, have interface{}) string { - text, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(spew.Sdump(want)), - B: difflib.SplitLines(spew.Sdump(have)), - FromFile: "want", - ToFile: "have", - Context: 3, - }) - return "\n" + text -} diff --git a/render/topologies.go b/render/topologies.go index b93d6e9f1..d8490e7d7 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -25,6 +25,13 @@ var ProcessRenderer = MakeReduce( }, ) +// ProcessRenderer is a Renderer which produces a renderable process +// name graph by munging the progess graph. +var ProcessNameRenderer = Map{ + MapFunc: MapProcess2Name, + Renderer: ProcessRenderer, +} + // ContainerRenderer is a Renderer which produces a renderable container // graph by merging the process graph and the container topology. var ContainerRenderer = MakeReduce( diff --git a/render/topologies_test.go b/render/topologies_test.go new file mode 100644 index 000000000..10e498529 --- /dev/null +++ b/render/topologies_test.go @@ -0,0 +1,390 @@ +package render_test + +import ( + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/pmezard/go-difflib/difflib" + + "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/report" +) + +func init() { + spew.Config.SortKeys = true // :\ +} + +var ( + clientHostID = "client.hostname.com" + serverHostID = "server.hostname.com" + randomHostID = "random.hostname.com" + unknownHostID = "" + + clientHostName = clientHostID + serverHostName = serverHostID + + clientHostNodeID = report.MakeHostNodeID(clientHostID) + serverHostNodeID = report.MakeHostNodeID(serverHostID) + randomHostNodeID = report.MakeHostNodeID(randomHostID) + + client54001NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1) + client54002NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2) + unknownClient1 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected + unknownClient2 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped. + unknownClient3 = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped + server80 = report.MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache + + clientAddressNodeID = report.MakeAddressNodeID(clientHostID, "10.10.10.20") + serverAddressNodeID = report.MakeAddressNodeID(serverHostID, "192.168.1.1") + randomAddressNodeID = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology + unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, "10.10.10.10") +) + +var ( + rpt = report.Report{ + Endpoint: report.Topology{ + Adjacency: report.Adjacency{ + report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80), + report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80), + report.MakeAdjacencyID(server80): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1, unknownClient2, unknownClient3), + }, + NodeMetadatas: report.NodeMetadatas{ + // NodeMetadata is arbitrary. We're free to put only precisely what we + // care to test into the fixture. Just be sure to include the bits + // that the mapping funcs extract :) + client54001NodeID: report.NodeMetadata{ + "name": "curl", + "domain": "client-54001-domain", + "pid": "10001", + report.HostNodeID: clientHostNodeID, + "host_name": clientHostName, + }, + client54002NodeID: report.NodeMetadata{ + "name": "curl", // should be same as above! + "domain": "client-54002-domain", // may be different than above + "pid": "10001", // should be same as above! + report.HostNodeID: clientHostNodeID, + }, + server80: report.NodeMetadata{ + "name": "apache", + "domain": "server-80-domain", + "pid": "215", + report.HostNodeID: serverHostNodeID, + }, + }, + EdgeMetadatas: report.EdgeMetadatas{ + report.MakeEdgeID(client54001NodeID, server80): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 100, + BytesEgress: 10, + }, + report.MakeEdgeID(client54002NodeID, server80): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 200, + BytesEgress: 20, + }, + + report.MakeEdgeID(server80, client54001NodeID): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 10, + BytesEgress: 100, + }, + report.MakeEdgeID(server80, client54002NodeID): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 20, + BytesEgress: 200, + }, + report.MakeEdgeID(server80, unknownClient1): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 30, + BytesEgress: 300, + }, + report.MakeEdgeID(server80, unknownClient2): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 40, + BytesEgress: 400, + }, + report.MakeEdgeID(server80, unknownClient3): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 50, + BytesEgress: 500, + }, + }, + }, + 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{}, + }, + Address: report.Topology{ + Adjacency: report.Adjacency{ + report.MakeAdjacencyID(clientAddressNodeID): report.MakeIDList(serverAddressNodeID), + report.MakeAdjacencyID(randomAddressNodeID): report.MakeIDList(serverAddressNodeID), + report.MakeAdjacencyID(serverAddressNodeID): report.MakeIDList(clientAddressNodeID, unknownAddressNodeID), // no backlink to random + }, + NodeMetadatas: report.NodeMetadatas{ + clientAddressNodeID: report.NodeMetadata{ + "name": "client.hostname.com", // hostname + "host_name": "client.hostname.com", + report.HostNodeID: clientHostNodeID, + }, + randomAddressNodeID: report.NodeMetadata{ + "name": "random.hostname.com", // hostname + report.HostNodeID: randomHostNodeID, + }, + serverAddressNodeID: report.NodeMetadata{ + "name": "server.hostname.com", // hostname + report.HostNodeID: serverHostNodeID, + }, + }, + EdgeMetadatas: report.EdgeMetadatas{ + report.MakeEdgeID(clientAddressNodeID, serverAddressNodeID): report.EdgeMetadata{ + WithConnCountTCP: true, + MaxConnCountTCP: 3, + }, + report.MakeEdgeID(randomAddressNodeID, serverAddressNodeID): report.EdgeMetadata{ + WithConnCountTCP: true, + MaxConnCountTCP: 20, // dangling connections, weird but possible + }, + report.MakeEdgeID(serverAddressNodeID, clientAddressNodeID): report.EdgeMetadata{ + WithConnCountTCP: true, + MaxConnCountTCP: 3, + }, + report.MakeEdgeID(serverAddressNodeID, unknownAddressNodeID): report.EdgeMetadata{ + WithConnCountTCP: true, + MaxConnCountTCP: 7, + }, + }, + }, + Host: report.Topology{ + Adjacency: report.Adjacency{}, + NodeMetadatas: report.NodeMetadatas{ + serverHostNodeID: report.NodeMetadata{ + "host_name": serverHostName, + "local_networks": "10.10.10.0/24", + "os": "Linux", + "load": "0.01 0.01 0.01", + }, + }, + EdgeMetadatas: report.EdgeMetadatas{}, + }, + } +) + +func trimNodeMetadata(rns render.RenderableNodes) render.RenderableNodes { + result := render.RenderableNodes{} + for id, rn := range rns { + rn.NodeMetadata = nil + result[id] = rn + } + return result +} + +func TestProcessRenderer(t *testing.T) { + want := render.RenderableNodes{ + "pid:client-54001-domain:10001": { + ID: "pid:client-54001-domain:10001", + LabelMajor: "curl", + LabelMinor: "client-54001-domain (10001)", + Rank: "10001", + Pseudo: false, + Adjacency: report.MakeIDList("pid:server-80-domain:215"), + Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 100, + report.KeyBytesEgress: 10, + }, + }, + "pid:client-54002-domain:10001": { + ID: "pid:client-54002-domain:10001", + LabelMajor: "curl", + LabelMinor: "client-54002-domain (10001)", + Rank: "10001", // same process + Pseudo: false, + Adjacency: report.MakeIDList("pid:server-80-domain:215"), + Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 200, + report.KeyBytesEgress: 20, + }, + }, + "pid:server-80-domain:215": { + ID: "pid:server-80-domain:215", + LabelMajor: "apache", + LabelMinor: "server-80-domain (215)", + Rank: "215", + Pseudo: false, + Adjacency: report.MakeIDList( + "pid:client-54001-domain:10001", + "pid:client-54002-domain:10001", + "pseudo;10.10.10.10;192.168.1.1;80", + "pseudo;10.10.10.11;192.168.1.1;80", + ), + Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 150, + report.KeyBytesEgress: 1500, + }, + }, + "pseudo;10.10.10.10;192.168.1.1;80": { + ID: "pseudo;10.10.10.10;192.168.1.1;80", + LabelMajor: "10.10.10.10", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + }, + "pseudo;10.10.10.11;192.168.1.1;80": { + ID: "pseudo;10.10.10.11;192.168.1.1;80", + LabelMajor: "10.10.10.11", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + }, + } + have := render.ProcessRenderer.Render(rpt) + have = trimNodeMetadata(have) + if !reflect.DeepEqual(want, have) { + t.Error("\n" + diff(want, have)) + } +} + +func TestProcessNameRenderer(t *testing.T) { + // For grouped, I've somewhat arbitrarily chosen to squash together all + // processes with the same name by removing the PID and domain (host) + // dimensions from the ID. That could be changed. + want := render.RenderableNodes{ + "curl": { + ID: "curl", + LabelMajor: "curl", + LabelMinor: "", + Rank: "curl", + Pseudo: false, + Adjacency: report.MakeIDList("apache"), + Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 300, + report.KeyBytesEgress: 30, + }, + }, + "apache": { + ID: "apache", + LabelMajor: "apache", + LabelMinor: "", + Rank: "apache", + Pseudo: false, + Adjacency: report.MakeIDList( + "curl", + "pseudo;10.10.10.10;apache", + "pseudo;10.10.10.11;apache", + ), + Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 150, + report.KeyBytesEgress: 1500, + }, + }, + "pseudo;10.10.10.10;apache": { + ID: "pseudo;10.10.10.10;apache", + LabelMajor: "10.10.10.10", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + }, + "pseudo;10.10.10.11;apache": { + ID: "pseudo;10.10.10.11;apache", + LabelMajor: "10.10.10.11", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + }, + } + have := render.ProcessNameRenderer.Render(rpt) + have = trimNodeMetadata(have) + if !reflect.DeepEqual(want, have) { + t.Error("\n" + diff(want, have)) + } +} + +func TestRenderByNetworkHostname(t *testing.T) { + want := render.RenderableNodes{ + "host:client.hostname.com": { + ID: "host:client.hostname.com", + LabelMajor: "client", // before first . + LabelMinor: "hostname.com", // after first . + Rank: "client", + Pseudo: false, + Adjacency: report.MakeIDList("host:server.hostname.com"), + Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeAddressNodeID("client.hostname.com", "10.10.10.20")), + Metadata: report.AggregateMetadata{ + report.KeyMaxConnCountTCP: 3, + }, + }, + "host:random.hostname.com": { + ID: "host:random.hostname.com", + LabelMajor: "random", // before first . + LabelMinor: "hostname.com", // after first . + Rank: "random", + Pseudo: false, + Adjacency: report.MakeIDList("host:server.hostname.com"), + Origins: report.MakeIDList(report.MakeHostNodeID("random.hostname.com"), report.MakeAddressNodeID("random.hostname.com", "172.16.11.9")), + Metadata: report.AggregateMetadata{ + report.KeyMaxConnCountTCP: 20, + }, + }, + "host:server.hostname.com": { + ID: "host:server.hostname.com", + LabelMajor: "server", // before first . + LabelMinor: "hostname.com", // after first . + Rank: "server", + Pseudo: false, + Adjacency: report.MakeIDList("host:client.hostname.com", "pseudo;10.10.10.10;192.168.1.1;"), + Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeAddressNodeID("server.hostname.com", "192.168.1.1")), + Metadata: report.AggregateMetadata{ + report.KeyMaxConnCountTCP: 10, + }, + }, + "pseudo;10.10.10.10;192.168.1.1;": { + ID: "pseudo;10.10.10.10;192.168.1.1;", + LabelMajor: "10.10.10.10", + LabelMinor: "", // after first . + Rank: "", + Pseudo: true, + Adjacency: nil, + Origins: nil, + Metadata: report.AggregateMetadata{}, + }, + } + have := render.LeafMap{ + Selector: report.SelectAddress, + Mapper: render.NetworkHostname, + Pseudo: render.GenericPseudoNode, + }.Render(rpt) + have = trimNodeMetadata(have) + if !reflect.DeepEqual(want, have) { + t.Error("\n" + diff(want, have)) + } +} + +func diff(want, have interface{}) string { + text, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(spew.Sdump(want)), + B: difflib.SplitLines(spew.Sdump(have)), + FromFile: "want", + ToFile: "have", + Context: 3, + }) + return "\n" + text +} From 93d062a3ab46f05b2c2f7189c3c9628fd854687e Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 10:24:34 +0000 Subject: [PATCH 04/13] gorename -from 'github.com/weaveworks/scope/render.RenderableNode.Metadata' -to AggregateMetadata --- render/detailed_node.go | 6 ++-- render/render.go | 2 +- render/renderable_node.go | 65 ++++++++++++++++++++------------------- render/topologies_test.go | 64 +++++++++++++++++++------------------- 4 files changed, 69 insertions(+), 68 deletions(-) diff --git a/render/detailed_node.go b/render/detailed_node.go index 4552a7823..93681c177 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -38,13 +38,13 @@ func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode { tables := []Table{} { rows := []Row{} - if val, ok := n.Metadata[report.KeyMaxConnCountTCP]; ok { + if val, ok := n.AggregateMetadata[report.KeyMaxConnCountTCP]; ok { rows = append(rows, Row{"TCP connections", strconv.FormatInt(int64(val), 10), ""}) } - if val, ok := n.Metadata[report.KeyBytesIngress]; ok { + if val, ok := n.AggregateMetadata[report.KeyBytesIngress]; ok { rows = append(rows, Row{"Bytes ingress", strconv.FormatInt(int64(val), 10), ""}) } - if val, ok := n.Metadata[report.KeyBytesEgress]; ok { + if val, ok := n.AggregateMetadata[report.KeyBytesEgress]; ok { rows = append(rows, Row{"Bytes egress", strconv.FormatInt(int64(val), 10), ""}) } if len(rows) > 0 { diff --git a/render/render.go b/render/render.go index d6b4b01ba..5459ef30f 100644 --- a/render/render.go +++ b/render/render.go @@ -206,7 +206,7 @@ func (m LeafMap) Render(rpt report.Report) RenderableNodes { srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcNodeID) edgeID := report.MakeEdgeID(srcNodeID, dstNodeID) if md, ok := t.EdgeMetadatas[edgeID]; ok { - srcRenderableNode.Metadata.Merge(md.Transform()) + srcRenderableNode.AggregateMetadata.Merge(md.Transform()) } } diff --git a/render/renderable_node.go b/render/renderable_node.go index efb801b72..d5ddb13dd 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -8,15 +8,16 @@ 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 - NodeMetadata report.NodeMetadata `json:"-"` // merged NodeMetadata of the nodes used to build this + 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 + + report.AggregateMetadata `json:"metadata"` // Numeric sums + report.NodeMetadata `json:"-"` // merged NodeMetadata of the nodes used to build this } // RenderableNodes is a set of RenderableNodes @@ -55,44 +56,44 @@ func (rn *RenderableNode) Merge(other RenderableNode) { rn.Adjacency = rn.Adjacency.Add(other.Adjacency...) rn.Origins = rn.Origins.Add(other.Origins...) - rn.Metadata.Merge(other.Metadata) + rn.AggregateMetadata.Merge(other.AggregateMetadata) rn.NodeMetadata.Merge(other.NodeMetadata) } // NewRenderableNode makes a new 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{}, - NodeMetadata: nmd, + ID: id, + LabelMajor: major, + LabelMinor: minor, + Rank: rank, + Pseudo: false, + AggregateMetadata: report.AggregateMetadata{}, + NodeMetadata: nmd, } } func newDerivedNode(id string, node RenderableNode) RenderableNode { return RenderableNode{ - ID: id, - LabelMajor: "", - LabelMinor: "", - Rank: "", - Pseudo: node.Pseudo, - Metadata: node.Metadata, - Origins: node.Origins, - NodeMetadata: node.NodeMetadata, + ID: id, + LabelMajor: "", + LabelMinor: "", + Rank: "", + Pseudo: node.Pseudo, + AggregateMetadata: node.AggregateMetadata, + Origins: node.Origins, + NodeMetadata: node.NodeMetadata, } } func newPseudoNode(id, major, minor string) RenderableNode { return RenderableNode{ - ID: id, - LabelMajor: major, - LabelMinor: minor, - Rank: "", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - NodeMetadata: report.NodeMetadata{}, + ID: id, + LabelMajor: major, + LabelMinor: minor, + Rank: "", + Pseudo: true, + AggregateMetadata: report.AggregateMetadata{}, + NodeMetadata: report.NodeMetadata{}, } } diff --git a/render/topologies_test.go b/render/topologies_test.go index 10e498529..dfa939c0f 100644 --- a/render/topologies_test.go +++ b/render/topologies_test.go @@ -207,7 +207,7 @@ func TestProcessRenderer(t *testing.T) { Pseudo: false, Adjacency: report.MakeIDList("pid:server-80-domain:215"), Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001")), - Metadata: report.AggregateMetadata{ + AggregateMetadata: report.AggregateMetadata{ report.KeyBytesIngress: 100, report.KeyBytesEgress: 10, }, @@ -220,7 +220,7 @@ func TestProcessRenderer(t *testing.T) { Pseudo: false, Adjacency: report.MakeIDList("pid:server-80-domain:215"), Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), - Metadata: report.AggregateMetadata{ + AggregateMetadata: report.AggregateMetadata{ report.KeyBytesIngress: 200, report.KeyBytesEgress: 20, }, @@ -238,22 +238,22 @@ func TestProcessRenderer(t *testing.T) { "pseudo;10.10.10.11;192.168.1.1;80", ), Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), - Metadata: report.AggregateMetadata{ + AggregateMetadata: report.AggregateMetadata{ report.KeyBytesIngress: 150, report.KeyBytesEgress: 1500, }, }, "pseudo;10.10.10.10;192.168.1.1;80": { - ID: "pseudo;10.10.10.10;192.168.1.1;80", - LabelMajor: "10.10.10.10", - Pseudo: true, - Metadata: report.AggregateMetadata{}, + ID: "pseudo;10.10.10.10;192.168.1.1;80", + LabelMajor: "10.10.10.10", + Pseudo: true, + AggregateMetadata: report.AggregateMetadata{}, }, "pseudo;10.10.10.11;192.168.1.1;80": { - ID: "pseudo;10.10.10.11;192.168.1.1;80", - LabelMajor: "10.10.10.11", - Pseudo: true, - Metadata: report.AggregateMetadata{}, + ID: "pseudo;10.10.10.11;192.168.1.1;80", + LabelMajor: "10.10.10.11", + Pseudo: true, + AggregateMetadata: report.AggregateMetadata{}, }, } have := render.ProcessRenderer.Render(rpt) @@ -276,7 +276,7 @@ func TestProcessNameRenderer(t *testing.T) { Pseudo: false, Adjacency: report.MakeIDList("apache"), Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), - Metadata: report.AggregateMetadata{ + AggregateMetadata: report.AggregateMetadata{ report.KeyBytesIngress: 300, report.KeyBytesEgress: 30, }, @@ -293,22 +293,22 @@ func TestProcessNameRenderer(t *testing.T) { "pseudo;10.10.10.11;apache", ), Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), - Metadata: report.AggregateMetadata{ + AggregateMetadata: report.AggregateMetadata{ report.KeyBytesIngress: 150, report.KeyBytesEgress: 1500, }, }, "pseudo;10.10.10.10;apache": { - ID: "pseudo;10.10.10.10;apache", - LabelMajor: "10.10.10.10", - Pseudo: true, - Metadata: report.AggregateMetadata{}, + ID: "pseudo;10.10.10.10;apache", + LabelMajor: "10.10.10.10", + Pseudo: true, + AggregateMetadata: report.AggregateMetadata{}, }, "pseudo;10.10.10.11;apache": { - ID: "pseudo;10.10.10.11;apache", - LabelMajor: "10.10.10.11", - Pseudo: true, - Metadata: report.AggregateMetadata{}, + ID: "pseudo;10.10.10.11;apache", + LabelMajor: "10.10.10.11", + Pseudo: true, + AggregateMetadata: report.AggregateMetadata{}, }, } have := render.ProcessNameRenderer.Render(rpt) @@ -328,7 +328,7 @@ func TestRenderByNetworkHostname(t *testing.T) { Pseudo: false, Adjacency: report.MakeIDList("host:server.hostname.com"), Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeAddressNodeID("client.hostname.com", "10.10.10.20")), - Metadata: report.AggregateMetadata{ + AggregateMetadata: report.AggregateMetadata{ report.KeyMaxConnCountTCP: 3, }, }, @@ -340,7 +340,7 @@ func TestRenderByNetworkHostname(t *testing.T) { Pseudo: false, Adjacency: report.MakeIDList("host:server.hostname.com"), Origins: report.MakeIDList(report.MakeHostNodeID("random.hostname.com"), report.MakeAddressNodeID("random.hostname.com", "172.16.11.9")), - Metadata: report.AggregateMetadata{ + AggregateMetadata: report.AggregateMetadata{ report.KeyMaxConnCountTCP: 20, }, }, @@ -352,19 +352,19 @@ func TestRenderByNetworkHostname(t *testing.T) { Pseudo: false, Adjacency: report.MakeIDList("host:client.hostname.com", "pseudo;10.10.10.10;192.168.1.1;"), Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeAddressNodeID("server.hostname.com", "192.168.1.1")), - Metadata: report.AggregateMetadata{ + AggregateMetadata: report.AggregateMetadata{ report.KeyMaxConnCountTCP: 10, }, }, "pseudo;10.10.10.10;192.168.1.1;": { - ID: "pseudo;10.10.10.10;192.168.1.1;", - LabelMajor: "10.10.10.10", - LabelMinor: "", // after first . - Rank: "", - Pseudo: true, - Adjacency: nil, - Origins: nil, - Metadata: report.AggregateMetadata{}, + ID: "pseudo;10.10.10.10;192.168.1.1;", + LabelMajor: "10.10.10.10", + LabelMinor: "", // after first . + Rank: "", + Pseudo: true, + Adjacency: nil, + Origins: nil, + AggregateMetadata: report.AggregateMetadata{}, }, } have := render.LeafMap{ From 3de721bb167576e4fe0bf65ec56fd8b0ad983d04 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 11:59:36 +0000 Subject: [PATCH 05/13] Make topologies_test.go pass. --- render/detailed_node.go | 6 +- render/detailed_node_test.go | 12 +-- render/mapping.go | 18 +--- render/mapping_test.go | 14 --- render/render.go | 17 ++-- render/topologies_test.go | 185 +++++++++++++++++++++-------------- 6 files changed, 127 insertions(+), 125 deletions(-) diff --git a/render/detailed_node.go b/render/detailed_node.go index 93681c177..6b3e9513f 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -102,10 +102,8 @@ func OriginTable(r report.Report, originID string) (Table, bool) { func endpointOriginTable(nmd report.NodeMetadata) (Table, bool) { rows := []Row{} for _, tuple := range []struct{ key, human string }{ - {"endpoint", "Endpoint"}, - {"host_name", "Host name"}, - {"pid", "PID"}, - {"name", "Process name"}, + {"addr", "Endpoint"}, + {"port", "Port"}, } { if val, ok := nmd[tuple.key]; ok { rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) diff --git a/render/detailed_node_test.go b/render/detailed_node_test.go index 0ca88387f..c5e02f6e9 100644 --- a/render/detailed_node_test.go +++ b/render/detailed_node_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/weaveworks/scope/render" - "github.com/weaveworks/scope/report" ) func TestMakeDetailedNode(t *testing.T) { @@ -21,9 +20,8 @@ func TestOriginTable(t *testing.T) { Title: "Origin Endpoint", Numeric: false, Rows: []render.Row{ - {"Host name", clientHostName, ""}, - {"PID", "10001", ""}, - {"Process name", "curl", ""}, + {"Endpoint", clientIP, ""}, + {"Port", clientPort54001, ""}, }, }, clientAddressNodeID: { @@ -33,12 +31,12 @@ func TestOriginTable(t *testing.T) { {"Host name", clientHostName, ""}, }, }, - report.MakeProcessNodeID(clientHostID, "4242"): { + serverProcessNodeID: { Title: "Origin Process", Numeric: false, Rows: []render.Row{ - {"Name (comm)", "curl", ""}, - {"PID", "4242", ""}, + {"Name (comm)", "apache", ""}, + {"PID", serverPID, ""}, }, }, serverHostNodeID: { diff --git a/render/mapping.go b/render/mapping.go index 54742d4be..e923e93b3 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -143,7 +143,10 @@ func MapProcess2Name(n RenderableNode) (RenderableNode, bool) { return RenderableNode{}, false } - return NewRenderableNode(name, name, "", name, n.NodeMetadata), true + node := newDerivedNode(name, n) + node.LabelMajor = name + node.Rank = name + return node, true } func getHostname(m report.NodeMetadata) string { @@ -151,19 +154,6 @@ func getHostname(m report.NodeMetadata) string { return hostname } -// 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. -func ProcessPID(m report.NodeMetadata) (RenderableNode, bool) { - var ( - identifier = fmt.Sprintf("%s:%s:%s", "pid", m["domain"], m["pid"]) - minor = fmt.Sprintf("%s (%s)", m["domain"], m["pid"]) - show = m["pid"] != "" && m["name"] != "" - ) - - 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). diff --git a/render/mapping_test.go b/render/mapping_test.go index 1b85656e5..2d2cbabdb 100644 --- a/render/mapping_test.go +++ b/render/mapping_test.go @@ -40,20 +40,6 @@ func TestUngroupedMapping(t *testing.T) { wantMinor: "", wantRank: "localhost", }, - { - f: render.ProcessPID, - id: "not-used-beta", - meta: report.NodeMetadata{ - "pid": "42", - "name": "curl", - "domain": "hosta", - }, - wantOK: true, - wantID: "pid:hosta:42", - wantMajor: "curl", - wantMinor: "hosta (42)", - wantRank: "42", - }, } { identity := fmt.Sprintf("(%d %s %v)", i, c.id, c.meta) diff --git a/render/render.go b/render/render.go index 5459ef30f..7dce85d27 100644 --- a/render/render.go +++ b/render/render.go @@ -151,10 +151,7 @@ func (m LeafMap) Render(rpt report.Report) RenderableNodes { // Build a set of RenderableNodes for all non-pseudo probes, and an // addressID to nodeID lookup map. Multiple addressIDs can map to the same // RenderableNodes. - var ( - source2mapped = map[string]string{} // source node ID -> mapped node ID - source2host = map[string]string{} // source node ID -> origin host ID - ) + source2mapped := map[string]string{} // source node ID -> mapped node ID for nodeID, metadata := range t.NodeMetadatas { mapped, ok := m.Mapper(metadata) if !ok { @@ -169,18 +166,19 @@ func (m LeafMap) Render(rpt report.Report) RenderableNodes { mapped.Merge(existing) } - mapped.Origins = mapped.Origins.Add(nodeID) + origins := mapped.Origins + origins = origins.Add(nodeID) + origins = origins.Add(metadata[report.HostNodeID]) + mapped.Origins = origins + nodes[mapped.ID] = mapped source2mapped[nodeID] = mapped.ID - source2host[nodeID] = metadata[report.HostNodeID] } // Walk the graph and make connections. for src, dsts := range t.Adjacency { var ( - srcNodeID, ok = report.ParseAdjacencyID(src) - //srcOriginHostID, _, ok2 = ParseNodeID(srcNodeID) - srcHostNodeID = source2host[srcNodeID] + srcNodeID, ok = report.ParseAdjacencyID(src) srcRenderableID = source2mapped[srcNodeID] // must exist srcRenderableNode = nodes[srcRenderableID] // must exist ) @@ -202,7 +200,6 @@ func (m LeafMap) Render(rpt report.Report) RenderableNodes { } srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID) - srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcHostNodeID) srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcNodeID) edgeID := report.MakeEdgeID(srcNodeID, dstNodeID) if md, ok := t.EdgeMetadatas[edgeID]; ok { diff --git a/render/topologies_test.go b/render/topologies_test.go index dfa939c0f..382e08167 100644 --- a/render/topologies_test.go +++ b/render/topologies_test.go @@ -1,6 +1,7 @@ package render_test import ( + "fmt" "reflect" "testing" @@ -21,91 +22,104 @@ var ( randomHostID = "random.hostname.com" unknownHostID = "" + clientIP = "10.10.10.20" + serverIP = "192.168.1.1" + clientPort54001 = "54001" + clientPort54002 = "54002" + serverPort = "80" + clientHostName = clientHostID serverHostName = serverHostID + clientPID = "10001" + serverPID = "215" + nonContainerPID = "1234" + clientHostNodeID = report.MakeHostNodeID(clientHostID) serverHostNodeID = report.MakeHostNodeID(serverHostID) randomHostNodeID = report.MakeHostNodeID(randomHostID) - client54001NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1) - client54002NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2) - unknownClient1 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected - unknownClient2 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped. - unknownClient3 = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped - server80 = report.MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache + client54001NodeID = report.MakeEndpointNodeID(clientHostID, clientIP, clientPort54001) // curl (1) + client54002NodeID = report.MakeEndpointNodeID(clientHostID, clientIP, clientPort54002) // curl (2) + unknownClient1NodeID = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected + unknownClient2NodeID = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped. + unknownClient3NodeID = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped + server80NodeID = report.MakeEndpointNodeID(serverHostID, serverIP, serverPort) // apache clientAddressNodeID = report.MakeAddressNodeID(clientHostID, "10.10.10.20") serverAddressNodeID = report.MakeAddressNodeID(serverHostID, "192.168.1.1") randomAddressNodeID = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, "10.10.10.10") + + clientProcessNodeID = report.MakeProcessNodeID(clientHostID, clientPID) + serverProcessNodeID = report.MakeProcessNodeID(serverHostID, serverPID) + nonContainerProcessNodeID = report.MakeProcessNodeID(serverHostID, nonContainerPID) ) var ( rpt = report.Report{ Endpoint: report.Topology{ Adjacency: report.Adjacency{ - report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80), - report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80), - report.MakeAdjacencyID(server80): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1, unknownClient2, unknownClient3), + report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80NodeID), + report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80NodeID), + report.MakeAdjacencyID(server80NodeID): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1NodeID, unknownClient2NodeID, unknownClient3NodeID), }, NodeMetadatas: report.NodeMetadatas{ // NodeMetadata is arbitrary. We're free to put only precisely what we // care to test into the fixture. Just be sure to include the bits // that the mapping funcs extract :) client54001NodeID: report.NodeMetadata{ - "name": "curl", - "domain": "client-54001-domain", - "pid": "10001", + "addr": clientIP, + "port": clientPort54001, + "pid": clientPID, report.HostNodeID: clientHostNodeID, - "host_name": clientHostName, }, client54002NodeID: report.NodeMetadata{ - "name": "curl", // should be same as above! - "domain": "client-54002-domain", // may be different than above - "pid": "10001", // should be same as above! + "addr": clientIP, + "port": clientPort54002, + "pid": clientPID, // should be same as above! report.HostNodeID: clientHostNodeID, }, - server80: report.NodeMetadata{ - "name": "apache", - "domain": "server-80-domain", - "pid": "215", + server80NodeID: report.NodeMetadata{ + "addr": serverIP, + "port": serverPort, + "pid": serverPID, report.HostNodeID: serverHostNodeID, }, }, EdgeMetadatas: report.EdgeMetadatas{ - report.MakeEdgeID(client54001NodeID, server80): report.EdgeMetadata{ + report.MakeEdgeID(client54001NodeID, server80NodeID): report.EdgeMetadata{ WithBytes: true, BytesIngress: 100, BytesEgress: 10, }, - report.MakeEdgeID(client54002NodeID, server80): report.EdgeMetadata{ + report.MakeEdgeID(client54002NodeID, server80NodeID): report.EdgeMetadata{ WithBytes: true, BytesIngress: 200, BytesEgress: 20, }, - report.MakeEdgeID(server80, client54001NodeID): report.EdgeMetadata{ + report.MakeEdgeID(server80NodeID, client54001NodeID): report.EdgeMetadata{ WithBytes: true, BytesIngress: 10, BytesEgress: 100, }, - report.MakeEdgeID(server80, client54002NodeID): report.EdgeMetadata{ + report.MakeEdgeID(server80NodeID, client54002NodeID): report.EdgeMetadata{ WithBytes: true, BytesIngress: 20, BytesEgress: 200, }, - report.MakeEdgeID(server80, unknownClient1): report.EdgeMetadata{ + report.MakeEdgeID(server80NodeID, unknownClient1NodeID): report.EdgeMetadata{ WithBytes: true, BytesIngress: 30, BytesEgress: 300, }, - report.MakeEdgeID(server80, unknownClient2): report.EdgeMetadata{ + report.MakeEdgeID(server80NodeID, unknownClient2NodeID): report.EdgeMetadata{ WithBytes: true, BytesIngress: 40, BytesEgress: 400, }, - report.MakeEdgeID(server80, unknownClient3): report.EdgeMetadata{ + report.MakeEdgeID(server80NodeID, unknownClient3NodeID): report.EdgeMetadata{ WithBytes: true, BytesIngress: 50, BytesEgress: 500, @@ -115,21 +129,23 @@ var ( 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", + clientProcessNodeID: report.NodeMetadata{ + "pid": clientPID, + "comm": "curl", + "docker_container_id": "a1b2c3d4e5", + report.HostNodeID: clientHostNodeID, }, - report.MakeProcessNodeID(serverHostID, "215"): report.NodeMetadata{ - "pid": "215", - "process_name": "apache", + serverProcessNodeID: report.NodeMetadata{ + "pid": serverPID, + "comm": "apache", + "docker_container_id": "5e4d3c2b1a", + report.HostNodeID: serverHostNodeID, + }, + nonContainerProcessNodeID: report.NodeMetadata{ + "pid": nonContainerPID, + "comm": "bash", + report.HostNodeID: serverHostNodeID, }, - - "no-container": report.NodeMetadata{}, }, EdgeMetadatas: report.EdgeMetadatas{}, }, @@ -188,6 +204,12 @@ var ( } ) +func init() { + if err := rpt.Validate(); err != nil { + panic(err) + } +} + func trimNodeMetadata(rns render.RenderableNodes) render.RenderableNodes { result := render.RenderableNodes{} for id, rn := range rns { @@ -198,51 +220,53 @@ func trimNodeMetadata(rns render.RenderableNodes) render.RenderableNodes { } func TestProcessRenderer(t *testing.T) { + var ( + clientProcessID = fmt.Sprintf("pid:%s:%s", clientHostID, clientPID) + serverProcessID = fmt.Sprintf("pid:%s:%s", serverHostID, serverPID) + nonContainerProcessID = fmt.Sprintf("pid:%s:%s", serverHostID, nonContainerPID) + ) + want := render.RenderableNodes{ - "pid:client-54001-domain:10001": { - ID: "pid:client-54001-domain:10001", + clientProcessID: { + ID: clientProcessID, LabelMajor: "curl", - LabelMinor: "client-54001-domain (10001)", - Rank: "10001", + LabelMinor: fmt.Sprintf("%s (%s)", clientHostID, clientPID), + Rank: clientPID, Pseudo: false, - Adjacency: report.MakeIDList("pid:server-80-domain:215"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001")), + Adjacency: report.MakeIDList(serverProcessID), + Origins: report.MakeIDList(client54001NodeID, client54002NodeID, clientProcessNodeID, clientHostNodeID), AggregateMetadata: report.AggregateMetadata{ - report.KeyBytesIngress: 100, - report.KeyBytesEgress: 10, + report.KeyBytesIngress: 300, + report.KeyBytesEgress: 30, }, }, - "pid:client-54002-domain:10001": { - ID: "pid:client-54002-domain:10001", - LabelMajor: "curl", - LabelMinor: "client-54002-domain (10001)", - Rank: "10001", // same process - Pseudo: false, - Adjacency: report.MakeIDList("pid:server-80-domain:215"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), - AggregateMetadata: report.AggregateMetadata{ - report.KeyBytesIngress: 200, - report.KeyBytesEgress: 20, - }, - }, - "pid:server-80-domain:215": { - ID: "pid:server-80-domain:215", + serverProcessID: { + ID: serverProcessID, LabelMajor: "apache", - LabelMinor: "server-80-domain (215)", - Rank: "215", + LabelMinor: fmt.Sprintf("%s (%s)", serverHostID, serverPID), + Rank: serverPID, Pseudo: false, Adjacency: report.MakeIDList( - "pid:client-54001-domain:10001", - "pid:client-54002-domain:10001", + clientProcessID, "pseudo;10.10.10.10;192.168.1.1;80", "pseudo;10.10.10.11;192.168.1.1;80", ), - Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), + Origins: report.MakeIDList(server80NodeID, serverProcessNodeID, serverHostNodeID), AggregateMetadata: report.AggregateMetadata{ report.KeyBytesIngress: 150, report.KeyBytesEgress: 1500, }, }, + nonContainerProcessID: { + ID: nonContainerProcessID, + LabelMajor: "bash", + LabelMinor: fmt.Sprintf("%s (%s)", serverHostID, nonContainerPID), + Rank: nonContainerPID, + Pseudo: false, + Adjacency: report.MakeIDList(), + Origins: report.MakeIDList(nonContainerProcessNodeID, serverHostNodeID), + AggregateMetadata: report.AggregateMetadata{}, + }, "pseudo;10.10.10.10;192.168.1.1;80": { ID: "pseudo;10.10.10.10;192.168.1.1;80", LabelMajor: "10.10.10.10", @@ -275,7 +299,7 @@ func TestProcessNameRenderer(t *testing.T) { Rank: "curl", Pseudo: false, Adjacency: report.MakeIDList("apache"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), + Origins: report.MakeIDList(client54001NodeID, client54002NodeID, clientProcessNodeID, clientHostNodeID), AggregateMetadata: report.AggregateMetadata{ report.KeyBytesIngress: 300, report.KeyBytesEgress: 30, @@ -289,23 +313,32 @@ func TestProcessNameRenderer(t *testing.T) { Pseudo: false, Adjacency: report.MakeIDList( "curl", - "pseudo;10.10.10.10;apache", - "pseudo;10.10.10.11;apache", + "pseudo;10.10.10.10;192.168.1.1;80", + "pseudo;10.10.10.11;192.168.1.1;80", ), - Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), + Origins: report.MakeIDList(server80NodeID, serverProcessNodeID, serverHostNodeID), AggregateMetadata: report.AggregateMetadata{ report.KeyBytesIngress: 150, report.KeyBytesEgress: 1500, }, }, - "pseudo;10.10.10.10;apache": { - ID: "pseudo;10.10.10.10;apache", + "bash": { + ID: "bash", + LabelMajor: "bash", + LabelMinor: "", + Rank: "bash", + Pseudo: false, + Origins: report.MakeIDList(nonContainerProcessNodeID, serverHostNodeID), + AggregateMetadata: report.AggregateMetadata{}, + }, + "pseudo;10.10.10.10;192.168.1.1;80": { + ID: "pseudo;10.10.10.10;192.168.1.1;80", LabelMajor: "10.10.10.10", Pseudo: true, AggregateMetadata: report.AggregateMetadata{}, }, - "pseudo;10.10.10.11;apache": { - ID: "pseudo;10.10.10.11;apache", + "pseudo;10.10.10.11;192.168.1.1;80": { + ID: "pseudo;10.10.10.11;192.168.1.1;80", LabelMajor: "10.10.10.11", Pseudo: true, AggregateMetadata: report.AggregateMetadata{}, From 75b5e0748283b4695bba409f1fd038bdf9179c6f Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 12:55:03 +0000 Subject: [PATCH 06/13] Add test for ContainerRenderer and make tests in /app pass. --- app/api_topologies_test.go | 6 +-- app/api_topology_test.go | 15 +++---- app/mock_reporter_test.go | 47 ++++++++++++++++++---- experimental/graphviz/handle.go | 2 +- render/mapping.go | 33 ++++++++------- render/renderable_node.go | 13 ++++++ render/topologies_test.go | 71 ++++++++++++++++++++++++++++++++- 7 files changed, 149 insertions(+), 38 deletions(-) diff --git a/app/api_topologies_test.go b/app/api_topologies_test.go index 6b5aff968..3d8494a1c 100644 --- a/app/api_topologies_test.go +++ b/app/api_topologies_test.go @@ -26,15 +26,15 @@ func TestAPITopology(t *testing.T) { } if have := topology.Stats.EdgeCount; have <= 0 { - t.Errorf("EdgeCount isn't positive: %d", have) + t.Errorf("EdgeCount isn't positive for %s: %d", topology.Name, have) } if have := topology.Stats.NodeCount; have <= 0 { - t.Errorf("NodeCount isn't positive: %d", have) + t.Errorf("NodeCount isn't positive for %s: %d", topology.Name, have) } if have := topology.Stats.NonpseudoNodeCount; have <= 0 { - t.Errorf("NonpseudoNodeCount isn't positive: %d", have) + t.Errorf("NonpseudoNodeCount isn't positive for %s: %d", topology.Name, have) } } } diff --git a/app/api_topology_test.go b/app/api_topology_test.go index 3c04c28b8..91dd772af 100644 --- a/app/api_topology_test.go +++ b/app/api_topology_test.go @@ -23,36 +23,37 @@ func TestAPITopologyApplications(t *testing.T) { t.Fatal(err) } equals(t, 4, len(topo.Nodes)) - node, ok := topo.Nodes["pid:node-a.local:23128"] + node, ok := topo.Nodes["pid:hostA:23128"] if !ok { t.Errorf("missing curl node") } equals(t, 1, len(node.Adjacency)) - equals(t, report.MakeIDList("pid:node-b.local:215"), node.Adjacency) + equals(t, report.MakeIDList("pid:hostB:215"), node.Adjacency) equals(t, report.MakeIDList( report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345"), report.MakeEndpointNodeID("hostA", "192.168.1.1", "12346"), + report.MakeProcessNodeID("hostA", "23128"), report.MakeHostNodeID("hostA"), ), node.Origins) equals(t, "curl", node.LabelMajor) - equals(t, "node-a.local (23128)", node.LabelMinor) + equals(t, "hostA (23128)", node.LabelMinor) equals(t, "23128", node.Rank) equals(t, false, node.Pseudo) } { - body := getRawJSON(t, ts, "/api/topology/applications/pid:node-a.local:23128") + body := getRawJSON(t, ts, "/api/topology/applications/pid:hostA:23128") var node APINode if err := json.Unmarshal(body, &node); err != nil { t.Fatal(err) } - equals(t, "pid:node-a.local:23128", node.Node.ID) + equals(t, "pid:hostA:23128", node.Node.ID) equals(t, "curl", node.Node.LabelMajor) - equals(t, "node-a.local (23128)", node.Node.LabelMinor) + equals(t, "hostA (23128)", node.Node.LabelMinor) equals(t, false, node.Node.Pseudo) // Let's not unit-test the specific content of the detail tables } { - body := getRawJSON(t, ts, "/api/topology/applications/pid:node-a.local:23128/pid:node-b.local:215") + body := getRawJSON(t, ts, "/api/topology/applications/pid:hostA:23128/pid:hostB:215") var edge APIEdge if err := json.Unmarshal(body, &edge); err != nil { t.Fatalf("JSON parse error: %s", err) diff --git a/app/mock_reporter_test.go b/app/mock_reporter_test.go index 5529b52da..cbcba0c84 100644 --- a/app/mock_reporter_test.go +++ b/app/mock_reporter_test.go @@ -55,32 +55,63 @@ func (s StaticReport) Report() report.Report { }, NodeMetadatas: report.NodeMetadatas{ report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345"): report.NodeMetadata{ + "addr": "192.168.1.1", + "port": "12345", "pid": "23128", - "name": "curl", - "domain": "node-a.local", report.HostNodeID: report.MakeHostNodeID("hostA"), }, report.MakeEndpointNodeID("hostA", "192.168.1.1", "12346"): report.NodeMetadata{ // <-- same as :12345 + "addr": "192.168.1.1", + "port": "12346", "pid": "23128", - "name": "curl", - "domain": "node-a.local", report.HostNodeID: report.MakeHostNodeID("hostA"), }, report.MakeEndpointNodeID("hostA", "192.168.1.1", "8888"): report.NodeMetadata{ + "addr": "192.168.1.1", + "port": "8888", "pid": "55100", - "name": "ssh", - "domain": "node-a.local", report.HostNodeID: report.MakeHostNodeID("hostA"), }, report.MakeEndpointNodeID("hostB", "192.168.1.2", "80"): report.NodeMetadata{ + "addr": "192.168.1.2", + "port": "80", "pid": "215", - "name": "apache", - "domain": "node-b.local", report.HostNodeID: report.MakeHostNodeID("hostB"), }, }, }, + Process: report.Topology{ + NodeMetadatas: report.NodeMetadatas{ + report.MakeProcessNodeID("hostA", "23128"): report.NodeMetadata{ + "pid": "23128", + "comm": "curl", + report.HostNodeID: report.MakeHostNodeID("hostA"), + }, + report.MakeProcessNodeID("hostA", "8888"): report.NodeMetadata{ + "pid": "8888", + "comm": "ssh", + report.HostNodeID: report.MakeHostNodeID("hostA"), + }, + report.MakeProcessNodeID("hostB", "80"): report.NodeMetadata{ + "pid": "80", + "comm": "apache", + "docker_container_id": "abcdefg", + report.HostNodeID: report.MakeHostNodeID("hostB"), + }, + }, + }, + + Container: report.Topology{ + NodeMetadatas: report.NodeMetadatas{ + report.MakeContainerNodeID("hostB", "abcdefg"): report.NodeMetadata{ + "docker_container_id": "abcdefg", + "docker_container_name": "server", + report.HostNodeID: report.MakeHostNodeID("hostB"), + }, + }, + }, + Address: report.Topology{ Adjacency: report.Adjacency{ report.MakeAdjacencyID(report.MakeAddressNodeID("hostA", "192.168.1.1")): report.MakeIDList(report.MakeAddressNodeID("hostB", "192.168.1.2"), report.MakeAddressNodeID("", "1.2.3.4")), diff --git a/experimental/graphviz/handle.go b/experimental/graphviz/handle.go index fbd560613..03c4e65a8 100644 --- a/experimental/graphviz/handle.go +++ b/experimental/graphviz/handle.go @@ -107,7 +107,7 @@ func mapFunc(r *http.Request) render.LeafMapFunc { case "hosts", "networkhost", "networkhostname": return render.NetworkHostname } - return render.ProcessPID + return render.MapProcessIdentity } func classView(r *http.Request) bool { diff --git a/render/mapping.go b/render/mapping.go index e923e93b3..edc5d5acd 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -7,9 +7,10 @@ import ( "github.com/weaveworks/scope/report" ) +// Constants are used in the tests. const ( - uncontainedID = "uncontained" - uncontainedMajor = "Uncontained" + UncontainedID = "uncontained" + UncontainedMajor = "Uncontained" humanTheInternet = "the Internet" ) @@ -44,11 +45,17 @@ type MapFunc func(RenderableNode) (RenderableNode, bool) // presences of certain keys. func MapEndpointIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( - id = fmt.Sprintf("endpoint:%s:%s:%s", getHostname(m), m["addr"], m["port"]) - major = fmt.Sprintf("%s:%s", m["addr"], m["port"]) - minor = fmt.Sprintf("%s (%s)", getHostname(m), m["pid"]) - rank = m["pid"] + id = fmt.Sprintf("endpoint:%s:%s:%s", getHostname(m), m["addr"], m["port"]) + major = fmt.Sprintf("%s:%s", m["addr"], m["port"]) + pid, ok = m["pid"] + minor = getHostname(m) + rank = major ) + + if ok { + minor = fmt.Sprintf("%s (%s)", getHostname(m), pid) + } + return NewRenderableNode(id, major, minor, rank, m), true } @@ -119,8 +126,8 @@ func MapEndpoint2Process(n RenderableNode) (RenderableNode, bool) { // must be merged with a container graph to get that info. func MapProcess2Container(n RenderableNode) (RenderableNode, bool) { id, ok := n.NodeMetadata["docker_container_id"] - if !ok { - return newPseudoNode(uncontainedID, uncontainedMajor, ""), true + if !ok || n.Pseudo { + return newDerivedPseudoNode(UncontainedID, UncontainedMajor, n), true } return newDerivedNode(id, n), true @@ -154,21 +161,13 @@ func getHostname(m report.NodeMetadata) string { return hostname } -// 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"], m), show -} - // ProcessContainerImage maps topology nodes to the container images they run // on. If no container metadata is found, nodes are grouped into the // Uncontained node. func ProcessContainerImage(m report.NodeMetadata) (RenderableNode, bool) { var id, major, minor, rank string if m["docker_image_id"] == "" { - id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained" + id, major, minor, rank = UncontainedID, UncontainedMajor, "", UncontainedID } else { id, major, minor, rank = m["docker_image_id"], m["docker_image_name"], "", m["docker_image_id"] } diff --git a/render/renderable_node.go b/render/renderable_node.go index d5ddb13dd..a83f251af 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -97,3 +97,16 @@ func newPseudoNode(id, major, minor string) RenderableNode { NodeMetadata: report.NodeMetadata{}, } } + +func newDerivedPseudoNode(id, major string, node RenderableNode) RenderableNode { + return RenderableNode{ + ID: id, + LabelMajor: major, + LabelMinor: "", + Rank: "", + Pseudo: true, + AggregateMetadata: node.AggregateMetadata, + Origins: node.Origins, + NodeMetadata: node.NodeMetadata, + } +} diff --git a/render/topologies_test.go b/render/topologies_test.go index 382e08167..0188c809f 100644 --- a/render/topologies_test.go +++ b/render/topologies_test.go @@ -54,6 +54,11 @@ var ( clientProcessNodeID = report.MakeProcessNodeID(clientHostID, clientPID) serverProcessNodeID = report.MakeProcessNodeID(serverHostID, serverPID) nonContainerProcessNodeID = report.MakeProcessNodeID(serverHostID, nonContainerPID) + + clientContainerID = "a1b2c3d4e5" + serverContainerID = "5e4d3c2b1a" + clientContainerNodeID = report.MakeContainerNodeID(clientHostID, clientContainerID) + serverContainerNodeID = report.MakeContainerNodeID(serverHostID, serverContainerID) ) var ( @@ -132,13 +137,13 @@ var ( clientProcessNodeID: report.NodeMetadata{ "pid": clientPID, "comm": "curl", - "docker_container_id": "a1b2c3d4e5", + "docker_container_id": clientContainerID, report.HostNodeID: clientHostNodeID, }, serverProcessNodeID: report.NodeMetadata{ "pid": serverPID, "comm": "apache", - "docker_container_id": "5e4d3c2b1a", + "docker_container_id": serverContainerID, report.HostNodeID: serverHostNodeID, }, nonContainerProcessNodeID: report.NodeMetadata{ @@ -149,6 +154,20 @@ var ( }, EdgeMetadatas: report.EdgeMetadatas{}, }, + Container: report.Topology{ + NodeMetadatas: report.NodeMetadatas{ + clientContainerNodeID: report.NodeMetadata{ + "docker_container_id": clientContainerID, + "docker_container_name": "client", + report.HostNodeID: clientHostNodeID, + }, + serverContainerNodeID: report.NodeMetadata{ + "docker_container_id": serverContainerID, + "docker_container_name": "server", + report.HostNodeID: serverHostNodeID, + }, + }, + }, Address: report.Topology{ Adjacency: report.Adjacency{ report.MakeAdjacencyID(clientAddressNodeID): report.MakeIDList(serverAddressNodeID), @@ -351,6 +370,54 @@ func TestProcessNameRenderer(t *testing.T) { } } +func TestContainerRenderer(t *testing.T) { + // For grouped, I've somewhat arbitrarily chosen to squash together all + // processes with the same name by removing the PID and domain (host) + // dimensions from the ID. That could be changed. + want := render.RenderableNodes{ + clientContainerID: { + ID: clientContainerID, + LabelMajor: "client", + LabelMinor: clientHostName, + Rank: "", + Pseudo: false, + Adjacency: report.MakeIDList(serverContainerID), + Origins: report.MakeIDList(clientContainerNodeID, client54001NodeID, client54002NodeID, clientProcessNodeID, clientHostNodeID), + AggregateMetadata: report.AggregateMetadata{ + report.KeyBytesIngress: 300, + report.KeyBytesEgress: 30, + }, + }, + serverContainerID: { + ID: serverContainerID, + LabelMajor: "server", + LabelMinor: serverHostName, + Rank: "", + Pseudo: false, + Adjacency: report.MakeIDList(clientContainerID, render.UncontainedID), + Origins: report.MakeIDList(serverContainerNodeID, server80NodeID, serverProcessNodeID, serverHostNodeID), + AggregateMetadata: report.AggregateMetadata{ + report.KeyBytesIngress: 150, + report.KeyBytesEgress: 1500, + }, + }, + render.UncontainedID: { + ID: render.UncontainedID, + LabelMajor: render.UncontainedMajor, + LabelMinor: "", + Rank: "", + Pseudo: true, + Origins: report.MakeIDList(nonContainerProcessNodeID, serverHostNodeID), + AggregateMetadata: report.AggregateMetadata{}, + }, + } + have := render.ContainerRenderer.Render(rpt) + have = trimNodeMetadata(have) + if !reflect.DeepEqual(want, have) { + t.Error("\n" + diff(want, have)) + } +} + func TestRenderByNetworkHostname(t *testing.T) { want := render.RenderableNodes{ "host:client.hostname.com": { From c764164d8329ea4bd7675bf05d5d6eca3f3bf87e Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 12:56:53 +0000 Subject: [PATCH 07/13] Non-leaf (derived) mappers should not propogate their NodeMetadata. --- render/renderable_node.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/renderable_node.go b/render/renderable_node.go index a83f251af..53d66bc83 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -82,7 +82,7 @@ func newDerivedNode(id string, node RenderableNode) RenderableNode { Pseudo: node.Pseudo, AggregateMetadata: node.AggregateMetadata, Origins: node.Origins, - NodeMetadata: node.NodeMetadata, + NodeMetadata: report.NodeMetadata{}, } } @@ -107,6 +107,6 @@ func newDerivedPseudoNode(id, major string, node RenderableNode) RenderableNode Pseudo: true, AggregateMetadata: node.AggregateMetadata, Origins: node.Origins, - NodeMetadata: node.NodeMetadata, + NodeMetadata: report.NodeMetadata{}, } } From 786a1588ea2a7ae23934ed8b9d4e9bb61b2f807b Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 13:01:56 +0000 Subject: [PATCH 08/13] Don't emit process name and domain from procspy. --- probe/spy.go | 4 ---- probe/spy_test.go | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/probe/spy.go b/probe/spy.go index 2e9c66d90..72e6bcb24 100644 --- a/probe/spy.go +++ b/probe/spy.go @@ -79,10 +79,6 @@ func addConnection( "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, } r.Endpoint.NodeMetadatas[scopedLocal] = md diff --git a/probe/spy_test.go b/probe/spy_test.go index c2574c7d4..3ef351381 100644 --- a/probe/spy_test.go +++ b/probe/spy_test.go @@ -124,9 +124,7 @@ func TestSpyWithProcesses(t *testing.T) { } for key, want := range map[string]string{ - "domain": nodeID, - "name": fixProcessName, - "pid": strconv.FormatUint(uint64(fixProcessPID), 10), + "pid": strconv.FormatUint(uint64(fixProcessPID), 10), } { if have := r.Endpoint.NodeMetadatas[scopedLocal][key]; want != have { t.Errorf("Process.NodeMetadatas[%q][%q]: want %q, have %q", scopedLocal, key, want, have) From 7e021baf3b8685ff119331ae839b9b2b9b7a8aa3 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 13:16:39 +0000 Subject: [PATCH 09/13] Add test for MakeDetailedNode. --- render/detailed_node_test.go | 61 +++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/render/detailed_node_test.go b/render/detailed_node_test.go index c5e02f6e9..c9f882eba 100644 --- a/render/detailed_node_test.go +++ b/render/detailed_node_test.go @@ -7,10 +7,6 @@ import ( "github.com/weaveworks/scope/render" ) -func TestMakeDetailedNode(t *testing.T) { - t.Skip("TODO") -} - func TestOriginTable(t *testing.T) { if _, ok := render.OriginTable(rpt, "not-found"); ok { t.Errorf("unknown origin ID gave unexpected success") @@ -59,3 +55,60 @@ func TestOriginTable(t *testing.T) { } } } + +func TestMakeDetailedNode(t *testing.T) { + renderableNode := render.ContainerRenderer.Render(rpt)[serverContainerID] + have := render.MakeDetailedNode(rpt, renderableNode) + want := render.DetailedNode{ + ID: serverContainerID, + LabelMajor: "server", + LabelMinor: serverHostName, + Pseudo: false, + Tables: []render.Table{ + { + Title: "Connections", + Numeric: true, + Rows: []render.Row{ + {"Bytes ingress", "150", ""}, + {"Bytes egress", "1500", ""}, + }, + }, + { + Title: "Origin Endpoint", + Numeric: false, + Rows: []render.Row{ + {"Endpoint", "192.168.1.1", ""}, + {"Port", "80", ""}, + }, + }, + { + Title: "Origin Process", + Numeric: false, + Rows: []render.Row{ + {"Name (comm)", "apache", ""}, + {"PID", "215", ""}, + }, + }, + { + Title: "Origin Container", + Numeric: false, + Rows: []render.Row{ + {"Container ID", "5e4d3c2b1a", ""}, + {"Container name", "server", ""}, + }, + }, + { + Title: "Origin Host", + Numeric: false, + Rows: []render.Row{ + {"Host name", "server.hostname.com", ""}, + {"Load", "0.01 0.01 0.01", ""}, + {"Operating system", "Linux", ""}, + }, + }, + }, + } + if !reflect.DeepEqual(want, have) { + t.Errorf("%s", diff(want, have)) + } +} From ee9ac591fd0d623507cf9af15526cae92f02466b Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 14:46:10 +0000 Subject: [PATCH 10/13] DockerTagger should tag the Process topology now. --- probe/tag/docker_tagger.go | 4 ++-- probe/tag/docker_tagger_test.go | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/probe/tag/docker_tagger.go b/probe/tag/docker_tagger.go index 7fd3059bc..99fa0b7ad 100644 --- a/probe/tag/docker_tagger.go +++ b/probe/tag/docker_tagger.go @@ -271,7 +271,7 @@ func (t *DockerTagger) Containers() []*docker.Container { // Tag implements Tagger. func (t *DockerTagger) Tag(r report.Report) report.Report { - for nodeID, nodeMetadata := range r.Endpoint.NodeMetadatas { + for nodeID, nodeMetadata := range r.Process.NodeMetadatas { pidStr, ok := nodeMetadata["pid"] if !ok { //log.Printf("dockerTagger: %q: no process node ID", id) @@ -318,7 +318,7 @@ func (t *DockerTagger) Tag(r report.Report) report.Report { md[ImageName] = image.RepoTags[0] } - r.Endpoint.NodeMetadatas[nodeID].Merge(md) + r.Process.NodeMetadatas[nodeID].Merge(md) } return r diff --git a/probe/tag/docker_tagger_test.go b/probe/tag/docker_tagger_test.go index dde9ff136..ce25784f3 100644 --- a/probe/tag/docker_tagger_test.go +++ b/probe/tag/docker_tagger_test.go @@ -67,8 +67,8 @@ func TestDockerTagger(t *testing.T) { } var ( - endpoint1NodeID = "somehost.com;192.168.1.1;12345" - endpoint2NodeID = "somehost.com;192.168.1.1;67890" + pid1NodeID = report.MakeProcessNodeID("somehost.com", "1") + pid2NodeID = report.MakeProcessNodeID("somehost.com", "2") endpointNodeMetadata = report.NodeMetadata{ ContainerID: "foo", ImageID: "baz", @@ -83,17 +83,17 @@ func TestDockerTagger(t *testing.T) { ) r := report.MakeReport() - r.Endpoint.NodeMetadatas[endpoint1NodeID] = report.NodeMetadata{"pid": "1"} - r.Endpoint.NodeMetadatas[endpoint2NodeID] = report.NodeMetadata{"pid": "2"} + r.Process.NodeMetadatas[pid1NodeID] = report.NodeMetadata{"pid": "1"} + r.Process.NodeMetadatas[pid2NodeID] = report.NodeMetadata{"pid": "2"} dockerTagger, _ := NewDockerTagger("/irrelevant", 10*time.Second) runtime.Gosched() - for _, endpointNodeID := range []string{endpoint1NodeID, endpoint2NodeID} { + for _, nodeID := range []string{pid1NodeID, pid2NodeID} { want := endpointNodeMetadata.Copy() - have := dockerTagger.Tag(r).Endpoint.NodeMetadatas[endpointNodeID].Copy() + have := dockerTagger.Tag(r).Process.NodeMetadatas[nodeID].Copy() delete(have, "pid") if !reflect.DeepEqual(want, have) { - t.Errorf("%q: want %+v, have %+v", endpointNodeID, want, have) + t.Errorf("%q: want %+v, have %+v", nodeID, want, have) } } From 18c544701aa26a00beff12f9742695a400d83ab6 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 17:21:10 +0000 Subject: [PATCH 11/13] Review feedback --- render/mapping.go | 31 ++++++-------- render/render.go | 27 ++++++------ render/render_test.go | 98 +++++++++++++++++++++---------------------- report/id.go | 6 +++ 4 files changed, 80 insertions(+), 82 deletions(-) diff --git a/render/mapping.go b/render/mapping.go index edc5d5acd..444591fc6 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -36,24 +36,24 @@ type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string) // MapFunc is anything which can take an arbitrary RenderableNode and // return another RenderableNode. // -// As with LeadMapFunc, if the final output parameter is false, the node +// As with LeafMapFunc, if the final output parameter is false, the node // shall be omitted from the rendered topology. 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. +// 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 presence of certain keys. func MapEndpointIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( - id = fmt.Sprintf("endpoint:%s:%s:%s", getHostname(m), m["addr"], m["port"]) + id = fmt.Sprintf("endpoint:%s:%s:%s", report.ExtractHostID(m), m["addr"], m["port"]) major = fmt.Sprintf("%s:%s", m["addr"], m["port"]) pid, ok = m["pid"] - minor = getHostname(m) + minor = report.ExtractHostID(m) rank = major ) if ok { - minor = fmt.Sprintf("%s (%s)", getHostname(m), pid) + minor = fmt.Sprintf("%s (%s)", report.ExtractHostID(m), pid) } return NewRenderableNode(id, major, minor, rank, m), true @@ -61,26 +61,26 @@ func MapEndpointIdentity(m report.NodeMetadata) (RenderableNode, bool) { // 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. +// presence of certain keys. func MapProcessIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( - id = fmt.Sprintf("pid:%s:%s", getHostname(m), m["pid"]) + id = fmt.Sprintf("pid:%s:%s", report.ExtractHostID(m), m["pid"]) major = m["comm"] - minor = fmt.Sprintf("%s (%s)", getHostname(m), m["pid"]) + minor = fmt.Sprintf("%s (%s)", report.ExtractHostID(m), m["pid"]) rank = m["pid"] ) return NewRenderableNode(id, major, minor, rank, m), true } -// MapContainerIdentity maps a container topology node to container +// MapContainerIdentity maps a container topology node to a 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 = getHostname(m) + minor = report.ExtractHostID(m) rank = m["docker_image_id"] ) @@ -109,7 +109,7 @@ func MapEndpoint2Process(n RenderableNode) (RenderableNode, bool) { return RenderableNode{}, false } - id := fmt.Sprintf("pid:%s:%s", getHostname(n.NodeMetadata), pid) + id := fmt.Sprintf("pid:%s:%s", report.ExtractHostID(n.NodeMetadata), pid) return newDerivedNode(id, n), true } @@ -156,11 +156,6 @@ func MapProcess2Name(n RenderableNode) (RenderableNode, bool) { return node, true } -func getHostname(m report.NodeMetadata) string { - hostname, _, _ := report.ParseNodeID(m[report.HostNodeID]) - return hostname -} - // ProcessContainerImage maps topology nodes to the container images they run // on. If no container metadata is found, nodes are grouped into the // Uncontained node. diff --git a/render/render.go b/render/render.go index 7dce85d27..97d54ce03 100644 --- a/render/render.go +++ b/render/render.go @@ -6,25 +6,25 @@ import ( "github.com/weaveworks/scope/report" ) -// Renderer is something that can render a report to a set of RenderableNodes +// Renderer is something that can render a report to a set of RenderableNodes. type Renderer interface { Render(report.Report) RenderableNodes AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata } // Reduce renderer is a Renderer which merges together the output of several -// other renderers +// other renderers. type Reduce []Renderer -// Map is a Renderer which produces a set of RendererNodes from the set of -// RendererNodes produces by another Renderer +// Map is a Renderer which produces a set of RenderableNodes from the set of +// RenderableNodes produced by another Renderer. type Map struct { MapFunc Renderer } -// LeafMap is a Renderer which produces a set of RendererNodes from a report.Topology -// by using a map functions and topology selector. +// LeafMap is a Renderer which produces a set of RenderableNodes from a report.Topology +// by using a map function and topology selector. type LeafMap struct { Selector report.TopologySelector Mapper LeafMapFunc @@ -36,12 +36,12 @@ type FilterUnconnected struct { Renderer } -// MakeReduce is the only sane way to produce a Reduce Renderer +// 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 +// Render produces a set of RenderableNodes given a Report. func (r Reduce) Render(rpt report.Report) RenderableNodes { result := RenderableNodes{} for _, renderer := range r { @@ -50,7 +50,7 @@ func (r Reduce) Render(rpt report.Report) RenderableNodes { return result } -// AggregateMetadata produces an AggregateMetadata for a given edge +// AggregateMetadata produces an AggregateMetadata for a given edge. func (r Reduce) AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata { metadata := report.AggregateMetadata{} for _, renderer := range r { @@ -59,7 +59,7 @@ func (r Reduce) AggregateMetadata(rpt report.Report, localID, remoteID string) r return metadata } -// Render transforms a set of RendererNodes produces by another Renderer +// Render transforms a set of RenderableNodes produces by another Renderer. // using a map function func (m Map) Render(rpt report.Report) RenderableNodes { output, _ := m.render(rpt) @@ -89,14 +89,13 @@ func (m Map) render(rpt report.Report) (RenderableNodes, map[string]string) { } // Rewrite Adjacency for new node IDs. - // NB we don't do pseudo nodes here; we assumer the input graph - // we properly-connected, and if the map func dropped a node, + // NB we don't do pseudo nodes here; we assume the input graph + // is properly-connected, and if the map func dropped a node, // we drop links to it. for outNodeID, inAdjacency := range adjacencies { outAdjacency := report.MakeIDList() for _, inAdjacent := range inAdjacency { - outAdjacent, ok := mapped[inAdjacent] - if ok { + if outAdjacent, ok := mapped[inAdjacent]; ok { outAdjacency = outAdjacency.Add(outAdjacent) } } diff --git a/render/render_test.go b/render/render_test.go index 99752b61a..b0ff17399 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -48,63 +48,61 @@ func TestReduceEdge(t *testing.T) { } } -func TestMapRender(t *testing.T) { +func TestMapRender1(t *testing.T) { // 1. Check when we return false, the node gets filtered out - { - mapper := render.Map{ - MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { - return render.RenderableNode{}, false - }, - Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ - "foo": {ID: "foo"}, - }}, - } - want := render.RenderableNodes{} - have := mapper.Render(report.MakeReport()) - if !reflect.DeepEqual(want, have) { - t.Errorf("want %+v, have %+v", want, have) - } + mapper := render.Map{ + MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { + return render.RenderableNode{}, false + }, + Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ + "foo": {ID: "foo"}, + }}, } + want := render.RenderableNodes{} + have := mapper.Render(report.MakeReport()) + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) + } +} +func TestMapRender2(t *testing.T) { // 2. Check we can remap two nodes into one - { - mapper := render.Map{ - MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { - return render.RenderableNode{ID: "bar"}, true - }, - Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ - "foo": {ID: "foo"}, - "baz": {ID: "baz"}, - }}, - } - want := render.RenderableNodes{ - "bar": render.RenderableNode{ID: "bar"}, - } - have := mapper.Render(report.MakeReport()) - if !reflect.DeepEqual(want, have) { - t.Errorf("want %+v, have %+v", want, have) - } + mapper := render.Map{ + MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { + return render.RenderableNode{ID: "bar"}, true + }, + Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ + "foo": {ID: "foo"}, + "baz": {ID: "baz"}, + }}, } + want := render.RenderableNodes{ + "bar": render.RenderableNode{ID: "bar"}, + } + have := mapper.Render(report.MakeReport()) + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) + } +} +func TestMapRender3(t *testing.T) { // 3. Check we can remap adjacencies - { - mapper := render.Map{ - MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { - return render.RenderableNode{ID: "_" + nodes.ID}, true - }, - Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ - "foo": {ID: "foo", Adjacency: report.MakeIDList("baz")}, - "baz": {ID: "baz", Adjacency: report.MakeIDList("foo")}, - }}, - } - want := render.RenderableNodes{ - "_foo": {ID: "_foo", Adjacency: report.MakeIDList("_baz")}, - "_baz": {ID: "_baz", Adjacency: report.MakeIDList("_foo")}, - } - have := mapper.Render(report.MakeReport()) - if !reflect.DeepEqual(want, have) { - t.Errorf("want %+v, have %+v", want, have) - } + mapper := render.Map{ + MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) { + return render.RenderableNode{ID: "_" + nodes.ID}, true + }, + Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ + "foo": {ID: "foo", Adjacency: report.MakeIDList("baz")}, + "baz": {ID: "baz", Adjacency: report.MakeIDList("foo")}, + }}, + } + want := render.RenderableNodes{ + "_foo": {ID: "_foo", Adjacency: report.MakeIDList("_baz")}, + "_baz": {ID: "_baz", Adjacency: report.MakeIDList("_foo")}, + } + have := mapper.Render(report.MakeReport()) + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) } } diff --git a/report/id.go b/report/id.go index df7c300e1..f29a91905 100644 --- a/report/id.go +++ b/report/id.go @@ -97,6 +97,12 @@ func ParseNodeID(nodeID string) (hostID string, remainder string, ok bool) { return fields[0], fields[1], true } +// ExtractHostID extracts the host id from NodeMetadata +func ExtractHostID(m NodeMetadata) string { + hostid, _, _ := ParseNodeID(m[HostNodeID]) + return hostid +} + // MakePseudoNodeID produces a pseudo node ID from its composite parts. func MakePseudoNodeID(parts ...string) string { return strings.Join(append([]string{"pseudo"}, parts...), ScopeDelim) From f30539601db2107295d6d1a31ea15d95dd068728 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 17:29:37 +0000 Subject: [PATCH 12/13] Restore docker tagging to the endpoint topology until we have the docker images topology. --- probe/tag/docker_tagger.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/probe/tag/docker_tagger.go b/probe/tag/docker_tagger.go index 99fa0b7ad..8455818a4 100644 --- a/probe/tag/docker_tagger.go +++ b/probe/tag/docker_tagger.go @@ -271,7 +271,13 @@ func (t *DockerTagger) Containers() []*docker.Container { // Tag implements Tagger. func (t *DockerTagger) Tag(r report.Report) report.Report { - for nodeID, nodeMetadata := range r.Process.NodeMetadatas { + t.tag(&r.Process) + t.tag(&r.Endpoint) + return r +} + +func (t *DockerTagger) tag(topology *report.Topology) { + for nodeID, nodeMetadata := range topology.NodeMetadatas { pidStr, ok := nodeMetadata["pid"] if !ok { //log.Printf("dockerTagger: %q: no process node ID", id) @@ -318,10 +324,8 @@ func (t *DockerTagger) Tag(r report.Report) report.Report { md[ImageName] = image.RepoTags[0] } - r.Process.NodeMetadatas[nodeID].Merge(md) + topology.NodeMetadatas[nodeID].Merge(md) } - - return r } // ContainerTopology produces a Toplogy of Containers From 0e029fe9bc19281d9fb9db97616fc856c43dd5db Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 18:18:57 +0000 Subject: [PATCH 13/13] Fix fluttering issue. --- render/renderable_node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/renderable_node.go b/render/renderable_node.go index 53d66bc83..e34db414a 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -69,7 +69,7 @@ func NewRenderableNode(id, major, minor, rank string, nmd report.NodeMetadata) R Rank: rank, Pseudo: false, AggregateMetadata: report.AggregateMetadata{}, - NodeMetadata: nmd, + NodeMetadata: nmd.Copy(), } }