diff --git a/render/ecs.go b/render/ecs.go index 50eef7226..6ec86df99 100644 --- a/render/ecs.go +++ b/render/ecs.go @@ -10,7 +10,7 @@ var ECSTaskRenderer = ConditionalRenderer(renderECSTopologies, PropagateSingleMetrics(report.Container), MakeReduce( MakeMap( - Map2Parent(report.ECSTask, UnmanagedID, nil), + Map2Parent([]string{report.ECSTask}, NoParentsPseudo, UnmanagedID, nil), MakeFilter( IsRunning, ContainerWithImageNameRenderer, @@ -27,7 +27,7 @@ var ECSServiceRenderer = ConditionalRenderer(renderECSTopologies, PropagateSingleMetrics(report.ECSTask), MakeReduce( MakeMap( - Map2Parent(report.ECSService, "", nil), + Map2Parent([]string{report.ECSService}, NoParentsDrop, "", nil), ECSTaskRenderer, ), SelectECSService, diff --git a/render/pod.go b/render/pod.go index 585fb86f3..3ebd143de 100644 --- a/render/pod.go +++ b/render/pod.go @@ -26,6 +26,15 @@ func isPauseContainer(n report.Node) bool { return ok && kubernetes.IsPauseImageName(image) } +type noParentsActionEnum int + +// Constants for specifying noParentsAction in Map2Parent +const ( + NoParentsPseudo noParentsActionEnum = iota + NoParentsDrop + NoParentsKeep +) + // PodRenderer is a Renderer which produces a renderable kubernetes // graph by merging the container graph and the pods topology. var PodRenderer = ConditionalRenderer(renderKubernetesTopologies, @@ -38,7 +47,7 @@ var PodRenderer = ConditionalRenderer(renderKubernetesTopologies, PropagateSingleMetrics(report.Container), MakeReduce( MakeMap( - Map2Parent(report.Pod, UnmanagedID, nil), + Map2Parent([]string{report.Pod}, NoParentsPseudo, UnmanagedID, nil), MakeFilter( ComposeFilterFuncs( IsRunning, @@ -61,7 +70,7 @@ var PodServiceRenderer = ConditionalRenderer(renderKubernetesTopologies, PropagateSingleMetrics(report.Pod), MakeReduce( MakeMap( - Map2Parent(report.Service, "", nil), + Map2Parent([]string{report.Service}, NoParentsDrop, "", nil), PodRenderer, ), SelectService, @@ -76,7 +85,7 @@ var DeploymentRenderer = ConditionalRenderer(renderKubernetesTopologies, PropagateSingleMetrics(report.ReplicaSet), MakeReduce( MakeMap( - Map2Parent(report.Deployment, "", mapPodCounts), + Map2Parent([]string{report.Deployment}, NoParentsDrop, "", mapPodCounts), ReplicaSetRenderer, ), SelectDeployment, @@ -91,7 +100,7 @@ var ReplicaSetRenderer = ConditionalRenderer(renderKubernetesTopologies, PropagateSingleMetrics(report.Pod), MakeReduce( MakeMap( - Map2Parent(report.ReplicaSet, "", nil), + Map2Parent([]string{report.ReplicaSet}, NoParentsDrop, "", nil), PodRenderer, ), SelectReplicaSet, @@ -106,7 +115,7 @@ var DaemonSetRenderer = ConditionalRenderer(renderKubernetesTopologies, PropagateSingleMetrics(report.Pod), MakeReduce( MakeMap( - Map2Parent(report.DaemonSet, "", nil), + Map2Parent([]string{report.DaemonSet}, NoParentsDrop, "", nil), PodRenderer, ), SelectDaemonSet, @@ -114,11 +123,30 @@ var DaemonSetRenderer = ConditionalRenderer(renderKubernetesTopologies, ), ) -// KubeCombinedRenderer is a Renderer which combines the daemonset and deployment views -// in (for now) a very naive way. -var KubeCombinedRenderer = MakeReduce( - DeploymentRenderer, - DaemonSetRenderer, +// KubeCombinedRenderer is a Renderer which combines the 'top abstraction' of all pods. +// We first map pods to all possible things they can map to, then we map again to +// deployments since some things (replica sets) can map to those (the rest are passed through +// unchanged). +// We can't simply combine the rendered graphs of the high level objects as they would never +// have connections to each other. +var KubeCombinedRenderer = ConditionalRenderer(renderKubernetesTopologies, + MakeReduce( + MakeMap( + Map2Parent([]string{report.Deployment}, NoParentsKeep, "", mapPodCounts), + MakeReduce( + MakeMap( + Map2Parent([]string{ + report.ReplicaSet, + report.DaemonSet, + }, NoParentsKeep, "", nil), + PodRenderer, + ), + SelectReplicaSet, + SelectDaemonSet, + ), + ), + SelectDeployment, + ), ) func mapPodCounts(parent, original report.Node) report.Node { @@ -148,18 +176,23 @@ func MapPod2IP(m report.Node) []string { // Map2Parent returns a MapFunc which maps Nodes to some parent grouping. func Map2Parent( - // The topology ID of the parents - topology string, - // Either the ID prefix of the pseudo node to use for nodes without - // any parents in the group, eg. UnmanagedID, or "" to drop nodes without any parents. + // The topology IDs to look for parents in + topologies []string, + // Choose what to do in the case of nodes with no parents. One of: + // NoParentsPseudo: Map them to a common pseudo node id with prefix noParentsPseudoID + // NoParentsDrop: Map them to no node. + // NoParentsKeep: Map them to themselves, preserving them in the new graph. + noParentsAction noParentsActionEnum, + // The ID prefix of the pseudo node to use for nodes without any parents in the group + // if noParentsAction == Pseudo, eg. UnmanagedID noParentsPseudoID string, // Optional (can be nil) function to modify any parent nodes, // eg. to copy over details from the original node. modifyMappedNode func(parent, original report.Node) report.Node, ) MapFunc { return func(n report.Node, _ report.Networks) report.Nodes { - // Uncontained becomes Unmanaged/whatever if noParentsPseudoID is set - if noParentsPseudoID != "" && strings.HasPrefix(n.ID, UncontainedIDPrefix) { + // Uncontained becomes Unmanaged/whatever if noParentsAction == Pseudo + if noParentsAction == NoParentsPseudo && strings.HasPrefix(n.ID, UncontainedIDPrefix) { id := MakePseudoNodeID(noParentsPseudoID, report.ExtractHostID(n)) node := NewDerivedPseudoNode(id, n) return report.Nodes{id: node} @@ -170,28 +203,36 @@ func Map2Parent( return report.Nodes{n.ID: n} } - // If some some reason the node doesn't have any of these ids - // (maybe slightly out of sync reports, or its not in this group), - // either drop it or put it in Uncontained/Unmanaged/whatever if one was given - groupIDs, ok := n.Parents.Lookup(topology) - if !ok || len(groupIDs) == 0 { - if noParentsPseudoID == "" { - return report.Nodes{} + // For each topology, map to any parents we can find + result := report.Nodes{} + for _, topology := range topologies { + if groupIDs, ok := n.Parents.Lookup(topology); ok { + for _, id := range groupIDs { + node := NewDerivedNode(id, n).WithTopology(topology) + node.Counters = node.Counters.Add(n.Topology, 1) + if modifyMappedNode != nil { + node = modifyMappedNode(node, n) + } + result[id] = node + } } - id := MakePseudoNodeID(UnmanagedID, report.ExtractHostID(n)) - node := NewDerivedPseudoNode(id, n) - return report.Nodes{id: node} } - result := report.Nodes{} - for _, id := range groupIDs { - node := NewDerivedNode(id, n).WithTopology(topology) - node.Counters = node.Counters.Add(n.Topology, 1) - if modifyMappedNode != nil { - node = modifyMappedNode(node, n) + if len(result) == 0 { + switch noParentsAction { + case NoParentsPseudo: + // Map to pseudo node + id := MakePseudoNodeID(UnmanagedID, report.ExtractHostID(n)) + node := NewDerivedPseudoNode(id, n) + result[id] = node + case NoParentsKeep: + // Pass n to output unmodified + result[n.ID] = n + case NoParentsDrop: + // Do nothing, we will return an empty result } - result[id] = node } + return result } } diff --git a/render/swarm.go b/render/swarm.go index b77177a5e..8385ac0d3 100644 --- a/render/swarm.go +++ b/render/swarm.go @@ -10,7 +10,7 @@ var SwarmServiceRenderer = ConditionalRenderer(renderSwarmTopologies, PropagateSingleMetrics(report.Container), MakeReduce( MakeMap( - Map2Parent(report.SwarmService, UnmanagedID, nil), + Map2Parent([]string{report.SwarmService}, NoParentsPseudo, UnmanagedID, nil), MakeFilter( IsRunning, ContainerWithImageNameRenderer,