Skip to content

Commit

Permalink
Add connection tables to details panel
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwilkie committed Feb 23, 2016
1 parent c741ee7 commit 5388fab
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 8 deletions.
5 changes: 3 additions & 2 deletions app/api_topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)})
}
}

Expand Down
149 changes: 147 additions & 2 deletions render/detailed/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
}
}
Expand Down Expand Up @@ -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,
}
}
2 changes: 2 additions & 0 deletions render/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}

Expand All @@ -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}
}

Expand Down
10 changes: 6 additions & 4 deletions report/id_list.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package report

import "sort"

// IDList is a list of string IDs, which are always sorted and unique.
type IDList StringSet

Expand Down Expand Up @@ -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)))
}
16 changes: 16 additions & 0 deletions report/string_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 5388fab

Please sign in to comment.