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 Mar 3, 2016
1 parent a40b5a3 commit 2fd23a3
Show file tree
Hide file tree
Showing 25 changed files with 1,344 additions and 623 deletions.
7 changes: 4 additions & 3 deletions app/api_topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,18 @@ func handleWs(ctx context.Context, rep Reporter, renderer render.Renderer, w htt
}

// Individual nodes.
func handleNode(nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) {
func handleNode(topologyID, nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) {
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(topologyID, rpt, rendered, node)})
}
}

Expand Down
25 changes: 2 additions & 23 deletions app/api_topology_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,27 +54,6 @@ func TestAll(t *testing.T) {
}
}

func TestAPITopologyContainers(t *testing.T) {
ts := topologyServer()
{
body := getRawJSON(t, ts, "/api/topology/containers")
var topo app.APITopology
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
if err := decoder.Decode(&topo); err != nil {
t.Fatal(err)
}
want := expected.RenderedContainers.Copy()
for id, node := range want {
node.ControlNode = ""
want[id] = node
}

if have := topo.Nodes.Prune(); !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
}

func TestAPITopologyProcesses(t *testing.T) {
ts := topologyServer()
defer ts.Close()
Expand Down Expand Up @@ -124,13 +103,13 @@ func TestAPITopologyHosts(t *testing.T) {
}
}
{
body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostRenderedID)
body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostID)
var node app.APINode
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
if err := decoder.Decode(&node); err != nil {
t.Fatal(err)
}
equals(t, expected.ServerHostRenderedID, node.Node.ID)
equals(t, expected.ServerHostID, node.Node.ID)
equals(t, "server", node.Node.Label)
equals(t, false, node.Node.Pseudo)
// Let's not unit-test the specific content of the detail tables
Expand Down
2 changes: 1 addition & 1 deletion app/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func TopologyHandler(c Reporter, preRoutes *mux.Router, postRoutes http.Handler)
}

handler := gzipHandler(requestContextDecorator(topologyRegistry.captureRendererWithoutFilters(
c, topologyID, handleNode(nodeID),
c, topologyID, handleNode(topologyID, nodeID),
)))
handler.ServeHTTP(w, r)
})
Expand Down
197 changes: 197 additions & 0 deletions render/detailed/connections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package detailed

import (
"sort"
"strconv"

"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
)

const (
portKey = "port"
portLabel = "Port"
countKey = "count"
countLabel = "Count"
number = "number"
)

// Exported for testing
var (
NormalColumns = []Column{
{ID: portKey, Label: portLabel},
{ID: countKey, Label: countLabel, DefaultSort: true},
}
InternetColumns = []Column{
{ID: "foo", Label: "Remote"},
{ID: portKey, Label: portLabel},
{ID: countKey, Label: countLabel, DefaultSort: true},
}
)

func endpointChildrenOf(n render.RenderableNode) []render.RenderableNode {
result := []render.RenderableNode{}
n.Children.ForEach(func(child render.RenderableNode) {
if _, _, _, ok := render.ParseEndpointID(child.ID); ok {
result = append(result, child)
}
})
return result
}

func endpointChildIDsOf(n render.RenderableNode) report.IDList {
result := report.MakeIDList()
n.Children.ForEach(func(child render.RenderableNode) {
if _, _, _, ok := render.ParseEndpointID(child.ID); ok {
result = append(result, child.ID)
}
})
return result
}

type connectionsRow struct {
remoteNode, localNode *render.RenderableNode
remoteAddr, localAddr string
port string // always the server-side port
}

func buildConnectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary {
nodes := []NodeSummary{}
for row, count := range in {
id, label, linkable := row.remoteNode.ID, row.remoteNode.LabelMajor, true
if row.remoteAddr != "" {
id, label, linkable = row.remoteAddr+":"+row.port, row.remoteAddr, false
}
metadata := []MetadataRow{}
if includeLocal {
metadata = append(metadata,
MetadataRow{
ID: "foo",
Value: row.localAddr,
Datatype: number,
})
}
metadata = append(metadata,
MetadataRow{
ID: portKey,
Value: row.port,
Datatype: number,
},
MetadataRow{
ID: countKey,
Value: strconv.Itoa(count),
Datatype: number,
},
)
nodes = append(nodes, NodeSummary{
ID: id,
Label: label,
Linkable: linkable,
Metadata: metadata,
})
}
sort.Sort(nodeSummariesByID(nodes))
return nodes
}

func isInternetNode(n render.RenderableNode) bool {
return n.ID == render.IncomingInternetID || n.ID == render.OutgoingInternetID
}

func makeIncomingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
localEndpointIDs := endpointChildIDsOf(n)

