Skip to content

Commit

Permalink
OriginHosts and OriginNodes become Origins
Browse files Browse the repository at this point in the history
Another baby step towards #123, this change follows from #192 and merges
the two concepts of Origin in a renderable node. We also cut out a layer
of abstraction, and add an OriginTable method to Report, which directly
generates a table of info for the detail pane given any origin node ID.
  • Loading branch information
peterbourgon committed Jun 9, 2015
1 parent d5dd377 commit 27598c5
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 179 deletions.
4 changes: 1 addition & 3 deletions app/api_topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ func handleNode(rep Reporter, t topologyView, w http.ResponseWriter, r *http.Req
http.NotFound(w, r)
return
}
originHostFunc := func(id string) (OriginHost, bool) { return getOriginHost(rpt.Host, id) }
originNodeFunc := func(id string) (OriginNode, bool) { return getOriginNode(t.selector(rpt), id) }
respondWith(w, http.StatusOK, APINode{Node: makeDetailed(node, originHostFunc, originNodeFunc)})
respondWith(w, http.StatusOK, APINode{Node: report.MakeDetailedNode(rpt, node)})
}

// Individual edges.
Expand Down
12 changes: 10 additions & 2 deletions app/api_topology_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ func TestAPITopologyApplications(t *testing.T) {
}
equals(t, 1, len(node.Adjacency))
equals(t, report.MakeIDList("pid:node-b.local:215"), node.Adjacency)
equals(t, report.MakeIDList("hostA"), node.OriginHosts)
equals(t, report.MakeIDList(
report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345"),
report.MakeEndpointNodeID("hostA", "192.168.1.1", "12346"),
report.MakeHostNodeID("hostA"),
), node.Origins,
)
equals(t, "curl", node.LabelMajor)
equals(t, "node-a.local (23128)", node.LabelMinor)
equals(t, "23128", node.Rank)
Expand Down Expand Up @@ -79,7 +84,10 @@ func TestAPITopologyHosts(t *testing.T) {
t.Errorf("missing host:host-b node")
}
equals(t, report.MakeIDList("host:host-a"), node.Adjacency)
equals(t, report.MakeIDList("hostB"), node.OriginHosts)
equals(t, report.MakeIDList(
report.MakeAddressNodeID("hostB", "192.168.1.2"),
report.MakeHostNodeID("hostB"),
), node.Origins)
equals(t, "host-b", node.LabelMajor)
equals(t, "", node.LabelMinor)
equals(t, "host-b", node.Rank)
Expand Down
93 changes: 0 additions & 93 deletions app/detail_pane.go
Original file line number Diff line number Diff line change
@@ -1,94 +1 @@
package main

