From 5388fab050bd688c94c2ed5771afdd24ad4e0538 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 23 Feb 2016 18:21:08 +0000 Subject: [PATCH] Add connection tables to details panel --- app/api_topology.go | 5 +- render/detailed/node.go | 149 +++++++++++++++++++++++++++++++++++++++- render/mapping.go | 2 + report/id_list.go | 10 +-- report/string_set.go | 16 +++++ 5 files changed, 174 insertions(+), 8 deletions(-) diff --git a/app/api_topology.go b/app/api_topology.go index 855e671fe3..97b4b7648f 100644 --- a/app/api_topology.go +++ b/app/api_topology.go @@ -57,13 +57,14 @@ func handleNode(nodeID string) func(context.Context, Reporter, render.Renderer, return func(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) { var ( rpt = rep.Report(ctx) - node, ok = renderer.Render(rep.Report(ctx))[nodeID] + rendered = renderer.Render(rep.Report(ctx)) + node, ok = rendered[nodeID] ) if !ok { http.NotFound(w, r) return } - respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(rpt, node)}) + respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(rpt, rendered, node)}) } } diff --git a/render/detailed/node.go b/render/detailed/node.go index 8846f910b1..04c5bf5055 100644 --- a/render/detailed/node.go +++ b/render/detailed/node.go @@ -2,7 +2,10 @@ package detailed import ( "sort" + "strconv" + "strings" + //log "github.com/Sirupsen/logrus" "github.com/ugorji/go/codec" "github.com/weaveworks/scope/probe/docker" @@ -78,16 +81,23 @@ func (c *ControlInstance) CodecDecodeSelf(decoder *codec.Decoder) { // MakeNode transforms a renderable node to a detailed node. It uses // aggregate metadata, plus the set of origin node IDs, to produce tables. -func MakeNode(r report.Report, n render.RenderableNode) Node { +func MakeNode(r report.Report, ns render.RenderableNodes, n render.RenderableNode) Node { summary, _ := MakeNodeSummary(n.Node) summary.ID = n.ID summary.Label = n.LabelMajor + + children := children(n) + + // Add a connections tables + children = append(children, makeIncomingConnectionsTable(n, ns)) + children = append(children, makeOutgoingConnectionsTable(n, ns)) + return Node{ NodeSummary: summary, Rank: n.Rank, Pseudo: n.Pseudo, Controls: controls(r, n), - Children: children(n), + Children: children, Parents: Parents(r, n), } } @@ -157,5 +167,140 @@ func children(n render.RenderableNode) []NodeSummaryGroup { nodeSummaryGroups = append(nodeSummaryGroups, group) } } + return nodeSummaryGroups } + +const ( + remotePortKey = "Remote Port" + localPortKey = "Local Port" + countKey = "Count" +) + +func makeIncomingConnectionsTable(n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup { + // Get all endpoint ids which are children of this node + myEndpoints := report.MakeIDList() + n.Children.ForEach(func(child report.Node) { + if child.Topology == report.Endpoint { + myEndpoints = append(myEndpoints, child.ID) + } + }) + + // Get the endpoint children of all nodes which point to one of my endpoint children + endpoints := map[string][]report.Node{} + for _, node := range ns { + if !node.Adjacency.Contains(n.ID) { + continue + } + + node.Children.ForEach(func(child report.Node) { + if child.Topology != report.Endpoint { + return + } + + intersection := child.Adjacency.Intersection(myEndpoints) + if len(intersection) <= 0 { + return + } + + for _, endpoint := range intersection { + endpoints[endpoint] = append(endpoints[endpoint], child) + } + }) + } + + // Dedupe nodes talking to same port multiple times + remotes := map[string]int{} + for myend, nodes := range endpoints { + // what port are they talking to? + parts := strings.SplitN(myend, ":", 4) + if len(parts) != 4 { + continue + } + port := parts[3] + + for _, node := range nodes { + // what is their IP address? + if parts := strings.SplitN(node.ID, ":", 4); len(parts) == 4 { + key := parts[2] + "|" + port + remotes[key] = remotes[key] + 1 + } + } + } + + // Build rows for these deduped children + nodes := []NodeSummary{} + for key, count := range remotes { + parts := strings.SplitN(key, "|", 2) + nodes = append(nodes, NodeSummary{ + ID: key, + Label: parts[0], + Metadata: []MetadataRow{ + { + ID: localPortKey, + Value: parts[1], + }, + { + ID: countKey, + Value: strconv.Itoa(count), + }, + }, + }) + } + + return NodeSummaryGroup{ + TopologyID: report.Endpoint, + Label: "Incoming Connections", + Columns: []Column{localPortKey, countKey}, + Nodes: nodes, + } +} + +func makeOutgoingConnectionsTable(n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup { + // Get all endpoints which are children of this node + endpoints := []report.Node{} + n.Children.ForEach(func(child report.Node) { + if child.Topology == report.Endpoint { + endpoints = append(endpoints, child) + } + }) + + // Dedupe children talking to same port multiple times + remotes := map[string]int{} + + for _, node := range endpoints { + for _, adjacent := range node.Adjacency { + if parts := strings.SplitN(adjacent, ":", 4); len(parts) == 4 { + key := parts[2] + "|" + parts[3] + remotes[key] = remotes[key] + 1 + } + } + } + + // Build rows for these deduped children + nodes := []NodeSummary{} + for key, count := range remotes { + parts := strings.SplitN(key, "|", 2) + nodes = append(nodes, NodeSummary{ + ID: key, + Label: parts[0], + Metadata: []MetadataRow{ + { + ID: remotePortKey, + Value: parts[1], + }, + { + ID: countKey, + Value: strconv.Itoa(count), + }, + }, + }) + } + + return NodeSummaryGroup{ + TopologyID: report.Endpoint, + Label: "Outgoing Connections", + Columns: []Column{remotePortKey, countKey}, + Nodes: nodes, + } +} diff --git a/render/mapping.go b/render/mapping.go index be6526634d..858d477ef9 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -413,6 +413,7 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes { // must be merged with a process graph to get that info. func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes { if n.Pseudo { + n.Children = n.Children.Add(n.Node) return RenderableNodes{n.ID: n} } @@ -424,6 +425,7 @@ func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes { id := MakeProcessID(report.ExtractHostID(n.Node), pid) node := NewDerivedNode(id, n.WithParents(report.EmptySets)) node.Shape = Square + node.Children = node.Children.Add(n.Node) return RenderableNodes{id: node} } diff --git a/report/id_list.go b/report/id_list.go index 50149a0c29..c9778bbcdb 100644 --- a/report/id_list.go +++ b/report/id_list.go @@ -1,7 +1,5 @@ package report -import "sort" - // IDList is a list of string IDs, which are always sorted and unique. type IDList StringSet @@ -35,6 +33,10 @@ func (a IDList) Merge(b IDList) IDList { // Contains returns true if id is in the list. func (a IDList) Contains(id string) bool { - i := sort.Search(len(a), func(i int) bool { return a[i] >= id }) - return i < len(a) && a[i] == id + return StringSet(a).Contains(id) +} + +// Intersection returns the intersection of a and b +func (a IDList) Intersection(b IDList) IDList { + return IDList(StringSet(a).Intersection(StringSet(b))) } diff --git a/report/string_set.go b/report/string_set.go index 6a43bee82d..42f02cea88 100644 --- a/report/string_set.go +++ b/report/string_set.go @@ -35,6 +35,22 @@ func (s StringSet) Contains(str string) bool { return i < len(s) && s[i] == str } +// Intersection returns the intersections of a and b +func (s StringSet) Intersection(b StringSet) StringSet { + result, i, j := EmptyStringSet, 0, 0 + for i < len(s) && j < len(b) { + if s[i] == b[j] { + result = result.Add(s[i]) + } + if s[i] < b[j] { + i++ + } else { + j++ + } + } + return result +} + // Add adds the strings to the StringSet. Add is the only valid way to grow a // StringSet. Add returns the StringSet to enable chaining. func (s StringSet) Add(strs ...string) StringSet {