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 25, 2016
1 parent d57b1e0 commit 297d080
Show file tree
Hide file tree
Showing 19 changed files with 975 additions and 590 deletions.
5 changes: 3 additions & 2 deletions app/api_topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,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
26 changes: 3 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 @@ -112,6 +91,7 @@ func TestAPITopologyHosts(t *testing.T) {
defer ts.Close()
is404(t, ts, "/api/topology/hosts/foobar")
{
render.ResetCache() // TODO figure out why this is needed.
body := getRawJSON(t, ts, "/api/topology/hosts")
var topo app.APITopology
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
Expand All @@ -124,13 +104,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
128 changes: 128 additions & 0 deletions render/detailed/connections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package detailed

import (
"strconv"
"strings"

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

const (
remotePortKey = "remote_port"
localPortKey = "local_port"
countKey = "count"
number = "number"
)

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 render.RenderableNode) {
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][]render.RenderableNode{}
for _, node := range ns {
if !node.Adjacency.Contains(n.ID) {
continue
}

node.Children.ForEach(func(child render.RenderableNode) {
if child.Topology != report.Endpoint {
return
}

for _, endpoint := range child.Adjacency.Intersection(myEndpoints) {
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
}
}
}

return NodeSummaryGroup{
ID: "incoming-connections",
Label: "Inbound",
Columns: []Column{
{ID: localPortKey, Label: "Port"},
{ID: countKey, Label: "Count", DefaultSort: true},
},
Nodes: buildConnectionNodes(remotes, localPortKey),
}
}

func makeOutgoingConnectionsTable(n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
// Get all endpoints which are children of this node
endpoints := []render.RenderableNode{}
n.Children.ForEach(func(child render.RenderableNode) {
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
}
}
}

return NodeSummaryGroup{
ID: "outgoing-connections",
Label: "Outbound",
Columns: []Column{
{ID: remotePortKey, Label: "Port"},
{ID: countKey, Label: "Count", DefaultSort: true},
},
Nodes: buildConnectionNodes(remotes, remotePortKey),
}
}

func buildConnectionNodes(in map[string]int, columnKey string) []NodeSummary {
nodes := []NodeSummary{}
for key, count := range in {
parts := strings.SplitN(key, "|", 2)
nodes = append(nodes, NodeSummary{
ID: key,
Label: parts[0],
Metadata: []MetadataRow{
{
ID: columnKey,
Value: parts[1],
Datatype: number,
},
{
ID: countKey,
Value: strconv.Itoa(count),
Datatype: number,
},
},
})
}
return nodes
}
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
39 changes: 27 additions & 12 deletions render/detailed/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import (
// we want deep information about an individual node.
type Node struct {
NodeSummary
Rank string `json:"rank,omitempty"`
Pseudo bool `json:"pseudo,omitempty"`
Controls []ControlInstance `json:"controls"`
Children []NodeSummaryGroup `json:"children,omitempty"`
Parents []Parent `json:"parents,omitempty"`
Rank string `json:"rank,omitempty"`
Pseudo bool `json:"pseudo,omitempty"`
Controls []ControlInstance `json:"controls"`
Children []NodeSummaryGroup `json:"children,omitempty"`
Parents []Parent `json:"parents,omitempty"`
Connections []NodeSummaryGroup `json:"connections,omitempty"`
}

// ControlInstance contains a control description, and all the info
Expand Down Expand Up @@ -78,17 +79,22 @@ 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 {
summary, _ := MakeNodeSummary(n.Node)
func MakeNode(r report.Report, ns render.RenderableNodes, n render.RenderableNode) Node {
summary, _ := MakeNodeSummary(n)
summary.ID = n.ID
summary.Label = n.LabelMajor

return Node{
NodeSummary: summary,
Rank: n.Rank,
Pseudo: n.Pseudo,
Controls: controls(r, n),
Children: children(n),
Parents: Parents(r, n),
Connections: []NodeSummaryGroup{
makeIncomingConnectionsTable(n, ns),
makeOutgoingConnectionsTable(n, ns),
},
}
}

Expand Down Expand Up @@ -130,17 +136,25 @@ var (
topologyID string
NodeSummaryGroup
}{
{report.Host, NodeSummaryGroup{TopologyID: "hosts", Label: "Hosts", Columns: []Column{host.CPUUsage, host.MemoryUsage}}},
{report.Host, NodeSummaryGroup{TopologyID: "hosts", Label: "Hosts", Columns: []Column{
MakeColumn(host.CPUUsage), MakeColumn(host.MemoryUsage),
}}},
{report.Pod, NodeSummaryGroup{TopologyID: "pods", Label: "Pods"}},
{report.Container, NodeSummaryGroup{TopologyID: "containers", Label: "Containers", Columns: []Column{docker.CPUTotalUsage, docker.MemoryUsage}}},
{report.Process, NodeSummaryGroup{TopologyID: "processes", Label: "Processes", Columns: []Column{process.PID, process.CPUUsage, process.MemoryUsage}}},
{report.ContainerImage, NodeSummaryGroup{TopologyID: "containers-by-image", Label: "Container Images", Columns: []Column{render.ContainersKey}}},
{report.Container, NodeSummaryGroup{TopologyID: "containers", Label: "Containers", Columns: []Column{
MakeColumn(docker.CPUTotalUsage), MakeColumn(docker.MemoryUsage),
}}},
{report.Process, NodeSummaryGroup{TopologyID: "processes", Label: "Processes", Columns: []Column{
MakeColumn(process.PID), MakeColumn(process.CPUUsage), MakeColumn(process.MemoryUsage),
}}},
{report.ContainerImage, NodeSummaryGroup{TopologyID: "containers-by-image", Label: "Container Images", Columns: []Column{
MakeColumn(render.ContainersKey),
}}},
}
)

func children(n render.RenderableNode) []NodeSummaryGroup {
summaries := map[string][]NodeSummary{}
n.Children.ForEach(func(child report.Node) {
n.Children.ForEach(func(child render.RenderableNode) {
if child.ID != n.ID {
if summary, ok := MakeNodeSummary(child); ok {
summaries[child.Topology] = append(summaries[child.Topology], summary)
Expand All @@ -157,5 +171,6 @@ func children(n render.RenderableNode) []NodeSummaryGroup {
nodeSummaryGroups = append(nodeSummaryGroups, group)
}
}

return nodeSummaryGroups
}
Loading

0 comments on commit 297d080

Please sign in to comment.