// For each node which has an edge TO me
counts := map[connectionsRow]int{}
for _, node := range ns {
if !node.Adjacency.Contains(n.ID) {
continue
}
remoteNode := node.Copy()

// Work out what port they are talking to, and count the number of
// connections to that port.
// This is complicated as for internet nodes we break out individual
// address, both when the internet node is remote (an incoming
// connection from the internet) and 'local' (ie you are loading
// details on the internet node)
for _, child := range endpointChildrenOf(node) {
for _, localEndpointID := range child.Adjacency.Intersection(localEndpointIDs) {
_, localAddr, port, ok := render.ParseEndpointID(localEndpointID)
if !ok {
continue
}
key := connectionsRow{
localNode: &n,
remoteNode: &remoteNode,
port: port,
}
if isInternetNode(n) {
key.localAddr = localAddr
}
counts[key] = counts[key] + 1
}
}
}

columnHeaders := NormalColumns
if isInternetNode(n) {
columnHeaders = InternetColumns
}
return NodeSummaryGroup{
ID: "incoming-connections",
TopologyID: topologyID,
Label: "Inbound",
Columns: columnHeaders,
Nodes: buildConnectionRows(counts, isInternetNode(n)),
}
}

func makeOutgoingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
localEndpoints := endpointChildrenOf(n)

// For each node which has an edge FROM me
counts := map[connectionsRow]int{}
for _, node := range ns {
if !n.Adjacency.Contains(node.ID) {
continue
}
remoteNode := node.Copy()
remoteEndpointIDs := endpointChildIDsOf(remoteNode)

for _, localEndpoint := range localEndpoints {
_, localAddr, _, ok := render.ParseEndpointID(localEndpoint.ID)
if !ok {
continue
}

for _, remoteEndpointID := range localEndpoint.Adjacency.Intersection(remoteEndpointIDs) {
_, _, port, ok := render.ParseEndpointID(remoteEndpointID)
if !ok {
continue
}
key := connectionsRow{
localNode: &n,
remoteNode: &remoteNode,
port: port,
}
if isInternetNode(n) {
key.localAddr = localAddr
}
counts[key] = counts[key] + 1
}
}
}

columnHeaders := NormalColumns
if isInternetNode(n) {
columnHeaders = InternetColumns
}
return NodeSummaryGroup{
ID: "outgoing-connections",
TopologyID: topologyID,
Label: "Outbound",
Columns: columnHeaders,
Nodes: buildConnectionRows(counts, isInternetNode(n)),
}
}
39 changes: 24 additions & 15 deletions render/detailed/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,22 @@ type Counter struct {
// MetadataRows implements MetadataRowTemplate
func (c Counter) MetadataRows(n report.Node) []MetadataRow {
if val, ok := n.Counters.Lookup(c.ID); ok {
return []MetadataRow{{ID: c.ID, Value: strconv.Itoa(val), Prime: c.Prime}}
return []MetadataRow{{
ID: c.ID,
Value: strconv.Itoa(val),
Prime: c.Prime,
Datatype: number,
}}
}
return nil
}

// MetadataRow is a row for the metadata table.
type MetadataRow struct {
ID string
Value string
Prime bool
ID string
Value string
Prime bool
Datatype string
}

// Copy returns a value copy of a metadata row.
Expand All @@ -129,20 +135,22 @@ func (*MetadataRow) UnmarshalJSON(b []byte) error {
}

type labelledMetadataRow struct {
ID string `json:"id"`
Label string `json:"label"`
Value string `json:"value"`
Prime bool `json:"prime,omitempty"`
ID string `json:"id"`
Label string `json:"label"`
Value string `json:"value"`
Prime bool `json:"prime,omitempty"`
Datatype string `json:"dataType,omitempty"`
}

// CodecEncodeSelf marshals this MetadataRow. It adds a label before
// rendering.
func (m *MetadataRow) CodecEncodeSelf(encoder *codec.Encoder) {
in := labelledMetadataRow{
ID: m.ID,
Label: Label(m.ID),
Value: m.Value,
Prime: m.Prime,
ID: m.ID,
Label: Label(m.ID),
Value: m.Value,
Prime: m.Prime,
Datatype: m.Datatype,
}
encoder.Encode(in)
}
Expand All @@ -152,9 +160,10 @@ func (m *MetadataRow) CodecDecodeSelf(decoder *codec.Decoder) {
var in labelledMetadataRow
decoder.Decode(&in)
*m = MetadataRow{
ID: in.ID,
Value: in.Value,
Prime: in.Prime,
ID: in.ID,
Value: in.Value,
Prime: in.Prime,
Datatype: in.Datatype,
}
}

Expand Down
Loading

0 comments on commit 2fd23a3

Please sign in to comment.