diff --git a/app/api_topologies.go b/app/api_topologies.go index 92e4e3d11..848a1c1c8 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -435,6 +435,7 @@ func (r *Registry) Add(ts ...APITopologyDesc) { defer r.Unlock() for _, t := range ts { t.URL = apiTopologyURL + t.id + t.renderer = render.Memoise(t.renderer) if t.parent != "" { parent := r.items[t.parent] diff --git a/render/container.go b/render/container.go index 83eec193c..b832c35ce 100644 --- a/render/container.go +++ b/render/container.go @@ -24,7 +24,7 @@ var UncontainedIDPrefix = MakePseudoNodeID(UncontainedID) // NB We only want processes in container _or_ processes with network connections // but we need to be careful to ensure we only include each edge once, by only // including the ProcessRenderer once. -var ContainerRenderer = MakeFilter( +var ContainerRenderer = Memoise(MakeFilter( func(n report.Node) bool { // Drop deleted containers state, ok := n.Latest.Lookup(docker.ContainerState) @@ -37,9 +37,9 @@ var ContainerRenderer = MakeFilter( ), ConnectionJoin(MapContainer2IP, SelectContainer), ), -) +)) -var mapEndpoint2IP = MakeMap(endpoint2IP, SelectEndpoint) +var mapEndpoint2IP = Memoise(MakeMap(endpoint2IP, SelectEndpoint)) const originalNodeID = "original_node_id" @@ -182,11 +182,11 @@ func (r containerWithImageNameRenderer) Render(rpt report.Report, dct Decorator) // ContainerWithImageNameRenderer is a Renderer which produces a container // graph where the ranks are the image names, not their IDs -var ContainerWithImageNameRenderer = containerWithImageNameRenderer{ContainerRenderer} +var ContainerWithImageNameRenderer = Memoise(containerWithImageNameRenderer{ContainerRenderer}) // ContainerImageRenderer is a Renderer which produces a renderable container // image graph by merging the container graph and the container image topology. -var ContainerImageRenderer = FilterEmpty(report.Container, +var ContainerImageRenderer = Memoise(FilterEmpty(report.Container, MakeMap( MapContainerImage2Name, MakeReduce( @@ -197,10 +197,12 @@ var ContainerImageRenderer = FilterEmpty(report.Container, SelectContainerImage, ), ), -) +)) // ContainerHostnameRenderer is a Renderer which produces a renderable container // by hostname graph.. +// +// not memoised var ContainerHostnameRenderer = FilterEmpty(report.Container, MakeReduce( MakeMap( diff --git a/render/ecs.go b/render/ecs.go index e36f3dc52..365b8fb28 100644 --- a/render/ecs.go +++ b/render/ecs.go @@ -5,7 +5,7 @@ import ( ) // ECSTaskRenderer is a Renderer for Amazon ECS tasks. -var ECSTaskRenderer = ConditionalRenderer(renderECSTopologies, +var ECSTaskRenderer = Memoise(ConditionalRenderer(renderECSTopologies, renderParents( report.Container, []string{report.ECSTask}, UnmanagedID, MakeFilter( @@ -13,9 +13,11 @@ var ECSTaskRenderer = ConditionalRenderer(renderECSTopologies, ContainerWithImageNameRenderer, ), ), -) +)) // ECSServiceRenderer is a Renderer for Amazon ECS services. +// +// not memoised var ECSServiceRenderer = ConditionalRenderer(renderECSTopologies, renderParents( report.ECSTask, []string{report.ECSService}, "", diff --git a/render/filters.go b/render/filters.go index bfc235a16..6d9f46272 100644 --- a/render/filters.go +++ b/render/filters.go @@ -114,20 +114,20 @@ type Filter struct { // MakeFilter makes a new Filter (that ignores pseudo nodes). func MakeFilter(f FilterFunc, r Renderer) Renderer { - return Memoise(&Filter{ + return &Filter{ Renderer: r, FilterFunc: func(n report.Node) bool { return n.Topology == Pseudo || f(n) }, - }) + } } // MakeFilterPseudo makes a new Filter that will not ignore pseudo nodes. func MakeFilterPseudo(f FilterFunc, r Renderer) Renderer { - return Memoise(&Filter{ + return &Filter{ Renderer: r, FilterFunc: f, - }) + } } // MakeFilterDecorator makes a decorator that filters out non-pseudo nodes diff --git a/render/host.go b/render/host.go index 1bd0ec1d4..8983eb05a 100644 --- a/render/host.go +++ b/render/host.go @@ -6,6 +6,8 @@ import ( // HostRenderer is a Renderer which produces a renderable host // graph from the host topology. +// +// not memoised var HostRenderer = MakeReduce( MakeMap( MapEndpoint2Host, diff --git a/render/pod.go b/render/pod.go index 6277dba18..28e3a9f67 100644 --- a/render/pod.go +++ b/render/pod.go @@ -42,7 +42,7 @@ func isPauseContainer(n report.Node) bool { // PodRenderer is a Renderer which produces a renderable kubernetes // graph by merging the container graph and the pods topology. -var PodRenderer = ConditionalRenderer(renderKubernetesTopologies, +var PodRenderer = Memoise(ConditionalRenderer(renderKubernetesTopologies, MakeFilter( func(n report.Node) bool { state, ok := n.Latest.Lookup(kubernetes.State) @@ -62,13 +62,17 @@ var PodRenderer = ConditionalRenderer(renderKubernetesTopologies, ), ), ), - ConnectionJoin(MapPod2IP, selectPodsWithDeployments{}), + // ConnectionJoin invokes the renderer twice, hence it + // helps to memoise it. + ConnectionJoin(MapPod2IP, Memoise(selectPodsWithDeployments{})), ), ), -) +)) // PodServiceRenderer is a Renderer which produces a renderable kubernetes services // graph by merging the pods graph and the services topology. +// +// not memoised var PodServiceRenderer = ConditionalRenderer(renderKubernetesTopologies, renderParents( report.Pod, []string{report.Service}, "", @@ -80,6 +84,8 @@ var PodServiceRenderer = ConditionalRenderer(renderKubernetesTopologies, // Pods with no controller are mapped to 'Unmanaged' // We can't simply combine the rendered graphs of the high level objects as they would never // have connections to each other. +// +// not memoised var KubeControllerRenderer = ConditionalRenderer(renderKubernetesTopologies, renderParents( report.Pod, []string{report.Deployment, report.DaemonSet, report.StatefulSet, report.CronJob}, UnmanagedID, diff --git a/render/process.go b/render/process.go index d38cc610e..5093ed3ec 100644 --- a/render/process.go +++ b/render/process.go @@ -26,7 +26,7 @@ var EndpointRenderer = SelectEndpoint // ProcessRenderer is a Renderer which produces a renderable process // graph by merging the endpoint graph and the process topology. -var ProcessRenderer = ConditionalRenderer(renderProcesses, +var ProcessRenderer = Memoise(ConditionalRenderer(renderProcesses, MakeReduce( MakeMap( MapEndpoint2Process, @@ -34,13 +34,13 @@ var ProcessRenderer = ConditionalRenderer(renderProcesses, ), SelectProcess, ), -) +)) // ColorConnectedProcessRenderer colors connected nodes from // ProcessRenderer. Since the process topology views only show // connected processes, we need this info to determine whether // processes appearing in a details panel are linkable. -var ColorConnectedProcessRenderer = ColorConnected(ProcessRenderer) +var ColorConnectedProcessRenderer = Memoise(ColorConnected(ProcessRenderer)) // processWithContainerNameRenderer is a Renderer which produces a process // graph enriched with container names where appropriate @@ -74,10 +74,14 @@ func (r processWithContainerNameRenderer) Render(rpt report.Report, dct Decorato // ProcessWithContainerNameRenderer is a Renderer which produces a process // graph enriched with container names where appropriate +// +// not memoised var ProcessWithContainerNameRenderer = processWithContainerNameRenderer{ProcessRenderer} // ProcessNameRenderer is a Renderer which produces a renderable process // name graph by munging the progess graph. +// +// not memoised var ProcessNameRenderer = ConditionalRenderer(renderProcesses, MakeMap( MapProcess2Name, diff --git a/render/render.go b/render/render.go index e770e6ff7..2e4357588 100644 --- a/render/render.go +++ b/render/render.go @@ -33,19 +33,18 @@ type Reduce []Renderer // MakeReduce is the only sane way to produce a Reduce Renderer. func MakeReduce(renderers ...Renderer) Renderer { - r := Reduce(renderers) - return Memoise(&r) + return Reduce(renderers) } // Render produces a set of Nodes given a Report. -func (r *Reduce) Render(rpt report.Report, dct Decorator) report.Nodes { - l := len(*r) +func (r Reduce) Render(rpt report.Report, dct Decorator) report.Nodes { + l := len(r) switch l { case 0: return report.Nodes{} } c := make(chan report.Nodes, l) - for _, renderer := range *r { + for _, renderer := range r { renderer := renderer // Pike!! go func() { c <- renderer.Render(rpt, dct) @@ -61,9 +60,9 @@ func (r *Reduce) Render(rpt report.Report, dct Decorator) report.Nodes { } // Stats implements Renderer -func (r *Reduce) Stats(rpt report.Report, dct Decorator) Stats { +func (r Reduce) Stats(rpt report.Report, dct Decorator) Stats { var result Stats - for _, renderer := range *r { + for _, renderer := range r { result = result.merge(renderer.Stats(rpt, dct)) } return result @@ -78,7 +77,7 @@ type Map struct { // MakeMap makes a new Map func MakeMap(f MapFunc, r Renderer) Renderer { - return Memoise(&Map{f, r}) + return &Map{f, r} } // Render transforms a set of Nodes produces by another Renderer. @@ -180,7 +179,7 @@ type conditionalRenderer struct { // ConditionalRenderer renders nothing if the condition is false, otherwise it defers // to the wrapped Renderer. func ConditionalRenderer(c Condition, r Renderer) Renderer { - return Memoise(conditionalRenderer{c, r}) + return conditionalRenderer{c, r} } func (cr conditionalRenderer) Render(rpt report.Report, dct Decorator) report.Nodes { diff --git a/render/swarm.go b/render/swarm.go index da6306914..28db6d96f 100644 --- a/render/swarm.go +++ b/render/swarm.go @@ -5,6 +5,8 @@ import ( ) // SwarmServiceRenderer is a Renderer for Docker Swarm services +// +// not memoised var SwarmServiceRenderer = ConditionalRenderer(renderSwarmTopologies, renderParents( report.Container, []string{report.SwarmService}, UnmanagedID, diff --git a/render/weave.go b/render/weave.go index 216a0cf1c..404acaae3 100644 --- a/render/weave.go +++ b/render/weave.go @@ -6,6 +6,8 @@ import ( ) // WeaveRenderer is a Renderer which produces a renderable weave topology. +// +// not memoised var WeaveRenderer = MakeMap( MapWeaveIdentity, SelectOverlay,