import (
"fmt"
"reflect"
"strconv"

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

func makeDetailed(
n report.RenderableNode,
originHostLookup func(string) (OriginHost, bool),
originNodeLookup func(string) (OriginNode, bool),
) report.DetailedNode {
tables := []report.Table{{
Title: "Connections",
Numeric: true,
Rows: []report.Row{
// TODO omit these rows if there's no data?
{"TCP connections", strconv.FormatInt(int64(n.Metadata[report.KeyMaxConnCountTCP]), 10), ""},
{"Bytes ingress", strconv.FormatInt(int64(n.Metadata[report.KeyBytesIngress]), 10), ""},
{"Bytes egress", strconv.FormatInt(int64(n.Metadata[report.KeyBytesEgress]), 10), ""},
},
}}

// Note that a RenderableNode may be the result of merge operation(s), and
// so may have multiple origin hosts and nodes.

outer:
for _, id := range n.OriginNodes {
// Origin node IDs in e.g. the process topology are actually network
// n-tuples. (The process topology is actually more like a network
// n-tuple topology.) So we can have multiple IDs mapping to the same
// process. There are several ways to dedupe that, but here we take
// the lazy way and do simple equivalence of the resulting table.
node, ok := originNodeLookup(id)
if !ok {
node = unknownOriginNode(id)
}
for _, table := range tables {
if reflect.DeepEqual(table, node.Table) {
continue outer
}
}
tables = append(tables, node.Table)
}

for _, id := range n.OriginHosts {
host, ok := originHostLookup(id)
if !ok {
host = unknownOriginHost(id)
}
tables = append(tables, report.Table{
Title: "Origin Host",
Numeric: false,
Rows: []report.Row{
{"Hostname", host.Hostname, ""},
{"Load", host.Load, ""},
{"OS", host.OS, ""},
{"ID", id, ""},
},
})
}

return report.DetailedNode{
ID: n.ID,
LabelMajor: n.LabelMajor,
LabelMinor: n.LabelMinor,
Pseudo: n.Pseudo,
Tables: tables,
}
}

func unknownOriginHost(id string) OriginHost {
return OriginHost{
Hostname: fmt.Sprintf("[%s]", id),
OS: "unknown",
Networks: []string{},
Load: "",
}
}

func unknownOriginNode(id string) OriginNode {
return OriginNode{
Table: report.Table{
Title: "Origin Node",
Numeric: false,
Rows: []report.Row{
{"ID", id, ""},
},
},
}
}
54 changes: 54 additions & 0 deletions report/detailed_node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package report

import (
"reflect"
"strconv"
)

// MakeDetailedNode transforms a renderable node to a detailed node. It uses
// aggregate metadata, plus the set of origin node IDs, to produce tables.
func MakeDetailedNode(r Report, n RenderableNode) DetailedNode {
tables := []Table{}
{
rows := []Row{}
if val, ok := n.Metadata[KeyMaxConnCountTCP]; ok {
rows = append(rows, Row{"TCP connections", strconv.FormatInt(int64(val), 10), ""})
}
if val, ok := n.Metadata[KeyBytesIngress]; ok {
rows = append(rows, Row{"Bytes ingress", strconv.FormatInt(int64(val), 10), ""})
}
if val, ok := n.Metadata[KeyBytesEgress]; ok {
rows = append(rows, Row{"Bytes egress", strconv.FormatInt(int64(val), 10), ""})
}
if len(rows) > 0 {
tables = append(tables, Table{"Connections", true, rows})
}
}

// RenderableNode may be the result of merge operation(s), and so may have
// multiple origins. The ultimate goal here is to generate tables to view
// in the UI, so we skip the intermediate representations, but we could
// add them later.
outer:
for _, id := range n.Origins {
table, ok := r.OriginTable(id)
if !ok {
continue
}
// Naïve equivalence-based deduplication.
for _, existing := range tables {
if reflect.DeepEqual(existing, table) {
continue outer
}
}
tables = append(tables, table)
}

return DetailedNode{
ID: n.ID,
LabelMajor: n.LabelMajor,
LabelMinor: n.LabelMinor,
Pseudo: n.Pseudo,
Tables: tables,
}
}
7 changes: 7 additions & 0 deletions report/detailed_node_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package report_test

import "testing"

func TestMakeDetailedNode(t *testing.T) {
t.Skip("TODO")
}
86 changes: 77 additions & 9 deletions report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@ type Report struct {
// an element of a topology. It should contain information that's relevant
// to rendering a node when there are many nodes visible at once.
type RenderableNode struct {
ID string `json:"id"` //
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
Rank string `json:"rank"` // to help the layout engine
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
Adjacency IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain)
OriginHosts IDList `json:"origin_hosts,omitempty"` // Which hosts contributed information to this node
OriginNodes IDList `json:"origin_nodes,omitempty"` // Which origin nodes (depends on topology) contributed
Metadata AggregateMetadata `json:"metadata"` // Numeric sums
ID string `json:"id"` //
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
Rank string `json:"rank"` // to help the layout engine
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
Adjacency IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain)
Origins IDList `json:"origins,omitempty"` // Core node IDs that contributed information
Metadata AggregateMetadata `json:"metadata"` // Numeric sums
}

// DetailedNode is the data type that's yielded to the JavaScript layer when
Expand Down Expand Up @@ -111,3 +110,72 @@ func (r Report) LocalNetworks() []*net.IPNet {
}
return ipNets
}

