From e69c8209fbe12e5701dd02ff285a58b3e1bde403 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 00:49:47 +0000 Subject: [PATCH 01/13] remove redundant operation We were setting the very same k/v that we just looked up. --- render/process.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/render/process.go b/render/process.go index 0d29890593..2e9b3804d3 100644 --- a/render/process.go +++ b/render/process.go @@ -45,7 +45,7 @@ func (r processWithContainerNameRenderer) Render(rpt report.Report) Nodes { outputs := make(report.Nodes, len(processes.Nodes)) for id, p := range processes.Nodes { outputs[id] = p - containerID, timestamp, ok := p.Latest.LookupEntry(docker.ContainerID) + containerID, ok := p.Latest.Lookup(docker.ContainerID) if !ok { continue } @@ -53,7 +53,6 @@ func (r processWithContainerNameRenderer) Render(rpt report.Report) Nodes { if !ok { continue } - p.Latest = p.Latest.Set(docker.ContainerID, timestamp, containerID) if containerName, timestamp, ok := container.Latest.LookupEntry(docker.ContainerName); ok { p.Latest = p.Latest.Set(docker.ContainerName, timestamp, containerName) } From 27c2f3a5d2ef2db6d2a63bc8a244868751d4bbfc Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 00:51:58 +0000 Subject: [PATCH 02/13] refactor: use propagateLatest instead of the equivalent long-hand code. --- render/process.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/render/process.go b/render/process.go index 2e9b3804d3..092ba00d26 100644 --- a/render/process.go +++ b/render/process.go @@ -53,9 +53,7 @@ func (r processWithContainerNameRenderer) Render(rpt report.Report) Nodes { if !ok { continue } - if containerName, timestamp, ok := container.Latest.LookupEntry(docker.ContainerName); ok { - p.Latest = p.Latest.Set(docker.ContainerName, timestamp, containerName) - } + propagateLatest(docker.ContainerName, container, p) outputs[id] = p } return Nodes{Nodes: outputs, Filtered: processes.Filtered} From d7e90303a63155dcc595f7a6e8fbfd404400e4e8 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 02:24:54 +0000 Subject: [PATCH 03/13] don't propagate PID in endpoints2Processes The 'create' function passed to addChild is only ever invoked when we cannot find a matching process in the process topology. In these cases the host and pid will be _all_ the info we are ever going to have, both of which are incorporated in the id. Node summarisation renders that as best it can; adding the PID as metadata does not make a difference but for a line with that info in the detail panel, which adds nothing since the PID is already included in the label. --- render/process.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/render/process.go b/render/process.go index 092ba00d26..ed52641cca 100644 --- a/render/process.go +++ b/render/process.go @@ -92,7 +92,7 @@ func (e endpoints2Processes) Render(rpt report.Report) Nodes { ret.addChild(n, id, newPseudoNode) } } else { - pid, timestamp, ok := n.Latest.LookupEntry(process.PID) + pid, ok := n.Latest.Lookup(process.PID) if !ok { continue } @@ -105,8 +105,7 @@ func (e endpoints2Processes) Render(rpt report.Report) Nodes { ret.addChild(n, id, func(id string) report.Node { // we have a pid, but no matching process node; // create a new one rather than dropping the data - return report.MakeNode(id).WithTopology(report.Process). - WithLatest(process.PID, timestamp, pid) + return report.MakeNode(id).WithTopology(report.Process) }) } } From a3ba3a5a554044482d459a91a0a44e845ea24509 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 02:31:26 +0000 Subject: [PATCH 04/13] don't propagate Name in processes2Names The name is already the id of the node, and that's what summarisation renders. Nothing looks at process.Name. --- render/process.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/render/process.go b/render/process.go index ed52641cca..c7712e535c 100644 --- a/render/process.go +++ b/render/process.go @@ -146,10 +146,9 @@ func processes2Names(processes Nodes) Nodes { for _, n := range processes.Nodes { if n.Topology == Pseudo { ret.passThrough(n) - } else if name, timestamp, ok := n.Latest.LookupEntry(process.Name); ok { + } else if name, ok := n.Latest.Lookup(process.Name); ok { ret.addChildAndChildren(n, name, func(id string) report.Node { - return report.MakeNode(id).WithTopology(MakeGroupNodeTopology(n.Topology, process.Name)). - WithLatest(process.Name, timestamp, name) + return report.MakeNode(id).WithTopology(MakeGroupNodeTopology(n.Topology, process.Name)) }) } } From bbd6c5fe471fbd3d58202c9b93eeb9593ef0888b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 01:15:39 +0000 Subject: [PATCH 05/13] don't propagate HostNodeID in endpoints2Hosts The HostNodeID is already the id of host nodes (as the name suggests), and that's what summarisation renders. Nothing looks at the HostNodeID metadata of host nodes. --- render/host.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/render/host.go b/render/host.go index 1e1e0b0a9e..10b8dc80d2 100644 --- a/render/host.go +++ b/render/host.go @@ -67,18 +67,12 @@ func (e endpoints2Hosts) Render(rpt report.Report) Nodes { for _, n := range endpoints.Nodes { // Nodes without a hostid are treated as pseudo nodes - if hostNodeID, timestamp, ok := n.Latest.LookupEntry(report.HostNodeID); !ok { + if hostNodeID, ok := n.Latest.Lookup(report.HostNodeID); !ok { if id, ok := pseudoNodeID(n, local); ok { ret.addChild(n, id, newPseudoNode) } } else { - id := report.MakeHostNodeID(report.ExtractHostID(n)) - ret.addChild(n, id, func(id string) report.Node { - // we have a hostNodeID, but no matching host node; - // create a new one rather than dropping the data - return newHostNode(id). - WithLatest(report.HostNodeID, timestamp, hostNodeID) - }) + ret.addChild(n, hostNodeID, newHostNode) } } return ret.result(endpoints) From a46b9c9c24e4e4c72f71dce0a475ba60d5365bbb Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 01:53:56 +0000 Subject: [PATCH 06/13] don't propagate ContainerHostname in MapContainer2Hostname The container hostname is already the id of the node, and that's what summarisation renders. Nothing looks at the docker.ContainerHostname metadata of nodes in the ContainerHostname group topology. --- render/container.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/render/container.go b/render/container.go index b97d088521..adb71f9286 100644 --- a/render/container.go +++ b/render/container.go @@ -352,13 +352,12 @@ func MapContainer2Hostname(n report.Node) report.Nodes { // Otherwise, if some some reason the container doesn't have a hostname // (maybe slightly out of sync reports), just drop it - id, timestamp, ok := n.Latest.LookupEntry(docker.ContainerHostname) + id, ok := n.Latest.Lookup(docker.ContainerHostname) if !ok { return report.Nodes{} } node := NewDerivedNode(id, n).WithTopology(MakeGroupNodeTopology(n.Topology, docker.ContainerHostname)) - node.Latest = node.Latest.Set(docker.ContainerHostname, timestamp, id) node.Counters = node.Counters.Add(n.Topology, 1) return report.Nodes{id: node} } From 90342962098e97ac67d078691989f9618c8e5742 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 01:59:10 +0000 Subject: [PATCH 07/13] don't propagate ImageID in MapContainerImage2Name This was building a set of all the image ids represented by the same unversioned image. Well, it was doing that until I broke it with a silly mistake in #1739 - instead of extracting the imageID from the original node ID, it's extracting it from the updated ID, which is the unversioned image. Even if it was working though, it's pointless since nothing is looking at that info. --- render/container.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/render/container.go b/render/container.go index adb71f9286..22d9ac7be7 100644 --- a/render/container.go +++ b/render/container.go @@ -336,10 +336,6 @@ func MapContainerImage2Name(n report.Node) report.Nodes { imageNameWithoutVersion := docker.ImageNameWithoutVersion(imageName) n.ID = report.MakeContainerImageNodeID(imageNameWithoutVersion) - if imageID, ok := report.ParseContainerImageNodeID(n.ID); ok { - n.Sets = n.Sets.Add(docker.ImageID, report.MakeStringSet(imageID)) - } - return report.Nodes{n.ID: n} } From 7ce160d492c4dfdbe87cd5308615d584f176b195 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 01:59:26 +0000 Subject: [PATCH 08/13] don't propagate ImageID in MapContainer2ContainerImage The ImageID is already the id of the node we are creation, and that's what summarisation renders in the event we fail to join this node with a node from the ContainerImage topology that has more metadata. Nothing is looking at the ImageID metadata field. --- render/container.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/render/container.go b/render/container.go index 22d9ac7be7..87d1138b6e 100644 --- a/render/container.go +++ b/render/container.go @@ -307,7 +307,7 @@ func MapContainer2ContainerImage(n report.Node) report.Nodes { // Otherwise, if some some reason the container doesn't have a image_id // (maybe slightly out of sync reports), just drop it - imageID, timestamp, ok := n.Latest.LookupEntry(docker.ImageID) + imageID, ok := n.Latest.Lookup(docker.ImageID) if !ok { return report.Nodes{} } @@ -316,7 +316,6 @@ func MapContainer2ContainerImage(n report.Node) report.Nodes { // counted to produce the minor label id := report.MakeContainerImageNodeID(imageID) result := NewDerivedNode(id, n).WithTopology(report.ContainerImage) - result.Latest = result.Latest.Set(docker.ImageID, timestamp, imageID) result.Counters = result.Counters.Add(n.Topology, 1) return report.Nodes{id: result} } From 5d06f90f1059446f19fb68c6f6388e54eac249d0 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 10:58:19 +0000 Subject: [PATCH 09/13] don't propagate HostNodeID in MapProcess2Container ...when creating Uncontained pseudo nodes. Summarisation of Uncontained/Umanaged only looks at the ID, which includes the HostNodeID. We adjust the promotion of Uncontained to Unmanaged, to operate on the ID instead of (re)extracting the hostID from the HostNodeID metadata. With that, nothing looks at the HostNodeID metadata of Uncontained/Unmanaged nodes. --- render/container.go | 1 - render/pod.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/render/container.go b/render/container.go index 87d1138b6e..481bc986c6 100644 --- a/render/container.go +++ b/render/container.go @@ -281,7 +281,6 @@ func MapProcess2Container(n report.Node) report.Nodes { } else { id = MakePseudoNodeID(UncontainedID, report.ExtractHostID(n)) node = NewDerivedPseudoNode(id, n) - node = propagateLatest(report.HostNodeID, n, node) node = propagateLatest(IsConnectedMark, n, node) } return report.Nodes{id: node} diff --git a/render/pod.go b/render/pod.go index 34bbb5115f..ebb304f80d 100644 --- a/render/pod.go +++ b/render/pod.go @@ -139,7 +139,7 @@ func Map2Parent( return func(n report.Node) report.Nodes { // Uncontained becomes Unmanaged/whatever if noParentsPseudoID is set if noParentsPseudoID != "" && strings.HasPrefix(n.ID, UncontainedIDPrefix) { - id := MakePseudoNodeID(noParentsPseudoID, report.ExtractHostID(n)) + id := MakePseudoNodeID(noParentsPseudoID, n.ID[len(UncontainedIDPrefix):]) node := NewDerivedPseudoNode(id, n) return report.Nodes{id: node} } From 4c1d5640ae30db795b2c633e416bbb47ced46bd7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 11:01:08 +0000 Subject: [PATCH 10/13] don't propagate IsConnectedMark in MapProcess2Container ...when creating Uncontained pseudo nodes. IsConnectedMark only affects summarisation of genuine process nodes. --- render/container.go | 1 - 1 file changed, 1 deletion(-) diff --git a/render/container.go b/render/container.go index 481bc986c6..e42ac959c8 100644 --- a/render/container.go +++ b/render/container.go @@ -281,7 +281,6 @@ func MapProcess2Container(n report.Node) report.Nodes { } else { id = MakePseudoNodeID(UncontainedID, report.ExtractHostID(n)) node = NewDerivedPseudoNode(id, n) - node = propagateLatest(IsConnectedMark, n, node) } return report.Nodes{id: node} } From 2d6badba79a071216c93184aa445391d7756bbe9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 13:49:40 +0000 Subject: [PATCH 11/13] cope with processes that have no HostNodeID metadata Some process nodes may not have a HostNodeID metadata, e.g. when an endpoint references a pid that we know nothing about. When mapping processes to containers, we therefore shouldn't rely on HostNodeID. Instead we can obtain the hostID from the process node ID. This has been broken for a while, possibly forever. --- render/container.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/render/container.go b/render/container.go index e42ac959c8..c0c3d621f3 100644 --- a/render/container.go +++ b/render/container.go @@ -268,9 +268,7 @@ func MapProcess2Container(n report.Node) report.Nodes { } // Otherwise, if the process is not in a container, group it into - // an per-host "Uncontained" node. If for whatever reason this - // node doesn't have a host id in their node metadata, it'll all - // get grouped into a single uncontained node. + // a per-host "Uncontained" node. var ( id string node report.Node @@ -279,7 +277,8 @@ func MapProcess2Container(n report.Node) report.Nodes { id = report.MakeContainerNodeID(containerID) node = NewDerivedNode(id, n).WithTopology(report.Container) } else { - id = MakePseudoNodeID(UncontainedID, report.ExtractHostID(n)) + hostID, _, _ := report.ParseProcessNodeID(n.ID) + id = MakePseudoNodeID(UncontainedID, hostID) node = NewDerivedPseudoNode(id, n) } return report.Nodes{id: node} From a7b6bb09a1f5513ec5aaf7a1cd2128fb685477d5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 02:27:26 +0000 Subject: [PATCH 12/13] refactor: lift closures and extract constants --- render/container.go | 4 +++- render/process.go | 20 ++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/render/container.go b/render/container.go index c0c3d621f3..a79051a1ae 100644 --- a/render/container.go +++ b/render/container.go @@ -335,6 +335,8 @@ func MapContainerImage2Name(n report.Node) report.Nodes { return report.Nodes{n.ID: n} } +var containerHostnameTopology = MakeGroupNodeTopology(report.Container, docker.ContainerHostname) + // MapContainer2Hostname maps container Nodes to 'hostname' renderabled nodes.. func MapContainer2Hostname(n report.Node) report.Nodes { // Propagate all pseudo nodes @@ -349,7 +351,7 @@ func MapContainer2Hostname(n report.Node) report.Nodes { return report.Nodes{} } - node := NewDerivedNode(id, n).WithTopology(MakeGroupNodeTopology(n.Topology, docker.ContainerHostname)) + node := NewDerivedNode(id, n).WithTopology(containerHostnameTopology) node.Counters = node.Counters.Add(n.Topology, 1) return report.Nodes{id: node} } diff --git a/render/process.go b/render/process.go index c7712e535c..72a01b44a5 100644 --- a/render/process.go +++ b/render/process.go @@ -76,6 +76,10 @@ var ProcessNameRenderer = CustomRenderer{RenderFunc: processes2Names, Renderer: type endpoints2Processes struct { } +func newProcessNode(id string) report.Node { + return report.MakeNode(id).WithTopology(report.Process) +} + func (e endpoints2Processes) Render(rpt report.Report) Nodes { if len(rpt.Process.Nodes) == 0 { return Nodes{} @@ -102,11 +106,7 @@ func (e endpoints2Processes) Render(rpt report.Report) Nodes { hostID, _ := report.ParseHostNodeID(hostNodeID) id := report.MakeProcessNodeID(hostID, pid) - ret.addChild(n, id, func(id string) report.Node { - // we have a pid, but no matching process node; - // create a new one rather than dropping the data - return report.MakeNode(id).WithTopology(report.Process) - }) + ret.addChild(n, id, newProcessNode) } } return ret.result(endpoints) @@ -139,6 +139,12 @@ func hasMoreThanOneConnection(n report.Node, endpoints report.Nodes) bool { return false } +var processNameTopology = MakeGroupNodeTopology(report.Process, process.Name) + +func newProcessNameNode(id string) report.Node { + return report.MakeNode(id).WithTopology(processNameTopology) +} + // processes2Names maps process Nodes to Nodes for each process name. func processes2Names(processes Nodes) Nodes { ret := newJoinResults(nil) @@ -147,9 +153,7 @@ func processes2Names(processes Nodes) Nodes { if n.Topology == Pseudo { ret.passThrough(n) } else if name, ok := n.Latest.Lookup(process.Name); ok { - ret.addChildAndChildren(n, name, func(id string) report.Node { - return report.MakeNode(id).WithTopology(MakeGroupNodeTopology(n.Topology, process.Name)) - }) + ret.addChildAndChildren(n, name, newProcessNameNode) } } return ret.result(processes) From c8ea7ba49ee8a191b09b07847e34646af23c1ef1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 28 Dec 2017 16:21:33 +0000 Subject: [PATCH 13/13] refactor: simplify joinResults.add* After dropping extra metadata in the rest of this PR, our usage of joinResults.add* only ever ends creating minimal nodes, from just an id and topology. Hence joinResults.add* can be invoked with simply an id and topology instead of a generic node creation function. --- render/container.go | 7 ++++--- render/host.go | 12 ++++-------- render/id.go | 4 ---- render/process.go | 14 +++----------- render/render.go | 21 +++++++++++---------- 5 files changed, 22 insertions(+), 36 deletions(-) diff --git a/render/container.go b/render/container.go index a79051a1ae..71b889d854 100644 --- a/render/container.go +++ b/render/container.go @@ -82,7 +82,7 @@ func (c connectionJoin) Render(rpt report.Report) Nodes { // Nodes without a hostid may be pseudo nodes - if so, pass through to result if _, ok := m.Latest.Lookup(report.HostNodeID); !ok { if id, ok := externalNodeID(m, addr, local); ok { - ret.addChild(m, id, newPseudoNode) + ret.addChild(m, id, Pseudo) continue } } @@ -94,8 +94,9 @@ func (c connectionJoin) Render(rpt report.Report) Nodes { id, found = ipNodes[report.MakeScopedEndpointNodeID(scope, addr, port)] } if found && id != "" { // not one we blanked out earlier - // We are guaranteed to find the id, so no need to pass a node constructor. - ret.addChild(m, id, nil) + // We are guaranteed to find the id, so really this should + // never end up creating a node. + ret.addChild(m, id, report.Container) } } return ret.result(endpoints) diff --git a/render/host.go b/render/host.go index 10b8dc80d2..f368ff1810 100644 --- a/render/host.go +++ b/render/host.go @@ -16,10 +16,6 @@ var HostRenderer = MakeReduce( endpoints2Hosts{}, ) -func newHostNode(id string) report.Node { - return report.MakeNode(id).WithTopology(report.Host) -} - // nodes2Hosts maps any Nodes to host Nodes. // // If this function is given a node without a hostname @@ -45,9 +41,9 @@ func nodes2Hosts(nodes Nodes) Nodes { // hosts, and hence mapping these adjacencies to host // adjacencies would produce edges that aren't present // in reality. - ret.addUnmappedChild(n, id, newHostNode) + ret.addUnmappedChild(n, id, report.Host) } else { - ret.addChild(n, id, newHostNode) + ret.addChild(n, id, report.Host) } } } @@ -69,10 +65,10 @@ func (e endpoints2Hosts) Render(rpt report.Report) Nodes { // Nodes without a hostid are treated as pseudo nodes if hostNodeID, ok := n.Latest.Lookup(report.HostNodeID); !ok { if id, ok := pseudoNodeID(n, local); ok { - ret.addChild(n, id, newPseudoNode) + ret.addChild(n, id, Pseudo) } } else { - ret.addChild(n, hostNodeID, newHostNode) + ret.addChild(n, hostNodeID, report.Host) } } return ret.result(endpoints) diff --git a/render/id.go b/render/id.go index 5fa34f8f24..3b8eda0a69 100644 --- a/render/id.go +++ b/render/id.go @@ -62,10 +62,6 @@ func NewDerivedPseudoNode(id string, node report.Node) report.Node { return output } -func newPseudoNode(id string) report.Node { - return report.MakeNode(id).WithTopology(Pseudo) -} - func pseudoNodeID(n report.Node, local report.Networks) (string, bool) { _, addr, _, ok := report.ParseEndpointNodeID(n.ID) if !ok { diff --git a/render/process.go b/render/process.go index 72a01b44a5..1fdd08b631 100644 --- a/render/process.go +++ b/render/process.go @@ -76,10 +76,6 @@ var ProcessNameRenderer = CustomRenderer{RenderFunc: processes2Names, Renderer: type endpoints2Processes struct { } -func newProcessNode(id string) report.Node { - return report.MakeNode(id).WithTopology(report.Process) -} - func (e endpoints2Processes) Render(rpt report.Report) Nodes { if len(rpt.Process.Nodes) == 0 { return Nodes{} @@ -93,7 +89,7 @@ func (e endpoints2Processes) Render(rpt report.Report) Nodes { // Nodes without a hostid are treated as pseudo nodes if hostNodeID, ok := n.Latest.Lookup(report.HostNodeID); !ok { if id, ok := pseudoNodeID(n, local); ok { - ret.addChild(n, id, newPseudoNode) + ret.addChild(n, id, Pseudo) } } else { pid, ok := n.Latest.Lookup(process.PID) @@ -106,7 +102,7 @@ func (e endpoints2Processes) Render(rpt report.Report) Nodes { hostID, _ := report.ParseHostNodeID(hostNodeID) id := report.MakeProcessNodeID(hostID, pid) - ret.addChild(n, id, newProcessNode) + ret.addChild(n, id, report.Process) } } return ret.result(endpoints) @@ -141,10 +137,6 @@ func hasMoreThanOneConnection(n report.Node, endpoints report.Nodes) bool { var processNameTopology = MakeGroupNodeTopology(report.Process, process.Name) -func newProcessNameNode(id string) report.Node { - return report.MakeNode(id).WithTopology(processNameTopology) -} - // processes2Names maps process Nodes to Nodes for each process name. func processes2Names(processes Nodes) Nodes { ret := newJoinResults(nil) @@ -153,7 +145,7 @@ func processes2Names(processes Nodes) Nodes { if n.Topology == Pseudo { ret.passThrough(n) } else if name, ok := n.Latest.Lookup(process.Name); ok { - ret.addChildAndChildren(n, name, newProcessNameNode) + ret.addChildAndChildren(n, name, processNameTopology) } } return ret.result(processes) diff --git a/render/render.go b/render/render.go index c8b80138f5..ff4e57924b 100644 --- a/render/render.go +++ b/render/render.go @@ -184,12 +184,12 @@ func (ret *joinResults) mapChild(from, to string) { } } -// Add m as a child of the node at id, creating a new result node if -// not already there. -func (ret *joinResults) addUnmappedChild(m report.Node, id string, create func(string) report.Node) { +// Add m as a child of the node at id, creating a new result node in +// the specified topology if not already there. +func (ret *joinResults) addUnmappedChild(m report.Node, id string, topology string) { result, exists := ret.nodes[id] if !exists { - result = create(id) + result = report.MakeNode(id).WithTopology(topology) } result.Children = result.Children.Add(m) if m.Topology != report.Endpoint { // optimisation: we never look at endpoint counts @@ -198,16 +198,17 @@ func (ret *joinResults) addUnmappedChild(m report.Node, id string, create func(s ret.nodes[id] = result } -// Add m as a child of the node at id, creating a new result node if -// not already there, and updating the mapping from old ID to new ID. -func (ret *joinResults) addChild(m report.Node, id string, create func(string) report.Node) { - ret.addUnmappedChild(m, id, create) +// Add m as a child of the node at id, creating a new result node in +// the specified topology if not already there, and updating the +// mapping from old ID to new ID. +func (ret *joinResults) addChild(m report.Node, id string, topology string) { + ret.addUnmappedChild(m, id, topology) ret.mapChild(m.ID, id) } // Like addChild, but also add m's children. -func (ret *joinResults) addChildAndChildren(m report.Node, id string, create func(string) report.Node) { - ret.addUnmappedChild(m, id, create) +func (ret *joinResults) addChildAndChildren(m report.Node, id string, topology string) { + ret.addUnmappedChild(m, id, topology) result := ret.nodes[id] result.Children = result.Children.Merge(m.Children) ret.nodes[id] = result