From 43a29db73cc375d9590abbe34fffff3b195d56c5 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 18 Apr 2016 12:40:50 +0100 Subject: [PATCH] Add explicit group node summariser instead of doing it in the other summaires --- render/detailed/summary.go | 127 +++++++++++++++++--------------- render/detailed/summary_test.go | 14 ++++ render/expected/expected.go | 27 ++++--- render/id.go | 5 ++ render/mapping.go | 4 +- 5 files changed, 103 insertions(+), 74 deletions(-) diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 3883db99b..1023f322a 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -29,6 +29,18 @@ const ( MarathonAppIDEnv = "MARATHON_APP_ID" ) +var ( + shapesByTopology = map[string]string{ + report.Host: Circle, + report.Process: Square, + report.Pod: Heptagon, + report.Service: Heptagon, + report.Container: Hexagon, + report.ContainerImage: Hexagon, + render.Pseudo: Circle, + } +) + // NodeSummaryGroup is a topology-typed group of children for a Node. type NodeSummaryGroup struct { ID string `json:"id"` @@ -89,6 +101,9 @@ func MakeNodeSummary(r report.Report, n report.Node) (NodeSummary, bool) { if renderer, ok := renderers[n.Topology]; ok { return renderer(baseNodeSummary(r, n), n) } + if strings.HasPrefix(n.Topology, "group:") { + return groupNodeSummary(baseNodeSummary(r, n), n) + } return NodeSummary{}, false } @@ -127,9 +142,13 @@ func (n NodeSummary) Copy() NodeSummary { } func baseNodeSummary(r report.Report, n report.Node) NodeSummary { + shape, ok := shapesByTopology[n.Topology] + if !ok { + shape = Circle + } return NodeSummary{ ID: n.ID, - Shape: Circle, + Shape: shape, Linkable: true, Metadata: NodeMetadata(r, n), Metrics: NodeMetrics(r, n), @@ -142,23 +161,23 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { base.Pseudo = true base.Rank = n.ID - if template, ok := map[string]struct{ Label, LabelMinor, Shape string }{ - render.TheInternetID: {render.InboundMajor, "", Cloud}, - render.IncomingInternetID: {render.InboundMajor, render.InboundMinor, Cloud}, - render.OutgoingInternetID: {render.OutboundMajor, render.OutboundMinor, Cloud}, + if template, ok := map[string]struct{ Label, LabelMinor string }{ + render.TheInternetID: {render.InboundMajor, ""}, + render.IncomingInternetID: {render.InboundMajor, render.InboundMinor}, + render.OutgoingInternetID: {render.OutboundMajor, render.OutboundMinor}, }[n.ID]; ok { base.Label = template.Label base.LabelMinor = template.LabelMinor - base.Shape = template.Shape + base.Shape = Cloud return base, true } // try rendering it as an uncontained node if strings.HasPrefix(n.ID, render.MakePseudoNodeID(render.UncontainedID)) { base.Label = render.UncontainedMajor + base.LabelMinor = report.ExtractHostID(n) base.Shape = Square base.Stack = true - base.LabelMinor = report.ExtractHostID(n) return base, true } @@ -174,6 +193,7 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { // try rendering it as an endpoint if addr, ok := n.Latest.Lookup(endpoint.Addr); ok { base.Label = addr + base.Shape = Circle return base, true } @@ -183,25 +203,15 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { func processNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { base.Label, _ = n.Latest.Lookup(process.Name) base.Rank, _ = n.Latest.Lookup(process.Name) - base.Shape = Square - if p, ok := n.Counters.Lookup(report.Process); ok { - base.Stack = true - if p == 1 { - base.LabelMinor = fmt.Sprintf("%d process", p) - } else { - base.LabelMinor = fmt.Sprintf("%d processes", p) - } + pid, ok := n.Latest.Lookup(process.PID) + if !ok { + return NodeSummary{}, false + } + if containerName, ok := n.Latest.Lookup(docker.ContainerName); ok { + base.LabelMinor = fmt.Sprintf("%s (%s:%s)", report.ExtractHostID(n), containerName, pid) } else { - pid, ok := n.Latest.Lookup(process.PID) - if !ok { - return NodeSummary{}, false - } - if containerName, ok := n.Latest.Lookup(docker.ContainerName); ok { - base.LabelMinor = fmt.Sprintf("%s (%s:%s)", report.ExtractHostID(n), containerName, pid) - } else { - base.LabelMinor = fmt.Sprintf("%s (%s)", report.ExtractHostID(n), pid) - } + base.LabelMinor = fmt.Sprintf("%s (%s)", report.ExtractHostID(n), pid) } _, isConnected := n.Latest.Lookup(render.IsConnected) @@ -211,23 +221,12 @@ func processNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { func containerNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { base.Label = getRenderableContainerName(n) - - if c, ok := n.Counters.Lookup(report.Container); ok { - base.Stack = true - if c == 1 { - base.LabelMinor = fmt.Sprintf("%d container", c) - } else { - base.LabelMinor = fmt.Sprintf("%d containers", c) - } - } else { - base.LabelMinor = report.ExtractHostID(n) - } + base.LabelMinor = report.ExtractHostID(n) if imageName, ok := n.Latest.Lookup(docker.ImageName); ok { base.Rank = render.ImageNameWithoutVersion(imageName) } - base.Shape = Hexagon return base, true } @@ -240,7 +239,6 @@ func containerImageNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bo imageNameWithoutVersion := render.ImageNameWithoutVersion(imageName) base.Label = imageNameWithoutVersion base.Rank = imageNameWithoutVersion - base.Shape = Hexagon base.Stack = true if base.Label == ImageNameNone { @@ -250,13 +248,7 @@ func containerImageNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bo } } - if i, ok := n.Counters.Lookup(report.ContainerImage); ok { - if i == 1 { - base.LabelMinor = fmt.Sprintf("%d image", i) - } else { - base.LabelMinor = fmt.Sprintf("%d images", i) - } - } else if c, ok := n.Counters.Lookup(report.Container); ok { + if c, ok := n.Counters.Lookup(report.Container); ok { if c == 1 { base.LabelMinor = fmt.Sprintf("%d container", c) } else { @@ -269,16 +261,8 @@ func containerImageNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bo func podNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { base.Label, _ = n.Latest.Lookup(kubernetes.PodName) base.Rank, _ = n.Latest.Lookup(kubernetes.PodID) - base.Shape = Heptagon - if p, ok := n.Counters.Lookup(report.Pod); ok { - base.Stack = true - if p == 1 { - base.LabelMinor = fmt.Sprintf("%d pod", p) - } else { - base.LabelMinor = fmt.Sprintf("%d pods", p) - } - } else if c, ok := n.Counters.Lookup(report.Container); ok { + if c, ok := n.Counters.Lookup(report.Container); ok { if c == 1 { base.LabelMinor = fmt.Sprintf("%d container", c) } else { @@ -292,7 +276,6 @@ func podNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { func serviceNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { base.Label, _ = n.Latest.Lookup(kubernetes.ServiceName) base.Rank, _ = n.Latest.Lookup(kubernetes.ServiceID) - base.Shape = Heptagon base.Stack = true // Services are always just a group of pods, so there's no counting multiple @@ -320,16 +303,38 @@ func hostNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { base.Label = hostname } - if h, ok := n.Counters.Lookup(report.Host); ok { - base.Stack = true - if h == 1 { - base.LabelMinor = fmt.Sprintf("%d host", h) - } else { - base.LabelMinor = fmt.Sprintf("%d hosts", h) + return base, true +} + +// groupNodeSummary renders the summary for a group node. n.Topology is +// expected to be of the form: group:container:hostname +func groupNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { + parts := strings.Split(n.Topology, ":") + if len(parts) != 3 { + return NodeSummary{}, false + } + + label, ok := n.Latest.Lookup(parts[2]) + if !ok { + return NodeSummary{}, false + } + base.Label, base.Rank = label, label + + if count, ok := n.Counters.Lookup(parts[1]); ok { + base.LabelMinor = fmt.Sprintf("%d %s", count, parts[1]) + if count != 1 { + if strings.HasSuffix(parts[1], "s") { + base.LabelMinor += "es" + } else { + base.LabelMinor += "s" + } } } - base.Shape = Circle + if base.Shape, ok = shapesByTopology[parts[1]]; !ok { + base.Shape = Circle + } + base.Stack = true return base, true } diff --git a/render/detailed/summary_test.go b/render/detailed/summary_test.go index d562c9a79..93e544e67 100644 --- a/render/detailed/summary_test.go +++ b/render/detailed/summary_test.go @@ -168,6 +168,20 @@ func TestMakeNodeSummary(t *testing.T) { Adjacency: report.MakeIDList(fixture.ServerHostNodeID), }, }, + { + name: "group node rendering", + input: expected.RenderedProcessNames[fixture.ServerName], + ok: true, + want: detailed.NodeSummary{ + ID: "apache", + Label: "apache", + LabelMinor: "1 process", + Rank: "apache", + Shape: "square", + Stack: true, + Linkable: true, + }, + }, } for _, testcase := range testcases { have, ok := detailed.MakeNodeSummary(fixture.Report, testcase.input) diff --git a/render/expected/expected.go b/render/expected/expected.go index 5f70ff9a5..64f6ac561 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -27,14 +27,15 @@ var ( return n } } - pseudo = node(render.Pseudo) - endpoint = node(report.Endpoint) - processNode = node(report.Process) - container = node(report.Container) - containerImage = node(report.ContainerImage) - pod = node(report.Pod) - service = node(report.Service) - hostNode = node(report.Host) + pseudo = node(render.Pseudo) + endpoint = node(report.Endpoint) + processNode = node(report.Process) + processNameNode = node(render.MakeGroupNodeTopology(report.Process, process.Name)) + container = node(report.Container) + containerImage = node(report.ContainerImage) + pod = node(report.Pod) + service = node(report.Service) + hostNode = node(report.Host) UnknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP) UnknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP) @@ -111,7 +112,9 @@ var ( } RenderedProcessNames = report.Nodes{ - fixture.Client1Name: processNode(fixture.Client1Name, fixture.ServerName). + fixture.Client1Name: processNameNode(fixture.Client1Name, fixture.ServerName). + WithLatests(map[string]string{process.Name: fixture.Client1Name}). + WithCounters(map[string]int{report.Process: 2}). WithChildren(report.MakeNodeSet( RenderedEndpoints[fixture.Client54001NodeID], RenderedEndpoints[fixture.Client54002NodeID], @@ -119,13 +122,15 @@ var ( RenderedProcesses[fixture.ClientProcess2NodeID], )), - fixture.ServerName: processNode(fixture.ServerName). + fixture.ServerName: processNameNode(fixture.ServerName). + WithLatests(map[string]string{process.Name: fixture.ServerName}). + WithCounters(map[string]int{report.Process: 1}). WithChildren(report.MakeNodeSet( RenderedEndpoints[fixture.Server80NodeID], RenderedProcesses[fixture.ServerProcessNodeID], )), - fixture.NonContainerName: processNode(fixture.NonContainerName, render.OutgoingInternetID). + fixture.NonContainerName: processNameNode(fixture.NonContainerName, render.OutgoingInternetID). WithChildren(report.MakeNodeSet( RenderedEndpoints[fixture.NonContainerNodeID], RenderedProcesses[fixture.NonContainerProcessNodeID], diff --git a/render/id.go b/render/id.go index 0ed8d4997..32309fdd9 100644 --- a/render/id.go +++ b/render/id.go @@ -8,3 +8,8 @@ import ( func MakePseudoNodeID(parts ...string) string { return strings.Join(append([]string{"pseudo"}, parts...), ":") } + +// MakeGroupNodeTopology joins the parts of a group topology into the topology of a group node +func MakeGroupNodeTopology(originalTopology, key string) string { + return strings.Join([]string{"group", originalTopology, key}, ":") +} diff --git a/render/mapping.go b/render/mapping.go index c1c246d41..2e9ff6cd5 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -265,7 +265,7 @@ func MapProcess2Name(n report.Node, _ report.Networks) report.Nodes { return report.Nodes{} } - node := NewDerivedNode(name, n).WithTopology(report.Process) + node := NewDerivedNode(name, n).WithTopology(MakeGroupNodeTopology(n.Topology, process.Name)) node.Latest = node.Latest.Set(process.Name, timestamp, name) node.Counters = node.Counters.Add(n.Topology, 1) return report.Nodes{name: node} @@ -461,7 +461,7 @@ func MapContainer2Hostname(n report.Node, _ report.Networks) report.Nodes { return report.Nodes{} } - node := NewDerivedNode(id, n) + node := NewDerivedNode(id, n).WithTopology(MakeGroupNodeTopology(n.Topology, docker.ContainerHostname)) node.Latest = node.Latest. Set(docker.ContainerHostname, timestamp, id). Delete(docker.ContainerName) // TODO(paulbellamy): total hack to render these by hostname instead.