diff --git a/app/api_topology.go b/app/api_topology.go
index 16f03f192c..639ecfb5f7 100644
--- a/app/api_topology.go
+++ b/app/api_topology.go
@@ -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)})
}
}
diff --git a/app/api_topology_test.go b/app/api_topology_test.go
index 7b4f58f23f..7c3ae88c02 100644
--- a/app/api_topology_test.go
+++ b/app/api_topology_test.go
@@ -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()
@@ -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
diff --git a/app/router.go b/app/router.go
index a414950fdf..7964861f68 100644
--- a/app/router.go
+++ b/app/router.go
@@ -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)
})
diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js
index 44f1b30da6..1d8fec325e 100644
--- a/client/app/scripts/components/node-details.js
+++ b/client/app/scripts/components/node-details.js
@@ -174,6 +174,14 @@ export default class NodeDetails extends React.Component {
}
+ {details.connections && details.connections.map(connections => {
+ return (
+
+
+
+ );
+ })}
+
{details.children && details.children.map(children => {
return (
diff --git a/client/app/scripts/components/node-details/node-details-table.js b/client/app/scripts/components/node-details/node-details-table.js
index a5890f08d2..82303eb585 100644
--- a/client/app/scripts/components/node-details/node-details-table.js
+++ b/client/app/scripts/components/node-details/node-details-table.js
@@ -5,6 +5,10 @@ import ShowMore from '../show-more';
import NodeDetailsTableNodeLink from './node-details-table-node-link';
import NodeDetailsTableNodeMetric from './node-details-table-node-metric';
+function isNumberField(field) {
+ return field.dataType && field.dataType === 'number';
+}
+
export default class NodeDetailsTable extends React.Component {
constructor(props, context) {
@@ -32,14 +36,28 @@ export default class NodeDetailsTable extends React.Component {
}
getDefaultSortBy() {
- // first metric
+ // default sorter specified by columns
+ const defaultSortColumn = _.find(this.props.columns, {defaultSort: true});
+ if (defaultSortColumn) {
+ return defaultSortColumn.id;
+ }
+ // otherwise choose first metric
return _.get(this.props.nodes, [0, 'metrics', 0, 'id']);
}
getMetaDataSorters() {
// returns an array of sorters that will take a node
return _.get(this.props.nodes, [0, 'metadata'], []).map((field, index) => {
- return node => node.metadata[index] ? node.metadata[index].value : null;
+ return node => {
+ const nodeMetadataField = node.metadata[index];
+ if (nodeMetadataField) {
+ if (isNumberField(nodeMetadataField)) {
+ return parseFloat(nodeMetadataField.value);
+ }
+ return nodeMetadataField.value;
+ }
+ return null;
+ };
});
}
@@ -49,6 +67,9 @@ export default class NodeDetailsTable extends React.Component {
if (sortBy !== null) {
const field = _.union(node.metrics, node.metadata).find(f => f.id === sortBy);
if (field) {
+ if (isNumberField(field)) {
+ return parseFloat(field.value);
+ }
return field.value;
}
}
diff --git a/integration/300_internet_edge_test.sh b/integration/300_internet_edge_test.sh
index 9fefa703b9..614123fcdd 100755
--- a/integration/300_internet_edge_test.sh
+++ b/integration/300_internet_edge_test.sh
@@ -22,11 +22,9 @@ do_connections() {
}
do_connections&
-wait_for_containers $HOST1 60 nginx "Inbound"
+wait_for_containers $HOST1 60 nginx "The Internet"
-has_container $HOST1 nginx
-has_container $HOST1 "Inbound"
-has_connection containers $HOST1 "Inbound" nginx
+has_connection_by_id containers $HOST1 "in-theinternet" $(node_id containers $HOST1 nginx)
kill %do_connections
diff --git a/integration/config.sh b/integration/config.sh
index 5e6b693647..251c436f10 100644
--- a/integration/config.sh
+++ b/integration/config.sh
@@ -59,14 +59,13 @@ container_id() {
}
# this checks we have an edge from container 1 to container 2
-has_connection() {
+has_connection_by_id() {
local view="$1"
local host="$2"
- local from="$3"
- local to="$4"
+ local from_id="$3"
+ local to_id="$4"
local timeout="${5:-60}"
- local from_id=$(node_id "${view}" "${host}" "${from}")
- local to_id=$(node_id "${view}" "${host}" "${to}")
+
for i in $(seq $timeout); do
local nodes="$(curl -s http://$host:4040/api/topology/${view}?system=show)"
local edge=$(echo "$nodes" | jq -r ".nodes[\"$from_id\"].adjacency | contains([\"$to_id\"])" 2>/dev/null)
@@ -82,6 +81,18 @@ has_connection() {
assert "curl -s http://$host:4040/api/topology/${view}?system=show | jq -r '.nodes[\"$from_id\"].adjacency | contains([\"$to_id\"])'" true
}
+has_connection() {
+ local view="$1"
+ local host="$2"
+ local from="$3"
+ local to="$4"
+ local timeout="${5:-60}"
+ local from_id="$(node_id "${view}" "${host}" "${from}")"
+ local to_id="$(node_id "${view}" "${host}" "${to}")"
+
+ has_connection_by_id "${view}" "${host}" "${from_id}" "${to_id}" "${timeout}"
+}
+
wait_for() {
local view="$1"
local host="$2"
diff --git a/render/detailed/connections.go b/render/detailed/connections.go
new file mode 100644
index 0000000000..1c48dd6d07
--- /dev/null
+++ b/render/detailed/connections.go
@@ -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},
+ }
+)
+
+type connectionsRow struct {
+ remoteNode, localNode *render.RenderableNode
+ remoteAddr, localAddr string
+ port string // always the server-side port
+}
+
+func incomingConnectionsTable(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: connectionRows(counts, isInternetNode(n)),
+ }
+}
+
+func outgoingConnectionsTable(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: connectionRows(counts, isInternetNode(n)),
+ }
+}
+
+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
+}
+
+func isInternetNode(n render.RenderableNode) bool {
+ return n.ID == render.IncomingInternetID || n.ID == render.OutgoingInternetID
+}
+
+func connectionRows(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
+}
diff --git a/render/detailed/metadata.go b/render/detailed/metadata.go
index a7ea65d5e6..c04d6c2da0 100644
--- a/render/detailed/metadata.go
+++ b/render/detailed/metadata.go
@@ -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.
@@ -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)
}
@@ -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,
}
}
diff --git a/render/detailed/node.go b/render/detailed/node.go
index 38da896eb3..057c55711f 100644
--- a/render/detailed/node.go
+++ b/render/detailed/node.go
@@ -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
@@ -78,10 +79,11 @@ 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(topologyID string, 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,
@@ -89,6 +91,10 @@ func MakeNode(r report.Report, n render.RenderableNode) Node {
Controls: controls(r, n),
Children: children(n),
Parents: Parents(r, n),
+ Connections: []NodeSummaryGroup{
+ incomingConnectionsTable(topologyID, n, ns),
+ outgoingConnectionsTable(topologyID, n, ns),
+ },
}
}
@@ -133,17 +139,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)
@@ -160,5 +174,6 @@ func children(n render.RenderableNode) []NodeSummaryGroup {
nodeSummaryGroups = append(nodeSummaryGroups, group)
}
}
+
return nodeSummaryGroups
}
diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go
index b5fadae75b..ec2cbe02d7 100644
--- a/render/detailed/node_test.go
+++ b/render/detailed/node_test.go
@@ -10,21 +10,29 @@ import (
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/render/detailed"
+ "github.com/weaveworks/scope/render/expected"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/fixture"
)
func TestMakeDetailedHostNode(t *testing.T) {
- renderableNode := render.HostRenderer.Render(fixture.Report)[render.MakeHostID(fixture.ClientHostID)]
- have := detailed.MakeNode(fixture.Report, renderableNode)
+ renderableNodes := render.HostRenderer.Render(fixture.Report)
+ renderableNode := renderableNodes[render.MakeHostID(fixture.ClientHostID)]
+ have := detailed.MakeNode("hosts", fixture.Report, renderableNodes, renderableNode)
containerImageNodeSummary, _ := detailed.MakeNodeSummary(
- render.ContainerImageRenderer.Render(fixture.Report)[render.MakeContainerImageID(fixture.ClientContainerImageName)].Node,
+ render.ContainerImageRenderer.Render(fixture.Report)[expected.ClientContainerImageID],
+ )
+ containerNodeSummary, _ := detailed.MakeNodeSummary(
+ render.ContainerRenderer.Render(fixture.Report)[expected.ClientContainerID],
+ )
+ process1NodeSummary, _ := detailed.MakeNodeSummary(
+ render.ProcessRenderer.Render(fixture.Report)[expected.ClientProcess1ID],
)
- containerNodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Container.Nodes[fixture.ClientContainerNodeID])
- process1NodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID])
process1NodeSummary.Linkable = true
- process2NodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID])
+ process2NodeSummary, _ := detailed.MakeNodeSummary(
+ render.ProcessRenderer.Render(fixture.Report)[expected.ClientProcess2ID],
+ )
process2NodeSummary.Linkable = true
want := detailed.Node{
NodeSummary: detailed.NodeSummary{
@@ -85,22 +93,56 @@ func TestMakeDetailedHostNode(t *testing.T) {
{
Label: "Containers",
TopologyID: "containers",
- Columns: []detailed.Column{docker.CPUTotalUsage, docker.MemoryUsage},
+ Columns: []detailed.Column{detailed.MakeColumn(docker.CPUTotalUsage), detailed.MakeColumn(docker.MemoryUsage)},
Nodes: []detailed.NodeSummary{containerNodeSummary},
},
{
Label: "Processes",
TopologyID: "processes",
- Columns: []detailed.Column{process.PID, process.CPUUsage, process.MemoryUsage},
+ Columns: []detailed.Column{detailed.MakeColumn(process.PID), detailed.MakeColumn(process.CPUUsage), detailed.MakeColumn(process.MemoryUsage)},
Nodes: []detailed.NodeSummary{process1NodeSummary, process2NodeSummary},
},
{
Label: "Container Images",
TopologyID: "containers-by-image",
- Columns: []detailed.Column{render.ContainersKey},
+ Columns: []detailed.Column{detailed.MakeColumn(render.ContainersKey)},
Nodes: []detailed.NodeSummary{containerImageNodeSummary},
},
},
+ Connections: []detailed.NodeSummaryGroup{
+ {
+ ID: "incoming-connections",
+ TopologyID: "hosts",
+ Label: "Inbound",
+ Columns: detailed.NormalColumns,
+ Nodes: []detailed.NodeSummary{},
+ },
+ {
+ ID: "outgoing-connections",
+ TopologyID: "hosts",
+ Label: "Outbound",
+ Columns: detailed.NormalColumns,
+ Nodes: []detailed.NodeSummary{
+ {
+ ID: "host:server.hostname.com",
+ Label: "server",
+ Linkable: true,
+ Metadata: []detailed.MetadataRow{
+ {
+ ID: "port",
+ Value: "80",
+ Datatype: "number",
+ },
+ {
+ ID: "count",
+ Value: "2",
+ Datatype: "number",
+ },
+ },
+ },
+ },
+ },
+ },
}
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))
@@ -109,11 +151,12 @@ func TestMakeDetailedHostNode(t *testing.T) {
func TestMakeDetailedContainerNode(t *testing.T) {
id := render.MakeContainerID(fixture.ServerContainerID)
- renderableNode, ok := render.ContainerRenderer.Render(fixture.Report)[id]
+ renderableNodes := render.ContainerRenderer.Render(fixture.Report)
+ renderableNode, ok := renderableNodes[id]
if !ok {
t.Fatalf("Node not found: %s", id)
}
- have := detailed.MakeNode(fixture.Report, renderableNode)
+ have := detailed.MakeNode("containers", fixture.Report, renderableNodes, renderableNode)
want := detailed.Node{
NodeSummary: detailed.NodeSummary{
ID: id,
@@ -145,14 +188,13 @@ func TestMakeDetailedContainerNode(t *testing.T) {
},
},
},
- Rank: "imageid456",
Pseudo: false,
Controls: []detailed.ControlInstance{},
Children: []detailed.NodeSummaryGroup{
{
Label: "Processes",
TopologyID: "processes",
- Columns: []detailed.Column{process.PID, process.CPUUsage, process.MemoryUsage},
+ Columns: []detailed.Column{detailed.MakeColumn(process.PID), detailed.MakeColumn(process.CPUUsage), detailed.MakeColumn(process.MemoryUsage)},
Nodes: []detailed.NodeSummary{
{
ID: fmt.Sprintf("process:%s:%s", "server.hostname.com", fixture.ServerPID),
@@ -178,6 +220,57 @@ func TestMakeDetailedContainerNode(t *testing.T) {
TopologyID: "hosts",
},
},
+ Connections: []detailed.NodeSummaryGroup{
+ {
+ ID: "incoming-connections",
+ TopologyID: "containers",
+ Label: "Inbound",
+ Columns: detailed.NormalColumns,
+ Nodes: []detailed.NodeSummary{
+ {
+ ID: "container:a1b2c3d4e5",
+ Label: "client",
+ Linkable: true,
+ Metadata: []detailed.MetadataRow{
+ {
+ ID: "port",
+ Value: "80",
+ Datatype: "number",
+ },
+ {
+ ID: "count",
+ Value: "2",
+ Datatype: "number",
+ },
+ },
+ },
+ {
+ ID: "in-theinternet",
+ Label: "The Internet",
+ Linkable: true,
+ Metadata: []detailed.MetadataRow{
+ {
+ ID: "port",
+ Value: "80",
+ Datatype: "number",
+ },
+ {
+ ID: "count",
+ Value: "1",
+ Datatype: "number",
+ },
+ },
+ },
+ },
+ },
+ {
+ ID: "outgoing-connections",
+ TopologyID: "containers",
+ Label: "Outbound",
+ Columns: detailed.NormalColumns,
+ Nodes: []detailed.NodeSummary{},
+ },
+ },
}
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))
diff --git a/render/detailed/summary.go b/render/detailed/summary.go
index 8cc65c0549..473db69c68 100644
--- a/render/detailed/summary.go
+++ b/render/detailed/summary.go
@@ -3,8 +3,6 @@ package detailed
import (
"fmt"
- "github.com/ugorji/go/codec"
-
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/kubernetes"
@@ -15,6 +13,7 @@ import (
// NodeSummaryGroup is a topology-typed group of children for a Node.
type NodeSummaryGroup struct {
+ ID string `json:"id"`
Label string `json:"label"`
Nodes []NodeSummary `json:"nodes"`
TopologyID string `json:"topologyId"`
@@ -36,29 +35,18 @@ func (g NodeSummaryGroup) Copy() NodeSummaryGroup {
// Column provides special json serialization for column ids, so they include
// their label for the frontend.
-type Column string
-
-// CodecEncodeSelf implements codec.Selfer
-func (c *Column) CodecEncodeSelf(encoder *codec.Encoder) {
- in := map[string]string{"id": string(*c), "label": Label(string(*c))}
- encoder.Encode(in)
-}
-
-// CodecDecodeSelf implements codec.Selfer
-func (c *Column) CodecDecodeSelf(decoder *codec.Decoder) {
- m := map[string]string{}
- decoder.Decode(&m)
- *c = Column(m["id"])
+type Column struct {
+ ID string `json:"id"`
+ Label string `json:"label"`
+ DefaultSort bool `json:"defaultSort"`
}
-// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
-func (Column) MarshalJSON() ([]byte, error) {
- panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead")
-}
-
-// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead
-func (*Column) UnmarshalJSON(b []byte) error {
- panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead")
+// MakeColumn makes a Column by looking up the label by id.
+func MakeColumn(id string) Column {
+ return Column{
+ ID: id,
+ Label: Label(id),
+ }
}
// NodeSummary is summary information about a child for a Node.
@@ -72,7 +60,7 @@ type NodeSummary struct {
}
// MakeNodeSummary summarizes a node, if possible.
-func MakeNodeSummary(n report.Node) (NodeSummary, bool) {
+func MakeNodeSummary(n render.RenderableNode) (NodeSummary, bool) {
renderers := map[string]func(report.Node) NodeSummary{
report.Process: processNodeSummary,
report.Container: containerNodeSummary,
@@ -81,7 +69,7 @@ func MakeNodeSummary(n report.Node) (NodeSummary, bool) {
report.Host: hostNodeSummary,
}
if renderer, ok := renderers[n.Topology]; ok {
- return renderer(n), true
+ return renderer(n.Node), true
}
return NodeSummary{}, false
}
diff --git a/render/expected/expected.go b/render/expected/expected.go
index ca20539250..76b763764c 100644
--- a/render/expected/expected.go
+++ b/render/expected/expected.go
@@ -16,16 +16,111 @@ var (
hexagon = "hexagon"
cloud = "cloud"
- uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostName)
- unknownPseudoNode1ID = render.MakePseudoNodeID("10.10.10.10", fixture.ServerIP, "80")
- unknownPseudoNode2ID = render.MakePseudoNodeID("10.10.10.11", fixture.ServerIP, "80")
- unknownPseudoNode1 = func(adjacent string) render.RenderableNode {
+ Client54001EndpointID = render.MakeEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54001)
+ Client54002EndpointID = render.MakeEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54002)
+ ServerEndpointID = render.MakeEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.ServerPort)
+ UnknownClient1EndpointID = render.MakeEndpointID("", fixture.UnknownClient1IP, fixture.UnknownClient1Port)
+ UnknownClient2EndpointID = render.MakeEndpointID("", fixture.UnknownClient2IP, fixture.UnknownClient2Port)
+ UnknownClient3EndpointID = render.MakeEndpointID("", fixture.UnknownClient3IP, fixture.UnknownClient3Port)
+ RandomClientEndpointID = render.MakeEndpointID("", fixture.RandomClientIP, fixture.RandomClientPort)
+ NonContainerEndpointID = render.MakeEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.NonContainerClientPort)
+ GoogleEndpointID = render.MakeEndpointID("", fixture.GoogleIP, fixture.GooglePort)
+
+ RenderedEndpoints = (render.RenderableNodes{
+ Client54001EndpointID: {
+ ID: Client54001EndpointID,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerEndpointID),
+ EdgeMetadata: report.EdgeMetadata{
+ EgressPacketCount: newu64(10),
+ EgressByteCount: newu64(100),
+ },
+ },
+ Client54002EndpointID: {
+ ID: Client54002EndpointID,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerEndpointID),
+ EdgeMetadata: report.EdgeMetadata{
+ EgressPacketCount: newu64(20),
+ EgressByteCount: newu64(200),
+ },
+ },
+ ServerEndpointID: {
+ ID: ServerEndpointID,
+ Shape: circle,
+ Node: report.MakeNode(),
+ EdgeMetadata: report.EdgeMetadata{
+ IngressPacketCount: newu64(210),
+ IngressByteCount: newu64(2100),
+ },
+ },
+ UnknownClient1EndpointID: {
+ ID: UnknownClient1EndpointID,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerEndpointID),
+ EdgeMetadata: report.EdgeMetadata{
+ EgressPacketCount: newu64(30),
+ EgressByteCount: newu64(300),
+ },
+ },
+ UnknownClient2EndpointID: {
+ ID: UnknownClient2EndpointID,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerEndpointID),
+ EdgeMetadata: report.EdgeMetadata{
+ EgressPacketCount: newu64(40),
+ EgressByteCount: newu64(400),
+ },
+ },
+ UnknownClient3EndpointID: {
+ ID: UnknownClient3EndpointID,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerEndpointID),
+ EdgeMetadata: report.EdgeMetadata{
+ EgressPacketCount: newu64(50),
+ EgressByteCount: newu64(500),
+ },
+ },
+ RandomClientEndpointID: {
+ ID: RandomClientEndpointID,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerEndpointID),
+ EdgeMetadata: report.EdgeMetadata{
+ EgressPacketCount: newu64(60),
+ EgressByteCount: newu64(600),
+ },
+ },
+ NonContainerEndpointID: {
+ ID: NonContainerEndpointID,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(GoogleEndpointID),
+ EdgeMetadata: report.EdgeMetadata{},
+ },
+ GoogleEndpointID: {
+ ID: GoogleEndpointID,
+ Shape: circle,
+ Node: report.MakeNode(),
+ },
+ }).Prune()
+
+ ClientProcess1ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID)
+ ClientProcess2ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID)
+ ServerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID)
+ nonContainerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.NonContainerPID)
+ unknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP, fixture.ServerPort)
+ unknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP, fixture.ServerPort)
+
+ unknownPseudoNode1 = func(adjacent string) render.RenderableNode {
return render.RenderableNode{
ID: unknownPseudoNode1ID,
- LabelMajor: "10.10.10.10",
+ LabelMajor: fixture.UnknownClient1IP,
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(adjacent),
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[UnknownClient1EndpointID],
+ RenderedEndpoints[UnknownClient2EndpointID],
+ ),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(70),
EgressByteCount: newu64(700),
@@ -35,10 +130,13 @@ var (
unknownPseudoNode2 = func(adjacent string) render.RenderableNode {
return render.RenderableNode{
ID: unknownPseudoNode2ID,
- LabelMajor: "10.10.10.11",
+ LabelMajor: fixture.UnknownClient3IP,
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(adjacent),
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[UnknownClient3EndpointID],
+ ),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(50),
EgressByteCount: newu64(500),
@@ -49,10 +147,13 @@ var (
return render.RenderableNode{
ID: render.IncomingInternetID,
LabelMajor: render.InboundMajor,
- LabelMinor: render.RequestsMinor,
+ LabelMinor: render.InboundMinor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode().WithAdjacent(adjacent),
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[RandomClientEndpointID],
+ ),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(60),
EgressByteCount: newu64(600),
@@ -62,16 +163,15 @@ var (
theOutgoingInternetNode = render.RenderableNode{
ID: render.OutgoingInternetID,
LabelMajor: render.OutboundMajor,
- LabelMinor: render.RequestsMinor,
+ LabelMinor: render.OutboundMinor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{},
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[GoogleEndpointID],
+ ),
}
- ClientProcess1ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID)
- ClientProcess2ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID)
- ServerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID)
- nonContainerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.NonContainerPID)
RenderedProcesses = (render.RenderableNodes{
ClientProcess1ID: {
@@ -79,9 +179,11 @@ var (
LabelMajor: fixture.Client1Name,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client1PID),
Rank: fixture.Client1Name,
- Pseudo: false,
Shape: square,
Node: report.MakeNode().WithAdjacent(ServerProcessID),
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[Client54001EndpointID],
+ ),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(10),
EgressByteCount: newu64(100),
@@ -92,9 +194,11 @@ var (
LabelMajor: fixture.Client2Name,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client2PID),
Rank: fixture.Client2Name,
- Pseudo: false,
Shape: square,
Node: report.MakeNode().WithAdjacent(ServerProcessID),
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[Client54002EndpointID],
+ ),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(20),
EgressByteCount: newu64(200),
@@ -105,22 +209,26 @@ var (
LabelMajor: fixture.ServerName,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.ServerPID),
Rank: fixture.ServerName,
- Pseudo: false,
Shape: square,
Node: report.MakeNode(),
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[ServerEndpointID],
+ ),
EdgeMetadata: report.EdgeMetadata{
IngressPacketCount: newu64(210),
IngressByteCount: newu64(2100),
},
},
nonContainerProcessID: {
- ID: nonContainerProcessID,
- LabelMajor: fixture.NonContainerName,
- LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID),
- Rank: fixture.NonContainerName,
- Pseudo: false,
- Shape: square,
- Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
+ ID: nonContainerProcessID,
+ LabelMajor: fixture.NonContainerName,
+ LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID),
+ Rank: fixture.NonContainerName,
+ Shape: square,
+ Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[NonContainerEndpointID],
+ ),
EdgeMetadata: report.EdgeMetadata{},
},
unknownPseudoNode1ID: unknownPseudoNode1(ServerProcessID),
@@ -129,22 +237,19 @@ var (
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
- ServerProcessRenderedID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID)
- ClientProcess1RenderedID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID)
- ClientProcess2RenderedID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID)
-
RenderedProcessNames = (render.RenderableNodes{
fixture.Client1Name: {
ID: fixture.Client1Name,
LabelMajor: fixture.Client1Name,
LabelMinor: "2 processes",
Rank: fixture.Client1Name,
- Pseudo: false,
Shape: square,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
- fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[Client54001EndpointID],
+ RenderedEndpoints[Client54002EndpointID],
+ RenderedProcesses[ClientProcess1ID],
+ RenderedProcesses[ClientProcess2ID],
),
Node: report.MakeNode().WithAdjacent(fixture.ServerName),
EdgeMetadata: report.EdgeMetadata{
@@ -157,11 +262,11 @@ var (
LabelMajor: fixture.ServerName,
LabelMinor: "1 process",
Rank: fixture.ServerName,
- Pseudo: false,
Shape: square,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[ServerEndpointID],
+ RenderedProcesses[ServerProcessID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -174,11 +279,11 @@ var (
LabelMajor: fixture.NonContainerName,
LabelMinor: "1 process",
Rank: fixture.NonContainerName,
- Pseudo: false,
Shape: square,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[NonContainerEndpointID],
+ RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
@@ -189,37 +294,37 @@ var (
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
- ServerContainerRenderedID = render.MakeContainerID(fixture.ServerContainerID)
- ClientContainerRenderedID = render.MakeContainerID(fixture.ClientContainerID)
+ ClientContainerID = render.MakeContainerID(fixture.ClientContainerID)
+ ServerContainerID = render.MakeContainerID(fixture.ServerContainerID)
+ uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostID)
RenderedContainers = (render.RenderableNodes{
- ClientContainerRenderedID: {
- ID: ClientContainerRenderedID,
+ ClientContainerID: {
+ ID: ClientContainerID,
LabelMajor: "client",
LabelMinor: fixture.ClientHostName,
- Rank: fixture.ClientContainerImageName,
- Pseudo: false,
Shape: hexagon,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
- fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[Client54001EndpointID],
+ RenderedEndpoints[Client54002EndpointID],
+ RenderedProcesses[ClientProcess1ID],
+ RenderedProcesses[ClientProcess2ID],
),
- Node: report.MakeNode().WithAdjacent(ServerContainerRenderedID),
+ Node: report.MakeNode().WithAdjacent(ServerContainerID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(30),
EgressByteCount: newu64(300),
},
ControlNode: fixture.ClientContainerNodeID,
},
- ServerContainerRenderedID: {
- ID: ServerContainerRenderedID,
+ ServerContainerID: {
+ ID: ServerContainerID,
LabelMajor: "server",
LabelMinor: fixture.ServerHostName,
- Rank: fixture.ServerContainerImageName,
- Pseudo: false,
Shape: hexagon,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[ServerEndpointID],
+ RenderedProcesses[ServerProcessID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -232,54 +337,57 @@ var (
ID: uncontainedServerID,
LabelMajor: render.UncontainedMajor,
LabelMinor: fixture.ServerHostName,
- Rank: "",
- Pseudo: true,
Shape: square,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
+ Pseudo: true,
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[NonContainerEndpointID],
+ RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
- render.IncomingInternetID: theIncomingInternetNode(ServerContainerRenderedID),
+ // unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerID),
+ // unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerID),
+ render.IncomingInternetID: theIncomingInternetNode(ServerContainerID),
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
- ClientContainerImageRenderedName = render.MakeContainerImageID(fixture.ClientContainerImageName)
- ServerContainerImageRenderedName = render.MakeContainerImageID(fixture.ServerContainerImageName)
+ ClientContainerImageID = render.MakeContainerImageID(fixture.ClientContainerImageName)
+ ServerContainerImageID = render.MakeContainerImageID(fixture.ServerContainerImageName)
RenderedContainerImages = (render.RenderableNodes{
- ClientContainerImageRenderedName: {
- ID: ClientContainerImageRenderedName,
+ ClientContainerImageID: {
+ ID: ClientContainerImageID,
LabelMajor: fixture.ClientContainerImageName,
LabelMinor: "1 container",
Rank: fixture.ClientContainerImageName,
- Pseudo: false,
Shape: hexagon,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
- fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
- fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[Client54001EndpointID],
+ RenderedEndpoints[Client54002EndpointID],
+ RenderedProcesses[ClientProcess1ID],
+ RenderedProcesses[ClientProcess2ID],
+ RenderedContainers[ClientContainerID],
),
- Node: report.MakeNode().WithAdjacent(ServerContainerImageRenderedName),
+ Node: report.MakeNode().WithAdjacent(ServerContainerImageID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(30),
EgressByteCount: newu64(300),
},
},
- ServerContainerImageRenderedName: {
- ID: ServerContainerImageRenderedName,
+ ServerContainerImageID: {
+ ID: ServerContainerImageID,
LabelMajor: fixture.ServerContainerImageName,
LabelMinor: "1 container",
Rank: fixture.ServerContainerImageName,
- Pseudo: false,
Shape: hexagon,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
- fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[ServerEndpointID],
+ RenderedProcesses[ServerProcessID],
+ RenderedContainers[ServerContainerID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -291,71 +399,131 @@ var (
ID: uncontainedServerID,
LabelMajor: render.UncontainedMajor,
LabelMinor: fixture.ServerHostName,
- Rank: "",
- Pseudo: true,
Shape: square,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
+ Pseudo: true,
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[NonContainerEndpointID],
+ RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
- render.IncomingInternetID: theIncomingInternetNode(ServerContainerImageRenderedName),
+ // unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerImageID),
+ // unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerImageID),
+ render.IncomingInternetID: theIncomingInternetNode(ServerContainerImageID),
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
- ServerHostRenderedID = render.MakeHostID(fixture.ServerHostID)
- ClientHostRenderedID = render.MakeHostID(fixture.ClientHostID)
- pseudoHostID1 = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP)
- pseudoHostID2 = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP)
+ ClientAddressID = render.MakeAddressID(fixture.ClientHostID, fixture.ClientIP)
+ ServerAddressID = render.MakeAddressID(fixture.ServerHostID, fixture.ServerIP)
+ unknownPseudoAddress1ID = render.MakePseudoNodeID("10.10.10.10", fixture.ServerIP)
+ unknownPseudoAddress2ID = render.MakePseudoNodeID("10.10.10.11", fixture.ServerIP)
+
+ RenderedAddresses = (render.RenderableNodes{
+ ClientAddressID: {
+ ID: ClientAddressID,
+ LabelMajor: fixture.ClientIP,
+ LabelMinor: fixture.ClientHostID,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerAddressID),
+ },
+ ServerAddressID: {
+ ID: ServerAddressID,
+ LabelMajor: fixture.ServerIP,
+ LabelMinor: fixture.ServerHostID,
+ Shape: circle,
+ Node: report.MakeNode(),
+ },
+ unknownPseudoAddress1ID: {
+ ID: unknownPseudoAddress1ID,
+ LabelMajor: "10.10.10.10",
+ Pseudo: true,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerAddressID),
+ },
+ unknownPseudoAddress2ID: {
+ ID: unknownPseudoAddress2ID,
+ LabelMajor: "10.10.10.11",
+ Pseudo: true,
+ Shape: circle,
+ Node: report.MakeNode().WithAdjacent(ServerAddressID),
+ },
+ render.IncomingInternetID: {
+ ID: render.IncomingInternetID,
+ LabelMajor: render.InboundMajor,
+ LabelMinor: render.InboundMinor,
+ Pseudo: true,
+ Shape: cloud,
+ Node: report.MakeNode().WithAdjacent(ServerAddressID),
+ EdgeMetadata: report.EdgeMetadata{},
+ },
+ }).Prune()
+
+ ServerHostID = render.MakeHostID(fixture.ServerHostID)
+ ClientHostID = render.MakeHostID(fixture.ClientHostID)
+ pseudoHostID1 = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP)
+ pseudoHostID2 = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP)
RenderedHosts = (render.RenderableNodes{
- ServerHostRenderedID: {
- ID: ServerHostRenderedID,
- LabelMajor: "server", // before first .
+ ClientHostID: {
+ ID: ClientHostID,
+ LabelMajor: "client", // before first .
LabelMinor: "hostname.com", // after first .
Rank: "hostname.com",
- Pseudo: false,
Shape: circle,
- Children: report.MakeNodeSet(
- fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
- fixture.Report.Container.Nodes[fixture.ServerProcessNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[Client54001EndpointID],
+ RenderedEndpoints[Client54002EndpointID],
+ RenderedProcesses[ClientProcess1ID],
+ RenderedProcesses[ClientProcess2ID],
+ RenderedContainers[ClientContainerID],
+ RenderedContainerImages[ClientContainerImageID],
+ RenderedAddresses[ClientAddressID],
),
- Node: report.MakeNode(),
+ Node: report.MakeNode().WithAdjacent(ServerHostID),
EdgeMetadata: report.EdgeMetadata{
- IngressPacketCount: newu64(210),
- IngressByteCount: newu64(2100),
+ EgressPacketCount: newu64(30),
+ EgressByteCount: newu64(300),
},
},
- ClientHostRenderedID: {
- ID: ClientHostRenderedID,
- LabelMajor: "client", // before first .
+ ServerHostID: {
+ ID: ServerHostID,
+ LabelMajor: "server", // before first .
LabelMinor: "hostname.com", // after first .
Rank: "hostname.com",
- Pseudo: false,
Shape: circle,
- Children: report.MakeNodeSet(
- fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
- fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
- fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[ServerEndpointID],
+
+ RenderedProcesses[ServerProcessID],
+ RenderedContainers[ServerContainerID],
+ RenderedContainerImages[ServerContainerImageID],
+
+ RenderedAddresses[ServerAddressID],
+
+ // See #1102
+ // RemappedEndpoints[NonContainerEndpointID],
+ // RenderedEndpoints[NonContainerPseudoEndpointID],
+ // RenderedProcesses[nonContainerProcessID],
),
- Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
+ Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
- EgressPacketCount: newu64(30),
- EgressByteCount: newu64(300),
+ IngressPacketCount: newu64(210),
+ IngressByteCount: newu64(2100),
},
},
+
pseudoHostID1: {
ID: pseudoHostID1,
LabelMajor: fixture.UnknownClient1IP,
Pseudo: true,
Shape: circle,
- Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
+ Node: report.MakeNode().WithAdjacent(ServerHostID),
EdgeMetadata: report.EdgeMetadata{},
- Children: report.MakeNodeSet(
- fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
- fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
+ Children: render.MakeRenderableNodeSet(
+ // RenderedEndpoints[unknownPseudoNode2ID],
+ // RenderedAddresses[unknownPseudoAddress1ID],
),
},
pseudoHostID2: {
@@ -363,17 +531,24 @@ var (
LabelMajor: fixture.UnknownClient3IP,
Pseudo: true,
Shape: circle,
- Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
+ Node: report.MakeNode().WithAdjacent(ServerHostID),
EdgeMetadata: report.EdgeMetadata{},
+ Children: render.MakeRenderableNodeSet(
+ // RenderedEndpoints[unknownPseudoNode2ID],
+ // RenderedAddresses[unknownPseudoAddress2ID],
+ ),
},
render.IncomingInternetID: {
ID: render.IncomingInternetID,
LabelMajor: render.InboundMajor,
- LabelMinor: render.RequestsMinor,
+ LabelMinor: render.InboundMinor,
Pseudo: true,
Shape: cloud,
- Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
+ Node: report.MakeNode().WithAdjacent(ServerHostID),
EdgeMetadata: report.EdgeMetadata{},
+ Children: render.MakeRenderableNodeSet(
+ // RenderedEndpoints[render.TheInternetID],
+ ),
},
}).Prune()
@@ -386,14 +561,13 @@ var (
LabelMajor: "pong-a",
LabelMinor: "1 container",
Rank: "ping/pong-a",
- Pseudo: false,
Shape: heptagon,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
- fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
- fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
- fixture.Report.ContainerImage.Nodes[fixture.ClientContainerImageNodeID],
- fixture.Report.Pod.Nodes[fixture.ClientPodNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[Client54001EndpointID],
+ RenderedEndpoints[Client54002EndpointID],
+ RenderedProcesses[ClientProcess1ID],
+ RenderedProcesses[ClientProcess2ID],
+ RenderedContainers[ClientContainerID],
),
Node: report.MakeNode().WithAdjacent(ServerPodRenderedID),
EdgeMetadata: report.EdgeMetadata{
@@ -406,13 +580,11 @@ var (
LabelMajor: "pong-b",
LabelMinor: "1 container",
Rank: "ping/pong-b",
- Pseudo: false,
Shape: heptagon,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
- fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
- fixture.Report.ContainerImage.Nodes[fixture.ServerContainerImageNodeID],
- fixture.Report.Pod.Nodes[fixture.ServerPodNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[ServerEndpointID],
+ RenderedProcesses[ServerProcessID],
+ RenderedContainers[ServerContainerID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -424,16 +596,18 @@ var (
ID: uncontainedServerID,
LabelMajor: render.UncontainedMajor,
LabelMinor: fixture.ServerHostName,
- Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[NonContainerEndpointID],
+ RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
+ // unknownPseudoNode1ID: unknownPseudoNode1(ServerPodRenderedID),
+ // unknownPseudoNode2ID: unknownPseudoNode2(ServerPodRenderedID),
render.IncomingInternetID: theIncomingInternetNode(ServerPodRenderedID),
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
@@ -446,19 +620,19 @@ var (
LabelMajor: "pongservice",
LabelMinor: "2 pods",
Rank: fixture.ServiceID,
- Pseudo: false,
Shape: heptagon,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
- fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
- fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
- fixture.Report.ContainerImage.Nodes[fixture.ClientContainerImageNodeID],
- fixture.Report.Pod.Nodes[fixture.ClientPodNodeID],
- fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
- fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
- fixture.Report.ContainerImage.Nodes[fixture.ServerContainerImageNodeID],
- fixture.Report.Pod.Nodes[fixture.ServerPodNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[Client54001EndpointID],
+ RenderedEndpoints[Client54002EndpointID],
+ RenderedEndpoints[ServerEndpointID],
+ RenderedProcesses[ClientProcess1ID],
+ RenderedProcesses[ClientProcess2ID],
+ RenderedProcesses[ServerProcessID],
+ RenderedContainers[ClientContainerID],
+ RenderedContainers[ServerContainerID],
+ RenderedPods[ClientPodRenderedID],
+ RenderedPods[ServerPodRenderedID],
),
Node: report.MakeNode().WithAdjacent(ServiceRenderedID),
EdgeMetadata: report.EdgeMetadata{
@@ -472,16 +646,18 @@ var (
ID: uncontainedServerID,
LabelMajor: render.UncontainedMajor,
LabelMinor: fixture.ServerHostName,
- Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
- Children: report.MakeNodeSet(
- fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
+ Children: render.MakeRenderableNodeSet(
+ RenderedEndpoints[NonContainerEndpointID],
+ RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
+ // unknownPseudoNode1ID: unknownPseudoNode1(ServiceRenderedID),
+ // unknownPseudoNode2ID: unknownPseudoNode2(ServiceRenderedID),
render.IncomingInternetID: theIncomingInternetNode(ServiceRenderedID),
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
diff --git a/render/id.go b/render/id.go
index 952c3d3694..03a0f693c8 100644
--- a/render/id.go
+++ b/render/id.go
@@ -4,6 +4,16 @@ import (
"strings"
)
+// ParseEndpointID parses endpoint IDs
+func ParseEndpointID(id string) (host, ip, port string, ok bool) {
+ parts := strings.SplitN(id, ":", 4)
+ if len(parts) != 4 || parts[0] != "endpoint" {
+ return
+ }
+ host, ip, port, ok = parts[1], parts[2], parts[3], true
+ return
+}
+
// makeID is the generic ID maker
func makeID(prefix string, parts ...string) string {
return strings.Join(append([]string{prefix}, parts...), ":")
diff --git a/render/mapping.go b/render/mapping.go
index 2c78b33aa4..0ebcaf6fb1 100644
--- a/render/mapping.go
+++ b/render/mapping.go
@@ -23,9 +23,10 @@ const (
TheInternetID = "theinternet"
IncomingInternetID = "in-" + TheInternetID
OutgoingInternetID = "out-" + TheInternetID
- InboundMajor = "Inbound"
- OutboundMajor = "Outbound"
- RequestsMinor = "Requests"
+ InboundMajor = "The Internet"
+ OutboundMajor = "The Internet"
+ InboundMinor = "Inbound connections"
+ OutboundMinor = "Outbound connections"
ContainersKey = "containers"
ipsKey = "ips"
@@ -50,19 +51,20 @@ func theInternetNode(m RenderableNode) RenderableNode {
if len(m.Adjacency) > 0 {
node.ID = IncomingInternetID
node.LabelMajor = InboundMajor
- node.LabelMinor = RequestsMinor
+ node.LabelMinor = InboundMinor
} else {
node.ID = OutgoingInternetID
node.LabelMajor = OutboundMajor
- node.LabelMinor = RequestsMinor
+ node.LabelMinor = OutboundMinor
}
return node
}
-// MapEndpointIdentity maps an endpoint topology node to a single endpoint
-// renderable node. As it is only ever run on endpoint topology nodes, we
-// expect that certain keys are present.
-func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNodes {
+// MapEndpointIdentity remaps endpoints to have an id format consistent
+// with render/id.go; no pseudo nodes are introduced in this step, so
+// that pseudo nodes introduces later are guaranteed to have endpoints
+// as children. This is needed to construct the connection details tables.
+func MapEndpointIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
addr, ok := m.Latest.Lookup(endpoint.Addr)
if !ok {
return RenderableNodes{}
@@ -79,46 +81,8 @@ func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNode
return RenderableNodes{}
}
- // Nodes without a hostid are treated as psuedo nodes
- if _, ok = m.Latest.Lookup(report.HostNodeID); !ok {
- // If the dstNodeAddr is not in a network local to this report, we emit an
- // internet node
- if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) {
- return RenderableNodes{TheInternetID: theInternetNode(m)}
- }
-
- // We are a 'client' pseudo node if the port is in the ephemeral port range.
- // Linux uses 32768 to 61000, IANA suggests 49152 to 65535.
- if p, err := strconv.Atoi(port); err == nil && len(m.Adjacency) > 0 && p >= 32768 && p < 65535 {
- // We only exist if there is something in our adjacency
- // Generate a single pseudo node for every (client ip, server ip, server port)
- dstNodeID := m.Adjacency[0]
- serverIP, serverPort := trySplitAddr(dstNodeID)
- outputID := MakePseudoNodeID(addr, serverIP, serverPort)
- return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, m)}
- }
-
- // Otherwise (the server node is missing), generate a pseudo node for every (server ip, server port)
- outputID := MakePseudoNodeID(addr, port)
- if port != "" {
- return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr+":"+port, m)}
- }
- return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, m)}
- }
-
- var (
- id = MakeEndpointID(report.ExtractHostID(m.Node), addr, port)
- major = fmt.Sprintf("%s:%s", addr, port)
- minor = report.ExtractHostID(m.Node)
- rank = major
- )
-
- pid, pidOK := m.Latest.Lookup(process.PID)
- if pidOK {
- minor = fmt.Sprintf("%s (%s)", minor, pid)
- }
-
- return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)}
+ id := MakeEndpointID(report.ExtractHostID(m.Node), addr, port)
+ return RenderableNodes{id: NewRenderableNodeWith(id, "", "", "", m)}
}
// MapProcessIdentity maps a process topology node to a process renderable
@@ -155,10 +119,9 @@ func MapContainerIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
id = MakeContainerID(containerID)
major, _ = GetRenderableContainerName(m.Node)
minor = report.ExtractHostID(m.Node)
- rank, _ = m.Latest.Lookup(docker.ImageID)
)
- node := NewRenderableNodeWith(id, major, minor, rank, m)
+ node := NewRenderableNodeWith(id, major, minor, "", m)
node.ControlNode = m.ID
node.Shape = Hexagon
return RenderableNodes{id: node}
@@ -289,10 +252,9 @@ func MapAddressIdentity(m RenderableNode, local report.Networks) RenderableNodes
id = MakeAddressID(report.ExtractHostID(m.Node), addr)
major = addr
minor = report.ExtractHostID(m.Node)
- rank = major
)
- return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)}
+ return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, "", m)}
}
// MapHostIdentity maps a host topology node to a host renderable node. As it
@@ -395,7 +357,7 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes {
return RenderableNodes{}
}
- // Propogate the internet pseudo node
+ // Propagate the internet pseudo node
if strings.HasSuffix(n.ID, TheInternetID) {
return RenderableNodes{n.ID: n}
}
@@ -425,9 +387,45 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes {
// format for a process, but without any Major or Minor labels.
// It does not have enough info to do that, and the resulting graph
// must be merged with a process graph to get that info.
-func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes {
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
+func MapEndpoint2Process(n RenderableNode, local report.Networks) RenderableNodes {
+ // Nodes without a hostid are treated as psuedo nodes
+ if _, ok := n.Latest.Lookup(report.HostNodeID); !ok {
+ var node RenderableNode
+
+ addr, ok := n.Latest.Lookup(endpoint.Addr)
+ if !ok {
+ return RenderableNodes{}
+ }
+
+ port, ok := n.Latest.Lookup(endpoint.Port)
+ if !ok {
+ return RenderableNodes{}
+ }
+
+ if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) {
+ // If the dstNodeAddr is not in a network local to this report, we emit an
+ // internet node
+ node = theInternetNode(n)
+
+ } else if p, err := strconv.Atoi(port); err == nil && len(n.Adjacency) > 0 && p >= 32768 && p < 65535 {
+ // We are a 'client' pseudo node if the port is in the ephemeral port range.
+ // Linux uses 32768 to 61000, IANA suggests 49152 to 65535.
+ // We only exist if there is something in our adjacency
+ // Generate a single pseudo node for every (client ip, server ip, server port)
+ _, serverIP, serverPort, _ := ParseEndpointID(n.Adjacency[0])
+ node = newDerivedPseudoNode(MakePseudoNodeID(addr, serverIP, serverPort), addr, n)
+
+ } else if port != "" {
+ // Otherwise (the server node is missing), generate a pseudo node for every (server ip, server port)
+ node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr+":"+port, n)
+
+ } else {
+ // Empty port for some reason...
+ node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr, n)
+ }
+
+ node.Children = node.Children.Add(n)
+ return RenderableNodes{node.ID: node}
}
pid, ok := n.Node.Latest.Lookup(process.PID)
@@ -438,6 +436,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)
return RenderableNodes{id: node}
}
@@ -487,7 +486,7 @@ func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes {
node.Stack = true
}
- node.Children = node.Children.Add(n.Node)
+ node.Children = node.Children.Add(n)
return RenderableNodes{id: node}
}
@@ -513,7 +512,7 @@ func MapProcess2Name(n RenderableNode, _ report.Networks) RenderableNodes {
node.Counters = node.Node.Counters.Add(processesKey, 1)
node.Node.Topology = "process_name"
node.Node.ID = name
- node.Children = node.Children.Add(n.Node)
+ node.Children = node.Children.Add(n)
node.Shape = Square
node.Stack = true
return RenderableNodes{name: node}
@@ -567,7 +566,7 @@ func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) Renderable
result.Node.Counters = result.Node.Counters.Add(ContainersKey, 1)
// Add the container as a child of the new image node
- result.Children = result.Children.Add(n.Node)
+ result.Children = result.Children.Add(n)
result.Node.Topology = "container_image"
result.Node.ID = report.MakeContainerImageNodeID(imageID)
@@ -576,42 +575,6 @@ func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) Renderable
return RenderableNodes{id: result}
}
-// MapPod2Service maps pod RenderableNodes to service RenderableNodes.
-//
-// If this function is given a node without a kubernetes_pod_id
-// (including other pseudo nodes), it will produce an "Uncontained"
-// pseudo node.
-//
-// Otherwise, this function will produce a node with the correct ID
-// format for a container, but without any Major or Minor labels.
-// It does not have enough info to do that, and the resulting graph
-// must be merged with a pod graph to get that info.
-func MapPod2Service(n RenderableNode, _ report.Networks) RenderableNodes {
- // Propogate all pseudo nodes
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
- }
-
- // Otherwise, if some some reason the pod doesn't have a service_ids (maybe
- // slightly out of sync reports, or its not in a service), just drop it
- ids, ok := n.Node.Latest.Lookup(kubernetes.ServiceIDs)
- if !ok {
- return RenderableNodes{}
- }
-
- result := RenderableNodes{}
- for _, serviceID := range strings.Fields(ids) {
- id := MakeServiceID(serviceID)
- n := NewDerivedNode(id, n.WithParents(report.EmptySets))
- n.Node.Counters = n.Node.Counters.Add(podsKey, 1)
- n.Children = n.Children.Add(n.Node)
- n.Shape = Heptagon
- n.Stack = true
- result[id] = n
- }
- return result
-}
-
// ImageNameWithoutVersion splits the image name apart, returning the name
// without the version, if possible
func ImageNameWithoutVersion(name string) string {
@@ -671,7 +634,7 @@ func MapX2Host(n RenderableNode, _ report.Networks) RenderableNodes {
}
id := MakeHostID(report.ExtractHostID(n.Node))
result := NewDerivedNode(id, n.WithParents(report.EmptySets))
- result.Children = result.Children.Add(n.Node)
+ result.Children = result.Children.Add(n)
result.Shape = Circle
return RenderableNodes{id: result}
}
@@ -717,11 +680,47 @@ func MapContainer2Pod(n RenderableNode, _ report.Networks) RenderableNodes {
})
}
- result.Children = result.Children.Add(n.Node)
result.Shape = Heptagon
+ result.Children = result.Children.Add(n)
return RenderableNodes{id: result}
}
+// MapPod2Service maps pod RenderableNodes to service RenderableNodes.
+//
+// If this function is given a node without a kubernetes_pod_id
+// (including other pseudo nodes), it will produce an "Uncontained"
+// pseudo node.
+//
+// Otherwise, this function will produce a node with the correct ID
+// format for a container, but without any Major or Minor labels.
+// It does not have enough info to do that, and the resulting graph
+// must be merged with a pod graph to get that info.
+func MapPod2Service(pod RenderableNode, _ report.Networks) RenderableNodes {
+ // Propogate all pseudo nodes
+ if pod.Pseudo {
+ return RenderableNodes{pod.ID: pod}
+ }
+
+ // Otherwise, if some some reason the pod doesn't have a service_ids (maybe
+ // slightly out of sync reports, or its not in a service), just drop it
+ ids, ok := pod.Node.Latest.Lookup(kubernetes.ServiceIDs)
+ if !ok {
+ return RenderableNodes{}
+ }
+
+ result := RenderableNodes{}
+ for _, serviceID := range strings.Fields(ids) {
+ id := MakeServiceID(serviceID)
+ node := NewDerivedNode(id, pod.WithParents(report.EmptySets))
+ node.Node.Counters = node.Node.Counters.Add(podsKey, 1)
+ node.Children = node.Children.Add(pod)
+ node.Shape = Heptagon
+ node.Stack = true
+ result[id] = node
+ }
+ return result
+}
+
// MapContainer2Hostname maps container RenderableNodes to 'hostname' renderabled nodes..
func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes {
// Propogate all pseudo nodes
@@ -744,7 +743,7 @@ func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes
result.Counters = result.Counters.Add(ContainersKey, 1)
result.Node.Topology = "container_hostname"
result.Node.ID = id
- result.Children = result.Children.Add(n.Node)
+ result.Children = result.Children.Add(n)
result.Shape = Hexagon
result.Stack = true
return RenderableNodes{id: result}
@@ -784,20 +783,3 @@ func MapCountPods(n RenderableNode, _ report.Networks) RenderableNodes {
}
return RenderableNodes{output.ID: output}
}
-
-// trySplitAddr is basically ParseArbitraryNodeID, since its callsites
-// (pseudo funcs) just have opaque node IDs and don't know what topology they
-// come from. Without changing how pseudo funcs work, we can't make it much
-// smarter.
-//
-// TODO change how pseudofuncs work, and eliminate this helper.
-func trySplitAddr(addr string) (string, string) {
- fields := strings.SplitN(addr, report.ScopeDelim, 3)
- if len(fields) == 3 {
- return fields[1], fields[2]
- }
- if len(fields) == 2 {
- return fields[1], ""
- }
- panic(addr)
-}
diff --git a/render/renderable_node.go b/render/renderable_node.go
index 6d43b63ebe..847057e000 100644
--- a/render/renderable_node.go
+++ b/render/renderable_node.go
@@ -8,15 +8,15 @@ import (
// 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
- Children report.NodeSet `json:"children,omitempty"` // Nodes which have been grouped into this one
- ControlNode string `json:"-"` // ID of node from which to show the controls in the UI
- Shape string `json:"shape"` // Shape node should be rendered as
- Stack bool `json:"stack"` // Should UI render this node as a stack?
+ 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
+ Children RenderableNodeSet `json:"children,omitempty"` // Nodes which have been grouped into this one
+ ControlNode string `json:"control_node"` // ID of node from which to show the controls in the UI
+ Shape string `json:"shape"` // Shape node should be rendered as
+ Stack bool `json:"stack"` // Should UI render this node as a stack?
report.EdgeMetadata `json:"metadata"` // Numeric sums
report.Node
@@ -159,7 +159,10 @@ func (rn RenderableNode) Copy() RenderableNode {
func (rn RenderableNode) Prune() RenderableNode {
cp := rn.Copy()
cp.Node = report.MakeNode().WithAdjacent(cp.Node.Adjacency...)
- cp.Children = report.EmptyNodeSet
+ cp.Children = MakeRenderableNodeSet()
+ rn.Children.ForEach(func(n RenderableNode) {
+ cp.Children = cp.Children.Add(n.Prune())
+ })
return cp
}
diff --git a/report/node_set.go b/render/renderable_node_set.go
similarity index 52%
rename from report/node_set.go
rename to render/renderable_node_set.go
index 25d10804dc..7cf012610a 100644
--- a/report/node_set.go
+++ b/render/renderable_node_set.go
@@ -1,33 +1,35 @@
-package report
+package render
import (
"bytes"
"encoding/gob"
"fmt"
- "reflect"
"sort"
+ "github.com/davecgh/go-spew/spew"
"github.com/mndrix/ps"
"github.com/ugorji/go/codec"
+
+ "github.com/weaveworks/scope/test/reflect"
)
-// NodeSet is a set of nodes keyed on (Topology, ID). Clients must use
+// RenderableNodeSet is a set of nodes keyed on (Topology, ID). Clients must use
// the Add method to add nodes
-type NodeSet struct {
+type RenderableNodeSet struct {
psMap ps.Map
}
-// EmptyNodeSet is the empty set of nodes.
-var EmptyNodeSet = NodeSet{ps.NewMap()}
+// EmptyRenderableNodeSet is the empty set of nodes.
+var EmptyRenderableNodeSet = RenderableNodeSet{ps.NewMap()}
-// MakeNodeSet makes a new NodeSet with the given nodes.
-func MakeNodeSet(nodes ...Node) NodeSet {
- return EmptyNodeSet.Add(nodes...)
+// MakeRenderableNodeSet makes a new RenderableNodeSet with the given nodes.
+func MakeRenderableNodeSet(nodes ...RenderableNode) RenderableNodeSet {
+ return EmptyRenderableNodeSet.Add(nodes...)
}
-// Add adds the nodes to the NodeSet. Add is the only valid way to grow a
-// NodeSet. Add returns the NodeSet to enable chaining.
-func (n NodeSet) Add(nodes ...Node) NodeSet {
+// Add adds the nodes to the RenderableNodeSet. Add is the only valid way to grow a
+// RenderableNodeSet. Add returns the RenderableNodeSet to enable chaining.
+func (n RenderableNodeSet) Add(nodes ...RenderableNode) RenderableNodeSet {
result := n.psMap
if result == nil {
result = ps.NewMap()
@@ -35,11 +37,11 @@ func (n NodeSet) Add(nodes ...Node) NodeSet {
for _, node := range nodes {
result = result.Set(fmt.Sprintf("%s|%s", node.Topology, node.ID), node)
}
- return NodeSet{result}
+ return RenderableNodeSet{result}
}
-// Merge combines the two NodeSets and returns a new result.
-func (n NodeSet) Merge(other NodeSet) NodeSet {
+// Merge combines the two RenderableNodeSets and returns a new result.
+func (n RenderableNodeSet) Merge(other RenderableNodeSet) RenderableNodeSet {
nSize, otherSize := n.Size(), other.Size()
if nSize == 0 {
return other
@@ -54,22 +56,22 @@ func (n NodeSet) Merge(other NodeSet) NodeSet {
iter.ForEach(func(key string, otherVal interface{}) {
result = result.Set(key, otherVal)
})
- return NodeSet{result}
+ return RenderableNodeSet{result}
}
// Lookup the node 'key'
-func (n NodeSet) Lookup(key string) (Node, bool) {
+func (n RenderableNodeSet) Lookup(key string) (RenderableNode, bool) {
if n.psMap != nil {
value, ok := n.psMap.Lookup(key)
if ok {
- return value.(Node), true
+ return value.(RenderableNode), true
}
}
- return Node{}, false
+ return RenderableNode{}, false
}
// Keys is a list of all the keys in this set.
-func (n NodeSet) Keys() []string {
+func (n RenderableNodeSet) Keys() []string {
if n.psMap == nil {
return nil
}
@@ -79,7 +81,7 @@ func (n NodeSet) Keys() []string {
}
// Size is the number of nodes in the set
-func (n NodeSet) Size() int {
+func (n RenderableNodeSet) Size() int {
if n.psMap == nil {
return 0
}
@@ -88,23 +90,23 @@ func (n NodeSet) Size() int {
// ForEach executes f for each node in the set. Nodes are traversed in sorted
// order.
-func (n NodeSet) ForEach(f func(Node)) {
+func (n RenderableNodeSet) ForEach(f func(RenderableNode)) {
for _, key := range n.Keys() {
if val, ok := n.psMap.Lookup(key); ok {
- f(val.(Node))
+ f(val.(RenderableNode))
}
}
}
// Copy is a noop
-func (n NodeSet) Copy() NodeSet {
+func (n RenderableNodeSet) Copy() RenderableNodeSet {
return n
}
-func (n NodeSet) String() string {
+func (n RenderableNodeSet) String() string {
keys := []string{}
if n.psMap == nil {
- n = EmptyNodeSet
+ n = EmptyRenderableNodeSet
}
psMap := n.psMap
if psMap == nil {
@@ -118,15 +120,15 @@ func (n NodeSet) String() string {
buf := bytes.NewBufferString("{")
for _, key := range keys {
val, _ := psMap.Lookup(key)
- fmt.Fprintf(buf, "%s: %v, ", key, val)
+ fmt.Fprintf(buf, "%s: %s, ", key, spew.Sdump(val))
}
- fmt.Fprintf(buf, "}\n")
+ fmt.Fprintf(buf, "}")
return buf.String()
}
-// DeepEqual tests equality with other NodeSets
-func (n NodeSet) DeepEqual(i interface{}) bool {
- d, ok := i.(NodeSet)
+// DeepEqual tests equality with other RenderableNodeSets
+func (n RenderableNodeSet) DeepEqual(i interface{}) bool {
+ d, ok := i.(RenderableNodeSet)
if !ok {
return false
}
@@ -149,20 +151,20 @@ func (n NodeSet) DeepEqual(i interface{}) bool {
return equal
}
-func (n NodeSet) toIntermediate() []Node {
- intermediate := make([]Node, 0, n.Size())
- n.ForEach(func(node Node) {
+func (n RenderableNodeSet) toIntermediate() []RenderableNode {
+ intermediate := make([]RenderableNode, 0, n.Size())
+ n.ForEach(func(node RenderableNode) {
intermediate = append(intermediate, node)
})
return intermediate
}
-func (n NodeSet) fromIntermediate(nodes []Node) NodeSet {
- return MakeNodeSet(nodes...)
+func (n RenderableNodeSet) fromIntermediate(nodes []RenderableNode) RenderableNodeSet {
+ return MakeRenderableNodeSet(nodes...)
}
// CodecEncodeSelf implements codec.Selfer
-func (n *NodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
+func (n *RenderableNodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
if n.psMap != nil {
encoder.Encode(n.toIntermediate())
} else {
@@ -171,37 +173,37 @@ func (n *NodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
}
// CodecDecodeSelf implements codec.Selfer
-func (n *NodeSet) CodecDecodeSelf(decoder *codec.Decoder) {
- in := []Node{}
+func (n *RenderableNodeSet) CodecDecodeSelf(decoder *codec.Decoder) {
+ in := []RenderableNode{}
if err := decoder.Decode(&in); err != nil {
return
}
- *n = NodeSet{}.fromIntermediate(in)
+ *n = RenderableNodeSet{}.fromIntermediate(in)
}
// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
-func (NodeSet) MarshalJSON() ([]byte, error) {
+func (RenderableNodeSet) MarshalJSON() ([]byte, error) {
panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead")
}
// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead
-func (*NodeSet) UnmarshalJSON(b []byte) error {
+func (*RenderableNodeSet) UnmarshalJSON(b []byte) error {
panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead")
}
// GobEncode implements gob.Marshaller
-func (n NodeSet) GobEncode() ([]byte, error) {
+func (n RenderableNodeSet) GobEncode() ([]byte, error) {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(n.toIntermediate())
return buf.Bytes(), err
}
// GobDecode implements gob.Unmarshaller
-func (n *NodeSet) GobDecode(input []byte) error {
- in := []Node{}
+func (n *RenderableNodeSet) GobDecode(input []byte) error {
+ in := []RenderableNode{}
if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil {
return err
}
- *n = NodeSet{}.fromIntermediate(in)
+ *n = RenderableNodeSet{}.fromIntermediate(in)
return nil
}
diff --git a/render/renderable_node_set_test.go b/render/renderable_node_set_test.go
new file mode 100644
index 0000000000..9ffcdeb756
--- /dev/null
+++ b/render/renderable_node_set_test.go
@@ -0,0 +1,272 @@
+package render_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/weaveworks/scope/render"
+ "github.com/weaveworks/scope/report"
+ "github.com/weaveworks/scope/test/reflect"
+)
+
+var benchmarkResult render.RenderableNodeSet
+
+type nodeSpec struct {
+ topology string
+ id string
+}
+
+func renderableNode(n report.Node) render.RenderableNode {
+ node := render.NewRenderableNode(n.ID)
+ node.Topology = n.Topology
+ return node
+}
+
+func TestMakeRenderableNodeSet(t *testing.T) {
+ for _, testcase := range []struct {
+ inputs []nodeSpec
+ wants []nodeSpec
+ }{
+ {inputs: nil, wants: nil},
+ {inputs: []nodeSpec{}, wants: []nodeSpec{}},
+ {
+ inputs: []nodeSpec{{"", "a"}},
+ wants: []nodeSpec{{"", "a"}},
+ },
+ {
+ inputs: []nodeSpec{{"", "a"}, {"", "a"}, {"1", "a"}},
+ wants: []nodeSpec{{"", "a"}, {"1", "a"}},
+ },
+ {
+ inputs: []nodeSpec{{"", "b"}, {"", "c"}, {"", "a"}},
+ wants: []nodeSpec{{"", "a"}, {"", "b"}, {"", "c"}},
+ },
+ {
+ inputs: []nodeSpec{{"2", "a"}, {"3", "a"}, {"1", "a"}},
+ wants: []nodeSpec{{"1", "a"}, {"2", "a"}, {"3", "a"}},
+ },
+ } {
+ var (
+ inputs []render.RenderableNode
+ wants []render.RenderableNode
+ )
+ for _, spec := range testcase.inputs {
+ node := render.NewRenderableNode(spec.id)
+ node.Topology = spec.topology
+ inputs = append(inputs, node)
+ }
+ for _, spec := range testcase.wants {
+ node := render.NewRenderableNode(spec.id)
+ node.Topology = spec.topology
+ wants = append(wants, node)
+ }
+ if want, have := render.MakeRenderableNodeSet(wants...), render.MakeRenderableNodeSet(inputs...); !reflect.DeepEqual(want, have) {
+ t.Errorf("%#v: want %#v, have %#v", inputs, wants, have)
+ }
+ }
+}
+
+func BenchmarkMakeRenderableNodeSet(b *testing.B) {
+ nodes := []render.RenderableNode{}
+ for i := 1000; i >= 0; i-- {
+ node := report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
+ "a": "1",
+ "b": "2",
+ })
+ rn := render.NewRenderableNode(node.ID)
+ rn.Node = node
+ nodes = append(nodes, rn)
+ }
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchmarkResult = render.MakeRenderableNodeSet(nodes...)
+ }
+}
+
+func TestRenderableNodeSetAdd(t *testing.T) {
+ for _, testcase := range []struct {
+ input render.RenderableNodeSet
+ nodes []render.RenderableNode
+ want render.RenderableNodeSet
+ }{
+ {
+ input: render.RenderableNodeSet{},
+ nodes: []render.RenderableNode{},
+ want: render.RenderableNodeSet{},
+ },
+ {
+ input: render.EmptyRenderableNodeSet,
+ nodes: []render.RenderableNode{},
+ want: render.EmptyRenderableNodeSet,
+ },
+ {
+ input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
+ nodes: []render.RenderableNode{},
+ want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
+ },
+ {
+ input: render.EmptyRenderableNodeSet,
+ nodes: []render.RenderableNode{renderableNode(report.MakeNode().WithID("a"))},
+ want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
+ },
+ {
+ input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
+ nodes: []render.RenderableNode{renderableNode(report.MakeNode().WithID("a"))},
+ want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
+ },
+ {
+ input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("b"))),
+ nodes: []render.RenderableNode{
+ renderableNode(report.MakeNode().WithID("a")),
+ renderableNode(report.MakeNode().WithID("b")),
+ },
+ want: render.MakeRenderableNodeSet(
+ renderableNode(report.MakeNode().WithID("a")),
+ renderableNode(report.MakeNode().WithID("b")),
+ ),
+ },
+ {
+ input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
+ nodes: []render.RenderableNode{
+ renderableNode(report.MakeNode().WithID("c")),
+ renderableNode(report.MakeNode().WithID("b")),
+ },
+ want: render.MakeRenderableNodeSet(
+ renderableNode(report.MakeNode().WithID("a")),
+ renderableNode(report.MakeNode().WithID("b")),
+ renderableNode(report.MakeNode().WithID("c")),
+ ),
+ },
+ {
+ input: render.MakeRenderableNodeSet(
+ renderableNode(report.MakeNode().WithID("a")),
+ renderableNode(report.MakeNode().WithID("c")),
+ ),
+ nodes: []render.RenderableNode{
+ renderableNode(report.MakeNode().WithID("b")),
+ renderableNode(report.MakeNode().WithID("b")),
+ renderableNode(report.MakeNode().WithID("b")),
+ },
+ want: render.MakeRenderableNodeSet(
+ renderableNode(report.MakeNode().WithID("a")),
+ renderableNode(report.MakeNode().WithID("b")),
+ renderableNode(report.MakeNode().WithID("c")),
+ ),
+ },
+ } {
+ originalLen := testcase.input.Size()
+ if want, have := testcase.want, testcase.input.Add(testcase.nodes...); !reflect.DeepEqual(want, have) {
+ t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.nodes, want, have)
+ }
+ if testcase.input.Size() != originalLen {
+ t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.nodes)
+ }
+ }
+}
+
+func BenchmarkRenderableNodeSetAdd(b *testing.B) {
+ n := render.EmptyRenderableNodeSet
+ for i := 0; i < 600; i++ {
+ n = n.Add(
+ renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
+ "a": "1",
+ "b": "2",
+ })),
+ )
+ }
+
+ node := renderableNode(report.MakeNode().WithID("401.5").WithLatests(map[string]string{
+ "a": "1",
+ "b": "2",
+ }))
+
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchmarkResult = n.Add(node)
+ }
+}
+
+func TestRenderableNodeSetMerge(t *testing.T) {
+ for _, testcase := range []struct {
+ input render.RenderableNodeSet
+ other render.RenderableNodeSet
+ want render.RenderableNodeSet
+ }{
+ {input: render.RenderableNodeSet{}, other: render.RenderableNodeSet{}, want: render.RenderableNodeSet{}},
+ {input: render.EmptyRenderableNodeSet, other: render.EmptyRenderableNodeSet, want: render.EmptyRenderableNodeSet},
+ {
+ input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ other: render.EmptyRenderableNodeSet,
+ want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ },
+ {
+ input: render.EmptyRenderableNodeSet,
+ other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ },
+ {
+ input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ other: render.MakeRenderableNodeSet(render.NewRenderableNode("b")),
+ want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")),
+ },
+ {
+ input: render.MakeRenderableNodeSet(render.NewRenderableNode("b")),
+ other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")),
+ },
+ {
+ input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ },
+ {
+ input: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("c")),
+ other: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")),
+ want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b"), render.NewRenderableNode("c")),
+ },
+ {
+ input: render.MakeRenderableNodeSet(render.NewRenderableNode("b")),
+ other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
+ want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")),
+ },
+ } {
+ originalLen := testcase.input.Size()
+ if want, have := testcase.want, testcase.input.Merge(testcase.other); !reflect.DeepEqual(want, have) {
+ t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.other, want, have)
+ }
+ if testcase.input.Size() != originalLen {
+ t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.other)
+ }
+ }
+}
+
+func BenchmarkRenderableNodeSetMerge(b *testing.B) {
+ n, other := render.RenderableNodeSet{}, render.RenderableNodeSet{}
+ for i := 0; i < 600; i++ {
+ n = n.Add(
+ renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
+ "a": "1",
+ "b": "2",
+ })),
+ )
+ }
+
+ for i := 400; i < 1000; i++ {
+ other = other.Add(
+ renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
+ "c": "1",
+ "d": "2",
+ })),
+ )
+ }
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchmarkResult = n.Merge(other)
+ }
+}
diff --git a/render/renderable_node_test.go b/render/renderable_node_test.go
index 9a9ad49a05..e6bf6745c7 100644
--- a/render/renderable_node_test.go
+++ b/render/renderable_node_test.go
@@ -37,7 +37,7 @@ func TestMergeRenderableNode(t *testing.T) {
Rank: "",
Pseudo: false,
Node: report.MakeNode().WithAdjacent("a1"),
- Children: report.MakeNodeSet(report.MakeNode().WithID("child1")),
+ Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child1")),
}
node2 := render.RenderableNode{
ID: "foo",
@@ -46,7 +46,7 @@ func TestMergeRenderableNode(t *testing.T) {
Rank: "rank",
Pseudo: false,
Node: report.MakeNode().WithAdjacent("a2"),
- Children: report.MakeNodeSet(report.MakeNode().WithID("child2")),
+ Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child2")),
}
want := render.RenderableNode{
ID: "foo",
@@ -55,7 +55,7 @@ func TestMergeRenderableNode(t *testing.T) {
Rank: "rank",
Pseudo: false,
Node: report.MakeNode().WithID("foo").WithAdjacent("a1").WithAdjacent("a2"),
- Children: report.MakeNodeSet(report.MakeNode().WithID("child1"), report.MakeNode().WithID("child2")),
+ Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child1"), render.NewRenderableNode("child2")),
EdgeMetadata: report.EdgeMetadata{},
}.Prune()
have := node1.Merge(node2).Prune()
diff --git a/render/short_lived_connections_test.go b/render/short_lived_connections_test.go
index 9adf6ce4ef..71c4c6dc9d 100644
--- a/render/short_lived_connections_test.go
+++ b/render/short_lived_connections_test.go
@@ -73,7 +73,7 @@ var (
render.IncomingInternetID: {
ID: render.IncomingInternetID,
LabelMajor: render.InboundMajor,
- LabelMinor: render.RequestsMinor,
+ LabelMinor: render.InboundMinor,
Pseudo: true,
Shape: "cloud",
Node: report.MakeNode().WithAdjacent(render.MakeContainerID(containerID)),
diff --git a/render/topologies_test.go b/render/topologies_test.go
index bbbcfcdf2f..a41ec90407 100644
--- a/render/topologies_test.go
+++ b/render/topologies_test.go
@@ -1,7 +1,6 @@
package render_test
import (
- "reflect"
"testing"
"github.com/weaveworks/scope/probe/docker"
@@ -11,8 +10,17 @@ import (
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/fixture"
+ "github.com/weaveworks/scope/test/reflect"
)
+func TestEndpointRenderer(t *testing.T) {
+ have := render.EndpointRenderer.Render(fixture.Report).Prune()
+ want := expected.RenderedEndpoints
+ if !reflect.DeepEqual(want, have) {
+ t.Error(test.Diff(want, have))
+ }
+}
+
func TestProcessRenderer(t *testing.T) {
have := render.ProcessRenderer.Render(fixture.Report).Prune()
want := expected.RenderedProcesses
@@ -30,7 +38,7 @@ func TestProcessNameRenderer(t *testing.T) {
}
func TestContainerRenderer(t *testing.T) {
- have := (render.ContainerWithImageNameRenderer.Render(fixture.Report)).Prune()
+ have := (render.ContainerRenderer.Render(fixture.Report)).Prune()
want := expected.RenderedContainers
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -44,9 +52,9 @@ func TestContainerFilterRenderer(t *testing.T) {
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
docker.LabelPrefix + "works.weave.role": "system",
})
- have := render.FilterSystem(render.ContainerWithImageNameRenderer).Render(input).Prune()
+ have := render.FilterSystem(render.ContainerRenderer).Render(input).Prune()
want := expected.RenderedContainers.Copy()
- delete(want, expected.ClientContainerRenderedID)
+ delete(want, expected.ClientContainerID)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -71,23 +79,17 @@ func TestContainerWithHostIPsRenderer(t *testing.T) {
}
}
-func TestContainerFilterRendererImageName(t *testing.T) {
- // Test nodes are filtered by image name as well.
- input := fixture.Report.Copy()
- input.ContainerImage.Nodes[fixture.ClientContainerImageNodeID] = input.ContainerImage.Nodes[fixture.ClientContainerImageNodeID].WithLatests(map[string]string{
- docker.ImageName: "beta.gcr.io/google_containers/pause",
- })
- have := render.FilterSystem(render.ContainerWithImageNameRenderer).Render(input).Prune()
- want := expected.RenderedContainers.Copy()
- delete(want, expected.ClientContainerRenderedID)
+func TestContainerImageRenderer(t *testing.T) {
+ have := render.ContainerImageRenderer.Render(fixture.Report).Prune()
+ want := expected.RenderedContainerImages
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
-func TestContainerImageRenderer(t *testing.T) {
- have := render.ContainerImageRenderer.Render(fixture.Report).Prune()
- want := expected.RenderedContainerImages
+func TestAddressRenderer(t *testing.T) {
+ have := render.AddressRenderer.Render(fixture.Report).Prune()
+ want := expected.RenderedAddresses
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -124,7 +126,7 @@ func TestPodFilterRenderer(t *testing.T) {
have := render.FilterSystem(render.PodRenderer).Render(input).Prune()
want := expected.RenderedPods.Copy()
delete(want, expected.ClientPodRenderedID)
- delete(want, expected.ClientContainerRenderedID)
+ delete(want, expected.ClientContainerID)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
diff --git a/report/counters.go b/report/counters.go
index 71900b628a..28ce8e4795 100644
--- a/report/counters.go
+++ b/report/counters.go
@@ -103,7 +103,7 @@ func (c Counters) String() string {
val, _ := c.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %d, ", key, val)
}
- fmt.Fprintf(buf, "}\n")
+ fmt.Fprintf(buf, "}")
return buf.String()
}
diff --git a/report/edge_metadatas.go b/report/edge_metadatas.go
index 7d4ee38612..4540d4ebf8 100644
--- a/report/edge_metadatas.go
+++ b/report/edge_metadatas.go
@@ -6,6 +6,7 @@ import (
"fmt"
"reflect"
"sort"
+ "strconv"
"github.com/mndrix/ps"
"github.com/ugorji/go/codec"
@@ -123,7 +124,7 @@ func (c EdgeMetadatas) String() string {
val, _ := c.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %v, ", key, val)
}
- fmt.Fprintf(buf, "}\n")
+ fmt.Fprintf(buf, "}")
return buf.String()
}
@@ -219,6 +220,28 @@ type EdgeMetadata struct {
IngressByteCount *uint64 `json:"ingress_byte_count,omitempty"` // Transport layer
}
+// String returns a string representation of this EdgeMetadata
+// Helps with our use of Spew and diff.
+func (e EdgeMetadata) String() string {
+ f := func(i *uint64) string {
+ if i == nil {
+ return "nil"
+ }
+ return strconv.FormatUint(*i, 10)
+ }
+
+ return fmt.Sprintf(`{
+EgressPacketCount: %v,
+IngressPacketCount: %v,
+EgressByteCount: %v,
+IngressByteCount: %v,
+}`,
+ f(e.EgressPacketCount),
+ f(e.IngressPacketCount),
+ f(e.EgressByteCount),
+ f(e.IngressByteCount))
+}
+
// Copy returns a value copy of the EdgeMetadata.
func (e EdgeMetadata) Copy() EdgeMetadata {
return EdgeMetadata{
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/latest_map.go b/report/latest_map.go
index 18af1bca90..0b407fa971 100644
--- a/report/latest_map.go
+++ b/report/latest_map.go
@@ -137,7 +137,7 @@ func (m LatestMap) String() string {
val, _ := m.Map.Lookup(key)
fmt.Fprintf(buf, "%s: %s,\n", key, val)
}
- fmt.Fprintf(buf, "}\n")
+ fmt.Fprintf(buf, "}")
return buf.String()
}
diff --git a/report/node_set_test.go b/report/node_set_test.go
deleted file mode 100644
index 05434ccb24..0000000000
--- a/report/node_set_test.go
+++ /dev/null
@@ -1,231 +0,0 @@
-package report_test
-
-import (
- "fmt"
- "testing"
-
- "github.com/weaveworks/scope/report"
- "github.com/weaveworks/scope/test/reflect"
-)
-
-var benchmarkResult report.NodeSet
-
-type nodeSpec struct {
- topology string
- id string
-}
-
-func TestMakeNodeSet(t *testing.T) {
- for _, testcase := range []struct {
- inputs []nodeSpec
- wants []nodeSpec
- }{
- {inputs: nil, wants: nil},
- {inputs: []nodeSpec{}, wants: []nodeSpec{}},
- {
- inputs: []nodeSpec{{"", "a"}},
- wants: []nodeSpec{{"", "a"}},
- },
- {
- inputs: []nodeSpec{{"", "a"}, {"", "a"}, {"1", "a"}},
- wants: []nodeSpec{{"", "a"}, {"1", "a"}},
- },
- {
- inputs: []nodeSpec{{"", "b"}, {"", "c"}, {"", "a"}},
- wants: []nodeSpec{{"", "a"}, {"", "b"}, {"", "c"}},
- },
- {
- inputs: []nodeSpec{{"2", "a"}, {"3", "a"}, {"1", "a"}},
- wants: []nodeSpec{{"1", "a"}, {"2", "a"}, {"3", "a"}},
- },
- } {
- var (
- inputs []report.Node
- wants []report.Node
- )
- for _, spec := range testcase.inputs {
- inputs = append(inputs, report.MakeNode().WithTopology(spec.topology).WithID(spec.id))
- }
- for _, spec := range testcase.wants {
- wants = append(wants, report.MakeNode().WithTopology(spec.topology).WithID(spec.id))
- }
- if want, have := report.MakeNodeSet(wants...), report.MakeNodeSet(inputs...); !reflect.DeepEqual(want, have) {
- t.Errorf("%#v: want %#v, have %#v", inputs, wants, have)
- }
- }
-}
-
-func BenchmarkMakeNodeSet(b *testing.B) {
- nodes := []report.Node{}
- for i := 1000; i >= 0; i-- {
- node := report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
- "a": "1",
- "b": "2",
- })
- nodes = append(nodes, node)
- }
- b.ReportAllocs()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- benchmarkResult = report.MakeNodeSet(nodes...)
- }
-}
-
-func TestNodeSetAdd(t *testing.T) {
- for _, testcase := range []struct {
- input report.NodeSet
- nodes []report.Node
- want report.NodeSet
- }{
- {input: report.NodeSet{}, nodes: []report.Node{}, want: report.NodeSet{}},
- {
- input: report.EmptyNodeSet,
- nodes: []report.Node{},
- want: report.EmptyNodeSet,
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("a")),
- nodes: []report.Node{},
- want: report.MakeNodeSet(report.MakeNode().WithID("a")),
- },
- {
- input: report.EmptyNodeSet,
- nodes: []report.Node{report.MakeNode().WithID("a")},
- want: report.MakeNodeSet(report.MakeNode().WithID("a")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("a")),
- nodes: []report.Node{report.MakeNode().WithID("a")},
- want: report.MakeNodeSet(report.MakeNode().WithID("a")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("b")),
- nodes: []report.Node{report.MakeNode().WithID("a"), report.MakeNode().WithID("b")},
- want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("a")),
- nodes: []report.Node{report.MakeNode().WithID("c"), report.MakeNode().WithID("b")},
- want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("c")),
- nodes: []report.Node{report.MakeNode().WithID("b"), report.MakeNode().WithID("b"), report.MakeNode().WithID("b")},
- want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")),
- },
- } {
- originalLen := testcase.input.Size()
- if want, have := testcase.want, testcase.input.Add(testcase.nodes...); !reflect.DeepEqual(want, have) {
- t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.nodes, want, have)
- }
- if testcase.input.Size() != originalLen {
- t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.nodes)
- }
- }
-}
-
-func BenchmarkNodeSetAdd(b *testing.B) {
- n := report.EmptyNodeSet
- for i := 0; i < 600; i++ {
- n = n.Add(
- report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
- "a": "1",
- "b": "2",
- }),
- )
- }
-
- node := report.MakeNode().WithID("401.5").WithLatests(map[string]string{
- "a": "1",
- "b": "2",
- })
-
- b.ReportAllocs()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- benchmarkResult = n.Add(node)
- }
-}
-
-func TestNodeSetMerge(t *testing.T) {
- for _, testcase := range []struct {
- input report.NodeSet
- other report.NodeSet
- want report.NodeSet
- }{
- {input: report.NodeSet{}, other: report.NodeSet{}, want: report.NodeSet{}},
- {input: report.EmptyNodeSet, other: report.EmptyNodeSet, want: report.EmptyNodeSet},
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("a")),
- other: report.EmptyNodeSet,
- want: report.MakeNodeSet(report.MakeNode().WithID("a")),
- },
- {
- input: report.EmptyNodeSet,
- other: report.MakeNodeSet(report.MakeNode().WithID("a")),
- want: report.MakeNodeSet(report.MakeNode().WithID("a")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("a")),
- other: report.MakeNodeSet(report.MakeNode().WithID("b")),
- want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("b")),
- other: report.MakeNodeSet(report.MakeNode().WithID("a")),
- want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("a")),
- other: report.MakeNodeSet(report.MakeNode().WithID("a")),
- want: report.MakeNodeSet(report.MakeNode().WithID("a")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("c")),
- other: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
- want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")),
- },
- {
- input: report.MakeNodeSet(report.MakeNode().WithID("b")),
- other: report.MakeNodeSet(report.MakeNode().WithID("a")),
- want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
- },
- } {
- originalLen := testcase.input.Size()
- if want, have := testcase.want, testcase.input.Merge(testcase.other); !reflect.DeepEqual(want, have) {
- t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.other, want, have)
- }
- if testcase.input.Size() != originalLen {
- t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.other)
- }
- }
-}
-
-func BenchmarkNodeSetMerge(b *testing.B) {
- n, other := report.NodeSet{}, report.NodeSet{}
- for i := 0; i < 600; i++ {
- n = n.Add(
- report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
- "a": "1",
- "b": "2",
- }),
- )
- }
-
- for i := 400; i < 1000; i++ {
- other = other.Add(
- report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
- "c": "1",
- "d": "2",
- }),
- )
- }
- b.ReportAllocs()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- benchmarkResult = n.Merge(other)
- }
-}
diff --git a/report/sets.go b/report/sets.go
index 71fdb7782a..0dd6125614 100644
--- a/report/sets.go
+++ b/report/sets.go
@@ -114,7 +114,7 @@ func (s Sets) String() string {
val, _ := s.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %v, ", key, val)
}
- fmt.Fprintf(buf, "}\n")
+ fmt.Fprintf(buf, "}")
return buf.String()
}
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 {