// OriginTable produces a table (to be consumed directly by the UI) based on
// an origin ID, which is (optimistically) a node ID in one of our topologies.
func (r Report) OriginTable(originID string) (Table, bool) {
for nodeID, nodeMetadata := range r.Endpoint.NodeMetadatas {
if originID == nodeID {
return endpointOriginTable(nodeMetadata)
}
}
for nodeID, nodeMetadata := range r.Address.NodeMetadatas {
if originID == nodeID {
return addressOriginTable(nodeMetadata)
}
}
for nodeID, nodeMetadata := range r.Host.NodeMetadatas {
if originID == nodeID {
return hostOriginTable(nodeMetadata)
}
}
return Table{}, false
}

func endpointOriginTable(nmd NodeMetadata) (Table, bool) {
rows := []Row{}
if val, ok := nmd["endpoint"]; ok {
rows = append(rows, Row{"Endpoint", val, ""})
}
if val, ok := nmd["host_name"]; ok {
rows = append(rows, Row{"Host name", val, ""})
}
return Table{
Title: "Origin Endpoint",
Numeric: false,
Rows: rows,
}, len(rows) > 0
}

func addressOriginTable(nmd NodeMetadata) (Table, bool) {
rows := []Row{}
if val, ok := nmd["address"]; ok {
rows = append(rows, Row{"Address", val, ""})
}
if val, ok := nmd["host_name"]; ok {
rows = append(rows, Row{"Host name", val, ""})
}
return Table{
Title: "Origin Address",
Numeric: false,
Rows: rows,
}, len(rows) > 0
}

func hostOriginTable(nmd NodeMetadata) (Table, bool) {
rows := []Row{}
if val, ok := nmd["host_name"]; ok {
rows = append(rows, Row{"Host name", val, ""})
}
if val, ok := nmd["load"]; ok {
rows = append(rows, Row{"Load", val, ""})
}
if val, ok := nmd["os"]; ok {
rows = append(rows, Row{"Operating system", val, ""})
}
return Table{
Title: "Origin Host",
Numeric: false,
Rows: rows,
}, len(rows) > 0
}
21 changes: 10 additions & 11 deletions report/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,14 @@ func (t Topology) RenderBy(mapFunc MapFunc, pseudoFunc PseudoFunc) map[string]Re
// the existing data, on the assumption that the MapFunc returns the same
// data.
nodes[mapped.ID] = RenderableNode{
ID: mapped.ID,
LabelMajor: mapped.Major,
LabelMinor: mapped.Minor,
Rank: mapped.Rank,
Pseudo: false,
Adjacency: IDList{}, // later
OriginHosts: IDList{}, // later
OriginNodes: IDList{}, // later
Metadata: AggregateMetadata{}, // later
ID: mapped.ID,
LabelMajor: mapped.Major,
LabelMinor: mapped.Minor,
Rank: mapped.Rank,
Pseudo: false,
Adjacency: IDList{}, // later
Origins: IDList{}, // later
Metadata: AggregateMetadata{}, // later
}
address2mapped[addressID] = mapped.ID
}
Expand Down Expand Up @@ -142,8 +141,8 @@ func (t Topology) RenderBy(mapFunc MapFunc, pseudoFunc PseudoFunc) map[string]Re
}

srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID)
srcRenderableNode.OriginHosts = srcRenderableNode.OriginHosts.Add(srcOriginHostID)
srcRenderableNode.OriginNodes = srcRenderableNode.OriginNodes.Add(srcNodeAddress)
srcRenderableNode.Origins = srcRenderableNode.Origins.Add(MakeHostNodeID(srcOriginHostID))
srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcNodeAddress)
edgeID := MakeEdgeID(srcNodeAddress, dstNodeAddress)
if md, ok := t.EdgeMetadatas[edgeID]; ok {
srcRenderableNode.Metadata.Merge(md.Transform())
Expand Down
Loading

0 comments on commit 27598c5

Please sign in to comment.