diff --git a/Makefile b/Makefile
index 5262b44806..6b8ff6568d 100644
--- a/Makefile
+++ b/Makefile
@@ -154,6 +154,9 @@ clean:
$(SCOPE_EXE) $(RUNSVINIT) prog/static.go client/build/*.js docker/weave .pkg \
$(CODECGEN_TARGETS) $(CODECGEN_DIR)/bin
+clean-codecgen:
+ rm -rf $(CODECGEN_TARGETS) $(CODECGEN_DIR)/bin
+
deps:
$(GO) get -u -f -tags $(GO_BUILD_TAGS) \
github.com/FiloSottile/gvt \
diff --git a/app/api_topologies.go b/app/api_topologies.go
index a76c851da7..71aa66ca73 100644
--- a/app/api_topologies.go
+++ b/app/api_topologies.go
@@ -266,7 +266,7 @@ func decorateWithStats(rpt report.Report, renderer render.Renderer) topologyStat
)
for _, n := range renderer.Render(rpt) {
nodes++
- if !n.Pseudo {
+ if n.Topology != render.Pseudo {
realNodes++
}
edges += len(n.Adjacency)
diff --git a/app/api_topology.go b/app/api_topology.go
index e28df96193..712b6d7f4a 100644
--- a/app/api_topology.go
+++ b/app/api_topology.go
@@ -18,7 +18,7 @@ const (
// APITopology is returned by the /api/topology/{name} handler.
type APITopology struct {
- Nodes render.RenderableNodes `json:"nodes"`
+ Nodes detailed.NodeSummaries `json:"nodes"`
}
// APINode is returned by the /api/topology/{name}/{id} handler.
@@ -34,7 +34,7 @@ func handleTopology(ctx context.Context, rep Reporter, renderer render.Renderer,
return
}
respondWith(w, http.StatusOK, APITopology{
- Nodes: renderer.Render(report).Prune(),
+ Nodes: detailed.Summaries(renderer.Render(report)),
})
}
@@ -104,7 +104,7 @@ func handleWebsocket(
}(conn)
var (
- previousTopo render.RenderableNodes
+ previousTopo detailed.NodeSummaries
tick = time.Tick(loop)
wait = make(chan struct{}, 1)
)
@@ -117,8 +117,8 @@ func handleWebsocket(
log.Errorf("Error generating report: %v", err)
return
}
- newTopo := renderer.Render(report).Prune()
- diff := render.TopoDiff(previousTopo, newTopo)
+ newTopo := detailed.Summaries(renderer.Render(report))
+ diff := detailed.TopoDiff(previousTopo, newTopo)
previousTopo = newTopo
if err := conn.WriteJSON(diff); err != nil {
diff --git a/app/api_topology_test.go b/app/api_topology_test.go
index 7c3ae88c02..aacbea59e4 100644
--- a/app/api_topology_test.go
+++ b/app/api_topology_test.go
@@ -3,16 +3,14 @@ package app_test
import (
"fmt"
"net/url"
- "reflect"
"testing"
"github.com/gorilla/websocket"
"github.com/ugorji/go/codec"
"github.com/weaveworks/scope/app"
- "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"
)
@@ -59,13 +57,13 @@ func TestAPITopologyProcesses(t *testing.T) {
defer ts.Close()
is404(t, ts, "/api/topology/processes/foobar")
{
- body := getRawJSON(t, ts, "/api/topology/processes/"+expected.ServerProcessID)
+ body := getRawJSON(t, ts, "/api/topology/processes/"+fixture.ServerProcessNodeID)
var node app.APINode
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
if err := decoder.Decode(&node); err != nil {
t.Fatal(err)
}
- equals(t, expected.ServerProcessID, node.Node.ID)
+ equals(t, fixture.ServerProcessNodeID, node.Node.ID)
equals(t, "apache", node.Node.Label)
equals(t, false, node.Node.Pseudo)
// Let's not unit-test the specific content of the detail tables
@@ -98,18 +96,21 @@ func TestAPITopologyHosts(t *testing.T) {
t.Fatal(err)
}
- if want, have := expected.RenderedHosts, topo.Nodes.Prune(); !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
+ // Should have the rendered host nodes
+ for id := range expected.RenderedHosts {
+ if _, ok := topo.Nodes[id]; !ok {
+ t.Errorf("Expected output to include node: %s, but wasn't found", id)
+ }
}
}
{
- body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostID)
+ body := getRawJSON(t, ts, "/api/topology/hosts/"+fixture.ServerHostNodeID)
var node app.APINode
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
if err := decoder.Decode(&node); err != nil {
t.Fatal(err)
}
- equals(t, expected.ServerHostID, node.Node.ID)
+ equals(t, fixture.ServerHostNodeID, 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
@@ -141,7 +142,7 @@ func TestAPITopologyWebsocket(t *testing.T) {
_, p, err := ws.ReadMessage()
ok(t, err)
- var d render.Diff
+ var d detailed.Diff
decoder := codec.NewDecoderBytes(p, &codec.JsonHandle{})
if err := decoder.Decode(&d); err != nil {
t.Fatalf("JSON parse error: %s", err)
diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js
index 8e70a45a0f..ee131c6632 100644
--- a/client/app/scripts/charts/nodes-chart.js
+++ b/client/app/scripts/charts/nodes-chart.js
@@ -244,7 +244,7 @@ export default class NodesChart extends React.Component {
// copy relevant fields to state nodes
return topology.map((node, id) => makeMap({
id,
- label: node.get('label_major'),
+ label: node.get('label'),
pseudo: node.get('pseudo'),
subLabel: node.get('label_minor'),
nodeCount: node.get('node_count'),
diff --git a/client/app/scripts/components/debug-toolbar.js b/client/app/scripts/components/debug-toolbar.js
index 8acfa6023a..bdb545ec54 100644
--- a/client/app/scripts/components/debug-toolbar.js
+++ b/client/app/scripts/components/debug-toolbar.js
@@ -21,7 +21,7 @@ const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCou
stack,
node_count: nodeCount,
id: name,
- label_major: name,
+ label: name,
label_minor: 'weave-1',
latest: {},
metadata: {},
diff --git a/client/app/scripts/components/embedded-terminal.js b/client/app/scripts/components/embedded-terminal.js
index 291820fb00..b80909b5bf 100644
--- a/client/app/scripts/components/embedded-terminal.js
+++ b/client/app/scripts/components/embedded-terminal.js
@@ -8,9 +8,9 @@ import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS,
export default function EmeddedTerminal({pipe, nodeId, details}) {
const node = details.get(nodeId);
const d = node && node.details;
- const titleBarColor = d && getNodeColorDark(d.rank, d.label_major);
- const statusBarColor = d && getNodeColor(d.rank, d.label_major);
- const title = d && d.label_major;
+ const titleBarColor = d && getNodeColorDark(d.rank, d.label);
+ const statusBarColor = d && getNodeColor(d.rank, d.label);
+ const title = d && d.label;
const style = {
right: DETAILS_PANEL_MARGINS.right + DETAILS_PANEL_WIDTH + 10 +
diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js
index a8d93c4e34..6fc44676db 100644
--- a/client/app/scripts/components/node-details.js
+++ b/client/app/scripts/components/node-details.js
@@ -54,7 +54,7 @@ export default class NodeDetails extends React.Component {
renderLoading() {
const node = this.props.nodes.get(this.props.nodeId);
- const label = node ? node.get('label_major') : this.props.label;
+ const label = node ? node.get('label') : this.props.label;
const nodeColor = (node ?
getNodeColorDark(node.get('rank'), label, node.get('pseudo')) :
getNeutralColor());
diff --git a/client/app/scripts/components/sparkline.js b/client/app/scripts/components/sparkline.js
index 03c1670049..cde7bd819c 100644
--- a/client/app/scripts/components/sparkline.js
+++ b/client/app/scripts/components/sparkline.js
@@ -20,7 +20,7 @@ export default class Sparkline extends React.Component {
let data = this.props.data;
// Do nothing if no data or data w/o date are passed in.
- if (data.length === 0 || data[0].date === undefined) {
+ if (data === undefined || data.length === 0 || data[0].date === undefined) {
return
;
}
diff --git a/client/app/scripts/stores/__tests__/app-store-test.js b/client/app/scripts/stores/__tests__/app-store-test.js
index 5ccee481b9..379e878e9d 100644
--- a/client/app/scripts/stores/__tests__/app-store-test.js
+++ b/client/app/scripts/stores/__tests__/app-store-test.js
@@ -17,7 +17,7 @@ describe('AppStore', () => {
rank: undefined,
adjacency: ['n1', 'n2'],
pseudo: undefined,
- label_major: undefined,
+ label: undefined,
label_minor: undefined
},
n2: {
@@ -25,7 +25,7 @@ describe('AppStore', () => {
rank: undefined,
adjacency: undefined,
pseudo: undefined,
- label_major: undefined,
+ label: undefined,
label_minor: undefined
}
};
diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js
index 66abc2c2a6..d6b2668845 100644
--- a/client/app/scripts/stores/app-store.js
+++ b/client/app/scripts/stores/app-store.js
@@ -22,7 +22,7 @@ const error = debug('scope:error');
function makeNode(node) {
return {
id: node.id,
- label_major: node.label_major,
+ label: node.label,
label_minor: node.label_minor,
node_count: node.node_count,
rank: node.rank,
diff --git a/experimental/_integration/easy_test.go b/experimental/_integration/easy_test.go
index 08e2260e06..9690847a7b 100644
--- a/experimental/_integration/easy_test.go
+++ b/experimental/_integration/easy_test.go
@@ -61,9 +61,9 @@ func TestMultipleProbes(t *testing.T) {
})
}
-func parseTopology(t *testing.T, p []byte) map[string]report.RenderableNode {
+func parseTopology(t *testing.T, p []byte) map[string]report.Node {
var r struct {
- Nodes map[string]report.RenderableNode `json:"nodes"`
+ Nodes map[string]report.Node `json:"nodes"`
}
if err := json.Unmarshal(p, &r); err != nil {
@@ -85,7 +85,7 @@ func parseEdge(t *testing.T, p []byte) map[string]interface{} {
return edge.Metadata
}
-func assertAdjacent(t *testing.T, n report.RenderableNode, ids ...string) {
+func assertAdjacent(t *testing.T, n report.Node, ids ...string) {
want := report.MakeIDList(ids...)
if have := n.Adjacency; !reflect.DeepEqual(want, have) {
diff --git a/experimental/graphviz/handlers.go b/experimental/graphviz/handlers.go
index e7e9b97b08..307c55c032 100644
--- a/experimental/graphviz/handlers.go
+++ b/experimental/graphviz/handlers.go
@@ -10,18 +10,18 @@ import (
"os"
"os/exec"
- "github.com/weaveworks/scope/render"
+ "github.com/weaveworks/scope/render/detailed"
"github.com/weaveworks/scope/report"
)
-func dot(w io.Writer, t render.RenderableNodes) {
+func dot(w io.Writer, t detailed.NodeSummaries) {
fmt.Fprintf(w, "digraph G {\n")
fmt.Fprintf(w, "\toutputorder=edgesfirst;\n")
fmt.Fprintf(w, "\toverlap=scale;\n")
fmt.Fprintf(w, "\tnode [style=filled];\n")
fmt.Fprintf(w, "\t\n")
for id, rn := range t {
- label := rn.LabelMajor
+ label := rn.Label
if len(label) > 20 {
label = label[:20] + "..."
}
diff --git a/experimental/graphviz/render.go b/experimental/graphviz/render.go
index ad36b3c723..de452b89fa 100644
--- a/experimental/graphviz/render.go
+++ b/experimental/graphviz/render.go
@@ -4,10 +4,11 @@ import (
"fmt"
"github.com/weaveworks/scope/render"
+ "github.com/weaveworks/scope/render/detailed"
"github.com/weaveworks/scope/report"
)
-func renderTo(rpt report.Report, topology string) (render.RenderableNodes, error) {
+func renderTo(rpt report.Report, topology string) (detailed.NodeSummaries, error) {
renderer, ok := map[string]render.Renderer{
"processes": render.FilterUnconnected(render.ProcessWithContainerNameRenderer),
"processes-by-name": render.FilterUnconnected(render.ProcessNameRenderer),
@@ -16,7 +17,7 @@ func renderTo(rpt report.Report, topology string) (render.RenderableNodes, error
"hosts": render.HostRenderer,
}[topology]
if !ok {
- return render.RenderableNodes{}, fmt.Errorf("unknown topology %v", topology)
+ return detailed.NodeSummaries{}, fmt.Errorf("unknown topology %v", topology)
}
- return renderer.Render(rpt), nil
+ return detailed.Summaries(renderer.Render(rpt)), nil
}
diff --git a/integration/config.sh b/integration/config.sh
index 251c436f10..a240aa3470 100644
--- a/integration/config.sh
+++ b/integration/config.sh
@@ -39,7 +39,7 @@ has() {
local host=$2
local name=$3
local count=${4:-1}
- assert "curl -s http://${host}:4040/api/topology/${view}?system=show | jq -r '[.nodes[] | select(.label_major == \"${name}\")] | length'" $count
+ assert "curl -s http://${host}:4040/api/topology/${view}?system=show | jq -r '[.nodes[] | select(.label == \"${name}\")] | length'" $count
}
# this checks we have a named container
@@ -51,7 +51,7 @@ node_id() {
local view="$1"
local host="$2"
local name="$3"
- echo $(curl -s http://${host}:4040/api/topology/${view}?system=show | jq -r ".nodes[] | select(.label_major == \"${name}\") | .id")
+ echo $(curl -s http://${host}:4040/api/topology/${view}?system=show | jq -r ".nodes[] | select(.label == \"${name}\") | .id")
}
container_id() {
@@ -103,7 +103,7 @@ wait_for() {
local nodes="$(curl -s http://$host:4040/api/topology/${view}?system=show)"
local found=0
for name in "$@"; do
- local count=$(echo "${nodes}" | jq -r "[.nodes[] | select(.label_major == \"${name}\")] | length")
+ local count=$(echo "${nodes}" | jq -r "[.nodes[] | select(.label == \"${name}\")] | length")
if [ -n "${count}" ] && [ "${count}" -ge 1 ]; then
found=$(( found + 1 ))
fi
diff --git a/render/benchmark_test.go b/render/benchmark_test.go
index 84afbb7589..05889b21ed 100644
--- a/render/benchmark_test.go
+++ b/render/benchmark_test.go
@@ -14,7 +14,7 @@ import (
var (
benchReportFile = flag.String("bench-report-file", "", "json report file to use for benchmarking (relative to this package)")
- benchmarkRenderResult map[string]render.RenderableNode
+ benchmarkRenderResult map[string]report.Node
benchmarkStatsResult render.Stats
)
diff --git a/render/detailed/connections.go b/render/detailed/connections.go
index 1c48dd6d07..ca446d0977 100644
--- a/render/detailed/connections.go
+++ b/render/detailed/connections.go
@@ -30,12 +30,12 @@ var (
)
type connectionsRow struct {
- remoteNode, localNode *render.RenderableNode
+ remoteNode, localNode *report.Node
remoteAddr, localAddr string
port string // always the server-side port
}
-func incomingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
+func incomingConnectionsTable(topologyID string, n report.Node, ns report.Nodes) NodeSummaryGroup {
localEndpointIDs := endpointChildIDsOf(n)
// For each node which has an edge TO me
@@ -54,7 +54,7 @@ func incomingConnectionsTable(topologyID string, n render.RenderableNode, ns ren
// details on the internet node)
for _, child := range endpointChildrenOf(node) {
for _, localEndpointID := range child.Adjacency.Intersection(localEndpointIDs) {
- _, localAddr, port, ok := render.ParseEndpointID(localEndpointID)
+ _, localAddr, port, ok := report.ParseEndpointNodeID(localEndpointID)
if !ok {
continue
}
@@ -84,26 +84,27 @@ func incomingConnectionsTable(topologyID string, n render.RenderableNode, ns ren
}
}
-func outgoingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
+func outgoingConnectionsTable(topologyID string, n report.Node, ns report.Nodes) 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) {
+ for _, id := range n.Adjacency {
+ node, ok := ns[id]
+ if !ok {
continue
}
remoteNode := node.Copy()
remoteEndpointIDs := endpointChildIDsOf(remoteNode)
for _, localEndpoint := range localEndpoints {
- _, localAddr, _, ok := render.ParseEndpointID(localEndpoint.ID)
+ _, localAddr, _, ok := report.ParseEndpointNodeID(localEndpoint.ID)
if !ok {
continue
}
for _, remoteEndpointID := range localEndpoint.Adjacency.Intersection(remoteEndpointIDs) {
- _, _, port, ok := render.ParseEndpointID(remoteEndpointID)
+ _, _, port, ok := report.ParseEndpointNodeID(remoteEndpointID)
if !ok {
continue
}
@@ -133,47 +134,54 @@ func outgoingConnectionsTable(topologyID string, n render.RenderableNode, ns ren
}
}
-func endpointChildrenOf(n render.RenderableNode) []render.RenderableNode {
- result := []render.RenderableNode{}
- n.Children.ForEach(func(child render.RenderableNode) {
- if _, _, _, ok := render.ParseEndpointID(child.ID); ok {
+func endpointChildrenOf(n report.Node) []report.Node {
+ result := []report.Node{}
+ n.Children.ForEach(func(child report.Node) {
+ if _, _, _, ok := report.ParseEndpointNodeID(child.ID); ok {
result = append(result, child)
}
})
return result
}
-func endpointChildIDsOf(n render.RenderableNode) report.IDList {
+func endpointChildIDsOf(n report.Node) report.IDList {
result := report.MakeIDList()
- n.Children.ForEach(func(child render.RenderableNode) {
- if _, _, _, ok := render.ParseEndpointID(child.ID); ok {
- result = append(result, child.ID)
+ n.Children.ForEach(func(child report.Node) {
+ if _, _, _, ok := report.ParseEndpointNodeID(child.ID); ok {
+ result = result.Add(child.ID)
}
})
return result
}
-func isInternetNode(n render.RenderableNode) bool {
+func isInternetNode(n report.Node) 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
+ // Use MakeNodeSummary to render the id and label of this node
+ // TODO(paulbellamy): Would be cleaner if we hade just a
+ // MakeNodeID(*row.remoteode). As we don't need the whole summary.
+ summary, ok := MakeNodeSummary(*row.remoteNode)
+ summary.Metadata, summary.Metrics, summary.DockerLabels = nil, nil, nil
+ if !ok && row.remoteAddr != "" {
+ summary = NodeSummary{
+ ID: row.remoteAddr + ":" + row.port,
+ Label: row.remoteAddr,
+ Linkable: false,
+ }
}
- metadata := []MetadataRow{}
if includeLocal {
- metadata = append(metadata,
+ summary.Metadata = append(summary.Metadata,
MetadataRow{
ID: "foo",
Value: row.localAddr,
Datatype: number,
})
}
- metadata = append(metadata,
+ summary.Metadata = append(summary.Metadata,
MetadataRow{
ID: portKey,
Value: row.port,
@@ -185,12 +193,7 @@ func connectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary
Datatype: number,
},
)
- nodes = append(nodes, NodeSummary{
- ID: id,
- Label: label,
- Linkable: linkable,
- Metadata: metadata,
- })
+ nodes = append(nodes, summary)
}
sort.Sort(nodeSummariesByID(nodes))
return nodes
diff --git a/render/detailed/docker_labels.go b/render/detailed/docker_labels.go
index b8f886eccb..0e086e1c81 100644
--- a/render/detailed/docker_labels.go
+++ b/render/detailed/docker_labels.go
@@ -11,6 +11,11 @@ import (
// on an origin ID, which is (optimistically) a node ID in one of our
// topologies.
func NodeDockerLabels(nmd report.Node) []MetadataRow {
+ if _, ok := nmd.Counters.Lookup(nmd.Topology); ok {
+ // This is a group of nodes, so no docker labels!
+ return nil
+ }
+
if nmd.Topology != report.Container && nmd.Topology != report.ContainerImage {
return nil
}
diff --git a/render/detailed/labels.go b/render/detailed/labels.go
index 0a5fd4735b..556e459a02 100644
--- a/render/detailed/labels.go
+++ b/render/detailed/labels.go
@@ -8,7 +8,7 @@ import (
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/probe/overlay"
"github.com/weaveworks/scope/probe/process"
- "github.com/weaveworks/scope/render"
+ "github.com/weaveworks/scope/report"
)
var labels = map[string]string{
@@ -46,7 +46,7 @@ var labels = map[string]string{
process.PID: "PID",
process.PPID: "Parent PID",
process.Threads: "# Threads",
- render.ContainersKey: "# Containers",
+ report.Container: "# Containers",
}
// Label maps from the internal keys to the human-readable label for a piece
diff --git a/render/detailed/metadata.go b/render/detailed/metadata.go
index dba2912039..cce29789bc 100644
--- a/render/detailed/metadata.go
+++ b/render/detailed/metadata.go
@@ -11,7 +11,6 @@ import (
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/probe/overlay"
"github.com/weaveworks/scope/probe/process"
- "github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
)
@@ -37,7 +36,7 @@ var (
}
containerImageNodeMetadata = []MetadataRowTemplate{
Latest{ID: docker.ImageID, Truncate: 12, Prime: true},
- Counter{ID: render.ContainersKey, Prime: true},
+ Counter{ID: report.Container, Prime: true},
}
podNodeMetadata = []MetadataRowTemplate{
Latest{ID: kubernetes.PodID, Prime: true},
@@ -119,10 +118,7 @@ type MetadataRow struct {
// Copy returns a value copy of a metadata row.
func (m MetadataRow) Copy() MetadataRow {
- return MetadataRow{
- ID: m.ID,
- Value: m.Value,
- }
+ return m
}
// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
@@ -171,6 +167,11 @@ func (m *MetadataRow) CodecDecodeSelf(decoder *codec.Decoder) {
// NodeMetadata produces a table (to be consumed directly by the UI) based on
// an origin ID, which is (optimistically) a node ID in one of our topologies.
func NodeMetadata(n report.Node) []MetadataRow {
+ if _, ok := n.Counters.Lookup(n.Topology); ok {
+ // This is a group of nodes, so no metadata!
+ return nil
+ }
+
renderers := map[string][]MetadataRowTemplate{
report.Process: processNodeMetadata,
report.Container: containerNodeMetadata,
diff --git a/render/detailed/metadata_test.go b/render/detailed/metadata_test.go
index 47feeba536..39ccc2658a 100644
--- a/render/detailed/metadata_test.go
+++ b/render/detailed/metadata_test.go
@@ -47,3 +47,29 @@ func TestNodeMetadata(t *testing.T) {
}
}
}
+
+func TestMetadataRowCopy(t *testing.T) {
+ var (
+ row = detailed.MetadataRow{
+ ID: "id",
+ Value: "value",
+ Prime: true,
+ Datatype: "datatype",
+ }
+ cp = row.Copy()
+ )
+
+ // copy should be identical
+ if !reflect.DeepEqual(row, cp) {
+ t.Error(test.Diff(row, cp))
+ }
+
+ // changing the copy should not change the original
+ cp.ID = ""
+ cp.Value = ""
+ cp.Prime = false
+ cp.Datatype = ""
+ if row.ID != "id" || row.Value != "value" || row.Prime != true || row.Datatype != "datatype" {
+ t.Errorf("Expected changing the copy not to modify the original")
+ }
+}
diff --git a/render/detailed/metrics.go b/render/detailed/metrics.go
index 5de036b156..5346dada21 100644
--- a/render/detailed/metrics.go
+++ b/render/detailed/metrics.go
@@ -47,6 +47,22 @@ type MetricRow struct {
Metric *report.Metric
}
+// Summary returns a copy of the MetricRow, without the samples, just the value if there is one.
+func (m MetricRow) Summary() MetricRow {
+ row := MetricRow{
+ ID: m.ID,
+ Format: m.Format,
+ Group: m.Group,
+ Value: m.Value,
+ }
+ if m.Metric != nil {
+ var metric = m.Metric.Copy()
+ metric.Samples = nil
+ row.Metric = &metric
+ }
+ return row
+}
+
// Copy returns a value copy of the MetricRow
func (m MetricRow) Copy() MetricRow {
row := MetricRow{
@@ -125,8 +141,13 @@ func (m *MetricRow) CodecDecodeSelf(decoder *codec.Decoder) {
}
// NodeMetrics produces a table (to be consumed directly by the UI) based on
-// an origin ID, which is (optimistically) a node ID in one of our topologies.
+// an a report.Node, which is (hopefully) a node in one of our topologies.
func NodeMetrics(n report.Node) []MetricRow {
+ if _, ok := n.Counters.Lookup(n.Topology); ok {
+ // This is a group of nodes, so no metrics!
+ return nil
+ }
+
renderers := map[string][]MetricRow{
report.Process: processNodeMetrics,
report.Container: containerNodeMetrics,
diff --git a/render/detailed/metrics_test.go b/render/detailed/metrics_test.go
index 3310a03981..d5085d0907 100644
--- a/render/detailed/metrics_test.go
+++ b/render/detailed/metrics_test.go
@@ -3,6 +3,7 @@ package detailed_test
import (
"reflect"
"testing"
+ "time"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
@@ -110,3 +111,30 @@ func TestNodeMetrics(t *testing.T) {
}
}
}
+
+func TestMetricRowSummary(t *testing.T) {
+ var (
+ now = time.Now()
+ metric = report.MakeMetric().Add(now, 1.234)
+ row = detailed.MetricRow{
+ ID: "id",
+ Format: "format",
+ Group: "group",
+ Value: 1.234,
+ Metric: &metric,
+ }
+ summary = row.Summary()
+ )
+ // summary should have all the same fields
+ if row.ID != summary.ID || row.Format != summary.Format || row.Group != summary.Group || row.Value != summary.Value {
+ t.Errorf("Expected summary to have same fields as original: %#v, but had %#v", row, summary)
+ }
+ // summary should not have any samples
+ if summary.Metric.Len() != 0 {
+ t.Errorf("Expected summary to have no samples, but had %d", summary.Metric.Len())
+ }
+ // original metric should still have its samples
+ if metric.Len() != 1 {
+ t.Errorf("Expected original metric to still have it's samples, but had %d", metric.Len())
+ }
+}
diff --git a/render/detailed/node.go b/render/detailed/node.go
index ab2831bdc6..8897f3ea93 100644
--- a/render/detailed/node.go
+++ b/render/detailed/node.go
@@ -8,7 +8,6 @@ import (
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/process"
- "github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
)
@@ -16,8 +15,6 @@ 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"`
@@ -79,15 +76,10 @@ 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(topologyID string, r report.Report, ns render.RenderableNodes, n render.RenderableNode) Node {
+func MakeNode(topologyID string, r report.Report, ns report.Nodes, n report.Node) Node {
summary, _ := MakeNodeSummary(n)
- summary.ID = n.ID
- summary.Label = n.LabelMajor
-
return Node{
NodeSummary: summary,
- Rank: n.Rank,
- Pseudo: n.Pseudo,
Controls: controls(r, n),
Children: children(n),
Parents: Parents(r, n),
@@ -121,15 +113,10 @@ func controlsFor(topology report.Topology, nodeID string) []ControlInstance {
return result
}
-func controls(r report.Report, n render.RenderableNode) []ControlInstance {
- if _, ok := r.Process.Nodes[n.ControlNode]; ok {
- return controlsFor(r.Process, n.ControlNode)
- } else if _, ok := r.Container.Nodes[n.ControlNode]; ok {
- return controlsFor(r.Container, n.ControlNode)
- } else if _, ok := r.ContainerImage.Nodes[n.ControlNode]; ok {
- return controlsFor(r.ContainerImage, n.ControlNode)
- } else if _, ok := r.Host.Nodes[n.ControlNode]; ok {
- return controlsFor(r.Host, n.ControlNode)
+func controls(r report.Report, n report.Node) []ControlInstance {
+ // TODO(paulbellamy): this ID will have been munged in rendering, so we should stop doing that, so that this matches up.
+ if t, ok := r.Topology(n.Topology); ok {
+ return controlsFor(t, n.ID)
}
return []ControlInstance{}
}
@@ -184,21 +171,24 @@ var (
TopologyID: "containers-by-image",
Label: "Container Images",
Columns: []Column{
- {ID: render.ContainersKey, Label: Label(render.ContainersKey), DefaultSort: true},
+ {ID: report.Container, Label: Label(report.Container), DefaultSort: true},
},
},
},
}
)
-func children(n render.RenderableNode) []NodeSummaryGroup {
+func children(n report.Node) []NodeSummaryGroup {
summaries := map[string][]NodeSummary{}
- 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)
- }
+ n.Children.ForEach(func(child report.Node) {
+ if child.ID == n.ID {
+ return
+ }
+ summary, ok := MakeNodeSummary(child)
+ if !ok {
+ return
}
+ summaries[child.Topology] = append(summaries[child.Topology], summary.SummarizeMetrics())
})
nodeSummaryGroups := []NodeSummaryGroup{}
diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go
index 2075b377cc..91d5adc0c0 100644
--- a/render/detailed/node_test.go
+++ b/render/detailed/node_test.go
@@ -1,8 +1,6 @@
package detailed_test
import (
- "fmt"
- "reflect"
"testing"
"github.com/weaveworks/scope/probe/docker"
@@ -10,35 +8,41 @@ 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/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/fixture"
+ "github.com/weaveworks/scope/test/reflect"
)
+func child(t *testing.T, r render.Renderer, id string) detailed.NodeSummary {
+ s, ok := detailed.MakeNodeSummary(r.Render(fixture.Report)[id])
+ if !ok {
+ t.Fatalf("Expected node %s to be summarizable, but wasn't", id)
+ }
+ return s.SummarizeMetrics()
+}
+
func TestMakeDetailedHostNode(t *testing.T) {
renderableNodes := render.HostRenderer.Render(fixture.Report)
- renderableNode := renderableNodes[render.MakeHostID(fixture.ClientHostID)]
+ renderableNode := renderableNodes[fixture.ClientHostNodeID]
have := detailed.MakeNode("hosts", fixture.Report, renderableNodes, renderableNode)
- containerImageNodeSummary, _ := detailed.MakeNodeSummary(
- 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],
- )
+ containerImageNodeSummary := child(t, render.ContainerImageRenderer, fixture.ClientContainerImageNodeID)
+ containerNodeSummary := child(t, render.ContainerRenderer, fixture.ClientContainerNodeID)
+ process1NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess1NodeID)
process1NodeSummary.Linkable = true
- process2NodeSummary, _ := detailed.MakeNodeSummary(
- render.ProcessRenderer.Render(fixture.Report)[expected.ClientProcess2ID],
- )
+ process2NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess2NodeID)
process2NodeSummary.Linkable = true
want := detailed.Node{
NodeSummary: detailed.NodeSummary{
- ID: render.MakeHostID(fixture.ClientHostID),
- Label: "client",
- Linkable: true,
+ ID: fixture.ClientHostNodeID,
+ Label: "client",
+ LabelMinor: "hostname.com",
+ Rank: "hostname.com",
+ Pseudo: false,
+ Shape: "circle",
+ Linkable: true,
+ Adjacency: report.MakeIDList(fixture.ServerHostNodeID),
Metadata: []detailed.MetadataRow{
{
ID: "host_name",
@@ -86,8 +90,6 @@ func TestMakeDetailedHostNode(t *testing.T) {
},
},
},
- Rank: "hostname.com",
- Pseudo: false,
Controls: []detailed.ControlInstance{},
Children: []detailed.NodeSummaryGroup{
{
@@ -106,7 +108,7 @@ func TestMakeDetailedHostNode(t *testing.T) {
Label: "Container Images",
TopologyID: "containers-by-image",
Columns: []detailed.Column{
- {ID: render.ContainersKey, Label: detailed.Label(render.ContainersKey), DefaultSort: true},
+ {ID: report.Container, Label: detailed.Label(report.Container), DefaultSort: true},
},
Nodes: []detailed.NodeSummary{containerImageNodeSummary},
},
@@ -126,9 +128,13 @@ func TestMakeDetailedHostNode(t *testing.T) {
Columns: detailed.NormalColumns,
Nodes: []detailed.NodeSummary{
{
- ID: "host:server.hostname.com",
- Label: "server",
- Linkable: true,
+ ID: fixture.ServerHostNodeID,
+ Label: "server",
+ LabelMinor: "hostname.com",
+ Rank: "hostname.com",
+ Shape: "circle",
+ Linkable: true,
+ Adjacency: report.MakeIDList(render.OutgoingInternetID),
Metadata: []detailed.MetadataRow{
{
ID: "port",
@@ -152,25 +158,31 @@ func TestMakeDetailedHostNode(t *testing.T) {
}
func TestMakeDetailedContainerNode(t *testing.T) {
- id := render.MakeContainerID(fixture.ServerContainerID)
+ id := fixture.ServerContainerNodeID
renderableNodes := render.ContainerRenderer.Render(fixture.Report)
renderableNode, ok := renderableNodes[id]
if !ok {
t.Fatalf("Node not found: %s", id)
}
have := detailed.MakeNode("containers", fixture.Report, renderableNodes, renderableNode)
+
+ serverProcessNodeSummary := child(t, render.ProcessRenderer, fixture.ServerProcessNodeID)
+ serverProcessNodeSummary.Linkable = true
want := detailed.Node{
NodeSummary: detailed.NodeSummary{
- ID: id,
- Label: "server",
- Linkable: true,
+ ID: id,
+ Label: "server",
+ LabelMinor: "server.hostname.com",
+ Shape: "hexagon",
+ Linkable: true,
+ Pseudo: false,
Metadata: []detailed.MetadataRow{
{ID: "docker_container_id", Value: fixture.ServerContainerID, Prime: true},
{ID: "docker_container_state", Value: "running", Prime: true},
{ID: "docker_image_id", Value: fixture.ServerContainerImageID},
},
DockerLabels: []detailed.MetadataRow{
- {ID: "label_" + render.AmazonECSContainerNameLabel, Value: `server`},
+ {ID: "label_" + detailed.AmazonECSContainerNameLabel, Value: `server`},
{ID: "label_foo1", Value: `bar1`},
{ID: "label_foo2", Value: `bar2`},
{ID: "label_io.kubernetes.pod.name", Value: "ping/pong-b"},
@@ -190,34 +202,23 @@ func TestMakeDetailedContainerNode(t *testing.T) {
},
},
},
- Pseudo: false,
Controls: []detailed.ControlInstance{},
Children: []detailed.NodeSummaryGroup{
{
Label: "Processes",
TopologyID: "processes",
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),
- Label: "apache",
- Linkable: true,
- Metadata: []detailed.MetadataRow{
- {ID: process.PID, Value: fixture.ServerPID, Prime: true, Datatype: "number"},
- },
- Metrics: []detailed.MetricRow{},
- },
- },
+ Nodes: []detailed.NodeSummary{serverProcessNodeSummary},
},
},
Parents: []detailed.Parent{
{
- ID: render.MakeContainerImageID(fixture.ServerContainerImageName),
+ ID: fixture.ServerContainerImageNodeID,
Label: fixture.ServerContainerImageName,
TopologyID: "containers-by-image",
},
{
- ID: render.MakeHostID(fixture.ServerHostName),
+ ID: fixture.ServerHostNodeID,
Label: fixture.ServerHostName,
TopologyID: "hosts",
},
@@ -230,9 +231,12 @@ func TestMakeDetailedContainerNode(t *testing.T) {
Columns: detailed.NormalColumns,
Nodes: []detailed.NodeSummary{
{
- ID: "container:a1b2c3d4e5",
- Label: "client",
- Linkable: true,
+ ID: fixture.ClientContainerNodeID,
+ Label: "client",
+ LabelMinor: "client.hostname.com",
+ Shape: "hexagon",
+ Linkable: true,
+ Adjacency: report.MakeIDList(fixture.ServerContainerNodeID),
Metadata: []detailed.MetadataRow{
{
ID: "port",
@@ -247,9 +251,14 @@ func TestMakeDetailedContainerNode(t *testing.T) {
},
},
{
- ID: "in-theinternet",
- Label: "The Internet",
- Linkable: true,
+ ID: render.IncomingInternetID,
+ Label: render.InboundMajor,
+ LabelMinor: render.InboundMinor,
+ Rank: render.IncomingInternetID,
+ Shape: "cloud",
+ Linkable: true,
+ Pseudo: true,
+ Adjacency: report.MakeIDList(fixture.ServerContainerNodeID),
Metadata: []detailed.MetadataRow{
{
ID: "port",
diff --git a/render/detailed/parents.go b/render/detailed/parents.go
index ecdb2bdcf0..991820e097 100644
--- a/render/detailed/parents.go
+++ b/render/detailed/parents.go
@@ -6,7 +6,6 @@ import (
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/kubernetes"
- "github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
)
@@ -19,7 +18,7 @@ type Parent struct {
// Parents renders the parents of this report.Node, which have been aggregated
// from the probe reports.
-func Parents(r report.Report, n render.RenderableNode) (result []Parent) {
+func Parents(r report.Report, n report.Node) (result []Parent) {
topologies := map[string]struct {
report.Topology
render func(report.Node) Parent
@@ -37,9 +36,9 @@ func Parents(r report.Report, n render.RenderableNode) (result []Parent) {
sort.Strings(topologyIDs)
for _, topologyID := range topologyIDs {
t := topologies[topologyID]
- parents, _ := n.Node.Parents.Lookup(topologyID)
+ parents, _ := n.Parents.Lookup(topologyID)
for _, id := range parents {
- if topologyID == n.Node.Topology && id == n.Node.ID {
+ if topologyID == n.Topology && id == n.ID {
continue
}
@@ -55,39 +54,42 @@ func Parents(r report.Report, n render.RenderableNode) (result []Parent) {
}
func containerParent(n report.Node) Parent {
- label, _ := render.GetRenderableContainerName(n)
+ label := getRenderableContainerName(n)
containerID, _ := n.Latest.Lookup(docker.ContainerID)
return Parent{
- ID: render.MakeContainerID(containerID),
+ ID: report.MakeContainerNodeID(containerID),
Label: label,
TopologyID: "containers",
}
}
func podParent(n report.Node) Parent {
+ namespace, _ := n.Latest.Lookup(kubernetes.Namespace)
podID, _ := n.Latest.Lookup(kubernetes.PodID)
podName, _ := n.Latest.Lookup(kubernetes.PodName)
return Parent{
- ID: render.MakePodID(podID),
+ ID: report.MakePodNodeID(namespace, podID),
Label: podName,
TopologyID: "pods",
}
}
func serviceParent(n report.Node) Parent {
+ namespace, _ := n.Latest.Lookup(kubernetes.Namespace)
serviceID, _ := n.Latest.Lookup(kubernetes.ServiceID)
serviceName, _ := n.Latest.Lookup(kubernetes.ServiceName)
return Parent{
- ID: render.MakeServiceID(serviceID),
+ ID: report.MakeServiceNodeID(namespace, serviceID),
Label: serviceName,
TopologyID: "pods-by-service",
}
}
func containerImageParent(n report.Node) Parent {
+ imageID, _ := n.Latest.Lookup(docker.ImageID)
imageName, _ := n.Latest.Lookup(docker.ImageName)
return Parent{
- ID: render.MakeContainerImageID(render.ImageNameWithoutVersion(imageName)),
+ ID: report.MakeContainerImageNodeID(imageID),
Label: imageName,
TopologyID: "containers-by-image",
}
@@ -96,7 +98,7 @@ func containerImageParent(n report.Node) Parent {
func hostParent(n report.Node) Parent {
hostName, _ := n.Latest.Lookup(host.HostName)
return Parent{
- ID: render.MakeHostID(hostName),
+ ID: report.MakeHostNodeID(hostName),
Label: hostName,
TopologyID: "hosts",
}
diff --git a/render/detailed/parents_test.go b/render/detailed/parents_test.go
index 572e750560..7a21b18bac 100644
--- a/render/detailed/parents_test.go
+++ b/render/detailed/parents_test.go
@@ -15,33 +15,39 @@ import (
func TestParents(t *testing.T) {
for _, c := range []struct {
name string
- node render.RenderableNode
+ node report.Node
want []detailed.Parent
}{
{
name: "Node accidentally tagged with itself",
- node: render.HostRenderer.Render(fixture.Report)[render.MakeHostID(fixture.ClientHostID)].WithParents(
+ node: render.HostRenderer.Render(fixture.Report)[fixture.ClientHostNodeID].WithParents(
report.EmptySets.Add(report.Host, report.MakeStringSet(fixture.ClientHostNodeID)),
),
want: nil,
},
{
- node: render.HostRenderer.Render(fixture.Report)[render.MakeHostID(fixture.ClientHostID)],
+ node: render.HostRenderer.Render(fixture.Report)[fixture.ClientHostNodeID],
want: nil,
},
{
- node: render.ContainerRenderer.Render(fixture.Report)[render.MakeContainerID(fixture.ClientContainerID)],
+ node: render.ContainerImageRenderer.Render(fixture.Report)[fixture.ClientContainerImageNodeID],
want: []detailed.Parent{
- {ID: render.MakeContainerImageID(fixture.ClientContainerImageName), Label: fixture.ClientContainerImageName, TopologyID: "containers-by-image"},
- {ID: render.MakeHostID(fixture.ClientHostID), Label: fixture.ClientHostName, TopologyID: "hosts"},
+ {ID: fixture.ClientHostNodeID, Label: fixture.ClientHostName, TopologyID: "hosts"},
},
},
{
- node: render.ProcessRenderer.Render(fixture.Report)[render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID)],
+ node: render.ContainerRenderer.Render(fixture.Report)[fixture.ClientContainerNodeID],
want: []detailed.Parent{
- {ID: render.MakeContainerID(fixture.ClientContainerID), Label: fixture.ClientContainerName, TopologyID: "containers"},
- {ID: render.MakeContainerImageID(fixture.ClientContainerImageName), Label: fixture.ClientContainerImageName, TopologyID: "containers-by-image"},
- {ID: render.MakeHostID(fixture.ClientHostID), Label: fixture.ClientHostName, TopologyID: "hosts"},
+ {ID: fixture.ClientContainerImageNodeID, Label: fixture.ClientContainerImageName, TopologyID: "containers-by-image"},
+ {ID: fixture.ClientHostNodeID, Label: fixture.ClientHostName, TopologyID: "hosts"},
+ },
+ },
+ {
+ node: render.ProcessRenderer.Render(fixture.Report)[fixture.ClientProcess1NodeID],
+ want: []detailed.Parent{
+ {ID: fixture.ClientContainerNodeID, Label: fixture.ClientContainerName, TopologyID: "containers"},
+ {ID: fixture.ClientContainerImageNodeID, Label: fixture.ClientContainerImageName, TopologyID: "containers-by-image"},
+ {ID: fixture.ClientHostNodeID, Label: fixture.ClientHostName, TopologyID: "hosts"},
},
},
} {
diff --git a/render/detailed/summary.go b/render/detailed/summary.go
index 473db69c68..a61506dbb6 100644
--- a/render/detailed/summary.go
+++ b/render/detailed/summary.go
@@ -2,8 +2,10 @@ package detailed
import (
"fmt"
+ "strings"
"github.com/weaveworks/scope/probe/docker"
+ "github.com/weaveworks/scope/probe/endpoint"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/probe/process"
@@ -11,6 +13,21 @@ import (
"github.com/weaveworks/scope/report"
)
+// Shapes that are allowed
+const (
+ Circle = "circle"
+ Square = "square"
+ Heptagon = "heptagon"
+ Hexagon = "hexagon"
+ Cloud = "cloud"
+
+ ImageNameNone = ""
+
+ // Keys we use to render container names
+ AmazonECSContainerNameLabel = "com.amazonaws.ecs.container-name"
+ KubernetesContainerNameLabel = "io.kubernetes.container.name"
+)
+
// NodeSummaryGroup is a topology-typed group of children for a Node.
type NodeSummaryGroup struct {
ID string `json:"id"`
@@ -53,33 +70,56 @@ func MakeColumn(id string) Column {
type NodeSummary struct {
ID string `json:"id"`
Label string `json:"label"`
- Linkable bool `json:"linkable"` // Whether this node can be linked-to
+ LabelMinor string `json:"label_minor"`
+ Rank string `json:"rank"`
+ Shape string `json:"shape,omitempty"`
+ Stack bool `json:"stack,omitempty"`
+ Linkable bool `json:"linkable,omitempty"` // Whether this node can be linked-to
+ Pseudo bool `json:"pseudo,omitempty"`
Metadata []MetadataRow `json:"metadata,omitempty"`
DockerLabels []MetadataRow `json:"docker_labels,omitempty"`
Metrics []MetricRow `json:"metrics,omitempty"`
+ Adjacency report.IDList `json:"adjacency,omitempty"`
}
// MakeNodeSummary summarizes a node, if possible.
-func MakeNodeSummary(n render.RenderableNode) (NodeSummary, bool) {
- renderers := map[string]func(report.Node) NodeSummary{
+func MakeNodeSummary(n report.Node) (NodeSummary, bool) {
+ renderers := map[string]func(NodeSummary, report.Node) (NodeSummary, bool){
+ render.Pseudo: pseudoNodeSummary,
report.Process: processNodeSummary,
report.Container: containerNodeSummary,
report.ContainerImage: containerImageNodeSummary,
report.Pod: podNodeSummary,
+ report.Service: serviceNodeSummary,
report.Host: hostNodeSummary,
}
if renderer, ok := renderers[n.Topology]; ok {
- return renderer(n.Node), true
+ return renderer(baseNodeSummary(n), n)
}
return NodeSummary{}, false
}
+// SummarizeMetrics returns a copy of the NodeSummary where the metrics are
+// replaced with their summaries
+func (n NodeSummary) SummarizeMetrics() NodeSummary {
+ cp := n.Copy()
+ for i, m := range cp.Metrics {
+ cp.Metrics[i] = m.Summary()
+ }
+ return cp
+}
+
// Copy returns a value copy of the NodeSummary
func (n NodeSummary) Copy() NodeSummary {
result := NodeSummary{
- ID: n.ID,
- Label: n.Label,
- Linkable: n.Linkable,
+ ID: n.ID,
+ Label: n.Label,
+ LabelMinor: n.LabelMinor,
+ Rank: n.Rank,
+ Shape: n.Shape,
+ Stack: n.Stack,
+ Linkable: n.Linkable,
+ Adjacency: n.Adjacency.Copy(),
}
for _, row := range n.Metadata {
result.Metadata = append(result.Metadata, row.Copy())
@@ -93,52 +133,202 @@ func (n NodeSummary) Copy() NodeSummary {
return result
}
-func baseNodeSummary(id, label string, linkable bool, nmd report.Node) NodeSummary {
+func baseNodeSummary(n report.Node) NodeSummary {
return NodeSummary{
- ID: id,
- Label: label,
- Linkable: linkable,
- Metadata: NodeMetadata(nmd),
- DockerLabels: NodeDockerLabels(nmd),
- Metrics: NodeMetrics(nmd),
+ ID: n.ID,
+ Shape: Circle,
+ Linkable: true,
+ Metadata: NodeMetadata(n),
+ DockerLabels: NodeDockerLabels(n),
+ Metrics: NodeMetrics(n),
+ Adjacency: n.Adjacency.Copy(),
}
}
-func processNodeSummary(nmd report.Node) NodeSummary {
- var (
- id string
- label, nameFound = nmd.Latest.Lookup(process.Name)
- )
- if pid, ok := nmd.Latest.Lookup(process.PID); ok {
- if !nameFound {
- label = fmt.Sprintf("(%s)", pid)
+func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
+ base.Pseudo = true
+ base.Rank = n.ID
+
+ if template, ok := map[string]struct{ Label, LabelMinor, Shape string }{
+ render.TheInternetID: {render.InboundMajor, "", Cloud},
+ render.IncomingInternetID: {render.InboundMajor, render.InboundMinor, Cloud},
+ render.OutgoingInternetID: {render.OutboundMajor, render.OutboundMinor, Cloud},
+ }[n.ID]; ok {
+ base.Label = template.Label
+ base.LabelMinor = template.LabelMinor
+ base.Shape = template.Shape
+ return base, true
+ }
+
+ // try rendering it as an uncontained node
+ if strings.HasPrefix(n.ID, render.MakePseudoNodeID(render.UncontainedID)) {
+ base.Label = render.UncontainedMajor
+ base.Shape = Square
+ base.Stack = true
+ base.LabelMinor = report.ExtractHostID(n)
+ return base, true
+ }
+
+ // try rendering it as an endpoint
+ if addr, ok := n.Latest.Lookup(endpoint.Addr); ok {
+ base.Label = addr
+ return base, true
+ }
+
+ return NodeSummary{}, false
+}
+
+func processNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
+ base.Label, _ = n.Latest.Lookup(process.Name)
+ base.Rank, _ = n.Latest.Lookup(process.Name)
+ base.Shape = Square
+
+ if p, ok := n.Counters.Lookup(report.Process); ok {
+ base.Stack = true
+ if p == 1 {
+ base.LabelMinor = fmt.Sprintf("%d process", p)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%d processes", p)
+ }
+ } else {
+ pid, ok := n.Latest.Lookup(process.PID)
+ if !ok {
+ return NodeSummary{}, false
+ }
+ if containerName, ok := n.Latest.Lookup(docker.ContainerName); ok {
+ base.LabelMinor = fmt.Sprintf("%s (%s:%s)", report.ExtractHostID(n), containerName, pid)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%s (%s)", report.ExtractHostID(n), pid)
}
- id = render.MakeProcessID(report.ExtractHostID(nmd), pid)
}
- _, isConnected := nmd.Latest.Lookup(render.IsConnected)
- return baseNodeSummary(id, label, isConnected, nmd)
+
+ _, isConnected := n.Latest.Lookup(render.IsConnected)
+ base.Linkable = isConnected
+ return base, true
}
-func containerNodeSummary(nmd report.Node) NodeSummary {
- label, _ := render.GetRenderableContainerName(nmd)
- containerID, _ := nmd.Latest.Lookup(docker.ContainerID)
- return baseNodeSummary(render.MakeContainerID(containerID), label, true, nmd)
+func containerNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
+ base.Label = getRenderableContainerName(n)
+
+ if c, ok := n.Counters.Lookup(report.Container); ok {
+ base.Stack = true
+ if c == 1 {
+ base.LabelMinor = fmt.Sprintf("%d container", c)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%d containers", c)
+ }
+ } else {
+ base.LabelMinor = report.ExtractHostID(n)
+ }
+
+ if imageName, ok := n.Latest.Lookup(docker.ImageName); ok {
+ base.Rank = render.ImageNameWithoutVersion(imageName)
+ }
+
+ base.Shape = Hexagon
+ return base, true
}
-func containerImageNodeSummary(nmd report.Node) NodeSummary {
- imageName, _ := nmd.Latest.Lookup(docker.ImageName)
- return baseNodeSummary(render.MakeContainerImageID(render.ImageNameWithoutVersion(imageName)), imageName, true, nmd)
+func containerImageNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
+ imageName, ok := n.Latest.Lookup(docker.ImageName)
+ if !ok {
+ return NodeSummary{}, false
+ }
+
+ imageNameWithoutVersion := render.ImageNameWithoutVersion(imageName)
+ base.Label = imageNameWithoutVersion
+ base.Rank = imageNameWithoutVersion
+ base.Shape = Hexagon
+ base.Stack = true
+
+ if base.Label == ImageNameNone {
+ base.Label, _ = n.Latest.Lookup(docker.ImageID)
+ if len(base.Label) > 12 {
+ base.Label = base.Label[:12]
+ }
+ }
+
+ if i, ok := n.Counters.Lookup(report.ContainerImage); ok {
+ if i == 1 {
+ base.LabelMinor = fmt.Sprintf("%d image", i)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%d images", i)
+ }
+ } else if c, ok := n.Counters.Lookup(report.Container); ok {
+ if c == 1 {
+ base.LabelMinor = fmt.Sprintf("%d container", c)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%d containers", c)
+ }
+ }
+ return base, true
}
-func podNodeSummary(nmd report.Node) NodeSummary {
- podID, _ := nmd.Latest.Lookup(kubernetes.PodID)
- podName, _ := nmd.Latest.Lookup(kubernetes.PodName)
- return baseNodeSummary(render.MakePodID(podID), podName, true, nmd)
+func podNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
+ base.Label, _ = n.Latest.Lookup(kubernetes.PodName)
+ base.Rank, _ = n.Latest.Lookup(kubernetes.PodID)
+ base.Shape = Heptagon
+
+ if p, ok := n.Counters.Lookup(report.Pod); ok {
+ base.Stack = true
+ if p == 1 {
+ base.LabelMinor = fmt.Sprintf("%d pod", p)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%d pods", p)
+ }
+ } else if c, ok := n.Counters.Lookup(report.Container); ok {
+ if c == 1 {
+ base.LabelMinor = fmt.Sprintf("%d container", c)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%d containers", c)
+ }
+ }
+
+ return base, true
}
-func hostNodeSummary(nmd report.Node) NodeSummary {
- hostName, _ := nmd.Latest.Lookup(host.HostName)
- return baseNodeSummary(render.MakeHostID(hostName), hostName, true, nmd)
+func serviceNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
+ base.Label, _ = n.Latest.Lookup(kubernetes.ServiceName)
+ base.Rank, _ = n.Latest.Lookup(kubernetes.ServiceID)
+ base.Shape = Heptagon
+ base.Stack = true
+
+ // Services are always just a group of pods, so there's no counting multiple
+ // services which might be grouped together.
+ if p, ok := n.Counters.Lookup(report.Pod); ok {
+ if p == 1 {
+ base.LabelMinor = fmt.Sprintf("%d pod", p)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%d pods", p)
+ }
+ }
+
+ return base, true
+}
+
+func hostNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
+ var (
+ hostname, _ = n.Latest.Lookup(host.HostName)
+ parts = strings.SplitN(hostname, ".", 2)
+ )
+
+ if len(parts) == 2 {
+ base.Label, base.LabelMinor, base.Rank = parts[0], parts[1], parts[1]
+ } else {
+ base.Label = hostname
+ }
+
+ if h, ok := n.Counters.Lookup(report.Host); ok {
+ base.Stack = true
+ if h == 1 {
+ base.LabelMinor = fmt.Sprintf("%d host", h)
+ } else {
+ base.LabelMinor = fmt.Sprintf("%d hosts", h)
+ }
+ }
+
+ base.Shape = Circle
+ return base, true
}
type nodeSummariesByID []NodeSummary
@@ -146,3 +336,54 @@ type nodeSummariesByID []NodeSummary
func (s nodeSummariesByID) Len() int { return len(s) }
func (s nodeSummariesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s nodeSummariesByID) Less(i, j int) bool { return s[i].ID < s[j].ID }
+
+// NodeSummaries is a set of NodeSummaries indexed by ID.
+type NodeSummaries map[string]NodeSummary
+
+// Summaries converts RenderableNodes into a set of NodeSummaries
+func Summaries(rns report.Nodes) NodeSummaries {
+ result := NodeSummaries{}
+ for id, node := range rns {
+ if summary, ok := MakeNodeSummary(node); ok {
+ for i, m := range summary.Metrics {
+ summary.Metrics[i] = m.Summary()
+ }
+ result[id] = summary
+ }
+ }
+ return result
+}
+
+// Copy returns a deep value-copy of NodeSummaries
+func (n NodeSummaries) Copy() NodeSummaries {
+ result := NodeSummaries{}
+ for k, v := range n {
+ result[k] = v.Copy()
+ }
+ return result
+}
+
+// getRenderableContainerName obtains a user-friendly container name, to render in the UI
+func getRenderableContainerName(nmd report.Node) string {
+ for _, key := range []string{
+ // Amazon's ecs-agent produces huge Docker container names, destructively
+ // derived from mangling Container Definition names in Task
+ // Definitions.
+ //
+ // However, the ecs-agent provides a label containing the original Container
+ // Definition name.
+ docker.LabelPrefix + AmazonECSContainerNameLabel,
+ // Kubernetes also mangles its Docker container names and provides a
+ // label with the original container name. However, note that this label
+ // is only provided by Kubernetes versions >= 1.2 (see
+ // https://github.com/kubernetes/kubernetes/pull/17234/ )
+ docker.LabelPrefix + KubernetesContainerNameLabel,
+ docker.ContainerName,
+ docker.ContainerHostname,
+ } {
+ if label, ok := nmd.Latest.Lookup(key); ok {
+ return label
+ }
+ }
+ return ""
+}
diff --git a/render/detailed/summary_test.go b/render/detailed/summary_test.go
new file mode 100644
index 0000000000..db1c362a41
--- /dev/null
+++ b/render/detailed/summary_test.go
@@ -0,0 +1,186 @@
+package detailed_test
+
+import (
+ "sort"
+ "testing"
+ "time"
+
+ "github.com/weaveworks/scope/common/mtime"
+ "github.com/weaveworks/scope/probe/docker"
+ "github.com/weaveworks/scope/probe/host"
+ "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/report"
+ "github.com/weaveworks/scope/test"
+ "github.com/weaveworks/scope/test/fixture"
+ "github.com/weaveworks/scope/test/reflect"
+)
+
+func TestSummaries(t *testing.T) {
+ {
+ // Just a convenient source of some rendered nodes
+ have := detailed.Summaries(render.ProcessRenderer.Render(fixture.Report))
+ // The ids of the processes rendered above
+ expectedIDs := []string{
+ fixture.ClientProcess1NodeID,
+ fixture.ClientProcess2NodeID,
+ fixture.ServerProcessNodeID,
+ fixture.NonContainerProcessNodeID,
+ expected.UnknownPseudoNode1ID,
+ expected.UnknownPseudoNode2ID,
+ render.IncomingInternetID,
+ render.OutgoingInternetID,
+ }
+ sort.Strings(expectedIDs)
+
+ // It should summarize each node
+ ids := []string{}
+ for id := range have {
+ ids = append(ids, id)
+ }
+ sort.Strings(ids)
+ if !reflect.DeepEqual(expectedIDs, ids) {
+ t.Fatalf("Expected Summaries to have summarized every node in the process renderer: %v, but got %v", expectedIDs, ids)
+ }
+ }
+
+ // It should summarize nodes' metrics
+ {
+ t1, t2 := mtime.Now().Add(-1*time.Minute), mtime.Now()
+ metric := report.MakeMetric().Add(t1, 1).Add(t2, 2)
+ input := fixture.Report.Copy()
+
+ input.Process.Nodes[fixture.ClientProcess1NodeID] = input.Process.Nodes[fixture.ClientProcess1NodeID].WithMetrics(report.Metrics{process.CPUUsage: metric})
+ have := detailed.Summaries(render.ProcessRenderer.Render(input))
+
+ node, ok := have[fixture.ClientProcess1NodeID]
+ if !ok {
+ t.Fatalf("Expected output to have the node we added the metric to")
+ }
+
+ var row detailed.MetricRow
+ ok = false
+ for _, metric := range node.Metrics {
+ if metric.ID == process.CPUUsage {
+ row = metric
+ ok = true
+ break
+ }
+ }
+ if !ok {
+ t.Fatalf("Expected node to have the metric we added")
+ }
+
+ // Our summarized MetricRow
+ want := detailed.MetricRow{
+ ID: process.CPUUsage,
+ Format: "percent",
+ Value: 2,
+ Metric: &report.Metric{
+ Samples: nil,
+ Min: metric.Min,
+ Max: metric.Max,
+ First: metric.First,
+ Last: metric.Last,
+ },
+ }
+ if !reflect.DeepEqual(want, row) {
+ t.Fatalf("Expected to have summarized the node's metrics: %s", test.Diff(want, row))
+ }
+ }
+}
+
+func TestMakeNodeSummary(t *testing.T) {
+ testcases := []struct {
+ name string
+ input report.Node
+ ok bool
+ want detailed.NodeSummary
+ }{
+ {
+ name: "single process rendering",
+ input: expected.RenderedProcesses[fixture.ClientProcess1NodeID],
+ ok: true,
+ want: detailed.NodeSummary{
+ ID: fixture.ClientProcess1NodeID,
+ Label: fixture.Client1Name,
+ LabelMinor: "client.hostname.com (10001)",
+ Rank: fixture.Client1Name,
+ Shape: "square",
+ Metadata: []detailed.MetadataRow{
+ {ID: process.PID, Value: fixture.Client1PID, Prime: true, Datatype: "number"},
+ },
+ Metrics: []detailed.MetricRow{},
+ Adjacency: report.MakeIDList(fixture.ServerProcessNodeID),
+ },
+ },
+ {
+ name: "single container rendering",
+ input: expected.RenderedContainers[fixture.ClientContainerNodeID],
+ ok: true,
+ want: detailed.NodeSummary{
+ ID: fixture.ClientContainerNodeID,
+ Label: fixture.ClientContainerName,
+ LabelMinor: fixture.ClientHostName,
+ Rank: fixture.ClientContainerImageName,
+ Shape: "hexagon",
+ Linkable: true,
+ Metadata: []detailed.MetadataRow{
+ {ID: docker.ContainerID, Value: fixture.ClientContainerID, Prime: true},
+ },
+ Metrics: []detailed.MetricRow{},
+ Adjacency: report.MakeIDList(fixture.ServerContainerNodeID),
+ },
+ },
+ {
+ name: "single container image rendering",
+ input: expected.RenderedContainerImages[fixture.ClientContainerImageNodeID],
+ ok: true,
+ want: detailed.NodeSummary{
+ ID: fixture.ClientContainerImageNodeID,
+ Label: fixture.ClientContainerImageName,
+ LabelMinor: "1 container",
+ Rank: fixture.ClientContainerImageName,
+ Shape: "hexagon",
+ Linkable: true,
+ Stack: true,
+ Metadata: []detailed.MetadataRow{
+ {ID: docker.ImageID, Value: fixture.ClientContainerImageID, Prime: true},
+ {ID: report.Container, Value: "1", Prime: true, Datatype: "number"},
+ },
+ Adjacency: report.MakeIDList(fixture.ServerContainerImageNodeID),
+ },
+ },
+ {
+ name: "single host rendering",
+ input: expected.RenderedHosts[fixture.ClientHostNodeID],
+ ok: true,
+ want: detailed.NodeSummary{
+ ID: fixture.ClientHostNodeID,
+ Label: "client",
+ LabelMinor: "hostname.com",
+ Rank: "hostname.com",
+ Shape: "circle",
+ Linkable: true,
+ Metadata: []detailed.MetadataRow{
+ {ID: host.HostName, Value: fixture.ClientHostName, Prime: false},
+ },
+ Metrics: []detailed.MetricRow{},
+ Adjacency: report.MakeIDList(fixture.ServerHostNodeID),
+ },
+ },
+ }
+ for _, testcase := range testcases {
+ have, ok := detailed.MakeNodeSummary(testcase.input)
+ if ok != testcase.ok {
+ t.Errorf("%s: MakeNodeSummary failed: expected ok value to be: %v", testcase.name, testcase.ok)
+ continue
+ }
+
+ if !reflect.DeepEqual(testcase.want, have) {
+ t.Errorf("%s: Node Summary did not match: %s", testcase.name, test.Diff(testcase.want, have))
+ }
+ }
+}
diff --git a/render/topology_diff.go b/render/detailed/topology_diff.go
similarity index 74%
rename from render/topology_diff.go
rename to render/detailed/topology_diff.go
index 9e0c552534..66836d5b89 100644
--- a/render/topology_diff.go
+++ b/render/detailed/topology_diff.go
@@ -1,19 +1,19 @@
-package render
+package detailed
import (
"reflect"
)
// Diff is returned by TopoDiff. It represents the changes between two
-// RenderableNode maps.
+// NodeSummary maps.
type Diff struct {
- Add []RenderableNode `json:"add"`
- Update []RenderableNode `json:"update"`
- Remove []string `json:"remove"`
+ Add []NodeSummary `json:"add"`
+ Update []NodeSummary `json:"update"`
+ Remove []string `json:"remove"`
}
// TopoDiff gives you the diff to get from A to B.
-func TopoDiff(a, b RenderableNodes) Diff {
+func TopoDiff(a, b NodeSummaries) Diff {
diff := Diff{}
notSeen := map[string]struct{}{}
diff --git a/render/detailed/topology_diff_test.go b/render/detailed/topology_diff_test.go
new file mode 100644
index 0000000000..dfdc360cda
--- /dev/null
+++ b/render/detailed/topology_diff_test.go
@@ -0,0 +1,90 @@
+package detailed_test
+
+import (
+ "reflect"
+ "sort"
+ "testing"
+
+ "github.com/weaveworks/scope/render/detailed"
+ "github.com/weaveworks/scope/report"
+ "github.com/weaveworks/scope/test"
+)
+
+// ByID is a sort interface for a NodeSummary slice.
+type ByID []detailed.NodeSummary
+
+func (r ByID) Len() int { return len(r) }
+func (r ByID) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
+func (r ByID) Less(i, j int) bool { return r[i].ID < r[j].ID }
+
+func TestTopoDiff(t *testing.T) {
+ nodea := detailed.NodeSummary{
+ ID: "nodea",
+ Label: "Node A",
+ LabelMinor: "'ts an a",
+ Pseudo: false,
+ Adjacency: report.MakeIDList("nodeb"),
+ }
+ nodeap := nodea.Copy()
+ nodeap.Adjacency = report.MakeIDList("nodeb", "nodeq") // not the same anymore
+ nodeb := detailed.NodeSummary{
+ ID: "nodeb",
+ Label: "Node B",
+ }
+
+ // Helper to make RenderableNode maps.
+ nodes := func(ns ...detailed.NodeSummary) detailed.NodeSummaries {
+ r := detailed.NodeSummaries{}
+ for _, n := range ns {
+ r[n.ID] = n
+ }
+ return r
+ }
+
+ for _, c := range []struct {
+ label string
+ have, want detailed.Diff
+ }{
+ {
+ label: "basecase: empty -> something",
+ have: detailed.TopoDiff(nodes(), nodes(nodea, nodeb)),
+ want: detailed.Diff{
+ Add: []detailed.NodeSummary{nodea, nodeb},
+ },
+ },
+ {
+ label: "basecase: something -> empty",
+ have: detailed.TopoDiff(nodes(nodea, nodeb), nodes()),
+ want: detailed.Diff{
+ Remove: []string{"nodea", "nodeb"},
+ },
+ },
+ {
+ label: "add and remove",
+ have: detailed.TopoDiff(nodes(nodea), nodes(nodeb)),
+ want: detailed.Diff{
+ Add: []detailed.NodeSummary{nodeb},
+ Remove: []string{"nodea"},
+ },
+ },
+ {
+ label: "no change",
+ have: detailed.TopoDiff(nodes(nodea), nodes(nodea)),
+ want: detailed.Diff{},
+ },
+ {
+ label: "change a single node",
+ have: detailed.TopoDiff(nodes(nodea), nodes(nodeap)),
+ want: detailed.Diff{
+ Update: []detailed.NodeSummary{nodeap},
+ },
+ },
+ } {
+ sort.Strings(c.have.Remove)
+ sort.Sort(ByID(c.have.Add))
+ sort.Sort(ByID(c.have.Update))
+ if !reflect.DeepEqual(c.want, c.have) {
+ t.Errorf("%s - %s", c.label, test.Diff(c.want, c.have))
+ }
+ }
+}
diff --git a/render/expected/expected.go b/render/expected/expected.go
index 20f096eecf..fe7d60c0ea 100644
--- a/render/expected/expected.go
+++ b/render/expected/expected.go
@@ -1,8 +1,9 @@
package expected
import (
- "fmt"
-
+ "github.com/weaveworks/scope/probe/docker"
+ "github.com/weaveworks/scope/probe/host"
+ "github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test/fixture"
@@ -16,566 +17,260 @@ var (
hexagon = "hexagon"
cloud = "cloud"
- 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)
- unknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP)
-
- unknownPseudoNode1 = func(adjacent string) render.RenderableNode {
- return render.RenderableNode{
- ID: unknownPseudoNode1ID,
- 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),
- },
+ // Helper to make a report.node with some common options
+ node = func(topology string) func(id string, adjacent ...string) report.Node {
+ return func(id string, adjacent ...string) report.Node {
+ n := report.MakeNode().WithID(id).WithTopology(topology)
+ for _, a := range adjacent {
+ n = n.WithAdjacent(a)
+ }
+ return n
}
}
- unknownPseudoNode2 = func(adjacent string) render.RenderableNode {
- return render.RenderableNode{
- ID: unknownPseudoNode2ID,
- 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),
- },
- }
+ pseudo = node(render.Pseudo)
+ endpoint = node(report.Endpoint)
+ processNode = node(report.Process)
+ container = node(report.Container)
+ containerImage = node(report.ContainerImage)
+ pod = node(report.Pod)
+ service = node(report.Service)
+ hostNode = node(report.Host)
+
+ UnknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP)
+ UnknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP)
+
+ unknownPseudoNode1 = func(adjacent ...string) report.Node {
+ return pseudo(UnknownPseudoNode1ID, adjacent...).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.UnknownClient1NodeID],
+ RenderedEndpoints[fixture.UnknownClient2NodeID],
+ ))
}
- theIncomingInternetNode = func(adjacent string) render.RenderableNode {
- return render.RenderableNode{
- ID: render.IncomingInternetID,
- LabelMajor: render.InboundMajor,
- 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),
- },
- }
+ unknownPseudoNode2 = func(adjacent ...string) report.Node {
+ return pseudo(UnknownPseudoNode2ID, adjacent...).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.UnknownClient3NodeID],
+ ))
}
- theOutgoingInternetNode = render.RenderableNode{
- ID: render.OutgoingInternetID,
- LabelMajor: render.OutboundMajor,
- LabelMinor: render.OutboundMinor,
- Pseudo: true,
- Shape: cloud,
- Node: report.MakeNode(),
- EdgeMetadata: report.EdgeMetadata{},
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[GoogleEndpointID],
- ),
+
+ theIncomingInternetNode = func(adjacent ...string) report.Node {
+ return pseudo(render.IncomingInternetID, adjacent...).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.RandomClientNodeID],
+ ))
}
- RenderedProcesses = (render.RenderableNodes{
- ClientProcess1ID: {
- ID: ClientProcess1ID,
- LabelMajor: fixture.Client1Name,
- LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client1PID),
- Rank: fixture.Client1Name,
- Shape: square,
- Node: report.MakeNode().WithAdjacent(ServerProcessID),
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[Client54001EndpointID],
- ),
- EdgeMetadata: report.EdgeMetadata{
- EgressPacketCount: newu64(10),
- EgressByteCount: newu64(100),
- },
- },
- ClientProcess2ID: {
- ID: ClientProcess2ID,
- LabelMajor: fixture.Client2Name,
- LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client2PID),
- Rank: fixture.Client2Name,
- Shape: square,
- Node: report.MakeNode().WithAdjacent(ServerProcessID),
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[Client54002EndpointID],
- ),
- EdgeMetadata: report.EdgeMetadata{
- EgressPacketCount: newu64(20),
- EgressByteCount: newu64(200),
- },
- },
- ServerProcessID: {
- ID: ServerProcessID,
- LabelMajor: fixture.ServerName,
- LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.ServerPID),
- Rank: fixture.ServerName,
- 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,
- Shape: square,
- Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[NonContainerEndpointID],
- ),
- EdgeMetadata: report.EdgeMetadata{},
- },
- unknownPseudoNode1ID: unknownPseudoNode1(ServerProcessID),
- unknownPseudoNode2ID: unknownPseudoNode2(ServerProcessID),
- render.IncomingInternetID: theIncomingInternetNode(ServerProcessID),
+ theOutgoingInternetNode = pseudo(render.OutgoingInternetID).WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.GoogleEndpointNodeID],
+ ))
+
+ RenderedEndpoints = report.Nodes{
+ fixture.Client54001NodeID: endpoint(fixture.Client54001NodeID, fixture.Server80NodeID),
+ fixture.Client54002NodeID: endpoint(fixture.Client54002NodeID, fixture.Server80NodeID),
+ fixture.Server80NodeID: endpoint(fixture.Server80NodeID),
+ fixture.UnknownClient1NodeID: endpoint(fixture.UnknownClient1NodeID, fixture.Server80NodeID),
+ fixture.UnknownClient2NodeID: endpoint(fixture.UnknownClient2NodeID, fixture.Server80NodeID),
+ fixture.UnknownClient3NodeID: endpoint(fixture.UnknownClient3NodeID, fixture.Server80NodeID),
+ fixture.RandomClientNodeID: endpoint(fixture.RandomClientNodeID, fixture.Server80NodeID),
+ fixture.NonContainerNodeID: endpoint(fixture.NonContainerNodeID, fixture.GoogleEndpointNodeID),
+ fixture.GoogleEndpointNodeID: endpoint(fixture.GoogleEndpointNodeID),
+ }
+
+ RenderedProcesses = report.Nodes{
+ fixture.ClientProcess1NodeID: processNode(fixture.ClientProcess1NodeID, fixture.ServerProcessNodeID).
+ WithLatests(map[string]string{
+ report.HostNodeID: fixture.ClientHostNodeID,
+ process.PID: fixture.Client1PID,
+ process.Name: fixture.Client1Name,
+ }).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Client54001NodeID],
+ )),
+
+ fixture.ClientProcess2NodeID: processNode(fixture.ClientProcess2NodeID, fixture.ServerProcessNodeID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Client54002NodeID],
+ )),
+
+ fixture.ServerProcessNodeID: processNode(fixture.ServerProcessNodeID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Server80NodeID],
+ )),
+
+ fixture.NonContainerProcessNodeID: processNode(fixture.NonContainerProcessNodeID, render.OutgoingInternetID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.NonContainerNodeID],
+ )),
+
+ UnknownPseudoNode1ID: unknownPseudoNode1(fixture.ServerProcessNodeID),
+ UnknownPseudoNode2ID: unknownPseudoNode2(fixture.ServerProcessNodeID),
+
+ render.IncomingInternetID: theIncomingInternetNode(fixture.ServerProcessNodeID),
render.OutgoingInternetID: theOutgoingInternetNode,
- }).Prune()
-
- RenderedProcessNames = (render.RenderableNodes{
- fixture.Client1Name: {
- ID: fixture.Client1Name,
- LabelMajor: fixture.Client1Name,
- LabelMinor: "2 processes",
- Rank: fixture.Client1Name,
- Shape: square,
- Stack: true,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[Client54001EndpointID],
- RenderedEndpoints[Client54002EndpointID],
- RenderedProcesses[ClientProcess1ID],
- RenderedProcesses[ClientProcess2ID],
- ),
- Node: report.MakeNode().WithAdjacent(fixture.ServerName),
- EdgeMetadata: report.EdgeMetadata{
- EgressPacketCount: newu64(30),
- EgressByteCount: newu64(300),
- },
- },
- fixture.ServerName: {
- ID: fixture.ServerName,
- LabelMajor: fixture.ServerName,
- LabelMinor: "1 process",
- Rank: fixture.ServerName,
- Shape: square,
- Stack: true,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[ServerEndpointID],
- RenderedProcesses[ServerProcessID],
- ),
- Node: report.MakeNode(),
- EdgeMetadata: report.EdgeMetadata{
- IngressPacketCount: newu64(210),
- IngressByteCount: newu64(2100),
- },
- },
- fixture.NonContainerName: {
- ID: fixture.NonContainerName,
- LabelMajor: fixture.NonContainerName,
- LabelMinor: "1 process",
- Rank: fixture.NonContainerName,
- Shape: square,
- Stack: true,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[NonContainerEndpointID],
- RenderedProcesses[nonContainerProcessID],
- ),
- Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
- EdgeMetadata: report.EdgeMetadata{},
- },
- unknownPseudoNode1ID: unknownPseudoNode1(fixture.ServerName),
- unknownPseudoNode2ID: unknownPseudoNode2(fixture.ServerName),
+ }
+
+ RenderedProcessNames = report.Nodes{
+ fixture.Client1Name: processNode(fixture.Client1Name, fixture.ServerName).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Client54001NodeID],
+ RenderedEndpoints[fixture.Client54002NodeID],
+ RenderedProcesses[fixture.ClientProcess1NodeID],
+ RenderedProcesses[fixture.ClientProcess2NodeID],
+ )),
+
+ fixture.ServerName: processNode(fixture.ServerName).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Server80NodeID],
+ RenderedProcesses[fixture.ServerProcessNodeID],
+ )),
+
+ fixture.NonContainerName: processNode(fixture.NonContainerName, render.OutgoingInternetID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.NonContainerNodeID],
+ RenderedProcesses[fixture.NonContainerProcessNodeID],
+ )),
+
+ UnknownPseudoNode1ID: unknownPseudoNode1(fixture.ServerName),
+ UnknownPseudoNode2ID: unknownPseudoNode2(fixture.ServerName),
render.IncomingInternetID: theIncomingInternetNode(fixture.ServerName),
render.OutgoingInternetID: theOutgoingInternetNode,
- }).Prune()
-
- ClientContainerID = render.MakeContainerID(fixture.ClientContainerID)
- ServerContainerID = render.MakeContainerID(fixture.ServerContainerID)
- uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostID)
-
- RenderedContainers = (render.RenderableNodes{
- ClientContainerID: {
- ID: ClientContainerID,
- LabelMajor: "client",
- LabelMinor: fixture.ClientHostName,
- Shape: hexagon,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[Client54001EndpointID],
- RenderedEndpoints[Client54002EndpointID],
- RenderedProcesses[ClientProcess1ID],
- RenderedProcesses[ClientProcess2ID],
- ),
- Node: report.MakeNode().WithAdjacent(ServerContainerID),
- EdgeMetadata: report.EdgeMetadata{
- EgressPacketCount: newu64(30),
- EgressByteCount: newu64(300),
- },
- ControlNode: fixture.ClientContainerNodeID,
- },
- ServerContainerID: {
- ID: ServerContainerID,
- LabelMajor: "server",
- LabelMinor: fixture.ServerHostName,
- Shape: hexagon,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[ServerEndpointID],
- RenderedProcesses[ServerProcessID],
- ),
- Node: report.MakeNode(),
- EdgeMetadata: report.EdgeMetadata{
- IngressPacketCount: newu64(210),
- IngressByteCount: newu64(2100),
- },
- ControlNode: fixture.ServerContainerNodeID,
- },
- uncontainedServerID: {
- ID: uncontainedServerID,
- LabelMajor: render.UncontainedMajor,
- LabelMinor: fixture.ServerHostName,
- Shape: square,
- Stack: true,
- Pseudo: true,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[NonContainerEndpointID],
- RenderedProcesses[nonContainerProcessID],
- ),
- Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
- EdgeMetadata: report.EdgeMetadata{},
- },
- // unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerID),
- // unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerID),
- render.IncomingInternetID: theIncomingInternetNode(ServerContainerID),
+ }
+
+ uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostID)
+ uncontainedServerNode = pseudo(uncontainedServerID, render.OutgoingInternetID).WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.NonContainerNodeID],
+ RenderedProcesses[fixture.NonContainerProcessNodeID],
+ ))
+
+ RenderedContainers = report.Nodes{
+ fixture.ClientContainerNodeID: container(fixture.ClientContainerNodeID, fixture.ServerContainerNodeID).
+ WithLatests(map[string]string{
+ report.HostNodeID: fixture.ClientHostNodeID,
+ docker.ContainerID: fixture.ClientContainerID,
+ docker.ContainerName: fixture.ClientContainerName,
+ docker.ImageName: fixture.ClientContainerImageName,
+ }).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Client54001NodeID],
+ RenderedEndpoints[fixture.Client54002NodeID],
+ RenderedProcesses[fixture.ClientProcess1NodeID],
+ RenderedProcesses[fixture.ClientProcess2NodeID],
+ )),
+
+ fixture.ServerContainerNodeID: container(fixture.ServerContainerNodeID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Server80NodeID],
+ RenderedProcesses[fixture.ServerProcessNodeID],
+ )),
+
+ uncontainedServerID: uncontainedServerNode,
+ render.IncomingInternetID: theIncomingInternetNode(fixture.ServerContainerNodeID),
render.OutgoingInternetID: theOutgoingInternetNode,
- }).Prune()
-
- ClientContainerImageID = render.MakeContainerImageID(fixture.ClientContainerImageName)
- ServerContainerImageID = render.MakeContainerImageID(fixture.ServerContainerImageName)
-
- RenderedContainerImages = (render.RenderableNodes{
- ClientContainerImageID: {
- ID: ClientContainerImageID,
- LabelMajor: fixture.ClientContainerImageName,
- LabelMinor: "1 container",
- Rank: fixture.ClientContainerImageName,
- Shape: hexagon,
- Stack: true,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[Client54001EndpointID],
- RenderedEndpoints[Client54002EndpointID],
- RenderedProcesses[ClientProcess1ID],
- RenderedProcesses[ClientProcess2ID],
- RenderedContainers[ClientContainerID],
- ),
- Node: report.MakeNode().WithAdjacent(ServerContainerImageID),
- EdgeMetadata: report.EdgeMetadata{
- EgressPacketCount: newu64(30),
- EgressByteCount: newu64(300),
- },
- },
- ServerContainerImageID: {
- ID: ServerContainerImageID,
- LabelMajor: fixture.ServerContainerImageName,
- LabelMinor: "1 container",
- Rank: fixture.ServerContainerImageName,
- Shape: hexagon,
- Stack: true,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[ServerEndpointID],
- RenderedProcesses[ServerProcessID],
- RenderedContainers[ServerContainerID],
- ),
- Node: report.MakeNode(),
- EdgeMetadata: report.EdgeMetadata{
- IngressPacketCount: newu64(210),
- IngressByteCount: newu64(2100),
- },
- },
- uncontainedServerID: {
- ID: uncontainedServerID,
- LabelMajor: render.UncontainedMajor,
- LabelMinor: fixture.ServerHostName,
- Shape: square,
- Stack: true,
- Pseudo: true,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[NonContainerEndpointID],
- RenderedProcesses[nonContainerProcessID],
- ),
- Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
- EdgeMetadata: report.EdgeMetadata{},
- },
- // unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerImageID),
- // unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerImageID),
- render.IncomingInternetID: theIncomingInternetNode(ServerContainerImageID),
+ }
+
+ RenderedContainerImages = report.Nodes{
+ fixture.ClientContainerImageNodeID: containerImage(fixture.ClientContainerImageNodeID, fixture.ServerContainerImageNodeID).
+ WithLatests(map[string]string{
+ report.HostNodeID: fixture.ClientHostNodeID,
+ docker.ImageID: fixture.ClientContainerImageID,
+ docker.ImageName: fixture.ClientContainerImageName,
+ }).
+ WithCounters(map[string]int{
+ report.Container: 1,
+ }).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Client54001NodeID],
+ RenderedEndpoints[fixture.Client54002NodeID],
+ RenderedProcesses[fixture.ClientProcess1NodeID],
+ RenderedProcesses[fixture.ClientProcess2NodeID],
+ RenderedContainers[fixture.ClientContainerNodeID],
+ )),
+
+ fixture.ServerContainerImageNodeID: containerImage(fixture.ServerContainerImageNodeID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Server80NodeID],
+ RenderedProcesses[fixture.ServerProcessNodeID],
+ RenderedContainers[fixture.ServerContainerNodeID],
+ )),
+
+ uncontainedServerID: uncontainedServerNode,
+ render.IncomingInternetID: theIncomingInternetNode(fixture.ServerContainerImageNodeID),
render.OutgoingInternetID: theOutgoingInternetNode,
- }).Prune()
-
- ClientPodRenderedID = render.MakePodID("ping/pong-a")
- ServerPodRenderedID = render.MakePodID("ping/pong-b")
-
- RenderedPods = (render.RenderableNodes{
- ClientPodRenderedID: {
- ID: ClientPodRenderedID,
- LabelMajor: "pong-a",
- LabelMinor: "1 container",
- Rank: "ping/pong-a",
- Shape: heptagon,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[Client54001EndpointID],
- RenderedEndpoints[Client54002EndpointID],
- RenderedProcesses[ClientProcess1ID],
- RenderedProcesses[ClientProcess2ID],
- RenderedContainers[ClientContainerID],
- ),
- Node: report.MakeNode().WithAdjacent(ServerPodRenderedID),
- EdgeMetadata: report.EdgeMetadata{
- EgressPacketCount: newu64(30),
- EgressByteCount: newu64(300),
- },
- },
- ServerPodRenderedID: {
- ID: ServerPodRenderedID,
- LabelMajor: "pong-b",
- LabelMinor: "1 container",
- Rank: "ping/pong-b",
- Shape: heptagon,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[ServerEndpointID],
- RenderedProcesses[ServerProcessID],
- RenderedContainers[ServerContainerID],
- ),
- Node: report.MakeNode(),
- EdgeMetadata: report.EdgeMetadata{
- IngressPacketCount: newu64(210),
- IngressByteCount: newu64(2100),
- },
- },
- uncontainedServerID: {
- ID: uncontainedServerID,
- LabelMajor: render.UncontainedMajor,
- LabelMinor: fixture.ServerHostName,
- Pseudo: true,
- Shape: square,
- Stack: true,
- 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),
+ }
+
+ RenderedHosts = report.Nodes{
+ fixture.ClientHostNodeID: hostNode(fixture.ClientHostNodeID, fixture.ServerHostNodeID).
+ WithLatests(map[string]string{
+ host.HostName: fixture.ClientHostName,
+ }).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Client54001NodeID],
+ RenderedEndpoints[fixture.Client54002NodeID],
+ RenderedProcesses[fixture.ClientProcess1NodeID],
+ RenderedProcesses[fixture.ClientProcess2NodeID],
+ RenderedContainers[fixture.ClientContainerNodeID],
+ RenderedContainerImages[fixture.ClientContainerImageNodeID],
+ //RenderedPods[fixture.ClientPodNodeID], #1142
+ )),
+
+ fixture.ServerHostNodeID: hostNode(fixture.ServerHostNodeID, render.OutgoingInternetID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Server80NodeID],
+ RenderedEndpoints[fixture.NonContainerNodeID],
+ RenderedProcesses[fixture.ServerProcessNodeID],
+ RenderedProcesses[fixture.NonContainerProcessNodeID],
+ RenderedContainers[fixture.ServerContainerNodeID],
+ RenderedContainerImages[fixture.ServerContainerImageNodeID],
+ //RenderedPods[fixture.ServerPodNodeID], #1142
+ )),
+
+ UnknownPseudoNode1ID: unknownPseudoNode1(fixture.ServerHostNodeID),
+ UnknownPseudoNode2ID: unknownPseudoNode2(fixture.ServerHostNodeID),
+ render.IncomingInternetID: theIncomingInternetNode(fixture.ServerHostNodeID),
render.OutgoingInternetID: theOutgoingInternetNode,
- }).Prune()
-
- ServerHostID = render.MakeHostID(fixture.ServerHostID)
- ClientHostID = render.MakeHostID(fixture.ClientHostID)
-
- RenderedHosts = (render.RenderableNodes{
- ClientHostID: {
- ID: ClientHostID,
- LabelMajor: "client", // before first .
- LabelMinor: "hostname.com", // after first .
- Rank: "hostname.com",
- Shape: circle,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[Client54001EndpointID],
- RenderedEndpoints[Client54002EndpointID],
- RenderedProcesses[ClientProcess1ID],
- RenderedProcesses[ClientProcess2ID],
- RenderedContainers[ClientContainerID],
- RenderedContainerImages[ClientContainerImageID],
- //RenderedPods[ClientPodRenderedID], #1142
- ),
- Node: report.MakeNode().WithAdjacent(ServerHostID),
- EdgeMetadata: report.EdgeMetadata{
- EgressPacketCount: newu64(30),
- EgressByteCount: newu64(300),
- },
- },
- ServerHostID: {
- ID: ServerHostID,
- LabelMajor: "server", // before first .
- LabelMinor: "hostname.com", // after first .
- Rank: "hostname.com",
- Shape: circle,
- Children: render.MakeRenderableNodeSet(
- RenderedEndpoints[ServerEndpointID],
- RenderedEndpoints[NonContainerEndpointID],
- RenderedProcesses[ServerProcessID],
- RenderedProcesses[nonContainerProcessID],
- RenderedContainers[ServerContainerID],
- RenderedContainerImages[ServerContainerImageID],
- //RenderedPods[ServerPodRenderedID], #1142
- ),
- Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
- EdgeMetadata: report.EdgeMetadata{
- IngressPacketCount: newu64(210),
- IngressByteCount: newu64(2100),
- },
- },
- unknownPseudoNode1ID: unknownPseudoNode1(ServerHostID),
- unknownPseudoNode2ID: unknownPseudoNode2(ServerHostID),
- render.IncomingInternetID: theIncomingInternetNode(ServerHostID),
+ }
+
+ RenderedPods = report.Nodes{
+ fixture.ClientPodNodeID: pod(fixture.ClientPodNodeID, fixture.ServerPodNodeID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Client54001NodeID],
+ RenderedEndpoints[fixture.Client54002NodeID],
+ RenderedProcesses[fixture.ClientProcess1NodeID],
+ RenderedProcesses[fixture.ClientProcess2NodeID],
+ RenderedContainers[fixture.ClientContainerNodeID],
+ )),
+
+ fixture.ServerPodNodeID: pod(fixture.ServerPodNodeID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Server80NodeID],
+ RenderedProcesses[fixture.ServerProcessNodeID],
+ RenderedContainers[fixture.ServerContainerNodeID],
+ )),
+
+ uncontainedServerID: uncontainedServerNode,
+ render.IncomingInternetID: theIncomingInternetNode(fixture.ServerPodNodeID),
render.OutgoingInternetID: theOutgoingInternetNode,
- }).Prune()
-
- ServiceRenderedID = render.MakeServiceID("ping/pongservice")
-
- RenderedPodServices = (render.RenderableNodes{
- ServiceRenderedID: {
- ID: ServiceRenderedID,
- LabelMajor: "pongservice",
- LabelMinor: "2 pods",
- Rank: fixture.ServiceID,
- Shape: heptagon,
- Stack: true,
- 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{
- EgressPacketCount: newu64(30),
- EgressByteCount: newu64(300),
- IngressPacketCount: newu64(210),
- IngressByteCount: newu64(2100),
- },
- },
- uncontainedServerID: {
- ID: uncontainedServerID,
- LabelMajor: render.UncontainedMajor,
- LabelMinor: fixture.ServerHostName,
- Pseudo: true,
- Shape: square,
- Stack: true,
- 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),
+ }
+
+ RenderedPodServices = report.Nodes{
+ fixture.ServiceNodeID: service(fixture.ServiceNodeID, fixture.ServiceNodeID).
+ WithChildren(report.MakeNodeSet(
+ RenderedEndpoints[fixture.Client54001NodeID],
+ RenderedEndpoints[fixture.Client54002NodeID],
+ RenderedEndpoints[fixture.Server80NodeID],
+ RenderedProcesses[fixture.ClientProcess1NodeID],
+ RenderedProcesses[fixture.ClientProcess2NodeID],
+ RenderedProcesses[fixture.ServerProcessNodeID],
+ RenderedContainers[fixture.ClientContainerNodeID],
+ RenderedContainers[fixture.ServerContainerNodeID],
+ RenderedPods[fixture.ClientPodNodeID],
+ RenderedPods[fixture.ServerPodNodeID],
+ )),
+
+ uncontainedServerID: uncontainedServerNode,
+ render.IncomingInternetID: theIncomingInternetNode(fixture.ServiceNodeID),
render.OutgoingInternetID: theOutgoingInternetNode,
- }).Prune()
+ }
)
func newu64(value uint64) *uint64 { return &value }
diff --git a/render/filters.go b/render/filters.go
index d918000660..27ed285a40 100644
--- a/render/filters.go
+++ b/render/filters.go
@@ -1,6 +1,12 @@
package render
import (
+ "strings"
+
+ "github.com/weaveworks/scope/common/mtime"
+ "github.com/weaveworks/scope/probe/docker"
+ "github.com/weaveworks/scope/probe/endpoint"
+ "github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/report"
)
@@ -8,12 +14,12 @@ import (
// in one call - useful for functions that need to consider the entire graph.
// We should minimise the use of this renderer type, as it is very inflexible.
type CustomRenderer struct {
- RenderFunc func(RenderableNodes) RenderableNodes
+ RenderFunc func(report.Nodes) report.Nodes
Renderer
}
// Render implements Renderer
-func (c CustomRenderer) Render(rpt report.Report) RenderableNodes {
+func (c CustomRenderer) Render(rpt report.Report) report.Nodes {
return c.RenderFunc(c.Renderer.Render(rpt))
}
@@ -23,7 +29,7 @@ func (c CustomRenderer) Render(rpt report.Report) RenderableNodes {
func ColorConnected(r Renderer) Renderer {
return CustomRenderer{
Renderer: r,
- RenderFunc: func(input RenderableNodes) RenderableNodes {
+ RenderFunc: func(input report.Nodes) report.Nodes {
connected := map[string]struct{}{}
void := struct{}{}
@@ -42,9 +48,7 @@ func ColorConnected(r Renderer) Renderer {
output := input.Copy()
for id := range connected {
- output[id] = output[id].WithNode(report.MakeNodeWith(map[string]string{
- IsConnected: "true",
- }))
+ output[id] = output[id].WithLatest(IsConnected, mtime.Now(), "true")
}
return output
},
@@ -54,22 +58,22 @@ func ColorConnected(r Renderer) Renderer {
// Filter removes nodes from a view based on a predicate.
type Filter struct {
Renderer
- FilterFunc func(RenderableNode) bool
+ FilterFunc func(report.Node) bool
}
// MakeFilter makes a new Filter.
-func MakeFilter(f func(RenderableNode) bool, r Renderer) Renderer {
+func MakeFilter(f func(report.Node) bool, r Renderer) Renderer {
return Memoise(&Filter{r, f})
}
// Render implements Renderer
-func (f *Filter) Render(rpt report.Report) RenderableNodes {
+func (f *Filter) Render(rpt report.Report) report.Nodes {
nodes, _ := f.render(rpt)
return nodes
}
-func (f *Filter) render(rpt report.Report) (RenderableNodes, int) {
- output := RenderableNodes{}
+func (f *Filter) render(rpt report.Report) (report.Nodes, int) {
+ output := report.Nodes{}
inDegrees := map[string]int{}
filtered := 0
for id, node := range f.Renderer.Render(rpt) {
@@ -100,7 +104,7 @@ func (f *Filter) render(rpt report.Report) (RenderableNodes, int) {
continue
}
node := output[id]
- if !node.Pseudo || len(node.Adjacency) > 0 {
+ if node.Topology != Pseudo || len(node.Adjacency) > 0 {
continue
}
delete(output, id)
@@ -123,16 +127,16 @@ const IsConnected = "is_connected"
// Complement takes a FilterFunc f and returns a FilterFunc that has the same
// effects, if any, and returns the opposite truth value.
-func Complement(f func(RenderableNode) bool) func(RenderableNode) bool {
- return func(node RenderableNode) bool { return !f(node) }
+func Complement(f func(report.Node) bool) func(report.Node) bool {
+ return func(node report.Node) bool { return !f(node) }
}
// FilterPseudo produces a renderer that removes pseudo nodes from the given
// renderer
func FilterPseudo(r Renderer) Renderer {
return MakeFilter(
- func(node RenderableNode) bool {
- return !node.Pseudo
+ func(node report.Node) bool {
+ return node.Topology != Pseudo
},
r,
)
@@ -142,7 +146,7 @@ func FilterPseudo(r Renderer) Renderer {
// from the given renderer
func FilterUnconnected(r Renderer) Renderer {
return MakeFilter(
- func(node RenderableNode) bool {
+ func(node report.Node) bool {
_, ok := node.Latest.Lookup(IsConnected)
return ok
},
@@ -157,22 +161,65 @@ func FilterNoop(in Renderer) Renderer {
// FilterStopped filters out stopped containers.
func FilterStopped(r Renderer) Renderer {
- return MakeFilter(RenderableNode.IsStopped, r)
+ return MakeFilter(IsStopped, r)
+}
+
+// IsStopped checks if the node is a stopped docker container
+func IsStopped(n report.Node) bool {
+ containerState, ok := n.Latest.Lookup(docker.ContainerState)
+ return !ok || containerState != docker.StateStopped
}
// FilterRunning filters out running containers.
func FilterRunning(r Renderer) Renderer {
- return MakeFilter(Complement(RenderableNode.IsStopped), r)
+ return MakeFilter(Complement(IsStopped), r)
+}
+
+// FilterNonProcspied removes endpoints which were not found in procspy.
+func FilterNonProcspied(r Renderer) Renderer {
+ return MakeFilter(
+ func(node report.Node) bool {
+ _, ok := node.Latest.Lookup(endpoint.Procspied)
+ return ok
+ },
+ r,
+ )
+}
+
+// IsSystem checks if the node is a "system" node
+func IsSystem(n report.Node) bool {
+ containerName, _ := n.Latest.Lookup(docker.ContainerName)
+ if _, ok := systemContainerNames[containerName]; ok {
+ return false
+ }
+ imageName, _ := n.Latest.Lookup(docker.ImageName)
+ imagePrefix := strings.SplitN(imageName, ":", 2)[0] // :(
+ if _, ok := systemImagePrefixes[imagePrefix]; ok {
+ return false
+ }
+ roleLabel, _ := n.Latest.Lookup(docker.LabelPrefix + "works.weave.role")
+ if roleLabel == "system" {
+ return false
+ }
+ namespace, _ := n.Latest.Lookup(kubernetes.Namespace)
+ if namespace == "kube-system" {
+ return false
+ }
+ podName, _ := n.Latest.Lookup(docker.LabelPrefix + "io.kubernetes.pod.name")
+ if strings.HasPrefix(podName, "kube-system/") {
+ return false
+ }
+ return true
}
// FilterSystem is a Renderer which filters out system nodes.
func FilterSystem(r Renderer) Renderer {
- return MakeFilter(RenderableNode.IsSystem, r)
+ return MakeFilter(IsSystem, r)
}
// FilterApplication is a Renderer which filters out system nodes.
func FilterApplication(r Renderer) Renderer {
- return MakeFilter(Complement(RenderableNode.IsSystem), r)
+ return MakeFilter(Complement(IsSystem), r)
}
var systemContainerNames = map[string]struct{}{
diff --git a/render/filters_test.go b/render/filters_test.go
index 70af0b88d0..e67e5fe9ab 100644
--- a/render/filters_test.go
+++ b/render/filters_test.go
@@ -11,16 +11,17 @@ import (
func TestFilterRender(t *testing.T) {
renderer := render.FilterUnconnected(
- mockRenderer{RenderableNodes: render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode().WithAdjacent("bar")},
- "bar": {ID: "bar", Node: report.MakeNode().WithAdjacent("foo")},
- "baz": {ID: "baz", Node: report.MakeNode()},
+ mockRenderer{Nodes: report.Nodes{
+ "foo": report.MakeNode().WithID("foo").WithAdjacent("bar"),
+ "bar": report.MakeNode().WithID("bar").WithAdjacent("foo"),
+ "baz": report.MakeNode().WithID("baz"),
}})
- want := render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode().WithAdjacent("bar")},
- "bar": {ID: "bar", Node: report.MakeNode().WithAdjacent("foo")},
+
+ have := report.MakeIDList()
+ for id := range renderer.Render(report.MakeReport()) {
+ have = have.Add(id)
}
- have := renderer.Render(report.MakeReport()).Prune()
+ want := report.MakeIDList("foo", "bar")
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -29,22 +30,19 @@ func TestFilterRender(t *testing.T) {
func TestFilterRender2(t *testing.T) {
// Test adjacencies are removed for filtered nodes.
renderer := render.Filter{
- FilterFunc: func(node render.RenderableNode) bool {
+ FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
- Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode().WithAdjacent("bar")},
- "bar": {ID: "bar", Node: report.MakeNode().WithAdjacent("foo")},
- "baz": {ID: "baz", Node: report.MakeNode()},
+ Renderer: mockRenderer{Nodes: report.Nodes{
+ "foo": report.MakeNode().WithID("foo").WithAdjacent("bar"),
+ "bar": report.MakeNode().WithID("bar").WithAdjacent("foo"),
+ "baz": report.MakeNode().WithID("baz"),
}},
}
- want := render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode()},
- "baz": {ID: "baz", Node: report.MakeNode()},
- }
- have := renderer.Render(report.MakeReport()).Prune()
- if !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
+
+ have := renderer.Render(report.MakeReport())
+ if have["foo"].Adjacency.Contains("bar") {
+ t.Error("adjacencies for removed nodes should have been removed")
}
}
@@ -52,59 +50,53 @@ func TestFilterUnconnectedPseudoNodes(t *testing.T) {
// Test pseudo nodes that are made unconnected by filtering
// are also removed.
{
- nodes := render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode().WithAdjacent("bar")},
- "bar": {ID: "bar", Node: report.MakeNode().WithAdjacent("baz")},
- "baz": {ID: "baz", Node: report.MakeNode(), Pseudo: true},
+ nodes := report.Nodes{
+ "foo": report.MakeNode().WithID("foo").WithAdjacent("bar"),
+ "bar": report.MakeNode().WithID("bar").WithAdjacent("baz"),
+ "baz": report.MakeNode().WithID("baz").WithTopology(render.Pseudo),
}
renderer := render.Filter{
- FilterFunc: func(node render.RenderableNode) bool {
+ FilterFunc: func(node report.Node) bool {
return true
},
- Renderer: mockRenderer{RenderableNodes: nodes},
+ Renderer: mockRenderer{Nodes: nodes},
}
- want := nodes.Prune()
- have := renderer.Render(report.MakeReport()).Prune()
+ want := nodes
+ have := renderer.Render(report.MakeReport())
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
{
renderer := render.Filter{
- FilterFunc: func(node render.RenderableNode) bool {
+ FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
- Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode().WithAdjacent("bar")},
- "bar": {ID: "bar", Node: report.MakeNode().WithAdjacent("baz")},
- "baz": {ID: "baz", Node: report.MakeNode(), Pseudo: true},
+ Renderer: mockRenderer{Nodes: report.Nodes{
+ "foo": report.MakeNode().WithID("foo").WithAdjacent("bar"),
+ "bar": report.MakeNode().WithID("bar").WithAdjacent("baz"),
+ "baz": report.MakeNode().WithID("baz").WithTopology(render.Pseudo),
}},
}
- want := render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode()},
- }
- have := renderer.Render(report.MakeReport()).Prune()
- if !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
+ have := renderer.Render(report.MakeReport())
+ if _, ok := have["baz"]; ok {
+ t.Error("expected the unconnected pseudonode baz to have been removed")
}
}
{
renderer := render.Filter{
- FilterFunc: func(node render.RenderableNode) bool {
+ FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
- Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode()},
- "bar": {ID: "bar", Node: report.MakeNode().WithAdjacent("foo")},
- "baz": {ID: "baz", Node: report.MakeNode().WithAdjacent("bar"), Pseudo: true},
+ Renderer: mockRenderer{Nodes: report.Nodes{
+ "foo": report.MakeNode().WithID("foo"),
+ "bar": report.MakeNode().WithID("bar").WithAdjacent("foo"),
+ "baz": report.MakeNode().WithID("baz").WithTopology(render.Pseudo).WithAdjacent("bar"),
}},
}
- want := render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode()},
- }
- have := renderer.Render(report.MakeReport()).Prune()
- if !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
+ have := renderer.Render(report.MakeReport())
+ if _, ok := have["baz"]; ok {
+ t.Error("expected the unconnected pseudonode baz to have been removed")
}
}
}
@@ -112,14 +104,13 @@ func TestFilterUnconnectedPseudoNodes(t *testing.T) {
func TestFilterUnconnectedSelf(t *testing.T) {
// Test nodes that are only connected to themselves are filtered.
{
- nodes := render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode().WithAdjacent("foo")},
+ nodes := report.Nodes{
+ "foo": report.MakeNode().WithID("foo").WithAdjacent("foo"),
}
- renderer := render.FilterUnconnected(mockRenderer{RenderableNodes: nodes})
- want := render.RenderableNodes{}
- have := renderer.Render(report.MakeReport()).Prune()
- if !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
+ renderer := render.FilterUnconnected(mockRenderer{Nodes: nodes})
+ have := renderer.Render(report.MakeReport())
+ if len(have) > 0 {
+ t.Error("expected node only connected to self to be removed")
}
}
}
@@ -127,17 +118,14 @@ func TestFilterUnconnectedSelf(t *testing.T) {
func TestFilterPseudo(t *testing.T) {
// Test pseudonodes are removed
{
- nodes := render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode()},
- "bar": {ID: "bar", Pseudo: true, Node: report.MakeNode()},
- }
- renderer := render.FilterPseudo(mockRenderer{RenderableNodes: nodes})
- want := render.RenderableNodes{
- "foo": {ID: "foo", Node: report.MakeNode()},
+ nodes := report.Nodes{
+ "foo": report.MakeNode().WithID("foo"),
+ "bar": report.MakeNode().WithID("bar").WithTopology(render.Pseudo),
}
- have := renderer.Render(report.MakeReport()).Prune()
- if !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
+ renderer := render.FilterPseudo(mockRenderer{Nodes: nodes})
+ have := renderer.Render(report.MakeReport())
+ if _, ok := have["bar"]; ok {
+ t.Error("expected pseudonode to be removed")
}
}
}
diff --git a/render/id.go b/render/id.go
index 47e98ec9b5..0ed8d49975 100644
--- a/render/id.go
+++ b/render/id.go
@@ -4,58 +4,7 @@ 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...), ":")
-}
-
-// MakeEndpointID makes an endpoint node ID for rendered nodes.
-func MakeEndpointID(hostID, addr, port string) string {
- return makeID("endpoint", hostID, addr, port)
-}
-
-// MakeProcessID makes a process node ID for rendered nodes.
-func MakeProcessID(hostID, pid string) string {
- return makeID("process", hostID, pid)
-}
-
-// MakeContainerID makes a container node ID for rendered nodes.
-func MakeContainerID(containerID string) string {
- return makeID("container", containerID)
-}
-
-// MakeContainerImageID makes a container image node ID for rendered nodes.
-func MakeContainerImageID(imageID string) string {
- return makeID("container_image", imageID)
-}
-
-// MakePodID makes a pod node ID for rendered nodes.
-func MakePodID(podID string) string {
- return makeID("pod", podID)
-}
-
-// MakeServiceID makes a service node ID for rendered nodes.
-func MakeServiceID(serviceID string) string {
- return makeID("service", serviceID)
-}
-
-// MakeHostID makes a host node ID for rendered nodes.
-func MakeHostID(hostID string) string {
- return makeID("host", hostID)
-}
-
-// MakePseudoNodeID produces a pseudo node ID from its composite parts,
-// for use in rendered nodes.
+// MakePseudoNodeID joins the parts of an id into the id of a pseudonode
func MakePseudoNodeID(parts ...string) string {
- return makeID("pseudo", parts...)
+ return strings.Join(append([]string{"pseudo"}, parts...), ":")
}
diff --git a/render/mapping.go b/render/mapping.go
index a449256fd8..ea1605bfe2 100644
--- a/render/mapping.go
+++ b/render/mapping.go
@@ -8,7 +8,6 @@ import (
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/endpoint"
- "github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
@@ -27,229 +26,53 @@ const (
InboundMinor = "Inbound connections"
OutboundMinor = "Outbound connections"
- ContainersKey = "containers"
- ipsKey = "ips"
- podsKey = "pods"
- processesKey = "processes"
- servicesKey = "services"
+ ipsKey = "ips"
- AmazonECSContainerNameLabel = "com.amazonaws.ecs.container-name"
- KubernetesContainerNameLabel = "io.kubernetes.container.name"
+ // Topology for pseudo-nodes and IPs so we can differentiate them at the end
+ Pseudo = "pseudo"
+ IP = "IP"
)
-// MapFunc is anything which can take an arbitrary RenderableNode and
-// return a set of other RenderableNodes.
+// MapFunc is anything which can take an arbitrary Node and
+// return a set of other Nodes.
//
// If the output is empty, the node shall be omitted from the rendered topology.
-type MapFunc func(RenderableNode, report.Networks) RenderableNodes
+type MapFunc func(report.Node, report.Networks) report.Nodes
-func theInternetNode(m RenderableNode) RenderableNode {
- node := newDerivedPseudoNode("", "", m)
- node.Shape = Cloud
- // emit one internet node for incoming, one for outgoing
- if len(m.Adjacency) > 0 {
- node.ID = IncomingInternetID
- node.LabelMajor = InboundMajor
- node.LabelMinor = InboundMinor
- } else {
- node.ID = OutgoingInternetID
- node.LabelMajor = OutboundMajor
- node.LabelMinor = OutboundMinor
- }
- return node
-}
-
-// 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{}
- }
-
- port, ok := m.Latest.Lookup(endpoint.Port)
- if !ok {
- return RenderableNodes{}
- }
-
- // We only show nodes found through procspy in this view.
- _, procspied := m.Latest.Lookup(endpoint.Procspied)
- if !procspied {
- return RenderableNodes{}
- }
-
- id := MakeEndpointID(report.ExtractHostID(m.Node), addr, port)
- return RenderableNodes{id: NewRenderableNodeWith(id, "", "", "", m)}
-}
-
-// MapProcessIdentity maps a process topology node to a process renderable
-// node. As it is only ever run on process topology nodes, we expect that
-// certain keys are present.
-func MapProcessIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
- pid, ok := m.Latest.Lookup(process.PID)
- if !ok {
- return RenderableNodes{}
- }
-
- var (
- id = MakeProcessID(report.ExtractHostID(m.Node), pid)
- major, _ = m.Latest.Lookup(process.Name)
- minor = fmt.Sprintf("%s (%s)", report.ExtractHostID(m.Node), pid)
- rank, _ = m.Latest.Lookup(process.Name)
- )
-
- node := NewRenderableNodeWith(id, major, minor, rank, m)
- node.Shape = Square
- return RenderableNodes{id: node}
+// NewDerivedNode makes a node based on node, but with a new ID
+func NewDerivedNode(id string, node report.Node) report.Node {
+ return node.WithID(id).WithChildren(report.MakeNodeSet(node)).PruneParents()
}
-// MapContainerIdentity maps a container topology node to a container
-// renderable node. As it is only ever run on container topology nodes, we
-// expect that certain keys are present.
-func MapContainerIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
- containerID, ok := m.Latest.Lookup(docker.ContainerID)
- if !ok {
- return RenderableNodes{}
- }
-
- var (
- id = MakeContainerID(containerID)
- major, _ = GetRenderableContainerName(m.Node)
- minor = report.ExtractHostID(m.Node)
- )
-
- node := NewRenderableNodeWith(id, major, minor, "", m)
- node.ControlNode = m.ID
- node.Shape = Hexagon
- return RenderableNodes{id: node}
-}
-
-// GetRenderableContainerName obtains a user-friendly container name, to render in the UI
-func GetRenderableContainerName(nmd report.Node) (string, bool) {
- // Amazon's ecs-agent produces huge Docker container names, destructively
- // derived from mangling Container Definition names in Task
- // Definitions.
- //
- // However, the ecs-agent provides a label containing the original Container
- // Definition name.
- if labelValue, ok := nmd.Latest.Lookup(docker.LabelPrefix + AmazonECSContainerNameLabel); ok {
- return labelValue, true
- }
-
- // Kubernetes also mangles its Docker container names and provides a
- // label with the original container name. However, note that this label
- // is only provided by Kubernetes versions >= 1.2 (see
- // https://github.com/kubernetes/kubernetes/pull/17234/ )
- if labelValue, ok := nmd.Latest.Lookup(docker.LabelPrefix + KubernetesContainerNameLabel); ok {
- return labelValue, true
- }
-
- name, ok := nmd.Latest.Lookup(docker.ContainerName)
- return name, ok
-}
-
-// MapContainerImageIdentity maps a container image topology node to container
-// image renderable node. As it is only ever run on container image topology
-// nodes, we expect that certain keys are present.
-func MapContainerImageIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
- imageID, ok := m.Latest.Lookup(docker.ImageID)
- if !ok {
- return RenderableNodes{}
- }
-
- var (
- id = MakeContainerImageID(imageID)
- major, _ = m.Latest.Lookup(docker.ImageName)
- rank = imageID
- )
-
- node := NewRenderableNodeWith(id, major, "", rank, m)
- node.Shape = Hexagon
- node.Stack = true
- return RenderableNodes{id: node}
-}
-
-// MapPodIdentity maps a pod topology node to pod renderable node. As it is
-// only ever run on pod topology nodes, we expect that certain keys
-// are present.
-func MapPodIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
- podID, ok := m.Latest.Lookup(kubernetes.PodID)
- if !ok {
- return RenderableNodes{}
- }
-
- var (
- id = MakePodID(podID)
- major, _ = m.Latest.Lookup(kubernetes.PodName)
- rank, _ = m.Latest.Lookup(kubernetes.PodID)
- )
-
- node := NewRenderableNodeWith(id, major, "", rank, m)
- node.Shape = Heptagon
- return RenderableNodes{id: node}
-}
-
-// MapServiceIdentity maps a service topology node to service renderable node. As it is
-// only ever run on service topology nodes, we expect that certain keys
-// are present.
-func MapServiceIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
- serviceID, ok := m.Latest.Lookup(kubernetes.ServiceID)
- if !ok {
- return RenderableNodes{}
- }
-
- var (
- id = MakeServiceID(serviceID)
- major, _ = m.Latest.Lookup(kubernetes.ServiceName)
- rank, _ = m.Latest.Lookup(kubernetes.ServiceID)
- )
-
- node := NewRenderableNodeWith(id, major, "", rank, m)
- node.Shape = Heptagon
- node.Stack = true
- return RenderableNodes{id: node}
+// NewDerivedPseudoNode makes a new pseudo node with the node as a child
+func NewDerivedPseudoNode(id string, node report.Node) report.Node {
+ return node.WithID(id).WithTopology(Pseudo).WithChildren(report.MakeNodeSet(node)).PruneParents()
}
-// MapHostIdentity maps a host topology node to a host renderable node. As it
-// is only ever run on host topology nodes, we expect that certain keys are
-// present.
-func MapHostIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
- var (
- id = MakeHostID(report.ExtractHostID(m.Node))
- hostname, _ = m.Latest.Lookup(host.HostName)
- parts = strings.SplitN(hostname, ".", 2)
- major, minor, rank = "", "", ""
- )
-
- if len(parts) == 2 {
- major, minor, rank = parts[0], parts[1], parts[1]
- } else {
- major = hostname
+func theInternetNode(m report.Node) report.Node {
+ // emit one internet node for incoming, one for outgoing
+ if len(m.Adjacency) > 0 {
+ return NewDerivedPseudoNode(IncomingInternetID, m)
}
-
- node := NewRenderableNodeWith(id, major, minor, rank, m)
- node.Shape = Circle
- return RenderableNodes{id: node}
+ return NewDerivedPseudoNode(OutgoingInternetID, m)
}
// MapEndpoint2IP maps endpoint nodes to their IP address, for joining
// with container nodes. We drop endpoint nodes with pids, as they
// will be joined to containers through the process topology, and we
// don't want to double count edges.
-func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes {
+func MapEndpoint2IP(m report.Node, local report.Networks) report.Nodes {
// Don't include procspied connections, to prevent double counting
_, ok := m.Latest.Lookup(endpoint.Procspied)
if ok {
- return RenderableNodes{}
+ return report.Nodes{}
}
scope, addr, port, ok := report.ParseEndpointNodeID(m.ID)
if !ok {
- return RenderableNodes{}
+ return report.Nodes{}
}
if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) {
- return RenderableNodes{TheInternetID: theInternetNode(m)}
+ return report.Nodes{TheInternetID: theInternetNode(m)}
}
// We don't always know what port a container is listening on, and
@@ -259,10 +82,9 @@ func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes {
// So we need to emit two nodes, for two different cases.
id := report.MakeScopedEndpointNodeID(scope, addr, "")
idWithPort := report.MakeScopedEndpointNodeID(scope, addr, port)
- m = m.WithParents(report.EmptySets)
- return RenderableNodes{
- id: NewRenderableNodeWith(id, "", "", "", m),
- idWithPort: NewRenderableNodeWith(idWithPort, "", "", "", m),
+ return report.Nodes{
+ id: NewDerivedNode(id, m).WithTopology(IP),
+ idWithPort: NewDerivedNode(idWithPort, m).WithTopology(IP),
}
}
@@ -271,8 +93,13 @@ var portMappingMatch = regexp.MustCompile(`([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.
// MapContainer2IP maps container nodes to their IP addresses (outputs
// multiple nodes). This allows container to be joined directly with
// the endpoint topology.
-func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes {
- result := RenderableNodes{}
+func MapContainer2IP(m report.Node, _ report.Networks) report.Nodes {
+ containerID, ok := m.Latest.Lookup(docker.ContainerID)
+ if !ok {
+ return report.Nodes{}
+ }
+
+ result := report.Nodes{}
if addrs, ok := m.Sets.Lookup(docker.ContainerIPsWithScopes); ok {
for _, addr := range addrs {
scope, addr, ok := report.ParseAddressNodeID(addr)
@@ -280,9 +107,11 @@ func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes {
continue
}
id := report.MakeScopedEndpointNodeID(scope, addr, "")
- node := NewRenderableNodeWith(id, "", "", "", m)
- node.Counters = node.Counters.Add(ipsKey, 1)
- result[id] = node
+ result[id] = NewDerivedNode(id, m).
+ WithTopology(IP).
+ WithLatests(map[string]string{docker.ContainerID: containerID}).
+ WithCounters(map[string]int{ipsKey: 1})
+
}
}
@@ -293,9 +122,11 @@ func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes {
if mapping := portMappingMatch.FindStringSubmatch(portMapping); mapping != nil {
ip, port := mapping[1], mapping[2]
id := report.MakeScopedEndpointNodeID("", ip, port)
- node := NewRenderableNodeWith(id, "", "", "", m.WithParents(report.EmptySets))
- node.Counters = node.Counters.Add(ipsKey, 1)
- result[id] = node
+ result[id] = NewDerivedNode(id, m).
+ WithTopology(IP).
+ WithLatests(map[string]string{docker.ContainerID: containerID}).
+ WithCounters(map[string]int{ipsKey: 1})
+
}
}
@@ -305,57 +136,54 @@ func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes {
// MapIP2Container maps IP nodes produced from MapContainer2IP back to
// container nodes. If there is more than one container with a given
// IP, it is dropped.
-func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes {
+func MapIP2Container(n report.Node, _ report.Networks) report.Nodes {
// If an IP is shared between multiple containers, we can't
// reliably attribute an connection based on its IP
- if count, _ := n.Node.Counters.Lookup(ipsKey); count > 1 {
- return RenderableNodes{}
+ if count, _ := n.Counters.Lookup(ipsKey); count > 1 {
+ return report.Nodes{}
}
// Propagate the internet pseudo node
if strings.HasSuffix(n.ID, TheInternetID) {
- return RenderableNodes{n.ID: n}
+ return report.Nodes{n.ID: n}
}
// If this node is not a container, exclude it.
// This excludes all the nodes we've dragged in from endpoint
// that we failed to join to a container.
- containerID, ok := n.Node.Latest.Lookup(docker.ContainerID)
+ containerID, ok := n.Latest.Lookup(docker.ContainerID)
if !ok {
- return RenderableNodes{}
+ return report.Nodes{}
}
- id := MakeContainerID(containerID)
- node := NewDerivedNode(id, n.WithParents(report.EmptySets))
- node.Shape = Hexagon
- return RenderableNodes{id: node}
+ id := report.MakeContainerNodeID(containerID)
+ return report.Nodes{
+ id: NewDerivedNode(id, n).
+ WithTopology(report.Container),
+ }
}
// MapEndpoint2Pseudo makes internet of host pesudo nodes from a endpoint node.
-func MapEndpoint2Pseudo(n RenderableNode, local report.Networks) RenderableNodes {
- var node RenderableNode
+func MapEndpoint2Pseudo(n report.Node, local report.Networks) report.Nodes {
+ var node report.Node
addr, ok := n.Latest.Lookup(endpoint.Addr)
if !ok {
- return RenderableNodes{}
+ return report.Nodes{}
}
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 {
-
- node = newDerivedPseudoNode(MakePseudoNodeID(addr), addr, n)
+ node = NewDerivedPseudoNode(MakePseudoNodeID(addr), n)
}
-
- node.Children = node.Children.Add(n)
- return RenderableNodes{node.ID: node}
+ return report.Nodes{node.ID: node}
}
-// MapEndpoint2Process maps endpoint RenderableNodes to process
-// RenderableNodes.
+// MapEndpoint2Process maps endpoint Nodes to process
+// Nodes.
//
// If this function is given a pseudo node, then it will just return it;
// Pseudo nodes will never have pids in them, and therefore will never
@@ -365,26 +193,26 @@ func MapEndpoint2Pseudo(n RenderableNode, local 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, local report.Networks) RenderableNodes {
+func MapEndpoint2Process(n report.Node, local report.Networks) report.Nodes {
// Nodes without a hostid are treated as pseudo nodes
if _, ok := n.Latest.Lookup(report.HostNodeID); !ok {
return MapEndpoint2Pseudo(n, local)
}
- pid, ok := n.Node.Latest.Lookup(process.PID)
+ pid, timestamp, ok := n.Latest.LookupEntry(process.PID)
if !ok {
- return RenderableNodes{}
+ return report.Nodes{}
}
- 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}
+ id := report.MakeProcessNodeID(report.ExtractHostID(n), pid)
+ node := NewDerivedNode(id, n).WithTopology(report.Process)
+ node.Latest = node.Latest.Set(process.PID, timestamp, pid)
+ node.Counters = node.Counters.Add(n.Topology, 1)
+ return report.Nodes{id: node}
}
-// MapProcess2Container maps process RenderableNodes to container
-// RenderableNodes.
+// MapProcess2Container maps process Nodes to container
+// Nodes.
//
// If this function is given a node without a docker_container_id
// (including other pseudo nodes), it will produce an "Uncontained"
@@ -394,15 +222,15 @@ func MapEndpoint2Process(n RenderableNode, local report.Networks) RenderableNode
// 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 container graph to get that info.
-func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes {
+func MapProcess2Container(n report.Node, _ report.Networks) report.Nodes {
// Propagate the internet pseudo node
if strings.HasSuffix(n.ID, TheInternetID) {
- return RenderableNodes{n.ID: n}
+ return report.Nodes{n.ID: n}
}
// Don't propagate non-internet pseudo nodes
- if n.Pseudo {
- return RenderableNodes{}
+ if n.Topology == Pseudo {
+ return report.Nodes{}
}
// Otherwise, if the process is not in a container, group it
@@ -410,77 +238,42 @@ func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes {
// this node doesn't have a host id in their nodemetadata, it'll
// all get grouped into a single uncontained node.
var (
- id string
- node RenderableNode
- hostID = report.ExtractHostID(n.Node)
+ id string
+ node report.Node
)
- n = n.WithParents(report.EmptySets)
- if containerID, ok := n.Node.Latest.Lookup(docker.ContainerID); ok {
- id = MakeContainerID(containerID)
- node = NewDerivedNode(id, n)
- node.Shape = Hexagon
+ if containerID, ok := n.Latest.Lookup(docker.ContainerID); ok {
+ id = report.MakeContainerNodeID(containerID)
+ node = NewDerivedNode(id, n).WithTopology(report.Container)
} else {
- nCopy := n.Copy()
- nCopy.Node = nCopy.Node.WithID("").WithTopology("") // Wipe the ID so it cannot be rendered.
- id = MakePseudoNodeID(UncontainedID, hostID)
- node = newDerivedPseudoNode(id, UncontainedMajor, nCopy)
- node.LabelMinor = hostID
- node.Shape = Square
- node.Stack = true
+ id = MakePseudoNodeID(UncontainedID, report.ExtractHostID(n))
+ node = NewDerivedPseudoNode(id, n)
}
-
- node.Children = node.Children.Add(n)
- return RenderableNodes{id: node}
+ return report.Nodes{id: node}
}
-// MapProcess2Name maps process RenderableNodes to RenderableNodes
+// MapProcess2Name maps process Nodes to Nodes
// for each process name.
//
// This mapper is unlike the other foo2bar mappers as the intention
-// is not to join the information with another topology. Therefore
-// it outputs a properly-formed node with labels etc.
-func MapProcess2Name(n RenderableNode, _ report.Networks) RenderableNodes {
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
+// is not to join the information with another topology.
+func MapProcess2Name(n report.Node, _ report.Networks) report.Nodes {
+ if n.Topology == Pseudo {
+ return report.Nodes{n.ID: n}
}
- name, ok := n.Node.Latest.Lookup(process.Name)
+ name, timestamp, ok := n.Latest.LookupEntry(process.Name)
if !ok {
- return RenderableNodes{}
- }
-
- node := NewDerivedNode(name, n)
- node.LabelMajor = name
- node.Rank = name
- node.Counters = node.Node.Counters.Add(processesKey, 1)
- node.Node.Topology = "process_name"
- node.Node.ID = name
- node.Children = node.Children.Add(n)
- node.Shape = Square
- node.Stack = true
- return RenderableNodes{name: node}
-}
-
-// MapCountProcessName maps 1:1 process name nodes, counting
-// the number of processes grouped together and putting
-// that info in the minor label.
-func MapCountProcessName(n RenderableNode, _ report.Networks) RenderableNodes {
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
+ return report.Nodes{}
}
- output := n.Copy()
- processes, _ := n.Node.Counters.Lookup(processesKey)
- if processes == 1 {
- output.LabelMinor = "1 process"
- } else {
- output.LabelMinor = fmt.Sprintf("%d processes", processes)
- }
- return RenderableNodes{output.ID: output}
+ node := NewDerivedNode(name, n).WithTopology(report.Process)
+ node.Latest = node.Latest.Set(process.Name, timestamp, name)
+ node.Counters = node.Counters.Add(n.Topology, 1)
+ return report.Nodes{name: node}
}
-// MapContainer2ContainerImage maps container RenderableNodes to container
-// image RenderableNodes.
+// MapContainer2ContainerImage maps container Nodes to container
+// image Nodes.
//
// If this function is given a node without a docker_image_id
// (including other pseudo nodes), it will produce an "Uncontained"
@@ -490,32 +283,25 @@ func MapCountProcessName(n RenderableNode, _ report.Networks) RenderableNodes {
// 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 container graph to get that info.
-func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) RenderableNodes {
+func MapContainer2ContainerImage(n report.Node, _ report.Networks) report.Nodes {
// Propagate all pseudo nodes
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
+ if n.Topology == Pseudo {
+ return report.Nodes{n.ID: n}
}
// Otherwise, if some some reason the container doesn't have a image_id
// (maybe slightly out of sync reports), just drop it
- imageID, ok := n.Node.Latest.Lookup(docker.ImageID)
+ imageID, timestamp, ok := n.Latest.LookupEntry(docker.ImageID)
if !ok {
- return RenderableNodes{}
+ return report.Nodes{}
}
// Add container id key to the counters, which will later be counted to produce the minor label
- id := MakeContainerImageID(imageID)
- result := NewDerivedNode(id, n.WithParents(report.EmptySets))
- 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)
-
- result.Node.Topology = "container_image"
- result.Node.ID = report.MakeContainerImageNodeID(imageID)
- result.Shape = Hexagon
- result.Stack = true
- return RenderableNodes{id: result}
+ id := report.MakeContainerImageNodeID(imageID)
+ result := NewDerivedNode(id, n).WithTopology(report.ContainerImage)
+ result.Latest = result.Latest.Set(docker.ImageID, timestamp, imageID)
+ result.Counters = result.Counters.Add(n.Topology, 1)
+ return report.Nodes{id: result}
}
// ImageNameWithoutVersion splits the image name apart, returning the name
@@ -529,36 +315,8 @@ func ImageNameWithoutVersion(name string) string {
return parts[0]
}
-// MapContainerImage2Name maps container images RenderableNodes to
-// RenderableNodes for each container image name.
-//
-// This mapper is unlike the other foo2bar mappers as the intention
-// is not to join the information with another topology. Therefore
-// it outputs a properly-formed node with labels etc.
-func MapContainerImage2Name(n RenderableNode, _ report.Networks) RenderableNodes {
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
- }
-
- name, ok := n.Node.Latest.Lookup(docker.ImageName)
- if !ok {
- return RenderableNodes{}
- }
-
- name = ImageNameWithoutVersion(name)
- id := MakeContainerImageID(name)
-
- node := NewDerivedNode(id, n)
- node.LabelMajor = name
- node.Rank = name
- node.Node = n.Node.Copy() // Propagate NMD for container counting.
- node.Shape = Hexagon
- node.Stack = true
- return RenderableNodes{id: node}
-}
-
-// MapX2Host maps any RenderableNodes to host
-// RenderableNodes.
+// MapX2Host maps any Nodes to host
+// Nodes.
//
// If this function is given a node without a hostname
// (including other pseudo nodes), it will drop the node.
@@ -567,39 +325,40 @@ func MapContainerImage2Name(n RenderableNode, _ report.Networks) RenderableNodes
// 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 container graph to get that info.
-func MapX2Host(n RenderableNode, _ report.Networks) RenderableNodes {
+func MapX2Host(n report.Node, _ report.Networks) report.Nodes {
// Don't propagate all pseudo nodes - we do this in MapEndpoint2Host
- if n.Pseudo {
- return RenderableNodes{}
- }
- if _, ok := n.Node.Latest.Lookup(report.HostNodeID); !ok {
- return RenderableNodes{}
- }
- id := MakeHostID(report.ExtractHostID(n.Node))
- result := NewDerivedNode(id, n.WithParents(report.EmptySets))
- result.Children = result.Children.Add(n)
- result.Shape = Circle
- result.EdgeMetadata = report.EdgeMetadata{} // Don't double count edge metadata
- return RenderableNodes{id: result}
+ if n.Topology == Pseudo {
+ return report.Nodes{}
+ }
+ hostNodeID, timestamp, ok := n.Latest.LookupEntry(report.HostNodeID)
+ if !ok {
+ return report.Nodes{}
+ }
+ id := report.MakeHostNodeID(report.ExtractHostID(n))
+ result := NewDerivedNode(id, n).WithTopology(report.Host)
+ result.Latest = result.Latest.Set(report.HostNodeID, timestamp, hostNodeID)
+ result.Counters = result.Counters.Add(n.Topology, 1)
+ return report.Nodes{id: result}
}
// MapEndpoint2Host takes nodes from the endpoint topology and produces
// host nodes or pseudo nodes.
-func MapEndpoint2Host(n RenderableNode, local report.Networks) RenderableNodes {
+func MapEndpoint2Host(n report.Node, local report.Networks) report.Nodes {
// Nodes without a hostid are treated as pseudo nodes
- if _, ok := n.Latest.Lookup(report.HostNodeID); !ok {
+ hostNodeID, timestamp, ok := n.Latest.LookupEntry(report.HostNodeID)
+ if !ok {
return MapEndpoint2Pseudo(n, local)
}
- id := MakeHostID(report.ExtractHostID(n.Node))
- result := NewDerivedNode(id, n.WithParents(report.EmptySets))
- result.Children = result.Children.Add(n)
- result.Shape = Circle
- return RenderableNodes{id: result}
+ id := report.MakeHostNodeID(report.ExtractHostID(n))
+ result := NewDerivedNode(id, n).WithTopology(report.Host)
+ result.Latest = result.Latest.Set(report.HostNodeID, timestamp, hostNodeID)
+ result.Counters = result.Counters.Add(n.Topology, 1)
+ return report.Nodes{id: result}
}
-// MapContainer2Pod maps container RenderableNodes to pod
-// RenderableNodes.
+// MapContainer2Pod maps container Nodes to pod
+// Nodes.
//
// If this function is given a node without a kubernetes_pod_id
// (including other pseudo nodes), it will produce an "Unmanaged"
@@ -609,42 +368,39 @@ func MapEndpoint2Host(n RenderableNode, local report.Networks) RenderableNodes {
// 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 container graph to get that info.
-func MapContainer2Pod(n RenderableNode, _ report.Networks) RenderableNodes {
+func MapContainer2Pod(n report.Node, _ report.Networks) report.Nodes {
// Propagate all pseudo nodes
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
+ if n.Topology == Pseudo {
+ return report.Nodes{n.ID: n}
}
// Otherwise, if some some reason the container doesn't have a pod_id (maybe
// slightly out of sync reports, or its not in a pod), just drop it
- podID, ok := n.Node.Latest.Lookup(kubernetes.PodID)
+ namespace, ok := n.Latest.Lookup(kubernetes.Namespace)
if !ok {
- return RenderableNodes{}
+ return report.Nodes{}
}
- id := MakePodID(podID)
-
- // Add container- key to NMD, which will later be counted to produce the
- // minor label
- result := NewRenderableNodeWith(id, "", "", podID, n.WithParents(report.EmptySets))
- result.Counters = result.Counters.Add(ContainersKey, 1)
+ podID, ok := n.Latest.Lookup(kubernetes.PodID)
+ if !ok {
+ return report.Nodes{}
+ }
+ podName := strings.TrimPrefix(podID, namespace+"/")
+ id := report.MakePodNodeID(namespace, podName)
// Due to a bug in kubernetes, addon pods on the master node are not returned
- // from the API. This is a workaround until
+ // from the API. Adding the namespace and pod name is a workaround until
// https://github.com/kubernetes/kubernetes/issues/14738 is fixed.
- if s := strings.SplitN(podID, "/", 2); len(s) == 2 {
- result.LabelMajor = s[1]
- result.Node = result.Node.WithLatests(map[string]string{
- kubernetes.Namespace: s[0],
- kubernetes.PodName: s[1],
- })
+ return report.Nodes{
+ id: NewDerivedNode(id, n).
+ WithTopology(report.Pod).
+ WithLatests(map[string]string{
+ kubernetes.Namespace: namespace,
+ kubernetes.PodName: podName,
+ }),
}
-
- result.Shape = Heptagon
- result.Children = result.Children.Add(n)
- return RenderableNodes{id: result}
}
-// MapPod2Service maps pod RenderableNodes to service RenderableNodes.
+// MapPod2Service maps pod Nodes to service Nodes.
//
// If this function is given a node without a kubernetes_pod_id
// (including other pseudo nodes), it will produce an "Uncontained"
@@ -654,91 +410,50 @@ func MapContainer2Pod(n RenderableNode, _ report.Networks) RenderableNodes {
// 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 {
+func MapPod2Service(pod report.Node, _ report.Networks) report.Nodes {
// Propagate all pseudo nodes
- if pod.Pseudo {
- return RenderableNodes{pod.ID: pod}
+ if pod.Topology == Pseudo {
+ return report.Nodes{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)
+ namespace, ok := pod.Latest.Lookup(kubernetes.Namespace)
if !ok {
- return RenderableNodes{}
+ return report.Nodes{}
+ }
+ ids, ok := pod.Latest.Lookup(kubernetes.ServiceIDs)
+ if !ok {
+ return report.Nodes{}
}
- result := RenderableNodes{}
+ result := report.Nodes{}
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
+ serviceName := strings.TrimPrefix(serviceID, namespace+"/")
+ id := report.MakeServiceNodeID(namespace, serviceName)
+ result[id] = NewDerivedNode(id, pod).WithTopology(report.Service)
}
return result
}
-// MapContainer2Hostname maps container RenderableNodes to 'hostname' renderabled nodes..
-func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes {
+// MapContainer2Hostname maps container Nodes to 'hostname' renderabled nodes..
+func MapContainer2Hostname(n report.Node, _ report.Networks) report.Nodes {
// Propagate all pseudo nodes
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
+ if n.Topology == Pseudo {
+ return report.Nodes{n.ID: n}
}
// Otherwise, if some some reason the container doesn't have a hostname
// (maybe slightly out of sync reports), just drop it
- id, ok := n.Node.Latest.Lookup(docker.ContainerHostname)
+ id, timestamp, ok := n.Latest.LookupEntry(docker.ContainerHostname)
if !ok {
- return RenderableNodes{}
+ return report.Nodes{}
}
- result := NewDerivedNode(id, n)
- result.LabelMajor = id
- result.Rank = id
-
- // Add container id key to the counters, which will later be counted to produce the minor label
- result.Counters = result.Counters.Add(ContainersKey, 1)
- result.Node.Topology = "container_hostname"
- result.Node.ID = id
- result.Children = result.Children.Add(n)
- result.Shape = Hexagon
- result.Stack = true
- return RenderableNodes{id: result}
-}
-
-// MapCountContainers maps 1:1 container image nodes, counting
-// the number of containers grouped together and putting
-// that info in the minor label.
-func MapCountContainers(n RenderableNode, _ report.Networks) RenderableNodes {
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
- }
-
- output := n.Copy()
- containers, _ := n.Node.Counters.Lookup(ContainersKey)
- if containers == 1 {
- output.LabelMinor = "1 container"
- } else {
- output.LabelMinor = fmt.Sprintf("%d containers", containers)
- }
- return RenderableNodes{output.ID: output}
-}
-
-// MapCountPods maps 1:1 service nodes, counting the number of pods grouped
-// together and putting that info in the minor label.
-func MapCountPods(n RenderableNode, _ report.Networks) RenderableNodes {
- if n.Pseudo {
- return RenderableNodes{n.ID: n}
- }
-
- output := n.Copy()
- pods, _ := n.Node.Counters.Lookup(podsKey)
- if pods == 1 {
- output.LabelMinor = "1 pod"
- } else {
- output.LabelMinor = fmt.Sprintf("%d pods", pods)
- }
- return RenderableNodes{output.ID: output}
+ node := NewDerivedNode(id, n)
+ node.Latest = node.Latest.
+ Set(docker.ContainerHostname, timestamp, id).
+ Delete(docker.ContainerName) // TODO(paulbellamy): total hack to render these by hostname instead.
+ node.Counters = node.Counters.Add(n.Topology, 1)
+ return report.Nodes{id: node}
}
diff --git a/render/mapping_test.go b/render/mapping_test.go
index 88be57d8cd..93382cdbd2 100644
--- a/render/mapping_test.go
+++ b/render/mapping_test.go
@@ -6,96 +6,24 @@ import (
"testing"
"github.com/weaveworks/scope/probe/docker"
- "github.com/weaveworks/scope/probe/endpoint"
- "github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
)
-func nrn(nmd report.Node) render.RenderableNode {
- return render.NewRenderableNode("").WithNode(nmd)
-}
-
-func TestMapEndpointIdentity(t *testing.T) {
- for _, input := range []testcase{
- {"empty", nrn(report.MakeNode()), false},
- {"", nrn(report.MakeNodeWith(map[string]string{endpoint.Addr: "1.2.3.4", endpoint.Procspied: "true"})), false},
- {"", nrn(report.MakeNodeWith(map[string]string{endpoint.Port: "1234", endpoint.Procspied: "true"})), false},
- {"", nrn(report.MakeNodeWith(map[string]string{endpoint.Addr: "1.2.3.4", endpoint.Port: "1234", endpoint.Procspied: "true"})), true},
- {"", nrn(report.MakeNodeWith(map[string]string{endpoint.Addr: "1.2.3.4", endpoint.Port: "40000", endpoint.Procspied: "true"})), true},
- {"", nrn(report.MakeNodeWith(map[string]string{report.HostNodeID: report.MakeHostNodeID("foo"), endpoint.Addr: "10.0.0.1", endpoint.Port: "20001", endpoint.Procspied: "true"})), true},
- } {
- testMap(t, render.MapEndpointIdentity, input)
- }
-}
-
-func TestMapProcessIdentity(t *testing.T) {
- for _, input := range []testcase{
- {"empty", nrn(report.MakeNode()), false},
- {"basic process", nrn(report.MakeNodeWith(map[string]string{process.PID: "201"})), true},
- } {
- testMap(t, render.MapProcessIdentity, input)
- }
-}
-
func TestMapProcess2Container(t *testing.T) {
for _, input := range []testcase{
- {"empty", nrn(report.MakeNode()), true},
- {"basic process", nrn(report.MakeNodeWith(map[string]string{process.PID: "201", docker.ContainerID: "a1b2c3"})), true},
- {"uncontained", nrn(report.MakeNodeWith(map[string]string{process.PID: "201", report.HostNodeID: report.MakeHostNodeID("foo")})), true},
+ {"empty", report.MakeNode(), true},
+ {"basic process", report.MakeNodeWith(map[string]string{process.PID: "201", docker.ContainerID: "a1b2c3"}), true},
+ {"uncontained", report.MakeNodeWith(map[string]string{process.PID: "201", report.HostNodeID: report.MakeHostNodeID("foo")}), true},
} {
testMap(t, render.MapProcess2Container, input)
}
}
-func TestMapContainerIdentity(t *testing.T) {
- for _, input := range []testcase{
- {"empty", nrn(report.MakeNode()), false},
- {"basic container", nrn(report.MakeNodeWith(map[string]string{docker.ContainerID: "a1b2c3"})), true},
- } {
- testMap(t, render.MapContainerIdentity, input)
- }
-}
-
-func TestMapContainerImageIdentity(t *testing.T) {
- for _, input := range []testcase{
- {"empty", nrn(report.MakeNode()), false},
- {"basic image", nrn(report.MakeNodeWith(map[string]string{docker.ImageID: "a1b2c3"})), true},
- } {
- testMap(t, render.MapContainerImageIdentity, input)
- }
-}
-
-func TestMapHostIdentity(t *testing.T) {
- for _, input := range []testcase{
- {"empty", nrn(report.MakeNode()), true}, // TODO it's questionable if this is actually correct
- } {
- testMap(t, render.MapHostIdentity, input)
- }
-}
-
-func TestMapPodIdentity(t *testing.T) {
- for _, input := range []testcase{
- {"empty", nrn(report.MakeNode()), false},
- {"basic pod", nrn(report.MakeNodeWith(map[string]string{kubernetes.PodID: "ping/pong", kubernetes.PodName: "pong"})), true},
- } {
- testMap(t, render.MapPodIdentity, input)
- }
-}
-
-func TestMapServiceIdentity(t *testing.T) {
- for _, input := range []testcase{
- {"empty", nrn(report.MakeNode()), false},
- {"basic service", nrn(report.MakeNodeWith(map[string]string{kubernetes.ServiceID: "ping/pong", kubernetes.ServiceName: "pong"})), true},
- } {
- testMap(t, render.MapServiceIdentity, input)
- }
-}
-
type testcase struct {
name string
- n render.RenderableNode
+ n report.Node
ok bool
}
diff --git a/render/memoise.go b/render/memoise.go
index 052c49328e..83371d50ca 100644
--- a/render/memoise.go
+++ b/render/memoise.go
@@ -24,13 +24,13 @@ func Memoise(r Renderer) Renderer {
}
}
-// Render produces a set of RenderableNodes given a Report.
+// Render produces a set of Nodes given a Report.
// Ideally, it just retrieves it from the cache, otherwise it calls through to
// `r` and stores the result.
-func (m *memoise) Render(rpt report.Report) RenderableNodes {
+func (m *memoise) Render(rpt report.Report) report.Nodes {
key := fmt.Sprintf("%s-%s", rpt.ID, m.id)
if result, err := renderCache.Get(key); err == nil {
- return result.(RenderableNodes)
+ return result.(report.Nodes)
}
output := m.Renderer.Render(rpt)
renderCache.Set(key, output)
diff --git a/render/memoise_test.go b/render/memoise_test.go
index 5b1ea04244..d5f230f26b 100644
--- a/render/memoise_test.go
+++ b/render/memoise_test.go
@@ -9,16 +9,16 @@ import (
"github.com/weaveworks/scope/test/reflect"
)
-type renderFunc func(r report.Report) render.RenderableNodes
+type renderFunc func(r report.Report) report.Nodes
-func (f renderFunc) Render(r report.Report) render.RenderableNodes { return f(r) }
-func (f renderFunc) Stats(r report.Report) render.Stats { return render.Stats{} }
+func (f renderFunc) Render(r report.Report) report.Nodes { return f(r) }
+func (f renderFunc) Stats(r report.Report) render.Stats { return render.Stats{} }
func TestMemoise(t *testing.T) {
calls := 0
- r := renderFunc(func(rpt report.Report) render.RenderableNodes {
+ r := renderFunc(func(rpt report.Report) report.Nodes {
calls++
- return render.RenderableNodes{rpt.ID: render.NewRenderableNode(rpt.ID)}
+ return report.Nodes{rpt.ID: report.MakeNode().WithID(rpt.ID)}
})
m := render.Memoise(r)
rpt1 := report.MakeReport()
diff --git a/render/render.go b/render/render.go
index a57f99be50..8c045c8e2f 100644
--- a/render/render.go
+++ b/render/render.go
@@ -4,9 +4,9 @@ import (
"github.com/weaveworks/scope/report"
)
-// Renderer is something that can render a report to a set of RenderableNodes.
+// Renderer is something that can render a report to a set of Nodes.
type Renderer interface {
- Render(report.Report) RenderableNodes
+ Render(report.Report) report.Nodes
Stats(report.Report) Stats
}
@@ -31,9 +31,9 @@ func MakeReduce(renderers ...Renderer) Renderer {
return Memoise(&r)
}
-// Render produces a set of RenderableNodes given a Report.
-func (r *Reduce) Render(rpt report.Report) RenderableNodes {
- result := RenderableNodes{}
+// Render produces a set of Nodes given a Report.
+func (r *Reduce) Render(rpt report.Report) report.Nodes {
+ result := report.Nodes{}
for _, renderer := range *r {
result = result.Merge(renderer.Render(rpt))
}
@@ -49,8 +49,8 @@ func (r *Reduce) Stats(rpt report.Report) Stats {
return result
}
-// Map is a Renderer which produces a set of RenderableNodes from the set of
-// RenderableNodes produced by another Renderer.
+// Map is a Renderer which produces a set of Nodes from the set of
+// Nodes produced by another Renderer.
type Map struct {
MapFunc
Renderer
@@ -61,25 +61,12 @@ func MakeMap(f MapFunc, r Renderer) Renderer {
return Memoise(&Map{f, r})
}
-// Render transforms a set of RenderableNodes produces by another Renderer.
+// Render transforms a set of Nodes produces by another Renderer.
// using a map function
-func (m *Map) Render(rpt report.Report) RenderableNodes {
- output, _ := m.render(rpt)
- return output
-}
-
-// Stats implements Renderer
-func (m *Map) Stats(rpt report.Report) Stats {
- // There doesn't seem to be an instance where we want stats to recurse
- // through Maps - for instance we don't want to see the number of filtered
- // processes in the container renderer.
- return Stats{}
-}
-
-func (m *Map) render(rpt report.Report) (RenderableNodes, map[string]report.IDList) {
+func (m *Map) Render(rpt report.Report) report.Nodes {
var (
input = m.Renderer.Render(rpt)
- output = RenderableNodes{}
+ output = report.Nodes{}
mapped = map[string]report.IDList{} // input node ID -> output node IDs
adjacencies = map[string]report.IDList{} // output node ID -> input node Adjacencies
localNetworks = LocalNetworks(rpt)
@@ -112,5 +99,13 @@ func (m *Map) render(rpt report.Report) (RenderableNodes, map[string]report.IDLi
output[outNodeID] = outNode
}
- return output, mapped
+ return output
+}
+
+// Stats implements Renderer
+func (m *Map) Stats(rpt report.Report) Stats {
+ // There doesn't seem to be an instance where we want stats to recurse
+ // through Maps - for instance we don't want to see the number of filtered
+ // processes in the container renderer.
+ return Stats{}
}
diff --git a/render/render_test.go b/render/render_test.go
index 7ac6924f5a..b05973ecc5 100644
--- a/render/render_test.go
+++ b/render/render_test.go
@@ -10,21 +10,21 @@ import (
)
type mockRenderer struct {
- render.RenderableNodes
+ report.Nodes
}
-func (m mockRenderer) Render(rpt report.Report) render.RenderableNodes { return m.RenderableNodes }
-func (m mockRenderer) Stats(rpt report.Report) render.Stats { return render.Stats{} }
+func (m mockRenderer) Render(rpt report.Report) report.Nodes { return m.Nodes }
+func (m mockRenderer) Stats(rpt report.Report) render.Stats { return render.Stats{} }
func TestReduceRender(t *testing.T) {
renderer := render.Reduce([]render.Renderer{
- mockRenderer{RenderableNodes: render.RenderableNodes{"foo": render.NewRenderableNode("foo")}},
- mockRenderer{RenderableNodes: render.RenderableNodes{"bar": render.NewRenderableNode("bar")}},
+ mockRenderer{Nodes: report.Nodes{"foo": report.MakeNode().WithID("foo")}},
+ mockRenderer{Nodes: report.Nodes{"bar": report.MakeNode().WithID("bar")}},
})
- want := render.RenderableNodes{
- "foo": render.NewRenderableNode("foo"),
- "bar": render.NewRenderableNode("bar"),
+ want := report.Nodes{
+ "foo": report.MakeNode().WithID("foo"),
+ "bar": report.MakeNode().WithID("bar"),
}
have := renderer.Render(report.MakeReport())
if !reflect.DeepEqual(want, have) {
@@ -35,14 +35,14 @@ func TestReduceRender(t *testing.T) {
func TestMapRender1(t *testing.T) {
// 1. Check when we return false, the node gets filtered out
mapper := render.Map{
- MapFunc: func(nodes render.RenderableNode, _ report.Networks) render.RenderableNodes {
- return render.RenderableNodes{}
+ MapFunc: func(nodes report.Node, _ report.Networks) report.Nodes {
+ return report.Nodes{}
},
- Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
- "foo": render.NewRenderableNode("foo"),
+ Renderer: mockRenderer{Nodes: report.Nodes{
+ "foo": report.MakeNode().WithID("foo"),
}},
}
- want := render.RenderableNodes{}
+ want := report.Nodes{}
have := mapper.Render(report.MakeReport())
if !reflect.DeepEqual(want, have) {
t.Errorf("want %+v, have %+v", want, have)
@@ -52,18 +52,18 @@ func TestMapRender1(t *testing.T) {
func TestMapRender2(t *testing.T) {
// 2. Check we can remap two nodes into one
mapper := render.Map{
- MapFunc: func(nodes render.RenderableNode, _ report.Networks) render.RenderableNodes {
- return render.RenderableNodes{
- "bar": render.NewRenderableNode("bar"),
+ MapFunc: func(nodes report.Node, _ report.Networks) report.Nodes {
+ return report.Nodes{
+ "bar": report.MakeNode().WithID("bar"),
}
},
- Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
- "foo": render.NewRenderableNode("foo"),
- "baz": render.NewRenderableNode("baz"),
+ Renderer: mockRenderer{Nodes: report.Nodes{
+ "foo": report.MakeNode().WithID("foo"),
+ "baz": report.MakeNode().WithID("baz"),
}},
}
- want := render.RenderableNodes{
- "bar": render.NewRenderableNode("bar"),
+ want := report.Nodes{
+ "bar": report.MakeNode().WithID("bar"),
}
have := mapper.Render(report.MakeReport())
if !reflect.DeepEqual(want, have) {
@@ -74,18 +74,18 @@ func TestMapRender2(t *testing.T) {
func TestMapRender3(t *testing.T) {
// 3. Check we can remap adjacencies
mapper := render.Map{
- MapFunc: func(nodes render.RenderableNode, _ report.Networks) render.RenderableNodes {
+ MapFunc: func(nodes report.Node, _ report.Networks) report.Nodes {
id := "_" + nodes.ID
- return render.RenderableNodes{id: render.NewRenderableNode(id)}
+ return report.Nodes{id: report.MakeNode().WithID(id)}
},
- Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
- "foo": render.NewRenderableNode("foo").WithNode(report.MakeNode().WithAdjacent("baz")),
- "baz": render.NewRenderableNode("baz").WithNode(report.MakeNode().WithAdjacent("foo")),
+ Renderer: mockRenderer{Nodes: report.Nodes{
+ "foo": report.MakeNode().WithID("foo").WithAdjacent("baz"),
+ "baz": report.MakeNode().WithID("baz").WithAdjacent("foo"),
}},
}
- want := render.RenderableNodes{
- "_foo": render.NewRenderableNode("_foo").WithNode(report.MakeNode().WithAdjacent("_baz")),
- "_baz": render.NewRenderableNode("_baz").WithNode(report.MakeNode().WithAdjacent("_foo")),
+ want := report.Nodes{
+ "_foo": report.MakeNode().WithID("_foo").WithAdjacent("_baz"),
+ "_baz": report.MakeNode().WithID("_baz").WithAdjacent("_foo"),
}
have := mapper.Render(report.MakeReport())
if !reflect.DeepEqual(want, have) {
diff --git a/render/renderable_node.go b/render/renderable_node.go
deleted file mode 100644
index 97af4e136b..0000000000
--- a/render/renderable_node.go
+++ /dev/null
@@ -1,241 +0,0 @@
-package render
-
-import (
- "strings"
-
- "github.com/weaveworks/scope/probe/docker"
- "github.com/weaveworks/scope/probe/kubernetes"
- "github.com/weaveworks/scope/report"
-)
-
-// RenderableNode is the data type that's yielded to the JavaScript layer as
-// 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 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
-}
-
-// Shapes that are allowed
-const (
- Circle = "circle"
- Square = "square"
- Heptagon = "heptagon"
- Hexagon = "hexagon"
- Cloud = "cloud"
-)
-
-// NewRenderableNode makes a new RenderableNode
-func NewRenderableNode(id string) RenderableNode {
- return RenderableNode{
- ID: id,
- LabelMajor: "",
- LabelMinor: "",
- Rank: "",
- Pseudo: false,
- EdgeMetadata: report.EdgeMetadata{},
- Node: report.MakeNode(),
- Shape: Circle,
- }
-}
-
-// NewRenderableNodeWith makes a new RenderableNode with some fields filled in
-func NewRenderableNodeWith(id, major, minor, rank string, node RenderableNode) RenderableNode {
- return RenderableNode{
- ID: id,
- LabelMajor: major,
- LabelMinor: minor,
- Rank: rank,
- Pseudo: false,
- Children: node.Children.Copy(),
- EdgeMetadata: node.EdgeMetadata.Copy(),
- Node: node.Node.Copy(),
- Shape: Circle,
- }
-}
-
-// NewDerivedNode create a renderable node based on node, but with a new ID
-func NewDerivedNode(id string, node RenderableNode) RenderableNode {
- return RenderableNode{
- ID: id,
- LabelMajor: "",
- LabelMinor: "",
- Rank: "",
- Pseudo: node.Pseudo,
- Children: node.Children.Copy(),
- EdgeMetadata: node.EdgeMetadata.Copy(),
- Node: node.Node.Copy(),
- ControlNode: "", // Do not propagate ControlNode when making a derived node!
- Shape: Circle,
- }
-}
-
-func newDerivedPseudoNode(id, major string, node RenderableNode) RenderableNode {
- return RenderableNode{
- ID: id,
- LabelMajor: major,
- LabelMinor: "",
- Rank: "",
- Pseudo: true,
- Children: node.Children.Copy(),
- EdgeMetadata: node.EdgeMetadata.Copy(),
- Node: node.Node.Copy(),
- Shape: Circle,
- }
-}
-
-// WithNode creates a new RenderableNode based on rn, with n
-func (rn RenderableNode) WithNode(n report.Node) RenderableNode {
- result := rn.Copy()
- result.Node = result.Node.Merge(n)
- return result
-}
-
-// WithParents creates a new RenderableNode based on rn, where n has the given parents set
-func (rn RenderableNode) WithParents(p report.Sets) RenderableNode {
- result := rn.Copy()
- result.Node.Parents = p
- return result
-}
-
-// Merge merges rn with other and returns a new RenderableNode
-func (rn RenderableNode) Merge(other RenderableNode) RenderableNode {
- result := rn.Copy()
-
- if result.LabelMajor == "" {
- result.LabelMajor = other.LabelMajor
- }
-
- if result.LabelMinor == "" {
- result.LabelMinor = other.LabelMinor
- }
-
- if result.Rank == "" {
- result.Rank = other.Rank
- }
-
- if result.ControlNode == "" {
- result.ControlNode = other.ControlNode
- }
-
- if result.Pseudo != other.Pseudo {
- panic(result.ID)
- }
-
- result.Stack = result.Stack || rn.Stack
- result.Children = rn.Children.Merge(other.Children)
- result.EdgeMetadata = rn.EdgeMetadata.Merge(other.EdgeMetadata)
- result.Node = rn.Node.Merge(other.Node)
-
- return result
-}
-
-// Copy makes a deep copy of rn
-func (rn RenderableNode) Copy() RenderableNode {
- return RenderableNode{
- ID: rn.ID,
- LabelMajor: rn.LabelMajor,
- LabelMinor: rn.LabelMinor,
- Rank: rn.Rank,
- Pseudo: rn.Pseudo,
- Children: rn.Children.Copy(),
- EdgeMetadata: rn.EdgeMetadata.Copy(),
- Node: rn.Node.Copy(),
- ControlNode: rn.ControlNode,
- Shape: rn.Shape,
- Stack: rn.Stack,
- }
-}
-
-// IsStopped checks if the RenderableNode is a stopped docker container.
-func (rn RenderableNode) IsStopped() bool {
- containerState, ok := rn.Latest.Lookup(docker.ContainerState)
- return !ok || containerState != docker.StateStopped
-}
-
-// IsSystem checks if the RenderableNode is a system container/pod/etc.
-func (rn RenderableNode) IsSystem() bool {
- containerName, _ := rn.Latest.Lookup(docker.ContainerName)
- if _, ok := systemContainerNames[containerName]; ok {
- return false
- }
- imageName, _ := rn.Latest.Lookup(docker.ImageName)
- imagePrefix := strings.SplitN(imageName, ":", 2)[0] // :(
- if _, ok := systemImagePrefixes[imagePrefix]; ok {
- return false
- }
- roleLabel, _ := rn.Latest.Lookup(docker.LabelPrefix + "works.weave.role")
- if roleLabel == "system" {
- return false
- }
- namespace, _ := rn.Latest.Lookup(kubernetes.Namespace)
- if namespace == "kube-system" {
- return false
- }
- podName, _ := rn.Latest.Lookup(docker.LabelPrefix + "io.kubernetes.pod.name")
- if strings.HasPrefix(podName, "kube-system/") {
- return false
- }
- return true
-}
-
-// Prune returns a copy of the RenderableNode with all information not
-// strictly necessary for rendering nodes and edges stripped away.
-// Specifically, that means cutting out parts of the Node.
-func (rn RenderableNode) Prune() RenderableNode {
- cp := rn.Copy()
- cp.Node = report.MakeNode().WithAdjacent(cp.Node.Adjacency...)
- cp.Children = MakeRenderableNodeSet()
- rn.Children.ForEach(func(n RenderableNode) {
- cp.Children = cp.Children.Add(n.Prune())
- })
- return cp
-}
-
-// RenderableNodes is a set of RenderableNodes
-type RenderableNodes map[string]RenderableNode
-
-// Copy produces a deep copy of the RenderableNodes
-func (rns RenderableNodes) Copy() RenderableNodes {
- result := RenderableNodes{}
- for key, value := range rns {
- result[key] = value.Copy()
- }
- return result
-}
-
-// Merge merges two sets of RenderableNodes, returning a new set.
-func (rns RenderableNodes) Merge(other RenderableNodes) RenderableNodes {
- result := RenderableNodes{}
- for key, value := range rns {
- result[key] = value
- }
- for key, value := range other {
- existing, ok := result[key]
- if ok {
- value = value.Merge(existing)
- }
- result[key] = value
- }
- return result
-}
-
-// Prune returns a copy of the RenderableNodes with all information not
-// strictly necessary for rendering nodes and edges in the UI cut away.
-func (rns RenderableNodes) Prune() RenderableNodes {
- cp := rns.Copy()
- for id, rn := range cp {
- cp[id] = rn.Prune()
- }
- return cp
-}
diff --git a/render/renderable_node_set_test.go b/render/renderable_node_set_test.go
deleted file mode 100644
index 9ffcdeb756..0000000000
--- a/render/renderable_node_set_test.go
+++ /dev/null
@@ -1,272 +0,0 @@
-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
deleted file mode 100644
index e6bf6745c7..0000000000
--- a/render/renderable_node_test.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package render_test
-
-import (
- "reflect"
- "testing"
-
- "github.com/weaveworks/scope/render"
- "github.com/weaveworks/scope/report"
- "github.com/weaveworks/scope/test"
-)
-
-func TestMergeRenderableNodes(t *testing.T) {
- nodes1 := render.RenderableNodes{
- "foo": render.NewRenderableNode("foo"),
- "bar": render.NewRenderableNode("bar"),
- }
- nodes2 := render.RenderableNodes{
- "bar": render.NewRenderableNode("bar"),
- "baz": render.NewRenderableNode("baz"),
- }
- want := (render.RenderableNodes{
- "foo": render.NewRenderableNode("foo"),
- "bar": render.NewRenderableNode("bar"),
- "baz": render.NewRenderableNode("baz"),
- }).Prune()
- have := nodes1.Merge(nodes2).Prune()
- if !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
- }
-}
-
-func TestMergeRenderableNode(t *testing.T) {
- node1 := render.RenderableNode{
- ID: "foo",
- LabelMajor: "",
- LabelMinor: "minor",
- Rank: "",
- Pseudo: false,
- Node: report.MakeNode().WithAdjacent("a1"),
- Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child1")),
- }
- node2 := render.RenderableNode{
- ID: "foo",
- LabelMajor: "major",
- LabelMinor: "",
- Rank: "rank",
- Pseudo: false,
- Node: report.MakeNode().WithAdjacent("a2"),
- Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child2")),
- }
- want := render.RenderableNode{
- ID: "foo",
- LabelMajor: "major",
- LabelMinor: "minor",
- Rank: "rank",
- Pseudo: false,
- Node: report.MakeNode().WithID("foo").WithAdjacent("a1").WithAdjacent("a2"),
- Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child1"), render.NewRenderableNode("child2")),
- EdgeMetadata: report.EdgeMetadata{},
- }.Prune()
- have := node1.Merge(node2).Prune()
- if !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
- }
-}
diff --git a/render/selectors.go b/render/selectors.go
index a141e126c1..683382872a 100644
--- a/render/selectors.go
+++ b/render/selectors.go
@@ -6,11 +6,12 @@ import (
// TopologySelector selects a single topology from a report.
// NB it is also a Renderer!
-type TopologySelector func(r report.Report) RenderableNodes
+type TopologySelector string
// Render implements Renderer
-func (t TopologySelector) Render(r report.Report) RenderableNodes {
- return t(r)
+func (t TopologySelector) Render(r report.Report) report.Nodes {
+ topology, _ := r.Topology(string(t))
+ return topology.Nodes
}
// Stats implements Renderer
@@ -18,60 +19,14 @@ func (t TopologySelector) Stats(r report.Report) Stats {
return Stats{}
}
-// MakeRenderableNodes converts a topology to a set of RenderableNodes
-func MakeRenderableNodes(t report.Topology) RenderableNodes {
- result := RenderableNodes{}
- for id, nmd := range t.Nodes {
- result[id] = NewRenderableNode(id).WithNode(nmd)
- }
-
- // Push EdgeMetadata to both ends of the edges
- for srcID, srcNode := range result {
- srcNode.Edges.ForEach(func(dstID string, emd report.EdgeMetadata) {
- srcNode.EdgeMetadata = srcNode.EdgeMetadata.Flatten(emd)
-
- dstNode := result[dstID]
- dstNode.EdgeMetadata = dstNode.EdgeMetadata.Flatten(emd.Reversed())
- result[dstID] = dstNode
- })
- result[srcID] = srcNode
- }
- return result
-}
-
+// The topology selectors implement a Renderer which fetch the nodes from the
+// various report topologies.
var (
- // SelectEndpoint selects the endpoint topology.
- SelectEndpoint = TopologySelector(func(r report.Report) RenderableNodes {
- return MakeRenderableNodes(r.Endpoint)
- })
-
- // SelectProcess selects the process topology.
- SelectProcess = TopologySelector(func(r report.Report) RenderableNodes {
- return MakeRenderableNodes(r.Process)
- })
-
- // SelectContainer selects the container topology.
- SelectContainer = TopologySelector(func(r report.Report) RenderableNodes {
- return MakeRenderableNodes(r.Container)
- })
-
- // SelectContainerImage selects the container image topology.
- SelectContainerImage = TopologySelector(func(r report.Report) RenderableNodes {
- return MakeRenderableNodes(r.ContainerImage)
- })
-
- // SelectHost selects the address topology.
- SelectHost = TopologySelector(func(r report.Report) RenderableNodes {
- return MakeRenderableNodes(r.Host)
- })
-
- // SelectPod selects the pod topology.
- SelectPod = TopologySelector(func(r report.Report) RenderableNodes {
- return MakeRenderableNodes(r.Pod)
- })
-
- // SelectService selects the service topology.
- SelectService = TopologySelector(func(r report.Report) RenderableNodes {
- return MakeRenderableNodes(r.Service)
- })
+ SelectEndpoint = TopologySelector(report.Endpoint)
+ SelectProcess = TopologySelector(report.Process)
+ SelectContainer = TopologySelector(report.Container)
+ SelectContainerImage = TopologySelector(report.ContainerImage)
+ SelectHost = TopologySelector(report.Host)
+ SelectPod = TopologySelector(report.Pod)
+ SelectService = TopologySelector(report.Service)
)
diff --git a/render/selectors_test.go b/render/selectors_test.go
deleted file mode 100644
index ee01e983b5..0000000000
--- a/render/selectors_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package render_test
-
-import (
- "testing"
-
- "github.com/weaveworks/scope/render"
- "github.com/weaveworks/scope/report"
- "github.com/weaveworks/scope/test"
- "github.com/weaveworks/scope/test/reflect"
-)
-
-func TestMakeRenderableNodes(t *testing.T) {
-
- var (
- newu64 = func(value uint64) *uint64 { return &value }
- srcNodeID = "srcNode"
- dstNode1ID = "dstNode1"
- dstNode2ID = "dstNode2"
- srcNode = report.MakeNode().
- WithEdge(dstNode1ID, report.EdgeMetadata{EgressPacketCount: newu64(100), EgressByteCount: newu64(1000)}).
- WithEdge(dstNode2ID, report.EdgeMetadata{EgressPacketCount: newu64(200), EgressByteCount: newu64(2000)})
- dstNode1 = report.MakeNode()
- dstNode2 = report.MakeNode()
- topology = report.MakeTopology().
- AddNode(srcNodeID, srcNode).
- AddNode(dstNode1ID, dstNode1).
- AddNode(dstNode2ID, dstNode2)
- )
-
- result := render.MakeRenderableNodes(topology)
- mustLookup := func(id string) render.RenderableNode {
- node, ok := result[id]
- if !ok {
- t.Fatalf("Expected result to contain node: %q, got: %v", id, result)
- }
- return node
- }
-
- // Source nodes should have the flattened edge metadata
- {
- have := mustLookup(srcNodeID).EdgeMetadata
- want := report.EdgeMetadata{EgressPacketCount: newu64(300), EgressByteCount: newu64(3000)}
- if !reflect.DeepEqual(want, have) {
- t.Errorf(test.Diff(want, have))
- }
- }
-
- // Result destination nodes should have the reverse of the source nodes
- {
- have := mustLookup(dstNode1ID).EdgeMetadata
- want := report.EdgeMetadata{IngressPacketCount: newu64(100), IngressByteCount: newu64(1000)}
- if !reflect.DeepEqual(want, have) {
- t.Errorf(test.Diff(want, have))
- }
-
- have = mustLookup(dstNode2ID).EdgeMetadata
- want = report.EdgeMetadata{IngressPacketCount: newu64(200), IngressByteCount: newu64(2000)}
- if !reflect.DeepEqual(want, have) {
- t.Errorf(test.Diff(want, have))
- }
- }
-}
diff --git a/render/short_lived_connections_test.go b/render/short_lived_connections_test.go
index 71c4c6dc9d..47b42a6e8e 100644
--- a/render/short_lived_connections_test.go
+++ b/render/short_lived_connections_test.go
@@ -2,7 +2,6 @@ package render_test
import (
"fmt"
- "reflect"
"testing"
"github.com/weaveworks/scope/probe/docker"
@@ -10,7 +9,6 @@ import (
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
- "github.com/weaveworks/scope/test"
)
var (
@@ -68,32 +66,18 @@ var (
},
},
}
-
- want = (render.RenderableNodes{
- render.IncomingInternetID: {
- ID: render.IncomingInternetID,
- LabelMajor: render.InboundMajor,
- LabelMinor: render.InboundMinor,
- Pseudo: true,
- Shape: "cloud",
- Node: report.MakeNode().WithAdjacent(render.MakeContainerID(containerID)),
- },
- render.MakeContainerID(containerID): {
- ID: render.MakeContainerID(containerID),
- LabelMajor: containerName,
- LabelMinor: serverHostID,
- Rank: "",
- Pseudo: false,
- Shape: "hexagon",
- Node: report.MakeNode(),
- ControlNode: containerNodeID,
- },
- }).Prune()
)
func TestShortLivedInternetNodeConnections(t *testing.T) {
- have := (render.ContainerWithImageNameRenderer.Render(rpt)).Prune()
- if !reflect.DeepEqual(want, have) {
- t.Error(test.Diff(want, have))
+ have := render.ContainerWithImageNameRenderer.Render(rpt).Prune()
+
+ // Conntracked-only connections from the internet should be assigned to the internet pseudonode
+ internet, ok := have[render.IncomingInternetID]
+ if !ok {
+ t.Fatal("Expected output to have an incoming internet node")
+ }
+
+ if !internet.Adjacency.Contains(report.MakeContainerNodeID(containerID)) {
+ t.Errorf("Expected internet node to have adjacency to %s, but only had %v", report.MakeContainerNodeID(containerID), internet.Adjacency)
}
}
diff --git a/render/topologies.go b/render/topologies.go
index 0603a1dc52..b7656bda41 100644
--- a/render/topologies.go
+++ b/render/topologies.go
@@ -1,20 +1,15 @@
package render
import (
- "fmt"
"net"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
- "github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
)
// EndpointRenderer is a Renderer which produces a renderable endpoint graph.
-var EndpointRenderer = MakeMap(
- MapEndpointIdentity,
- SelectEndpoint,
-)
+var EndpointRenderer = FilterNonProcspied(SelectEndpoint)
// ProcessRenderer is a Renderer which produces a renderable process
// graph by merging the endpoint graph and the process topology.
@@ -23,10 +18,7 @@ var ProcessRenderer = MakeReduce(
MapEndpoint2Process,
EndpointRenderer,
),
- MakeMap(
- MapProcessIdentity,
- SelectProcess,
- ),
+ SelectProcess,
)
// processWithContainerNameRenderer is a Renderer which produces a process
@@ -35,30 +27,26 @@ type processWithContainerNameRenderer struct {
Renderer
}
-func (r processWithContainerNameRenderer) Render(rpt report.Report) RenderableNodes {
+func (r processWithContainerNameRenderer) Render(rpt report.Report) report.Nodes {
processes := r.Renderer.Render(rpt)
- containers := MakeMap(
- MapContainerIdentity,
- SelectContainer,
- ).Render(rpt)
+ containers := SelectContainer.Render(rpt)
- outputs := RenderableNodes{}
+ outputs := report.Nodes{}
for id, p := range processes {
outputs[id] = p
- pid, ok := p.Node.Latest.Lookup(process.PID)
+ containerID, timestamp, ok := p.Latest.LookupEntry(docker.ContainerID)
if !ok {
continue
}
- containerID, ok := p.Node.Latest.Lookup(docker.ContainerID)
- if !ok {
- continue
- }
- container, ok := containers[MakeContainerID(containerID)]
+ container, ok := containers[report.MakeContainerNodeID(containerID)]
if !ok {
continue
}
output := p.Copy()
- output.LabelMinor = fmt.Sprintf("%s (%s:%s)", report.ExtractHostID(p.Node), container.LabelMajor, pid)
+ output.Latest = output.Latest.Set(docker.ContainerID, timestamp, containerID)
+ if containerName, timestamp, ok := container.Latest.LookupEntry(docker.ContainerName); ok {
+ output.Latest = output.Latest.Set(docker.ContainerName, timestamp, containerName)
+ }
outputs[id] = output
}
return outputs
@@ -71,11 +59,8 @@ var ProcessWithContainerNameRenderer = processWithContainerNameRenderer{ProcessR
// ProcessNameRenderer is a Renderer which produces a renderable process
// name graph by munging the progess graph.
var ProcessNameRenderer = MakeMap(
- MapCountProcessName,
- MakeMap(
- MapProcess2Name,
- ProcessRenderer,
- ),
+ MapProcess2Name,
+ ProcessRenderer,
)
// ContainerRenderer is a Renderer which produces a renderable container
@@ -85,9 +70,9 @@ var ProcessNameRenderer = MakeMap(
// including the ProcessRenderer once.
var ContainerRenderer = MakeReduce(
MakeFilter(
- func(n RenderableNode) bool {
- _, inContainer := n.Node.Latest.Lookup(docker.ContainerID)
- _, isConnected := n.Node.Latest.Lookup(IsConnected)
+ func(n report.Node) bool {
+ _, inContainer := n.Latest.Lookup(docker.ContainerID)
+ _, isConnected := n.Latest.Lookup(IsConnected)
return inContainer || isConnected
},
MakeMap(
@@ -114,10 +99,7 @@ var ContainerRenderer = MakeReduce(
),
)),
- MakeMap(
- MapContainerIdentity,
- SelectContainer,
- ),
+ SelectContainer,
)
type containerWithHostIPsRenderer struct {
@@ -126,22 +108,19 @@ type containerWithHostIPsRenderer struct {
// Render produces a process graph where the ips for host network mode are set
// to the host's IPs.
-func (r containerWithHostIPsRenderer) Render(rpt report.Report) RenderableNodes {
+func (r containerWithHostIPsRenderer) Render(rpt report.Report) report.Nodes {
containers := r.Renderer.Render(rpt)
- hosts := MakeMap(
- MapHostIdentity,
- SelectHost,
- ).Render(rpt)
+ hosts := SelectHost.Render(rpt)
- outputs := RenderableNodes{}
+ outputs := report.Nodes{}
for id, c := range containers {
outputs[id] = c
- networkMode, ok := c.Node.Latest.Lookup(docker.ContainerNetworkMode)
+ networkMode, ok := c.Latest.Lookup(docker.ContainerNetworkMode)
if !ok || networkMode != docker.NetworkModeHost {
continue
}
- h, ok := hosts[MakeHostID(report.ExtractHostID(c.Node))]
+ h, ok := hosts[report.MakeHostNodeID(report.ExtractHostID(c))]
if !ok {
continue
}
@@ -172,26 +151,22 @@ type containerWithImageNameRenderer struct {
// Render produces a process graph where the minor labels contain the
// container name, if found. It also merges the image node metadata into the
// container metadata.
-func (r containerWithImageNameRenderer) Render(rpt report.Report) RenderableNodes {
+func (r containerWithImageNameRenderer) Render(rpt report.Report) report.Nodes {
containers := r.Renderer.Render(rpt)
- images := MakeMap(
- MapContainerImageIdentity,
- SelectContainerImage,
- ).Render(rpt)
+ images := SelectContainerImage.Render(rpt)
- outputs := RenderableNodes{}
+ outputs := report.Nodes{}
for id, c := range containers {
outputs[id] = c
- imageID, ok := c.Node.Latest.Lookup(docker.ImageID)
+ imageID, ok := c.Latest.Lookup(docker.ImageID)
if !ok {
continue
}
- image, ok := images[MakeContainerImageID(imageID)]
+ image, ok := images[report.MakeContainerImageNodeID(imageID)]
if !ok {
continue
}
output := c.Copy()
- output.Rank = ImageNameWithoutVersion(image.LabelMajor)
output.Latest = image.Latest.Merge(c.Latest)
outputs[id] = output
}
@@ -204,31 +179,19 @@ var ContainerWithImageNameRenderer = containerWithImageNameRenderer{ContainerWit
// ContainerImageRenderer is a Renderer which produces a renderable container
// image graph by merging the container graph and the container image topology.
-var ContainerImageRenderer = MakeMap(
- MapCountContainers,
+var ContainerImageRenderer = MakeReduce(
MakeMap(
- MapContainerImage2Name,
- MakeReduce(
- MakeMap(
- MapContainer2ContainerImage,
- ContainerRenderer,
- ),
- MakeMap(
- MapContainerImageIdentity,
- SelectContainerImage,
- ),
- ),
+ MapContainer2ContainerImage,
+ ContainerRenderer,
),
+ SelectContainerImage,
)
// ContainerHostnameRenderer is a Renderer which produces a renderable container
// by hostname graph..
var ContainerHostnameRenderer = MakeMap(
- MapCountContainers,
- MakeMap(
- MapContainer2Hostname,
- ContainerRenderer,
- ),
+ MapContainer2Hostname,
+ ContainerRenderer,
)
// HostRenderer is a Renderer which produces a renderable host
@@ -253,45 +216,27 @@ var HostRenderer = MakeReduce(
// Pods don't have a host id - #1142
// MakeMap(
// MapX2Host,
- // MakeMap(
- // MapPodIdentity,
// SelectPod,
- // ),
// ),
- MakeMap(
- MapHostIdentity,
- SelectHost,
- ),
+ SelectHost,
)
// PodRenderer is a Renderer which produces a renderable kubernetes
// graph by merging the container graph and the pods topology.
-var PodRenderer = MakeMap(
- MapCountContainers,
- MakeReduce(
- MakeMap(
- MapContainer2Pod,
- ContainerRenderer,
- ),
- MakeMap(
- MapPodIdentity,
- SelectPod,
- ),
+var PodRenderer = MakeReduce(
+ MakeMap(
+ MapContainer2Pod,
+ ContainerRenderer,
),
+ SelectPod,
)
// PodServiceRenderer is a Renderer which produces a renderable kubernetes services
// graph by merging the pods graph and the services topology.
-var PodServiceRenderer = MakeMap(
- MapCountPods,
- MakeReduce(
- MakeMap(
- MapPod2Service,
- PodRenderer,
- ),
- MakeMap(
- MapServiceIdentity,
- SelectService,
- ),
+var PodServiceRenderer = MakeReduce(
+ MakeMap(
+ MapPod2Service,
+ PodRenderer,
),
+ SelectService,
)
diff --git a/render/topologies_test.go b/render/topologies_test.go
index 379b4c1acd..be51fb4ab3 100644
--- a/render/topologies_test.go
+++ b/render/topologies_test.go
@@ -15,7 +15,7 @@ import (
func TestEndpointRenderer(t *testing.T) {
have := render.EndpointRenderer.Render(fixture.Report).Prune()
- want := expected.RenderedEndpoints
+ want := expected.RenderedEndpoints.Prune()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -23,7 +23,7 @@ func TestEndpointRenderer(t *testing.T) {
func TestProcessRenderer(t *testing.T) {
have := render.ProcessRenderer.Render(fixture.Report).Prune()
- want := expected.RenderedProcesses
+ want := expected.RenderedProcesses.Prune()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -31,15 +31,15 @@ func TestProcessRenderer(t *testing.T) {
func TestProcessNameRenderer(t *testing.T) {
have := render.ProcessNameRenderer.Render(fixture.Report).Prune()
- want := expected.RenderedProcessNames
+ want := expected.RenderedProcessNames.Prune()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestContainerRenderer(t *testing.T) {
- have := (render.ContainerRenderer.Render(fixture.Report)).Prune()
- want := expected.RenderedContainers
+ have := render.ContainerRenderer.Render(fixture.Report).Prune()
+ want := expected.RenderedContainers.Prune()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -53,8 +53,8 @@ func TestContainerFilterRenderer(t *testing.T) {
docker.LabelPrefix + "works.weave.role": "system",
})
have := render.FilterSystem(render.ContainerRenderer).Render(input).Prune()
- want := expected.RenderedContainers.Copy()
- delete(want, expected.ClientContainerID)
+ want := expected.RenderedContainers.Copy().Prune()
+ delete(want, fixture.ClientContainerNodeID)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -68,11 +68,14 @@ func TestContainerWithHostIPsRenderer(t *testing.T) {
nodes := render.ContainerWithHostIPsRenderer.Render(input)
// Test host network nodes get the host IPs added.
- haveNode, ok := nodes[render.MakeContainerID(fixture.ClientContainerID)]
+ haveNode, ok := nodes[fixture.ClientContainerNodeID]
if !ok {
t.Fatal("Expected output to have the client container node")
}
- have, _ := haveNode.Sets.Lookup(docker.ContainerIPs)
+ have, ok := haveNode.Sets.Lookup(docker.ContainerIPs)
+ if !ok {
+ t.Fatal("Container had no IPs set.")
+ }
want := report.MakeStringSet("10.10.10.0")
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -81,7 +84,7 @@ func TestContainerWithHostIPsRenderer(t *testing.T) {
func TestContainerImageRenderer(t *testing.T) {
have := render.ContainerImageRenderer.Render(fixture.Report).Prune()
- want := expected.RenderedContainerImages
+ want := expected.RenderedContainerImages.Prune()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -89,7 +92,7 @@ func TestContainerImageRenderer(t *testing.T) {
func TestHostRenderer(t *testing.T) {
have := render.HostRenderer.Render(fixture.Report).Prune()
- want := expected.RenderedHosts
+ want := expected.RenderedHosts.Prune()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -97,7 +100,7 @@ func TestHostRenderer(t *testing.T) {
func TestPodRenderer(t *testing.T) {
have := render.PodRenderer.Render(fixture.Report).Prune()
- want := expected.RenderedPods
+ want := expected.RenderedPods.Prune()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -116,9 +119,9 @@ func TestPodFilterRenderer(t *testing.T) {
docker.LabelPrefix + "io.kubernetes.pod.name": "kube-system/foo",
})
have := render.FilterSystem(render.PodRenderer).Render(input).Prune()
- want := expected.RenderedPods.Copy()
- delete(want, expected.ClientPodRenderedID)
- delete(want, expected.ClientContainerID)
+ want := expected.RenderedPods.Copy().Prune()
+ delete(want, fixture.ClientPodNodeID)
+ delete(want, fixture.ClientContainerNodeID)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -126,7 +129,7 @@ func TestPodFilterRenderer(t *testing.T) {
func TestPodServiceRenderer(t *testing.T) {
have := render.PodServiceRenderer.Render(fixture.Report).Prune()
- want := expected.RenderedPodServices
+ want := expected.RenderedPodServices.Prune()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
diff --git a/render/topology_diff_test.go b/render/topology_diff_test.go
deleted file mode 100644
index bbc5234722..0000000000
--- a/render/topology_diff_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package render_test
-
-import (
- "reflect"
- "sort"
- "testing"
-
- "github.com/weaveworks/scope/render"
- "github.com/weaveworks/scope/report"
- "github.com/weaveworks/scope/test"
-)
-
-// ByID is a sort interface for a RenderableNode slice.
-type ByID []render.RenderableNode
-
-func (r ByID) Len() int { return len(r) }
-func (r ByID) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
-func (r ByID) Less(i, j int) bool { return r[i].ID < r[j].ID }
-
-func TestTopoDiff(t *testing.T) {
- nodea := render.RenderableNode{
- ID: "nodea",
- LabelMajor: "Node A",
- LabelMinor: "'ts an a",
- Pseudo: false,
- Node: report.MakeNode().WithAdjacent("nodeb"),
- }
- nodeap := nodea
- nodeap.Adjacency = []string{
- "nodeb",
- "nodeq", // not the same anymore
- }
- nodeb := render.RenderableNode{
- ID: "nodeb",
- LabelMajor: "Node B",
- }
-
- // Helper to make RenderableNode maps.
- nodes := func(ns ...render.RenderableNode) render.RenderableNodes {
- r := render.RenderableNodes{}
- for _, n := range ns {
- r[n.ID] = n
- }
- return r
- }
-
- for _, c := range []struct {
- label string
- have, want render.Diff
- }{
- {
- label: "basecase: empty -> something",
- have: render.TopoDiff(nodes(), nodes(nodea, nodeb)),
- want: render.Diff{
- Add: []render.RenderableNode{nodea, nodeb},
- },
- },
- {
- label: "basecase: something -> empty",
- have: render.TopoDiff(nodes(nodea, nodeb), nodes()),
- want: render.Diff{
- Remove: []string{"nodea", "nodeb"},
- },
- },
- {
- label: "add and remove",
- have: render.TopoDiff(nodes(nodea), nodes(nodeb)),
- want: render.Diff{
- Add: []render.RenderableNode{nodeb},
- Remove: []string{"nodea"},
- },
- },
- {
- label: "no change",
- have: render.TopoDiff(nodes(nodea), nodes(nodea)),
- want: render.Diff{},
- },
- {
- label: "change a single node",
- have: render.TopoDiff(nodes(nodea), nodes(nodeap)),
- want: render.Diff{
- Update: []render.RenderableNode{nodeap},
- },
- },
- } {
- sort.Strings(c.have.Remove)
- sort.Sort(ByID(c.have.Add))
- sort.Sort(ByID(c.have.Update))
- if !reflect.DeepEqual(c.want, c.have) {
- t.Errorf("%s - %s", c.label, test.Diff(c.want, c.have))
- }
- }
-}
diff --git a/report/counters.go b/report/counters.go
index 28ce8e4795..f7c5b7f08a 100644
--- a/report/counters.go
+++ b/report/counters.go
@@ -88,21 +88,28 @@ func (c Counters) Merge(other Counters) Counters {
return Counters{output}
}
-func (c Counters) String() string {
- if c.psMap == nil {
- return "{}"
- }
- keys := []string{}
- for _, k := range c.psMap.Keys() {
- keys = append(keys, k)
+// ForEach calls f for each k/v pair of counters. Keys are iterated in
+// lexicographical order.
+func (c Counters) ForEach(f func(key string, val int)) {
+ if c.psMap != nil {
+ keys := c.psMap.Keys()
+ sort.Strings(keys)
+ for _, key := range keys {
+ if val, ok := c.psMap.Lookup(key); ok {
+ f(key, val.(int))
+ }
+ }
}
- sort.Strings(keys)
+}
+// String serializes Counters into a string.
+func (c Counters) String() string {
buf := bytes.NewBufferString("{")
- for _, key := range keys {
- val, _ := c.psMap.Lookup(key)
- fmt.Fprintf(buf, "%s: %d, ", key, val)
- }
+ prefix := ""
+ c.ForEach(func(k string, v int) {
+ fmt.Fprintf(buf, "%s%s: %d", prefix, k, v)
+ prefix = ", "
+ })
fmt.Fprintf(buf, "}")
return buf.String()
}
diff --git a/report/counters_internal_test.go b/report/counters_internal_test.go
index 7e8f29e5d4..0f912aebbe 100644
--- a/report/counters_internal_test.go
+++ b/report/counters_internal_test.go
@@ -132,3 +132,33 @@ func TestCountersEncoding(t *testing.T) {
}
}
+
+func TestCountersString(t *testing.T) {
+ {
+ var c Counters
+ have := c.String()
+ want := `{}`
+ if want != have {
+ t.Errorf("Expected: %s, Got %s", want, have)
+ }
+ }
+
+ {
+ have := EmptyCounters.String()
+ want := `{}`
+ if want != have {
+ t.Errorf("Expected: %s, Got %s", want, have)
+ }
+ }
+
+ {
+ have := EmptyCounters.
+ Add("foo", 1).
+ Add("bar", 2).String()
+
+ want := `{bar: 2, foo: 1}`
+ if want != have {
+ t.Errorf("Expected: %s, Got %s", want, have)
+ }
+ }
+}
diff --git a/report/latest_map.go b/report/latest_map.go
index 0b407fa971..117c2d7d22 100644
--- a/report/latest_map.go
+++ b/report/latest_map.go
@@ -86,14 +86,21 @@ func (m LatestMap) Merge(other LatestMap) LatestMap {
// Lookup the value for the given key.
func (m LatestMap) Lookup(key string) (string, bool) {
+ v, _, ok := m.LookupEntry(key)
+ return v, ok
+}
+
+// LookupEntry returns the raw entry for the given key.
+func (m LatestMap) LookupEntry(key string) (string, time.Time, bool) {
if m.Map == nil {
- return "", false
+ return "", time.Time{}, false
}
value, ok := m.Map.Lookup(key)
if !ok {
- return "", false
+ return "", time.Time{}, false
}
- return value.(LatestEntry).Value, true
+ e := value.(LatestEntry)
+ return e.Value, e.Timestamp, true
}
// Set the value for the given key.
diff --git a/report/latest_map_internal_test.go b/report/latest_map_internal_test.go
index e515374387..e0c15a50e2 100644
--- a/report/latest_map_internal_test.go
+++ b/report/latest_map_internal_test.go
@@ -29,6 +29,18 @@ func TestLatestMapAdd(t *testing.T) {
})
}
+func TestLatestMapLookupEntry(t *testing.T) {
+ now := time.Now()
+ entry := LatestEntry{Timestamp: now, Value: "Bar"}
+ have := EmptyLatestMap.Set("foo", entry.Timestamp, entry.Value)
+ if got, timestamp, ok := have.LookupEntry("foo"); !ok || got != entry.Value || !timestamp.Equal(entry.Timestamp) {
+ t.Errorf("got: %#v %v != expected %#v", got, timestamp, entry)
+ }
+ if got, timestamp, ok := have.LookupEntry("not found"); ok {
+ t.Errorf("found unexpected entry for %q: %#v %v", "not found", got, timestamp)
+ }
+}
+
func TestLatestMapAddNil(t *testing.T) {
now := time.Now()
have := LatestMap{}.Set("foo", now, "Bar")
diff --git a/report/metrics.go b/report/metrics.go
index 2128ecaaeb..c911b41c8e 100644
--- a/report/metrics.go
+++ b/report/metrics.go
@@ -216,8 +216,8 @@ func (m Metric) LastSample() *Sample {
// WireMetrics is the on-the-wire representation of Metrics.
type WireMetrics struct {
- Samples []Sample `json:"samples"` // On the wire, samples are sorted oldest to newest,
- Min float64 `json:"min"` // the opposite order to how we store them internally.
+ Samples []Sample `json:"samples,omitempty"` // On the wire, samples are sorted oldest to newest,
+ Min float64 `json:"min"` // the opposite order to how we store them internally.
Max float64 `json:"max"`
First string `json:"first,omitempty"`
Last string `json:"last,omitempty"`
diff --git a/report/node.go b/report/node.go
index 043811b363..bf731f63f8 100644
--- a/report/node.go
+++ b/report/node.go
@@ -20,6 +20,7 @@ type Node struct {
Latest LatestMap `json:"latest,omitempty"`
Metrics Metrics `json:"metrics,omitempty"`
Parents Sets `json:"parents,omitempty"`
+ Children NodeSet `json:"children,omitempty"`
}
// MakeNode creates a new Node with no initial metadata.
@@ -152,6 +153,27 @@ func (n Node) WithParents(parents Sets) Node {
return result
}
+// PruneParents returns a fresh copy of n, without any parents.
+func (n Node) PruneParents() Node {
+ result := n.Copy()
+ result.Parents = EmptySets
+ return result
+}
+
+// WithChildren returns a fresh copy of n, with children merged in.
+func (n Node) WithChildren(children NodeSet) Node {
+ result := n.Copy()
+ result.Children = result.Children.Merge(children)
+ return result
+}
+
+// WithChild returns a fresh copy of n, with one child merged in.
+func (n Node) WithChild(child Node) Node {
+ result := n.Copy()
+ result.Children = result.Children.Merge(MakeNodeSet(child))
+ return result
+}
+
// Copy returns a value copy of the Node.
func (n Node) Copy() Node {
cp := MakeNode()
@@ -165,6 +187,7 @@ func (n Node) Copy() Node {
cp.Latest = n.Latest.Copy()
cp.Metrics = n.Metrics.Copy()
cp.Parents = n.Parents.Copy()
+ cp.Children = n.Children.Copy()
return cp
}
@@ -177,6 +200,8 @@ func (n Node) Merge(other Node) Node {
}
if cp.Topology == "" {
cp.Topology = other.Topology
+ } else if other.Topology != "" && cp.Topology != other.Topology {
+ panic("Cannot merge nodes with different topology types: " + cp.Topology + " != " + other.Topology)
}
cp.Counters = cp.Counters.Merge(other.Counters)
cp.Sets = cp.Sets.Merge(other.Sets)
@@ -186,5 +211,21 @@ func (n Node) Merge(other Node) Node {
cp.Latest = cp.Latest.Merge(other.Latest)
cp.Metrics = cp.Metrics.Merge(other.Metrics)
cp.Parents = cp.Parents.Merge(other.Parents)
+ cp.Children = cp.Children.Merge(other.Children)
return cp
}
+
+// Prune returns a copy of the Node with all information not strictly necessary
+// for rendering nodes and edges stripped away. Specifically, that means
+// cutting out parts of the Node.
+func (n Node) Prune() Node {
+ prunedChildren := MakeNodeSet()
+ n.Children.ForEach(func(child Node) {
+ prunedChildren = prunedChildren.Add(child.Prune())
+ })
+ return MakeNode().
+ WithID(n.ID).
+ WithTopology(n.Topology).
+ WithAdjacent(n.Adjacency.Copy()...).
+ WithChildren(prunedChildren)
+}
diff --git a/render/renderable_node_set.go b/report/node_set.go
similarity index 54%
rename from render/renderable_node_set.go
rename to report/node_set.go
index 7cf012610a..4b278d1bf1 100644
--- a/render/renderable_node_set.go
+++ b/report/node_set.go
@@ -1,4 +1,4 @@
-package render
+package report
import (
"bytes"
@@ -13,35 +13,35 @@ import (
"github.com/weaveworks/scope/test/reflect"
)
-// RenderableNodeSet is a set of nodes keyed on (Topology, ID). Clients must use
+// NodeSet is a set of nodes keyed on ID. Clients must use
// the Add method to add nodes
-type RenderableNodeSet struct {
+type NodeSet struct {
psMap ps.Map
}
-// EmptyRenderableNodeSet is the empty set of nodes.
-var EmptyRenderableNodeSet = RenderableNodeSet{ps.NewMap()}
+// EmptyNodeSet is the empty set of nodes.
+var EmptyNodeSet = NodeSet{ps.NewMap()}
-// MakeRenderableNodeSet makes a new RenderableNodeSet with the given nodes.
-func MakeRenderableNodeSet(nodes ...RenderableNode) RenderableNodeSet {
- return EmptyRenderableNodeSet.Add(nodes...)
+// MakeNodeSet makes a new NodeSet with the given nodes.
+func MakeNodeSet(nodes ...Node) NodeSet {
+ return EmptyNodeSet.Add(nodes...)
}
-// 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 {
+// 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 {
result := n.psMap
if result == nil {
result = ps.NewMap()
}
for _, node := range nodes {
- result = result.Set(fmt.Sprintf("%s|%s", node.Topology, node.ID), node)
+ result = result.Set(node.ID, node)
}
- return RenderableNodeSet{result}
+ return NodeSet{result}
}
-// Merge combines the two RenderableNodeSets and returns a new result.
-func (n RenderableNodeSet) Merge(other RenderableNodeSet) RenderableNodeSet {
+// Merge combines the two NodeSets and returns a new result.
+func (n NodeSet) Merge(other NodeSet) NodeSet {
nSize, otherSize := n.Size(), other.Size()
if nSize == 0 {
return other
@@ -56,22 +56,22 @@ func (n RenderableNodeSet) Merge(other RenderableNodeSet) RenderableNodeSet {
iter.ForEach(func(key string, otherVal interface{}) {
result = result.Set(key, otherVal)
})
- return RenderableNodeSet{result}
+ return NodeSet{result}
}
// Lookup the node 'key'
-func (n RenderableNodeSet) Lookup(key string) (RenderableNode, bool) {
+func (n NodeSet) Lookup(key string) (Node, bool) {
if n.psMap != nil {
value, ok := n.psMap.Lookup(key)
if ok {
- return value.(RenderableNode), true
+ return value.(Node), true
}
}
- return RenderableNode{}, false
+ return Node{}, false
}
// Keys is a list of all the keys in this set.
-func (n RenderableNodeSet) Keys() []string {
+func (n NodeSet) Keys() []string {
if n.psMap == nil {
return nil
}
@@ -81,7 +81,7 @@ func (n RenderableNodeSet) Keys() []string {
}
// Size is the number of nodes in the set
-func (n RenderableNodeSet) Size() int {
+func (n NodeSet) Size() int {
if n.psMap == nil {
return 0
}
@@ -90,23 +90,23 @@ func (n RenderableNodeSet) Size() int {
// ForEach executes f for each node in the set. Nodes are traversed in sorted
// order.
-func (n RenderableNodeSet) ForEach(f func(RenderableNode)) {
+func (n NodeSet) ForEach(f func(Node)) {
for _, key := range n.Keys() {
if val, ok := n.psMap.Lookup(key); ok {
- f(val.(RenderableNode))
+ f(val.(Node))
}
}
}
// Copy is a noop
-func (n RenderableNodeSet) Copy() RenderableNodeSet {
+func (n NodeSet) Copy() NodeSet {
return n
}
-func (n RenderableNodeSet) String() string {
+func (n NodeSet) String() string {
keys := []string{}
if n.psMap == nil {
- n = EmptyRenderableNodeSet
+ n = EmptyNodeSet
}
psMap := n.psMap
if psMap == nil {
@@ -126,9 +126,9 @@ func (n RenderableNodeSet) String() string {
return buf.String()
}
-// DeepEqual tests equality with other RenderableNodeSets
-func (n RenderableNodeSet) DeepEqual(i interface{}) bool {
- d, ok := i.(RenderableNodeSet)
+// DeepEqual tests equality with other NodeSets
+func (n NodeSet) DeepEqual(i interface{}) bool {
+ d, ok := i.(NodeSet)
if !ok {
return false
}
@@ -151,20 +151,20 @@ func (n RenderableNodeSet) DeepEqual(i interface{}) bool {
return equal
}
-func (n RenderableNodeSet) toIntermediate() []RenderableNode {
- intermediate := make([]RenderableNode, 0, n.Size())
- n.ForEach(func(node RenderableNode) {
+func (n NodeSet) toIntermediate() []Node {
+ intermediate := make([]Node, 0, n.Size())
+ n.ForEach(func(node Node) {
intermediate = append(intermediate, node)
})
return intermediate
}
-func (n RenderableNodeSet) fromIntermediate(nodes []RenderableNode) RenderableNodeSet {
- return MakeRenderableNodeSet(nodes...)
+func (n NodeSet) fromIntermediate(nodes []Node) NodeSet {
+ return MakeNodeSet(nodes...)
}
// CodecEncodeSelf implements codec.Selfer
-func (n *RenderableNodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
+func (n *NodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
if n.psMap != nil {
encoder.Encode(n.toIntermediate())
} else {
@@ -173,37 +173,37 @@ func (n *RenderableNodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
}
// CodecDecodeSelf implements codec.Selfer
-func (n *RenderableNodeSet) CodecDecodeSelf(decoder *codec.Decoder) {
- in := []RenderableNode{}
+func (n *NodeSet) CodecDecodeSelf(decoder *codec.Decoder) {
+ in := []Node{}
if err := decoder.Decode(&in); err != nil {
return
}
- *n = RenderableNodeSet{}.fromIntermediate(in)
+ *n = NodeSet{}.fromIntermediate(in)
}
// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
-func (RenderableNodeSet) MarshalJSON() ([]byte, error) {
+func (NodeSet) MarshalJSON() ([]byte, error) {
panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead")
}
// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead
-func (*RenderableNodeSet) UnmarshalJSON(b []byte) error {
+func (*NodeSet) UnmarshalJSON(b []byte) error {
panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead")
}
// GobEncode implements gob.Marshaller
-func (n RenderableNodeSet) GobEncode() ([]byte, error) {
+func (n NodeSet) GobEncode() ([]byte, error) {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(n.toIntermediate())
return buf.Bytes(), err
}
// GobDecode implements gob.Unmarshaller
-func (n *RenderableNodeSet) GobDecode(input []byte) error {
- in := []RenderableNode{}
+func (n *NodeSet) GobDecode(input []byte) error {
+ in := []Node{}
if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil {
return err
}
- *n = RenderableNodeSet{}.fromIntermediate(in)
+ *n = NodeSet{}.fromIntermediate(in)
return nil
}
diff --git a/report/node_set_test.go b/report/node_set_test.go
new file mode 100644
index 0000000000..c8746a68fb
--- /dev/null
+++ b/report/node_set_test.go
@@ -0,0 +1,249 @@
+package report_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/weaveworks/scope/report"
+ "github.com/weaveworks/scope/test/reflect"
+)
+
+var benchmarkResult report.NodeSet
+
+type nodeSpec struct {
+ id string
+}
+
+func TestMakeNodeSet(t *testing.T) {
+ for _, testcase := range []struct {
+ inputs []string
+ wants []string
+ }{
+ {inputs: nil, wants: nil},
+ {
+ inputs: []string{"a"},
+ wants: []string{"a"},
+ },
+ {
+ inputs: []string{"b", "c", "a"},
+ wants: []string{"a", "b", "c"},
+ },
+ {
+ inputs: []string{"a", "a", "a"},
+ wants: []string{"a"},
+ },
+ } {
+ var inputs []report.Node
+ for _, id := range testcase.inputs {
+ inputs = append(inputs, report.MakeNode().WithID(id))
+ }
+ set := report.MakeNodeSet(inputs...)
+ var have []string
+ set.ForEach(func(node report.Node) { have = append(have, node.ID) })
+ if !reflect.DeepEqual(testcase.wants, have) {
+ t.Errorf("%#v: want %#v, have %#v", testcase.inputs, testcase.wants, have)
+ }
+ }
+}
+
+func BenchmarkMakeNodeSet(b *testing.B) {
+ nodes := []report.Node{}
+ for i := 1000; i >= 0; i-- {
+ nodes = append(nodes, report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
+ "a": "1",
+ "b": "2",
+ }))
+ }
+ 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/report.go b/report/report.go
index 3878e530ed..7be47d92a2 100644
--- a/report/report.go
+++ b/report/report.go
@@ -148,6 +148,21 @@ func (r Report) Topologies() []Topology {
}
}
+// Topology gets a topology by name
+func (r Report) Topology(name string) (Topology, bool) {
+ t, ok := map[string]Topology{
+ Endpoint: r.Endpoint,
+ Process: r.Process,
+ Container: r.Container,
+ ContainerImage: r.ContainerImage,
+ Pod: r.Pod,
+ Service: r.Service,
+ Host: r.Host,
+ Overlay: r.Overlay,
+ }[name]
+ return t, ok
+}
+
// Validate checks the report for various inconsistencies.
func (r Report) Validate() error {
var errs []string
diff --git a/report/report_test.go b/report/report_test.go
index a36ed02251..7becea8ce2 100644
--- a/report/report_test.go
+++ b/report/report_test.go
@@ -28,6 +28,16 @@ func TestReportTopologies(t *testing.T) {
}
}
+func TestReportTopology(t *testing.T) {
+ r := report.MakeReport()
+ if _, ok := r.Topology(report.Container); !ok {
+ t.Errorf("Expected %s topology to be found", report.Container)
+ }
+ if _, ok := r.Topology("foo"); ok {
+ t.Errorf("Expected %s topology not to be found", "foo")
+ }
+}
+
func TestNode(t *testing.T) {
{
node := report.MakeNode().WithLatests(map[string]string{
diff --git a/report/topology.go b/report/topology.go
index 2cd890db43..46af52b436 100644
--- a/report/topology.go
+++ b/report/topology.go
@@ -78,6 +78,16 @@ func (n Nodes) Merge(other Nodes) Nodes {
return cp
}
+// Prune returns a copy of the Nodes with all information not strictly
+// necessary for rendering nodes and edges in the UI cut away.
+func (n Nodes) Prune() Nodes {
+ result := Nodes{}
+ for id, node := range n {
+ result[id] = node.Prune()
+ }
+ return result
+}
+
// Validate checks the topology for various inconsistencies.
func (t Topology) Validate() error {
errs := []string{}
diff --git a/test/fixture/report_fixture.go b/test/fixture/report_fixture.go
index edcfec2ab9..4eacbc67ab 100644
--- a/test/fixture/report_fixture.go
+++ b/test/fixture/report_fixture.go
@@ -8,7 +8,7 @@ import (
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/probe/process"
- "github.com/weaveworks/scope/render"
+ "github.com/weaveworks/scope/render/detailed"
"github.com/weaveworks/scope/report"
)
@@ -121,7 +121,7 @@ var (
// Node is arbitrary. We're free to put only precisely what we
// care to test into the fixture. Just be sure to include the bits
// that the mapping funcs extract :)
- Client54001NodeID: report.MakeNode().WithLatests(map[string]string{
+ Client54001NodeID: report.MakeNode().WithID(Client54001NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: ClientIP,
endpoint.Port: ClientPort54001,
process.PID: Client1PID,
@@ -132,7 +132,7 @@ var (
EgressByteCount: newu64(100),
}),
- Client54002NodeID: report.MakeNode().WithLatests(map[string]string{
+ Client54002NodeID: report.MakeNode().WithID(Client54002NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: ClientIP,
endpoint.Port: ClientPort54002,
process.PID: Client2PID,
@@ -143,7 +143,7 @@ var (
EgressByteCount: newu64(200),
}),
- Server80NodeID: report.MakeNode().WithLatests(map[string]string{
+ Server80NodeID: report.MakeNode().WithID(Server80NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: ServerIP,
endpoint.Port: ServerPort,
process.PID: ServerPID,
@@ -151,7 +151,7 @@ var (
endpoint.Procspied: True,
}),
- NonContainerNodeID: report.MakeNode().WithLatests(map[string]string{
+ NonContainerNodeID: report.MakeNode().WithID(NonContainerNodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: ServerIP,
endpoint.Port: NonContainerClientPort,
process.PID: NonContainerPID,
@@ -160,7 +160,7 @@ var (
}).WithAdjacent(GoogleEndpointNodeID),
// Probe pseudo nodes
- UnknownClient1NodeID: report.MakeNode().WithLatests(map[string]string{
+ UnknownClient1NodeID: report.MakeNode().WithID(UnknownClient1NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: UnknownClient1IP,
endpoint.Port: UnknownClient1Port,
endpoint.Procspied: True,
@@ -169,7 +169,7 @@ var (
EgressByteCount: newu64(300),
}),
- UnknownClient2NodeID: report.MakeNode().WithLatests(map[string]string{
+ UnknownClient2NodeID: report.MakeNode().WithID(UnknownClient2NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: UnknownClient2IP,
endpoint.Port: UnknownClient2Port,
endpoint.Procspied: True,
@@ -178,7 +178,7 @@ var (
EgressByteCount: newu64(400),
}),
- UnknownClient3NodeID: report.MakeNode().WithLatests(map[string]string{
+ UnknownClient3NodeID: report.MakeNode().WithID(UnknownClient3NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: UnknownClient3IP,
endpoint.Port: UnknownClient3Port,
endpoint.Procspied: True,
@@ -187,7 +187,7 @@ var (
EgressByteCount: newu64(500),
}),
- RandomClientNodeID: report.MakeNode().WithLatests(map[string]string{
+ RandomClientNodeID: report.MakeNode().WithID(RandomClientNodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: RandomClientIP,
endpoint.Port: RandomClientPort,
endpoint.Procspied: True,
@@ -196,7 +196,7 @@ var (
EgressByteCount: newu64(600),
}),
- GoogleEndpointNodeID: report.MakeNode().WithLatests(map[string]string{
+ GoogleEndpointNodeID: report.MakeNode().WithID(GoogleEndpointNodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{
endpoint.Addr: GoogleIP,
endpoint.Port: GooglePort,
endpoint.Procspied: True,
@@ -267,17 +267,17 @@ var (
docker.MemoryUsage: ClientContainerMemoryMetric,
}),
ServerContainerNodeID: report.MakeNodeWith(map[string]string{
- docker.ContainerID: ServerContainerID,
- docker.ContainerName: "task-name-5-server-aceb93e2f2b797caba01",
- docker.ContainerState: docker.StateRunning,
- docker.ImageID: ServerContainerImageID,
- report.HostNodeID: ServerHostNodeID,
- docker.LabelPrefix + render.AmazonECSContainerNameLabel: "server",
- docker.LabelPrefix + "foo1": "bar1",
- docker.LabelPrefix + "foo2": "bar2",
- docker.LabelPrefix + "io.kubernetes.pod.name": ServerPodID,
- kubernetes.PodID: ServerPodID,
- kubernetes.Namespace: KubernetesNamespace,
+ docker.ContainerID: ServerContainerID,
+ docker.ContainerName: "task-name-5-server-aceb93e2f2b797caba01",
+ docker.ContainerState: docker.StateRunning,
+ docker.ImageID: ServerContainerImageID,
+ report.HostNodeID: ServerHostNodeID,
+ docker.LabelPrefix + detailed.AmazonECSContainerNameLabel: "server",
+ docker.LabelPrefix + "foo1": "bar1",
+ docker.LabelPrefix + "foo2": "bar2",
+ docker.LabelPrefix + "io.kubernetes.pod.name": ServerPodID,
+ kubernetes.PodID: ServerPodID,
+ kubernetes.Namespace: KubernetesNamespace,
}).WithID(ServerContainerNodeID).WithTopology(report.Container).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ServerHostNodeID)).
Add("container_image", report.MakeStringSet(ServerContainerImageNodeID)).