diff --git a/app/api_topology_test.go b/app/api_topology_test.go index 1c7eb66c4..0a8df90c2 100644 --- a/app/api_topology_test.go +++ b/app/api_topology_test.go @@ -22,6 +22,9 @@ func fixNodeMetadatas(nodes render.RenderableNodes) render.RenderableNodes { if node.NodeMetadata.Metadata == nil { node.NodeMetadata.Metadata = map[string]string{} } + if node.NodeMetadata.Counters == nil { + node.NodeMetadata.Counters = map[string]int{} + } result[id] = node } return result diff --git a/render/expected/expected.go b/render/expected/expected.go index 194d99f2b..e69d809d3 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -135,7 +135,7 @@ var ( "curl": { ID: "curl", LabelMajor: "curl", - LabelMinor: "", + LabelMinor: "2 processes", Rank: "curl", Pseudo: false, Adjacency: report.MakeIDList("apache"), @@ -155,7 +155,7 @@ var ( "apache": { ID: "apache", LabelMajor: "apache", - LabelMinor: "", + LabelMinor: "1 process", Rank: "apache", Pseudo: false, Adjacency: report.MakeIDList(), @@ -173,7 +173,7 @@ var ( "bash": { ID: "bash", LabelMajor: "bash", - LabelMinor: "", + LabelMinor: "1 process", Rank: "bash", Pseudo: false, Origins: report.MakeIDList( @@ -249,7 +249,7 @@ var ( test.ClientContainerImageName: { ID: test.ClientContainerImageName, LabelMajor: test.ClientContainerImageName, - LabelMinor: "", + LabelMinor: "1 container", Rank: test.ClientContainerImageName, Pseudo: false, Adjacency: report.MakeIDList(test.ServerContainerImageName), @@ -271,7 +271,7 @@ var ( test.ServerContainerImageName: { ID: test.ServerContainerImageName, LabelMajor: test.ServerContainerImageName, - LabelMinor: "", + LabelMinor: "1 container", Rank: test.ServerContainerImageName, Pseudo: false, Adjacency: report.MakeIDList(), diff --git a/render/mapping.go b/render/mapping.go index 8d3369b25..c2a01913a 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -19,6 +19,9 @@ const ( TheInternetID = "theinternet" TheInternetMajor = "The Internet" + + containersKey = "containers" + processesKey = "processes" ) // LeafMapFunc is anything which can take an arbitrary NodeMetadata, which is @@ -250,9 +253,27 @@ func MapProcess2Name(n RenderableNode) (RenderableNode, bool) { node := newDerivedNode(name, n) node.LabelMajor = name node.Rank = name + node.NodeMetadata.Counters[processesKey] = 1 return node, true } +// MapCountProcessName maps 1:1 process name nodes, counting +// the number of processes grouped together and putting +// that info in the minor label. +func MapCountProcessName(n RenderableNode) (RenderableNode, bool) { + if n.Pseudo { + return n, true + } + + processes := n.NodeMetadata.Counters[processesKey] + if processes == 1 { + n.LabelMinor = "1 process" + } else { + n.LabelMinor = fmt.Sprintf("%d processes", processes) + } + return n, true +} + // MapContainer2ContainerImage maps container RenderableNodes to container // image RenderableNodes. // @@ -277,7 +298,10 @@ func MapContainer2ContainerImage(n RenderableNode) (RenderableNode, bool) { return n, false } - return newDerivedNode(id, n), true + // Add container- key to NMD, which will later be counted to produce the minor label + result := newDerivedNode(id, n) + result.NodeMetadata.Counters[containersKey] = 1 + return result, true } // MapContainerImage2Name maps container images RenderableNodes to @@ -304,9 +328,27 @@ func MapContainerImage2Name(n RenderableNode) (RenderableNode, bool) { node := newDerivedNode(name, n) node.LabelMajor = name node.Rank = name + node.NodeMetadata = n.NodeMetadata.Copy() // Propagate NMD for container counting. return node, true } +// MapCountContainers maps 1:1 container image nodes, counting +// the number of containers grouped together and putting +// that info in the minor label. +func MapCountContainers(n RenderableNode) (RenderableNode, bool) { + if n.Pseudo { + return n, true + } + + containers := n.NodeMetadata.Counters[containersKey] + if containers == 1 { + n.LabelMinor = "1 container" + } else { + n.LabelMinor = fmt.Sprintf("%d container(s)", containers) + } + return n, true +} + // MapAddress2Host maps address RenderableNodes to host RenderableNodes. // // Otherthan pseudo nodes, we can assume all nodes have a HostID diff --git a/render/topologies.go b/render/topologies.go index 36688b47a..6f6bb079e 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -71,8 +71,11 @@ func (r ProcessWithContainerNameRenderer) EdgeMetadata(rpt report.Report, localI // ProcessRenderer is a Renderer which produces a renderable process // name graph by munging the progess graph. var ProcessNameRenderer = Map{ - MapFunc: MapProcess2Name, - Renderer: ProcessRenderer, + MapFunc: MapCountProcessName, + Renderer: Map{ + MapFunc: MapProcess2Name, + Renderer: ProcessRenderer, + }, } // ContainerRenderer is a Renderer which produces a renderable container @@ -92,18 +95,21 @@ var ContainerRenderer = MakeReduce( // ContainerImageRenderer is a Renderer which produces a renderable container // image graph by merging the container graph and the container image topology. var ContainerImageRenderer = Map{ - MapFunc: MapContainerImage2Name, - Renderer: MakeReduce( - Map{ - MapFunc: MapContainer2ContainerImage, - Renderer: ContainerRenderer, - }, - LeafMap{ - Selector: report.SelectContainerImage, - Mapper: MapContainerImageIdentity, - Pseudo: PanicPseudoNode, - }, - ), + MapFunc: MapCountContainers, + Renderer: Map{ + MapFunc: MapContainerImage2Name, + Renderer: MakeReduce( + Map{ + MapFunc: MapContainer2ContainerImage, + Renderer: ContainerRenderer, + }, + LeafMap{ + Selector: report.SelectContainerImage, + Mapper: MapContainerImageIdentity, + Pseudo: PanicPseudoNode, + }, + ), + }, } // AddressRenderer is a Renderer which produces a renderable address diff --git a/report/merge.go b/report/merge.go index 406abe6ce..223ffa433 100644 --- a/report/merge.go +++ b/report/merge.go @@ -48,6 +48,9 @@ func (nm NodeMetadata) Merge(other NodeMetadata) NodeMetadata { for k, v := range other.Metadata { nm.Metadata[k] = v // other takes precedence } + for k, v := range other.Counters { + nm.Counters[k] = nm.Counters[k] + v + } return nm } diff --git a/report/topology.go b/report/topology.go index 346805f60..6c4760ea3 100644 --- a/report/topology.go +++ b/report/topology.go @@ -42,6 +42,7 @@ type EdgeMetadata struct { // about a given node in a given topology. type NodeMetadata struct { Metadata map[string]string + Counters map[string]int } // MakeNodeMetadata creates a new NodeMetadata with no initial metadata. @@ -53,6 +54,7 @@ func MakeNodeMetadata() NodeMetadata { func MakeNodeMetadataWith(m map[string]string) NodeMetadata { return NodeMetadata{ Metadata: m, + Counters: map[string]int{}, } } @@ -62,6 +64,9 @@ func (nm NodeMetadata) Copy() NodeMetadata { for k, v := range nm.Metadata { cp.Metadata[k] = v } + for k, v := range nm.Counters { + cp.Counters[k] = v + } return cp }